From 3ca71520bfb664f0ea809ffdf41505936e4d5e90 Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Thu, 22 Mar 2018 17:57:15 +0100 Subject: [socket-nif] preliminary version of the new socket interface (nififying) --- erts/emulator/Makefile.in | 2 + erts/emulator/nifs/common/socket_nif.c | 1927 ++++++++++++++++++++++++++++++++ erts/preloaded/ebin/init.beam | Bin 51428 -> 51508 bytes erts/preloaded/src/Makefile | 1 + erts/preloaded/src/init.erl | 1 + erts/preloaded/src/socket.erl | 878 +++++++++++++++ 6 files changed, 2809 insertions(+) create mode 100644 erts/emulator/nifs/common/socket_nif.c create mode 100644 erts/preloaded/src/socket.erl diff --git a/erts/emulator/Makefile.in b/erts/emulator/Makefile.in index 054692819e..6ef36ed3e4 100644 --- a/erts/emulator/Makefile.in +++ b/erts/emulator/Makefile.in @@ -640,6 +640,7 @@ PRELOAD_BEAM = $(ERL_TOP)/erts/preloaded/ebin/otp_ring0.beam \ $(ERL_TOP)/erts/preloaded/ebin/prim_eval.beam \ $(ERL_TOP)/erts/preloaded/ebin/prim_inet.beam \ $(ERL_TOP)/erts/preloaded/ebin/prim_file.beam \ + $(ERL_TOP)/erts/preloaded/ebin/socket.beam \ $(ERL_TOP)/erts/preloaded/ebin/zlib.beam \ $(ERL_TOP)/erts/preloaded/ebin/prim_zip.beam \ $(ERL_TOP)/erts/preloaded/ebin/erl_prim_loader.beam \ @@ -881,6 +882,7 @@ NIF_OBJS = \ $(OBJDIR)/erl_tracer_nif.o \ $(OBJDIR)/prim_buffer_nif.o \ $(OBJDIR)/prim_file_nif.o \ + $(OBJDIR)/socket_nif.o \ $(OBJDIR)/zlib_nif.o ifeq ($(TARGET),win32) diff --git a/erts/emulator/nifs/common/socket_nif.c b/erts/emulator/nifs/common/socket_nif.c new file mode 100644 index 0000000000..2c51ff2000 --- /dev/null +++ b/erts/emulator/nifs/common/socket_nif.c @@ -0,0 +1,1927 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 2018-2018. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * %CopyrightEnd% + * + * ---------------------------------------------------------------------- + * Purpose : The NIF (C) part of the socket interface + * ---------------------------------------------------------------------- + * + */ + +/* #include */ +/* #include */ +/* #include */ +/* #include */ +/* #include */ +/* #include */ +/* #include */ +/* #include */ +/* #include */ +/* #include */ +/* #include */ +/* #include */ +/* #include */ +/* #include */ + + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +/* If we HAVE_SCTP_H and Solaris, we need to define the following in + * order to get SCTP working: + */ +#if (defined(HAVE_SCTP_H) && defined(__sun) && defined(__SVR4)) +#define SOLARIS10 1 +/* WARNING: This is not quite correct, it may also be Solaris 11! */ +#define _XPG4_2 +#define __EXTENSIONS__ +#endif + +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_UNISTD_H +#include +#endif +#ifdef HAVE_SYS_UIO_H +#include +#endif + +#ifdef HAVE_NET_IF_DL_H +#include +#endif + +#ifdef HAVE_IFADDRS_H +#include +#endif + +#ifdef HAVE_NETPACKET_PACKET_H +#include +#endif + +#ifdef HAVE_SYS_UN_H +#include +#endif + +/* SENDFILE STUFF HERE IF WE NEED IT... */ + +#if defined(__APPLE__) && defined(__MACH__) && !defined(__DARWIN__) +#define __DARWIN__ 1 +#endif + + +#ifdef __WIN32__ +#define STRNCASECMP strncasecmp +#define INCL_WINSOCK_API_TYPEDEFS 1 + +#ifndef WINDOWS_H_INCLUDES_WINSOCK2_H +#include +#endif +#include +#include /* NEED VC 6.0 or higher */ + +/* Visual studio 2008+: NTDDI_VERSION needs to be set for iphlpapi.h + * to define the right structures. It needs to be set to WINXP (or LONGHORN) + * for IPV6 to work and it's set lower by default, so we need to change it. + */ +#ifdef HAVE_SDKDDKVER_H +# include +# ifdef NTDDI_VERSION +# undef NTDDI_VERSION +# endif +# define NTDDI_VERSION NTDDI_WINXP +#endif +#include + +#undef WANT_NONBLOCKING +#include "sys.h" + +#else /* !__WIN32__ */ + +#include +#ifdef NETDB_H_NEEDS_IN_H +#include +#endif +#include + +#include +#include + +#ifdef DEF_INADDR_LOOPBACK_IN_RPC_TYPES_H +#include +#endif + +#include +#include + +#include +#ifdef HAVE_ARPA_NAMESER_H +#include +#endif + +#ifdef HAVE_SYS_SOCKIO_H +#include +#endif + +#ifdef HAVE_SYS_IOCTL_H +#include +#endif + +#include + +#ifdef HAVE_SCHED_H +#include +#endif + +#ifdef HAVE_SETNS_H +#include +#endif + +#define HAVE_UDP + +#ifndef WANT_NONBLOCKING +#define WANT_NONBLOCKING +#endif +#include "sys.h" + +#endif + +#include + + +/* All platforms fail on malloc errors. */ +#define FATAL_MALLOC + + + +/* *** Boolean *type* stuff... *** */ +typedef unsigned int BOOLEAN_T; +#define TRUE 1 +#define FALSE 0 +#define BOOL2STR(__B__) ((__B__) ? "true" : "false") +#define BOOL2ATOM(__B__) ((__B__) ? atom_true : atom_false) + + +/* Debug stuff... */ +#define SOCKET_NIF_DEBUG_DEFAULT TRUE +#define SOCKET_DEBUG_DEFAULT TRUE + +/* Used in debug printouts */ +#ifdef __WIN32__ +#define LLU "%I64u" +#else +#define LLU "%llu" +#endif +typedef unsigned long long llu_t; + + + +/* Socket stuff */ +#define INVALID_SOCKET -1 +#define INVALID_EVENT -1 +#define SOCKET_ERROR -1 + +#define SOCKET int +#define HANDLE long int + + +/* ============================================================================== + * The IS_SOCKET_ERROR macro below is used for portability reasons. + * While POSIX specifies that errors from socket-related system calls + * should be indicated with a -1 return value, some users have experienced + * non-Windows OS kernels that return negative values other than -1. + * While one can argue that such kernels are technically broken, comparing + * against values less than 0 covers their out-of-spec return values without + * imposing incorrect semantics on systems that manage to correctly return -1 + * for errors, thus increasing Erlang's portability. + */ +#ifdef __WIN32__ +#define IS_SOCKET_ERROR(val) ((val) == SOCKET_ERROR) +#else +#define IS_SOCKET_ERROR(val) ((val) < 0) +#endif + + +/* *** Misc macros and defines *** */ +#define MALLOC(SZ) enif_alloc(SZ) +#define FREE(P) enif_free(P) +#define MKA(E,S) enif_make_atom(E, S) +#define MKT2(E,E1,E2) enif_make_tuple2(E, E1, E2) +#define MCREATE(N) enif_mutex_create(N) + + +/* *** Socket state defs *** */ + +#define SOCKET_FLAG_OPEN 0x0001 +#define SOCKET_FLAG_ACTIVE 0x0004 +#define SOCKET_FLAG_LISTEN 0x0008 +#define SOCKET_FLAG_CON 0x0010 +#define SOCKET_FLAG_ACC 0x0020 +#define SOCKET_FLAG_BUSY 0x0040 +#define SOCKET_FLAG_MULTI_CLIENT 0x0100 /* Multiple clients for one descriptor, * + * i.e. multi-accept */ +#define SOCKET_STATE_CLOSED (0) +#define SOCKET_STATE_OPEN (SOCKET_FLAG_OPEN) +#define SOCKET_STATE_CONNECTED (SOCKET_STATE_OPEN | SOCKET_FLAG_ACTIVE) +#define SOCKET_STATE_LISTENING (SOCKET_STATE_OPEN | SOCKET_FLAG_LISTEN) +#define SOCKET_STATE_CONNECTING (SOCKET_STATE_OPEN | SOCKET_FLAG_CON) +#define SOCKET_STATE_ACCEPTING (SOCKET_STATE_LISTENING | SOCKET_FLAG_ACC) +#define SOCKET_STATE_MULTI_ACCEPTING (SOCKET_STATE_ACCEPTING | SOCKET_FLAG_MULTI_CLIENT) + +#define IS_OPEN(d) \ + (((d)->state & SOCKET_FLAG_OPEN) == SOCKET_FLAG_OPEN) + +#define IS_CONNECTED(d) \ + (((d)->state & SOCKET_STATE_CONNECTED) == SOCKET_STATE_CONNECTED) + +#define IS_CONNECTING(d) \ + (((d)->state & SOCKET_FLAG_CON) == SOCKET_FLAG_CON) + +#define IS_BUSY(d) \ + (((d)->state & SOCKET_FLAG_BUSY) == SOCKET_FLAG_BUSY) + + +/*---------------------------------------------------------------------------- + * Interface constants. + * + * This section must be "identical" to the corresponding socket.hrl + */ + +/* domain */ +#define SOCKET_DOMAIN_LOCAL 1 +#define SOCKET_DOMAIN_INET 2 +#define SOCKET_DOMAIN_INET6 3 + +/* type */ +#define SOCKET_TYPE_STREAM 1 +#define SOCKET_TYPE_DGRAM 2 +#define SOCKET_TYPE_RAW 3 +// #define SOCKET_TYPE_RDM 4 +#define SOCKET_TYPE_SEQPACKET 5 + +/* protocol */ +#define SOCKET_PROTOCOL_IP 1 +#define SOCKET_PROTOCOL_TCP 2 +#define SOCKET_PROTOCOL_UDP 3 +#define SOCKET_PROTOCOL_SCTP 4 + + +/* =================================================================== * + * * + * Various enif macros * + * * + * =================================================================== */ + +#define IS_ATOM(E, TE) enif_is_atom((E), (TE)) +#define IS_BIN(E, TE) enif_is_binary((E), (TE)) +#define IS_NUM(E, TE) enif_is_number((E), (TE)) +#define IS_TUPLE(E, TE) enif_is_tuple((E), (TE)) + +#define GET_ATOM_LEN(E, TE, LP) \ + enif_get_atom_length((E), (TE), (LP), ERL_NIF_LATIN1) +#define GET_ATOM(E, TE, BP, MAX) \ + enif_get_atom((E), (TE), (BP), (MAX), ERL_NIF_LATIN1) +#define GET_BIN(E, TE, BP) enif_inspect_iolist_as_binary((E), (TE), (BP)) +#define GET_INT(E, TE, IP) enif_get_int((E), (TE), (IP)) +#define GET_TUPLE(E, TE, TSZ, TA) enif_get_tuple((E), (TE), (TSZ), (TA)) + + +/* =================================================================== * + * * + * Basic socket operations * + * * + * =================================================================== */ + +#ifdef __WIN32__ + +/* *** Windown macros *** */ + +#define sock_bind(s, addr, len) bind((s), (addr), (len)) +#define sock_name(s, addr, len) getsockname((s), (addr), (len)) +#define sock_open(domain, type, proto) \ + make_noninheritable_handle(socket((domain), (type), (proto))) +#define sock_htons(x) htons((x)) +#define sock_htonl(x) htonl((x)) + +#define sock_errno() WSAGetLastError() +#define sock_create_event(s) WSACreateEvent() + +#define SET_BLOCKING(s) ioctlsocket(s, FIONBIO, &zero_value) +#define SET_NONBLOCKING(s) ioctlsocket(s, FIONBIO, &one_value) +static unsigned long zero_value = 0; +static unsigned long one_value = 1; + +#else /* !__WIN32__ */ + +#define sock_bind(s, addr, len) bind((s), (addr), (len)) +#define sock_name(s, addr, len) getsockname((s), (addr), (len)) +#define sock_open(domain, type, proto) socket((domain), (type), (proto)) +#define sock_htons(x) htons((x)) +#define sock_htonl(x) htonl((x)) + +#define sock_errno() errno +#define sock_create_event(s) (s) /* return file descriptor */ + + +#endif /* !__WIN32__ */ + +#ifdef HAVE_SOCKLEN_T +# define SOCKLEN_T socklen_t +#else +# define SOCKLEN_T size_t +#endif + + +/* The general purpose sockaddr */ +typedef struct { + union { + struct sockaddr sa; + struct sockaddr_in sai; + +#ifdef HAVE_IN6 + struct sockaddr_in6 sai6; +#endif + +#ifdef HAVE_SYS_UN_H + struct sockaddr_un sal; +#endif + + } u; + unsigned int len; +} SocketAddress; + +#define which_address_port(sap) \ + ((((sap)->u.sai.sin_family == AF_INET) || \ + ((sap)->u.sai.sin_family == AF_INET6)) ? \ + ((sap)->u.sai.sin_port) : -1) + + +typedef struct { + // The actual socket + SOCKET sock; + HANDLE event; + + /* "Stuff" about the socket */ + int domain; + int type; + int protocol; + + unsigned int state; + SocketAddress remote; + + + // Controller (owner) process + ErlNifPid ctrlPid; + ErlNifMonitor ctrlMon; + + // Write + ErlNifMutex* writeMtx; + BOOLEAN_T isWritable; + unsigned int writePkgCnt; + unsigned int writeByteCnt; + unsigned int writeTries; + unsigned int writeWaits; + + // Read + ErlNifMutex* readMtx; + BOOLEAN_T isReadable; + ErlNifBinary rbuffer; + unsigned int readCapacity; + unsigned int readPkgCnt; + unsigned int readByteCnt; + unsigned int readTries; + unsigned int readWaits; + + + /* We need to keep track of the "request(s)" we have pending. + * If for instance an accept takes to long, the issuer may + * decide to "cancel" the accept (actually the select). This + * is done by calling the *nif_cancel* function with the request + * ref as argument. + * We also need to keep track of requests so that if a new + * request is issued before the current has completed, we + * reply with e.g. ebusy (or something to that effect). + * Or do we? Can the caller actually do that? + */ + + + /* Misc stuff */ + BOOLEAN_T dbg; +} SocketDescriptor; + + +/* Global stuff (do we really need to "collect" + * these things?) + */ +typedef struct { + /* These are for debugging, testing and the like */ + ERL_NIF_TERM version; + ERL_NIF_TERM buildDate; + BOOLEAN_T dbg; +} SocketData; + + +/* ---------------------------------------------------------------------- + * F o r w a r d s + * ---------------------------------------------------------------------- + */ + +/* THIS IS JUST TEMPORARY */ +extern char* erl_errno_id(int error); + + +static ERL_NIF_TERM nif_is_loaded(ErlNifEnv* env, + int argc, + const ERL_NIF_TERM argv[]); +static ERL_NIF_TERM nif_info(ErlNifEnv* env, + int argc, + const ERL_NIF_TERM argv[]); +/* +This is a *global* debug function (enable or disable for all +operations and all sockets. +static ERL_NIF_TERM nif_debug(ErlNifEnv* env, + int argc, + const ERL_NIF_TERM argv[]); +*/ +static ERL_NIF_TERM nif_open(ErlNifEnv* env, + int argc, + const ERL_NIF_TERM argv[]); +static ERL_NIF_TERM nif_bind(ErlNifEnv* env, + int argc, + const ERL_NIF_TERM argv[]); +static ERL_NIF_TERM nif_connect(ErlNifEnv* env, + int argc, + const ERL_NIF_TERM argv[]); +static ERL_NIF_TERM nif_listen(ErlNifEnv* env, + int argc, + const ERL_NIF_TERM argv[]); +static ERL_NIF_TERM nif_accept(ErlNifEnv* env, + int argc, + const ERL_NIF_TERM argv[]); +static ERL_NIF_TERM nif_accept4(ErlNifEnv* env, + int argc, + const ERL_NIF_TERM argv[]); +static ERL_NIF_TERM nif_send(ErlNifEnv* env, + int argc, + const ERL_NIF_TERM argv[]); +static ERL_NIF_TERM nif_sendto(ErlNifEnv* env, + int argc, + const ERL_NIF_TERM argv[]); +static ERL_NIF_TERM nif_recv(ErlNifEnv* env, + int argc, + const ERL_NIF_TERM argv[]); +static ERL_NIF_TERM nif_recvfrom(ErlNifEnv* env, + int argc, + const ERL_NIF_TERM argv[]); +static ERL_NIF_TERM nif_close(ErlNifEnv* env, + int argc, + const ERL_NIF_TERM argv[]); +static ERL_NIF_TERM nif_setopt(ErlNifEnv* env, + int argc, + const ERL_NIF_TERM argv[]); +static ERL_NIF_TERM nif_getopt(ErlNifEnv* env, + int argc, + const ERL_NIF_TERM argv[]); +static ERL_NIF_TERM nif_cancel(ErlNifEnv* env, + int argc, + const ERL_NIF_TERM argv[]); + + +static char* decode_laddress(ErlNifEnv* env, + int domain, + ERL_NIF_TERM localAddr, + SocketAddress* localP); +static char* decode_laddress_binary(ErlNifEnv* env, + int domain, + ERL_NIF_TERM localAddr, + SocketAddress* localP); +static char* decode_laddress_tuple(ErlNifEnv* env, + int domain, + ERL_NIF_TERM laddr, + SocketAddress* localP); +static char* decode_address_tuple(ErlNifEnv* env, + int domain, + const ERL_NIF_TERM* addrt, + int port, + SocketAddress* localP); +static char* decode_address_atom(ErlNifEnv* env, + int domain, + char* addr, + int addrLen, + int port, + SocketAddress* localP); + +static ERL_NIF_TERM nopen(ErlNifEnv* env, + int domain, + int type, + int protocol, + char* netns); +static ERL_NIF_TERM nbind(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM addr); +static ERL_NIF_TERM nconnect(ErlNifEnv* env, + SocketDescriptor* descP, + const ERL_NIF_TERM* addr, + int port); + +static BOOLEAN_T edomain2domain(int edomain, int* domain); +static BOOLEAN_T etype2type(int etype, int* type); +static BOOLEAN_T eproto2proto(int eproto, int* proto); +#ifdef HAVE_SETNS +static BOOLEAN_T emap2netns(ErlNifEnv* env, ERL_NIF_TERM map, char** netns); +static BOOLEAN_T change_network_namespace(char* netns, int* cns, int* err); +static BOOLEAN_T restore_network_namespace(int ns, SOCKET sock, int* err); +#endif + + +static void socket_dtor(ErlNifEnv* env, void* obj); +static void socket_stop(ErlNifEnv* env, + void* obj, + int fd, + int is_direct_call); +static void socket_down(ErlNifEnv* env, + void* obj, + const ErlNifPid* pid, + const ErlNifMonitor* mon); + +static ERL_NIF_TERM make_ok(ErlNifEnv* env, ERL_NIF_TERM any); +static ERL_NIF_TERM make_error(ErlNifEnv* env, ERL_NIF_TERM reason); +static ERL_NIF_TERM make_error1(ErlNifEnv* env, char* reason); +static ERL_NIF_TERM make_error2(ErlNifEnv* env, int err); + + +static BOOLEAN_T extract_item_on_load(ErlNifEnv* env, + ERL_NIF_TERM map, + ERL_NIF_TERM key, + ERL_NIF_TERM* val); + +static BOOLEAN_T extract_debug_on_load(ErlNifEnv* env, + ERL_NIF_TERM map, + BOOLEAN_T def); + +static int on_load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info); + + +#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 */ + + + +/* *** String constants *** */ +static char str_false[] = "false"; +static char str_error[] = "error"; +static char str_ok[] = "ok"; +static char str_true[] = "true"; +static char str_undefined[] = "undefined"; + +/* (special) error string constants */ +static char str_eagain[] = "eagain"; +static char str_eafnosupport[] = "eafnosupport"; +static char str_einval[] = "einval"; +static char str_eisconn[] = "eisconn"; +static char str_exbadstate[] = "exbadstate"; +static char str_exmon[] = "exmonitor"; // failed monitor +static char str_exself[] = "exself"; // failed self + + +/* *** Atoms *** */ +static ERL_NIF_TERM atom_false; +static ERL_NIF_TERM atom_error; +static ERL_NIF_TERM atom_ok; +static ERL_NIF_TERM atom_true; +static ERL_NIF_TERM atom_undefined; + +static ERL_NIF_TERM atom_eagain; +static ERL_NIF_TERM atom_eafnosupport; +static ERL_NIF_TERM atom_einval; +static ERL_NIF_TERM atom_eisconn; +static ERL_NIF_TERM atom_exbadstate; +static ERL_NIF_TERM atom_exmon; +static ERL_NIF_TERM atom_exself; + + +/* *** Sockets *** */ +static ErlNifResourceType* sockets; +static ErlNifResourceTypeInit socketInit = { + socket_dtor, + socket_stop, + (ErlNifResourceDown*) socket_down +}; + +// Initiated when the nif is loaded +static SocketData socketData; + + +/* ---------------------------------------------------------------------- + * N I F F u n c t i o n s + * ---------------------------------------------------------------------- + * + * Utility and admin functions: + * ---------------------------- + * nif_is_loaded/0 + * nif_info/0 + * (nif_debug/1) + * + * The "proper" socket functions: + * ------------------------------ + * nif_open(Domain, Type, Protocol, Extra) + * nif_bind(Sock, LocalAddr) + * nif_connect(Sock, Addr, Port) + * nif_listen(Sock, Backlog) + * nif_accept(LSock, Ref) + * nif_accept4(LSock, Ref) + * nif_send(Sock, Data, Flags) + * nif_sendto(Sock, Data, Flags, DstAddr, DstPort) + * nif_recv(Sock, Flags) + * nif_recvfrom(Sock, Flags) + * nif_close(Sock) + * + * And some functions to manipulate and retrieve socket options: + * ------------------------------------------------------------- + * nif_setopt/3 + * nif_getopt/2 + * + * And some socket admin functions: + * ------------------------------------------------------------- + * nif_cancel(Sock, Ref) + */ + + +/* ---------------------------------------------------------------------- + * nif_is_loaded + * + * Description: + * This functions only purpose is to return the atom 'true'. + * This will happen *if* the (socket) nif library is loaded. + * If its not, the erlang (nif_is_loaded) will instead return + * 'false'. + */ +static +ERL_NIF_TERM nif_is_loaded(ErlNifEnv* env, + int argc, + const ERL_NIF_TERM argv[]) +{ + if (argc != 0) + return enif_make_badarg(env); + + return atom_true; +} + + +/* ---------------------------------------------------------------------- + * nif_info + * + * Description: + * This is currently just a placeholder... + */ +static +ERL_NIF_TERM nif_info(ErlNifEnv* env, + int argc, + const ERL_NIF_TERM argv[]) +{ + ERL_NIF_TERM info = enif_make_new_map(env); + return info; +} + + +/* ---------------------------------------------------------------------- + * nif_open + * + * Description: + * Create an endpoint for communication. + * + * Arguments: + * Domain + * Type + * Protocol + * Extra - A map with obscure options. + * Currently the only allowed option is netns (network namespace). + * This is *only* allowed on linux! + */ +static +ERL_NIF_TERM nif_open(ErlNifEnv* env, + int argc, + const ERL_NIF_TERM argv[]) +{ + int edomain, etype, eproto; + int domain, type, proto; + char* netns; + ERL_NIF_TERM emap; + + /* Extract arguments and perform preliminary validation */ + + if ((argc != 4) || + !enif_get_int(env, argv[0], &edomain) || + !enif_get_int(env, argv[1], &etype) || + !enif_get_int(env, argv[2], &eproto) || + !enif_is_map(env, argv[3])) { + return enif_make_badarg(env); + } + emap = argv[3]; + + if (!edomain2domain(edomain, &domain)) + return enif_make_badarg(env); + + if (!etype2type(etype, &type)) + return enif_make_badarg(env); + + if (!eproto2proto(eproto, &proto)) + return enif_make_badarg(env); + +#ifdef HAVE_SETNS + /* We *currently* only support one extra option: netns */ + if (!emap2netns(env, emap, &netns)) + return enif_make_badarg(env); +#else + netns = NULL; +#endif + + return nopen(env, domain, type, proto, netns); +} + + +/* nopen - create an endpoint for communication + * + * Assumes the input has been validated. + */ +static +ERL_NIF_TERM nopen(ErlNifEnv* env, + int domain, int type, int protocol, + char* netns) +{ + SocketDescriptor* descP; + ERL_NIF_TERM res; + int save_errno = 0; + SOCKET sock; + HANDLE event; +#ifdef HAVE_SETNS + int current_ns; +#endif + + +#ifdef HAVE_SETNS + if (!change_network_namespace(netns, ¤t_ns, &save_errno)) + return make_error2(env, save_errno); +#endif + + if ((sock = sock_open(domain, type, protocol)) == INVALID_SOCKET) + return make_error2(env, sock_errno()); + +#ifdef HAVE_SETNS + if (!restore_network_namespace(current_ns, sock, &save_errno)) + return make_error2(env, save_errno); + + if (netns != NULL) + FREE(netns); +#endif + + + if ((event = sock_create_event(sock)) == INVALID_EVENT) { + save_errno = sock_errno(); + while ((close(sock) == INVALID_SOCKET) && (sock_errno() == EINTR)); + return make_error2(env, save_errno); + } + + + SET_NONBLOCKING(sock); + + + /* Create and initiate the socket "descriptor" */ + descP = enif_alloc_resource(sockets, sizeof(SocketDescriptor)); + { + char buf[64]; /* Buffer used for building the mutex name */ + sprintf(buf, "socket[w,%d]", sock); + descP->writeMtx = MCREATE(buf); + sprintf(buf, "socket[r,%d]", sock); + descP->readMtx = MCREATE(buf); + } + descP->isWritable = TRUE; + descP->isReadable = TRUE; + descP->writePkgCnt = 0; + descP->writeByteCnt = 0; + descP->writeTries = 0; + descP->writeWaits = 0; + descP->readPkgCnt = 0; + descP->readByteCnt = 0; + descP->readTries = 0; + descP->readWaits = 0; + descP->dbg = SOCKET_DEBUG_DEFAULT; + descP->state = SOCKET_STATE_OPEN; + descP->domain = domain; + descP->type = type; + descP->protocol = protocol; + descP->sock = sock; + descP->event = event; + + res = enif_make_resource(env, descP); + enif_release_resource(descP); // We should really store a reference ... + + + /* Keep track of the creator + * This should not be a problem but just in case + * the *open* function is used with the wrong kind + * of environment... + */ + if (enif_self(env, &descP->ctrlPid) == NULL) + return make_error(env, atom_exself); + + if (enif_monitor_process(env, descP, + &descP->ctrlPid, + &descP->ctrlMon) > 0) + return make_error(env, atom_exmon); + + +#ifdef __WIN32__ + /* What is the point of this? + * And how do we handle it? + * Since the select message will be delivered to the controlling + * process, which has no idea what to do with this... + * + * TODO! + */ + enif_select(env, + event, + ERL_NIF_SELECT_READ, + descP, NULL, atom_undefined); +#endif + + + return make_ok(env, res); +} + + + +#ifdef HAVE_SETNS +/* We should really have another API, so that we can return errno... */ + +/* *** change network namespace *** + * Retreive the current namespace and set the new. + * Return result and previous namespace if successfull. + */ +static +BOOLEAN_T change_network_namespace(char* netns, int* cns, int* err) +{ + int save_errno; + int current_ns = 0; + int new_ns = 0; + + if (netns != NULL) { + current_ns = open("/proc/self/ns/net", O_RDONLY); + if (current_ns == INVALID_SOCKET) { + *cns = current_ns; + *err = sock_errno(); + return FALSE; + } + new_ns = open(netns, O_RDONLY); + if (new_ns == INVALID_SOCKET) { + save_errno = sock_errno(); + while (close(current_ns) == INVALID_SOCKET && + sock_errno() == EINTR); + *cns = -1; + *err = save_errno; + return FALSE; + } + if (setns(new_ns, CLONE_NEWNET) != 0) { + save_errno = sock_errno(); + while ((close(new_ns) == INVALID_SOCKET) && + (sock_errno() == EINTR)); + while ((close(current_ns) == INVALID_SOCKET) && + (sock_errno() == EINTR)); + *cns = -1; + *err = save_errno; + return FALSE; + } else { + while ((close(new_ns) == INVALID_SOCKET) && + (sock_errno() == EINTR)); + *cns = current_ns; + *err = 0; + return TRUE; + } + } else { + *cns = INVALID_SOCKET; + *err = 0; + return TRUE; + } +} + + +/* *** restore network namespace *** + * Restore the previous namespace (see above). + */ +static +BOOLEAN_T restore_network_namespace(int ns, SOCKET sock, int* err) +{ + int save_errno; + if (ns != INVALID_SOCKET) { + if (setns(ns, CLONE_NEWNET) != 0) { + /* XXX Failed to restore network namespace. + * What to do? Tidy up and return an error... + * Note that the thread now might still be in the namespace. + * Can this even happen? Should the emulator be aborted? + */ + if (sock != INVALID_SOCKET) + save_errno = sock_errno(); + while (close(sock) == INVALID_SOCKET && + sock_errno() == EINTR); + sock = INVALID_SOCKET; + while (close(ns) == INVALID_SOCKET && + sock_errno() == EINTR); + *err = save_errno; + return FALSE; + } else { + while (close(ns) == INVALID_SOCKET && + sock_errno() == EINTR); + *err = 0; + return TRUE; + } + } + + *err = 0; + return TRUE; +} +#endif + + + +/* ---------------------------------------------------------------------- + * nif_bind + * + * Description: + * Bind a name to a socket. + * + * Arguments: + * Socket (ref) - Points to the socket descriptor. + * LocalAddr - Local address is either: + * - a binary - when the socket domain = local + * - a tuple of size 2 with + * - first element is the address + * - second element is the port number + * The address can be in the form of either: + * - A tuple of size 4 or 8 (depending on domain) + * - The atom 'any' + * - The atom 'loopback' + */ +static +ERL_NIF_TERM nif_bind(ErlNifEnv* env, + int argc, + const ERL_NIF_TERM argv[]) +{ + SocketDescriptor* descP; + + /* Extract arguments and perform preliminary validation */ + + if ((argc != 2) || + !enif_get_resource(env, argv[0], sockets, (void**) &descP)) { + return enif_make_badarg(env); + } + + /* Basic arg validation: + * - if binary domain must be local (unix) + * - if tuple domain must be either inet or inet6 + */ + if (IS_BIN(env, argv[1]) && (descP->domain != AF_UNIX)) { + return enif_make_badarg(env); + } else if (IS_TUPLE(env, argv[1]) && + (descP->domain != AF_INET) && + (descP->domain != AF_INET6)) { + return enif_make_badarg(env); + } + + + /* Make sure we are ready + * Not sure how this would even happen, but... + */ + if (descP->state != SOCKET_STATE_OPEN) + return make_error(env, atom_exbadstate); + + return nbind(env, descP, argv[1]); +} + + +static +ERL_NIF_TERM nbind(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM addr) +{ + SocketAddress local; + char* err; + int port; + + if ((err = decode_laddress(env, descP->domain, addr, &local)) != NULL) + return make_error1(env, err); + + if (IS_SOCKET_ERROR(sock_bind(descP->sock, + (struct sockaddr*) &local.u, local.len))) { + return make_error2(env, sock_errno()); + } + + port = which_address_port(&local); + if (port == 0) { + SOCKLEN_T addrLen = sizeof(local.u); + sys_memzero((char *) &local.u, addrLen); + sock_name(descP->sock, &local.u.sa, &addrLen); + port = which_address_port(&local); + } else if (port == -1) { + port = 0; + } + + return make_ok(env, enif_make_int(env, port)); + +} + + +/* Decode the (local) address. The format of the address should + * either be an binary (domain = local) or a two tuple (first + * part the actual address tuple and the second part the port + * number) if the domain is either INET or INET6. + */ +static +char* decode_laddress(ErlNifEnv* env, + int domain, + ERL_NIF_TERM localAddr, + SocketAddress* localP) +{ + if (IS_BIN(env, localAddr)) { + return decode_laddress_binary(env, domain, localAddr, localP); + } else if (IS_TUPLE(env, localAddr)) { + return decode_laddress_tuple(env, domain, localAddr, localP); + } else { + return str_einval; + } + +} + + +/* Only for domain = local (unix) + * The erlang interface module ensures that the size of the + * binary is > 0, so we need not do that here. + */ +static +char* decode_laddress_binary(ErlNifEnv* env, + int domain, + ERL_NIF_TERM localAddr, + SocketAddress* localP) +{ +#ifdef HAVE_SYS_UN_H + ErlNifBinary bin; + + if (domain != AF_UNIX) + return str_einval; + + if (!GET_BIN(env, localAddr, &bin)) + return str_einval; + + if ((bin.size + +#ifdef __linux__ + /* Make sure the address gets zero terminated + * except when the first byte is \0 because then it is + * sort of zero terminated although the zero termination + * comes before the address... + * This fix handles Linux's nonportable + * abstract socket address extension. + */ + (bin.data[0] == '\0' ? 0 : 1) +#else + 1 +#endif + ) > sizeof(localP->u.sal.sun_path)) + return str_einval; + + sys_memzero((char*)&localP->u, sizeof(struct sockaddr_un)); + localP->u.sal.sun_family = domain; + sys_memcpy(localP->u.sal.sun_path, bin.data, bin.size); + localP->len = offsetof(struct sockaddr_un, sun_path) + bin.size; +#ifndef NO_SA_LEN + localP->u.sal.sun_len = localP->len; +#endif + return NULL; + +#else // HAVE_SYS_UN_H + return str_eafnosupport; +#endif + +} + + +/* Only for domain = INET and INET6 + * The (local) address here is a two tuple: + * {Addr, Port} + * where + * Addr: + * - a tuple of size 4 (INET) or size 8 (INET6) + * - the atoms 'any' or 'loopback' + * Port: + * - the port number (int) + * + */ +static +char* decode_laddress_tuple(ErlNifEnv* env, + int domain, + ERL_NIF_TERM laddr, + SocketAddress* localP) +{ + const ERL_NIF_TERM* laddrt; + int laddrtSz; + int port; + + /* First, get the tuple and verify its size (2) */ + + if (!GET_TUPLE(env, laddr, &laddrtSz, &laddrt)) + return str_einval; + + if (laddrtSz != 2) + return str_einval; + + /* So far so good. The first element is either a tuple or an atom */ + + if (IS_TUPLE(env, laddrt[0]) && + IS_NUM(env, laddrt[1])) { + + /* We handle two different tuples: + * - size 4 (INET) + * - size 8 (INET6) + */ + + const ERL_NIF_TERM* addrt; + int addrtSz; + + if (!GET_TUPLE(env, laddrt[0], &addrtSz, &addrt)) + return str_einval; // PLACEHOLDER + + if (!GET_INT(env, laddrt[1], &port)) + return str_einval; // PLACEHOLDER + + switch (domain) { + case AF_INET: + if (addrtSz != 4) + return str_einval; + break; + +#if defined(HAVE_IN6) && defined(AF_INET6) + case AF_INET6: + if (addrtSz != 8) + return str_einval; + break; +#endif + + default: + return str_eafnosupport; + break; + } + + return decode_address_tuple(env, + domain, addrt, port, + localP); + + } else if (IS_ATOM(env, laddrt[0]) && + IS_NUM(env, laddrt[1])) { + + /* There are only two atoms we handle: + * - any + * - loopback + */ + + unsigned int len; + char a[16]; // Just in case... + + if (!(GET_ATOM_LEN(env, laddrt[1], &len) && + (len > 0) && + (len <= (sizeof("loopback"))))) + return str_einval; + + if (!GET_ATOM(env, laddrt[0], a, sizeof(a))) + return str_einval; + + return decode_address_atom(env, + domain, a, len, port, + localP); + + } else { + return str_einval; + } + +} + + +/* Decode the 4- or 8-element address tuple + * and initiate the socket address structure. + */ +static +char* decode_address_tuple(ErlNifEnv* env, + int domain, + const ERL_NIF_TERM* addrt, + int port, + SocketAddress* addrP) +{ + + /* We now *know* that the size of the tuple is correct, + * so we don't need to check anything here, just unpack. + */ + + switch (domain) { + case AF_INET: + { + int a, v; + char laddr[4]; + + sys_memzero((char*)&addrP->u, sizeof(struct sockaddr_in)); +#ifndef NO_SA_LEN + addrP->u.sai.sin_len = sizeof(struct sockaddr_in); +#endif + addrP->u.sai.sin_family = domain; + addrP->u.sai.sin_port = sock_htons(port); + for (a = 0; a < 4; a++) { + if (!GET_INT(env, addrt[a], &v)) + return str_einval; + laddr[a] = v; + } + sys_memcpy(&localP->u.sai.sin_addr, &laddr, sizeof(laddr)); + addrP->len = sizeof(struct sockaddr_in); + return NULL; + } + break; + +#if defined(HAVE_IN6) && defined(AF_INET6) + case AF_INET6: + { + int a, v; + char laddr[16]; + + sys_memzero((char*)&addrP->u, sizeof(struct sockaddr_in6)); +#ifndef NO_SA_LEN + addrP->u.sai6.sin6_len = sizeof(struct sockaddr_in6); +#endif + addrP->u.sai6.sin6_family = domain; + addrP->u.sai6.sin6_port = sock_htons(port); + addrP->u.sai6.sin6_flowinfo = 0; + /* The address tuple is of size 8 + * and each element is a two byte integer + */ + for (a = 0; a < 8; a++) { + if (!GET_INT(env, addrt[a], &v)) + return str_einval; + laddr[a*2 ] = ((v >> 8) & 0xFF); + laddr[a*2+1] = (v & 0xFF); + } + sys_memcpy(&localP->u.sai6.sin6_addr, &laddr, sizeof(laddr)); + addrP->len = sizeof(struct sockaddr_in6); + return NULL; + } + break; +#endif + + } /* switch (domain) */ + + return str_eafnosupport; + +} + + +/* Decode the address when its an atom. + * Currently we only accept two atoms: 'any' and 'loopback' + */ +static +char* decode_address_atom(ErlNifEnv* env, + int domain, + char* addr, + int addrLen, + int port, + SocketAddress* localP) +{ + BOOLEAN_T any; + + if (strncmp(addr, "any", addrLen) == 0) { + any = TRUE; + } if (strncmp(addr, "loopback", addrLen) == 0) { + any = FALSE; + } else { + return str_einval; + } + + /* If we get this far, we *know* its either 'any' or 'loopback' */ + + switch (domain) { + case AF_INET: + { + struct in_addr addr; + if (any) { + addr.s_addr = sock_htonl(INADDR_ANY); + } else { + addr.s_addr = sock_htonl(INADDR_LOOPBACK); + } + sys_memzero((char*) localP, sizeof(struct sockaddr_in)); +#ifndef NO_SA_LEN + localP->u.sai.sin_len = sizeof(struct sockaddr_in6); +#endif + localP->u.sai.sin_family = domain; + localP->u.sai.sin_port = sock_htons(port); + localP->u.sai.sin_addr.s_addr = addr.s_addr; + localP->len = sizeof(struct sockaddr_in); + } + break; + +#if defined(HAVE_IN6) && defined(AF_INET6) + case AF_INET6: + { + const struct in6_addr* paddr; + if (any) { + paddr = &in6addr_any; + } else { + paddr = &in6addr_loopback; + } + sys_memzero((char*)localP, sizeof(struct sockaddr_in6)); +#ifndef NO_SA_LEN + localP->u.sai6.sin6_len = sizeof(struct sockaddr_in6); +#endif + localP->u.sai6.sin6_family = domain; + localP->u.sai6.sin6_port = sock_htons(port); + localP->u.sai6.sin6_flowinfo = 0; + localP->u.sai6.sin6_addr = *paddr; + localP->len = sizeof(struct sockaddr_in6); + } + break; +#endif + + default: + return str_einval; + break; + } + + return NULL; +} + + + +/* ---------------------------------------------------------------------- + * nif_connect + * + * Description: + * Initiate a connection on a socket + * + * Arguments: + * Socket (ref) - Points to the socket descriptor. + * Addr - Address of "remote" host. + * A tuple of size 4 or 8 (depending on domain) + * Port - Port number of "remote" host. + */ +static +ERL_NIF_TERM nif_connect(ErlNifEnv* env, + int argc, + const ERL_NIF_TERM argv[]) +{ + SocketDescriptor* descP; + int addrSz; + const ERL_NIF_TERM* addr; + int port; + + /* Extract arguments and perform preliminary validation */ + + if ((argc != 3) || + !enif_get_resource(env, argv[0], sockets, (void**) &descP) || + !GET_TUPLE(env, argv[1], &addrSz, &addr) || + !GET_INT(env, argv[2], &port)) { + return enif_make_badarg(env); + } + + switch (descP->domain) { + case AF_INET: + if (addrSz != 4) + return make_error(env, atom_einval); + break; + +#if defined(HAVE_IN6) && defined(AF_INET6) + case AF_INET6: + if (addrSz != 8) + return make_error(env, atom_einval); + break; +#endif + + default: + return make_error(env, atom_eafnosupport); + break; + } // switch (descP->domain) + + return nconnect(env, descP, addr, port); +} + + +static +ERL_NIF_TERM nconnect(ErlNifEnv* env, + SocketDescriptor* descP, + const ERL_NIF_TERM* addr, + int port) +{ + /* Verify that we are where in the proper state */ + + if (!IS_OPEN(descP)) + return make_error(env, atom_exbadstate); + + if (IS_CONNECTED(descP)) + return make_error(env, atom_eisconn); + + if (IS_CONNECTING(descP)) + return make_error(env, atom_einval); + + if ((xerr = decode_address_tuple(env, + descP->domain, addr, port, + &descP->remote)) != NULL) + return make_error1(env, xerr); + + code = sock_connect(descP->sock, + (struct sockaddr*) &descP->remote.u, descP->remote.len); + + if (IS_SOCKET_ERROR(code) && + ((sock_errno() == ERRNO_BLOCK) || /* Winsock2 */ + (sock_errno() == EINPROGRESS))) { /* Unix & OSE!! */ + ERL_NIF_TERM ref = MKREF(env); + descP->state = INET_STATE_CONNECTING; + enif_select(env, + descP->sock, + (ERL_NIF_SELECT_WRITE), + descP, NULL, ref); + return make_ok(env, ref); + } else if (code == 0) { /* ok we are connected */ + descP->state = SOCKET_STATE_CONNECTED; + /* Do we need to do somthing for "active" mode? + * Is there even such a thing *here*? + */ + return atom_ok; + } else { + return make_error2(env, sock_errno()); + } + +} + + +/* ---------------------------------------------------------------------- + * nif_finalize_connection + * + * Description: + * Make socket ready for input and output. + * + * Arguments: + * Socket (ref) - Points to the socket descriptor. + */ +static +ERL_NIF_TERM nif_finalize_connection(ErlNifEnv* env, + int argc, + const ERL_NIF_TERM argv[]) +{ + SocketDescriptor* descP; + + /* Extract arguments and perform preliminary validation */ + + if ((argc != 1) || + !enif_get_resource(env, argv[0], sockets, (void**) &descP)) { + return enif_make_badarg(env); + } + + return nfinalize_connection(descP); + +} + + +static +ERL_NIF_TERM nfinalize_connection(ErlNifEnv* env, + SocketDescriptor* descP) +{ + int error; + + if (descP->state != INET_STATE_CONNECTING) + return make_error(env, atom_exisnconn); + + if (!is_connected(descP, &error)) + return make_error2(env, error); + + descP->state = INET_STATE_CONNECTED; + + return make_ok1(env); +} + + + +/* ---------------------------------------------------------------------- + * U t i l i t y F u n c t i o n s + * ---------------------------------------------------------------------- + */ + +/* edomain2domain - convert internal (erlang) domain to (proper) domain + * + * Note that only a subset is supported. + */ +static +BOOLEAN_T edomain2domain(int edomain, int* domain) +{ + switch (edomain) { + case SOCKET_DOMAIN_INET: + *domain = AF_INET; + break; + +#if defined(HAVE_IN6) && defined(AF_INET6) + case SOCKET_DOMAIN_INET6: + *domain = AF_INET6; + break; +#endif +#ifdef HAVE_SYS_UN_H + case SOCKET_DOMAIN_LOCAL: + *domain = AF_UNIX; + break; +#endif + + default: + return FALSE; + } + + return TRUE; +} + + + +/* etype2type - convert internal (erlang) type to (proper) type + * + * Note that only a subset is supported. + */ +static +BOOLEAN_T etype2type(int etype, int* type) +{ + switch (etype) { + case SOCKET_TYPE_STREAM: + *type = SOCK_STREAM; + break; + + case SOCKET_TYPE_DGRAM: + *type = SOCK_DGRAM; + break; + + case SOCKET_TYPE_RAW: + *type = SOCK_RAW; + break; + + case SOCKET_TYPE_SEQPACKET: + *type = SOCK_SEQPACKET; + break; + + default: + return FALSE; + } + + return TRUE; +} + + + +/* eproto2proto - convert internal (erlang) protocol to (proper) protocol + * + * Note that only a subset is supported. + */ +static +BOOLEAN_T eproto2proto(int eproto, int* proto) +{ + switch (eproto) { + case SOCKET_PROTOCOL_IP: + *proto = IPPROTO_IP; + break; + + case SOCKET_PROTOCOL_TCP: + *proto = IPPROTO_TCP; + break; + + case SOCKET_PROTOCOL_UDP: + *proto = IPPROTO_UDP; + break; + + case SOCKET_PROTOCOL_SCTP: + *proto = IPPROTO_SCTP; + break; + + default: + return FALSE; + } + + return TRUE; +} + + +#ifdef HAVE_SETNS + /* emap2netns - extract the netns field from the extra map + * + * Note that currently we only support one extra option, the netns. + */ +static +BOOLEAN_T emap2netns(ErlNifEnv* env, ERL_NIF_TERM map, char** netns) +{ + size_t sz; + ERL_NIF_TERM key; + ERL_NIF_TERM value; + unsigned int len; + char* buf; + int written; + + /* Note that its acceptable that the extra map is empty */ + if (!enif_get_map_size(env, map, &sz) || + (sz != 1)) { + *netns = NULL; + return TRUE; + } + + /* The currently only supported extra option is: netns */ + key = enif_make_atom(env, "netns"); + if (!enif_get_map_value(env, map, key, &value)) { + *netns = NULL; // Just in case... + return FALSE; + } + + /* So far so good. The value should be a string, check. */ + if (!enif_is_list(env, value)) { + *netns = NULL; // Just in case... + return FALSE; + } + + if (!enif_get_list_length(env, value, &len)) { + *netns = NULL; // Just in case... + return FALSE; + } + + if ((buf = MALLOC(len+1)) == NULL) { + *netns = NULL; // Just in case... + return FALSE; + } + + written = enif_get_string(env, value, buf, len+1, ERL_NIF_LATIN1); + if (written == (len+1)) { + *netns = buf; + return TRUE; + } else { + *netns = NULL; // Just in case... + return FALSE; + } +} +#endif + + +/* Create an ok two (2) tuple in the form: {ok, Any}. + * The second element (Any) is already in the form of an + * ERL_NIF_TERM so all we have to do is create the tuple. + */ +static +ERL_NIF_TERM make_ok(ErlNifEnv* env, ERL_NIF_TERM any) +{ + return MKT2(env, atom_ok, any); +} + + +/* Create an error two (2) tuple in the form: {error, Reason}. + * The second element (Reason) is already in the form of an + * ERL_NIF_TERM so all we have to do is create the tuple. + */ +static +ERL_NIF_TERM make_error(ErlNifEnv* env, ERL_NIF_TERM reason) +{ + return MKT2(env, atom_error, reason); +} + + +/* Create an error two (2) tuple in the form: {error, Reason}. + * The second element, Reason, is a string to be converted into + * an atom. + */ +static +ERL_NIF_TERM make_error1(ErlNifEnv* env, char* reason) +{ + return make_error(env, MKA(env, reason)); +} + + +/* Create an error two (2) tuple in the form: {error, Reason}. + * The second element, Reason, is the errno value in its + * basic form (integer) which will (eventually) be converted + * into an atom. + */ +static +ERL_NIF_TERM make_error2(ErlNifEnv* env, int err) +{ + return make_error1(env, erl_errno_id(err)); +} + + + +/* ---------------------------------------------------------------------- + * C a l l b a c k F u n c t i o n s + * ---------------------------------------------------------------------- + */ + +/* ========================================================================= + * socket_dtor - Callback function for resource destructor + * + */ +static +void socket_dtor(ErlNifEnv* env, void* obj) +{ +} + + +/* ========================================================================= + * socket_stop - Callback function for resource stop + * + */ +static +void socket_stop(ErlNifEnv* env, void* obj, int fd, int is_direct_call) +{ +} + + +/* ========================================================================= + * socket_down - Callback function for resource down (monitored processes) + * + */ +static +void socket_down(ErlNifEnv* env, + void* obj, + const ErlNifPid* pid, + const ErlNifMonitor* mon) +{ +} + + + +/* ---------------------------------------------------------------------- + * L o a d / u n l o a d / u p g r a d e F u n c t i o n s + * ---------------------------------------------------------------------- + */ + +static +ErlNifFunc socket_funcs[] = +{ + // Some utility functions + {"nif_is_loaded", 0, nif_is_loaded}, + {"nif_info", 0, nif_info}, + // {"nif_debug", 1, nif_debug_}, + + // The proper "socket" interface + {"nif_open", 4, nif_open}, + {"nif_bind", 3, nif_bind}, + {"nif_connect", 3, nif_connect}, + {"nif_listen", 2, nif_listen}, + {"nif_accept", 2, nif_accept}, + {"nif_accept4", 3, nif_accept4}, + {"nif_send", 3, nif_send}, + {"nif_sendto", 5, nif_sendto}, + {"nif_recv", 2, nif_recv}, + {"nif_recvfrom", 2, nif_recvfrom}, + {"nif_close", 1, nif_close}, + {"nif_setopt", 3, nif_setopt}, + {"nif_getopt", 2, nif_getopt}, + + /* "Extra" functions to "complete" the socket interface. + * For instance, the function nif_finalyze_connection + * is called after the connect select has "completed". + */ + {"nif_finalize_connection", 1, nif_finalyze_connection}, + {"nif_cancel", 2, nif_cancel}, +}; + + +static +BOOLEAN_T extract_item_on_load(ErlNifEnv* env, + ERL_NIF_TERM map, + ERL_NIF_TERM key, + ERL_NIF_TERM* val) +{ + if (!enif_is_map(env, map)) + return FALSE; + + if (!enif_get_map_value(env, map, key, val)) + return FALSE; + + return TRUE; +} + +static +BOOLEAN_T extract_debug_on_load(ErlNifEnv* env, ERL_NIF_TERM map, BOOLEAN_T def) +{ + ERL_NIF_TERM dbgKey = enif_make_atom(env, "debug"); + ERL_NIF_TERM dbgVal; + unsigned int len; + char d[16]; // Just in case... + + /* Extra the value of the debug property */ + if (!extract_item_on_load(env, map, dbgKey, &dbgVal)) + return def; + + /* Verify that the value is actually an atom */ + if (!enif_is_atom(env, dbgVal)) + return def; + + /* Verify that the value is of acceptable length */ + if (!(GET_ATOM_LEN(env, dbgVal, &len) && + (len > 0) && + (len <= sizeof("false")))) + return def; + + /* And finally try to extract the value */ + if (!GET_ATOM(env, dbgVal, d, sizeof(d))) + return def; + + if (strncmp(d, "true", len) == 0) + return TRUE; + else + return FALSE; + +} + +/* ======================================================================= + * load_info - A map of misc info (e.g global debug) + */ + +static +int on_load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info) +{ + socketData.dbg = extract_debug_on_load(env, load_info, + SOCKET_NIF_DEBUG_DEFAULT); + + /* Misc atoms */ + // atom_active = MKA(env, str_active); + // atom_active_n = MKA(env, str_active_n); + // atom_active_once = MKA(env, str_active_once); + // atom_binary = MKA(env, str_binary); + // atom_buildDate = MKA(env, str_buildDate); + // atom_closed = MKA(env, str_closed); + atom_error = MKA(env, str_error); + atom_false = MKA(env, str_false); + // atom_list = MKA(env, str_list); + // atom_mode = MKA(env, str_mode); + atom_ok = MKA(env, str_ok); + // atom_once = MKA(env, str_once); + // atom_passive = MKA(env, str_passive); + // atom_receiver = MKA(env, str_receiver); + // atom_tcp_closed = MKA(env, str_tcp_closed); + atom_true = MKA(env, str_true); + atom_undefined = MKA(env, str_undefined); + // atom_version = MKA(env, str_version); + + /* Error codes */ + atom_eagain = MKA(env, str_eagain); + atom_eafnosupport = MKA(env, str_eafnosupport); + atom_einval = MKA(env, str_einval); + atom_eisconn = MKA(env, str_eisconn); + // atom_exalloc = MKA(env, str_exalloc); + atom_exbadstate = MKA(env, str_exbadstate); + // atom_exnotopen = MKA(env, str_exnotopen); + atom_exmon = MKA(env, str_exmon); + atom_exself = MKA(env, str_exself); + // atom_exsend = MKA(env, str_exsend); + // atom_exisnconning = MKA(env, str_exisnconning); + + // For storing "global" things... + // socketData.env = enif_alloc_env(); // We should really check + // socketData.version = MKA(env, ERTS_VERSION); + // socketData.buildDate = MKA(env, ERTS_BUILD_DATE); + + sockets = enif_open_resource_type_x(env, + "sockets", + &socketInit, + ERL_NIF_RT_CREATE, + NULL); + + return !sockets; +} + +ERL_NIF_INIT(socket, socket_funcs, on_load, NULL, NULL, NULL) diff --git a/erts/preloaded/ebin/init.beam b/erts/preloaded/ebin/init.beam index 858a9dc63e..6658ff1a33 100644 Binary files a/erts/preloaded/ebin/init.beam and b/erts/preloaded/ebin/init.beam differ diff --git a/erts/preloaded/src/Makefile b/erts/preloaded/src/Makefile index 4333f6643a..aa9390b038 100644 --- a/erts/preloaded/src/Makefile +++ b/erts/preloaded/src/Makefile @@ -39,6 +39,7 @@ PRE_LOADED_ERL_MODULES = \ prim_buffer \ prim_file \ prim_inet \ + socket \ zlib \ prim_zip \ otp_ring0 \ diff --git a/erts/preloaded/src/init.erl b/erts/preloaded/src/init.erl index 253fcf7a1f..303301cbff 100644 --- a/erts/preloaded/src/init.erl +++ b/erts/preloaded/src/init.erl @@ -202,6 +202,7 @@ boot(BootArgs) -> %% Load the static nifs zlib:on_load(), + socket:on_load(), erl_tracer:on_load(), prim_buffer:on_load(), prim_file:on_load(), diff --git a/erts/preloaded/src/socket.erl b/erts/preloaded/src/socket.erl new file mode 100644 index 0000000000..f8e3d0419a --- /dev/null +++ b/erts/preloaded/src/socket.erl @@ -0,0 +1,878 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2018-2018. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% + +-module(socket). + +%% Administrative and "global" utility functions +-export([ + on_load/0, on_load/1, on_load/2, + info/0 + ]). + +-export([ + open/2, open/3, open/4, + bind/2, bind/3, + connect/3, + listen/1, listen/2, + accept/1, accept/2, + accept4/1, accept4/2, accept4/3, + + send/2, send/3, sendto/5, + recv/1, recv/2, recvfrom/1, recvfrom/2, + + close/1, + + setopt/3, + getopt/2, + formated_timestamp/0 + ]). + +-export_type([ + domain/0, + type/0, + protocol/0, + socket/0, + + ip_address/0, + ip4_address/0, + ip6_address/0, + port_number/0, + + accept_flags/0, + accept_flag/0, + + send_flags/0, + send_flag/0 + ]). + + +%% We support only a subset of all domains. +-type domain() :: local | inet | inet6. + +%% We support only a subset of all types. +-type type() :: stream | dgram | raw | seqpacket. + +%% We support only a subset of all protocols: +-type protocol() :: ip | tcp | udp | sctp. + +-type ip_address() :: ip4_address() | ip6_address(). + +-type ip4_address() :: {0..255, 0..255, 0..255, 0..255}. + +-type ip6_address() :: + {0..65535, + 0..65535, + 0..65535, + 0..65535, + 0..65535, + 0..65535, + 0..65535, + 0..65535}. + +-type port_number() :: 0..65535. + +-type socket_info() :: map(). +%% -record(socket, {info :: socket_info, +%% ref :: reference()}). +-opaque socket() :: {socket, socket_info(), reference()}. +%% -opaque socket() :: #socket{}. + +-type accept_flags() :: [accept_flag()]. +-type accept_flag() :: nonblock | cloexec. + +-type send_flags() :: [send_flag()]. +-type send_flag() :: confirm | + dontroute | + dontwait | + eor | + more | + nosignal | + oob. + +-type recv_flags() :: [recv_flag()]. +-type recv_flag() :: cmsg_cloexec | + dontwait | + errqueue | + oob | + peek | + trunc | + waitall. + +-type setopt_key() :: foo. +-type getopt_key() :: foo. + +-define(SOCKET_DOMAIN_LOCAL, 1). +-define(SOCKET_DOMAIN_UNIX, ?SOCKET_DOMAIN_LOCAL). +-define(SOCKET_DOMAIN_INET, 2). +-define(SOCKET_DOMAIN_INET6, 3). + +-define(SOCKET_TYPE_STREAM, 1). +-define(SOCKET_TYPE_DGRAM, 2). +-define(SOCKET_TYPE_RAW, 3). +%% -define(SOCKET_TYPE_RDM, 4). +-define(SOCKET_TYPE_SEQPACKET, 5). + +-define(SOCKET_PROTOCOL_IP, 1). +-define(SOCKET_PROTOCOL_TCP, 2). +-define(SOCKET_PROTOCOL_UDP, 3). +-define(SOCKET_PROTOCOL_SCTP, 4). + +-define(SOCKET_LISTEN_BACKLOG_DEFAULT, 5). + +%% Bit numbers (from right). +-define(SOCKET_ACCEPT_FLAG_NONBLOCK, 0). +-define(SOCKET_ACCEPT_FLAG_CLOEXEC, 1). + +-define(SOCKET_ACCEPT_FLAGS_DEFAULT, []). + +-define(SOCKET_SEND_FLAG_CONFIRM, 0). +-define(SOCKET_SEND_FLAG_DONTROUTE, 1). +-define(SOCKET_SEND_FLAG_DONTWAIT, 2). +-define(SOCKET_SEND_FLAG_EOR, 3). +-define(SOCKET_SEND_FLAG_MORE, 4). +-define(SOCKET_SEND_FLAG_NOSIGNAL, 5). +-define(SOCKET_SEND_FLAG_OOB, 6). + +-define(SOCKET_SEND_FLAGS_DEFAULT, []). + +-define(SOCKET_RECV_FLAG_CMSG_CLOEXEC, 0). +-define(SOCKET_RECV_FLAG_DONTWAIT, 1). +-define(SOCKET_RECV_FLAG_ERRQUEUE, 2). +-define(SOCKET_RECV_FLAG_OOB, 3). +-define(SOCKET_RECV_FLAG_PEEK, 4). +-define(SOCKET_RECV_FLAG_TRUNC, 5). +-define(SOCKET_RECV_FLAG_WAITALL, 6). + +-define(SOCKET_RECV_FLAGS_DEFAULT, []). + +-define(SOCKET_SETOPT_KEY_DEBUG, 0). + +-define(SOCKET_GETOPT_KEY_DEBUG, ?SOCKET_SETOPT_KEY_DEBUG). + + + +%% =========================================================================== +%% +%% Administrative and utility API +%% +%% =========================================================================== + +-spec on_load() -> ok. + +%% Should we require that the Extra arg is a map? +on_load() -> + on_load(#{}). + +-spec on_load(Extra) -> ok when + Extra :: maps:map(). + +on_load(Extra) when is_map(Extra) -> + on_load(atom_to_list(?MODULE), Extra). + +-spec on_load(Path, Extra) -> ok when + Path :: string(), + Extra :: maps:map(). + +on_load(Path, Extra) when is_list(Path) andalso is_map(Extra) -> + on_load(nif_is_loaded(), Path, Extra). + +on_load(true, _Path, _Extra) -> + ok; +on_load(false, Path, Extra) -> + ok = erlang:load_nif(Path, maps:put(timestamp, formated_timestamp(), Extra)). + + + +-spec info() -> list(). + +info() -> + nif_info(). + + + +%% =========================================================================== +%% +%% The proper socket API +%% +%% =========================================================================== + +%% open - create an endpoint for communication + +open(Domain, Type) -> + open(Domain, Type, null). + +-spec open(Domain, Type, Protocol) -> {ok, Socket} | {error, Reason} when + Domain :: domain(), + Type :: type(), + Protocol :: null | protocol(), + Socket :: socket(), + Reason :: term(). + +open(Domain, Type, Protocol) -> + open(Domain, Type, Protocol, #{}). + +-spec open(Domain, Type, Protocol, Extra) -> {ok, Socket} | {error, Reason} when + Domain :: domain(), + Type :: type(), + Protocol :: null | protocol(), + Extra :: map(), + Socket :: socket(), + Reason :: term(). + +open(Domain, Type, Protocol0, Extra) when is_map(Extra) -> + try + begin + Protocol = default_protocol(Protocol0, Type), + EDomain = enc_domain(Domain), + EType = enc_type(Domain, Type), + EProtocol = enc_protocol(Type, Protocol), + case nif_open(EDomain, EType, EProtocol, Extra) of + {ok, SockRef} -> + SocketInfo = #{domain => Domain, + type => Type, + protocol => Protocol}, + Socket = {socket, SocketInfo, SockRef}, + {ok, Socket}; + {error, _} = ERROR -> + ERROR + end + end + catch + throw:T -> + T; + error:Reason -> + {error, Reason} + end. + +%% Note that this is just a convenience function for when the protocol was +%% not specified. If its actually specied, then that will be selected. +%% Also, this only works for the some of the type's (stream, dgram and +%% seqpacket). +default_protocol(null, stream) -> tcp; +default_protocol(null, dgram) -> udp; +default_protocol(null, seqpacket) -> sctp; +default_protocol(null, Type) -> throw({error, {no_default_protocol, Type}}); +default_protocol(Protocol, _) -> Protocol. + + +%% bind - bind a name to a socket + +-spec bind(Socket, FileOrAddr) -> ok | {error, Reason} when + Socket :: socket(), + FileOrAddr :: binary() | string() | ip_address() | any | loopback, + Reason :: term(). + +bind(Socket, File) when is_binary(File) -> + if + byte_size(File) =:= 0 -> + {error, einval}; + byte_size(File) =< 255 -> + nif_bind(Socket, File); + true -> + {error, einval} + end; +bind(Socket, File) when is_list(File) andalso (File =/= []) -> + Bin = unicode:characters_to_binary(File, file:native_name_encoding()), + if + byte_size(Bin) =< 255 -> + nif_bind(Socket, Bin); + true -> + {error, einval} + end; +bind(Socket, Addr) when is_tuple(Addr) orelse + (Addr =:= any) orelse + (Addr =:= loopback) -> + bind(Socket, Addr, 0). + + +-spec bind(Socket, Address, Port) -> ok | {ok, NewPort} | {error, Reason} when + Socket :: socket(), + Address :: ip_address() | any | loopback, + Port :: port_number(), + NewPort :: port_number(), + Reason :: term(). + +%% Shall we keep info about domain so that we can verify address? +bind({socket, _, SockRef}, Addr, Port) + when (is_tuple(Addr) andalso + ((size(Addr) =:= 4) orelse (size(Addr) =:= 8))) orelse + ((Addr =:= any) orelse (Addr =:= loopback)) andalso + (is_integer(Port) andalso (Port >= 0)) -> + nif_bind(SockRef, {Addr, Port}). + + +%% connect - initiate a connection on a socket + +-spec connect(Socket, Addr, Port) -> ok | {error, Reason} when + Socket :: socket(), + Addr :: ip_address(), + Port :: port_number(), + Reason :: term(). + +connect(Socket, Addr, Port) -> + connect(Socket, Addr, Port, infinity). + +-spec connect(Socket, Addr, Port, Timeout) -> ok | {error, Reason} when + Socket :: socket(), + Addr :: ip_address(), + Port :: port_number(), + Timeout :: timeout(), + Reason :: term(). + +connect(_Socket, _Addr, _Port, Timeout) + when (is_integer(Timeout) andalso (Timeout =< 0)) -> + {error, timeout}; +connect({socket, _, SockRef}, Addr, Port, Timeout) + when (is_tuple(Addr) andalso + ((size(Addr) =:= 4) orelse (size(Addr) =:= 8))) andalso + (is_integer(Port) andalso (Port >= 0)) andalso + ((Timeout =:= infinity) orelse is_integer(Timeout)) -> + TS = timestamp(Timeout), + case nif_connect(SockRef, Addr, Port) of + ok -> + %% Connected! + ok; + {ok, Ref} -> + %% Connecting... + NewTimeout = next_timeout(TS, Timeout), + receive + {select, SockRef, Ref, ready_output} -> + nif_finalize_connection(SockRef) + after NewTimeout -> + nif_cancel(SockRef, Ref), + {error, timeout} + end; + {error, _} = ERROR -> + ERROR + end. + + +%% listen - listen for connections on a socket + +-spec listen(Socket, Backlog) -> ok | {error, Reason} when + Socket :: socket(), + Backlog :: pos_integer(), + Reason :: term(). + +listen(Socket) -> + listen(Socket, ?SOCKET_LISTEN_BACKLOG_DEFAULT). + +listen({socket, _, SockRef}, Backlog) + when (is_integer(Backlog) andalso (Backlog >= 0)) orelse is_boolean(Backlog) -> + EBacklog = enc_backlog(Backlog), + nif_listen(SockRef, EBacklog). + + + +%% accept, accept4 - accept a connection on a socket + +-spec accept(LSocket, Timeout) -> {ok, Socket} | {error, Reason} when + LSocket :: socket(), + Timeout :: timeout(), + Socket :: socket(), + Reason :: term(). + +accept(Socket) -> + accept(Socket, infinity). + +%% Do we really need this optimization? +accept(_, Timeout) when is_integer(Timeout) andalso (Timeout < 0) -> + {error, timeout}; +accept({socket, _, LSockRef}, Timeout) + when is_integer(Timeout) orelse (Timeout =:= infinity) -> + Ref = make_ref(), + do_accept(LSockRef, Ref, Timeout). + +do_accept(_, _Ref, Timeout) when is_integer(Timeout) andalso (Timeout < 0) -> + {error, timeout}; +do_accept(LSockRef, Ref, Timeout) -> + TS = timestamp(Timeout), + case nif_accept(LSockRef, Ref) of + {ok, SockRef} -> + Socket = {socket, foo, SockRef}, + {ok, Socket}; + {error, eagain} -> + receive + {select, LSockRef, Ref, ready_input} -> + do_accept(LSockRef, make_ref(), next_timeout(TS, Timeout)) + after Timeout -> + %% Shall we cancel the select? How? + nif_cancel(LSockRef, Ref), + flush_select_msgs(LSockRef, Ref), + {error, timeout} + end + end. + +flush_select_msgs(LSRef, Ref) -> + receive + {select, LSRef, Ref, _} -> + flush_select_msgs(LSRef, Ref) + after 0 -> + ok + end. + + +-spec accept4(LSocket, Flags, Timeout) -> {ok, Socket} | {error, Reason} when + LSocket :: socket(), + Flags :: accept_flags(), + Timeout :: timeout(), + Socket :: socket(), + Reason :: term(). + +accept4(LSocket) -> + accept4(LSocket, ?SOCKET_ACCEPT_FLAGS_DEFAULT). + +accept4(LSocket, Flags) when is_list(Flags) -> + accept4(LSocket, Flags, infinity); +accept4(LSocket, Timeout) -> + accept4(LSocket, ?SOCKET_ACCEPT_FLAGS_DEFAULT, Timeout). + +%% Do we really need this optimization? +accept4(_LSocket, _Flags, Timeout) when is_integer(Timeout) andalso (Timeout < 0) -> + {error, timeout}; +accept4({socket, _, LSockRef}, Flags, Timeout) + when is_list(Flags) andalso + (is_integer(Timeout) orelse (Timeout =:= infinity)) -> + EFlags = enc_accept_flags(Flags), + Ref = make_ref(), + do_accept4(LSockRef, EFlags, Ref, Timeout). + +do_accept4(_, _EFlags, _Ref, Timeout) + when is_integer(Timeout) andalso (Timeout < 0) -> + {error, timeout}; +do_accept4(LSockRef, EFlags, Ref, Timeout) -> + TS = timestamp(Timeout), + case nif_accept4(LSockRef, EFlags, Ref) of + {ok, SockRef} -> + Socket = {socket, foo, SockRef}, + {ok, Socket}; + {error, eagain} -> + receive + {select, LSockRef, Ref, ready_input} -> + do_accept4(LSockRef, EFlags, make_ref(), + next_timeout(TS, Timeout)) + after Timeout -> + %% Shall we cancel the select? How? + nif_cancel(LSockRef, Ref), + flush_select_msgs(LSockRef, Ref), + {error, timeout} + end + end. + + + +%% send, sendto, sendmsg - send a message on a socket + +-spec send(Socket, Data, Flags) -> ok | {error, Reason} when + Socket :: socket(), + Data :: binary(), + Flags :: send_flags(), + Reason :: term(). + +send(Socket, Data) -> + send(Socket, Data, ?SOCKET_SEND_FLAGS_DEFAULT). + +send({socket, _, SockRef}, Data, Flags) + when is_binary(Data) andalso is_list(Flags) -> + EFlags = enc_send_flags(Flags), + nif_send(SockRef, Data, EFlags). + + +-spec sendto(Socket, Data, Flags, DestAddr, Port) -> ok | {error, Reason} when + Socket :: socket(), + Data :: binary(), + Flags :: send_flags(), + DestAddr :: ip_address(), + Port :: port_number(), + Reason :: term(). + +sendto({socket, _, SockRef}, Data, Flags, DestAddr, DestPort) + when is_binary(Data) andalso + is_list(Flags) andalso + (is_tuple(DestAddr) andalso + ((size(DestAddr) =:= 4) orelse + (size(DestAddr) =:= 8))) andalso + (is_integer(DestPort) andalso (DestPort >= 0)) -> + EFlags = enc_send_flags(Flags), + nif_sendto(SockRef, Data, EFlags, DestAddr, DestPort). + + +%% -spec sendmsg(Socket, MsgHdr, Flags) -> ok | {error, Reason} when +%% Socket :: socket(), +%% MsgHdr :: msg_header(), +%% Flags :: send_flags(), +%% Reason :: term(). + + + +%% recv, recvfrom, recvmsg - receive a message from a socket + +-spec recv(Socket, Flags) -> {ok, Data} | {error, Reason} when + Socket :: socket(), + Flags :: recv_flags(), + Data :: binary(), + Reason :: term(). + +recv(Socket) -> + recv(Socket, ?SOCKET_RECV_FLAGS_DEFAULT). + +%% WE "may" need a timeout option here... +recv({socket, _, SockRef}, Flags) when is_list(Flags) -> + EFlags = enc_recv_flags(Flags), + nif_recv(SockRef, EFlags). + + +-spec recvfrom(Socket, Flags) -> {ok, Data, SrcAddr, SrcPort} | {error, Reason} when + Socket :: socket(), + Flags :: recv_flags(), + Data :: binary(), + SrcAddr :: ip_address(), + SrcPort :: port_number(), + Reason :: term(). + +recvfrom(Socket) -> + recvfrom(Socket, ?SOCKET_RECV_FLAGS_DEFAULT). + +recvfrom({socket, _, SockRef}, Flags) when is_list(Flags) -> + EFlags = enc_recv_flags(Flags), + nif_recvfrom(SockRef, EFlags). + +%% -spec recvmsg(Socket, [out] MsgHdr, Flags) -> {ok, Data} | {error, Reason} when +%% Socket :: socket(), +%% MsgHdr :: msg_header(), +%% Flags :: recv_flags(), +%% Data :: binary(), +%% Reason :: term(). + + + +%% close - close a file descriptor + +-spec close(Socket) -> ok | {error, Reason} when + Socket :: socket(), + Reason :: term(). + +close({socket, _, SockRef}) -> + nif_close(SockRef). + + + +%% setopt - manipulate individual properties of a socket +%% +%% What properties are valid depend on what kind of socket it is +%% (domain, type and protocol) +%% If its an "invalid" option (or value), we should not crash but return some +%% useful error... +%% + +-spec setopt(Socket, Key, Value) -> ok | {error, Reason} when + Socket :: socket(), + Key :: setopt_key(), + Value :: term(), + Reason :: term(). + +setopt({socket, Info, SockRef}, Key, Value) -> + try + begin + Domain = maps:get(domain, Info), + Type = maps:get(type, Info), + Protocol = maps:get(protocol, Info), + EKey = enc_setopt_key(Key, Domain, Type, Protocol), + EVal = enc_setopt_value(Key, Value, Domain, Type, Protocol), + nif_setopt(SockRef, EKey, EVal) + end + catch + throw:T -> + T; + error:Reason -> + {error, Reason} % Process more? + end. + + +%% getopt - retrieve individual properties of a socket +%% +%% What properties are valid depend on what kind of socket it is +%% (domain, type and protocol). +%% If its an "invalid" option, we should not crash but return some +%% useful error... +%% + +-spec getopt(Socket, Key) -> {ok, Value} | {error, Reason} when + Socket :: socket(), + Key :: getopt_key(), + Value :: term(), + Reason :: term(). + +getopt({socket, Info, SockRef}, Key) -> + try + begin + Domain = maps:get(domain, Info), + Type = maps:get(type, Info), + Protocol = maps:get(protocol, Info), + EKey = enc_getopt_key(Key, Domain, Type, Protocol), + %% We may need to decode the value (for the same reason + %% we needed to encode the value for setopt). + case nif_getopt(SockRef, EKey) of + {ok, EVal} -> + Val = dec_getopt_value(Key, EVal, Domain, Type, Protocol), + {ok, Val}; + {error, _} = ERROR -> + ERROR + end + end + catch + throw:T -> + T; + error:Reason -> + {error, Reason} % Process more? + end. + + + +%% =========================================================================== +%% +%% Encode / decode +%% +%% =========================================================================== + +-spec enc_domain(Domain) -> non_neg_integer() when + Domain :: domain(). + +enc_domain(local) -> ?SOCKET_DOMAIN_LOCAL; +enc_domain(inet) -> ?SOCKET_DOMAIN_INET; +enc_domain(inet6) -> ?SOCKET_DOMAIN_INET6; +enc_domain(Domain) -> throw({error, {invalid_domain, Domain}}). + +-spec enc_type(Domain, Type) -> non_neg_integer() when + Domain :: domain(), + Type :: type(). + +%% What combos are valid? +enc_type(_, stream) -> ?SOCKET_TYPE_STREAM; +enc_type(_, dgram) -> ?SOCKET_TYPE_DGRAM; +enc_type(_, raw) -> ?SOCKET_TYPE_RAW; +enc_type(_, seqpacket) -> ?SOCKET_TYPE_SEQPACKET; +enc_type(_, Type) -> throw({error, {invalid_type, Type}}). + +-spec enc_protocol(Type, Protocol) -> non_neg_integer() when + Type :: type(), + Protocol :: protocol(). + +enc_protocol(dgram, ip) -> ?SOCKET_PROTOCOL_IP; +enc_protocol(stream, tcp) -> ?SOCKET_PROTOCOL_TCP; +enc_protocol(dgram, udp) -> ?SOCKET_PROTOCOL_UDP; +enc_protocol(seqpacket, sctp) -> ?SOCKET_PROTOCOL_SCTP; +enc_protocol(Type, Proto) -> throw({error, {invalid_protocol, {Type, Proto}}}). + + +-spec enc_backlog(Backlog) -> non_neg_integer() when + Backlog :: non_neg_integer() | boolean(). + +enc_backlog(true) -> + ?SOCKET_LISTEN_BACKLOG_DEFAULT; +enc_backlog(false) -> + 0; +enc_backlog(Backlog) when is_integer(Backlog) andalso (Backlog >= 0) -> + Backlog. + +-spec enc_accept_flags(Flags) -> non_neg_integer() when + Flags :: accept_flags(). + +enc_accept_flags(Flags) -> + EFlags = [{nonblock, ?SOCKET_ACCEPT_FLAG_NONBLOCK}, + {cloexec, ?SOCKET_ACCEPT_FLAG_CLOEXEC}], + enc_flags(Flags, EFlags). + + +-spec enc_send_flags(Flags) -> non_neg_integer() when + Flags :: send_flags(). + +enc_send_flags(Flags) -> + EFlags = [{confirm, ?SOCKET_SEND_FLAG_CONFIRM}, + {dontroute, ?SOCKET_SEND_FLAG_DONTROUTE}, + {dontwait, ?SOCKET_SEND_FLAG_DONTWAIT}, + {eor, ?SOCKET_SEND_FLAG_EOR}, + {more, ?SOCKET_SEND_FLAG_MORE}, + {nosignal, ?SOCKET_SEND_FLAG_NOSIGNAL}, + {oob, ?SOCKET_SEND_FLAG_OOB}], + enc_flags(Flags, EFlags). + +-spec enc_recv_flags(Flags) -> non_neg_integer() when + Flags :: recv_flags(). + +enc_recv_flags(Flags) -> + EFlags = [{cmsg_cloexec, ?SOCKET_RECV_FLAG_CMSG_CLOEXEC}, + {dontwait, ?SOCKET_RECV_FLAG_DONTWAIT}, + {errqueue, ?SOCKET_RECV_FLAG_ERRQUEUE}, + {oob, ?SOCKET_RECV_FLAG_OOB}, + {peek, ?SOCKET_RECV_FLAG_PEEK}, + {trunc, ?SOCKET_RECV_FLAG_TRUNC}, + {waitall, ?SOCKET_RECV_FLAG_WAITALL}], + enc_flags(Flags, EFlags). + + +enc_flags([], _) -> + 0; +enc_flags(Flags, EFlags) -> + F = fun(Flag, Acc) -> + case lists:keysearch(Flag, 1, EFlags) of + {value, {Flag, EFlag}} -> + Acc bor (1 bsl EFlag); + false -> + throw({error, {unknown_flag, Flag}}) + end + end, + lists:foldl(F, 0, Flags). + +%% We should ...really... do something with the domain, type and protocol args... +enc_setopt_key(debug, _, _, _) -> + ?SOCKET_SETOPT_KEY_DEBUG. + +%% We should ...really... do something with the domain, type and protocol args... +enc_setopt_value(debug, V, _, _, _) when is_boolean(V) -> + V. + + +%% We should ...really... do something with the domain, type and protocol args... +enc_getopt_key(debug, _, _, _) -> + ?SOCKET_GETOPT_KEY_DEBUG. + +%% We should ...really... do something with the domain, type and protocol args... +dec_getopt_value(debug, B, _, _, _) when is_boolean(B) -> + B. + + + +%% =========================================================================== +%% +%% Misc utility functions +%% +%% =========================================================================== + +formated_timestamp() -> + format_timestamp(os:timestamp()). + +format_timestamp(Now) -> + N2T = fun(N) -> calendar:now_to_local_time(N) end, + format_timestamp(Now, N2T, true). + +format_timestamp({_N1, _N2, N3} = N, N2T, true) -> + FormatExtra = ".~.2.0w", + ArgsExtra = [N3 div 10000], + format_timestamp(N, N2T, FormatExtra, ArgsExtra); +format_timestamp({_N1, _N2, _N3} = N, N2T, false) -> + FormatExtra = "", + ArgsExtra = [], + format_timestamp(N, N2T, FormatExtra, ArgsExtra). + +format_timestamp(N, N2T, FormatExtra, ArgsExtra) -> + {Date, Time} = N2T(N), + {YYYY,MM,DD} = Date, + {Hour,Min,Sec} = Time, + FormatDate = + io_lib:format("~.4w-~.2.0w-~.2.0w ~.2.0w:~.2.0w:~.2.0w" ++ FormatExtra, + [YYYY, MM, DD, Hour, Min, Sec] ++ ArgsExtra), + lists:flatten(FormatDate). + + +%% A timestamp in ms + +timestamp(infinity) -> + undefined; +timestamp(_) -> + timestamp(). + +timestamp() -> + {A,B,C} = os:timestamp(), + A*1000000000+B*1000+(C div 1000). + +next_timeout(infinity = Timeout, _) -> + Timeout; +next_timeout(Timeout, TS) -> + NewTimeout = Timeout - tdiff(TS, timestamp()), + if + (NewTimeout > 0) -> + NewTimeout; + true -> + 0 + end. + +tdiff(T1, T2) -> + T2 - T1. + + + + + +%% =========================================================================== +%% +%% Below follows the actual NIF-functions. +%% +%% =========================================================================== + +nif_is_loaded() -> false. + +nif_info() -> + erlang:error(badarg). + +nif_open(_Domain, _Type, _Protocol, _Extra) -> + erlang:error(badarg). + +nif_bind(_SRef, _LocalAddr) -> + erlang:error(badarg). + +nif_connect(_SRef, _Addr, _Port) -> + erlang:error(badarg). + +nif_finalize_connection(_SRef) -> + erlang:error(badarg). + +nif_listen(_SRef, _Backlog) -> + erlang:error(badarg). + +nif_accept(_SRef, _Ref) -> + erlang:error(badarg). + +nif_accept4(_SRef, _Flags, _Ref) -> + erlang:error(badarg). + +nif_send(_SRef, _Data, _Flags) -> + erlang:error(badarg). + +nif_sendto(_SRef, _Data, _Flags, _Dest, _Port) -> + erlang:error(badarg). + +nif_recv(_SRef, _Flags) -> + erlang:error(badarg). + +nif_recvfrom(_SRef, _Flags) -> + erlang:error(badarg). + +nif_cancel(_SRef, _Ref) -> + erlang:error(badarg). + +nif_close(_SRef) -> + erlang:error(badarg). + +nif_setopt(_Ref, _Key, _Val) -> + erlang:error(badarg). + +nif_getopt(_Ref, _Key) -> + erlang:error(badarg). -- cgit v1.2.3 From e752b4a8187903da609004c56f0f019b1d7b7605 Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Mon, 9 Apr 2018 12:29:25 +0200 Subject: [socket-nif] Completed connect Still not implemented the 'cancel' operation. This will be used when, for instance, we need to cancel an ongoing connect that has taken to long time to complete (the select). The idea is to use select(STOP). --- erts/emulator/nifs/common/socket_nif.c | 110 +++++++++++++++++++++++++++------ 1 file changed, 91 insertions(+), 19 deletions(-) diff --git a/erts/emulator/nifs/common/socket_nif.c b/erts/emulator/nifs/common/socket_nif.c index 2c51ff2000..a702e35a0d 100644 --- a/erts/emulator/nifs/common/socket_nif.c +++ b/erts/emulator/nifs/common/socket_nif.c @@ -226,6 +226,7 @@ typedef unsigned long long llu_t; #define MALLOC(SZ) enif_alloc(SZ) #define FREE(P) enif_free(P) #define MKA(E,S) enif_make_atom(E, S) +#define MKREF(E) enif_make_ref(E) #define MKT2(E,E1,E2) enif_make_tuple2(E, E1, E2) #define MCREATE(N) enif_mutex_create(N) @@ -317,11 +318,13 @@ typedef unsigned long long llu_t; /* *** Windown macros *** */ #define sock_bind(s, addr, len) bind((s), (addr), (len)) +#define sock_connect(s, addr, len) connect((s), (addr), (len)) +#define sock_getopt(s,t,n,v,l) getsockopt((s),(t),(n),(v),(l)) +#define sock_htons(x) htons((x)) +#define sock_htonl(x) htonl((x)) #define sock_name(s, addr, len) getsockname((s), (addr), (len)) #define sock_open(domain, type, proto) \ make_noninheritable_handle(socket((domain), (type), (proto))) -#define sock_htons(x) htons((x)) -#define sock_htonl(x) htonl((x)) #define sock_errno() WSAGetLastError() #define sock_create_event(s) WSACreateEvent() @@ -334,10 +337,12 @@ static unsigned long one_value = 1; #else /* !__WIN32__ */ #define sock_bind(s, addr, len) bind((s), (addr), (len)) -#define sock_name(s, addr, len) getsockname((s), (addr), (len)) -#define sock_open(domain, type, proto) socket((domain), (type), (proto)) +#define sock_connect(s, addr, len) connect((s), (addr), (len)) +#define sock_getopt(s,t,n,v,l) getsockopt((s),(t),(n),(v),(l)) #define sock_htons(x) htons((x)) #define sock_htonl(x) htonl((x)) +#define sock_name(s, addr, len) getsockname((s), (addr), (len)) +#define sock_open(domain, type, proto) socket((domain), (type), (proto)) #define sock_errno() errno #define sock_create_event(s) (s) /* return file descriptor */ @@ -502,6 +507,9 @@ static ERL_NIF_TERM nif_setopt(ErlNifEnv* env, static ERL_NIF_TERM nif_getopt(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); +static ERL_NIF_TERM nif_finalize_connection(ErlNifEnv* env, + int argc, + const ERL_NIF_TERM argv[]); static ERL_NIF_TERM nif_cancel(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); @@ -543,6 +551,10 @@ static ERL_NIF_TERM nconnect(ErlNifEnv* env, SocketDescriptor* descP, const ERL_NIF_TERM* addr, int port); +static ERL_NIF_TERM nfinalize_connection(ErlNifEnv* env, + SocketDescriptor* descP); + +static BOOLEAN_T verify_is_connected(SocketDescriptor* descP, int* err); static BOOLEAN_T edomain2domain(int edomain, int* domain); static BOOLEAN_T etype2type(int etype, int* type); @@ -617,6 +629,7 @@ static char str_eagain[] = "eagain"; static char str_eafnosupport[] = "eafnosupport"; static char str_einval[] = "einval"; static char str_eisconn[] = "eisconn"; +static char str_eisnconn[] = "eisnconn"; static char str_exbadstate[] = "exbadstate"; static char str_exmon[] = "exmonitor"; // failed monitor static char str_exself[] = "exself"; // failed self @@ -633,6 +646,7 @@ static ERL_NIF_TERM atom_eagain; static ERL_NIF_TERM atom_eafnosupport; static ERL_NIF_TERM atom_einval; static ERL_NIF_TERM atom_eisconn; +static ERL_NIF_TERM atom_eisnconn; static ERL_NIF_TERM atom_exbadstate; static ERL_NIF_TERM atom_exmon; static ERL_NIF_TERM atom_exself; @@ -1273,8 +1287,8 @@ char* decode_address_tuple(ErlNifEnv* env, return str_einval; laddr[a] = v; } - sys_memcpy(&localP->u.sai.sin_addr, &laddr, sizeof(laddr)); - addrP->len = sizeof(struct sockaddr_in); + sys_memcpy(&addrP->u.sai.sin_addr, &laddr, sizeof(laddr)); + addrP->len = sizeof(struct sockaddr_in); return NULL; } break; @@ -1301,8 +1315,8 @@ char* decode_address_tuple(ErlNifEnv* env, laddr[a*2 ] = ((v >> 8) & 0xFF); laddr[a*2+1] = (v & 0xFF); } - sys_memcpy(&localP->u.sai6.sin6_addr, &laddr, sizeof(laddr)); - addrP->len = sizeof(struct sockaddr_in6); + sys_memcpy(&addrP->u.sai6.sin6_addr, &laddr, sizeof(laddr)); + addrP->len = sizeof(struct sockaddr_in6); return NULL; } break; @@ -1449,6 +1463,9 @@ ERL_NIF_TERM nconnect(ErlNifEnv* env, const ERL_NIF_TERM* addr, int port) { + int code; + char* xerr; + /* Verify that we are where in the proper state */ if (!IS_OPEN(descP)) @@ -1472,7 +1489,7 @@ ERL_NIF_TERM nconnect(ErlNifEnv* env, ((sock_errno() == ERRNO_BLOCK) || /* Winsock2 */ (sock_errno() == EINPROGRESS))) { /* Unix & OSE!! */ ERL_NIF_TERM ref = MKREF(env); - descP->state = INET_STATE_CONNECTING; + descP->state = SOCKET_STATE_CONNECTING; enif_select(env, descP->sock, (ERL_NIF_SELECT_WRITE), @@ -1514,26 +1531,81 @@ ERL_NIF_TERM nif_finalize_connection(ErlNifEnv* env, return enif_make_badarg(env); } - return nfinalize_connection(descP); + return nfinalize_connection(env, descP); } +/* *** nfinalize_connection *** + * Perform the final check to verify a connection. + */ static ERL_NIF_TERM nfinalize_connection(ErlNifEnv* env, SocketDescriptor* descP) { int error; - if (descP->state != INET_STATE_CONNECTING) - return make_error(env, atom_exisnconn); + if (descP->state != SOCKET_STATE_CONNECTING) + return make_error(env, atom_eisnconn); - if (!is_connected(descP, &error)) + if (!verify_is_connected(descP, &error)) { + descP->state = SOCKET_STATE_OPEN; /* restore state */ return make_error2(env, error); + } + + descP->state = SOCKET_STATE_CONNECTED; + + return atom_ok; +} + + +/* *** verify_is_connected *** + * Check if a connection has been established. + */ +static +BOOLEAN_T verify_is_connected(SocketDescriptor* descP, int* err) +{ + /* + * *** This is strange *** + * + * This *should* work on Windows NT too, but doesn't. + * An bug in Winsock 2.0 for Windows NT? + * + * See "Unix Netwok Programming", W.R.Stevens, p 412 for a + * discussion about Unix portability and non blocking connect. + */ + +#ifndef SO_ERROR + int sz, code; + + sz = sizeof(descP->inet.remote); + sys_memzero((char *) &descP->inet.remote, sz); + code = sock_peer(desc->sock, + (struct sockaddr*) &descP->remote, &sz); + + if (IS_SOCKET_ERROR(code)) { + *err = sock_errno(); + return FALSE; + } + +#else - descP->state = INET_STATE_CONNECTED; + int error = 0; /* Has to be initiated, we check it */ + unsigned int sz = sizeof(error); /* even if we get -1 */ + int code = sock_getopt(descP->sock, + SOL_SOCKET, SO_ERROR, + (void *)&error, &sz); - return make_ok1(env); + if ((code < 0) || error) { + *err = error; + return FALSE; + } + +#endif /* SO_ERROR */ + + *err = 0; + + return TRUE; } @@ -1811,10 +1883,10 @@ ErlNifFunc socket_funcs[] = {"nif_getopt", 2, nif_getopt}, /* "Extra" functions to "complete" the socket interface. - * For instance, the function nif_finalyze_connection - * is called after the connect select has "completed". + * For instance, the function nif_finalize_connection + * is called after the connect *select* has "completed". */ - {"nif_finalize_connection", 1, nif_finalyze_connection}, + {"nif_finalize_connection", 1, nif_finalize_connection}, {"nif_cancel", 2, nif_cancel}, }; @@ -1902,13 +1974,13 @@ int on_load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info) atom_eafnosupport = MKA(env, str_eafnosupport); atom_einval = MKA(env, str_einval); atom_eisconn = MKA(env, str_eisconn); + atom_eisnconn = MKA(env, str_eisnconn); // atom_exalloc = MKA(env, str_exalloc); atom_exbadstate = MKA(env, str_exbadstate); // atom_exnotopen = MKA(env, str_exnotopen); atom_exmon = MKA(env, str_exmon); atom_exself = MKA(env, str_exself); // atom_exsend = MKA(env, str_exsend); - // atom_exisnconning = MKA(env, str_exisnconning); // For storing "global" things... // socketData.env = enif_alloc_env(); // We should really check -- cgit v1.2.3 From 9e6fda01b1af0c42cee9f983d5bddecc7eb7e240 Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Mon, 9 Apr 2018 12:54:52 +0200 Subject: [socket-nif] Completed listen --- erts/emulator/nifs/common/socket_nif.c | 58 ++++++++++++++++++++++++++++++++++ erts/preloaded/src/socket.erl | 32 +++++++++++-------- 2 files changed, 77 insertions(+), 13 deletions(-) diff --git a/erts/emulator/nifs/common/socket_nif.c b/erts/emulator/nifs/common/socket_nif.c index a702e35a0d..88fb2206e4 100644 --- a/erts/emulator/nifs/common/socket_nif.c +++ b/erts/emulator/nifs/common/socket_nif.c @@ -322,6 +322,7 @@ typedef unsigned long long llu_t; #define sock_getopt(s,t,n,v,l) getsockopt((s),(t),(n),(v),(l)) #define sock_htons(x) htons((x)) #define sock_htonl(x) htonl((x)) +#define sock_listen(s, b) listen((s), (b)) #define sock_name(s, addr, len) getsockname((s), (addr), (len)) #define sock_open(domain, type, proto) \ make_noninheritable_handle(socket((domain), (type), (proto))) @@ -341,6 +342,7 @@ static unsigned long one_value = 1; #define sock_getopt(s,t,n,v,l) getsockopt((s),(t),(n),(v),(l)) #define sock_htons(x) htons((x)) #define sock_htonl(x) htonl((x)) +#define sock_listen(s, b) listen((s), (b)) #define sock_name(s, addr, len) getsockname((s), (addr), (len)) #define sock_open(domain, type, proto) socket((domain), (type), (proto)) @@ -551,6 +553,9 @@ static ERL_NIF_TERM nconnect(ErlNifEnv* env, SocketDescriptor* descP, const ERL_NIF_TERM* addr, int port); +static ERL_NIF_TERM nlisten(ErlNifEnv* env, + SocketDescriptor* descP, + int backlog); static ERL_NIF_TERM nfinalize_connection(ErlNifEnv* env, SocketDescriptor* descP); @@ -1610,6 +1615,59 @@ BOOLEAN_T verify_is_connected(SocketDescriptor* descP, int* err) +/* ---------------------------------------------------------------------- + * nif_listen + * + * Description: + * Listen for connections on a socket. + * + * Arguments: + * Socket (ref) - Points to the socket descriptor. + * Backlog - The maximum length to which the queue of pending + * connections for socket may grow. + */ +static +ERL_NIF_TERM nif_listen(ErlNifEnv* env, + int argc, + const ERL_NIF_TERM argv[]) +{ + SocketDescriptor* descP; + int backlog; + + /* Extract arguments and perform preliminary validation */ + + if ((argc != 2) || + !enif_get_resource(env, argv[0], sockets, (void**) &descP) || + !GET_INT(env, argv[1], &backlog)) { + return enif_make_badarg(env); + } + + return nlisten(env, descP, backlog); +} + + + +static +ERL_NIF_TERM nlisten(ErlNifEnv* env, + SocketDescriptor* descP, + int backlog) +{ + if (descP->state == SOCKET_STATE_CLOSED) + return make_error(env, atom_exbadstate); + + if (!IS_OPEN(descP)) + return make_error(env, atom_exbadstate); + + if (IS_SOCKET_ERROR(sock_listen(descP->sock, backlog))) + return make_error2(env, sock_errno()); + + descP->state = SOCKET_STATE_LISTENING; + + return atom_ok; +} + + + /* ---------------------------------------------------------------------- * U t i l i t y F u n c t i o n s * ---------------------------------------------------------------------- diff --git a/erts/preloaded/src/socket.erl b/erts/preloaded/src/socket.erl index f8e3d0419a..0886c0d211 100644 --- a/erts/preloaded/src/socket.erl +++ b/erts/preloaded/src/socket.erl @@ -213,7 +213,10 @@ info() -> %% %% =========================================================================== +%% =========================================================================== +%% %% open - create an endpoint for communication +%% open(Domain, Type) -> open(Domain, Type, null). @@ -272,7 +275,10 @@ default_protocol(null, Type) -> throw({error, {no_default_protocol, Type}}) default_protocol(Protocol, _) -> Protocol. +%% =========================================================================== +%% %% bind - bind a name to a socket +%% -spec bind(Socket, FileOrAddr) -> ok | {error, Reason} when Socket :: socket(), @@ -318,7 +324,11 @@ bind({socket, _, SockRef}, Addr, Port) nif_bind(SockRef, {Addr, Port}). + +%% =========================================================================== +%% %% connect - initiate a connection on a socket +%% -spec connect(Socket, Addr, Port) -> ok | {error, Reason} when Socket :: socket(), @@ -364,7 +374,11 @@ connect({socket, _, SockRef}, Addr, Port, Timeout) end. + +%% =========================================================================== +%% %% listen - listen for connections on a socket +%% -spec listen(Socket, Backlog) -> ok | {error, Reason} when Socket :: socket(), @@ -375,13 +389,15 @@ listen(Socket) -> listen(Socket, ?SOCKET_LISTEN_BACKLOG_DEFAULT). listen({socket, _, SockRef}, Backlog) - when (is_integer(Backlog) andalso (Backlog >= 0)) orelse is_boolean(Backlog) -> - EBacklog = enc_backlog(Backlog), - nif_listen(SockRef, EBacklog). + when (is_integer(Backlog) andalso (Backlog >= 0)) -> + nif_listen(SockRef, Backlog). +%% =========================================================================== +%% %% accept, accept4 - accept a connection on a socket +%% -spec accept(LSocket, Timeout) -> {ok, Socket} | {error, Reason} when LSocket :: socket(), @@ -682,16 +698,6 @@ enc_protocol(seqpacket, sctp) -> ?SOCKET_PROTOCOL_SCTP; enc_protocol(Type, Proto) -> throw({error, {invalid_protocol, {Type, Proto}}}). --spec enc_backlog(Backlog) -> non_neg_integer() when - Backlog :: non_neg_integer() | boolean(). - -enc_backlog(true) -> - ?SOCKET_LISTEN_BACKLOG_DEFAULT; -enc_backlog(false) -> - 0; -enc_backlog(Backlog) when is_integer(Backlog) andalso (Backlog >= 0) -> - Backlog. - -spec enc_accept_flags(Flags) -> non_neg_integer() when Flags :: accept_flags(). -- cgit v1.2.3 From c5c8da4ecb985837817e60738811793754c679a0 Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Wed, 11 Apr 2018 12:38:58 +0200 Subject: [socket-nif] Completed accept --- erts/emulator/nifs/common/socket_nif.c | 432 ++++++++++++++++++++++++++++++--- erts/preloaded/src/socket.erl | 96 +++----- 2 files changed, 426 insertions(+), 102 deletions(-) diff --git a/erts/emulator/nifs/common/socket_nif.c b/erts/emulator/nifs/common/socket_nif.c index 88fb2206e4..3e8fe7061a 100644 --- a/erts/emulator/nifs/common/socket_nif.c +++ b/erts/emulator/nifs/common/socket_nif.c @@ -229,6 +229,8 @@ typedef unsigned long long llu_t; #define MKREF(E) enif_make_ref(E) #define MKT2(E,E1,E2) enif_make_tuple2(E, E1, E2) #define MCREATE(N) enif_mutex_create(N) +#define MLOCK(M) enif_mutex_lock(M) +#define MUNLOCK(M) enif_mutex_unlock(M) /* *** Socket state defs *** */ @@ -239,15 +241,13 @@ typedef unsigned long long llu_t; #define SOCKET_FLAG_CON 0x0010 #define SOCKET_FLAG_ACC 0x0020 #define SOCKET_FLAG_BUSY 0x0040 -#define SOCKET_FLAG_MULTI_CLIENT 0x0100 /* Multiple clients for one descriptor, * - * i.e. multi-accept */ + #define SOCKET_STATE_CLOSED (0) #define SOCKET_STATE_OPEN (SOCKET_FLAG_OPEN) #define SOCKET_STATE_CONNECTED (SOCKET_STATE_OPEN | SOCKET_FLAG_ACTIVE) #define SOCKET_STATE_LISTENING (SOCKET_STATE_OPEN | SOCKET_FLAG_LISTEN) #define SOCKET_STATE_CONNECTING (SOCKET_STATE_OPEN | SOCKET_FLAG_CON) #define SOCKET_STATE_ACCEPTING (SOCKET_STATE_LISTENING | SOCKET_FLAG_ACC) -#define SOCKET_STATE_MULTI_ACCEPTING (SOCKET_STATE_ACCEPTING | SOCKET_FLAG_MULTI_CLIENT) #define IS_OPEN(d) \ (((d)->state & SOCKET_FLAG_OPEN) == SOCKET_FLAG_OPEN) @@ -317,7 +317,10 @@ typedef unsigned long long llu_t; /* *** Windown macros *** */ +#define sock_accept(s, addr, len) \ + make_noninheritable_handle(accept((s), (addr), (len))) #define sock_bind(s, addr, len) bind((s), (addr), (len)) +#define sock_close(s) closesocket((s)) #define sock_connect(s, addr, len) connect((s), (addr), (len)) #define sock_getopt(s,t,n,v,l) getsockopt((s),(t),(n),(v),(l)) #define sock_htons(x) htons((x)) @@ -337,7 +340,14 @@ static unsigned long one_value = 1; #else /* !__WIN32__ */ +#ifdef HAS_ACCEPT4 +// We have to figure out what the flags are... +#define sock_accept(s, addr, len) accept4((s), (addr), (len), (SOCK_CLOEXEC)) +#else +#define sock_accept(s, addr, len) accept((s), (addr), (len)) +#endif #define sock_bind(s, addr, len) bind((s), (addr), (len)) +#define sock_close(s) close((s)) #define sock_connect(s, addr, len) connect((s), (addr), (len)) #define sock_getopt(s,t,n,v,l) getsockopt((s),(t),(n),(v),(l)) #define sock_htons(x) htons((x)) @@ -383,6 +393,12 @@ typedef struct { ((sap)->u.sai.sin_port) : -1) +typedef struct { + ErlNifPid pid; // PID of the acceptor + ErlNifMonitor mon; // Monitor for the acceptor + ERL_NIF_TERM ref; // The (unique) reference of the (accept) request +} SocketAcceptor; + typedef struct { // The actual socket SOCKET sock; @@ -419,6 +435,13 @@ typedef struct { unsigned int readTries; unsigned int readWaits; + /* Accept + * We also need a queue for waiting acceptors... + * Lets see if this can be a common "request" queue... + */ + ErlNifMutex* accMtx; + SocketAcceptor acceptor; + /* We need to keep track of the "request(s)" we have pending. * If for instance an accept takes to long, the issuer may @@ -485,9 +508,6 @@ static ERL_NIF_TERM nif_listen(ErlNifEnv* env, static ERL_NIF_TERM nif_accept(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); -static ERL_NIF_TERM nif_accept4(ErlNifEnv* env, - int argc, - const ERL_NIF_TERM argv[]); static ERL_NIF_TERM nif_send(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); @@ -556,11 +576,26 @@ static ERL_NIF_TERM nconnect(ErlNifEnv* env, static ERL_NIF_TERM nlisten(ErlNifEnv* env, SocketDescriptor* descP, int backlog); +static ERL_NIF_TERM naccept_listening(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM ref); +static ERL_NIF_TERM naccept_accepting(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM ref); +static ERL_NIF_TERM naccept(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM ref); static ERL_NIF_TERM nfinalize_connection(ErlNifEnv* env, SocketDescriptor* descP); static BOOLEAN_T verify_is_connected(SocketDescriptor* descP, int* err); +static SocketDescriptor* alloc_descriptor(SOCKET sock, HANDLE event); + +static int compare_pids(ErlNifEnv* env, + const ErlNifPid* pid1, + const ErlNifPid* pid2); + static BOOLEAN_T edomain2domain(int edomain, int* domain); static BOOLEAN_T etype2type(int etype, int* type); static BOOLEAN_T eproto2proto(int eproto, int* proto); @@ -636,6 +671,7 @@ static char str_einval[] = "einval"; static char str_eisconn[] = "eisconn"; static char str_eisnconn[] = "eisnconn"; static char str_exbadstate[] = "exbadstate"; +static char str_exbusy[] = "exbusy"; static char str_exmon[] = "exmonitor"; // failed monitor static char str_exself[] = "exself"; // failed self @@ -653,6 +689,7 @@ static ERL_NIF_TERM atom_einval; static ERL_NIF_TERM atom_eisconn; static ERL_NIF_TERM atom_eisnconn; static ERL_NIF_TERM atom_exbadstate; +static ERL_NIF_TERM atom_exbusy; static ERL_NIF_TERM atom_exmon; static ERL_NIF_TERM atom_exself; @@ -686,7 +723,6 @@ static SocketData socketData; * nif_connect(Sock, Addr, Port) * nif_listen(Sock, Backlog) * nif_accept(LSock, Ref) - * nif_accept4(LSock, Ref) * nif_send(Sock, Data, Flags) * nif_sendto(Sock, Data, Flags, DstAddr, DstPort) * nif_recv(Sock, Flags) @@ -835,7 +871,7 @@ ERL_NIF_TERM nopen(ErlNifEnv* env, if ((event = sock_create_event(sock)) == INVALID_EVENT) { save_errno = sock_errno(); - while ((close(sock) == INVALID_SOCKET) && (sock_errno() == EINTR)); + while ((sock_close(sock) == INVALID_SOCKET) && (sock_errno() == EINTR)); return make_error2(env, save_errno); } @@ -844,31 +880,16 @@ ERL_NIF_TERM nopen(ErlNifEnv* env, /* Create and initiate the socket "descriptor" */ - descP = enif_alloc_resource(sockets, sizeof(SocketDescriptor)); - { - char buf[64]; /* Buffer used for building the mutex name */ - sprintf(buf, "socket[w,%d]", sock); - descP->writeMtx = MCREATE(buf); - sprintf(buf, "socket[r,%d]", sock); - descP->readMtx = MCREATE(buf); + if ((descP = alloc_descriptor(sock, event)) == NULL) { + sock_close(sock); + // Not sure if this is really the proper error, but... + return enif_make_badarg(env); } - descP->isWritable = TRUE; - descP->isReadable = TRUE; - descP->writePkgCnt = 0; - descP->writeByteCnt = 0; - descP->writeTries = 0; - descP->writeWaits = 0; - descP->readPkgCnt = 0; - descP->readByteCnt = 0; - descP->readTries = 0; - descP->readWaits = 0; - descP->dbg = SOCKET_DEBUG_DEFAULT; - descP->state = SOCKET_STATE_OPEN; - descP->domain = domain; - descP->type = type; - descP->protocol = protocol; - descP->sock = sock; - descP->event = event; + + descP->state = SOCKET_STATE_OPEN; + descP->domain = domain; + descP->type = type; + descP->protocol = protocol; res = enif_make_resource(env, descP); enif_release_resource(descP); // We should really store a reference ... @@ -1668,6 +1689,351 @@ ERL_NIF_TERM nlisten(ErlNifEnv* env, +/* ---------------------------------------------------------------------- + * nif_accept + * + * Description: + * Accept a connection on a socket. + * + * Arguments: + * Socket (ref) - Points to the socket descriptor. + * Request ref - Unique "id" of this request + * (used for the select, if none is in queue). + */ +static +ERL_NIF_TERM nif_accept(ErlNifEnv* env, + int argc, + const ERL_NIF_TERM argv[]) +{ + SocketDescriptor* descP; + ERL_NIF_TERM ref; + + /* Extract arguments and perform preliminary validation */ + + if ((argc != 2) || + !enif_get_resource(env, argv[0], sockets, (void**) &descP)) { + return enif_make_badarg(env); + } + ref = argv[1]; + + return naccept(env, descP, ref); +} + + +static +ERL_NIF_TERM naccept(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM ref) +{ + ERL_NIF_TERM res; + + switch (descP->state) { + case SOCKET_STATE_LISTENING: + MLOCK(descP->accMtx); + res = naccept_listening(env, descP, ref); + MUNLOCK(descP->accMtx); + break; + + case SOCKET_STATE_ACCEPTING: + MLOCK(descP->accMtx); + res = naccept_accepting(env, descP, ref); + MUNLOCK(descP->accMtx); + break; + + default: + res = make_error(env, atom_einval); + break; + } + + return res; +} + + +/* *** naccept_listening *** + * We have no active acceptor and no acceptors in queue. + */ +static +ERL_NIF_TERM naccept_listening(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM ref) +{ + SocketAddress remote; + unsigned int n; + SOCKET accSock; + HANDLE accEvent; + int save_errno; + ErlNifPid caller; + + if (enif_self(env, &caller) == NULL) + return make_error(env, atom_exself); + + n = sizeof(descP->remote.u); + sys_memzero((char *) &remote, n); + accSock = sock_accept(descP->sock, (struct sockaddr*) &remote, &n); + if (accSock == INVALID_SOCKET) { + save_errno = sock_errno(); + if (save_errno == ERRNO_BLOCK) { + + /* *** Try again later *** */ + + descP->acceptor.pid = caller; + if (enif_monitor_process(env, descP, + &descP->acceptor.pid, + &descP->acceptor.mon) > 0) + return make_error(env, atom_exmon); + + descP->acceptor.ref = ref; + + enif_select(env, + descP->sock, + (ERL_NIF_SELECT_READ), + descP, NULL, ref); + + /* Shall we really change state? + * The ready event is sent directly to the calling + * process, which simply calls this function again. + * Basically, state accepting means that we have + * an "outstanding" accept. + * Shall we store the pid of the calling process? + * And if someone else calls accept, return with ebusy? + * Can any process call accept or just the controlling + * process? + * We also need a monitor it case the calling process is + * called before we are done! + * + * Change state (to accepting) and store pid of the acceptor + * (current process). Only accept calls from the acceptor + * process (ebusy) and once we have a successful accept, + * change state back to listening. If cancel is called instead + * (only accepted from the acceptor process), we reset + * state to listening and also resets the pid to "null" + * (is there such a value?). + * Need a mutex to secure that we don't test and change the + * pid at the same time. + */ + + descP->state = SOCKET_STATE_ACCEPTING; + + return make_error(env, atom_eagain); + + } else { + return make_error2(env, save_errno); + } + + } else { + SocketDescriptor* accDescP; + ERL_NIF_TERM accRef; + + /* + * We got one + */ + + if ((accEvent = sock_create_event(accSock)) == INVALID_EVENT) { + save_errno = sock_errno(); + while ((sock_close(accSock) == INVALID_SOCKET) && + (sock_errno() == EINTR)); + return make_error2(env, save_errno); + } + + if ((accDescP = alloc_descriptor(accSock, accEvent)) == NULL) { + sock_close(accSock); + return enif_make_badarg(env); + } + + accDescP->domain = descP->domain; + accDescP->type = descP->type; + accDescP->protocol = descP->protocol; + + accRef = enif_make_resource(env, accDescP); + enif_release_resource(accDescP); // We should really store a reference ... + + accDescP->ctrlPid = caller; + if (enif_monitor_process(env, accDescP, + &accDescP->ctrlPid, + &accDescP->ctrlMon) > 0) { + sock_close(accSock); + return make_error(env, atom_exmon); + } + + accDescP->remote = remote; + SET_NONBLOCKING(accDescP->sock); + +#ifdef __WIN32__ + /* See 'What is the point of this?' above */ + enif_select(env, + (ERL_NIF_SELECT_READ), + descP, NULL, atom_undefined); +#endif + + accDescP->state = SOCKET_STATE_CONNECTED; + + return make_ok(env, accRef); + } +} + + +/* *** naccept_accepting *** + * We have an active acceptor and possibly acceptors waiting in queue. + * At the moment the queue is *not* implemented. + */ +static +ERL_NIF_TERM naccept_accepting(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM ref) +{ + SocketAddress remote; + unsigned int n; + SOCKET accSock; + HANDLE accEvent; + ErlNifPid caller; + int save_errno; + + if (enif_self(env, &caller) == NULL) + return make_error(env, atom_exself); + + if (compare_pids(env, &descP->acceptor.pid, &caller) != 0) { + /* This will have to do until we implement the queue. + * When we have the queue, we should simply push this request, + * and instead return with eagain (the caller will then wait + * for the select message). + */ + return make_error(env, atom_exbusy); + } + + n = sizeof(descP->remote.u); + sys_memzero((char *) &remote, n); + accSock = sock_accept(descP->sock, (struct sockaddr*) &remote, &n); + if (accSock == INVALID_SOCKET) { + save_errno = sock_errno(); + if (save_errno == ERRNO_BLOCK) { + + /* + * Just try again, no real error, just a ghost trigger from poll, + */ + + enif_select(env, + descP->sock, + (ERL_NIF_SELECT_READ), + descP, NULL, ref); + + return make_error(env, atom_eagain); + } else { + return make_error2(env, save_errno); + } + } else { + SocketDescriptor* accDescP; + ERL_NIF_TERM accRef; + + /* + * We got one + */ + + if ((accEvent = sock_create_event(accSock)) == INVALID_EVENT) { + save_errno = sock_errno(); + while ((sock_close(accSock) == INVALID_SOCKET) && + (sock_errno() == EINTR)); + return make_error2(env, save_errno); + } + + if ((accDescP = alloc_descriptor(accSock, accEvent)) == NULL) { + sock_close(accSock); + return enif_make_badarg(env); + } + + accDescP->domain = descP->domain; + accDescP->type = descP->type; + accDescP->protocol = descP->protocol; + + accRef = enif_make_resource(env, accDescP); + enif_release_resource(accDescP); // We should really store a reference ... + + accDescP->ctrlPid = caller; + if (enif_monitor_process(env, accDescP, + &accDescP->ctrlPid, + &accDescP->ctrlMon) > 0) { + sock_close(accSock); + return make_error(env, atom_exmon); + } + + accDescP->remote = remote; + SET_NONBLOCKING(accDescP->sock); + +#ifdef __WIN32__ + /* See 'What is the point of this?' above */ + enif_select(env, + (ERL_NIF_SELECT_READ), + descP, NULL, atom_undefined); +#endif + + accDescP->state = SOCKET_STATE_CONNECTED; + + + /* Here we should have the test if we have something in the queue. + * And if so, pop it and copy the (waiting) acceptor, and then + * make a new select with that info). + */ + descP->state = SOCKET_STATE_LISTENING; + + return make_ok(env, accRef); + } +} + + + +/* *** alloc_descriptor *** + * Allocate and perform basic initialization of a socket descriptor. + * + */ +static +SocketDescriptor* alloc_descriptor(SOCKET sock, HANDLE event) +{ + SocketDescriptor* descP; + + if ((descP = enif_alloc_resource(sockets, sizeof(SocketDescriptor))) != NULL) { + char buf[64]; /* Buffer used for building the mutex name */ + + sprintf(buf, "socket[w,%d]", sock); + descP->writeMtx = MCREATE(buf); + + sprintf(buf, "socket[r,%d]", sock); + descP->readMtx = MCREATE(buf); + + sprintf(buf, "socket[acc,%d]", sock); + descP->accMtx = MCREATE(buf); + + descP->dbg = SOCKET_DEBUG_DEFAULT; + descP->isWritable = TRUE; + descP->isReadable = TRUE; + descP->writePkgCnt = 0; + descP->writeByteCnt = 0; + descP->writeTries = 0; + descP->writeWaits = 0; + descP->readPkgCnt = 0; + descP->readByteCnt = 0; + descP->readTries = 0; + descP->readWaits = 0; + + descP->sock = sock; + descP->event = event; + + } + + return descP; +} + + +static +int compare_pids(ErlNifEnv* env, + const ErlNifPid* pid1, + const ErlNifPid* pid2) +{ + ERL_NIF_TERM p1 = enif_make_pid(env, pid1); + ERL_NIF_TERM p2 = enif_make_pid(env, pid2); + + return enif_is_identical(p1, p2); +} + + /* ---------------------------------------------------------------------- * U t i l i t y F u n c t i o n s * ---------------------------------------------------------------------- @@ -1931,7 +2297,6 @@ ErlNifFunc socket_funcs[] = {"nif_connect", 3, nif_connect}, {"nif_listen", 2, nif_listen}, {"nif_accept", 2, nif_accept}, - {"nif_accept4", 3, nif_accept4}, {"nif_send", 3, nif_send}, {"nif_sendto", 5, nif_sendto}, {"nif_recv", 2, nif_recv}, @@ -2035,6 +2400,7 @@ int on_load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info) atom_eisnconn = MKA(env, str_eisnconn); // atom_exalloc = MKA(env, str_exalloc); atom_exbadstate = MKA(env, str_exbadstate); + atom_exbusy = MKA(env, str_exbusy); // atom_exnotopen = MKA(env, str_exnotopen); atom_exmon = MKA(env, str_exmon); atom_exself = MKA(env, str_exself); diff --git a/erts/preloaded/src/socket.erl b/erts/preloaded/src/socket.erl index 0886c0d211..f3a3d493ac 100644 --- a/erts/preloaded/src/socket.erl +++ b/erts/preloaded/src/socket.erl @@ -32,7 +32,6 @@ connect/3, listen/1, listen/2, accept/1, accept/2, - accept4/1, accept4/2, accept4/3, send/2, send/3, sendto/5, recv/1, recv/2, recvfrom/1, recvfrom/2, @@ -53,6 +52,7 @@ ip_address/0, ip4_address/0, ip6_address/0, + in6_sockaddr/0, port_number/0, accept_flags/0, @@ -76,6 +76,10 @@ -type ip4_address() :: {0..255, 0..255, 0..255, 0..255}. +-type uint32() :: 0..16#FFFFFFFF. +-type ip6_flow_info() :: uint32(). +-type ip6_scope_id() :: uint32(). + -type ip6_address() :: {0..65535, 0..65535, @@ -85,6 +89,11 @@ 0..65535, 0..65535, 0..65535}. +%% We need to polish this further... +-record(in6_sockaddr, {addr :: ip6_address(), + flowinfo = 0 :: ip6_flow_info(), + scope_id :: ip6_scope_id()}). +-type in6_sockaddr() :: #in6_sockaddr{}. -type port_number() :: 0..65535. @@ -394,6 +403,7 @@ listen({socket, _, SockRef}, Backlog) + %% =========================================================================== %% %% accept, accept4 - accept a connection on a socket @@ -411,25 +421,27 @@ accept(Socket) -> %% Do we really need this optimization? accept(_, Timeout) when is_integer(Timeout) andalso (Timeout < 0) -> {error, timeout}; -accept({socket, _, LSockRef}, Timeout) +accept({socket, SI, LSockRef}, Timeout) when is_integer(Timeout) orelse (Timeout =:= infinity) -> Ref = make_ref(), - do_accept(LSockRef, Ref, Timeout). + do_accept(LSockRef, SI, Ref, Timeout). -do_accept(_, _Ref, Timeout) when is_integer(Timeout) andalso (Timeout < 0) -> +do_accept(_, _, _Ref, Timeout) when is_integer(Timeout) andalso (Timeout < 0) -> {error, timeout}; -do_accept(LSockRef, Ref, Timeout) -> +do_accept(LSockRef, SI, Ref, Timeout) -> TS = timestamp(Timeout), case nif_accept(LSockRef, Ref) of {ok, SockRef} -> - Socket = {socket, foo, SockRef}, + SocketInfo = #{domain => maps:get(domain, SI), + type => maps:get(type, SI), + protocol => maps:get(protocol, SI)}, + Socket = {socket, SocketInfo, SockRef}, {ok, Socket}; {error, eagain} -> receive {select, LSockRef, Ref, ready_input} -> - do_accept(LSockRef, make_ref(), next_timeout(TS, Timeout)) + do_accept(LSockRef, SI, make_ref(), next_timeout(TS, Timeout)) after Timeout -> - %% Shall we cancel the select? How? nif_cancel(LSockRef, Ref), flush_select_msgs(LSockRef, Ref), {error, timeout} @@ -445,56 +457,10 @@ flush_select_msgs(LSRef, Ref) -> end. --spec accept4(LSocket, Flags, Timeout) -> {ok, Socket} | {error, Reason} when - LSocket :: socket(), - Flags :: accept_flags(), - Timeout :: timeout(), - Socket :: socket(), - Reason :: term(). - -accept4(LSocket) -> - accept4(LSocket, ?SOCKET_ACCEPT_FLAGS_DEFAULT). - -accept4(LSocket, Flags) when is_list(Flags) -> - accept4(LSocket, Flags, infinity); -accept4(LSocket, Timeout) -> - accept4(LSocket, ?SOCKET_ACCEPT_FLAGS_DEFAULT, Timeout). - -%% Do we really need this optimization? -accept4(_LSocket, _Flags, Timeout) when is_integer(Timeout) andalso (Timeout < 0) -> - {error, timeout}; -accept4({socket, _, LSockRef}, Flags, Timeout) - when is_list(Flags) andalso - (is_integer(Timeout) orelse (Timeout =:= infinity)) -> - EFlags = enc_accept_flags(Flags), - Ref = make_ref(), - do_accept4(LSockRef, EFlags, Ref, Timeout). - -do_accept4(_, _EFlags, _Ref, Timeout) - when is_integer(Timeout) andalso (Timeout < 0) -> - {error, timeout}; -do_accept4(LSockRef, EFlags, Ref, Timeout) -> - TS = timestamp(Timeout), - case nif_accept4(LSockRef, EFlags, Ref) of - {ok, SockRef} -> - Socket = {socket, foo, SockRef}, - {ok, Socket}; - {error, eagain} -> - receive - {select, LSockRef, Ref, ready_input} -> - do_accept4(LSockRef, EFlags, make_ref(), - next_timeout(TS, Timeout)) - after Timeout -> - %% Shall we cancel the select? How? - nif_cancel(LSockRef, Ref), - flush_select_msgs(LSockRef, Ref), - {error, timeout} - end - end. - - - +%% =========================================================================== +%% %% send, sendto, sendmsg - send a message on a socket +%% -spec send(Socket, Data, Flags) -> ok | {error, Reason} when Socket :: socket(), @@ -511,6 +477,8 @@ send({socket, _, SockRef}, Data, Flags) nif_send(SockRef, Data, EFlags). +%% --------------------------------------------------------------------------- + -spec sendto(Socket, Data, Flags, DestAddr, Port) -> ok | {error, Reason} when Socket :: socket(), Data :: binary(), @@ -530,6 +498,8 @@ sendto({socket, _, SockRef}, Data, Flags, DestAddr, DestPort) nif_sendto(SockRef, Data, EFlags, DestAddr, DestPort). +%% --------------------------------------------------------------------------- + %% -spec sendmsg(Socket, MsgHdr, Flags) -> ok | {error, Reason} when %% Socket :: socket(), %% MsgHdr :: msg_header(), @@ -698,15 +668,6 @@ enc_protocol(seqpacket, sctp) -> ?SOCKET_PROTOCOL_SCTP; enc_protocol(Type, Proto) -> throw({error, {invalid_protocol, {Type, Proto}}}). --spec enc_accept_flags(Flags) -> non_neg_integer() when - Flags :: accept_flags(). - -enc_accept_flags(Flags) -> - EFlags = [{nonblock, ?SOCKET_ACCEPT_FLAG_NONBLOCK}, - {cloexec, ?SOCKET_ACCEPT_FLAG_CLOEXEC}], - enc_flags(Flags, EFlags). - - -spec enc_send_flags(Flags) -> non_neg_integer() when Flags :: send_flags(). @@ -856,9 +817,6 @@ nif_listen(_SRef, _Backlog) -> nif_accept(_SRef, _Ref) -> erlang:error(badarg). -nif_accept4(_SRef, _Flags, _Ref) -> - erlang:error(badarg). - nif_send(_SRef, _Data, _Flags) -> erlang:error(badarg). -- cgit v1.2.3 From 2d57ebfc6fb723a476fdcffbb366558a6fa18844 Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Thu, 12 Apr 2018 18:24:00 +0200 Subject: [socket-nif] Completed send We still need to handle simultaneous ops. That is, handle if two different procs tries to send at the same time. Or a recv and send at the same time. Ops queue? --- erts/emulator/nifs/common/socket_nif.c | 387 +++++++++++++++++++++++++++++---- erts/preloaded/src/socket.erl | 178 +++++++++++++-- 2 files changed, 504 insertions(+), 61 deletions(-) diff --git a/erts/emulator/nifs/common/socket_nif.c b/erts/emulator/nifs/common/socket_nif.c index 3e8fe7061a..bf9179d857 100644 --- a/erts/emulator/nifs/common/socket_nif.c +++ b/erts/emulator/nifs/common/socket_nif.c @@ -231,6 +231,9 @@ typedef unsigned long long llu_t; #define MCREATE(N) enif_mutex_create(N) #define MLOCK(M) enif_mutex_lock(M) #define MUNLOCK(M) enif_mutex_unlock(M) +#define SELECT(E,FD,M,O,P,R) \ + if (enif_select((E), (FD), (M), (O), (P), (R)) < 0) \ + return enif_make_badarg((E)); /* *** Socket state defs *** */ @@ -262,6 +265,39 @@ typedef unsigned long long llu_t; (((d)->state & SOCKET_FLAG_BUSY) == SOCKET_FLAG_BUSY) +#define SOCKET_SEND_FLAG_CONFIRM 0 +#define SOCKET_SEND_FLAG_DONTROUTE 1 +#define SOCKET_SEND_FLAG_DONTWAIT 2 +#define SOCKET_SEND_FLAG_EOR 3 +#define SOCKET_SEND_FLAG_MORE 4 +#define SOCKET_SEND_FLAG_NOSIGNAL 5 +#define SOCKET_SEND_FLAG_OOB 6 +#define SOCKET_SEND_FLAG_LOW SOCKET_SEND_FLAG_CONFIRM +#define SOCKET_SEND_FLAG_HIGH SOCKET_SEND_FLAG_OOB + +typedef union { + struct { + unsigned int open:1; + // 0 = not conn, 1 = connecting, 2 = connected + unsigned int connect:2; + // unsigned int connecting:1; + // unsigned int connected:1; + // 0 = not listen, 1 = listening, 2 = accepting + unsigned int listen:2; + // unsigned int listening:1; + // unsigned int accepting:1; + /* Room for more... */ + } flags; + unsigned int field; // Make it easy to reset all flags... +} SocketState; + +/* +#define IS_OPEN(d) ((d)->state.flags.open) +#define IS_CONNECTED(d) ((d)->state.flags.connect == SOCKET_STATE_CONNECTED) +#define IS_CONNECTING(d) ((d)->state.flags.connect == SOCKET_STATE_CONNECTING) +*/ + + /*---------------------------------------------------------------------------- * Interface constants. * @@ -329,6 +365,7 @@ typedef unsigned long long llu_t; #define sock_name(s, addr, len) getsockname((s), (addr), (len)) #define sock_open(domain, type, proto) \ make_noninheritable_handle(socket((domain), (type), (proto))) +#define sock_send(s,buf,len,flag) send((s),(buf),(len),(flag)) #define sock_errno() WSAGetLastError() #define sock_create_event(s) WSACreateEvent() @@ -355,6 +392,7 @@ static unsigned long one_value = 1; #define sock_listen(s, b) listen((s), (b)) #define sock_name(s, addr, len) getsockname((s), (addr), (len)) #define sock_open(domain, type, proto) socket((domain), (type), (proto)) +#define sock_send(s,buf,len,flag) send((s), (buf), (len), (flag)) #define sock_errno() errno #define sock_create_event(s) (s) /* return file descriptor */ @@ -420,20 +458,21 @@ typedef struct { // Write ErlNifMutex* writeMtx; BOOLEAN_T isWritable; - unsigned int writePkgCnt; - unsigned int writeByteCnt; - unsigned int writeTries; - unsigned int writeWaits; + uint32_t writePkgCnt; + uint32_t writeByteCnt; + uint32_t writeTries; + uint32_t writeWaits; + uint32_t writeFails; // Read ErlNifMutex* readMtx; BOOLEAN_T isReadable; ErlNifBinary rbuffer; - unsigned int readCapacity; - unsigned int readPkgCnt; - unsigned int readByteCnt; - unsigned int readTries; - unsigned int readWaits; + uint32_t readCapacity; + uint32_t readPkgCnt; + uint32_t readByteCnt; + uint32_t readTries; + uint32_t readWaits; /* Accept * We also need a queue for waiting acceptors... @@ -585,6 +624,11 @@ static ERL_NIF_TERM naccept_accepting(ErlNifEnv* env, static ERL_NIF_TERM naccept(ErlNifEnv* env, SocketDescriptor* descP, ERL_NIF_TERM ref); +static ERL_NIF_TERM nsend(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM sendRef, + ErlNifBinary* dataP, + int flags); static ERL_NIF_TERM nfinalize_connection(ErlNifEnv* env, SocketDescriptor* descP); @@ -599,12 +643,14 @@ static int compare_pids(ErlNifEnv* env, static BOOLEAN_T edomain2domain(int edomain, int* domain); static BOOLEAN_T etype2type(int etype, int* type); static BOOLEAN_T eproto2proto(int eproto, int* proto); +static BOOLEAN_T esendflags2sendflags(unsigned int esendflags, int* sendflags); #ifdef HAVE_SETNS static BOOLEAN_T emap2netns(ErlNifEnv* env, ERL_NIF_TERM map, char** netns); static BOOLEAN_T change_network_namespace(char* netns, int* cns, int* err); static BOOLEAN_T restore_network_namespace(int ns, SOCKET sock, int* err); #endif +static BOOLEAN_T cnt_inc(uint32_t* cnt, uint32_t inc); static void socket_dtor(ErlNifEnv* env, void* obj); static void socket_stop(ErlNifEnv* env, @@ -669,7 +715,7 @@ static char str_eagain[] = "eagain"; static char str_eafnosupport[] = "eafnosupport"; static char str_einval[] = "einval"; static char str_eisconn[] = "eisconn"; -static char str_eisnconn[] = "eisnconn"; +static char str_enotconn[] = "enotconn"; static char str_exbadstate[] = "exbadstate"; static char str_exbusy[] = "exbusy"; static char str_exmon[] = "exmonitor"; // failed monitor @@ -687,7 +733,7 @@ static ERL_NIF_TERM atom_eagain; static ERL_NIF_TERM atom_eafnosupport; static ERL_NIF_TERM atom_einval; static ERL_NIF_TERM atom_eisconn; -static ERL_NIF_TERM atom_eisnconn; +static ERL_NIF_TERM atom_enotconn; static ERL_NIF_TERM atom_exbadstate; static ERL_NIF_TERM atom_exbusy; static ERL_NIF_TERM atom_exmon; @@ -917,10 +963,10 @@ ERL_NIF_TERM nopen(ErlNifEnv* env, * * TODO! */ - enif_select(env, - event, - ERL_NIF_SELECT_READ, - descP, NULL, atom_undefined); + SELECT(env, + event, + ERL_NIF_SELECT_READ, + descP, NULL, atom_undefined); #endif @@ -1516,10 +1562,10 @@ ERL_NIF_TERM nconnect(ErlNifEnv* env, (sock_errno() == EINPROGRESS))) { /* Unix & OSE!! */ ERL_NIF_TERM ref = MKREF(env); descP->state = SOCKET_STATE_CONNECTING; - enif_select(env, - descP->sock, - (ERL_NIF_SELECT_WRITE), - descP, NULL, ref); + SELECT(env, + descP->sock, + (ERL_NIF_SELECT_WRITE), + descP, NULL, ref); return make_ok(env, ref); } else if (code == 0) { /* ok we are connected */ descP->state = SOCKET_STATE_CONNECTED; @@ -1572,7 +1618,7 @@ ERL_NIF_TERM nfinalize_connection(ErlNifEnv* env, int error; if (descP->state != SOCKET_STATE_CONNECTING) - return make_error(env, atom_eisnconn); + return make_error(env, atom_enotconn); if (!verify_is_connected(descP, &error)) { descP->state = SOCKET_STATE_OPEN; /* restore state */ @@ -1784,10 +1830,10 @@ ERL_NIF_TERM naccept_listening(ErlNifEnv* env, descP->acceptor.ref = ref; - enif_select(env, - descP->sock, - (ERL_NIF_SELECT_READ), - descP, NULL, ref); + SELECT(env, + descP->sock, + (ERL_NIF_SELECT_READ), + descP, NULL, ref); /* Shall we really change state? * The ready event is sent directly to the calling @@ -1860,9 +1906,10 @@ ERL_NIF_TERM naccept_listening(ErlNifEnv* env, #ifdef __WIN32__ /* See 'What is the point of this?' above */ - enif_select(env, - (ERL_NIF_SELECT_READ), - descP, NULL, atom_undefined); + SELECT(env, + descP->sock, + (ERL_NIF_SELECT_READ), + descP, NULL, atom_undefined); #endif accDescP->state = SOCKET_STATE_CONNECTED; @@ -1911,10 +1958,10 @@ ERL_NIF_TERM naccept_accepting(ErlNifEnv* env, * Just try again, no real error, just a ghost trigger from poll, */ - enif_select(env, - descP->sock, - (ERL_NIF_SELECT_READ), - descP, NULL, ref); + SELECT(env, + descP->sock, + (ERL_NIF_SELECT_READ), + descP, NULL, ref); return make_error(env, atom_eagain); } else { @@ -1960,9 +2007,10 @@ ERL_NIF_TERM naccept_accepting(ErlNifEnv* env, #ifdef __WIN32__ /* See 'What is the point of this?' above */ - enif_select(env, - (ERL_NIF_SELECT_READ), - descP, NULL, atom_undefined); + SELECT(env, + descP->sock, + (ERL_NIF_SELECT_READ), + descP, NULL, atom_undefined); #endif accDescP->state = SOCKET_STATE_CONNECTED; @@ -2008,6 +2056,7 @@ SocketDescriptor* alloc_descriptor(SOCKET sock, HANDLE event) descP->writeByteCnt = 0; descP->writeTries = 0; descP->writeWaits = 0; + descP->writeFails = 0; descP->readPkgCnt = 0; descP->readByteCnt = 0; descP->readTries = 0; @@ -2022,6 +2071,190 @@ SocketDescriptor* alloc_descriptor(SOCKET sock, HANDLE event) } + +/* ---------------------------------------------------------------------- + * nif_send + * + * Description: + * Send a message on a socket + * + * Arguments: + * Socket (ref) - Points to the socket descriptor. + * Sendref - A unique id for this (send) request. + * Data - The data to send in the form of a IOVec. + * Flags - Send flags. + */ + +static +ERL_NIF_TERM nif_send(ErlNifEnv* env, + int argc, + const ERL_NIF_TERM argv[]) +{ + SocketDescriptor* descP; + ERL_NIF_TERM sendRef; + ErlNifBinary data; + unsigned int eflags; + int flags; + ERL_NIF_TERM res; + + /* Extract arguments and perform preliminary validation */ + + if ((argc != 4) || + !enif_get_resource(env, argv[0], sockets, (void**) &descP) || + !enif_inspect_iolist_as_binary(env, argv[2], &data) || + !enif_get_uint(env, argv[3], &eflags)) { + return enif_make_badarg(env); + } + sendRef = argv[1]; + + if (!IS_CONNECTED(descP)) + return make_error(env, atom_enotconn); + + if (!esendflags2sendflags(eflags, &flags)) + return enif_make_badarg(env); + + MLOCK(descP->writeMtx); + + res = nsend(env, descP, sendRef, &data, flags); + + MUNLOCK(descP->writeMtx); + + return res; +} + + +/* What do we do when another process tries to write + * when the current writer has a select already waiting? + * Queue it? And what about simultaneous read and write? + * Queue up all operations towards the socket? + * + * We (may) need a currentOp field and an ops queue field. + */ +static +ERL_NIF_TERM nsend(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM sendRef, + ErlNifBinary* dataP, + int flags) +{ + int save_errno; + ssize_t written; + + if (!descP->isWritable) + return enif_make_badarg(env); + + /* We ignore the wrap for the moment. + * Maybe we should issue a wrap-message to controlling process... + */ + cnt_inc(&descP->writeTries, 1); + + written = sock_send(descP->sock, dataP->data, dataP->size, flags); + + if (written == dataP->size) { + + cnt_inc(&descP->writePkgCnt, 1); + cnt_inc(&descP->writeByteCnt, written); + + return atom_ok; + + } else if (written < 0) { + + /* Ouch, check what kind of failure */ + save_errno = sock_errno(); + if ((save_errno != EAGAIN) && + (save_errno != EINTR)) { + + cnt_inc(&descP->writeFails, 1); + + return make_error2(env, save_errno); + + } else { + + /* Ok, try again later */ + + written = 0; + + } + } + + /* We failed to write the *entire* packet (anything less then size + * of the packet, which is 0 <= written < sizeof packet), + * so schedule the rest for later. + */ + + cnt_inc(&descP->writeWaits, 1); + + SELECT(env, descP->sock, (ERL_NIF_SELECT_WRITE), + descP, NULL, sendRef); + + return make_ok(env, enif_make_int(env, written)); + +} + + +#ifdef FOBAR +static +ERL_NIF_TERM nwritev(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM sendRef, + ERL_NIF_TERM data) +{ + ERL_NIF_TERM tail; + ErlNifIOVec vec; + ErlNifIOVec* iovec = &vec; + SysIOVec* sysiovec; + int save_errno; + int iovcnt, n; + + if (!enif_inspect_iovec(env, MAX_VSZ, data, &tail, &iovec)) + return enif_make_badarg(env); + + if (enif_ioq_size(descP->outQ) > 0) { + /* If the I/O queue contains data we enqueue the iovec + * and then peek the data to write out of the queue. + */ + if (!enif_ioq_enqv(q, iovec, 0)) + return -3; + + sysiovec = enif_ioq_peek(descP->outQ, &iovcnt); + + } else { + /* If the I/O queue is empty we skip the trip through it. */ + iovcnt = iovec->iovcnt; + sysiovec = iovec->iov; + } + + /* Attempt to write the data */ + n = writev(fd, sysiovec, iovcnt); + saved_errno = errno; + + if (enif_ioq_size(descP->outQ) == 0) { + /* If the I/O queue was initially empty we enqueue any + remaining data into the queue for writing later. */ + if (n >= 0 && !enif_ioq_enqv(descP->outQ, iovec, n)) + return -3; + } else { + /* Dequeue any data that was written from the queue. */ + if (n > 0 && !enif_ioq_deq(descP->outQ, n, NULL)) + return -4; + } + /* return n, which is either number of bytes written or -1 if + some error happened */ + errno = saved_errno; + return n; +} +#endif + + + +/* ---------------------------------------------------------------------- + * U t i l i t y F u n c t i o n s + * ---------------------------------------------------------------------- + */ + +/* compare_pids - Test if two pids are equal + * + */ static int compare_pids(ErlNifEnv* env, const ErlNifPid* pid1, @@ -2034,11 +2267,6 @@ int compare_pids(ErlNifEnv* env, } -/* ---------------------------------------------------------------------- - * U t i l i t y F u n c t i o n s - * ---------------------------------------------------------------------- - */ - /* edomain2domain - convert internal (erlang) domain to (proper) domain * * Note that only a subset is supported. @@ -2070,7 +2298,6 @@ BOOLEAN_T edomain2domain(int edomain, int* domain) } - /* etype2type - convert internal (erlang) type to (proper) type * * Note that only a subset is supported. @@ -2103,7 +2330,6 @@ BOOLEAN_T etype2type(int etype, int* type) } - /* eproto2proto - convert internal (erlang) protocol to (proper) protocol * * Note that only a subset is supported. @@ -2193,6 +2419,58 @@ BOOLEAN_T emap2netns(ErlNifEnv* env, ERL_NIF_TERM map, char** netns) #endif +/* esendflags2sendflags - convert internal (erlang) send flags to (proper) + * send flags. + */ +static +BOOLEAN_T esendflags2sendflags(unsigned int esendflags, int* sendflags) +{ + unsigned int ef; + int tmp = 0; + + for (ef = SOCKET_SEND_FLAG_LOW; ef <= SOCKET_SEND_FLAG_HIGH; ef++) { + switch (ef) { + case SOCKET_SEND_FLAG_CONFIRM: + tmp |= MSG_CONFIRM; + break; + + case SOCKET_SEND_FLAG_DONTROUTE: + tmp |= MSG_DONTROUTE; + break; + + case SOCKET_SEND_FLAG_DONTWAIT: + tmp |= MSG_DONTWAIT; + break; + + case SOCKET_SEND_FLAG_EOR: + tmp |= MSG_EOR; + break; + + case SOCKET_SEND_FLAG_MORE: + tmp |= MSG_MORE; + break; + + case SOCKET_SEND_FLAG_NOSIGNAL: + tmp |= MSG_NOSIGNAL; + break; + + case SOCKET_SEND_FLAG_OOB: + tmp |= MSG_OOB; + break; + + default: + return FALSE; + } + + } + + *sendflags = tmp; + + return TRUE; +} + + + /* Create an ok two (2) tuple in the form: {ok, Any}. * The second element (Any) is already in the form of an * ERL_NIF_TERM so all we have to do is create the tuple. @@ -2239,6 +2517,31 @@ ERL_NIF_TERM make_error2(ErlNifEnv* env, int err) +/* ---------------------------------------------------------------------- + * C o u n t e r F u n c t i o n s + * ---------------------------------------------------------------------- + */ + +static +BOOLEAN_T cnt_inc(uint32_t* cnt, uint32_t inc) +{ + BOOLEAN_T wrap; + uint32_t max = 0xFFFFFFFF; + uint32_t current = *cnt; + + if ((max - inc) >= current) { + *cnt += inc; + wrap = FALSE; + } else { + *cnt = inc - (max - current) - 1; + wrap = TRUE; + } + + return (wrap); +} + + + /* ---------------------------------------------------------------------- * C a l l b a c k F u n c t i o n s * ---------------------------------------------------------------------- @@ -2397,7 +2700,7 @@ int on_load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info) atom_eafnosupport = MKA(env, str_eafnosupport); atom_einval = MKA(env, str_einval); atom_eisconn = MKA(env, str_eisconn); - atom_eisnconn = MKA(env, str_eisnconn); + atom_enotconn = MKA(env, str_enotconn); // atom_exalloc = MKA(env, str_exalloc); atom_exbadstate = MKA(env, str_exbadstate); atom_exbusy = MKA(env, str_exbusy); diff --git a/erts/preloaded/src/socket.erl b/erts/preloaded/src/socket.erl index f3a3d493ac..985b45a956 100644 --- a/erts/preloaded/src/socket.erl +++ b/erts/preloaded/src/socket.erl @@ -33,13 +33,21 @@ listen/1, listen/2, accept/1, accept/2, - send/2, send/3, sendto/5, - recv/1, recv/2, recvfrom/1, recvfrom/2, + send/2, send/3, send/4, + sendto/5, + %% sendmsg/4, + %% writev/4, OR SENDV? It will be strange for recv then: recvv (instead of readv) + + recv/1, recv/2, + recvfrom/1, recvfrom/2, + %% recvmsg/4, + %% readv/3, close/1, setopt/3, getopt/2, + %% ????? formated_timestamp/0 ]). @@ -457,45 +465,103 @@ flush_select_msgs(LSRef, Ref) -> end. + %% =========================================================================== %% %% send, sendto, sendmsg - send a message on a socket %% --spec send(Socket, Data, Flags) -> ok | {error, Reason} when - Socket :: socket(), - Data :: binary(), - Flags :: send_flags(), - Reason :: term(). - send(Socket, Data) -> - send(Socket, Data, ?SOCKET_SEND_FLAGS_DEFAULT). + send(Socket, Data, ?SOCKET_SEND_FLAGS_DEFAULT, infinity). -send({socket, _, SockRef}, Data, Flags) - when is_binary(Data) andalso is_list(Flags) -> +send(Socket, Data, Flags) when is_list(Flags) -> + send(Socket, Data, Flags, infinity); +send(Socket, Data, Timeout) -> + send(Socket, Data, ?SOCKET_SEND_FLAGS_DEFAULT, Timeout). + + +-spec send(Socket, Data, Flags, Timeout) -> ok | {error, Reason} when + Socket :: socket(), + Data :: iodata(), + Flags :: send_flags(), + Timeout :: timeout(), + Reason :: term(). + +send(Socket, Data, Flags, Timeout) when is_list(Data) -> + Bin = erlang:list_to_binary(Data), + send(Socket, Bin, Flags, Timeout); +send(Socket, Data, Flags, Timeout) when is_binary(Data) andalso is_list(Flags) -> EFlags = enc_send_flags(Flags), - nif_send(SockRef, Data, EFlags). + do_send(Socket, make_ref(), Data, EFlags, Timeout). + +do_send(SockRef, SendRef, Data, _EFlags, Timeout) + when (Timeout =< 0) -> + nif_cancel(SockRef, SendRef), + flush_select_msgs(SockRef, SendRef), + {error, {timeout, size(Data)}}; +do_send(SockRef, SendRef, Data, EFlags, Timeout) -> + TS = timestamp(Timeout), + case nif_send(SockRef, SendRef, Data, EFlags) of + ok -> + {ok, next_timeout(TS, Timeout)}; + {ok, Written} -> + %% We are partially done, wait for continuation + receive + {select, SockRef, SendRef, ready_output} when (Written > 0) -> + <<_:Written/binary, Rest/binary>> = Data, + do_send(SockRef, make_ref(), Rest, EFlags, + next_timeout(TS, Timeout)); + {select, SockRef, SendRef, ready_output} -> + do_send(SockRef, make_ref(), Data, EFlags, + next_timeout(TS, Timeout)) + after Timeout -> + nif_cancel(SockRef, SendRef), + flush_select_msgs(SockRef, SendRef), + {error, timeout} + end; + {error, eagain} -> + receive + {select, SockRef, SendRef, ready_output} -> + do_send(SockRef, SendRef, Data, EFlags, + next_timeout(TS, Timeout)) + after Timeout -> + nif_cancel(SockRef, SendRef), + flush_select_msgs(SockRef, SendRef), + {error, timeout} + end; + + {error, _} = ERROR -> + ERROR + end. + + %% --------------------------------------------------------------------------- +%% +%% Do we need a timeout argument here also? +%% -spec sendto(Socket, Data, Flags, DestAddr, Port) -> ok | {error, Reason} when Socket :: socket(), Data :: binary(), Flags :: send_flags(), - DestAddr :: ip_address(), + DestAddr :: null | ip_address(), Port :: port_number(), Reason :: term(). sendto({socket, _, SockRef}, Data, Flags, DestAddr, DestPort) when is_binary(Data) andalso is_list(Flags) andalso - (is_tuple(DestAddr) andalso - ((size(DestAddr) =:= 4) orelse - (size(DestAddr) =:= 8))) andalso + ((is_tuple(DestAddr) andalso + ((size(DestAddr) =:= 4) orelse + (size(DestAddr) =:= 8))) orelse + (DestAddr =:= null)) andalso (is_integer(DestPort) andalso (DestPort >= 0)) -> + %% We may need something like send/4 above? EFlags = enc_send_flags(Flags), - nif_sendto(SockRef, Data, EFlags, DestAddr, DestPort). + nif_sendto(SockRef, make_ref(), Data, EFlags, DestAddr, DestPort). + %% --------------------------------------------------------------------------- @@ -508,7 +574,73 @@ sendto({socket, _, SockRef}, Data, Flags, DestAddr, DestPort) +%% =========================================================================== +%% +%% writev - write data into multiple buffers +%% + +%% send(Socket, Data, Flags, Timeout) +%% when (is_list(Data) orelse is_binary(Data)) andalso is_list(Flags) -> +%% IOVec = erlang:iolist_to_iovec(Data), +%% EFlags = enc_send_flags(Flags), +%% send_iovec(Socket, IOVec, EFlags, Timeout). + + +%% %% Iterate over the IO-vector (list of binaries). + +%% send_iovec(_Socket, [] = _IOVec, _EFlags, _Timeout) -> +%% ok; +%% send_iovec({socket, _, SockRef} = Socket, [Bin|IOVec], EFlags, Timeout) -> +%% case do_send(SockRef, make_ref(), Bin, EFlags, Timeout) of +%% {ok, NewTimeout} -> +%% send_iovec(Socket, IOVec, EFlags, NewTimeout); +%% {error, _} = ERROR -> +%% ERROR +%% end. + + +%% do_send(SockRef, SendRef, Data, _EFlags, Timeout) +%% when (Timeout < 0) -> +%% nif_cancel(SockRef, SendRef), +%% flush_select_msgs(SockRef, SendRef), +%% {error, {timeout, size(Data)}}; +%% do_send(SockRef, SendRef, Data, EFlags, Timeout) -> +%% TS = timestamp(Timeout), +%% case nif_send(SockRef, SendRef, Data, EFlags) of +%% ok -> +%% {ok, next_timeout(TS, Timeout)}; +%% {ok, Written} -> +%% %% We are partially done, wait for continuation +%% receive +%% {select, SockRef, SendRef, ready_output} -> +%% <<_:Written/binary, Rest/binary>> = Data, +%% do_send(SockRef, make_ref(), Rest, EFlags, +%% next_timeout(TS, Timeout)) +%% after Timeout -> +%% nif_cancel(SockRef, SendRef), +%% flush_select_msgs(SockRef, SendRef), +%% {error, timeout} +%% end; +%% {error, eagain} -> +%% receive +%% {select, SockRef, SendRef, ready_output} -> +%% do_send(SockRef, SendRef, Data, EFlags, +%% next_timeout(TS, Timeout)) +%% after Timeout -> +%% nif_cancel(SockRef, SendRef), +%% flush_select_msgs(SockRef, SendRef), +%% {error, timeout} +%% end; + +%% {error, _} = ERROR -> +%% ERROR +%% end. + + +%% =========================================================================== +%% %% recv, recvfrom, recvmsg - receive a message from a socket +%% -spec recv(Socket, Flags) -> {ok, Data} | {error, Reason} when Socket :: socket(), @@ -540,6 +672,7 @@ recvfrom({socket, _, SockRef}, Flags) when is_list(Flags) -> EFlags = enc_recv_flags(Flags), nif_recvfrom(SockRef, EFlags). + %% -spec recvmsg(Socket, [out] MsgHdr, Flags) -> {ok, Data} | {error, Reason} when %% Socket :: socket(), %% MsgHdr :: msg_header(), @@ -549,6 +682,13 @@ recvfrom({socket, _, SockRef}, Flags) when is_list(Flags) -> +%% =========================================================================== +%% +%% readv - read data into multiple buffers +%% + + + %% close - close a file descriptor -spec close(Socket) -> ok | {error, Reason} when @@ -817,10 +957,10 @@ nif_listen(_SRef, _Backlog) -> nif_accept(_SRef, _Ref) -> erlang:error(badarg). -nif_send(_SRef, _Data, _Flags) -> +nif_send(_SockRef, _SendRef, _Data, _Flags) -> erlang:error(badarg). -nif_sendto(_SRef, _Data, _Flags, _Dest, _Port) -> +nif_sendto(_SRef, _SendRef, _Data, _Flags, _Dest, _Port) -> erlang:error(badarg). nif_recv(_SRef, _Flags) -> -- cgit v1.2.3 From 5920705deb70a44311e1b7552cfa73553f284164 Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Fri, 13 Apr 2018 20:50:42 +0200 Subject: [socket-nif] Implemented sendto Still not handling queue'ing of multiple send requests. --- erts/emulator/nifs/common/socket_nif.c | 777 ++++++++++++++++++++++----------- erts/preloaded/src/socket.erl | 90 +++- 2 files changed, 611 insertions(+), 256 deletions(-) diff --git a/erts/emulator/nifs/common/socket_nif.c b/erts/emulator/nifs/common/socket_nif.c index bf9179d857..46c5c696e2 100644 --- a/erts/emulator/nifs/common/socket_nif.c +++ b/erts/emulator/nifs/common/socket_nif.c @@ -340,6 +340,7 @@ typedef union { enif_get_atom((E), (TE), (BP), (MAX), ERL_NIF_LATIN1) #define GET_BIN(E, TE, BP) enif_inspect_iolist_as_binary((E), (TE), (BP)) #define GET_INT(E, TE, IP) enif_get_int((E), (TE), (IP)) +#define GET_UINT(E, TE, IP) enif_get_uint((E), (TE), (IP)) #define GET_TUPLE(E, TE, TSZ, TA) enif_get_tuple((E), (TE), (TSZ), (TA)) @@ -366,6 +367,8 @@ typedef union { #define sock_open(domain, type, proto) \ make_noninheritable_handle(socket((domain), (type), (proto))) #define sock_send(s,buf,len,flag) send((s),(buf),(len),(flag)) +#define sock_sendto(s,buf,blen,flag,addr,alen) \ + sendto((s),(buf),(blen),(flag),(addr),(alen)) #define sock_errno() WSAGetLastError() #define sock_create_event(s) WSACreateEvent() @@ -393,6 +396,8 @@ static unsigned long one_value = 1; #define sock_name(s, addr, len) getsockname((s), (addr), (len)) #define sock_open(domain, type, proto) socket((domain), (type), (proto)) #define sock_send(s,buf,len,flag) send((s), (buf), (len), (flag)) +#define sock_sendto(s,buf,blen,flag,addr,alen) \ + sendto((s),(buf),(blen),(flag),(addr),(alen)) #define sock_errno() errno #define sock_create_event(s) (s) /* return file descriptor */ @@ -510,6 +515,39 @@ typedef struct { } SocketData; +/* typedef struct socket_queue_element { */ +/* struct socket_queue_element* next; */ +/* /\* */ +/* unsigned int tag; */ +/* union { */ +/* SocketAcceptor acceptor; */ +/* } u; */ +/* *\/ */ +/* SocketAcceptor acceptor; */ +/* } SocketQueueElement; */ + +/* typedef struct socket_queue { */ +/* SocketQueueElement* first; */ +/* SocketQueueElement* last; */ +/* } SocketQueue; */ + +/* Macros for defining the various queues (accept, send receive) */ +#define SOCKET_QUEUE_ELEMENT(QE,QEP) \ + typedef struct socket_queue_element_##QEP { \ + struct socket_queue_element_##QEP* next; \ + QE elem; \ + } QE##Element; + +#define SOCKET_QUEUE(QE,Q) \ + typedef struct { \ + QE* first; \ + QE* last; \ + } Q; + +/* The Acceptor Queue types */ +SOCKET_QUEUE_ELEMENT(SocketAcceptor, acceptor); +SOCKET_QUEUE(SocketAcceptorElement, SocketAcceptQueue); + /* ---------------------------------------------------------------------- * F o r w a r d s * ---------------------------------------------------------------------- @@ -576,30 +614,6 @@ static ERL_NIF_TERM nif_cancel(ErlNifEnv* env, const ERL_NIF_TERM argv[]); -static char* decode_laddress(ErlNifEnv* env, - int domain, - ERL_NIF_TERM localAddr, - SocketAddress* localP); -static char* decode_laddress_binary(ErlNifEnv* env, - int domain, - ERL_NIF_TERM localAddr, - SocketAddress* localP); -static char* decode_laddress_tuple(ErlNifEnv* env, - int domain, - ERL_NIF_TERM laddr, - SocketAddress* localP); -static char* decode_address_tuple(ErlNifEnv* env, - int domain, - const ERL_NIF_TERM* addrt, - int port, - SocketAddress* localP); -static char* decode_address_atom(ErlNifEnv* env, - int domain, - char* addr, - int addrLen, - int port, - SocketAddress* localP); - static ERL_NIF_TERM nopen(ErlNifEnv* env, int domain, int type, @@ -629,9 +643,56 @@ static ERL_NIF_TERM nsend(ErlNifEnv* env, ERL_NIF_TERM sendRef, ErlNifBinary* dataP, int flags); +static ERL_NIF_TERM nsendto(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM sendRef, + ErlNifBinary* dataP, + int flags, + SocketAddress* toAddrP); + +static ERL_NIF_TERM send_check_result(ErlNifEnv* env, + SocketDescriptor* descP, + ssize_t written, + ssize_t dataSize, + ERL_NIF_TERM sendRef); + static ERL_NIF_TERM nfinalize_connection(ErlNifEnv* env, SocketDescriptor* descP); + +static char* decode_laddress(ErlNifEnv* env, + int domain, + ERL_NIF_TERM localAddr, + SocketAddress* localP); +static char* decode_laddress_binary(ErlNifEnv* env, + int domain, + ERL_NIF_TERM localAddr, + SocketAddress* localP); +static char* decode_laddress_tuple(ErlNifEnv* env, + int domain, + ERL_NIF_TERM laddr, + SocketAddress* localP); +static char* decode_address_tuple(ErlNifEnv* env, + int domain, + const ERL_NIF_TERM* addrt, + int port, + SocketAddress* localP); +static char* decode_address_atom(ErlNifEnv* env, + int domain, + char* addr, + int addrLen, + int port, + SocketAddress* localP); +static char* decode_send_addr(ErlNifEnv* env, + int domain, + ERL_NIF_TERM addr, + int port, + SocketAddress** toAddrP); +static char* decode_send_addr_tuple(ErlNifEnv* env, + int domain, + ERL_NIF_TERM addr, + int port, + SocketAddress* toAddrP); static BOOLEAN_T verify_is_connected(SocketDescriptor* descP, int* err); static SocketDescriptor* alloc_descriptor(SOCKET sock, HANDLE event); @@ -640,6 +701,8 @@ static int compare_pids(ErlNifEnv* env, const ErlNifPid* pid1, const ErlNifPid* pid2); + + static BOOLEAN_T edomain2domain(int edomain, int* domain); static BOOLEAN_T etype2type(int etype, int* type); static BOOLEAN_T eproto2proto(int eproto, int* proto); @@ -965,7 +1028,7 @@ ERL_NIF_TERM nopen(ErlNifEnv* env, */ SELECT(env, event, - ERL_NIF_SELECT_READ, + (ERL_NIF_SELECT_READ), descP, NULL, atom_undefined); #endif @@ -1115,6 +1178,7 @@ ERL_NIF_TERM nif_bind(ErlNifEnv* env, /* Make sure we are ready * Not sure how this would even happen, but... */ + /* WHY NOT !IS_OPEN(...) */ if (descP->state != SOCKET_STATE_OPEN) return make_error(env, atom_exbadstate); @@ -1308,7 +1372,7 @@ char* decode_laddress_tuple(ErlNifEnv* env, unsigned int len; char a[16]; // Just in case... - if (!(GET_ATOM_LEN(env, laddrt[1], &len) && + if (!(GET_ATOM_LEN(env, laddrt[0], &len) && (len > 0) && (len <= (sizeof("loopback"))))) return str_einval; @@ -1327,154 +1391,6 @@ char* decode_laddress_tuple(ErlNifEnv* env, } -/* Decode the 4- or 8-element address tuple - * and initiate the socket address structure. - */ -static -char* decode_address_tuple(ErlNifEnv* env, - int domain, - const ERL_NIF_TERM* addrt, - int port, - SocketAddress* addrP) -{ - - /* We now *know* that the size of the tuple is correct, - * so we don't need to check anything here, just unpack. - */ - - switch (domain) { - case AF_INET: - { - int a, v; - char laddr[4]; - - sys_memzero((char*)&addrP->u, sizeof(struct sockaddr_in)); -#ifndef NO_SA_LEN - addrP->u.sai.sin_len = sizeof(struct sockaddr_in); -#endif - addrP->u.sai.sin_family = domain; - addrP->u.sai.sin_port = sock_htons(port); - for (a = 0; a < 4; a++) { - if (!GET_INT(env, addrt[a], &v)) - return str_einval; - laddr[a] = v; - } - sys_memcpy(&addrP->u.sai.sin_addr, &laddr, sizeof(laddr)); - addrP->len = sizeof(struct sockaddr_in); - return NULL; - } - break; - -#if defined(HAVE_IN6) && defined(AF_INET6) - case AF_INET6: - { - int a, v; - char laddr[16]; - - sys_memzero((char*)&addrP->u, sizeof(struct sockaddr_in6)); -#ifndef NO_SA_LEN - addrP->u.sai6.sin6_len = sizeof(struct sockaddr_in6); -#endif - addrP->u.sai6.sin6_family = domain; - addrP->u.sai6.sin6_port = sock_htons(port); - addrP->u.sai6.sin6_flowinfo = 0; - /* The address tuple is of size 8 - * and each element is a two byte integer - */ - for (a = 0; a < 8; a++) { - if (!GET_INT(env, addrt[a], &v)) - return str_einval; - laddr[a*2 ] = ((v >> 8) & 0xFF); - laddr[a*2+1] = (v & 0xFF); - } - sys_memcpy(&addrP->u.sai6.sin6_addr, &laddr, sizeof(laddr)); - addrP->len = sizeof(struct sockaddr_in6); - return NULL; - } - break; -#endif - - } /* switch (domain) */ - - return str_eafnosupport; - -} - - -/* Decode the address when its an atom. - * Currently we only accept two atoms: 'any' and 'loopback' - */ -static -char* decode_address_atom(ErlNifEnv* env, - int domain, - char* addr, - int addrLen, - int port, - SocketAddress* localP) -{ - BOOLEAN_T any; - - if (strncmp(addr, "any", addrLen) == 0) { - any = TRUE; - } if (strncmp(addr, "loopback", addrLen) == 0) { - any = FALSE; - } else { - return str_einval; - } - - /* If we get this far, we *know* its either 'any' or 'loopback' */ - - switch (domain) { - case AF_INET: - { - struct in_addr addr; - if (any) { - addr.s_addr = sock_htonl(INADDR_ANY); - } else { - addr.s_addr = sock_htonl(INADDR_LOOPBACK); - } - sys_memzero((char*) localP, sizeof(struct sockaddr_in)); -#ifndef NO_SA_LEN - localP->u.sai.sin_len = sizeof(struct sockaddr_in6); -#endif - localP->u.sai.sin_family = domain; - localP->u.sai.sin_port = sock_htons(port); - localP->u.sai.sin_addr.s_addr = addr.s_addr; - localP->len = sizeof(struct sockaddr_in); - } - break; - -#if defined(HAVE_IN6) && defined(AF_INET6) - case AF_INET6: - { - const struct in6_addr* paddr; - if (any) { - paddr = &in6addr_any; - } else { - paddr = &in6addr_loopback; - } - sys_memzero((char*)localP, sizeof(struct sockaddr_in6)); -#ifndef NO_SA_LEN - localP->u.sai6.sin6_len = sizeof(struct sockaddr_in6); -#endif - localP->u.sai6.sin6_family = domain; - localP->u.sai6.sin6_port = sock_htons(port); - localP->u.sai6.sin6_flowinfo = 0; - localP->u.sai6.sin6_addr = *paddr; - localP->len = sizeof(struct sockaddr_in6); - } - break; -#endif - - default: - return str_einval; - break; - } - - return NULL; -} - - /* ---------------------------------------------------------------------- * nif_connect @@ -2028,50 +1944,6 @@ ERL_NIF_TERM naccept_accepting(ErlNifEnv* env, -/* *** alloc_descriptor *** - * Allocate and perform basic initialization of a socket descriptor. - * - */ -static -SocketDescriptor* alloc_descriptor(SOCKET sock, HANDLE event) -{ - SocketDescriptor* descP; - - if ((descP = enif_alloc_resource(sockets, sizeof(SocketDescriptor))) != NULL) { - char buf[64]; /* Buffer used for building the mutex name */ - - sprintf(buf, "socket[w,%d]", sock); - descP->writeMtx = MCREATE(buf); - - sprintf(buf, "socket[r,%d]", sock); - descP->readMtx = MCREATE(buf); - - sprintf(buf, "socket[acc,%d]", sock); - descP->accMtx = MCREATE(buf); - - descP->dbg = SOCKET_DEBUG_DEFAULT; - descP->isWritable = TRUE; - descP->isReadable = TRUE; - descP->writePkgCnt = 0; - descP->writeByteCnt = 0; - descP->writeTries = 0; - descP->writeWaits = 0; - descP->writeFails = 0; - descP->readPkgCnt = 0; - descP->readByteCnt = 0; - descP->readTries = 0; - descP->readWaits = 0; - - descP->sock = sock; - descP->event = event; - - } - - return descP; -} - - - /* ---------------------------------------------------------------------- * nif_send * @@ -2080,7 +1952,7 @@ SocketDescriptor* alloc_descriptor(SOCKET sock, HANDLE event) * * Arguments: * Socket (ref) - Points to the socket descriptor. - * Sendref - A unique id for this (send) request. + * SendRef - A unique id for this (send) request. * Data - The data to send in the form of a IOVec. * Flags - Send flags. */ @@ -2101,8 +1973,8 @@ ERL_NIF_TERM nif_send(ErlNifEnv* env, if ((argc != 4) || !enif_get_resource(env, argv[0], sockets, (void**) &descP) || - !enif_inspect_iolist_as_binary(env, argv[2], &data) || - !enif_get_uint(env, argv[3], &eflags)) { + !GET_BIN(env, argv[2], &data) || + !GET_UINT(env, argv[3], &eflags)) { return enif_make_badarg(env); } sendRef = argv[1]; @@ -2115,6 +1987,19 @@ ERL_NIF_TERM nif_send(ErlNifEnv* env, MLOCK(descP->writeMtx); + /* We need to handle the case when another process tries + * to write at the same time. + * If the current write could not write its entire package + * this time (resulting in an select). The write of the + * other process must be made to wait until current + * is done! + * Basically, we need a write queue! + * + * A 'writing' field (boolean), which is set if we did + * not manage to write the entire message and reset every + * time we do. + */ + res = nsend(env, descP, sendRef, &data, flags); MUNLOCK(descP->writeMtx); @@ -2137,7 +2022,6 @@ ERL_NIF_TERM nsend(ErlNifEnv* env, ErlNifBinary* dataP, int flags) { - int save_errno; ssize_t written; if (!descP->isWritable) @@ -2150,48 +2034,117 @@ ERL_NIF_TERM nsend(ErlNifEnv* env, written = sock_send(descP->sock, dataP->data, dataP->size, flags); - if (written == dataP->size) { + return send_check_result(env, descP, written, dataP->size, sendRef); - cnt_inc(&descP->writePkgCnt, 1); - cnt_inc(&descP->writeByteCnt, written); +} - return atom_ok; - } else if (written < 0) { +/* ---------------------------------------------------------------------- + * nif_sendto + * + * Description: + * Send a message on a socket + * + * Arguments: + * Socket (ref) - Points to the socket descriptor. + * SendRef - A unique id for this (send) request. + * Data - The data to send in the form of a IOVec. + * Flags - Send flags. + * DestAddr - Destination address. + * DestPort - Destination Port. + */ - /* Ouch, check what kind of failure */ - save_errno = sock_errno(); - if ((save_errno != EAGAIN) && - (save_errno != EINTR)) { +static +ERL_NIF_TERM nif_sendto(ErlNifEnv* env, + int argc, + const ERL_NIF_TERM argv[]) +{ + SocketDescriptor* descP; + ERL_NIF_TERM sendRef; + ErlNifBinary data; + unsigned int eflags; + int flags; + ERL_NIF_TERM addr; + int port; + SocketAddress remoteAddr; + SocketAddress* remoteAddrP = &remoteAddr; + char* xerr; + // ERL_NIF_TERM res; - cnt_inc(&descP->writeFails, 1); + /* Extract arguments and perform preliminary validation */ - return make_error2(env, save_errno); + if ((argc != 6) || + !enif_get_resource(env, argv[0], sockets, (void**) &descP) || + !GET_BIN(env, argv[2], &data) || + !GET_UINT(env, argv[3], &eflags) || + !GET_INT(env, argv[5], &port)) { + return enif_make_badarg(env); + } + sendRef = argv[1]; + addr = argv[4]; - } else { + /* THIS TEST IS NOT CORRECT!!! */ + if (!IS_OPEN(descP)) + return make_error(env, atom_einval); - /* Ok, try again later */ + if (!esendflags2sendflags(eflags, &flags)) + return enif_make_badarg(env); - written = 0; + if ((xerr = decode_send_addr(env, descP->domain, + addr, port, + &remoteAddrP)) != NULL) + return make_error1(env, xerr); - } - } + return nsendto(env, descP, sendRef, &data, flags, remoteAddrP); +} - /* We failed to write the *entire* packet (anything less then size - * of the packet, which is 0 <= written < sizeof packet), - * so schedule the rest for later. - */ - cnt_inc(&descP->writeWaits, 1); +static +ERL_NIF_TERM nsendto(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM sendRef, + ErlNifBinary* dataP, + int flags, + SocketAddress* toAddrP) +{ + ssize_t written; - SELECT(env, descP->sock, (ERL_NIF_SELECT_WRITE), - descP, NULL, sendRef); + if (!descP->isWritable) + return enif_make_badarg(env); - return make_ok(env, enif_make_int(env, written)); + /* We ignore the wrap for the moment. + * Maybe we should issue a wrap-message to controlling process... + */ + cnt_inc(&descP->writeTries, 1); + + if (toAddrP != NULL) { + written = sock_sendto(descP->sock, + dataP->data, dataP->size, flags, + &toAddrP->u.sa, toAddrP->len); + } else { + written = sock_sendto(descP->sock, + dataP->data, dataP->size, flags, + NULL, 0); + } + return send_check_result(env, descP, written, dataP->size, sendRef); } + +/* ---------------------------------------------------------------------- + * nif_writev / nif_sendv + * + * Description: + * Send a message (vector) on a socket + * + * Arguments: + * Socket (ref) - Points to the socket descriptor. + * SendRef - A unique id for this (send) request. + * Data - A vector of binaries + * Flags - Send flags. + */ + #ifdef FOBAR static ERL_NIF_TERM nwritev(ErlNifEnv* env, @@ -2247,11 +2200,343 @@ ERL_NIF_TERM nwritev(ErlNifEnv* env, + + + /* ---------------------------------------------------------------------- * U t i l i t y F u n c t i o n s * ---------------------------------------------------------------------- */ +static +ERL_NIF_TERM send_check_result(ErlNifEnv* env, + SocketDescriptor* descP, + ssize_t written, + ssize_t dataSize, + ERL_NIF_TERM sendRef) +{ + if (written == dataSize) { + + cnt_inc(&descP->writePkgCnt, 1); + cnt_inc(&descP->writeByteCnt, written); + + return atom_ok; + + } else if (written < 0) { + + /* Ouch, check what kind of failure */ + int save_errno = sock_errno(); + if ((save_errno != EAGAIN) && + (save_errno != EINTR)) { + + cnt_inc(&descP->writeFails, 1); + + return make_error2(env, save_errno); + + } else { + + /* Ok, try again later */ + + written = 0; + + } + } + + /* We failed to write the *entire* packet (anything less then size + * of the packet, which is 0 <= written < sizeof packet), + * so schedule the rest for later. + */ + + cnt_inc(&descP->writeWaits, 1); + + SELECT(env, descP->sock, (ERL_NIF_SELECT_WRITE), + descP, NULL, sendRef); + + return make_ok(env, enif_make_int(env, written)); + +} + + +/* The rather odd thing about the 'toAddrP' (the **) is + * because we need to be able to return a NULL pointer, + * in the case of the dest address is the atom 'null'. + * Its possible to call the sendto function with the + * args NULL (address) and 0 (port number). + * + * This function whouls really have a char* return value + * type!! + */ +static +char* decode_send_addr(ErlNifEnv* env, + int domain, + ERL_NIF_TERM addr, + int port, + SocketAddress** toAddrP) +{ + if (IS_ATOM(env, addr)) { + unsigned int len; + char a[16]; // Just in case... + + /* The only acceptable value is the atom 'null' */ + + if (!(GET_ATOM_LEN(env, addr, &len) && + (len > 0) && + (len <= (sizeof("null"))))) + return str_einval; + + if (!GET_ATOM(env, addr, a, sizeof(a))) + return str_einval; + + *toAddrP = NULL; + if (strncmp(a, "null", len) == 0) + return NULL; + else + return str_einval; + + } else if (IS_TUPLE(env, addr)) { + /* We now know that the we have a proper address. */ + return decode_send_addr_tuple(env, domain, addr, port, *toAddrP); + } else { + return str_einval; + } +} + + +static +char* decode_send_addr_tuple(ErlNifEnv* env, + int domain, + ERL_NIF_TERM addr, + int port, + SocketAddress* toAddrP) +{ + /* We handle two different tuples: + * - size 4 (INET) + * - size 8 (INET6) + */ + + const ERL_NIF_TERM* addrt; + int addrtSz; + + if (!GET_TUPLE(env, addr, &addrtSz, &addrt)) + return str_einval; // PLACEHOLDER + + switch (domain) { + case AF_INET: + if (addrtSz != 4) + return str_einval; + break; + +#if defined(HAVE_IN6) && defined(AF_INET6) + case AF_INET6: + if (addrtSz != 8) + return str_einval; + break; +#endif + + default: + return str_eafnosupport; + break; + } + + return decode_address_tuple(env, domain, + addrt, port, + toAddrP); + +} + + +/* Decode the 4- or 8-element address tuple + * and initiate the socket address structure. + */ +static +char* decode_address_tuple(ErlNifEnv* env, + int domain, + const ERL_NIF_TERM* addrt, + int port, + SocketAddress* addrP) +{ + + /* We now *know* that the size of the tuple is correct, + * so we don't need to check anything here, just unpack. + */ + + switch (domain) { + case AF_INET: + { + int a, v; + char laddr[4]; + + sys_memzero((char*)&addrP->u, sizeof(struct sockaddr_in)); +#ifndef NO_SA_LEN + addrP->u.sai.sin_len = sizeof(struct sockaddr_in); +#endif + addrP->u.sai.sin_family = domain; + addrP->u.sai.sin_port = sock_htons(port); + for (a = 0; a < 4; a++) { + if (!GET_INT(env, addrt[a], &v)) + return str_einval; + laddr[a] = v; + } + sys_memcpy(&addrP->u.sai.sin_addr, &laddr, sizeof(laddr)); + addrP->len = sizeof(struct sockaddr_in); + return NULL; + } + break; + +#if defined(HAVE_IN6) && defined(AF_INET6) + case AF_INET6: + { + int a, v; + char laddr[16]; + + sys_memzero((char*)&addrP->u, sizeof(struct sockaddr_in6)); +#ifndef NO_SA_LEN + addrP->u.sai6.sin6_len = sizeof(struct sockaddr_in6); +#endif + addrP->u.sai6.sin6_family = domain; + addrP->u.sai6.sin6_port = sock_htons(port); + addrP->u.sai6.sin6_flowinfo = 0; + /* The address tuple is of size 8 + * and each element is a two byte integer + */ + for (a = 0; a < 8; a++) { + if (!GET_INT(env, addrt[a], &v)) + return str_einval; + laddr[a*2 ] = ((v >> 8) & 0xFF); + laddr[a*2+1] = (v & 0xFF); + } + sys_memcpy(&addrP->u.sai6.sin6_addr, &laddr, sizeof(laddr)); + addrP->len = sizeof(struct sockaddr_in6); + return NULL; + } + break; +#endif + + } /* switch (domain) */ + + return str_eafnosupport; + +} + + +/* Decode the address when its an atom. + * Currently we only accept two atoms: 'any' and 'loopback' + */ +static +char* decode_address_atom(ErlNifEnv* env, + int domain, + char* addr, + int addrLen, + int port, + SocketAddress* localP) +{ + BOOLEAN_T any; + + if (strncmp(addr, "any", addrLen) == 0) { + any = TRUE; + } if (strncmp(addr, "loopback", addrLen) == 0) { + any = FALSE; + } else { + return str_einval; + } + + /* If we get this far, we *know* its either 'any' or 'loopback' */ + + switch (domain) { + case AF_INET: + { + struct in_addr addr; + if (any) { + addr.s_addr = sock_htonl(INADDR_ANY); + } else { + addr.s_addr = sock_htonl(INADDR_LOOPBACK); + } + sys_memzero((char*) localP, sizeof(struct sockaddr_in)); +#ifndef NO_SA_LEN + localP->u.sai.sin_len = sizeof(struct sockaddr_in6); +#endif + localP->u.sai.sin_family = domain; + localP->u.sai.sin_port = sock_htons(port); + localP->u.sai.sin_addr.s_addr = addr.s_addr; + localP->len = sizeof(struct sockaddr_in); + } + break; + +#if defined(HAVE_IN6) && defined(AF_INET6) + case AF_INET6: + { + const struct in6_addr* paddr; + if (any) { + paddr = &in6addr_any; + } else { + paddr = &in6addr_loopback; + } + sys_memzero((char*)localP, sizeof(struct sockaddr_in6)); +#ifndef NO_SA_LEN + localP->u.sai6.sin6_len = sizeof(struct sockaddr_in6); +#endif + localP->u.sai6.sin6_family = domain; + localP->u.sai6.sin6_port = sock_htons(port); + localP->u.sai6.sin6_flowinfo = 0; + localP->u.sai6.sin6_addr = *paddr; + localP->len = sizeof(struct sockaddr_in6); + } + break; +#endif + + default: + return str_einval; + break; + } + + return NULL; +} + + + +/* *** alloc_descriptor *** + * Allocate and perform basic initialization of a socket descriptor. + * + */ +static +SocketDescriptor* alloc_descriptor(SOCKET sock, HANDLE event) +{ + SocketDescriptor* descP; + + if ((descP = enif_alloc_resource(sockets, sizeof(SocketDescriptor))) != NULL) { + char buf[64]; /* Buffer used for building the mutex name */ + + sprintf(buf, "socket[w,%d]", sock); + descP->writeMtx = MCREATE(buf); + + sprintf(buf, "socket[r,%d]", sock); + descP->readMtx = MCREATE(buf); + + sprintf(buf, "socket[acc,%d]", sock); + descP->accMtx = MCREATE(buf); + + descP->dbg = SOCKET_DEBUG_DEFAULT; + descP->isWritable = TRUE; + descP->isReadable = TRUE; + descP->writePkgCnt = 0; + descP->writeByteCnt = 0; + descP->writeTries = 0; + descP->writeWaits = 0; + descP->writeFails = 0; + descP->readPkgCnt = 0; + descP->readByteCnt = 0; + descP->readTries = 0; + descP->readWaits = 0; + + descP->sock = sock; + descP->event = event; + + } + + return descP; +} + + /* compare_pids - Test if two pids are equal * */ diff --git a/erts/preloaded/src/socket.erl b/erts/preloaded/src/socket.erl index 985b45a956..0a78feab4e 100644 --- a/erts/preloaded/src/socket.erl +++ b/erts/preloaded/src/socket.erl @@ -135,6 +135,26 @@ -type setopt_key() :: foo. -type getopt_key() :: foo. +-record(msg_hdr, + { + %% Optional address + %% On an unconnected socket this is used to specify the target + %% address for a datagram. + %% For a connected socket, this field should be specified []. + name :: list(), + + %% Scatter/gather array + iov :: [binary()], % iovec(), + + %% Ancillary (control) data + ctrl :: binary(), + + %% Unused + flags = [] :: list() + }). +-type msg_hdr() :: #msg_hdr{}. + + -define(SOCKET_DOMAIN_LOCAL, 1). -define(SOCKET_DOMAIN_UNIX, ?SOCKET_DOMAIN_LOCAL). -define(SOCKET_DOMAIN_INET, 2). @@ -542,25 +562,75 @@ do_send(SockRef, SendRef, Data, EFlags, Timeout) -> %% Do we need a timeout argument here also? %% --spec sendto(Socket, Data, Flags, DestAddr, Port) -> ok | {error, Reason} when +sendto(Socket, Data, Flags, DestAddr, DestPort) -> + sendto(Socket, Data, Flags, DestAddr, DestPort, infinity). + +-spec sendto(Socket, Data, Flags, DestAddr, DestPort, Timeout) -> + ok | {error, Reason} when Socket :: socket(), Data :: binary(), Flags :: send_flags(), DestAddr :: null | ip_address(), - Port :: port_number(), + DestPort :: port_number(), + Timeout :: timeout(), Reason :: term(). -sendto({socket, _, SockRef}, Data, Flags, DestAddr, DestPort) +sendto(Socket, Data, Flags, DestAddr, DestPort, Timeout) when is_list(Data) -> + Bin = erlang:list_to_binary(Data), + sendto(Socket, Bin, Flags, DestAddr, DestPort, Timeout); +sendto(Socket, Data, Flags, DestAddr, DestPort, Timeout) when is_binary(Data) andalso is_list(Flags) andalso - ((is_tuple(DestAddr) andalso - ((size(DestAddr) =:= 4) orelse - (size(DestAddr) =:= 8))) orelse - (DestAddr =:= null)) andalso - (is_integer(DestPort) andalso (DestPort >= 0)) -> - %% We may need something like send/4 above? + (is_tuple(DestAddr) orelse (DestAddr =:= null)) andalso + is_integer(DestPort) andalso + (is_integer(Timeout) orelse (Timeout =:= infinity)) -> EFlags = enc_send_flags(Flags), - nif_sendto(SockRef, make_ref(), Data, EFlags, DestAddr, DestPort). + do_sendto(Socket, make_ref(), Data, EFlags, DestAddr, DestPort, Timeout). + +do_sendto(SockRef, SendRef, Data, _EFlags, _DestAddr, _DestPort, Timeout) + when (Timeout =< 0) -> + nif_cancel(SockRef, SendRef), + flush_select_msgs(SockRef, SendRef), + {error, {timeout, size(Data)}}; +do_sendto(SockRef, SendRef, Data, EFlags, DestAddr, DestPort, Timeout) -> + TS = timestamp(Timeout), + case nif_sendto(SockRef, SendRef, Data, DestAddr, DestPort, EFlags) of + ok -> + {ok, next_timeout(TS, Timeout)}; + {ok, Written} -> + %% We are partially done, wait for continuation + receive + {select, SockRef, SendRef, ready_output} when (Written > 0) -> + <<_:Written/binary, Rest/binary>> = Data, + do_sendto(SockRef, make_ref(), Rest, EFlags, + DestAddr, DestPort, + next_timeout(TS, Timeout)); + {select, SockRef, SendRef, ready_output} -> + do_sendto(SockRef, make_ref(), Data, EFlags, + DestAddr, DestPort, + next_timeout(TS, Timeout)) + after Timeout -> + nif_cancel(SockRef, SendRef), + flush_select_msgs(SockRef, SendRef), + {error, timeout} + end; + {error, eagain} -> + %% Is this what we can expect? + %% If we have to wait because there is another ongoing write?? + receive + {select, SockRef, SendRef, ready_output} -> + do_sendto(SockRef, SendRef, Data, EFlags, + DestAddr, DestPort, + next_timeout(TS, Timeout)) + after Timeout -> + nif_cancel(SockRef, SendRef), + flush_select_msgs(SockRef, SendRef), + {error, timeout} + end; + + {error, _} = ERROR -> + ERROR + end. -- cgit v1.2.3 From 28611d6e6daab8ae24e5e593c001bcd6442506eb Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Thu, 19 Apr 2018 10:57:54 +0200 Subject: [socket-nif] Completed recv Need to fix the use of the request ref (ID) handling in previous functions. --- erts/emulator/nifs/common/socket_nif.c | 526 +++++++++++++++++++++++++-------- erts/preloaded/src/socket.erl | 204 ++++++++++--- 2 files changed, 563 insertions(+), 167 deletions(-) diff --git a/erts/emulator/nifs/common/socket_nif.c b/erts/emulator/nifs/common/socket_nif.c index 46c5c696e2..d3aa3db2aa 100644 --- a/erts/emulator/nifs/common/socket_nif.c +++ b/erts/emulator/nifs/common/socket_nif.c @@ -223,19 +223,20 @@ typedef unsigned long long llu_t; /* *** Misc macros and defines *** */ -#define MALLOC(SZ) enif_alloc(SZ) -#define FREE(P) enif_free(P) -#define MKA(E,S) enif_make_atom(E, S) -#define MKREF(E) enif_make_ref(E) -#define MKT2(E,E1,E2) enif_make_tuple2(E, E1, E2) -#define MCREATE(N) enif_mutex_create(N) -#define MLOCK(M) enif_mutex_lock(M) -#define MUNLOCK(M) enif_mutex_unlock(M) +#define MALLOC(SZ) enif_alloc(SZ) +#define FREE(P) enif_free(P) +#define MKA(E,S) enif_make_atom(E, S) +#define MKBIN(E,B) enif_make_binary(E, B) +#define MKREF(E) enif_make_ref(E) +#define MKT2(E,E1,E2) enif_make_tuple2(E, E1, E2) +#define MKT3(E,E1,E2,E3) enif_make_tuple3(E, E1, E2, E3) +#define MCREATE(N) enif_mutex_create(N) +#define MLOCK(M) enif_mutex_lock(M) +#define MUNLOCK(M) enif_mutex_unlock(M) #define SELECT(E,FD,M,O,P,R) \ if (enif_select((E), (FD), (M), (O), (P), (R)) < 0) \ return enif_make_badarg((E)); - /* *** Socket state defs *** */ #define SOCKET_FLAG_OPEN 0x0001 @@ -275,6 +276,18 @@ typedef unsigned long long llu_t; #define SOCKET_SEND_FLAG_LOW SOCKET_SEND_FLAG_CONFIRM #define SOCKET_SEND_FLAG_HIGH SOCKET_SEND_FLAG_OOB +#define SOCKET_RECV_FLAG_CMSG_CLOEXEC 0 +#define SOCKET_RECV_FLAG_DONTWAIT 1 +#define SOCKET_RECV_FLAG_ERRQUEUE 2 +#define SOCKET_RECV_FLAG_OOB 3 +#define SOCKET_RECV_FLAG_PEEK 4 +#define SOCKET_RECV_FLAG_TRUNC 5 +#define SOCKET_RECV_FLAG_WAITALL 6 +#define SOCKET_RECV_FLAG_LOW SOCKET_RECV_FLAG_CMSG_CLOEXEC +#define SOCKET_RECV_FLAG_HIGH SOCKET_RECV_FLAG_WAITALL + +#define SOCKET_RECV_BUFFER_SIZE_DEFAULT 2048 + typedef union { struct { unsigned int open:1; @@ -343,6 +356,8 @@ typedef union { #define GET_UINT(E, TE, IP) enif_get_uint((E), (TE), (IP)) #define GET_TUPLE(E, TE, TSZ, TA) enif_get_tuple((E), (TE), (TSZ), (TA)) +#define ALLOC_BIN(SZ, BP) enif_alloc_binary((SZ), (BP)) + /* =================================================================== * * * @@ -366,6 +381,7 @@ typedef union { #define sock_name(s, addr, len) getsockname((s), (addr), (len)) #define sock_open(domain, type, proto) \ make_noninheritable_handle(socket((domain), (type), (proto))) +#define sock_recv(s,buf,len,flag) recv((s),(buf),(len),(flag)) #define sock_send(s,buf,len,flag) send((s),(buf),(len),(flag)) #define sock_sendto(s,buf,blen,flag,addr,alen) \ sendto((s),(buf),(blen),(flag),(addr),(alen)) @@ -395,6 +411,7 @@ static unsigned long one_value = 1; #define sock_listen(s, b) listen((s), (b)) #define sock_name(s, addr, len) getsockname((s), (addr), (len)) #define sock_open(domain, type, proto) socket((domain), (type), (proto)) +#define sock_recv(s,buf,len,flag) recv((s),(buf),(len),(flag)) #define sock_send(s,buf,len,flag) send((s), (buf), (len), (flag)) #define sock_sendto(s,buf,blen,flag,addr,alen) \ sendto((s),(buf),(blen),(flag),(addr),(alen)) @@ -437,17 +454,28 @@ typedef struct { typedef struct { - ErlNifPid pid; // PID of the acceptor - ErlNifMonitor mon; // Monitor for the acceptor - ERL_NIF_TERM ref; // The (unique) reference of the (accept) request -} SocketAcceptor; + ErlNifPid pid; // PID of the requesting process + ErlNifMonitor mon; // Monitor to the requesting process + ERL_NIF_TERM ref; // The (unique) reference (ID) of the request +} SocketRequestor; + +typedef struct socket_request_queue_element { + struct socket_request_queue_element* next; + SocketRequestor data; +} SocketRequestQueueElement; typedef struct { - // The actual socket + SocketRequestQueueElement* first; + SocketRequestQueueElement* last; +} SocketRequestQueue; + + +typedef struct { + /* +++ The actual socket +++ */ SOCKET sock; HANDLE event; - /* "Stuff" about the socket */ + /* +++ Stuff "about" the socket +++ */ int domain; int type; int protocol; @@ -456,51 +484,45 @@ typedef struct { SocketAddress remote; - // Controller (owner) process + /* +++ Controller (owner) process +++ */ ErlNifPid ctrlPid; ErlNifMonitor ctrlMon; - // Write - ErlNifMutex* writeMtx; - BOOLEAN_T isWritable; - uint32_t writePkgCnt; - uint32_t writeByteCnt; - uint32_t writeTries; - uint32_t writeWaits; - uint32_t writeFails; - - // Read - ErlNifMutex* readMtx; - BOOLEAN_T isReadable; - ErlNifBinary rbuffer; - uint32_t readCapacity; - uint32_t readPkgCnt; - uint32_t readByteCnt; - uint32_t readTries; - uint32_t readWaits; - - /* Accept - * We also need a queue for waiting acceptors... - * Lets see if this can be a common "request" queue... - */ - ErlNifMutex* accMtx; - SocketAcceptor acceptor; - - - /* We need to keep track of the "request(s)" we have pending. - * If for instance an accept takes to long, the issuer may - * decide to "cancel" the accept (actually the select). This - * is done by calling the *nif_cancel* function with the request - * ref as argument. - * We also need to keep track of requests so that if a new - * request is issued before the current has completed, we - * reply with e.g. ebusy (or something to that effect). - * Or do we? Can the caller actually do that? - */ - - - /* Misc stuff */ + /* +++ Write stuff +++ */ + ErlNifMutex* writeMtx; + SocketRequestor currentWriter; + SocketRequestor* currentWriterP; // NULL or points to currentWriter + SocketRequestQueue writersQ; + BOOLEAN_T isWritable; + uint32_t writePkgCnt; + uint32_t writeByteCnt; + uint32_t writeTries; + uint32_t writeWaits; + uint32_t writeFails; + + /* +++ Read stuff +++ */ + ErlNifMutex* readMtx; + SocketRequestor currentReader; + SocketRequestor* currentReaderP; // NULL or points to currentReader + SocketRequestQueue readersQ; + BOOLEAN_T isReadable; + ErlNifBinary rbuffer; // DO WE NEED THIS + uint32_t readCapacity; // DO WE NEED THIS + uint32_t readPkgCnt; + uint32_t readByteCnt; + uint32_t readTries; + uint32_t readWaits; + + /* +++ Accept stuff +++ */ + ErlNifMutex* accMtx; + SocketRequestor currentAcceptor; + SocketRequestor* currentAcceptorP; // NULL or points to currentReader + SocketRequestQueue acceptorsQ; + + /* +++ Misc stuff +++ */ + BOOLEAN_T iow; // Inform On Wrap BOOLEAN_T dbg; + } SocketDescriptor; @@ -515,39 +537,6 @@ typedef struct { } SocketData; -/* typedef struct socket_queue_element { */ -/* struct socket_queue_element* next; */ -/* /\* */ -/* unsigned int tag; */ -/* union { */ -/* SocketAcceptor acceptor; */ -/* } u; */ -/* *\/ */ -/* SocketAcceptor acceptor; */ -/* } SocketQueueElement; */ - -/* typedef struct socket_queue { */ -/* SocketQueueElement* first; */ -/* SocketQueueElement* last; */ -/* } SocketQueue; */ - -/* Macros for defining the various queues (accept, send receive) */ -#define SOCKET_QUEUE_ELEMENT(QE,QEP) \ - typedef struct socket_queue_element_##QEP { \ - struct socket_queue_element_##QEP* next; \ - QE elem; \ - } QE##Element; - -#define SOCKET_QUEUE(QE,Q) \ - typedef struct { \ - QE* first; \ - QE* last; \ - } Q; - -/* The Acceptor Queue types */ -SOCKET_QUEUE_ELEMENT(SocketAcceptor, acceptor); -SOCKET_QUEUE(SocketAcceptorElement, SocketAcceptQueue); - /* ---------------------------------------------------------------------- * F o r w a r d s * ---------------------------------------------------------------------- @@ -600,12 +589,12 @@ static ERL_NIF_TERM nif_recvfrom(ErlNifEnv* env, static ERL_NIF_TERM nif_close(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); -static ERL_NIF_TERM nif_setopt(ErlNifEnv* env, - int argc, - const ERL_NIF_TERM argv[]); -static ERL_NIF_TERM nif_getopt(ErlNifEnv* env, - int argc, - const ERL_NIF_TERM argv[]); +static ERL_NIF_TERM nif_setsockopt(ErlNifEnv* env, + int argc, + const ERL_NIF_TERM argv[]); +static ERL_NIF_TERM nif_getsockopt(ErlNifEnv* env, + int argc, + const ERL_NIF_TERM argv[]); static ERL_NIF_TERM nif_finalize_connection(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); @@ -649,12 +638,23 @@ static ERL_NIF_TERM nsendto(ErlNifEnv* env, ErlNifBinary* dataP, int flags, SocketAddress* toAddrP); +static ERL_NIF_TERM nrecv(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM recvRef, + int len, + int flags); static ERL_NIF_TERM send_check_result(ErlNifEnv* env, SocketDescriptor* descP, ssize_t written, ssize_t dataSize, ERL_NIF_TERM sendRef); +static ERL_NIF_TERM recv_check_result(ErlNifEnv* env, + SocketDescriptor* descP, + int read, + int toRead, + ErlNifBinary* bufP, + ERL_NIF_TERM recvRef); static ERL_NIF_TERM nfinalize_connection(ErlNifEnv* env, SocketDescriptor* descP); @@ -707,6 +707,7 @@ static BOOLEAN_T edomain2domain(int edomain, int* domain); static BOOLEAN_T etype2type(int etype, int* type); static BOOLEAN_T eproto2proto(int eproto, int* proto); static BOOLEAN_T esendflags2sendflags(unsigned int esendflags, int* sendflags); +static BOOLEAN_T erecvflags2recvflags(unsigned int erecvflags, int* recvflags); #ifdef HAVE_SETNS static BOOLEAN_T emap2netns(ErlNifEnv* env, ERL_NIF_TERM map, char** netns); static BOOLEAN_T change_network_namespace(char* netns, int* cns, int* err); @@ -725,7 +726,8 @@ static void socket_down(ErlNifEnv* env, const ErlNifPid* pid, const ErlNifMonitor* mon); -static ERL_NIF_TERM make_ok(ErlNifEnv* env, ERL_NIF_TERM any); +static ERL_NIF_TERM make_ok2(ErlNifEnv* env, ERL_NIF_TERM val); +static ERL_NIF_TERM make_ok3(ErlNifEnv* env, ERL_NIF_TERM val1, ERL_NIF_TERM val2); static ERL_NIF_TERM make_error(ErlNifEnv* env, ERL_NIF_TERM reason); static ERL_NIF_TERM make_error1(ErlNifEnv* env, char* reason); static ERL_NIF_TERM make_error2(ErlNifEnv* env, int err); @@ -767,8 +769,9 @@ static const struct in6_addr in6addr_loopback = /* *** String constants *** */ -static char str_false[] = "false"; +static char str_closed[] = "closed"; static char str_error[] = "error"; +static char str_false[] = "false"; static char str_ok[] = "ok"; static char str_true[] = "true"; static char str_undefined[] = "undefined"; @@ -779,6 +782,7 @@ static char str_eafnosupport[] = "eafnosupport"; static char str_einval[] = "einval"; static char str_eisconn[] = "eisconn"; static char str_enotconn[] = "enotconn"; +static char str_exalloc[] = "exalloc"; static char str_exbadstate[] = "exbadstate"; static char str_exbusy[] = "exbusy"; static char str_exmon[] = "exmonitor"; // failed monitor @@ -786,8 +790,9 @@ static char str_exself[] = "exself"; // failed self /* *** Atoms *** */ -static ERL_NIF_TERM atom_false; +static ERL_NIF_TERM atom_closed; static ERL_NIF_TERM atom_error; +static ERL_NIF_TERM atom_false; static ERL_NIF_TERM atom_ok; static ERL_NIF_TERM atom_true; static ERL_NIF_TERM atom_undefined; @@ -797,6 +802,7 @@ static ERL_NIF_TERM atom_eafnosupport; static ERL_NIF_TERM atom_einval; static ERL_NIF_TERM atom_eisconn; static ERL_NIF_TERM atom_enotconn; +static ERL_NIF_TERM atom_exalloc; static ERL_NIF_TERM atom_exbadstate; static ERL_NIF_TERM atom_exbusy; static ERL_NIF_TERM atom_exmon; @@ -832,16 +838,16 @@ static SocketData socketData; * nif_connect(Sock, Addr, Port) * nif_listen(Sock, Backlog) * nif_accept(LSock, Ref) - * nif_send(Sock, Data, Flags) - * nif_sendto(Sock, Data, Flags, DstAddr, DstPort) - * nif_recv(Sock, Flags) + * nif_send(Sock, SendRef, Data, Flags) + * nif_sendto(Sock, SendRef, Data, Flags, DstAddr, DstPort) + * nif_recv(Sock, RecvRef, Length, Flags) * nif_recvfrom(Sock, Flags) * nif_close(Sock) * * And some functions to manipulate and retrieve socket options: * ------------------------------------------------------------- - * nif_setopt/3 - * nif_getopt/2 + * nif_setsockopt/3 + * nif_getsockopt/2 * * And some socket admin functions: * ------------------------------------------------------------- @@ -1033,7 +1039,7 @@ ERL_NIF_TERM nopen(ErlNifEnv* env, #endif - return make_ok(env, res); + return make_ok2(env, res); } @@ -1213,7 +1219,7 @@ ERL_NIF_TERM nbind(ErlNifEnv* env, port = 0; } - return make_ok(env, enif_make_int(env, port)); + return make_ok2(env, enif_make_int(env, port)); } @@ -1482,7 +1488,7 @@ ERL_NIF_TERM nconnect(ErlNifEnv* env, descP->sock, (ERL_NIF_SELECT_WRITE), descP, NULL, ref); - return make_ok(env, ref); + return make_ok2(env, ref); } else if (code == 0) { /* ok we are connected */ descP->state = SOCKET_STATE_CONNECTED; /* Do we need to do somthing for "active" mode? @@ -1738,13 +1744,13 @@ ERL_NIF_TERM naccept_listening(ErlNifEnv* env, /* *** Try again later *** */ - descP->acceptor.pid = caller; + descP->currentAcceptor.pid = caller; if (enif_monitor_process(env, descP, - &descP->acceptor.pid, - &descP->acceptor.mon) > 0) + &descP->currentAcceptor.pid, + &descP->currentAcceptor.mon) > 0) return make_error(env, atom_exmon); - descP->acceptor.ref = ref; + descP->currentAcceptor.ref = ref; SELECT(env, descP->sock, @@ -1830,7 +1836,7 @@ ERL_NIF_TERM naccept_listening(ErlNifEnv* env, accDescP->state = SOCKET_STATE_CONNECTED; - return make_ok(env, accRef); + return make_ok2(env, accRef); } } @@ -1854,7 +1860,7 @@ ERL_NIF_TERM naccept_accepting(ErlNifEnv* env, if (enif_self(env, &caller) == NULL) return make_error(env, atom_exself); - if (compare_pids(env, &descP->acceptor.pid, &caller) != 0) { + if (compare_pids(env, &descP->currentAcceptor.pid, &caller) != 0) { /* This will have to do until we implement the queue. * When we have the queue, we should simply push this request, * and instead return with eagain (the caller will then wait @@ -1938,7 +1944,7 @@ ERL_NIF_TERM naccept_accepting(ErlNifEnv* env, */ descP->state = SOCKET_STATE_LISTENING; - return make_ok(env, accRef); + return make_ok2(env, accRef); } } @@ -2200,6 +2206,101 @@ ERL_NIF_TERM nwritev(ErlNifEnv* env, +/* ---------------------------------------------------------------------- + * nif_recv + * + * Description: + * Receive a message on a socket. + * Normally used only on a connected socket! + * If we are trying to read > 0 bytes, then that is what we do. + * But if we have specified 0 bytes, then we want to read + * whatever is in the buffers (everything it got). + * + * Arguments: + * Socket (ref) - Points to the socket descriptor. + * RecvRef - A unique id for this (send) request. + * Length - The number of bytes to receive. + * Flags - Receive flags. + */ + +static +ERL_NIF_TERM nif_recv(ErlNifEnv* env, + int argc, + const ERL_NIF_TERM argv[]) +{ + SocketDescriptor* descP; + ERL_NIF_TERM recvRef; + int len; + unsigned int eflags; + int flags; + ERL_NIF_TERM res; + + if ((argc != 4) || + !enif_get_resource(env, argv[0], sockets, (void**) &descP) || + !GET_INT(env, argv[2], &len) || + !GET_UINT(env, argv[3], &eflags)) { + return enif_make_badarg(env); + } + recvRef = argv[1]; + + if (!IS_CONNECTED(descP)) + return make_error(env, atom_enotconn); + + if (!erecvflags2recvflags(eflags, &flags)) + return enif_make_badarg(env); + + MLOCK(descP->readMtx); + + /* We need to handle the case when another process tries + * to receive at the same time. + * If the current recv could not read its entire package + * this time (resulting in an select). The read of the + * other process must be made to wait until current + * is done! + * Basically, we need a read queue! + * + * A 'reading' field (boolean), which is set if we did + * not manage to read the entire message and reset every + * time we do. + */ + + res = nrecv(env, descP, recvRef, len, flags); + + MUNLOCK(descP->readMtx); + + return res; + +} + + +static +ERL_NIF_TERM nrecv(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM recvRef, + int len, + int flags) +{ + ssize_t read; + ErlNifBinary buf; + + if (!descP->isReadable) + return enif_make_badarg(env); + + if (!ALLOC_BIN((len ? len : SOCKET_RECV_BUFFER_SIZE_DEFAULT), &buf)) + return make_error(env, atom_exalloc); + + /* We ignore the wrap for the moment. + * Maybe we should issue a wrap-message to controlling process... + */ + cnt_inc(&descP->readTries, 1); + + read = sock_recv(descP->sock, buf.data, buf.size, flags); + + return recv_check_result(env, descP, + read, len, + &buf, + recvRef); +} @@ -2237,7 +2338,7 @@ ERL_NIF_TERM send_check_result(ErlNifEnv* env, /* Ok, try again later */ - written = 0; + written = 0; // SHOULD RESULT IN {error, eagain}!!!! } } @@ -2252,12 +2353,109 @@ ERL_NIF_TERM send_check_result(ErlNifEnv* env, SELECT(env, descP->sock, (ERL_NIF_SELECT_WRITE), descP, NULL, sendRef); - return make_ok(env, enif_make_int(env, written)); + return make_ok2(env, enif_make_int(env, written)); } -/* The rather odd thing about the 'toAddrP' (the **) is +static +ERL_NIF_TERM recv_check_result(ErlNifEnv* env, + SocketDescriptor* descP, + int read, + int toRead, + ErlNifBinary* bufP, + ERL_NIF_TERM recvRef) +{ + /* There is a special case: If the provided 'to read' value is + * zero (0). That means that if we filled the (default size) + * buffer, we need to continue to read (since there *may* be + * more data), but we cannot loop here. Instead we inform the + * caller that it must call again. + */ + + if (bufP->size == read) { + + /* +++ We filled the buffer +++ */ + + if (toRead == 0) { + + /* +++ Give us everything you have got => needs to continue +++ */ + + /* How do we do this? + * Either: + * 1) Send up each chunk of data for each of the read + * and let the erlang code assemble it: {ok, false, Bin} + * (when complete it should return {ok, true, Bin}). + * We need to read atleast one more time to be sure if its + * done... + * 2) Or put it in a buffer here, and then let the erlang code + * know that it should call again (special return value) + * (continuous binary realloc "here"). + * + * => We choose alt 1 for now. + */ + + return make_ok3(env, atom_false, MKBIN(env, bufP)); + + } else { + + /* +++ We got exactly as much as we requested +++ */ + + /* + * WE NEED TO INFORM ANY WAITING READERS + * + */ + + return make_ok3(env, atom_true, MKBIN(env, bufP)); + + } + + } else if (read < 0) { + + /* +++ Error handling +++ */ + + int save_errno = sock_errno(); + + if (save_errno == ECONNRESET) { + + /* +++ Oups - closed +++ */ + + /* + * IF THE CURRENT PROCESS IS *NOT* THE CONTROLLING + * PROCESS, WE NEED TO INFORM IT!!! + * + * ALL WAITING PROCESSES MUST ALSO GET THE ERROR!! + * + * + */ + + SELECT(env, + descP->sock, + (ERL_NIF_SELECT_STOP), + descP, NULL, recvRef); + + return make_error(env, atom_closed); + + } else if ((save_errno == ERRNO_BLOCK) || + (save_errno == EAGAIN)) { + return make_error(env, atom_eagain); + } else { + return make_error2(env, save_errno); + } + + } else { + + /* +++ We got only a part of what was expected - receive more later +++ */ + + return make_ok3(env, atom_false, MKBIN(env, bufP)); + + } +} + + +/* *** decode_send_addr *** + * + * The rather odd thing about the 'toAddrP' (the **) is * because we need to be able to return a NULL pointer, * in the case of the dest address is the atom 'null'. * Its possible to call the sendto function with the @@ -2507,14 +2705,24 @@ SocketDescriptor* alloc_descriptor(SOCKET sock, HANDLE event) char buf[64]; /* Buffer used for building the mutex name */ sprintf(buf, "socket[w,%d]", sock); - descP->writeMtx = MCREATE(buf); + descP->writeMtx = MCREATE(buf); + descP->currentWriterP = NULL; // currentWriter not used + descP->writersQ.first = NULL; + descP->writersQ.last = NULL; sprintf(buf, "socket[r,%d]", sock); - descP->readMtx = MCREATE(buf); + descP->readMtx = MCREATE(buf); + descP->currentReaderP = NULL; // currentReader not used + descP->readersQ.first = NULL; + descP->readersQ.last = NULL; sprintf(buf, "socket[acc,%d]", sock); - descP->accMtx = MCREATE(buf); + descP->accMtx = MCREATE(buf); + descP->currentAcceptorP = NULL; // currentAcceptor not used + descP->acceptorsQ.first = NULL; + descP->acceptorsQ.last = NULL; + descP->iow = FALSE; descP->dbg = SOCKET_DEBUG_DEFAULT; descP->isWritable = TRUE; descP->isReadable = TRUE; @@ -2756,17 +2964,81 @@ BOOLEAN_T esendflags2sendflags(unsigned int esendflags, int* sendflags) +/* erecvflags2recvflags - convert internal (erlang) send flags to (proper) + * send flags. + */ +static +BOOLEAN_T erecvflags2recvflags(unsigned int erecvflags, int* recvflags) +{ + unsigned int ef; + int tmp = 0; + + for (ef = SOCKET_RECV_FLAG_LOW; ef <= SOCKET_RECV_FLAG_HIGH; ef++) { + switch (ef) { + case SOCKET_RECV_FLAG_CMSG_CLOEXEC: + tmp |= MSG_CMSG_CLOEXEC; + break; + + case SOCKET_RECV_FLAG_DONTWAIT: + tmp |= MSG_DONTWAIT; + break; + + case SOCKET_RECV_FLAG_ERRQUEUE: + tmp |= MSG_ERRQUEUE; + break; + + case SOCKET_RECV_FLAG_OOB: + tmp |= MSG_OOB; + break; + + case SOCKET_RECV_FLAG_PEEK: + tmp |= MSG_PEEK; + break; + + case SOCKET_RECV_FLAG_TRUNC: + tmp |= MSG_TRUNC; + break; + + case SOCKET_RECV_FLAG_WAITALL: + tmp |= MSG_WAITALL; + break; + + default: + return FALSE; + } + + } + + *recvflags = tmp; + + return TRUE; +} + + + /* Create an ok two (2) tuple in the form: {ok, Any}. * The second element (Any) is already in the form of an * ERL_NIF_TERM so all we have to do is create the tuple. */ static -ERL_NIF_TERM make_ok(ErlNifEnv* env, ERL_NIF_TERM any) +ERL_NIF_TERM make_ok2(ErlNifEnv* env, ERL_NIF_TERM any) { return MKT2(env, atom_ok, any); } +/* Create an ok three (3) tuple in the form: {ok, Val1, Val2}. + * The second (Val1) and third (Val2) elements are already in + * the form of an ERL_NIF_TERM so all we have to do is create + * the tuple. + */ +static +ERL_NIF_TERM make_ok3(ErlNifEnv* env, ERL_NIF_TERM val1, ERL_NIF_TERM val2) +{ + return MKT3(env, atom_ok, val1, val2); +} + + /* Create an error two (2) tuple in the form: {error, Reason}. * The second element (Reason) is already in the form of an * ERL_NIF_TERM so all we have to do is create the tuple. @@ -2885,13 +3157,13 @@ ErlNifFunc socket_funcs[] = {"nif_connect", 3, nif_connect}, {"nif_listen", 2, nif_listen}, {"nif_accept", 2, nif_accept}, - {"nif_send", 3, nif_send}, - {"nif_sendto", 5, nif_sendto}, - {"nif_recv", 2, nif_recv}, + {"nif_send", 4, nif_send}, + {"nif_sendto", 6, nif_sendto}, + {"nif_recv", 4, nif_recv}, {"nif_recvfrom", 2, nif_recvfrom}, {"nif_close", 1, nif_close}, - {"nif_setopt", 3, nif_setopt}, - {"nif_getopt", 2, nif_getopt}, + {"nif_setsockopt", 3, nif_setsockopt}, + {"nif_getsockopt", 2, nif_getsockopt}, /* "Extra" functions to "complete" the socket interface. * For instance, the function nif_finalize_connection @@ -2966,7 +3238,7 @@ int on_load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info) // atom_active_once = MKA(env, str_active_once); // atom_binary = MKA(env, str_binary); // atom_buildDate = MKA(env, str_buildDate); - // atom_closed = MKA(env, str_closed); + atom_closed = MKA(env, str_closed); atom_error = MKA(env, str_error); atom_false = MKA(env, str_false); // atom_list = MKA(env, str_list); @@ -2986,7 +3258,7 @@ int on_load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info) atom_einval = MKA(env, str_einval); atom_eisconn = MKA(env, str_eisconn); atom_enotconn = MKA(env, str_enotconn); - // atom_exalloc = MKA(env, str_exalloc); + atom_exalloc = MKA(env, str_exalloc); atom_exbadstate = MKA(env, str_exbadstate); atom_exbusy = MKA(env, str_exbusy); // atom_exnotopen = MKA(env, str_exnotopen); diff --git a/erts/preloaded/src/socket.erl b/erts/preloaded/src/socket.erl index 0a78feab4e..6784477123 100644 --- a/erts/preloaded/src/socket.erl +++ b/erts/preloaded/src/socket.erl @@ -38,7 +38,7 @@ %% sendmsg/4, %% writev/4, OR SENDV? It will be strange for recv then: recvv (instead of readv) - recv/1, recv/2, + recv/2, recv/3, recv/4, recvfrom/1, recvfrom/2, %% recvmsg/4, %% readv/3, @@ -173,11 +173,7 @@ -define(SOCKET_LISTEN_BACKLOG_DEFAULT, 5). -%% Bit numbers (from right). --define(SOCKET_ACCEPT_FLAG_NONBLOCK, 0). --define(SOCKET_ACCEPT_FLAG_CLOEXEC, 1). - --define(SOCKET_ACCEPT_FLAGS_DEFAULT, []). +-define(SOCKET_ACCEPT_TIMEOUT_DEFAULT, infinity). -define(SOCKET_SEND_FLAG_CONFIRM, 0). -define(SOCKET_SEND_FLAG_DONTROUTE, 1). @@ -187,7 +183,9 @@ -define(SOCKET_SEND_FLAG_NOSIGNAL, 5). -define(SOCKET_SEND_FLAG_OOB, 6). --define(SOCKET_SEND_FLAGS_DEFAULT, []). +-define(SOCKET_SEND_FLAGS_DEFAULT, []). +-define(SOCKET_SEND_TIMEOUT_DEFAULT, infinity). +-define(SOCKET_SENDTO_TIMEOUT_DEFAULT, ?SOCKET_SEND_TIMEOUT_DEFAULT). -define(SOCKET_RECV_FLAG_CMSG_CLOEXEC, 0). -define(SOCKET_RECV_FLAG_DONTWAIT, 1). @@ -197,7 +195,8 @@ -define(SOCKET_RECV_FLAG_TRUNC, 5). -define(SOCKET_RECV_FLAG_WAITALL, 6). --define(SOCKET_RECV_FLAGS_DEFAULT, []). +-define(SOCKET_RECV_FLAGS_DEFAULT, []). +-define(SOCKET_RECV_TIMEOUT_DEFAULT, infinity). -define(SOCKET_SETOPT_KEY_DEBUG, 0). @@ -403,7 +402,7 @@ connect({socket, _, SockRef}, Addr, Port, Timeout) {select, SockRef, Ref, ready_output} -> nif_finalize_connection(SockRef) after NewTimeout -> - nif_cancel(SockRef, Ref), + nif_cancel(SockRef, connect, Ref), {error, timeout} end; {error, _} = ERROR -> @@ -444,7 +443,7 @@ listen({socket, _, SockRef}, Backlog) Reason :: term(). accept(Socket) -> - accept(Socket, infinity). + accept(Socket, ?SOCKET_ACCEPT_TIMEOUT_DEFAULT). %% Do we really need this optimization? accept(_, Timeout) when is_integer(Timeout) andalso (Timeout < 0) -> @@ -466,11 +465,12 @@ do_accept(LSockRef, SI, Ref, Timeout) -> Socket = {socket, SocketInfo, SockRef}, {ok, Socket}; {error, eagain} -> + NewTimeout = next_timeout(TS, Timeout), receive {select, LSockRef, Ref, ready_input} -> do_accept(LSockRef, SI, make_ref(), next_timeout(TS, Timeout)) - after Timeout -> - nif_cancel(LSockRef, Ref), + after NewTimeout -> + nif_cancel(LSockRef, accept, Ref), flush_select_msgs(LSockRef, Ref), {error, timeout} end @@ -492,14 +492,13 @@ flush_select_msgs(LSRef, Ref) -> %% send(Socket, Data) -> - send(Socket, Data, ?SOCKET_SEND_FLAGS_DEFAULT, infinity). + send(Socket, Data, ?SOCKET_SEND_FLAGS_DEFAULT, ?SOCKET_SEND_TIMEOUT_DEFAULT). send(Socket, Data, Flags) when is_list(Flags) -> - send(Socket, Data, Flags, infinity); + send(Socket, Data, Flags, ?SOCKET_SEND_TIMEOUT_DEFAULT); send(Socket, Data, Timeout) -> send(Socket, Data, ?SOCKET_SEND_FLAGS_DEFAULT, Timeout). - -spec send(Socket, Data, Flags, Timeout) -> ok | {error, Reason} when Socket :: socket(), Data :: iodata(), @@ -516,15 +515,20 @@ send(Socket, Data, Flags, Timeout) when is_binary(Data) andalso is_list(Flags) do_send(SockRef, SendRef, Data, _EFlags, Timeout) when (Timeout =< 0) -> - nif_cancel(SockRef, SendRef), + %% + %% THIS IS THE WRONG SEND REF + %% WE SHOULD NOT HAVE THIS REF AS AN ARGUMENT - SEE RECV + %% + nif_cancel(SockRef, send, SendRef), flush_select_msgs(SockRef, SendRef), {error, {timeout, size(Data)}}; do_send(SockRef, SendRef, Data, EFlags, Timeout) -> TS = timestamp(Timeout), case nif_send(SockRef, SendRef, Data, EFlags) of ok -> - {ok, next_timeout(TS, Timeout)}; + ok; {ok, Written} -> + NewTimeout = next_timeout(TS, Timeout), %% We are partially done, wait for continuation receive {select, SockRef, SendRef, ready_output} when (Written > 0) -> @@ -534,8 +538,8 @@ do_send(SockRef, SendRef, Data, EFlags, Timeout) -> {select, SockRef, SendRef, ready_output} -> do_send(SockRef, make_ref(), Data, EFlags, next_timeout(TS, Timeout)) - after Timeout -> - nif_cancel(SockRef, SendRef), + after NewTimeout -> + nif_cancel(SockRef, send, SendRef), flush_select_msgs(SockRef, SendRef), {error, timeout} end; @@ -545,7 +549,7 @@ do_send(SockRef, SendRef, Data, EFlags, Timeout) -> do_send(SockRef, SendRef, Data, EFlags, next_timeout(TS, Timeout)) after Timeout -> - nif_cancel(SockRef, SendRef), + nif_cancel(SockRef, send, SendRef), flush_select_msgs(SockRef, SendRef), {error, timeout} end; @@ -563,7 +567,7 @@ do_send(SockRef, SendRef, Data, EFlags, Timeout) -> %% sendto(Socket, Data, Flags, DestAddr, DestPort) -> - sendto(Socket, Data, Flags, DestAddr, DestPort, infinity). + sendto(Socket, Data, Flags, DestAddr, DestPort, ?SOCKET_SENDTO_TIMEOUT_DEFAULT). -spec sendto(Socket, Data, Flags, DestAddr, DestPort, Timeout) -> ok | {error, Reason} when @@ -589,12 +593,16 @@ sendto(Socket, Data, Flags, DestAddr, DestPort, Timeout) do_sendto(SockRef, SendRef, Data, _EFlags, _DestAddr, _DestPort, Timeout) when (Timeout =< 0) -> - nif_cancel(SockRef, SendRef), + %% + %% THIS IS THE WRONG SEND REF + %% WE SHOULD NOT HAVE THIS REF AS AN ARGUMENT - SEE RECV + %% + nif_cancel(SockRef, sendto, SendRef), flush_select_msgs(SockRef, SendRef), {error, {timeout, size(Data)}}; do_sendto(SockRef, SendRef, Data, EFlags, DestAddr, DestPort, Timeout) -> TS = timestamp(Timeout), - case nif_sendto(SockRef, SendRef, Data, DestAddr, DestPort, EFlags) of + case nif_sendto(SockRef, SendRef, Data, EFlags, DestAddr, DestPort) of ok -> {ok, next_timeout(TS, Timeout)}; {ok, Written} -> @@ -610,7 +618,7 @@ do_sendto(SockRef, SendRef, Data, EFlags, DestAddr, DestPort, Timeout) -> DestAddr, DestPort, next_timeout(TS, Timeout)) after Timeout -> - nif_cancel(SockRef, SendRef), + nif_cancel(SockRef, sendto, SendRef), flush_select_msgs(SockRef, SendRef), {error, timeout} end; @@ -623,7 +631,7 @@ do_sendto(SockRef, SendRef, Data, EFlags, DestAddr, DestPort, Timeout) -> DestAddr, DestPort, next_timeout(TS, Timeout)) after Timeout -> - nif_cancel(SockRef, SendRef), + nif_cancel(SockRef, sendto, SendRef), flush_select_msgs(SockRef, SendRef), {error, timeout} end; @@ -711,20 +719,136 @@ do_sendto(SockRef, SendRef, Data, EFlags, DestAddr, DestPort, Timeout) -> %% %% recv, recvfrom, recvmsg - receive a message from a socket %% +%% Description: +%% There is a special case for the argument Length. If its set to zero (0), +%% it means "give me everything you have". +%% +%% Returns: {ok, Binary} | {error, Reason} +%% Binary - The received data as a binary +%% Reason - The error reason: +%% timeout | {timeout, AccData} | +%% posix() | {posix(), AccData} | +%% atom() | {atom(), AccData} +%% AccData - The data (as a binary) that we did manage to receive +%% before the timeout. +%% +%% Arguments: +%% Socket - The socket to read from. +%% Length - The number of bytes to read. +%% Flags - A list of "options" for the read. +%% Timeout - Time-out in milliseconds. + +recv(Socket, Length) -> + recv(Socket, Length, + ?SOCKET_RECV_FLAGS_DEFAULT, + ?SOCKET_RECV_TIMEOUT_DEFAULT). + +recv(Socket, Length, Flags) when is_list(Flags) -> + recv(Socket, Length, Flags, ?SOCKET_RECV_TIMEOUT_DEFAULT); +recv(Socket, Length, Timeout) -> + recv(Socket, Length, ?SOCKET_RECV_FLAGS_DEFAULT, Timeout). + +-spec recv(Socket, Length, Flags, Timeout) -> {ok, Data} | {error, Reason} when + Socket :: socket(), + Length :: non_neg_integer(), + Flags :: recv_flags(), + Timeout :: timeout(), + Data :: binary(), + Reason :: term(). --spec recv(Socket, Flags) -> {ok, Data} | {error, Reason} when - Socket :: socket(), - Flags :: recv_flags(), - Data :: binary(), - Reason :: term(). +recv(Socket, Length, Flags, Timeout) + when (is_integer(Length) andalso (Length >= 0)) andalso + is_list(Flags) andalso + (is_integer(Timeout) orelse (Timeout =:= infinity)) -> + EFlags = enc_recv_flags(Flags), + do_recv(Socket, Length, EFlags, <<>>, EFlags). + +do_recv({socket, _, SockRef} = Socket, Length, EFlags, Acc, Timeout) + when (Timeout =:= infinity) orelse + (is_integer(Timeout) andalso (Timeout > 0)) -> + TS = timestamp(Timeout), + RecvRef = make_ref(), + case nif_recv(SockRef, RecvRef, Length, EFlags) of + {ok, true = _Complete, Bin} when (size(Acc) =:= 0) -> + {ok, Bin}; + {ok, true = _Complete, Bin} -> + {ok, <>}; + + %% It depends on the amount of bytes we tried to read: + %% 0 - Read everything available + %% We got something, but there may be more - keep reading. + %% > 0 - We got a part of the message and we will be notified + %% when there is more to read (a select message) + {ok, false = _Complete, Bin} when (Length =:= 0) -> + do_recv(Socket, Length, EFlags, + <>, + next_timeout(TS, Timeout)); + + {ok, false = _Completed, Bin} when (size(Acc) =:= 0) -> + %% We got the first chunk of it. + %% We will be notified (select message) when there + %% is more to read. + NewTimeout = next_timeout(TS, Timeout), + receive + {select, SockRef, RecvRef, ready_input} -> + do_recv(Socket, Length-size(Bin), EFlags, + Bin, + next_timeout(TS, Timeout)) + after NewTimeout -> + nif_cancel(SockRef, recv, RecvRef), + flush_select_msgs(SockRef, RecvRef), + {error, {timeout, Acc}} + end; + + {ok, false = _Completed, Bin} -> + %% We got a chunk of it! + NewTimeout = next_timeout(TS, Timeout), + receive + {select, SockRef, RecvRef, ready_input} -> + do_recv(Socket, Length-size(Bin), EFlags, + <>, + next_timeout(TS, Timeout)) + after NewTimeout -> + nif_cancel(SockRef, recv, RecvRef), + flush_select_msgs(SockRef, RecvRef), + {error, {timeout, Acc}} + end; -recv(Socket) -> - recv(Socket, ?SOCKET_RECV_FLAGS_DEFAULT). + %% We return with the accumulated binary regardless if its empty... + {error, eagain} when (Length =:= 0) -> + {ok, Acc}; -%% WE "may" need a timeout option here... -recv({socket, _, SockRef}, Flags) when is_list(Flags) -> - EFlags = enc_recv_flags(Flags), - nif_recv(SockRef, EFlags). + {error, eagain} -> + %% There is nothing just now, but we will be notified when there + %% is something to read (a select message). + NewTimeout = next_timeout(TS, Timeout), + receive + {select, SockRef, RecvRef, ready_input} -> + do_recv(Socket, Length, EFlags, + Acc, + next_timeout(TS, Timeout)) + after NewTimeout -> + nif_cancel(SockRef, recv, RecvRef), + flush_select_msgs(SockRef, RecvRef), + {error, timeout} + end; + + {error, _} = ERROR when (size(Acc) =:= 0) -> + ERROR; + + {error, Reason} -> + {error, {Reason, Acc}} + + end; + +do_recv({socket, _, SockRef} = _Socket, 0 = _Length, _Eflags, Acc, _Timeout) -> + %% The current recv operation is to be cancelled, so no need for a ref... + %% The cancel will end our 'read everything you have' and "activate" + %% any waiting readers. + nif_cancel(SockRef, recv, undefined), + {ok, Acc}; +do_recv(_Socket, _Length, _EFlags, Acc, _Timeout) -> + {error, {timeout, Acc}}. -spec recvfrom(Socket, Flags) -> {ok, Data, SrcAddr, SrcPort} | {error, Reason} when @@ -980,9 +1104,9 @@ timestamp() -> {A,B,C} = os:timestamp(), A*1000000000+B*1000+(C div 1000). -next_timeout(infinity = Timeout, _) -> +next_timeout(_, infinity = Timeout) -> Timeout; -next_timeout(Timeout, TS) -> +next_timeout(TS, Timeout) -> NewTimeout = Timeout - tdiff(TS, timestamp()), if (NewTimeout > 0) -> @@ -1033,13 +1157,13 @@ nif_send(_SockRef, _SendRef, _Data, _Flags) -> nif_sendto(_SRef, _SendRef, _Data, _Flags, _Dest, _Port) -> erlang:error(badarg). -nif_recv(_SRef, _Flags) -> +nif_recv(_SRef, _RecvRef, _Length, _Flags) -> erlang:error(badarg). nif_recvfrom(_SRef, _Flags) -> erlang:error(badarg). -nif_cancel(_SRef, _Ref) -> +nif_cancel(_SRef, _Op, _Ref) -> erlang:error(badarg). nif_close(_SRef) -> -- cgit v1.2.3 From d5aecb115070de76cb42b44edee6bbcb5f4a3724 Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Thu, 19 Apr 2018 11:45:00 +0200 Subject: [socket-nif] Corrected timout handling The timeout handling for accept and send was unnecessarily complex. --- erts/preloaded/src/socket.erl | 151 +++++++++++++++++++++--------------------- 1 file changed, 75 insertions(+), 76 deletions(-) diff --git a/erts/preloaded/src/socket.erl b/erts/preloaded/src/socket.erl index 6784477123..0dadcecaa0 100644 --- a/erts/preloaded/src/socket.erl +++ b/erts/preloaded/src/socket.erl @@ -446,18 +446,16 @@ accept(Socket) -> accept(Socket, ?SOCKET_ACCEPT_TIMEOUT_DEFAULT). %% Do we really need this optimization? -accept(_, Timeout) when is_integer(Timeout) andalso (Timeout < 0) -> +accept(_, Timeout) when is_integer(Timeout) andalso (Timeout =< 0) -> {error, timeout}; accept({socket, SI, LSockRef}, Timeout) when is_integer(Timeout) orelse (Timeout =:= infinity) -> - Ref = make_ref(), - do_accept(LSockRef, SI, Ref, Timeout). + do_accept(LSockRef, SI, Timeout). -do_accept(_, _, _Ref, Timeout) when is_integer(Timeout) andalso (Timeout < 0) -> - {error, timeout}; -do_accept(LSockRef, SI, Ref, Timeout) -> - TS = timestamp(Timeout), - case nif_accept(LSockRef, Ref) of +do_accept(LSockRef, SI, Timeout) -> + TS = timestamp(Timeout), + AccRef = make_ref(), + case nif_accept(LSockRef, AccRef) of {ok, SockRef} -> SocketInfo = #{domain => maps:get(domain, SI), type => maps:get(type, SI), @@ -467,23 +465,15 @@ do_accept(LSockRef, SI, Ref, Timeout) -> {error, eagain} -> NewTimeout = next_timeout(TS, Timeout), receive - {select, LSockRef, Ref, ready_input} -> - do_accept(LSockRef, SI, make_ref(), next_timeout(TS, Timeout)) + {select, LSockRef, AccRef, ready_input} -> + do_accept(LSockRef, SI, next_timeout(TS, Timeout)) after NewTimeout -> - nif_cancel(LSockRef, accept, Ref), - flush_select_msgs(LSockRef, Ref), + nif_cancel(LSockRef, accept, AccRef), + flush_select_msgs(LSockRef, AccRef), {error, timeout} end end. -flush_select_msgs(LSRef, Ref) -> - receive - {select, LSRef, Ref, _} -> - flush_select_msgs(LSRef, Ref) - after 0 -> - ok - end. - %% =========================================================================== @@ -511,19 +501,11 @@ send(Socket, Data, Flags, Timeout) when is_list(Data) -> send(Socket, Bin, Flags, Timeout); send(Socket, Data, Flags, Timeout) when is_binary(Data) andalso is_list(Flags) -> EFlags = enc_send_flags(Flags), - do_send(Socket, make_ref(), Data, EFlags, Timeout). - -do_send(SockRef, SendRef, Data, _EFlags, Timeout) - when (Timeout =< 0) -> - %% - %% THIS IS THE WRONG SEND REF - %% WE SHOULD NOT HAVE THIS REF AS AN ARGUMENT - SEE RECV - %% - nif_cancel(SockRef, send, SendRef), - flush_select_msgs(SockRef, SendRef), - {error, {timeout, size(Data)}}; -do_send(SockRef, SendRef, Data, EFlags, Timeout) -> - TS = timestamp(Timeout), + do_send(Socket, Data, EFlags, Timeout). + +do_send(SockRef, Data, EFlags, Timeout) -> + TS = timestamp(Timeout), + SendRef = make_ref(), case nif_send(SockRef, SendRef, Data, EFlags) of ok -> ok; @@ -533,25 +515,25 @@ do_send(SockRef, SendRef, Data, EFlags, Timeout) -> receive {select, SockRef, SendRef, ready_output} when (Written > 0) -> <<_:Written/binary, Rest/binary>> = Data, - do_send(SockRef, make_ref(), Rest, EFlags, + do_send(SockRef, Rest, EFlags, next_timeout(TS, Timeout)); {select, SockRef, SendRef, ready_output} -> - do_send(SockRef, make_ref(), Data, EFlags, + do_send(SockRef, Data, EFlags, next_timeout(TS, Timeout)) after NewTimeout -> nif_cancel(SockRef, send, SendRef), flush_select_msgs(SockRef, SendRef), - {error, timeout} + {error, {timeout, size(Data)}} end; {error, eagain} -> receive {select, SockRef, SendRef, ready_output} -> - do_send(SockRef, SendRef, Data, EFlags, + do_send(SockRef, Data, EFlags, next_timeout(TS, Timeout)) after Timeout -> nif_cancel(SockRef, send, SendRef), flush_select_msgs(SockRef, SendRef), - {error, timeout} + {error, {timeout, size(Data)}} end; {error, _} = ERROR -> @@ -563,8 +545,6 @@ do_send(SockRef, SendRef, Data, EFlags, Timeout) -> %% --------------------------------------------------------------------------- %% -%% Do we need a timeout argument here also? -%% sendto(Socket, Data, Flags, DestAddr, DestPort) -> sendto(Socket, Data, Flags, DestAddr, DestPort, ?SOCKET_SENDTO_TIMEOUT_DEFAULT). @@ -589,46 +569,36 @@ sendto(Socket, Data, Flags, DestAddr, DestPort, Timeout) is_integer(DestPort) andalso (is_integer(Timeout) orelse (Timeout =:= infinity)) -> EFlags = enc_send_flags(Flags), - do_sendto(Socket, make_ref(), Data, EFlags, DestAddr, DestPort, Timeout). - -do_sendto(SockRef, SendRef, Data, _EFlags, _DestAddr, _DestPort, Timeout) - when (Timeout =< 0) -> - %% - %% THIS IS THE WRONG SEND REF - %% WE SHOULD NOT HAVE THIS REF AS AN ARGUMENT - SEE RECV - %% - nif_cancel(SockRef, sendto, SendRef), - flush_select_msgs(SockRef, SendRef), - {error, {timeout, size(Data)}}; -do_sendto(SockRef, SendRef, Data, EFlags, DestAddr, DestPort, Timeout) -> - TS = timestamp(Timeout), + do_sendto(Socket, Data, EFlags, DestAddr, DestPort, Timeout). + +do_sendto(SockRef, Data, EFlags, DestAddr, DestPort, Timeout) -> + TS = timestamp(Timeout), + SendRef = make_ref(), case nif_sendto(SockRef, SendRef, Data, EFlags, DestAddr, DestPort) of ok -> - {ok, next_timeout(TS, Timeout)}; + %% We are done + ok; + {ok, Written} -> %% We are partially done, wait for continuation receive {select, SockRef, SendRef, ready_output} when (Written > 0) -> <<_:Written/binary, Rest/binary>> = Data, - do_sendto(SockRef, make_ref(), Rest, EFlags, - DestAddr, DestPort, + do_sendto(SockRef, Rest, EFlags, DestAddr, DestPort, next_timeout(TS, Timeout)); {select, SockRef, SendRef, ready_output} -> - do_sendto(SockRef, make_ref(), Data, EFlags, - DestAddr, DestPort, + do_sendto(SockRef, Data, EFlags, DestAddr, DestPort, next_timeout(TS, Timeout)) after Timeout -> nif_cancel(SockRef, sendto, SendRef), flush_select_msgs(SockRef, SendRef), {error, timeout} end; + {error, eagain} -> - %% Is this what we can expect? - %% If we have to wait because there is another ongoing write?? receive {select, SockRef, SendRef, ready_output} -> - do_sendto(SockRef, SendRef, Data, EFlags, - DestAddr, DestPort, + do_sendto(SockRef, Data, EFlags, DestAddr, DestPort, next_timeout(TS, Timeout)) after Timeout -> nif_cancel(SockRef, sendto, SendRef), @@ -761,9 +731,13 @@ recv(Socket, Length, Flags, Timeout) is_list(Flags) andalso (is_integer(Timeout) orelse (Timeout =:= infinity)) -> EFlags = enc_recv_flags(Flags), - do_recv(Socket, Length, EFlags, <<>>, EFlags). + do_recv(Socket, undefined, Length, EFlags, <<>>, EFlags). -do_recv({socket, _, SockRef} = Socket, Length, EFlags, Acc, Timeout) +%% We need to pass the "old recv ref" around because of the special case +%% with Length = 0. This case makes it neccessary to have a timeout function +%% clause since we may never wait for anything (no receive select), and so the +%% the only timeout check will be the function clause. +do_recv({socket, _, SockRef} = Socket, _OldRef, Length, EFlags, Acc, Timeout) when (Timeout =:= infinity) orelse (is_integer(Timeout) andalso (Timeout > 0)) -> TS = timestamp(Timeout), @@ -780,7 +754,8 @@ do_recv({socket, _, SockRef} = Socket, Length, EFlags, Acc, Timeout) %% > 0 - We got a part of the message and we will be notified %% when there is more to read (a select message) {ok, false = _Complete, Bin} when (Length =:= 0) -> - do_recv(Socket, Length, EFlags, + do_recv(Socket, RecvRef, + Length, EFlags, <>, next_timeout(TS, Timeout)); @@ -791,7 +766,8 @@ do_recv({socket, _, SockRef} = Socket, Length, EFlags, Acc, Timeout) NewTimeout = next_timeout(TS, Timeout), receive {select, SockRef, RecvRef, ready_input} -> - do_recv(Socket, Length-size(Bin), EFlags, + do_recv(Socket, RecvRef, + Length-size(Bin), EFlags, Bin, next_timeout(TS, Timeout)) after NewTimeout -> @@ -805,7 +781,8 @@ do_recv({socket, _, SockRef} = Socket, Length, EFlags, Acc, Timeout) NewTimeout = next_timeout(TS, Timeout), receive {select, SockRef, RecvRef, ready_input} -> - do_recv(Socket, Length-size(Bin), EFlags, + do_recv(Socket, RecvRef, + Length-size(Bin), EFlags, <>, next_timeout(TS, Timeout)) after NewTimeout -> @@ -824,7 +801,8 @@ do_recv({socket, _, SockRef} = Socket, Length, EFlags, Acc, Timeout) NewTimeout = next_timeout(TS, Timeout), receive {select, SockRef, RecvRef, ready_input} -> - do_recv(Socket, Length, EFlags, + do_recv(Socket, RecvRef, + Length, EFlags, Acc, next_timeout(TS, Timeout)) after NewTimeout -> @@ -841,16 +819,26 @@ do_recv({socket, _, SockRef} = Socket, Length, EFlags, Acc, Timeout) end; -do_recv({socket, _, SockRef} = _Socket, 0 = _Length, _Eflags, Acc, _Timeout) -> +do_recv({socket, _, SockRef} = _Socket, RecvRef, + 0 = _Length, _Eflags, Acc, _Timeout) -> %% The current recv operation is to be cancelled, so no need for a ref... %% The cancel will end our 'read everything you have' and "activate" - %% any waiting readers. - nif_cancel(SockRef, recv, undefined), + %% any waiting reader. + nif_cancel(SockRef, recv, RecvRef), {ok, Acc}; -do_recv(_Socket, _Length, _EFlags, Acc, _Timeout) -> - {error, {timeout, Acc}}. +do_recv(_Socket, _RecvRef, _Length, _EFlags, Acc, _Timeout) when (size(Acc) > 0) -> + {error, {timeout, Acc}}; +do_recv(_Socket, _RecvRef, _Length, _EFlags, _Acc, _Timeout) -> + {error, timeout}. + +%% --------------------------------------------------------------------------- +%% + +recvfrom(Socket) -> + recvfrom(Socket, ?SOCKET_RECV_FLAGS_DEFAULT). + -spec recvfrom(Socket, Flags) -> {ok, Data, SrcAddr, SrcPort} | {error, Reason} when Socket :: socket(), Flags :: recv_flags(), @@ -859,14 +847,14 @@ do_recv(_Socket, _Length, _EFlags, Acc, _Timeout) -> SrcPort :: port_number(), Reason :: term(). -recvfrom(Socket) -> - recvfrom(Socket, ?SOCKET_RECV_FLAGS_DEFAULT). - recvfrom({socket, _, SockRef}, Flags) when is_list(Flags) -> EFlags = enc_recv_flags(Flags), nif_recvfrom(SockRef, EFlags). +%% --------------------------------------------------------------------------- +%% + %% -spec recvmsg(Socket, [out] MsgHdr, Flags) -> {ok, Data} | {error, Reason} when %% Socket :: socket(), %% MsgHdr :: msg_header(), @@ -883,6 +871,8 @@ recvfrom({socket, _, SockRef}, Flags) when is_list(Flags) -> +%% =========================================================================== +%% %% close - close a file descriptor -spec close(Socket) -> ok | {error, Reason} when @@ -1067,6 +1057,15 @@ dec_getopt_value(debug, B, _, _, _) when is_boolean(B) -> %% %% =========================================================================== +flush_select_msgs(LSRef, Ref) -> + receive + {select, LSRef, Ref, _} -> + flush_select_msgs(LSRef, Ref) + after 0 -> + ok + end. + + formated_timestamp() -> format_timestamp(os:timestamp()). -- cgit v1.2.3 From 04335ca6aedfc5ad9f0d6a8d193dfd76a222291c Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Mon, 23 Apr 2018 10:40:28 +0200 Subject: [socket-nif] Completed the recv and recvfrom functions Also updated the socket type (now a record for easy use). --- erts/emulator/nifs/common/socket_nif.c | 716 ++++++++++++++++++++++++++------- erts/preloaded/src/socket.erl | 172 +++++--- 2 files changed, 682 insertions(+), 206 deletions(-) diff --git a/erts/emulator/nifs/common/socket_nif.c b/erts/emulator/nifs/common/socket_nif.c index d3aa3db2aa..6e6851a608 100644 --- a/erts/emulator/nifs/common/socket_nif.c +++ b/erts/emulator/nifs/common/socket_nif.c @@ -181,6 +181,9 @@ typedef unsigned int BOOLEAN_T; #define BOOL2STR(__B__) ((__B__) ? "true" : "false") #define BOOL2ATOM(__B__) ((__B__) ? atom_true : atom_false) +/* Two byte integer decoding */ +#define get_int16(s) ((((unsigned char*) (s))[0] << 8) | \ + (((unsigned char*) (s))[1])) /* Debug stuff... */ #define SOCKET_NIF_DEBUG_DEFAULT TRUE @@ -223,19 +226,6 @@ typedef unsigned long long llu_t; /* *** Misc macros and defines *** */ -#define MALLOC(SZ) enif_alloc(SZ) -#define FREE(P) enif_free(P) -#define MKA(E,S) enif_make_atom(E, S) -#define MKBIN(E,B) enif_make_binary(E, B) -#define MKREF(E) enif_make_ref(E) -#define MKT2(E,E1,E2) enif_make_tuple2(E, E1, E2) -#define MKT3(E,E1,E2,E3) enif_make_tuple3(E, E1, E2, E3) -#define MCREATE(N) enif_mutex_create(N) -#define MLOCK(M) enif_mutex_lock(M) -#define MUNLOCK(M) enif_mutex_unlock(M) -#define SELECT(E,FD,M,O,P,R) \ - if (enif_select((E), (FD), (M), (O), (P), (R)) < 0) \ - return enif_make_badarg((E)); /* *** Socket state defs *** */ @@ -268,23 +258,20 @@ typedef unsigned long long llu_t; #define SOCKET_SEND_FLAG_CONFIRM 0 #define SOCKET_SEND_FLAG_DONTROUTE 1 -#define SOCKET_SEND_FLAG_DONTWAIT 2 -#define SOCKET_SEND_FLAG_EOR 3 -#define SOCKET_SEND_FLAG_MORE 4 -#define SOCKET_SEND_FLAG_NOSIGNAL 5 -#define SOCKET_SEND_FLAG_OOB 6 +#define SOCKET_SEND_FLAG_EOR 2 +#define SOCKET_SEND_FLAG_MORE 3 +#define SOCKET_SEND_FLAG_NOSIGNAL 4 +#define SOCKET_SEND_FLAG_OOB 5 #define SOCKET_SEND_FLAG_LOW SOCKET_SEND_FLAG_CONFIRM #define SOCKET_SEND_FLAG_HIGH SOCKET_SEND_FLAG_OOB #define SOCKET_RECV_FLAG_CMSG_CLOEXEC 0 -#define SOCKET_RECV_FLAG_DONTWAIT 1 -#define SOCKET_RECV_FLAG_ERRQUEUE 2 -#define SOCKET_RECV_FLAG_OOB 3 -#define SOCKET_RECV_FLAG_PEEK 4 -#define SOCKET_RECV_FLAG_TRUNC 5 -#define SOCKET_RECV_FLAG_WAITALL 6 +#define SOCKET_RECV_FLAG_ERRQUEUE 1 +#define SOCKET_RECV_FLAG_OOB 2 +#define SOCKET_RECV_FLAG_PEEK 3 +#define SOCKET_RECV_FLAG_TRUNC 4 #define SOCKET_RECV_FLAG_LOW SOCKET_RECV_FLAG_CMSG_CLOEXEC -#define SOCKET_RECV_FLAG_HIGH SOCKET_RECV_FLAG_WAITALL +#define SOCKET_RECV_FLAG_HIGH SOCKET_RECV_FLAG_TRUNC #define SOCKET_RECV_BUFFER_SIZE_DEFAULT 2048 @@ -342,6 +329,27 @@ typedef union { * * * =================================================================== */ +#define MALLOC(SZ) enif_alloc((SZ)) +#define FREE(P) enif_free((P)) +#define MKA(E,S) enif_make_atom((E), (S)) +#define MKBIN(E,B) enif_make_binary((E), (B)) +#define MKI(E,I) enif_make_int((E), (I)) +#define MKREF(E) enif_make_ref((E)) +#define MKS(E,S) enif_make_string((E), (S), ERL_NIF_LATIN1) +#define MKSL(E,S,L) enif_make_string_len((E), (S), (L), ERL_NIF_LATIN1) +#define MKSBIN(E,B,ST,SZ) enif_make_sub_binary((E), (B), (ST), (SZ)) +#define MKT2(E,E1,E2) enif_make_tuple2((E), (E1), (E2)) +#define MKT3(E,E1,E2,E3) enif_make_tuple3((E), (E1), (E2), (E3)) +#define MKT4(E,E1,E2,E3,E4) enif_make_tuple4((E), (E1), (E2), (E3), (E4)) +#define MKT8(E,E1,E2,E3,E4,E5,E6,E7,E8) \ + enif_make_tuple8((E), (E1), (E2), (E3), (E4), (E5), (E6), (E7), (E8)) +#define MCREATE(N) enif_mutex_create((N)) +#define MLOCK(M) enif_mutex_lock((M)) +#define MUNLOCK(M) enif_mutex_unlock((M)) +#define SELECT(E,FD,M,O,P,R) \ + if (enif_select((E), (FD), (M), (O), (P), (R)) < 0) \ + return enif_make_badarg((E)); + #define IS_ATOM(E, TE) enif_is_atom((E), (TE)) #define IS_BIN(E, TE) enif_is_binary((E), (TE)) #define IS_NUM(E, TE) enif_is_number((E), (TE)) @@ -379,12 +387,15 @@ typedef union { #define sock_htonl(x) htonl((x)) #define sock_listen(s, b) listen((s), (b)) #define sock_name(s, addr, len) getsockname((s), (addr), (len)) +#define sock_ntohs(x) ntohs((x)) #define sock_open(domain, type, proto) \ make_noninheritable_handle(socket((domain), (type), (proto))) #define sock_recv(s,buf,len,flag) recv((s),(buf),(len),(flag)) +#define sock_recvfrom(s,buf,blen,flag,addr,alen) \ + recvfrom((s),(buf),(blen),(flag),(addr),(alen)) #define sock_send(s,buf,len,flag) send((s),(buf),(len),(flag)) #define sock_sendto(s,buf,blen,flag,addr,alen) \ - sendto((s),(buf),(blen),(flag),(addr),(alen)) + sendto((s),(buf),(blen),(flag),(addr),(alen)) #define sock_errno() WSAGetLastError() #define sock_create_event(s) WSACreateEvent() @@ -410,8 +421,11 @@ static unsigned long one_value = 1; #define sock_htonl(x) htonl((x)) #define sock_listen(s, b) listen((s), (b)) #define sock_name(s, addr, len) getsockname((s), (addr), (len)) +#define sock_ntohs(x) ntohs((x)) #define sock_open(domain, type, proto) socket((domain), (type), (proto)) #define sock_recv(s,buf,len,flag) recv((s),(buf),(len),(flag)) +#define sock_recvfrom(s,buf,blen,flag,addr,alen) \ + recvfrom((s),(buf),(blen),(flag),(addr),(alen)) #define sock_send(s,buf,len,flag) send((s), (buf), (len), (flag)) #define sock_sendto(s,buf,blen,flag,addr,alen) \ sendto((s),(buf),(blen),(flag),(addr),(alen)) @@ -430,27 +444,23 @@ static unsigned long one_value = 1; /* The general purpose sockaddr */ -typedef struct { - union { - struct sockaddr sa; - struct sockaddr_in sai; +typedef union { + struct sockaddr sa; + struct sockaddr_in sai; #ifdef HAVE_IN6 - struct sockaddr_in6 sai6; + struct sockaddr_in6 sai6; #endif #ifdef HAVE_SYS_UN_H - struct sockaddr_un sal; + struct sockaddr_un sal; #endif - - } u; - unsigned int len; } SocketAddress; #define which_address_port(sap) \ - ((((sap)->u.sai.sin_family == AF_INET) || \ - ((sap)->u.sai.sin_family == AF_INET6)) ? \ - ((sap)->u.sai.sin_port) : -1) + ((((sap)->sai.sin_family == AF_INET) || \ + ((sap)->sai.sin_family == AF_INET6)) ? \ + ((sap)->sai.sin_port) : -1) typedef struct { @@ -519,9 +529,10 @@ typedef struct { SocketRequestor* currentAcceptorP; // NULL or points to currentReader SocketRequestQueue acceptorsQ; - /* +++ Misc stuff +++ */ - BOOLEAN_T iow; // Inform On Wrap - BOOLEAN_T dbg; + /* +++ Config & Misc stuff +++ */ + size_t rBufSz; // Read buffer size (when data length = 0 is specified) + BOOLEAN_T iow; // Inform On Wrap + BOOLEAN_T dbg; } SocketDescriptor; @@ -637,12 +648,18 @@ static ERL_NIF_TERM nsendto(ErlNifEnv* env, ERL_NIF_TERM sendRef, ErlNifBinary* dataP, int flags, - SocketAddress* toAddrP); + SocketAddress* toAddrP, + unsigned int toAddrLen); static ERL_NIF_TERM nrecv(ErlNifEnv* env, SocketDescriptor* descP, ERL_NIF_TERM recvRef, int len, int flags); +static ERL_NIF_TERM nrecvfrom(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM recvRef, + uint16_t bufSz, + int flags); static ERL_NIF_TERM send_check_result(ErlNifEnv* env, SocketDescriptor* descP, @@ -655,6 +672,13 @@ static ERL_NIF_TERM recv_check_result(ErlNifEnv* env, int toRead, ErlNifBinary* bufP, ERL_NIF_TERM recvRef); +static ERL_NIF_TERM recvfrom_check_result(ErlNifEnv* env, + SocketDescriptor* descP, + int read, + ErlNifBinary* bufP, + SocketAddress* fromAddrP, + unsigned int fromAddrLen, + ERL_NIF_TERM recvRef); static ERL_NIF_TERM nfinalize_connection(ErlNifEnv* env, SocketDescriptor* descP); @@ -663,36 +687,49 @@ static ERL_NIF_TERM nfinalize_connection(ErlNifEnv* env, static char* decode_laddress(ErlNifEnv* env, int domain, ERL_NIF_TERM localAddr, - SocketAddress* localP); + SocketAddress* localP, + unsigned int* addrLenP); static char* decode_laddress_binary(ErlNifEnv* env, int domain, ERL_NIF_TERM localAddr, - SocketAddress* localP); + SocketAddress* localP, + unsigned int* addrLenP); static char* decode_laddress_tuple(ErlNifEnv* env, int domain, ERL_NIF_TERM laddr, - SocketAddress* localP); + SocketAddress* localP, + unsigned int* addrLenP); static char* decode_address_tuple(ErlNifEnv* env, int domain, const ERL_NIF_TERM* addrt, int port, - SocketAddress* localP); + SocketAddress* localP, + unsigned int* addrLenP); static char* decode_address_atom(ErlNifEnv* env, int domain, char* addr, int addrLen, int port, - SocketAddress* localP); + SocketAddress* localP, + unsigned int* addrLenP); static char* decode_send_addr(ErlNifEnv* env, int domain, ERL_NIF_TERM addr, int port, - SocketAddress** toAddrP); + SocketAddress** toAddrP, + unsigned int* addrLenP); static char* decode_send_addr_tuple(ErlNifEnv* env, int domain, ERL_NIF_TERM addr, int port, - SocketAddress* toAddrP); + SocketAddress* toAddrP, + unsigned int* addrLenP); +static void encode_address(ErlNifEnv* env, + SocketAddress* fromAddrP, + unsigned int fromAddrLen, + ERL_NIF_TERM* fromDomainT, + ERL_NIF_TERM* fromSourceT); + static BOOLEAN_T verify_is_connected(SocketDescriptor* descP, int* err); static SocketDescriptor* alloc_descriptor(SOCKET sock, HANDLE event); @@ -716,6 +753,10 @@ static BOOLEAN_T restore_network_namespace(int ns, SOCKET sock, int* err); static BOOLEAN_T cnt_inc(uint32_t* cnt, uint32_t inc); +#if defined(HAVE_SYS_UN_H) || defined(SO_BINDTODEVICE) +static size_t my_strnlen(const char *s, size_t maxlen); +#endif + static void socket_dtor(ErlNifEnv* env, void* obj); static void socket_stop(ErlNifEnv* env, void* obj, @@ -1198,22 +1239,23 @@ ERL_NIF_TERM nbind(ErlNifEnv* env, ERL_NIF_TERM addr) { SocketAddress local; + unsigned int addrLen; char* err; int port; - if ((err = decode_laddress(env, descP->domain, addr, &local)) != NULL) + if ((err = decode_laddress(env, descP->domain, addr, &local, &addrLen)) != NULL) return make_error1(env, err); if (IS_SOCKET_ERROR(sock_bind(descP->sock, - (struct sockaddr*) &local.u, local.len))) { + (struct sockaddr*) &local, addrLen))) { return make_error2(env, sock_errno()); } port = which_address_port(&local); if (port == 0) { - SOCKLEN_T addrLen = sizeof(local.u); - sys_memzero((char *) &local.u, addrLen); - sock_name(descP->sock, &local.u.sa, &addrLen); + SOCKLEN_T len = sizeof(local); + sys_memzero((char *) &local, len); + sock_name(descP->sock, &local.sa, &len); port = which_address_port(&local); } else if (port == -1) { port = 0; @@ -1233,12 +1275,13 @@ static char* decode_laddress(ErlNifEnv* env, int domain, ERL_NIF_TERM localAddr, - SocketAddress* localP) + SocketAddress* localP, + unsigned int* addrLenP) { if (IS_BIN(env, localAddr)) { - return decode_laddress_binary(env, domain, localAddr, localP); + return decode_laddress_binary(env, domain, localAddr, localP, addrLenP); } else if (IS_TUPLE(env, localAddr)) { - return decode_laddress_tuple(env, domain, localAddr, localP); + return decode_laddress_tuple(env, domain, localAddr, localP, addrLenP); } else { return str_einval; } @@ -1254,8 +1297,11 @@ static char* decode_laddress_binary(ErlNifEnv* env, int domain, ERL_NIF_TERM localAddr, - SocketAddress* localP) + SocketAddress* localP, + unsigned int* addrLenP) { + unsigned int addrLen; + #ifdef HAVE_SYS_UN_H ErlNifBinary bin; @@ -1278,16 +1324,17 @@ char* decode_laddress_binary(ErlNifEnv* env, #else 1 #endif - ) > sizeof(localP->u.sal.sun_path)) + ) > sizeof(localP->sal.sun_path)) return str_einval; - sys_memzero((char*)&localP->u, sizeof(struct sockaddr_un)); - localP->u.sal.sun_family = domain; - sys_memcpy(localP->u.sal.sun_path, bin.data, bin.size); - localP->len = offsetof(struct sockaddr_un, sun_path) + bin.size; + sys_memzero((char*)localP, sizeof(struct sockaddr_un)); + localP->sal.sun_family = domain; + sys_memcpy(localP->sal.sun_path, bin.data, bin.size); + addrLen = offsetof(struct sockaddr_un, sun_path) + bin.size; #ifndef NO_SA_LEN - localP->u.sal.sun_len = localP->len; + localP->u.sal.sun_len = addrLen; #endif + *addrLenP = addrLen; return NULL; #else // HAVE_SYS_UN_H @@ -1312,7 +1359,8 @@ static char* decode_laddress_tuple(ErlNifEnv* env, int domain, ERL_NIF_TERM laddr, - SocketAddress* localP) + SocketAddress* localP, + unsigned int* addrLenP) { const ERL_NIF_TERM* laddrt; int laddrtSz; @@ -1365,7 +1413,7 @@ char* decode_laddress_tuple(ErlNifEnv* env, return decode_address_tuple(env, domain, addrt, port, - localP); + localP, addrLenP); } else if (IS_ATOM(env, laddrt[0]) && IS_NUM(env, laddrt[1])) { @@ -1388,7 +1436,7 @@ char* decode_laddress_tuple(ErlNifEnv* env, return decode_address_atom(env, domain, a, len, port, - localP); + localP, addrLenP); } else { return str_einval; @@ -1457,8 +1505,9 @@ ERL_NIF_TERM nconnect(ErlNifEnv* env, const ERL_NIF_TERM* addr, int port) { - int code; - char* xerr; + unsigned int addrLen; + int code; + char* xerr; /* Verify that we are where in the proper state */ @@ -1473,11 +1522,11 @@ ERL_NIF_TERM nconnect(ErlNifEnv* env, if ((xerr = decode_address_tuple(env, descP->domain, addr, port, - &descP->remote)) != NULL) + &descP->remote, &addrLen)) != NULL) return make_error1(env, xerr); code = sock_connect(descP->sock, - (struct sockaddr*) &descP->remote.u, descP->remote.len); + (struct sockaddr*) &descP->remote, addrLen); if (IS_SOCKET_ERROR(code) && ((sock_errno() == ERRNO_BLOCK) || /* Winsock2 */ @@ -1572,8 +1621,8 @@ BOOLEAN_T verify_is_connected(SocketDescriptor* descP, int* err) #ifndef SO_ERROR int sz, code; - sz = sizeof(descP->inet.remote); - sys_memzero((char *) &descP->inet.remote, sz); + sz = sizeof(descP->remote); + sys_memzero((char *) &descP->remote, sz); code = sock_peer(desc->sock, (struct sockaddr*) &descP->remote, &sz); @@ -1735,7 +1784,7 @@ ERL_NIF_TERM naccept_listening(ErlNifEnv* env, if (enif_self(env, &caller) == NULL) return make_error(env, atom_exself); - n = sizeof(descP->remote.u); + n = sizeof(remote); sys_memzero((char *) &remote, n); accSock = sock_accept(descP->sock, (struct sockaddr*) &remote, &n); if (accSock == INVALID_SOCKET) { @@ -1869,7 +1918,7 @@ ERL_NIF_TERM naccept_accepting(ErlNifEnv* env, return make_error(env, atom_exbusy); } - n = sizeof(descP->remote.u); + n = sizeof(descP->remote); sys_memzero((char *) &remote, n); accSock = sock_accept(descP->sock, (struct sockaddr*) &remote, &n); if (accSock == INVALID_SOCKET) { @@ -2074,6 +2123,7 @@ ERL_NIF_TERM nif_sendto(ErlNifEnv* env, int port; SocketAddress remoteAddr; SocketAddress* remoteAddrP = &remoteAddr; + unsigned int remoteAddrLen; char* xerr; // ERL_NIF_TERM res; @@ -2098,10 +2148,11 @@ ERL_NIF_TERM nif_sendto(ErlNifEnv* env, if ((xerr = decode_send_addr(env, descP->domain, addr, port, - &remoteAddrP)) != NULL) + &remoteAddrP, + &remoteAddrLen)) != NULL) return make_error1(env, xerr); - return nsendto(env, descP, sendRef, &data, flags, remoteAddrP); + return nsendto(env, descP, sendRef, &data, flags, remoteAddrP, remoteAddrLen); } @@ -2111,7 +2162,8 @@ ERL_NIF_TERM nsendto(ErlNifEnv* env, ERL_NIF_TERM sendRef, ErlNifBinary* dataP, int flags, - SocketAddress* toAddrP) + SocketAddress* toAddrP, + unsigned int toAddrLen) { ssize_t written; @@ -2126,7 +2178,7 @@ ERL_NIF_TERM nsendto(ErlNifEnv* env, if (toAddrP != NULL) { written = sock_sendto(descP->sock, dataP->data, dataP->size, flags, - &toAddrP->u.sa, toAddrP->len); + &toAddrP->sa, toAddrLen); } else { written = sock_sendto(descP->sock, dataP->data, dataP->size, flags, @@ -2273,6 +2325,11 @@ ERL_NIF_TERM nif_recv(ErlNifEnv* env, } +/* The (read) buffer handling *must* be optimized! + * But for now we make it easy for ourselves by + * allocating a binary (of the specified or default + * size) and then throwing it away... + */ static ERL_NIF_TERM nrecv(ErlNifEnv* env, SocketDescriptor* descP, @@ -2286,7 +2343,11 @@ ERL_NIF_TERM nrecv(ErlNifEnv* env, if (!descP->isReadable) return enif_make_badarg(env); - if (!ALLOC_BIN((len ? len : SOCKET_RECV_BUFFER_SIZE_DEFAULT), &buf)) + /* Allocate a buffer: + * Either as much as we want to read or (if zero (0)) use the "default" + * size (what has been configured). + */ + if (!ALLOC_BIN((len ? len : descP->rBufSz), &buf)) return make_error(env, atom_exalloc); /* We ignore the wrap for the moment. @@ -2304,6 +2365,121 @@ ERL_NIF_TERM nrecv(ErlNifEnv* env, +/* ---------------------------------------------------------------------- + * nif_recvfrom + * + * Description: + * Receive a message on a socket. + * Normally used only on a (un-) connected socket! + * If a buffer size = 0 is specified, then the we will use the default + * buffer size for this socket (whatever has been configured). + * + * Arguments: + * Socket (ref) - Points to the socket descriptor. + * RecvRef - A unique id for this (send) request. + * BufSz - Size of the buffer into which we put the received message. + * Flags - Receive flags. + */ + +static +ERL_NIF_TERM nif_recvfrom(ErlNifEnv* env, + int argc, + const ERL_NIF_TERM argv[]) +{ + SocketDescriptor* descP; + ERL_NIF_TERM recvRef; + unsigned int bufSz; + unsigned int eflags; + int flags; + ERL_NIF_TERM res; + + if ((argc != 4) || + !enif_get_resource(env, argv[0], sockets, (void**) &descP) || + !GET_UINT(env, argv[2], &bufSz) || + !GET_UINT(env, argv[3], &eflags)) { + return enif_make_badarg(env); + } + recvRef = argv[1]; + + /* if (IS_OPEN(descP)) */ + /* return make_error(env, atom_enotconn); */ + + if (!erecvflags2recvflags(eflags, &flags)) + return enif_make_badarg(env); + + MLOCK(descP->readMtx); + + /* + * We need to handle the case when another process tries + * to receive at the same time. + * If the current recv could not read its entire package + * this time (resulting in an select). The read of the + * other process must be made to wait until current + * is done! + * Basically, we need a read queue! + * + * A 'reading' field (boolean), which is set if we did + * not manage to read the entire message and reset every + * time we do. + * + */ + + res = nrecvfrom(env, descP, recvRef, bufSz, flags); + + MUNLOCK(descP->readMtx); + + return res; + +} + + +/* The (read) buffer handling *must* be optimized! + * But for now we make it easy for ourselves by + * allocating a binary (of the specified or default + * size) and then throwing it away... + */ +static +ERL_NIF_TERM nrecvfrom(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM recvRef, + uint16_t bufSz, + int flags) +{ + SocketAddress fromAddr; + unsigned int addrLen; + ssize_t read; + ErlNifBinary buf; + + if (!descP->isReadable) + return enif_make_badarg(env); + + /* Allocate a buffer: + * Either as much as we want to read or (if zero (0)) use the "default" + * size (what has been configured). + */ + if (!ALLOC_BIN((bufSz ? bufSz : descP->rBufSz), &buf)) + return make_error(env, atom_exalloc); + + /* We ignore the wrap for the moment. + * Maybe we should issue a wrap-message to controlling process... + */ + cnt_inc(&descP->readTries, 1); + + addrLen = sizeof(fromAddr); + sys_memzero((char*) &fromAddr, addrLen); + + read = sock_recvfrom(descP->sock, buf.data, buf.size, flags, + &fromAddr.sa, &addrLen); + + return recvfrom_check_result(env, descP, + read, + &buf, + &fromAddr, addrLen, + recvRef); +} + + + /* ---------------------------------------------------------------------- * U t i l i t y F u n c t i o n s * ---------------------------------------------------------------------- @@ -2338,7 +2514,11 @@ ERL_NIF_TERM send_check_result(ErlNifEnv* env, /* Ok, try again later */ - written = 0; // SHOULD RESULT IN {error, eagain}!!!! + /* + * SHOULD RESULT IN {error, eagain}!!!! + * + */ + written = 0; } } @@ -2366,11 +2546,11 @@ ERL_NIF_TERM recv_check_result(ErlNifEnv* env, ErlNifBinary* bufP, ERL_NIF_TERM recvRef) { + ERL_NIF_TERM data; + /* There is a special case: If the provided 'to read' value is - * zero (0). That means that if we filled the (default size) - * buffer, we need to continue to read (since there *may* be - * more data), but we cannot loop here. Instead we inform the - * caller that it must call again. + * zero (0). That means that we reads as much as we can, using + * the default read buffer size. */ if (bufP->size == read) { @@ -2395,7 +2575,9 @@ ERL_NIF_TERM recv_check_result(ErlNifEnv* env, * => We choose alt 1 for now. */ - return make_ok3(env, atom_false, MKBIN(env, bufP)); + data = MKBIN(env, bufP); + + return make_ok3(env, atom_false, data); } else { @@ -2406,7 +2588,9 @@ ERL_NIF_TERM recv_check_result(ErlNifEnv* env, * */ - return make_ok3(env, atom_true, MKBIN(env, bufP)); + data = MKBIN(env, bufP); + + return make_ok3(env, atom_true, data); } @@ -2416,6 +2600,89 @@ ERL_NIF_TERM recv_check_result(ErlNifEnv* env, int save_errno = sock_errno(); + if (save_errno == ECONNRESET) { + + /* +++ Oups - closed +++ */ + + /* + * + * IF THE CURRENT PROCESS IS *NOT* THE CONTROLLING + * PROCESS, WE NEED TO INFORM IT!!! + * + * ALL WAITING PROCESSES MUST ALSO GET THE ERROR!! + * + * + */ + + SELECT(env, + descP->sock, + (ERL_NIF_SELECT_STOP), + descP, NULL, recvRef); + + return make_error(env, atom_closed); + + } else if ((save_errno == ERRNO_BLOCK) || + (save_errno == EAGAIN)) { + return make_error(env, atom_eagain); + } else { + return make_error2(env, save_errno); + } + + } else { + + /* +++ We did not fill the buffer +++ */ + + if (toRead == 0) { + + /* +++ We got a chunk of data but +++ + * +++ since we did not fill the +++ + * +++ buffer, we must split it +++ + * +++ into a sub-binary. +++ + */ + + data = MKBIN(env, bufP); + data = MKSBIN(env, data, 0, read); + + return make_ok3(env, atom_true, data); + + } else { + + /* +++ We got only a part of what was expected +++ + * +++ => receive more later. +++ */ + + return make_ok3(env, atom_false, MKBIN(env, bufP)); + } + } +} + + +/* The recvfrom function delivers one (1) message. If our buffer + * is to small, the message will be truncated. So, regardless + * if we filled the buffer or not, we have got what we are going + * to get regarding this message. + */ +static +ERL_NIF_TERM recvfrom_check_result(ErlNifEnv* env, + SocketDescriptor* descP, + int read, + ErlNifBinary* bufP, + SocketAddress* fromAddrP, + unsigned int fromAddrLen, + ERL_NIF_TERM recvRef) +{ + ERL_NIF_TERM data; + + /* There is a special case: If the provided 'to read' value is + * zero (0). That means that we reads as much as we can, using + * the default read buffer size. + */ + + if (read < 0) { + + /* +++ Error handling +++ */ + + int save_errno = sock_errno(); + if (save_errno == ECONNRESET) { /* +++ Oups - closed +++ */ @@ -2445,9 +2712,29 @@ ERL_NIF_TERM recv_check_result(ErlNifEnv* env, } else { - /* +++ We got only a part of what was expected - receive more later +++ */ + /* +++ We sucessfully got a message - time to encode the address +++ */ + + ERL_NIF_TERM fromDomainT, fromSourceT; - return make_ok3(env, atom_false, MKBIN(env, bufP)); + encode_address(env, + fromAddrP, fromAddrLen, + &fromDomainT, &fromSourceT); + + if (read == bufP->size) { + data = MKBIN(env, bufP); + } else { + + /* +++ We got a chunk of data but +++ + * +++ since we did not fill the +++ + * +++ buffer, we must split it +++ + * +++ into a sub-binary. +++ + */ + + data = MKBIN(env, bufP); + data = MKSBIN(env, data, 0, read); + } + + return make_ok2(env, MKT3(env, fromDomainT, fromSourceT, data)); } } @@ -2469,7 +2756,8 @@ char* decode_send_addr(ErlNifEnv* env, int domain, ERL_NIF_TERM addr, int port, - SocketAddress** toAddrP) + SocketAddress** toAddrP, + unsigned int* toAddrLenP) { if (IS_ATOM(env, addr)) { unsigned int len; @@ -2493,7 +2781,8 @@ char* decode_send_addr(ErlNifEnv* env, } else if (IS_TUPLE(env, addr)) { /* We now know that the we have a proper address. */ - return decode_send_addr_tuple(env, domain, addr, port, *toAddrP); + return decode_send_addr_tuple(env, domain, addr, port, + *toAddrP, toAddrLenP); } else { return str_einval; } @@ -2505,7 +2794,8 @@ char* decode_send_addr_tuple(ErlNifEnv* env, int domain, ERL_NIF_TERM addr, int port, - SocketAddress* toAddrP) + SocketAddress* toAddrP, + unsigned int* toAddrLenP) { /* We handle two different tuples: * - size 4 (INET) @@ -2538,7 +2828,7 @@ char* decode_send_addr_tuple(ErlNifEnv* env, return decode_address_tuple(env, domain, addrt, port, - toAddrP); + toAddrP, toAddrLenP); } @@ -2551,7 +2841,8 @@ char* decode_address_tuple(ErlNifEnv* env, int domain, const ERL_NIF_TERM* addrt, int port, - SocketAddress* addrP) + SocketAddress* addrP, + unsigned int* addrLenP) { /* We now *know* that the size of the tuple is correct, @@ -2564,19 +2855,19 @@ char* decode_address_tuple(ErlNifEnv* env, int a, v; char laddr[4]; - sys_memzero((char*)&addrP->u, sizeof(struct sockaddr_in)); + sys_memzero((char*)addrP, sizeof(struct sockaddr_in)); #ifndef NO_SA_LEN - addrP->u.sai.sin_len = sizeof(struct sockaddr_in); + addrP->sai.sin_len = sizeof(struct sockaddr_in); #endif - addrP->u.sai.sin_family = domain; - addrP->u.sai.sin_port = sock_htons(port); + addrP->sai.sin_family = domain; + addrP->sai.sin_port = sock_htons(port); for (a = 0; a < 4; a++) { if (!GET_INT(env, addrt[a], &v)) return str_einval; laddr[a] = v; } - sys_memcpy(&addrP->u.sai.sin_addr, &laddr, sizeof(laddr)); - addrP->len = sizeof(struct sockaddr_in); + sys_memcpy(&addrP->sai.sin_addr, &laddr, sizeof(laddr)); + *addrLenP = sizeof(struct sockaddr_in); return NULL; } break; @@ -2587,13 +2878,13 @@ char* decode_address_tuple(ErlNifEnv* env, int a, v; char laddr[16]; - sys_memzero((char*)&addrP->u, sizeof(struct sockaddr_in6)); + sys_memzero((char*)addrP, sizeof(struct sockaddr_in6)); #ifndef NO_SA_LEN - addrP->u.sai6.sin6_len = sizeof(struct sockaddr_in6); + addrP->sai6.sin6_len = sizeof(struct sockaddr_in6); #endif - addrP->u.sai6.sin6_family = domain; - addrP->u.sai6.sin6_port = sock_htons(port); - addrP->u.sai6.sin6_flowinfo = 0; + addrP->sai6.sin6_family = domain; + addrP->sai6.sin6_port = sock_htons(port); + addrP->sai6.sin6_flowinfo = 0; /* The address tuple is of size 8 * and each element is a two byte integer */ @@ -2603,8 +2894,8 @@ char* decode_address_tuple(ErlNifEnv* env, laddr[a*2 ] = ((v >> 8) & 0xFF); laddr[a*2+1] = (v & 0xFF); } - sys_memcpy(&addrP->u.sai6.sin6_addr, &laddr, sizeof(laddr)); - addrP->len = sizeof(struct sockaddr_in6); + sys_memcpy(&addrP->sai6.sin6_addr, &laddr, sizeof(laddr)); + *addrLenP = sizeof(struct sockaddr_in6); return NULL; } break; @@ -2616,6 +2907,131 @@ char* decode_address_tuple(ErlNifEnv* env, } +/* Encode the 4- or 8-element address tuple from the socket address structure. + * + * This function is called when we have received a message. So, if we for some + * reason fail to decode the address or parts of it, it makes more sense to + * return with "undefined" for the values rather then fail completely (and not + * deliver the received message). + * + * Returns two things (assuming the encode works): + * + * Domain: inet | inet6 | local + * Source: {Address, Port} | string() + * + */ +static +void encode_address(ErlNifEnv* env, + SocketAddress* addrP, + unsigned int addrLen, + ERL_NIF_TERM* domainT, + ERL_NIF_TERM* sourceT) +{ + short port; + + switch (addrP->sa.sa_family) { + + /* +++ inet (IPv4) +++ */ + + case AF_INET: + if (addrLen >= sizeof(struct sockaddr_in)) { + ERL_NIF_TERM addrT, portT; + unsigned int i; + ERL_NIF_TERM at4[4]; + char* a4 = (char*) &addrP->sai.sin_addr; + + port = sock_ntohs(addrP->sai.sin_port); + for (i = 0; i < 4; i++) { + at4[i] = MKI(env, a4[i]); + } + + *domainT = MKA(env, "inet"); // Shall we encode these? See decode + addrT = MKT4(env, at4[0], at4[1], at4[2], at4[3]); + portT = MKI(env, port); + *sourceT = MKT2(env, addrT, portT); + } else { + *domainT = atom_undefined; + *sourceT = atom_undefined; + } + break; + + + /* +++ inet6 (IPv6) +++ */ + +#if defined(HAVE_IN6) && defined(AF_INET6) + case AF_INET6: + if (addrLen >= sizeof(struct sockaddr_in6)) { + ERL_NIF_TERM addrT, portT; + unsigned int i; + ERL_NIF_TERM at6[8]; + char* a16 = (char*) &addrP->sai6.sin6_addr; + + port = sock_ntohs(addrP->sai6.sin6_port); + /* The address tuple is of size 8 + * and each element is a two byte integer + */ + for (i = 0; i < 8; i++) { + // at6[i] = MKI(env, get_int16(a16[i*2])); + at6[i] = MKI(env, get_int16(a16 + i*2)); + } + + *domainT = MKA(env, "inet6"); // Shall we encode these? See decode + addrT = MKT8(env, + at6[0], at6[1], at6[2], at6[3], + at6[4], at6[5], at6[6], at6[7]); + portT = MKI(env, port); + *sourceT = MKT2(env, addrT, portT); + } else { + *domainT = atom_undefined; + *sourceT = atom_undefined; + } + break; +#endif + + /* +++ local (Unix Domain Sockets) +++ */ + +#ifdef HAVE_SYS_UN_H + case AF_UNIX: + { + size_t n, m; + + *domainT = MKA(env, "local"); + if (addrLen < offsetof(struct sockaddr_un, sun_path)) { + *sourceT = atom_undefined; + } else { + n = addrLen - offsetof(struct sockaddr_un, sun_path); + if (255 < n) { + *sourceT = atom_undefined; + } else { + m = my_strnlen(addrP->sal.sun_path, n); +#ifdef __linux__ + /* Assume that the address is a zero terminated string, + * except when the first byte is \0 i.e the string length is 0, + * then use the reported length instead. + * This fix handles Linux's nonportable + * abstract socket address extension. + */ + if (m == 0) { + m = n; + } +#endif + + *sourceT = MKSL(env, addrP->sal.sun_path, m); + } + } + } + break; +#endif + + default: + *domainT = atom_undefined; + *sourceT = atom_undefined; + break; + + } /* switch (addrP->sa.sa_family) */ + +} + /* Decode the address when its an atom. * Currently we only accept two atoms: 'any' and 'loopback' @@ -2626,7 +3042,8 @@ char* decode_address_atom(ErlNifEnv* env, char* addr, int addrLen, int port, - SocketAddress* localP) + SocketAddress* addrP, + unsigned int* addrLenP) { BOOLEAN_T any; @@ -2649,14 +3066,14 @@ char* decode_address_atom(ErlNifEnv* env, } else { addr.s_addr = sock_htonl(INADDR_LOOPBACK); } - sys_memzero((char*) localP, sizeof(struct sockaddr_in)); + sys_memzero((char*) addrP, sizeof(struct sockaddr_in)); #ifndef NO_SA_LEN - localP->u.sai.sin_len = sizeof(struct sockaddr_in6); + addrP->sai.sin_len = sizeof(struct sockaddr_in6); #endif - localP->u.sai.sin_family = domain; - localP->u.sai.sin_port = sock_htons(port); - localP->u.sai.sin_addr.s_addr = addr.s_addr; - localP->len = sizeof(struct sockaddr_in); + addrP->sai.sin_family = domain; + addrP->sai.sin_port = sock_htons(port); + addrP->sai.sin_addr.s_addr = addr.s_addr; + *addrLenP = sizeof(struct sockaddr_in); } break; @@ -2669,15 +3086,15 @@ char* decode_address_atom(ErlNifEnv* env, } else { paddr = &in6addr_loopback; } - sys_memzero((char*)localP, sizeof(struct sockaddr_in6)); + sys_memzero((char*)addrP, sizeof(struct sockaddr_in6)); #ifndef NO_SA_LEN - localP->u.sai6.sin6_len = sizeof(struct sockaddr_in6); + addrP->sai6.sin6_len = sizeof(struct sockaddr_in6); #endif - localP->u.sai6.sin6_family = domain; - localP->u.sai6.sin6_port = sock_htons(port); - localP->u.sai6.sin6_flowinfo = 0; - localP->u.sai6.sin6_addr = *paddr; - localP->len = sizeof(struct sockaddr_in6); + addrP->sai6.sin6_family = domain; + addrP->sai6.sin6_port = sock_htons(port); + addrP->sai6.sin6_flowinfo = 0; + addrP->sai6.sin6_addr = *paddr; + *addrLenP = sizeof(struct sockaddr_in6); } break; #endif @@ -2709,12 +3126,23 @@ SocketDescriptor* alloc_descriptor(SOCKET sock, HANDLE event) descP->currentWriterP = NULL; // currentWriter not used descP->writersQ.first = NULL; descP->writersQ.last = NULL; + descP->isWritable = TRUE; + descP->writePkgCnt = 0; + descP->writeByteCnt = 0; + descP->writeTries = 0; + descP->writeWaits = 0; + descP->writeFails = 0; sprintf(buf, "socket[r,%d]", sock); descP->readMtx = MCREATE(buf); descP->currentReaderP = NULL; // currentReader not used descP->readersQ.first = NULL; descP->readersQ.last = NULL; + descP->isReadable = TRUE; + descP->readPkgCnt = 0; + descP->readByteCnt = 0; + descP->readTries = 0; + descP->readWaits = 0; sprintf(buf, "socket[acc,%d]", sock); descP->accMtx = MCREATE(buf); @@ -2722,22 +3150,12 @@ SocketDescriptor* alloc_descriptor(SOCKET sock, HANDLE event) descP->acceptorsQ.first = NULL; descP->acceptorsQ.last = NULL; - descP->iow = FALSE; - descP->dbg = SOCKET_DEBUG_DEFAULT; - descP->isWritable = TRUE; - descP->isReadable = TRUE; - descP->writePkgCnt = 0; - descP->writeByteCnt = 0; - descP->writeTries = 0; - descP->writeWaits = 0; - descP->writeFails = 0; - descP->readPkgCnt = 0; - descP->readByteCnt = 0; - descP->readTries = 0; - descP->readWaits = 0; - - descP->sock = sock; - descP->event = event; + descP->rBufSz = SOCKET_RECV_BUFFER_SIZE_DEFAULT; + descP->iow = FALSE; + descP->dbg = SOCKET_DEBUG_DEFAULT; + + descP->sock = sock; + descP->event = event; } @@ -2931,10 +3349,6 @@ BOOLEAN_T esendflags2sendflags(unsigned int esendflags, int* sendflags) tmp |= MSG_DONTROUTE; break; - case SOCKET_SEND_FLAG_DONTWAIT: - tmp |= MSG_DONTWAIT; - break; - case SOCKET_SEND_FLAG_EOR: tmp |= MSG_EOR; break; @@ -2979,10 +3393,6 @@ BOOLEAN_T erecvflags2recvflags(unsigned int erecvflags, int* recvflags) tmp |= MSG_CMSG_CLOEXEC; break; - case SOCKET_RECV_FLAG_DONTWAIT: - tmp |= MSG_DONTWAIT; - break; - case SOCKET_RECV_FLAG_ERRQUEUE: tmp |= MSG_ERRQUEUE; break; @@ -2999,10 +3409,6 @@ BOOLEAN_T erecvflags2recvflags(unsigned int erecvflags, int* recvflags) tmp |= MSG_TRUNC; break; - case SOCKET_RECV_FLAG_WAITALL: - tmp |= MSG_WAITALL; - break; - default: return FALSE; } @@ -3015,6 +3421,18 @@ BOOLEAN_T erecvflags2recvflags(unsigned int erecvflags, int* recvflags) } +#if defined(HAVE_SYS_UN_H) || defined(SO_BINDTODEVICE) +/* strnlen doesn't exist everywhere */ +static +size_t my_strnlen(const char *s, size_t maxlen) +{ + size_t i = 0; + while (i < maxlen && s[i] != '\0') + i++; + return i; +} +#endif + /* Create an ok two (2) tuple in the form: {ok, Any}. * The second element (Any) is already in the form of an diff --git a/erts/preloaded/src/socket.erl b/erts/preloaded/src/socket.erl index 0dadcecaa0..bae561cd51 100644 --- a/erts/preloaded/src/socket.erl +++ b/erts/preloaded/src/socket.erl @@ -39,7 +39,7 @@ %% writev/4, OR SENDV? It will be strange for recv then: recvv (instead of readv) recv/2, recv/3, recv/4, - recvfrom/1, recvfrom/2, + recvfrom/1, recvfrom/2, recvfrom/3, recvfrom/4, %% recvmsg/4, %% readv/3, @@ -106,10 +106,10 @@ -type port_number() :: 0..65535. -type socket_info() :: map(). -%% -record(socket, {info :: socket_info, -%% ref :: reference()}). --opaque socket() :: {socket, socket_info(), reference()}. -%% -opaque socket() :: #socket{}. +-record(socket, {info :: socket_info(), + ref :: reference()}). +%% -opaque socket() :: {socket, socket_info(), reference()}. +-opaque socket() :: #socket{}. -type accept_flags() :: [accept_flag()]. -type accept_flag() :: nonblock | cloexec. @@ -117,20 +117,26 @@ -type send_flags() :: [send_flag()]. -type send_flag() :: confirm | dontroute | - dontwait | eor | more | nosignal | oob. +%% Extend with OWN flags for other usage: +%% - adapt-buffer-sz: +%% This will have the effect that the nif recvfrom will use +%% MSG_PEEK to ensure no part of the message is lost, but if +%% necessary adapt (increase) the buffer size until all of +%% it fits. +%% +%% Note that not all of these flags is useful for every recv function! +%% -type recv_flags() :: [recv_flag()]. -type recv_flag() :: cmsg_cloexec | - dontwait | errqueue | oob | peek | - trunc | - waitall. + trunc. -type setopt_key() :: foo. -type getopt_key() :: foo. @@ -177,23 +183,20 @@ -define(SOCKET_SEND_FLAG_CONFIRM, 0). -define(SOCKET_SEND_FLAG_DONTROUTE, 1). --define(SOCKET_SEND_FLAG_DONTWAIT, 2). --define(SOCKET_SEND_FLAG_EOR, 3). --define(SOCKET_SEND_FLAG_MORE, 4). --define(SOCKET_SEND_FLAG_NOSIGNAL, 5). --define(SOCKET_SEND_FLAG_OOB, 6). +-define(SOCKET_SEND_FLAG_EOR, 2). +-define(SOCKET_SEND_FLAG_MORE, 3). +-define(SOCKET_SEND_FLAG_NOSIGNAL, 4). +-define(SOCKET_SEND_FLAG_OOB, 5). -define(SOCKET_SEND_FLAGS_DEFAULT, []). -define(SOCKET_SEND_TIMEOUT_DEFAULT, infinity). -define(SOCKET_SENDTO_TIMEOUT_DEFAULT, ?SOCKET_SEND_TIMEOUT_DEFAULT). -define(SOCKET_RECV_FLAG_CMSG_CLOEXEC, 0). --define(SOCKET_RECV_FLAG_DONTWAIT, 1). --define(SOCKET_RECV_FLAG_ERRQUEUE, 2). --define(SOCKET_RECV_FLAG_OOB, 3). --define(SOCKET_RECV_FLAG_PEEK, 4). --define(SOCKET_RECV_FLAG_TRUNC, 5). --define(SOCKET_RECV_FLAG_WAITALL, 6). +-define(SOCKET_RECV_FLAG_ERRQUEUE, 1). +-define(SOCKET_RECV_FLAG_OOB, 2). +-define(SOCKET_RECV_FLAG_PEEK, 3). +-define(SOCKET_RECV_FLAG_TRUNC, 4). -define(SOCKET_RECV_FLAGS_DEFAULT, []). -define(SOCKET_RECV_TIMEOUT_DEFAULT, infinity). @@ -287,7 +290,8 @@ open(Domain, Type, Protocol0, Extra) when is_map(Extra) -> SocketInfo = #{domain => Domain, type => Type, protocol => Protocol}, - Socket = {socket, SocketInfo, SockRef}, + Socket = #socket{info = SocketInfo, + ref = SockRef}, {ok, Socket}; {error, _} = ERROR -> ERROR @@ -352,7 +356,7 @@ bind(Socket, Addr) when is_tuple(Addr) orelse Reason :: term(). %% Shall we keep info about domain so that we can verify address? -bind({socket, _, SockRef}, Addr, Port) +bind(#socket{ref = SockRef}, Addr, Port) when (is_tuple(Addr) andalso ((size(Addr) =:= 4) orelse (size(Addr) =:= 8))) orelse ((Addr =:= any) orelse (Addr =:= loopback)) andalso @@ -385,7 +389,7 @@ connect(Socket, Addr, Port) -> connect(_Socket, _Addr, _Port, Timeout) when (is_integer(Timeout) andalso (Timeout =< 0)) -> {error, timeout}; -connect({socket, _, SockRef}, Addr, Port, Timeout) +connect(#socket{ref = SockRef}, Addr, Port, Timeout) when (is_tuple(Addr) andalso ((size(Addr) =:= 4) orelse (size(Addr) =:= 8))) andalso (is_integer(Port) andalso (Port >= 0)) andalso @@ -424,7 +428,7 @@ connect({socket, _, SockRef}, Addr, Port, Timeout) listen(Socket) -> listen(Socket, ?SOCKET_LISTEN_BACKLOG_DEFAULT). -listen({socket, _, SockRef}, Backlog) +listen(#socket{ref = SockRef}, Backlog) when (is_integer(Backlog) andalso (Backlog >= 0)) -> nif_listen(SockRef, Backlog). @@ -448,7 +452,7 @@ accept(Socket) -> %% Do we really need this optimization? accept(_, Timeout) when is_integer(Timeout) andalso (Timeout =< 0) -> {error, timeout}; -accept({socket, SI, LSockRef}, Timeout) +accept(#socket{info = SI, ref = LSockRef}, Timeout) when is_integer(Timeout) orelse (Timeout =:= infinity) -> do_accept(LSockRef, SI, Timeout). @@ -460,7 +464,8 @@ do_accept(LSockRef, SI, Timeout) -> SocketInfo = #{domain => maps:get(domain, SI), type => maps:get(type, SI), protocol => maps:get(protocol, SI)}, - Socket = {socket, SocketInfo, SockRef}, + Socket = #socket{info = SocketInfo, + ref = SockRef}, {ok, Socket}; {error, eagain} -> NewTimeout = next_timeout(TS, Timeout), @@ -499,9 +504,10 @@ send(Socket, Data, Timeout) -> send(Socket, Data, Flags, Timeout) when is_list(Data) -> Bin = erlang:list_to_binary(Data), send(Socket, Bin, Flags, Timeout); -send(Socket, Data, Flags, Timeout) when is_binary(Data) andalso is_list(Flags) -> +send(#socket{ref = SockRef}, Data, Flags, Timeout) + when is_binary(Data) andalso is_list(Flags) -> EFlags = enc_send_flags(Flags), - do_send(Socket, Data, EFlags, Timeout). + do_send(SockRef, Data, EFlags, Timeout). do_send(SockRef, Data, EFlags, Timeout) -> TS = timestamp(Timeout), @@ -562,14 +568,14 @@ sendto(Socket, Data, Flags, DestAddr, DestPort) -> sendto(Socket, Data, Flags, DestAddr, DestPort, Timeout) when is_list(Data) -> Bin = erlang:list_to_binary(Data), sendto(Socket, Bin, Flags, DestAddr, DestPort, Timeout); -sendto(Socket, Data, Flags, DestAddr, DestPort, Timeout) +sendto(#socket{ref = SockRef}, Data, Flags, DestAddr, DestPort, Timeout) when is_binary(Data) andalso is_list(Flags) andalso (is_tuple(DestAddr) orelse (DestAddr =:= null)) andalso is_integer(DestPort) andalso (is_integer(Timeout) orelse (Timeout =:= infinity)) -> EFlags = enc_send_flags(Flags), - do_sendto(Socket, Data, EFlags, DestAddr, DestPort, Timeout). + do_sendto(SockRef, Data, EFlags, DestAddr, DestPort, Timeout). do_sendto(SockRef, Data, EFlags, DestAddr, DestPort, Timeout) -> TS = timestamp(Timeout), @@ -726,18 +732,18 @@ recv(Socket, Length, Timeout) -> Data :: binary(), Reason :: term(). -recv(Socket, Length, Flags, Timeout) +recv(#socket{ref = SockRef}, Length, Flags, Timeout) when (is_integer(Length) andalso (Length >= 0)) andalso is_list(Flags) andalso (is_integer(Timeout) orelse (Timeout =:= infinity)) -> EFlags = enc_recv_flags(Flags), - do_recv(Socket, undefined, Length, EFlags, <<>>, EFlags). + do_recv(SockRef, undefined, Length, EFlags, <<>>, Timeout). %% We need to pass the "old recv ref" around because of the special case %% with Length = 0. This case makes it neccessary to have a timeout function %% clause since we may never wait for anything (no receive select), and so the %% the only timeout check will be the function clause. -do_recv({socket, _, SockRef} = Socket, _OldRef, Length, EFlags, Acc, Timeout) +do_recv(SockRef, _OldRef, Length, EFlags, Acc, Timeout) when (Timeout =:= infinity) orelse (is_integer(Timeout) andalso (Timeout > 0)) -> TS = timestamp(Timeout), @@ -754,7 +760,7 @@ do_recv({socket, _, SockRef} = Socket, _OldRef, Length, EFlags, Acc, Timeout) %% > 0 - We got a part of the message and we will be notified %% when there is more to read (a select message) {ok, false = _Complete, Bin} when (Length =:= 0) -> - do_recv(Socket, RecvRef, + do_recv(SockRef, RecvRef, Length, EFlags, <>, next_timeout(TS, Timeout)); @@ -766,7 +772,7 @@ do_recv({socket, _, SockRef} = Socket, _OldRef, Length, EFlags, Acc, Timeout) NewTimeout = next_timeout(TS, Timeout), receive {select, SockRef, RecvRef, ready_input} -> - do_recv(Socket, RecvRef, + do_recv(SockRef, RecvRef, Length-size(Bin), EFlags, Bin, next_timeout(TS, Timeout)) @@ -781,7 +787,7 @@ do_recv({socket, _, SockRef} = Socket, _OldRef, Length, EFlags, Acc, Timeout) NewTimeout = next_timeout(TS, Timeout), receive {select, SockRef, RecvRef, ready_input} -> - do_recv(Socket, RecvRef, + do_recv(SockRef, RecvRef, Length-size(Bin), EFlags, <>, next_timeout(TS, Timeout)) @@ -801,7 +807,7 @@ do_recv({socket, _, SockRef} = Socket, _OldRef, Length, EFlags, Acc, Timeout) NewTimeout = next_timeout(TS, Timeout), receive {select, SockRef, RecvRef, ready_input} -> - do_recv(Socket, RecvRef, + do_recv(SockRef, RecvRef, Length, EFlags, Acc, next_timeout(TS, Timeout)) @@ -819,37 +825,92 @@ do_recv({socket, _, SockRef} = Socket, _OldRef, Length, EFlags, Acc, Timeout) end; -do_recv({socket, _, SockRef} = _Socket, RecvRef, - 0 = _Length, _Eflags, Acc, _Timeout) -> +do_recv(SockRef, RecvRef, 0 = _Length, _Eflags, Acc, _Timeout) -> %% The current recv operation is to be cancelled, so no need for a ref... %% The cancel will end our 'read everything you have' and "activate" %% any waiting reader. nif_cancel(SockRef, recv, RecvRef), {ok, Acc}; -do_recv(_Socket, _RecvRef, _Length, _EFlags, Acc, _Timeout) when (size(Acc) > 0) -> +do_recv(_SockRef, _RecvRef, _Length, _EFlags, Acc, _Timeout) when (size(Acc) > 0) -> {error, {timeout, Acc}}; -do_recv(_Socket, _RecvRef, _Length, _EFlags, _Acc, _Timeout) -> +do_recv(_SockRef, _RecvRef, _Length, _EFlags, _Acc, _Timeout) -> {error, timeout}. %% --------------------------------------------------------------------------- %% +%% With recvfrom we get messages, which means that regardless of how +%% much we want to read, we return when we get a message. +%% The MaxSize argument basically defines the size of our receive +%% buffer. By setting the size to zero (0), we use the configured +%% size (see setopt). +%% It may be impossible to know what (buffer) size is appropriate +%% "in advance", and in those cases it may be convenient to use the +%% (recv) 'peek' flag. When this flag is provided the message is *not* +%% "consumed" from the underlying buffers, so another recvfrom call +%% is needed, possibly with a then adjusted buffer size. +%% recvfrom(Socket) -> - recvfrom(Socket, ?SOCKET_RECV_FLAGS_DEFAULT). + recvfrom(Socket, 0). + +recvfrom(Socket, BufSz) -> + recvfrom(Socket, BufSz, + ?SOCKET_RECV_FLAGS_DEFAULT, + ?SOCKET_RECV_TIMEOUT_DEFAULT). + + +recvfrom(Socket, Flags, Timeout) when is_list(Flags) -> + recvfrom(Socket, 0, Flags, Timeout); +recvfrom(Socket, BufSz, Flags) when is_list(Flags) -> + recvfrom(Socket, BufSz, Flags, ?SOCKET_RECV_TIMEOUT_DEFAULT); +recvfrom(Socket, BufSz, Timeout) -> + recvfrom(Socket, BufSz, ?SOCKET_RECV_FLAGS_DEFAULT, Timeout). + +-spec recvfrom(Socket, BufSz, Flags, Timeout) -> {ok, {SrcDomain, Source, Data}} | {error, Reason} when + Socket :: socket(), + BufSz :: non_neg_integer(), + Flags :: recv_flags(), + Timeout :: timeout(), + SrcDomain :: domain() | undefined, + Source :: {ip_address(), port_number()} | string() | undefined, + Data :: binary(), + Reason :: term(). + +recvfrom(#socket{ref = SockRef}, BufSz, Flags, Timeout) + when (is_integer(BufSz) andalso (BufSz >= 0)) andalso + is_list(Flags) andalso + (is_integer(Timeout) orelse (Timeout =:= infinity)) -> + EFlags = enc_recv_flags(Flags), + do_recvfrom(SockRef, BufSz, EFlags, Timeout). --spec recvfrom(Socket, Flags) -> {ok, Data, SrcAddr, SrcPort} | {error, Reason} when - Socket :: socket(), - Flags :: recv_flags(), - Data :: binary(), - SrcAddr :: ip_address(), - SrcPort :: port_number(), - Reason :: term(). +do_recvfrom(SockRef, BufSz, EFlags, Timeout) -> + TS = timestamp(Timeout), + RecvRef = make_ref(), + case nif_recvfrom(SockRef, RecvRef, BufSz, EFlags) of + {ok, {_Domain, _Source, _NewData}} = OK -> + OK; + + {error, eagain} -> + %% There is nothing just now, but we will be notified when there + %% is something to read (a select message). + NewTimeout = next_timeout(TS, Timeout), + receive + {select, SockRef, RecvRef, ready_input} -> + do_recvfrom(SockRef, BufSz, EFlags, + next_timeout(TS, Timeout)) + after NewTimeout -> + nif_cancel(SockRef, recvfrom, RecvRef), + flush_select_msgs(SockRef, RecvRef), + {error, timeout} + end; + + {error, _} = ERROR -> + ERROR + + end. -recvfrom({socket, _, SockRef}, Flags) when is_list(Flags) -> - EFlags = enc_recv_flags(Flags), - nif_recvfrom(SockRef, EFlags). %% --------------------------------------------------------------------------- @@ -998,7 +1059,6 @@ enc_protocol(Type, Proto) -> throw({error, {invalid_protocol, {Type, Proto}} enc_send_flags(Flags) -> EFlags = [{confirm, ?SOCKET_SEND_FLAG_CONFIRM}, {dontroute, ?SOCKET_SEND_FLAG_DONTROUTE}, - {dontwait, ?SOCKET_SEND_FLAG_DONTWAIT}, {eor, ?SOCKET_SEND_FLAG_EOR}, {more, ?SOCKET_SEND_FLAG_MORE}, {nosignal, ?SOCKET_SEND_FLAG_NOSIGNAL}, @@ -1010,12 +1070,10 @@ enc_send_flags(Flags) -> enc_recv_flags(Flags) -> EFlags = [{cmsg_cloexec, ?SOCKET_RECV_FLAG_CMSG_CLOEXEC}, - {dontwait, ?SOCKET_RECV_FLAG_DONTWAIT}, {errqueue, ?SOCKET_RECV_FLAG_ERRQUEUE}, {oob, ?SOCKET_RECV_FLAG_OOB}, {peek, ?SOCKET_RECV_FLAG_PEEK}, - {trunc, ?SOCKET_RECV_FLAG_TRUNC}, - {waitall, ?SOCKET_RECV_FLAG_WAITALL}], + {trunc, ?SOCKET_RECV_FLAG_TRUNC}], enc_flags(Flags, EFlags). @@ -1159,7 +1217,7 @@ nif_sendto(_SRef, _SendRef, _Data, _Flags, _Dest, _Port) -> nif_recv(_SRef, _RecvRef, _Length, _Flags) -> erlang:error(badarg). -nif_recvfrom(_SRef, _Flags) -> +nif_recvfrom(_SRef, _RecvRef, _Length, _Flags) -> erlang:error(badarg). nif_cancel(_SRef, _Op, _Ref) -> -- cgit v1.2.3 From 599a320f630991823fc28b6a8a9f09851e261fed Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Tue, 24 Apr 2018 17:38:52 +0200 Subject: [socket-nif] "Completed" the close function There is probably a lot of things left to be here. For instance the handling of ECONNRESET when reading (recv and recvfrom). Also some stuff about setopt and getopt. --- erts/emulator/nifs/common/socket_nif.c | 542 ++++++++++++++++++++++++++++++--- erts/preloaded/src/socket.erl | 123 ++++++-- 2 files changed, 597 insertions(+), 68 deletions(-) diff --git a/erts/emulator/nifs/common/socket_nif.c b/erts/emulator/nifs/common/socket_nif.c index 6e6851a608..d55de9ff4e 100644 --- a/erts/emulator/nifs/common/socket_nif.c +++ b/erts/emulator/nifs/common/socket_nif.c @@ -185,6 +185,10 @@ typedef unsigned int BOOLEAN_T; #define get_int16(s) ((((unsigned char*) (s))[0] << 8) | \ (((unsigned char*) (s))[1])) +#define SASSERT(e) \ + ((void) ((e) ? 1 : (xabort(#e, __func__, __FILE__, __LINE__), 0))) + + /* Debug stuff... */ #define SOCKET_NIF_DEBUG_DEFAULT TRUE #define SOCKET_DEBUG_DEFAULT TRUE @@ -235,6 +239,7 @@ typedef unsigned long long llu_t; #define SOCKET_FLAG_CON 0x0010 #define SOCKET_FLAG_ACC 0x0020 #define SOCKET_FLAG_BUSY 0x0040 +#define SOCKET_FLAG_CLOSE 0x0080 #define SOCKET_STATE_CLOSED (0) #define SOCKET_STATE_OPEN (SOCKET_FLAG_OPEN) @@ -242,6 +247,7 @@ typedef unsigned long long llu_t; #define SOCKET_STATE_LISTENING (SOCKET_STATE_OPEN | SOCKET_FLAG_LISTEN) #define SOCKET_STATE_CONNECTING (SOCKET_STATE_OPEN | SOCKET_FLAG_CON) #define SOCKET_STATE_ACCEPTING (SOCKET_STATE_LISTENING | SOCKET_FLAG_ACC) +#define SOCKET_STATE_CLOSING (SOCKET_FLAG_CLOSE) #define IS_OPEN(d) \ (((d)->state & SOCKET_FLAG_OPEN) == SOCKET_FLAG_OPEN) @@ -331,6 +337,7 @@ typedef union { #define MALLOC(SZ) enif_alloc((SZ)) #define FREE(P) enif_free((P)) + #define MKA(E,S) enif_make_atom((E), (S)) #define MKBIN(E,B) enif_make_binary((E), (B)) #define MKI(E,I) enif_make_int((E), (I)) @@ -343,9 +350,15 @@ typedef union { #define MKT4(E,E1,E2,E3,E4) enif_make_tuple4((E), (E1), (E2), (E3), (E4)) #define MKT8(E,E1,E2,E3,E4,E5,E6,E7,E8) \ enif_make_tuple8((E), (E1), (E2), (E3), (E4), (E5), (E6), (E7), (E8)) + #define MCREATE(N) enif_mutex_create((N)) +#define MDESTROY(M) enif_mutex_destroy((M)) #define MLOCK(M) enif_mutex_lock((M)) #define MUNLOCK(M) enif_mutex_unlock((M)) + +#define MONP(E,D,P,M) enif_monitor_process((E), (D), (P), (M)) +#define DEMONP(E,D,M) enif_demonitor_process((E), (D), (M)) + #define SELECT(E,FD,M,O,P,R) \ if (enif_select((E), (FD), (M), (O), (P), (R)) < 0) \ return enif_make_badarg((E)); @@ -375,13 +388,16 @@ typedef union { #ifdef __WIN32__ -/* *** Windown macros *** */ +/* *** Windows macros *** */ #define sock_accept(s, addr, len) \ make_noninheritable_handle(accept((s), (addr), (len))) #define sock_bind(s, addr, len) bind((s), (addr), (len)) #define sock_close(s) closesocket((s)) +#define sock_close_event(e) WSACloseEvent(e) #define sock_connect(s, addr, len) connect((s), (addr), (len)) +#define sock_create_event(s) WSACreateEvent() +#define sock_errno() WSAGetLastError() #define sock_getopt(s,t,n,v,l) getsockopt((s),(t),(n),(v),(l)) #define sock_htons(x) htons((x)) #define sock_htonl(x) htonl((x)) @@ -397,16 +413,16 @@ typedef union { #define sock_sendto(s,buf,blen,flag,addr,alen) \ sendto((s),(buf),(blen),(flag),(addr),(alen)) -#define sock_errno() WSAGetLastError() -#define sock_create_event(s) WSACreateEvent() #define SET_BLOCKING(s) ioctlsocket(s, FIONBIO, &zero_value) #define SET_NONBLOCKING(s) ioctlsocket(s, FIONBIO, &one_value) static unsigned long zero_value = 0; static unsigned long one_value = 1; + #else /* !__WIN32__ */ + #ifdef HAS_ACCEPT4 // We have to figure out what the flags are... #define sock_accept(s, addr, len) accept4((s), (addr), (len), (SOCK_CLOEXEC)) @@ -415,7 +431,10 @@ static unsigned long one_value = 1; #endif #define sock_bind(s, addr, len) bind((s), (addr), (len)) #define sock_close(s) close((s)) +#define sock_close_event(e) /* do nothing */ #define sock_connect(s, addr, len) connect((s), (addr), (len)) +#define sock_create_event(s) (s) /* return file descriptor */ +#define sock_errno() errno #define sock_getopt(s,t,n,v,l) getsockopt((s),(t),(n),(v),(l)) #define sock_htons(x) htons((x)) #define sock_htonl(x) htonl((x)) @@ -430,10 +449,6 @@ static unsigned long one_value = 1; #define sock_sendto(s,buf,blen,flag,addr,alen) \ sendto((s),(buf),(blen),(flag),(addr),(alen)) -#define sock_errno() errno -#define sock_create_event(s) (s) /* return file descriptor */ - - #endif /* !__WIN32__ */ #ifdef HAVE_SOCKLEN_T @@ -470,7 +485,7 @@ typedef struct { } SocketRequestor; typedef struct socket_request_queue_element { - struct socket_request_queue_element* next; + struct socket_request_queue_element* nextP; SocketRequestor data; } SocketRequestQueueElement; @@ -534,6 +549,12 @@ typedef struct { BOOLEAN_T iow; // Inform On Wrap BOOLEAN_T dbg; + /* +++ Close stuff +++ */ + ErlNifMutex* closeMtx; + ErlNifPid closerPid; + ErlNifMonitor closerMon; + ERL_NIF_TERM closeRef; + } SocketDescriptor; @@ -609,6 +630,9 @@ static ERL_NIF_TERM nif_getsockopt(ErlNifEnv* env, static ERL_NIF_TERM nif_finalize_connection(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); +static ERL_NIF_TERM nif_finalize_close(ErlNifEnv* env, + int argc, + const ERL_NIF_TERM argv[]); static ERL_NIF_TERM nif_cancel(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); @@ -660,6 +684,8 @@ static ERL_NIF_TERM nrecvfrom(ErlNifEnv* env, ERL_NIF_TERM recvRef, uint16_t bufSz, int flags); +static ERL_NIF_TERM nclose(ErlNifEnv* env, + SocketDescriptor* descP); static ERL_NIF_TERM send_check_result(ErlNifEnv* env, SocketDescriptor* descP, @@ -682,6 +708,8 @@ static ERL_NIF_TERM recvfrom_check_result(ErlNifEnv* env, static ERL_NIF_TERM nfinalize_connection(ErlNifEnv* env, SocketDescriptor* descP); +static ERL_NIF_TERM nfinalize_close(ErlNifEnv* env, + SocketDescriptor* descP); static char* decode_laddress(ErlNifEnv* env, @@ -730,6 +758,12 @@ static void encode_address(ErlNifEnv* env, ERL_NIF_TERM* fromDomainT, ERL_NIF_TERM* fromSourceT); +static void inform_waiting_procs(ErlNifEnv* env, + SocketDescriptor* descP, + SocketRequestQueue* q, + BOOLEAN_T free, + ERL_NIF_TERM msg); + static BOOLEAN_T verify_is_connected(SocketDescriptor* descP, int* err); static SocketDescriptor* alloc_descriptor(SOCKET sock, HANDLE event); @@ -773,6 +807,19 @@ static ERL_NIF_TERM make_error(ErlNifEnv* env, ERL_NIF_TERM reason); static ERL_NIF_TERM make_error1(ErlNifEnv* env, char* reason); static ERL_NIF_TERM make_error2(ErlNifEnv* env, int err); +static char* send_msg_error_closed(ErlNifEnv* env, + ErlNifPid* pid); +static char* send_msg_error(ErlNifEnv* env, + ERL_NIF_TERM reason, + ErlNifPid* pid); +static char* send_msg(ErlNifEnv* env, + ERL_NIF_TERM msg, + ErlNifPid* pid); + +static void xabort(const char* expr, + const char* func, + const char* file, + int line); static BOOLEAN_T extract_item_on_load(ErlNifEnv* env, ERL_NIF_TERM map, @@ -810,10 +857,14 @@ static const struct in6_addr in6addr_loopback = /* *** String constants *** */ +static char str_close[] = "close"; static char str_closed[] = "closed"; +static char str_closing[] = "closing"; static char str_error[] = "error"; static char str_false[] = "false"; static char str_ok[] = "ok"; +static char str_select[] = "select"; +static char str_timeout[] = "timeout"; static char str_true[] = "true"; static char str_undefined[] = "undefined"; @@ -822,19 +873,25 @@ static char str_eagain[] = "eagain"; static char str_eafnosupport[] = "eafnosupport"; static char str_einval[] = "einval"; static char str_eisconn[] = "eisconn"; +static char str_enotclosing[] = "enotclosing"; static char str_enotconn[] = "enotconn"; static char str_exalloc[] = "exalloc"; static char str_exbadstate[] = "exbadstate"; static char str_exbusy[] = "exbusy"; static char str_exmon[] = "exmonitor"; // failed monitor static char str_exself[] = "exself"; // failed self +static char str_exsend[] = "exsend"; // failed send /* *** Atoms *** */ +static ERL_NIF_TERM atom_close; static ERL_NIF_TERM atom_closed; +static ERL_NIF_TERM atom_closing; static ERL_NIF_TERM atom_error; static ERL_NIF_TERM atom_false; static ERL_NIF_TERM atom_ok; +static ERL_NIF_TERM atom_select; +static ERL_NIF_TERM atom_timeout; static ERL_NIF_TERM atom_true; static ERL_NIF_TERM atom_undefined; @@ -842,12 +899,14 @@ static ERL_NIF_TERM atom_eagain; static ERL_NIF_TERM atom_eafnosupport; static ERL_NIF_TERM atom_einval; static ERL_NIF_TERM atom_eisconn; +static ERL_NIF_TERM atom_enotclosing; static ERL_NIF_TERM atom_enotconn; static ERL_NIF_TERM atom_exalloc; static ERL_NIF_TERM atom_exbadstate; static ERL_NIF_TERM atom_exbusy; static ERL_NIF_TERM atom_exmon; static ERL_NIF_TERM atom_exself; +static ERL_NIF_TERM atom_exsend; /* *** Sockets *** */ @@ -1059,9 +1118,9 @@ ERL_NIF_TERM nopen(ErlNifEnv* env, if (enif_self(env, &descP->ctrlPid) == NULL) return make_error(env, atom_exself); - if (enif_monitor_process(env, descP, - &descP->ctrlPid, - &descP->ctrlMon) > 0) + if (MONP(env, descP, + &descP->ctrlPid, + &descP->ctrlMon) > 0) return make_error(env, atom_exmon); @@ -1794,9 +1853,9 @@ ERL_NIF_TERM naccept_listening(ErlNifEnv* env, /* *** Try again later *** */ descP->currentAcceptor.pid = caller; - if (enif_monitor_process(env, descP, - &descP->currentAcceptor.pid, - &descP->currentAcceptor.mon) > 0) + if (MONP(env, descP, + &descP->currentAcceptor.pid, + &descP->currentAcceptor.mon) > 0) return make_error(env, atom_exmon); descP->currentAcceptor.ref = ref; @@ -1865,9 +1924,9 @@ ERL_NIF_TERM naccept_listening(ErlNifEnv* env, enif_release_resource(accDescP); // We should really store a reference ... accDescP->ctrlPid = caller; - if (enif_monitor_process(env, accDescP, - &accDescP->ctrlPid, - &accDescP->ctrlMon) > 0) { + if (MONP(env, accDescP, + &accDescP->ctrlPid, + &accDescP->ctrlMon) > 0) { sock_close(accSock); return make_error(env, atom_exmon); } @@ -1966,9 +2025,9 @@ ERL_NIF_TERM naccept_accepting(ErlNifEnv* env, enif_release_resource(accDescP); // We should really store a reference ... accDescP->ctrlPid = caller; - if (enif_monitor_process(env, accDescP, - &accDescP->ctrlPid, - &accDescP->ctrlMon) > 0) { + if (MONP(env, accDescP, + &accDescP->ctrlPid, + &accDescP->ctrlMon) > 0) { sock_close(accSock); return make_error(env, atom_exmon); } @@ -2480,6 +2539,189 @@ ERL_NIF_TERM nrecvfrom(ErlNifEnv* env, +/* ---------------------------------------------------------------------- + * nif_close + * + * Description: + * Close a (socket) file descriptor. + * + * Arguments: + * Socket (ref) - Points to the socket descriptor. + */ + +static +ERL_NIF_TERM nif_close(ErlNifEnv* env, + int argc, + const ERL_NIF_TERM argv[]) +{ + SocketDescriptor* descP; + + if ((argc != 1) || + !enif_get_resource(env, argv[0], sockets, (void**) &descP)) { + return enif_make_badarg(env); + } + + return nclose(env, descP); +} + + +static +ERL_NIF_TERM nclose(ErlNifEnv* env, + SocketDescriptor* descP) +{ + ERL_NIF_TERM reply, reason; + BOOLEAN_T doClose; + int selectRes; + + MLOCK(descP->closeMtx); + + if (descP->state == SOCKET_STATE_CLOSED) { + reason = atom_closed; + doClose = FALSE; + } else if (descP->state == SOCKET_STATE_CLOSING) { + reason = atom_closing; + doClose = FALSE; + } else { + + /* Store the PID of the caller, + * since we need to inform it when we + * (that is, the stop callback function) + * completes. + */ + + if (enif_self(env, &descP->closerPid) == NULL) { + MUNLOCK(descP->closeMtx); + return make_error(env, atom_exself); + } + + /* Monitor the caller, since we should complete this operation even if + * the caller dies (for whatever reason). + */ + + if (MONP(env, descP, + &descP->closerPid, + &descP->closerMon) > 0) { + MUNLOCK(descP->closeMtx); + return make_error(env, atom_exmon); + } + + descP->state = SOCKET_STATE_CLOSING; + doClose = TRUE; + } + + MUNLOCK(descP->closeMtx); + + if (doClose) { + descP->closeRef = MKREF(env); + selectRes = enif_select(env, descP->sock, (ERL_NIF_SELECT_STOP), + descP, NULL, descP->closeRef); + if (selectRes & ERL_NIF_SELECT_STOP_CALLED) { + /* Prep done - inform the caller it can finalize (close) directly */ + reply = atom_ok; + } else if (selectRes & ERL_NIF_SELECT_STOP_SCHEDULED) { + /* The stop callback function has been *scheduled* which means that we + * have to wait for it to complete. */ + reply = make_ok2(env, descP->closeRef); + } else { + /* + * + * WE SHOULD REALLY HAVE A WAY TO CLOBBER THE SOCKET, + * SO WE DON'T LET STUFF LEAK. + * NOW, BECAUSE WE FAILED TO SELECT, WE CANNOT FINISH + * THE CLOSE, WHAT TO DO? ABORT? + * + * + */ + reason = MKT2(env, atom_select, MKI(env, selectRes)); + reply = make_error(env, reason); + } + } else { + reply = make_error(env, reason); + } + + return reply; +} + + + +/* ---------------------------------------------------------------------- + * nif_finalize_close + * + * Description: + * Perform the actual socket close! + * Note that this function is executed in a dirfty scheduler. + * + * Arguments: + * Socket (ref) - Points to the socket descriptor. + */ +static +ERL_NIF_TERM nif_finalize_close(ErlNifEnv* env, + int argc, + const ERL_NIF_TERM argv[]) +{ + SocketDescriptor* descP; + + /* Extract arguments and perform preliminary validation */ + + if ((argc != 1) || + !enif_get_resource(env, argv[0], sockets, (void**) &descP)) { + return enif_make_badarg(env); + } + + return nfinalize_close(env, descP); + +} + + +/* *** nfinalize_close *** + * Perform the final step in the socket close. + */ +static +ERL_NIF_TERM nfinalize_close(ErlNifEnv* env, + SocketDescriptor* descP) +{ + ERL_NIF_TERM reply; + + if (descP->state == SOCKET_STATE_CLOSED) + return atom_ok; + + if (descP->state != SOCKET_STATE_CLOSING) + return make_error(env, atom_enotclosing); + + /* This nif is executed in a dirty scheduler just so that + * it can "hang" (whith minumum effect on the VM) while the + * kernel writes our buffers. IF we have set the linger option + * for this ({true, integer() > 0}). For this to work we must + * be blocking... + */ + SET_BLOCKING(descP->sock); + + if (sock_close(descP->sock) != 0) { + int save_errno = sock_errno(); + + if (save_errno != ERRNO_BLOCK) { + /* Not all data in the buffers where sent, + * make sure the caller gets this. + */ + reply = make_error(env, atom_timeout); + } else { + reply = make_error2(env, save_errno); + } + } else { + reply = atom_ok; + } + sock_close_event(descP->event); + + descP->sock = INVALID_SOCKET; + descP->event = INVALID_EVENT; + + descP->state = SOCKET_STATE_CLOSED; + + return reply; +} + + + /* ---------------------------------------------------------------------- * U t i l i t y F u n c t i o n s * ---------------------------------------------------------------------- @@ -2610,10 +2852,16 @@ ERL_NIF_TERM recv_check_result(ErlNifEnv* env, * PROCESS, WE NEED TO INFORM IT!!! * * ALL WAITING PROCESSES MUST ALSO GET THE ERROR!! + * HANDLED BY THE STOP (CALLBACK) FUNCTION? + * + * WE DON'T NEED TO WAIT FOR OUTPUT TO BE WRITTEN, + * JUST ABORT THE SOCKET!!! * * */ + descP->state = SOCKET_STATE_CLOSING; + SELECT(env, descP->sock, (ERL_NIF_SELECT_STOP), @@ -3150,6 +3398,9 @@ SocketDescriptor* alloc_descriptor(SOCKET sock, HANDLE event) descP->acceptorsQ.first = NULL; descP->acceptorsQ.last = NULL; + sprintf(buf, "socket[close,%d]", sock); + descP->closeMtx = MCREATE(buf); + descP->rBufSz = SOCKET_RECV_BUFFER_SIZE_DEFAULT; descP->iow = FALSE; descP->dbg = SOCKET_DEBUG_DEFAULT; @@ -3491,6 +3742,65 @@ ERL_NIF_TERM make_error2(ErlNifEnv* env, int err) } +/* Send an error closed message to the specified process: + * + * This message is for processes that are waiting in the + * erlang API functions for a select message. + */ +static +char* send_msg_error_closed(ErlNifEnv* env, + ErlNifPid* pid) +{ + return send_msg_error(env, atom_closed, pid); +} + + +/* Send an error message to the specified process: + * A message in the form: + * + * {error, Reason} + * + * This message is for processes that are waiting in the + * erlang API functions for a select message. + */ +static +char* send_msg_error(ErlNifEnv* env, + ERL_NIF_TERM reason, + ErlNifPid* pid) +{ + ERL_NIF_TERM msg = enif_make_tuple2(env, atom_error, reason); + + return send_msg(env, msg, pid); +} + + +/* Send a message to the specified process. + */ +static +char* send_msg(ErlNifEnv* env, + ERL_NIF_TERM msg, + ErlNifPid* pid) +{ + if (!enif_send(env, pid, NULL, msg)) + return str_exsend; + else + return NULL; +} + + +static +void xabort(const char* expr, + const char* func, + const char* file, + int line) +{ + fflush(stdout); + fprintf(stderr, "%s:%d:%s() Assertion failed: %s\n", + file, line, func, expr); + fflush(stderr); + abort(); +} + /* ---------------------------------------------------------------------- * C o u n t e r F u n c t i o n s @@ -3529,19 +3839,153 @@ BOOLEAN_T cnt_inc(uint32_t* cnt, uint32_t inc) static void socket_dtor(ErlNifEnv* env, void* obj) { + SocketDescriptor* descP = (SocketDescriptor*) obj; + + MDESTROY(descP->writeMtx); + MDESTROY(descP->readMtx); + MDESTROY(descP->accMtx); + MDESTROY(descP->closeMtx); } /* ========================================================================= * socket_stop - Callback function for resource stop * + * When the socket is stopped, we need to inform: + * + * * the controlling process + * * the current writer and any waiting writers + * * the current reader and any waiting readers + * * the current acceptor and any waiting acceptor + * + * Also, make sure no process gets the message twice + * (in case it is, for instance, both controlling process + * and a writer). + * + * + * We do not handle linger-issues yet! So anything in the out + * buffers will be left for the OS to solve... + * Do we need a special "close"-thread? Dirty scheduler? + * */ static void socket_stop(ErlNifEnv* env, void* obj, int fd, int is_direct_call) { + SocketDescriptor* descP = (SocketDescriptor*) obj; + ERL_NIF_TERM errClosed = MKT2(env, atom_error, atom_closed); + + MLOCK(descP->writeMtx); + MLOCK(descP->readMtx); + MLOCK(descP->accMtx); + MLOCK(descP->closeMtx); + + + descP->state = SOCKET_STATE_CLOSING; + descP->isReadable = FALSE; + descP->isWritable = FALSE; + + + /* We should check that we actually have a monitor. + * This *should* be done with a "NULL" monitor value, + * which there currently is none... + */ + DEMONP(env, descP, &descP->ctrlMon); + + if (descP->currentWriterP != NULL) { + /* We have a (current) writer and *may* therefor also have + * writers waiting. + */ + + SASSERT( (NULL == send_msg_error_closed(env, &descP->currentWriter.pid)) ); + + /* And also deal with the waiting writers (in the same way) */ + inform_waiting_procs(env, descP, &descP->writersQ, TRUE, errClosed); + } + + if (descP->currentReaderP != NULL) { + + /* We have a (current) reader and *may* therefor also have + * readers waiting. + */ + + SASSERT( (NULL == send_msg_error_closed(env, &descP->currentReader.pid)) ); + + /* And also deal with the waiting readers (in the same way) */ + inform_waiting_procs(env, descP, &descP->readersQ, TRUE, errClosed); + } + + if (descP->currentAcceptorP != NULL) { + /* We have a (current) acceptor and *may* therefor also have + * acceptors waiting. + */ + + SASSERT( (NULL == send_msg_error_closed(env, &descP->currentAcceptor.pid)) ); + + /* And also deal with the waiting acceptors (in the same way) */ + inform_waiting_procs(env, descP, &descP->acceptorsQ, TRUE, errClosed); + } + + + if (descP->sock != INVALID_SOCKET) { + + /* +++ send close message to the waiting process +++ + * + * {close, CloseRef} + * + */ + + send_msg(env, MKT2(env, atom_close, descP->closeRef), &descP->closerPid); + + DEMONP(env, descP, &descP->closerMon); + } + + + MUNLOCK(descP->closeMtx); + MUNLOCK(descP->accMtx); + MUNLOCK(descP->readMtx); + MUNLOCK(descP->writeMtx); + } +/* This function traverse the queue and sends the specified + * message to each member, and if the 'free' argument is TRUE, + * the queue will be emptied. + */ +static +void inform_waiting_procs(ErlNifEnv* env, + SocketDescriptor* descP, + SocketRequestQueue* q, + BOOLEAN_T free, + ERL_NIF_TERM msg) +{ + SocketRequestQueueElement* currentP = q->first; + SocketRequestQueueElement* nextP; + + while (currentP != NULL) { + + /* + * Should we inform anyone if we fail to demonitor? + * NOT SURE WHAT THAT WOULD REPRESENT AND IT IS NOT + * IMPORTANT IN *THIS* CASE, BUT ITS A FUNDAMENTAL OP... + * + */ + + SASSERT( (NULL == send_msg(env, msg, ¤tP->data.pid)) ); + DEMONP(env, descP, ¤tP->data.mon); + nextP = currentP->nextP; + if (free) FREE(currentP); + currentP = nextP; + } + + if (free) { + q->first = NULL; + q->last = NULL; + } +} + + + /* ========================================================================= * socket_down - Callback function for resource down (monitored processes) * @@ -3564,31 +4008,32 @@ void socket_down(ErlNifEnv* env, static ErlNifFunc socket_funcs[] = { - // Some utility functions - {"nif_is_loaded", 0, nif_is_loaded}, - {"nif_info", 0, nif_info}, - // {"nif_debug", 1, nif_debug_}, - - // The proper "socket" interface - {"nif_open", 4, nif_open}, - {"nif_bind", 3, nif_bind}, - {"nif_connect", 3, nif_connect}, - {"nif_listen", 2, nif_listen}, - {"nif_accept", 2, nif_accept}, - {"nif_send", 4, nif_send}, - {"nif_sendto", 6, nif_sendto}, - {"nif_recv", 4, nif_recv}, - {"nif_recvfrom", 2, nif_recvfrom}, - {"nif_close", 1, nif_close}, - {"nif_setsockopt", 3, nif_setsockopt}, - {"nif_getsockopt", 2, nif_getsockopt}, - - /* "Extra" functions to "complete" the socket interface. - * For instance, the function nif_finalize_connection - * is called after the connect *select* has "completed". - */ - {"nif_finalize_connection", 1, nif_finalize_connection}, - {"nif_cancel", 2, nif_cancel}, + // Some utility functions + {"nif_is_loaded", 0, nif_is_loaded, 0}, + {"nif_info", 0, nif_info, 0}, + // {"nif_debug", 1, nif_debug_, 0}, + + // The proper "socket" interface + {"nif_open", 4, nif_open, 0}, + {"nif_bind", 3, nif_bind, 0}, + {"nif_connect", 3, nif_connect, 0}, + {"nif_listen", 2, nif_listen, 0}, + {"nif_accept", 2, nif_accept, 0}, + {"nif_send", 4, nif_send, 0}, + {"nif_sendto", 6, nif_sendto, 0}, + {"nif_recv", 4, nif_recv, 0}, + {"nif_recvfrom", 2, nif_recvfrom, 0}, + {"nif_close", 1, nif_close, 0}, + {"nif_setsockopt", 3, nif_setsockopt, 0}, + {"nif_getsockopt", 2, nif_getsockopt, 0}, + + /* "Extra" functions to "complete" the socket interface. + * For instance, the function nif_finalize_connection + * is called after the connect *select* has "completed". + */ + {"nif_finalize_connection", 1, nif_finalize_connection, 0}, + {"nif_cancel", 2, nif_cancel, 0}, + {"nif_finalize_close", 1, nif_finalize_close, ERL_NIF_DIRTY_JOB_IO_BOUND} }; @@ -3656,7 +4101,9 @@ int on_load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info) // atom_active_once = MKA(env, str_active_once); // atom_binary = MKA(env, str_binary); // atom_buildDate = MKA(env, str_buildDate); + atom_close = MKA(env, str_close); atom_closed = MKA(env, str_closed); + atom_closing = MKA(env, str_closing); atom_error = MKA(env, str_error); atom_false = MKA(env, str_false); // atom_list = MKA(env, str_list); @@ -3665,7 +4112,9 @@ int on_load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info) // atom_once = MKA(env, str_once); // atom_passive = MKA(env, str_passive); // atom_receiver = MKA(env, str_receiver); + atom_select = MKA(env, str_select); // atom_tcp_closed = MKA(env, str_tcp_closed); + atom_timeout = MKA(env, str_timeout); atom_true = MKA(env, str_true); atom_undefined = MKA(env, str_undefined); // atom_version = MKA(env, str_version); @@ -3675,6 +4124,7 @@ int on_load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info) atom_eafnosupport = MKA(env, str_eafnosupport); atom_einval = MKA(env, str_einval); atom_eisconn = MKA(env, str_eisconn); + atom_enotclosing = MKA(env, str_enotclosing); atom_enotconn = MKA(env, str_enotconn); atom_exalloc = MKA(env, str_exalloc); atom_exbadstate = MKA(env, str_exbadstate); @@ -3682,7 +4132,7 @@ int on_load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info) // atom_exnotopen = MKA(env, str_exnotopen); atom_exmon = MKA(env, str_exmon); atom_exself = MKA(env, str_exself); - // atom_exsend = MKA(env, str_exsend); + atom_exsend = MKA(env, str_exsend); // For storing "global" things... // socketData.env = enif_alloc_env(); // We should really check diff --git a/erts/preloaded/src/socket.erl b/erts/preloaded/src/socket.erl index bae561cd51..044fe73906 100644 --- a/erts/preloaded/src/socket.erl +++ b/erts/preloaded/src/socket.erl @@ -45,10 +45,8 @@ close/1, - setopt/3, - getopt/2, - %% ????? - formated_timestamp/0 + setopt/4, + getopt/3 ]). -export_type([ @@ -105,6 +103,14 @@ -type port_number() :: 0..65535. +%% otp - The option is internal to our (OTP) imeplementation. +%% socket - The socket layer (SOL_SOCKET). +%% ip - The ip layer (SOL_IP). +%% tcp - The TCP (Transport Control Protocol) layer (IPPROTO_TCP). +%% udp - The UDP (User Datagram Protocol) layer (IPPROTO_UDP). +%% Int - Raw level, sent down and used "as is". +-type option_level() :: otp | socket | ip | tcp | udp | non_neg_integer(). + -type socket_info() :: map(). -record(socket, {info :: socket_info(), ref :: reference()}). @@ -201,6 +207,14 @@ -define(SOCKET_RECV_FLAGS_DEFAULT, []). -define(SOCKET_RECV_TIMEOUT_DEFAULT, infinity). +-define(SOCKET_SETOPT_LEVEL_ENCODED, 0). +-define(SOCKET_SETOPT_LEVEL_RAW, 1). +-define(SOCKET_SETOPT_LEVEL_OTP, 0). +-define(SOCKET_SETOPT_LEVEL_SOCKET, 1). +-define(SOCKET_SETOPT_LEVEL_IP, 2). +-define(SOCKET_SETOPT_LEVEL_TCP, 3). +-define(SOCKET_SETOPT_LEVEL_UDP, 4). + -define(SOCKET_SETOPT_KEY_DEBUG, 0). -define(SOCKET_GETOPT_KEY_DEBUG, ?SOCKET_SETOPT_KEY_DEBUG). @@ -935,16 +949,37 @@ do_recvfrom(SockRef, BufSz, EFlags, Timeout) -> %% =========================================================================== %% %% close - close a file descriptor +%% -spec close(Socket) -> ok | {error, Reason} when Socket :: socket(), Reason :: term(). -close({socket, _, SockRef}) -> - nif_close(SockRef). +close(#socket{ref = SockRef}) -> + case nif_close(SockRef) of + ok -> + nif_finalize_close(SockRef); + {ok, CloseRef} -> + %% We must wait + receive + {close, CloseRef} -> + %% + %% + %% WHAT HAPPENS IF THIS PROCESS IS KILLED + %% BEFORE WE CAN EXECUTE THE FINAL CLOSE??? + %% + %% + nif_finalize_close(SockRef) + end; + {error, _} = ERROR -> + ERROR + end. + +%% =========================================================================== +%% %% setopt - manipulate individual properties of a socket %% %% What properties are valid depend on what kind of socket it is @@ -952,22 +987,30 @@ close({socket, _, SockRef}) -> %% If its an "invalid" option (or value), we should not crash but return some %% useful error... %% +%% +%% +%% WE NEED TOP MAKE SURE THAT THE USER DOES NOT MAKE US BLOCKING +%% AS MUCH OF THE CODE EXPECTS TO BE NON-BLOCKING!! +%% +%% --spec setopt(Socket, Key, Value) -> ok | {error, Reason} when +-spec setopt(Socket, Level, Key, Value) -> ok | {error, Reason} when Socket :: socket(), + Level :: option_level(), Key :: setopt_key(), Value :: term(), Reason :: term(). -setopt({socket, Info, SockRef}, Key, Value) -> +setopt(#socket{info = Info, ref = SockRef}, Level, Key, Value) -> try begin Domain = maps:get(domain, Info), Type = maps:get(type, Info), Protocol = maps:get(protocol, Info), - EKey = enc_setopt_key(Key, Domain, Type, Protocol), - EVal = enc_setopt_value(Key, Value, Domain, Type, Protocol), - nif_setopt(SockRef, EKey, EVal) + ELevel = enc_setopt_level(Level), + EKey = enc_setopt_key(Level, Key, Domain, Type, Protocol), + EVal = enc_setopt_value(Level, Key, Value, Domain, Type, Protocol), + nif_setopt(SockRef, ELevel, EKey, EVal) end catch throw:T -> @@ -985,24 +1028,26 @@ setopt({socket, Info, SockRef}, Key, Value) -> %% useful error... %% --spec getopt(Socket, Key) -> {ok, Value} | {error, Reason} when +-spec getopt(Socket, Level, Key) -> {ok, Value} | {error, Reason} when Socket :: socket(), + Level :: option_level(), Key :: getopt_key(), Value :: term(), Reason :: term(). -getopt({socket, Info, SockRef}, Key) -> +getopt(#socket{info = Info, ref = SockRef}, Level, Key) -> try begin Domain = maps:get(domain, Info), Type = maps:get(type, Info), Protocol = maps:get(protocol, Info), - EKey = enc_getopt_key(Key, Domain, Type, Protocol), + ELevel = enc_getopt_level(Level), + EKey = enc_getopt_key(Level, Key, Domain, Type, Protocol), %% We may need to decode the value (for the same reason %% we needed to encode the value for setopt). - case nif_getopt(SockRef, EKey) of + case nif_getopt(SockRef, ELevel, EKey) of {ok, EVal} -> - Val = dec_getopt_value(Key, EVal, Domain, Type, Protocol), + Val = dec_getopt_value(Level, Key, EVal, Domain, Type, Protocol), {ok, Val}; {error, _} = ERROR -> ERROR @@ -1090,21 +1135,52 @@ enc_flags(Flags, EFlags) -> end, lists:foldl(F, 0, Flags). +enc_setopt_level(otp) -> + {?SOCKET_SETOPT_LEVEL_ENCODED, ?SOCKET_SETOPT_LEVEL_OTP}; +enc_setopt_level(socket) -> + {?SOCKET_SETOPT_LEVEL_ENCODED, ?SOCKET_SETOPT_LEVEL_SOCKET}; +enc_setopt_level(ip) -> + {?SOCKET_SETOPT_LEVEL_ENCODED, ?SOCKET_SETOPT_LEVEL_IP}; +enc_setopt_level(tcp) -> + {?SOCKET_SETOPT_LEVEL_ENCODED, ?SOCKET_SETOPT_LEVEL_TCP}; +enc_setopt_level(udp) -> + {?SOCKET_SETOPT_LEVEL_ENCODED, ?SOCKET_SETOPT_LEVEL_UDP}; +%% Any option that is of an raw level must be provided as a binary +%% already fully encoded! +enc_setopt_level(L) when is_integer(L) -> + {?SOCKET_SETOPT_LEVEL_RAW, L}. + + %% We should ...really... do something with the domain, type and protocol args... -enc_setopt_key(debug, _, _, _) -> +%% Also, any option which has an integer level (raw) must also be provided +%% in a raw mode, that is, as an integer. +enc_setopt_key(L, K, _, _, _) when is_integer(L) andalso is_integer(K) -> + K; +enc_setopt_key(otp, debug, _, _, _) -> ?SOCKET_SETOPT_KEY_DEBUG. %% We should ...really... do something with the domain, type and protocol args... -enc_setopt_value(debug, V, _, _, _) when is_boolean(V) -> +enc_setopt_value(otp, debug, V, _, _, _) when is_boolean(V) -> + V; +enc_setopt_value(socket, linger, abort, D, T, P) -> + enc_setopt_value(socket, linger, {true, 0}, D, T, P); +enc_setopt_value(socket, linger, {OnOff, Secs} = V, _D, _T, _P) + when is_boolean(OnOff) andalso is_integer(Secs) andalso (Secs >= 0) -> + V; +enc_setopt_value(L, _, V, _, _, _) when is_integer(L) andalso is_binary(V) -> V. + +enc_getopt_level(Level) -> + enc_setopt_level(Level). + %% We should ...really... do something with the domain, type and protocol args... -enc_getopt_key(debug, _, _, _) -> +enc_getopt_key(otp, debug, _, _, _) -> ?SOCKET_GETOPT_KEY_DEBUG. %% We should ...really... do something with the domain, type and protocol args... -dec_getopt_value(debug, B, _, _, _) when is_boolean(B) -> +dec_getopt_value(otp, debug, B, _, _, _) when is_boolean(B) -> B. @@ -1226,8 +1302,11 @@ nif_cancel(_SRef, _Op, _Ref) -> nif_close(_SRef) -> erlang:error(badarg). -nif_setopt(_Ref, _Key, _Val) -> +nif_finalize_close(_SRef) -> + erlang:error(badarg). + +nif_setopt(_Ref, _Lev, _Key, _Val) -> erlang:error(badarg). -nif_getopt(_Ref, _Key) -> +nif_getopt(_Ref, _Lev, _Key) -> erlang:error(badarg). -- cgit v1.2.3 From 82bfbdd7919e1aee360b76bdbaca17d2cf00ee73 Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Wed, 25 Apr 2018 14:54:03 +0200 Subject: [socket-nif] More close-related work There are still some questions regarding what hapopens when writing / reading from an (remote) closed socket (I talking about "properly" closed sockets). --- erts/emulator/nifs/common/socket_nif.c | 122 ++++++++++++++++++++++++++------- erts/preloaded/src/socket.erl | 62 +++++++++++++---- 2 files changed, 146 insertions(+), 38 deletions(-) diff --git a/erts/emulator/nifs/common/socket_nif.c b/erts/emulator/nifs/common/socket_nif.c index d55de9ff4e..dadea3b6fb 100644 --- a/erts/emulator/nifs/common/socket_nif.c +++ b/erts/emulator/nifs/common/socket_nif.c @@ -554,6 +554,7 @@ typedef struct { ErlNifPid closerPid; ErlNifMonitor closerMon; ERL_NIF_TERM closeRef; + BOOLEAN_T closeLocal; } SocketDescriptor; @@ -762,7 +763,7 @@ static void inform_waiting_procs(ErlNifEnv* env, SocketDescriptor* descP, SocketRequestQueue* q, BOOLEAN_T free, - ERL_NIF_TERM msg); + ERL_NIF_TERM reason); static BOOLEAN_T verify_is_connected(SocketDescriptor* descP, int* err); @@ -812,6 +813,10 @@ static char* send_msg_error_closed(ErlNifEnv* env, static char* send_msg_error(ErlNifEnv* env, ERL_NIF_TERM reason, ErlNifPid* pid); +static char* send_msg_nif_abort(ErlNifEnv* env, + ERL_NIF_TERM ref, + ERL_NIF_TERM reason, + ErlNifPid* pid); static char* send_msg(ErlNifEnv* env, ERL_NIF_TERM msg, ErlNifPid* pid); @@ -862,6 +867,7 @@ static char str_closed[] = "closed"; static char str_closing[] = "closing"; static char str_error[] = "error"; static char str_false[] = "false"; +static char str_nif_abort[] = "nif_abort"; static char str_ok[] = "ok"; static char str_select[] = "select"; static char str_timeout[] = "timeout"; @@ -889,6 +895,7 @@ static ERL_NIF_TERM atom_closed; static ERL_NIF_TERM atom_closing; static ERL_NIF_TERM atom_error; static ERL_NIF_TERM atom_false; +static ERL_NIF_TERM atom_nif_abort; static ERL_NIF_TERM atom_ok; static ERL_NIF_TERM atom_select; static ERL_NIF_TERM atom_timeout; @@ -2605,8 +2612,9 @@ ERL_NIF_TERM nclose(ErlNifEnv* env, return make_error(env, atom_exmon); } - descP->state = SOCKET_STATE_CLOSING; - doClose = TRUE; + descP->closeLocal = TRUE; + descP->state = SOCKET_STATE_CLOSING; + doClose = TRUE; } MUNLOCK(descP->closeMtx); @@ -2854,13 +2862,15 @@ ERL_NIF_TERM recv_check_result(ErlNifEnv* env, * ALL WAITING PROCESSES MUST ALSO GET THE ERROR!! * HANDLED BY THE STOP (CALLBACK) FUNCTION? * - * WE DON'T NEED TO WAIT FOR OUTPUT TO BE WRITTEN, - * JUST ABORT THE SOCKET!!! + * SINCE THIS IS A REMOTE CLOSE, WE DON'T NEED TO WAIT + * FOR OUTPUT TO BE WRITTEN (NO ONE WILL READ), JUST + * ABORT THE SOCKET REGARDLESS OF LINGER??? * * */ - descP->state = SOCKET_STATE_CLOSING; + descP->closeLocal = FALSE; + descP->state = SOCKET_STATE_CLOSING; SELECT(env, descP->sock, @@ -2944,6 +2954,9 @@ ERL_NIF_TERM recvfrom_check_result(ErlNifEnv* env, * */ + descP->closeLocal = FALSE; + descP->state = SOCKET_STATE_CLOSING; + SELECT(env, descP->sock, (ERL_NIF_SELECT_STOP), @@ -3768,9 +3781,29 @@ char* send_msg_error(ErlNifEnv* env, ERL_NIF_TERM reason, ErlNifPid* pid) { - ERL_NIF_TERM msg = enif_make_tuple2(env, atom_error, reason); + ERL_NIF_TERM msg = enif_make_tuple2(env, atom_error, reason); + + return send_msg(env, msg, pid); +} + + +/* Send an (nif-) abort message to the specified process: + * A message in the form: + * + * {nif_abort, Ref, Reason} + * + * This message is for processes that are waiting in the + * erlang API functions for a select message. + */ +static +char* send_msg_nif_abort(ErlNifEnv* env, + ERL_NIF_TERM ref, + ERL_NIF_TERM reason, + ErlNifPid* pid) +{ + ERL_NIF_TERM msg = MKT3(env, atom_nif_abort, ref, reason); - return send_msg(env, msg, pid); + return send_msg(env, msg, pid); } @@ -3871,8 +3904,7 @@ void socket_dtor(ErlNifEnv* env, void* obj) static void socket_stop(ErlNifEnv* env, void* obj, int fd, int is_direct_call) { - SocketDescriptor* descP = (SocketDescriptor*) obj; - ERL_NIF_TERM errClosed = MKT2(env, atom_error, atom_closed); + SocketDescriptor* descP = (SocketDescriptor*) obj; MLOCK(descP->writeMtx); MLOCK(descP->readMtx); @@ -3880,7 +3912,7 @@ void socket_stop(ErlNifEnv* env, void* obj, int fd, int is_direct_call) MLOCK(descP->closeMtx); - descP->state = SOCKET_STATE_CLOSING; + descP->state = SOCKET_STATE_CLOSING; // Just in case...??? descP->isReadable = FALSE; descP->isWritable = FALSE; @@ -3896,10 +3928,13 @@ void socket_stop(ErlNifEnv* env, void* obj, int fd, int is_direct_call) * writers waiting. */ - SASSERT( (NULL == send_msg_error_closed(env, &descP->currentWriter.pid)) ); + SASSERT( (NULL == send_msg_nif_abort(env, + descP->currentWriter.ref, + atom_closed, + &descP->currentWriter.pid)) ); /* And also deal with the waiting writers (in the same way) */ - inform_waiting_procs(env, descP, &descP->writersQ, TRUE, errClosed); + inform_waiting_procs(env, descP, &descP->writersQ, TRUE, atom_closed); } if (descP->currentReaderP != NULL) { @@ -3908,10 +3943,13 @@ void socket_stop(ErlNifEnv* env, void* obj, int fd, int is_direct_call) * readers waiting. */ - SASSERT( (NULL == send_msg_error_closed(env, &descP->currentReader.pid)) ); + SASSERT( (NULL == send_msg_nif_abort(env, + descP->currentReader.ref, + atom_closed, + &descP->currentReader.pid)) ); /* And also deal with the waiting readers (in the same way) */ - inform_waiting_procs(env, descP, &descP->readersQ, TRUE, errClosed); + inform_waiting_procs(env, descP, &descP->readersQ, TRUE, atom_closed); } if (descP->currentAcceptorP != NULL) { @@ -3919,24 +3957,50 @@ void socket_stop(ErlNifEnv* env, void* obj, int fd, int is_direct_call) * acceptors waiting. */ - SASSERT( (NULL == send_msg_error_closed(env, &descP->currentAcceptor.pid)) ); + SASSERT( (NULL == send_msg_nif_abort(env, + descP->currentAcceptor.ref, + atom_closed, + &descP->currentAcceptor.pid)) ); /* And also deal with the waiting acceptors (in the same way) */ - inform_waiting_procs(env, descP, &descP->acceptorsQ, TRUE, errClosed); + inform_waiting_procs(env, descP, &descP->acceptorsQ, TRUE, atom_closed); } if (descP->sock != INVALID_SOCKET) { - /* +++ send close message to the waiting process +++ + /* + * * - * {close, CloseRef} + * WE NEED TO CHECK IF THIS OPERATION IS TRIGGERED + * LOCALLY (VIA A CALL TO CLOSE) OR REMOTELLY + * (VIA I.E. ECONSRESET). * + * */ - send_msg(env, MKT2(env, atom_close, descP->closeRef), &descP->closerPid); + if (descP->closeLocal) { + + /* +++ send close message to the waiting process +++ + * + * {close, CloseRef} + * + */ + + send_msg(env, MKT2(env, atom_close, descP->closeRef), &descP->closerPid); + + DEMONP(env, descP, &descP->closerMon); + + } else { - DEMONP(env, descP, &descP->closerMon); + /* + * + * + * ABORT? + * + * + */ + } } @@ -3949,29 +4013,34 @@ void socket_stop(ErlNifEnv* env, void* obj, int fd, int is_direct_call) /* This function traverse the queue and sends the specified - * message to each member, and if the 'free' argument is TRUE, - * the queue will be emptied. + * nif_abort message with the specified reason to each member, + * and if the 'free' argument is TRUE, the queue will be emptied. */ static void inform_waiting_procs(ErlNifEnv* env, SocketDescriptor* descP, SocketRequestQueue* q, BOOLEAN_T free, - ERL_NIF_TERM msg) + ERL_NIF_TERM reason) { SocketRequestQueueElement* currentP = q->first; SocketRequestQueueElement* nextP; while (currentP != NULL) { - /* + /* + * * Should we inform anyone if we fail to demonitor? * NOT SURE WHAT THAT WOULD REPRESENT AND IT IS NOT * IMPORTANT IN *THIS* CASE, BUT ITS A FUNDAMENTAL OP... + * * */ - SASSERT( (NULL == send_msg(env, msg, ¤tP->data.pid)) ); + SASSERT( (NULL == send_msg_nif_abort(env, + currentP->data.ref, + reason, + ¤tP->data.pid)) ); DEMONP(env, descP, ¤tP->data.mon); nextP = currentP->nextP; if (free) FREE(currentP); @@ -4108,6 +4177,7 @@ int on_load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info) atom_false = MKA(env, str_false); // atom_list = MKA(env, str_list); // atom_mode = MKA(env, str_mode); + atom_nif_abort = MKA(env, str_nif_abort); atom_ok = MKA(env, str_ok); // atom_once = MKA(env, str_once); // atom_passive = MKA(env, str_passive); diff --git a/erts/preloaded/src/socket.erl b/erts/preloaded/src/socket.erl index 044fe73906..f1e8a8b099 100644 --- a/erts/preloaded/src/socket.erl +++ b/erts/preloaded/src/socket.erl @@ -105,11 +105,12 @@ %% otp - The option is internal to our (OTP) imeplementation. %% socket - The socket layer (SOL_SOCKET). -%% ip - The ip layer (SOL_IP). +%% ip - The IP layer (SOL_IP). +%% ipv6 - The IPv6 layer (SOL_IPV6). %% tcp - The TCP (Transport Control Protocol) layer (IPPROTO_TCP). %% udp - The UDP (User Datagram Protocol) layer (IPPROTO_UDP). %% Int - Raw level, sent down and used "as is". --type option_level() :: otp | socket | ip | tcp | udp | non_neg_integer(). +-type option_level() :: otp | socket | ip | ipv6 | tcp | udp | non_neg_integer(). -type socket_info() :: map(). -record(socket, {info :: socket_info(), @@ -212,8 +213,9 @@ -define(SOCKET_SETOPT_LEVEL_OTP, 0). -define(SOCKET_SETOPT_LEVEL_SOCKET, 1). -define(SOCKET_SETOPT_LEVEL_IP, 2). --define(SOCKET_SETOPT_LEVEL_TCP, 3). --define(SOCKET_SETOPT_LEVEL_UDP, 4). +-define(SOCKET_SETOPT_LEVEL_IPV6, 3). +-define(SOCKET_SETOPT_LEVEL_TCP, 4). +-define(SOCKET_SETOPT_LEVEL_UDP, 5). -define(SOCKET_SETOPT_KEY_DEBUG, 0). @@ -485,7 +487,11 @@ do_accept(LSockRef, SI, Timeout) -> NewTimeout = next_timeout(TS, Timeout), receive {select, LSockRef, AccRef, ready_input} -> - do_accept(LSockRef, SI, next_timeout(TS, Timeout)) + do_accept(LSockRef, SI, next_timeout(TS, Timeout)); + + {nif_abort, AccRef, Reason} -> + {error, Reason} + after NewTimeout -> nif_cancel(LSockRef, accept, AccRef), flush_select_msgs(LSockRef, AccRef), @@ -539,7 +545,11 @@ do_send(SockRef, Data, EFlags, Timeout) -> next_timeout(TS, Timeout)); {select, SockRef, SendRef, ready_output} -> do_send(SockRef, Data, EFlags, - next_timeout(TS, Timeout)) + next_timeout(TS, Timeout)); + + {nif_abort, SendRef, Reason} -> + {error, Reason} + after NewTimeout -> nif_cancel(SockRef, send, SendRef), flush_select_msgs(SockRef, SendRef), @@ -549,7 +559,11 @@ do_send(SockRef, Data, EFlags, Timeout) -> receive {select, SockRef, SendRef, ready_output} -> do_send(SockRef, Data, EFlags, - next_timeout(TS, Timeout)) + next_timeout(TS, Timeout)); + + {nif_abort, SendRef, Reason} -> + {error, Reason} + after Timeout -> nif_cancel(SockRef, send, SendRef), flush_select_msgs(SockRef, SendRef), @@ -608,7 +622,11 @@ do_sendto(SockRef, Data, EFlags, DestAddr, DestPort, Timeout) -> next_timeout(TS, Timeout)); {select, SockRef, SendRef, ready_output} -> do_sendto(SockRef, Data, EFlags, DestAddr, DestPort, - next_timeout(TS, Timeout)) + next_timeout(TS, Timeout)); + + {nif_abort, SendRef, Reason} -> + {error, Reason} + after Timeout -> nif_cancel(SockRef, sendto, SendRef), flush_select_msgs(SockRef, SendRef), @@ -789,7 +807,12 @@ do_recv(SockRef, _OldRef, Length, EFlags, Acc, Timeout) do_recv(SockRef, RecvRef, Length-size(Bin), EFlags, Bin, - next_timeout(TS, Timeout)) + next_timeout(TS, Timeout)); + + {nif_abort, RecvRef, Reason} -> + {error, Reason} + + after NewTimeout -> nif_cancel(SockRef, recv, RecvRef), flush_select_msgs(SockRef, RecvRef), @@ -804,7 +827,12 @@ do_recv(SockRef, _OldRef, Length, EFlags, Acc, Timeout) do_recv(SockRef, RecvRef, Length-size(Bin), EFlags, <>, - next_timeout(TS, Timeout)) + next_timeout(TS, Timeout)); + + {nif_abort, RecvRef, Reason} -> + {error, Reason} + + after NewTimeout -> nif_cancel(SockRef, recv, RecvRef), flush_select_msgs(SockRef, RecvRef), @@ -824,7 +852,11 @@ do_recv(SockRef, _OldRef, Length, EFlags, Acc, Timeout) do_recv(SockRef, RecvRef, Length, EFlags, Acc, - next_timeout(TS, Timeout)) + next_timeout(TS, Timeout)); + + {nif_abort, RecvRef, Reason} -> + {error, Reason} + after NewTimeout -> nif_cancel(SockRef, recv, RecvRef), flush_select_msgs(SockRef, RecvRef), @@ -913,7 +945,11 @@ do_recvfrom(SockRef, BufSz, EFlags, Timeout) -> receive {select, SockRef, RecvRef, ready_input} -> do_recvfrom(SockRef, BufSz, EFlags, - next_timeout(TS, Timeout)) + next_timeout(TS, Timeout)); + + {nif_abort, RecvRef, Reason} -> + {error, Reason} + after NewTimeout -> nif_cancel(SockRef, recvfrom, RecvRef), flush_select_msgs(SockRef, RecvRef), @@ -1141,6 +1177,8 @@ enc_setopt_level(socket) -> {?SOCKET_SETOPT_LEVEL_ENCODED, ?SOCKET_SETOPT_LEVEL_SOCKET}; enc_setopt_level(ip) -> {?SOCKET_SETOPT_LEVEL_ENCODED, ?SOCKET_SETOPT_LEVEL_IP}; +enc_setopt_level(ipv6) -> + {?SOCKET_SETOPT_LEVEL_ENCODED, ?SOCKET_SETOPT_LEVEL_IPV6}; enc_setopt_level(tcp) -> {?SOCKET_SETOPT_LEVEL_ENCODED, ?SOCKET_SETOPT_LEVEL_TCP}; enc_setopt_level(udp) -> -- cgit v1.2.3 From b69436fc5970ff8fc749a37351e9d9c5a54f445e Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Thu, 26 Apr 2018 16:16:24 +0200 Subject: [socket-nif] Completed the shutdown function --- erts/emulator/nifs/common/socket_nif.c | 254 +++++++++++++++++++++++++++++---- erts/preloaded/src/socket.erl | 49 ++++++- 2 files changed, 271 insertions(+), 32 deletions(-) diff --git a/erts/emulator/nifs/common/socket_nif.c b/erts/emulator/nifs/common/socket_nif.c index dadea3b6fb..fdae18a513 100644 --- a/erts/emulator/nifs/common/socket_nif.c +++ b/erts/emulator/nifs/common/socket_nif.c @@ -193,6 +193,10 @@ typedef unsigned int BOOLEAN_T; #define SOCKET_NIF_DEBUG_DEFAULT TRUE #define SOCKET_DEBUG_DEFAULT TRUE +/* Counters and stuff (Don't know where to sent this stuff anyway) */ +#define SOCKET_NIF_IOW_DEFAULT FALSE + + /* Used in debug printouts */ #ifdef __WIN32__ #define LLU "%I64u" @@ -328,6 +332,11 @@ typedef union { #define SOCKET_PROTOCOL_UDP 3 #define SOCKET_PROTOCOL_SCTP 4 +/* shutdown how */ +#define SOCKET_SHUTDOWN_HOW_RD 0 +#define SOCKET_SHUTDOWN_HOW_WR 1 +#define SOCKET_SHUTDOWN_HOW_RDWR 2 + /* =================================================================== * * * @@ -412,6 +421,7 @@ typedef union { #define sock_send(s,buf,len,flag) send((s),(buf),(len),(flag)) #define sock_sendto(s,buf,blen,flag,addr,alen) \ sendto((s),(buf),(blen),(flag),(addr),(alen)) +#define sock_shutdown(s, how) shutdown((s), (how)) #define SET_BLOCKING(s) ioctlsocket(s, FIONBIO, &zero_value) @@ -448,6 +458,7 @@ static unsigned long one_value = 1; #define sock_send(s,buf,len,flag) send((s), (buf), (len), (flag)) #define sock_sendto(s,buf,blen,flag,addr,alen) \ sendto((s),(buf),(blen),(flag),(addr),(alen)) +#define sock_shutdown(s, how) shutdown((s), (how)) #endif /* !__WIN32__ */ @@ -563,10 +574,24 @@ typedef struct { * these things?) */ typedef struct { - /* These are for debugging, testing and the like */ - ERL_NIF_TERM version; - ERL_NIF_TERM buildDate; - BOOLEAN_T dbg; + /* These are for debugging, testing and the like */ + ERL_NIF_TERM version; + ERL_NIF_TERM buildDate; + BOOLEAN_T dbg; + + ErlNifMutex* cntMtx; + BOOLEAN_T iow; + uint32_t numSockets; + uint32_t numTypeDGrams; + uint32_t numTypeStreams; + uint32_t numTypeSeqPkg; + uint32_t numDomainLocal; + uint32_t numDomainInet; + uint32_t numDomainInet6; + uint32_t numProtoIP; + uint32_t numProtoTCP; + uint32_t numProtoUDP; + uint32_t numProtoSCTP; } SocketData; @@ -622,6 +647,9 @@ static ERL_NIF_TERM nif_recvfrom(ErlNifEnv* env, static ERL_NIF_TERM nif_close(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); +static ERL_NIF_TERM nif_shutdown(ErlNifEnv* env, + int argc, + const ERL_NIF_TERM argv[]); static ERL_NIF_TERM nif_setsockopt(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); @@ -687,6 +715,9 @@ static ERL_NIF_TERM nrecvfrom(ErlNifEnv* env, int flags); static ERL_NIF_TERM nclose(ErlNifEnv* env, SocketDescriptor* descP); +static ERL_NIF_TERM nshutdown(ErlNifEnv* env, + SocketDescriptor* descP, + int how); static ERL_NIF_TERM send_check_result(ErlNifEnv* env, SocketDescriptor* descP, @@ -778,6 +809,7 @@ static int compare_pids(ErlNifEnv* env, static BOOLEAN_T edomain2domain(int edomain, int* domain); static BOOLEAN_T etype2type(int etype, int* type); static BOOLEAN_T eproto2proto(int eproto, int* proto); +static BOOLEAN_T ehow2how(unsigned int ehow, int* how); static BOOLEAN_T esendflags2sendflags(unsigned int esendflags, int* sendflags); static BOOLEAN_T erecvflags2recvflags(unsigned int erecvflags, int* recvflags); #ifdef HAVE_SETNS @@ -834,6 +866,9 @@ static BOOLEAN_T extract_item_on_load(ErlNifEnv* env, static BOOLEAN_T extract_debug_on_load(ErlNifEnv* env, ERL_NIF_TERM map, BOOLEAN_T def); +static BOOLEAN_T extract_iow_on_load(ErlNifEnv* env, + ERL_NIF_TERM map, + BOOLEAN_T def); static int on_load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info); @@ -950,6 +985,7 @@ static SocketData socketData; * nif_recv(Sock, RecvRef, Length, Flags) * nif_recvfrom(Sock, Flags) * nif_close(Sock) + * nif_shutdown(Sock, How) * * And some functions to manipulate and retrieve socket options: * ------------------------------------------------------------- @@ -2730,6 +2766,69 @@ ERL_NIF_TERM nfinalize_close(ErlNifEnv* env, +/* ---------------------------------------------------------------------- + * nif_shutdown + * + * Description: + * Disable sends and/or receives on a socket. + * + * Arguments: + * Socket (ref) - Points to the socket descriptor. + * How - What will be shutdown. + */ + +static +ERL_NIF_TERM nif_shutdown(ErlNifEnv* env, + int argc, + const ERL_NIF_TERM argv[]) +{ + SocketDescriptor* descP; + unsigned int ehow; + int how; + + if ((argc != 2) || + !enif_get_resource(env, argv[0], sockets, (void**) &descP) || + !GET_UINT(env, argv[1], &ehow)) { + return enif_make_badarg(env); + } + + if (!ehow2how(ehow, &how)) + return enif_make_badarg(env); + + return nshutdown(env, descP, how); +} + + + +static +ERL_NIF_TERM nshutdown(ErlNifEnv* env, + SocketDescriptor* descP, + int how) +{ + ERL_NIF_TERM reply; + + if (sock_shutdown(descP->sock, how) == 0) { + switch (how) { + case SHUT_RD: + descP->isReadable = FALSE; + break; + case SHUT_WR: + descP->isWritable = FALSE; + break; + case SHUT_RDWR: + descP->isReadable = FALSE; + descP->isWritable = FALSE; + break; + } + reply = atom_ok; + } else { + reply = make_error2(env, sock_errno()); + } + + return reply; +} + + /* ---------------------------------------------------------------------- * U t i l i t y F u n c t i o n s * ---------------------------------------------------------------------- @@ -3533,8 +3632,8 @@ BOOLEAN_T eproto2proto(int eproto, int* proto) return FALSE; } - return TRUE; -} + return TRUE; + } #ifdef HAVE_SETNS @@ -3685,6 +3784,35 @@ BOOLEAN_T erecvflags2recvflags(unsigned int erecvflags, int* recvflags) } + +/* eproto2proto - convert internal (erlang) protocol to (proper) protocol + * + * Note that only a subset is supported. + */ +static +BOOLEAN_T ehow2how(unsigned int ehow, int* how) +{ + switch (ehow) { + case SOCKET_SHUTDOWN_HOW_RD: + *how = SHUT_RD; + break; + + case SOCKET_SHUTDOWN_HOW_WR: + *how = SHUT_WR; + break; + + case SOCKET_SHUTDOWN_HOW_RDWR: + *how = SHUT_RDWR; + break; + + default: + return FALSE; + } + + return TRUE; + } + + #if defined(HAVE_SYS_UN_H) || defined(SO_BINDTODEVICE) /* strnlen doesn't exist everywhere */ static @@ -3896,9 +4024,14 @@ void socket_dtor(ErlNifEnv* env, void* obj) * and a writer). * * + * * We do not handle linger-issues yet! So anything in the out * buffers will be left for the OS to solve... * Do we need a special "close"-thread? Dirty scheduler? + * + * What happens if we are "stopped" for another reason then 'close'? + * For instance, down? + * * */ static @@ -3985,6 +4118,11 @@ void socket_stop(ErlNifEnv* env, void* obj, int fd, int is_direct_call) * * {close, CloseRef} * + * + * + * WHAT HAPPENS IF THE RECEIVER HAS DIED IN THE MEANTIME???? + * + * */ send_msg(env, MKT2(env, atom_close, descP->closeRef), &descP->closerPid); @@ -4093,6 +4231,7 @@ ErlNifFunc socket_funcs[] = {"nif_recv", 4, nif_recv, 0}, {"nif_recvfrom", 2, nif_recvfrom, 0}, {"nif_close", 1, nif_close, 0}, + {"nif_shutdown", 2, nif_shutdown, 0}, {"nif_setsockopt", 3, nif_setsockopt, 0}, {"nif_getsockopt", 2, nif_getsockopt, 0}, @@ -4121,38 +4260,74 @@ BOOLEAN_T extract_item_on_load(ErlNifEnv* env, return TRUE; } + static + BOOLEAN_T extract_debug_on_load(ErlNifEnv* env, ERL_NIF_TERM map, BOOLEAN_T def) + { + ERL_NIF_TERM dbgKey = enif_make_atom(env, "debug"); + ERL_NIF_TERM dbgVal; + unsigned int len; + char d[16]; // Just in case... + + /* Extra the value of the debug property */ + if (!extract_item_on_load(env, map, dbgKey, &dbgVal)) + return def; + + /* Verify that the value is actually an atom */ + if (!enif_is_atom(env, dbgVal)) + return def; + + /* Verify that the value is of acceptable length */ + if (!(GET_ATOM_LEN(env, dbgVal, &len) && + (len > 0) && + (len <= sizeof("false")))) + return def; + + /* And finally try to extract the value */ + if (!GET_ATOM(env, dbgVal, d, sizeof(d))) + return def; + + if (strncmp(d, "true", len) == 0) + return TRUE; + else + return FALSE; + + } + + static -BOOLEAN_T extract_debug_on_load(ErlNifEnv* env, ERL_NIF_TERM map, BOOLEAN_T def) +BOOLEAN_T extract_iow_on_load(ErlNifEnv* env, ERL_NIF_TERM map, BOOLEAN_T def) { - ERL_NIF_TERM dbgKey = enif_make_atom(env, "debug"); - ERL_NIF_TERM dbgVal; - unsigned int len; - char d[16]; // Just in case... + ERL_NIF_TERM iowKey = enif_make_atom(env, "iow"); + ERL_NIF_TERM iowVal; + unsigned int len; + char b[16]; // Just in case... - /* Extra the value of the debug property */ - if (!extract_item_on_load(env, map, dbgKey, &dbgVal)) - return def; + /* Extra the value of the debug property */ + if (!extract_item_on_load(env, map, iowKey, &iowVal)) + return def; - /* Verify that the value is actually an atom */ - if (!enif_is_atom(env, dbgVal)) - return def; + /* Verify that the value is actually an atom */ + if (!enif_is_atom(env, iowVal)) + return def; - /* Verify that the value is of acceptable length */ - if (!(GET_ATOM_LEN(env, dbgVal, &len) && - (len > 0) && - (len <= sizeof("false")))) - return def; + /* Verify that the value is of acceptable length */ + if (!(GET_ATOM_LEN(env, iowVal, &len) && + (len > 0) && + (len <= sizeof("false")))) + return def; - /* And finally try to extract the value */ - if (!GET_ATOM(env, dbgVal, d, sizeof(d))) - return def; + /* And finally try to extract the value */ + if (!GET_ATOM(env, iowVal, b, sizeof(b))) + return def; + + if (strncmp(b, "true", len) == 0) + return TRUE; + else + return FALSE; + + } - if (strncmp(d, "true", len) == 0) - return TRUE; - else - return FALSE; -} /* ======================================================================= * load_info - A map of misc info (e.g global debug) @@ -4164,7 +4339,24 @@ int on_load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info) socketData.dbg = extract_debug_on_load(env, load_info, SOCKET_NIF_DEBUG_DEFAULT); - /* Misc atoms */ + /* +++ Global Counters +++ */ + socketData.cntMtx = MCREATE("socket[gcnt]"); + socketData.iow = extract_iow_on_load(env, + load_info, + SOCKET_NIF_IOW_DEFAULT); + socketData.numSockets = 0; + socketData.numTypeDGrams = 0; + socketData.numTypeStreams = 0; + socketData.numTypeSeqPkg = 0; + socketData.numDomainLocal = 0; + socketData.numDomainInet = 0; + socketData.numDomainInet6 = 0; + socketData.numProtoIP = 0; + socketData.numProtoTCP = 0; + socketData.numProtoUDP = 0; + socketData.numProtoSCTP = 0; + + /* +++ Misc atoms +++ */ // atom_active = MKA(env, str_active); // atom_active_n = MKA(env, str_active_n); // atom_active_once = MKA(env, str_active_once); diff --git a/erts/preloaded/src/socket.erl b/erts/preloaded/src/socket.erl index f1e8a8b099..8dce86c518 100644 --- a/erts/preloaded/src/socket.erl +++ b/erts/preloaded/src/socket.erl @@ -44,6 +44,7 @@ %% readv/3, close/1, + shutdown/2, setopt/4, getopt/3 @@ -65,7 +66,9 @@ accept_flag/0, send_flags/0, - send_flag/0 + send_flag/0, + + shutdown_how/0 ]). @@ -148,6 +151,8 @@ -type setopt_key() :: foo. -type getopt_key() :: foo. +-type shutdown_how() :: read | write | read_write. + -record(msg_hdr, { %% Optional address @@ -221,6 +226,9 @@ -define(SOCKET_GETOPT_KEY_DEBUG, ?SOCKET_SETOPT_KEY_DEBUG). +-define(SOCKET_SHUTDOWN_HOW_READ, 0). +-define(SOCKET_SHUTDOWN_HOW_WRITE, 1). +-define(SOCKET_SHUTDOWN_HOW_READ_WRITE, 2). %% =========================================================================== @@ -1014,6 +1022,32 @@ close(#socket{ref = SockRef}) -> +%% =========================================================================== +%% +%% shutdown - shut down part of a full-duplex connection +%% + +-spec shutdown(Socket, How) -> ok | {error, Reason} when + Socket :: socket(), + How :: shutdown_how(), + Reason :: term(). + +shutdown(#socket{ref = SockRef}, How) -> + try + begin + EHow = enc_shutdown_how(How), + nif_shutdown(SockRef, EHow) + end + catch + throw:T -> + T; + error:Reason -> + {error, Reason} + end. + + + + %% =========================================================================== %% %% setopt - manipulate individual properties of a socket @@ -1223,6 +1257,16 @@ dec_getopt_value(otp, debug, B, _, _, _) when is_boolean(B) -> +enc_shutdown_how(read) -> + ?SOCKET_SHUTDOWN_HOW_READ; +enc_shutdown_how(write) -> + ?SOCKET_SHUTDOWN_HOW_WRITE; +enc_shutdown_how(read_write) -> + ?SOCKET_SHUTDOWN_HOW_READ_WRITE. + + + + %% =========================================================================== %% %% Misc utility functions @@ -1340,6 +1384,9 @@ nif_cancel(_SRef, _Op, _Ref) -> nif_close(_SRef) -> erlang:error(badarg). +nif_shutdown(_SRef, _How) -> + erlang:error(badarg). + nif_finalize_close(_SRef) -> erlang:error(badarg). -- cgit v1.2.3 From 8e14247bc5faf5abf67901b6ae5028f2b1897c65 Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Fri, 4 May 2018 16:00:26 +0200 Subject: [socket-nif] Preliminary setopt *Very* partial setopt implementation. --- erts/emulator/nifs/common/socket_nif.c | 1029 ++++++++++++++++++++++++++++++-- erts/preloaded/src/socket.erl | 721 ++++++++++++++++++++-- 2 files changed, 1660 insertions(+), 90 deletions(-) diff --git a/erts/emulator/nifs/common/socket_nif.c b/erts/emulator/nifs/common/socket_nif.c index fdae18a513..532ac5c211 100644 --- a/erts/emulator/nifs/common/socket_nif.c +++ b/erts/emulator/nifs/common/socket_nif.c @@ -59,6 +59,7 @@ #include #include #include +#include #ifdef HAVE_UNISTD_H #include @@ -338,6 +339,26 @@ typedef union { #define SOCKET_SHUTDOWN_HOW_RDWR 2 +#define SOCKET_OPT_LEVEL_OTP 0 +#define SOCKET_OPT_LEVEL_SOCKET 1 +#define SOCKET_OPT_LEVEL_IP 2 +#define SOCKET_OPT_LEVEL_IPV6 3 +#define SOCKET_OPT_LEVEL_TCP 4 +#define SOCKET_OPT_LEVEL_UDP 5 +#define SOCKET_OPT_LEVEL_SCTP 6 + +#define SOCKET_OPT_OTP_DEBUG 0 +#define SOCKET_OPT_OTP_IOW 1 +#define SOCKET_OPT_SOCK_KEEPALIVE 0 +#define SOCKET_OPT_SOCK_LINGER 1 +#define SOCKET_OPT_IP_RECVTOS 0 +#define SOCKET_OPT_IP_ROUTER_ALERT 1 +#define SOCKET_OPT_IP_TOS 2 +#define SOCKET_OPT_IP_TTL 3 +#define SOCKET_OPT_IPV6_HOPLIMIT 0 +#define SOCKET_OPT_TCP_MAXSEG 0 + + /* =================================================================== * * * * Various enif macros * @@ -407,7 +428,7 @@ typedef union { #define sock_connect(s, addr, len) connect((s), (addr), (len)) #define sock_create_event(s) WSACreateEvent() #define sock_errno() WSAGetLastError() -#define sock_getopt(s,t,n,v,l) getsockopt((s),(t),(n),(v),(l)) +#define sock_getopt(s,l,o,v,ln) getsockopt((s),(l),(o),(v),(ln)) #define sock_htons(x) htons((x)) #define sock_htonl(x) htonl((x)) #define sock_listen(s, b) listen((s), (b)) @@ -421,6 +442,7 @@ typedef union { #define sock_send(s,buf,len,flag) send((s),(buf),(len),(flag)) #define sock_sendto(s,buf,blen,flag,addr,alen) \ sendto((s),(buf),(blen),(flag),(addr),(alen)) +#define sock_setopt(s,l,o,v,ln) setsockopt((s),(l),(o),(v),(ln)) #define sock_shutdown(s, how) shutdown((s), (how)) @@ -458,6 +480,7 @@ static unsigned long one_value = 1; #define sock_send(s,buf,len,flag) send((s), (buf), (len), (flag)) #define sock_sendto(s,buf,blen,flag,addr,alen) \ sendto((s),(buf),(blen),(flag),(addr),(alen)) +#define sock_setopt(s,l,o,v,ln) setsockopt((s),(l),(o),(v),(ln)) #define sock_shutdown(s, how) shutdown((s), (how)) #endif /* !__WIN32__ */ @@ -570,6 +593,27 @@ typedef struct { } SocketDescriptor; +#define SOCKET_OPT_VALUE_UNDEF 0 +#define SOCKET_OPT_VALUE_BOOL 1 +#define SOCKET_OPT_VALUE_INT 2 +#define SOCKET_OPT_VALUE_LINGER 3 +#define SOCKET_OPT_VALUE_BIN 4 + +typedef struct { + unsigned int tag; + union { + BOOLEAN_T boolVal; + int intVal; + struct linger lingerVal; + ErlNifBinary binVal; + } u; + /* + void* optValP; // Points to the actual data (above) + socklen_t optValLen; // The size of the option value + */ +} SocketOptValue; + + /* Global stuff (do we really need to "collect" * these things?) */ @@ -650,12 +694,12 @@ static ERL_NIF_TERM nif_close(ErlNifEnv* env, static ERL_NIF_TERM nif_shutdown(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); -static ERL_NIF_TERM nif_setsockopt(ErlNifEnv* env, - int argc, - const ERL_NIF_TERM argv[]); -static ERL_NIF_TERM nif_getsockopt(ErlNifEnv* env, - int argc, - const ERL_NIF_TERM argv[]); +static ERL_NIF_TERM nif_setopt(ErlNifEnv* env, + int argc, + const ERL_NIF_TERM argv[]); +static ERL_NIF_TERM nif_getopt(ErlNifEnv* env, + int argc, + const ERL_NIF_TERM argv[]); static ERL_NIF_TERM nif_finalize_connection(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); @@ -718,6 +762,22 @@ static ERL_NIF_TERM nclose(ErlNifEnv* env, static ERL_NIF_TERM nshutdown(ErlNifEnv* env, SocketDescriptor* descP, int how); +static ERL_NIF_TERM nsetopt(ErlNifEnv* env, + SocketDescriptor* descP, + BOOLEAN_T isEncoded, + BOOLEAN_T isOTP, + int level, + int opt, + SocketOptValue* valP); +static ERL_NIF_TERM nsetopt_otp(ErlNifEnv* env, + SocketDescriptor* descP, + int opt, + SocketOptValue* valP); +static ERL_NIF_TERM nsetopt_gen(ErlNifEnv* env, + SocketDescriptor* descP, + int level, + int opt, + SocketOptValue* valP); static ERL_NIF_TERM send_check_result(ErlNifEnv* env, SocketDescriptor* descP, @@ -789,6 +849,15 @@ static void encode_address(ErlNifEnv* env, unsigned int fromAddrLen, ERL_NIF_TERM* fromDomainT, ERL_NIF_TERM* fromSourceT); +static BOOLEAN_T decode_sock_linger(ErlNifEnv* env, + ERL_NIF_TERM eVal, + struct linger* valP); +static BOOLEAN_T decode_ip_tos(ErlNifEnv* env, + ERL_NIF_TERM eVal, + int* val); +static BOOLEAN_T decode_bool(ErlNifEnv* env, + ERL_NIF_TERM eVal, + BOOLEAN_T* val); static void inform_waiting_procs(ErlNifEnv* env, SocketDescriptor* descP, @@ -796,6 +865,12 @@ static void inform_waiting_procs(ErlNifEnv* env, BOOLEAN_T free, ERL_NIF_TERM reason); +static int socket_setopt(int sock, + int level, + int opt, + const void* optVal, + const socklen_t optLen); + static BOOLEAN_T verify_is_connected(SocketDescriptor* descP, int* err); static SocketDescriptor* alloc_descriptor(SOCKET sock, HANDLE event); @@ -812,6 +887,62 @@ static BOOLEAN_T eproto2proto(int eproto, int* proto); static BOOLEAN_T ehow2how(unsigned int ehow, int* how); static BOOLEAN_T esendflags2sendflags(unsigned int esendflags, int* sendflags); static BOOLEAN_T erecvflags2recvflags(unsigned int erecvflags, int* recvflags); +static BOOLEAN_T elevel2level(BOOLEAN_T isEncoded, + int eLevel, + BOOLEAN_T* isOTP, + int* level); +static BOOLEAN_T eoptval2optval(ErlNifEnv* env, + BOOLEAN_T isEncoded, + BOOLEAN_T isOTP, + int level, + int eOpt, + ERL_NIF_TERM eVal, + int* opt, + SocketOptValue* val); +static BOOLEAN_T eoptval2optval_otp(ErlNifEnv* env, + int eOpt, + ERL_NIF_TERM eVal, + int* opt, + SocketOptValue* valP); +static BOOLEAN_T eoptval2optval_plain(ErlNifEnv* env, + int eOpt, + ERL_NIF_TERM eVal, + int* opt, + SocketOptValue* valP); +static BOOLEAN_T eoptval2optval_socket(ErlNifEnv* env, + int eOpt, + ERL_NIF_TERM eVal, + int* opt, + SocketOptValue* valP); +static BOOLEAN_T eoptval2optval_ip(ErlNifEnv* env, + int eOpt, + ERL_NIF_TERM eVal, + int* opt, + SocketOptValue* valP); +#if defined(SOL_IPV6) +static BOOLEAN_T eoptval2optval_ipv6(ErlNifEnv* env, + int eOpt, + ERL_NIF_TERM eVal, + int* opt, + SocketOptValue* valP); +#endif +static BOOLEAN_T eoptval2optval_tcp(ErlNifEnv* env, + int eOpt, + ERL_NIF_TERM eVal, + int* opt, + SocketOptValue* valP); +static BOOLEAN_T eoptval2optval_udp(ErlNifEnv* env, + int eOpt, + ERL_NIF_TERM eVal, + int* opt, + SocketOptValue* valP); +#ifdef HAVE_SCTP +static BOOLEAN_T eoptval2optval_sctp(ErlNifEnv* env, + int eOpt, + ERL_NIF_TERM eVal, + int* opt, + SocketOptValue* valP); +#endif #ifdef HAVE_SETNS static BOOLEAN_T emap2netns(ErlNifEnv* env, ERL_NIF_TERM map, char** netns); static BOOLEAN_T change_network_namespace(char* netns, int* cns, int* err); @@ -989,8 +1120,8 @@ static SocketData socketData; * * And some functions to manipulate and retrieve socket options: * ------------------------------------------------------------- - * nif_setsockopt/3 - * nif_getsockopt/2 + * nif_setopt/3 + * nif_getopt/2 * * And some socket admin functions: * ------------------------------------------------------------- @@ -2829,6 +2960,697 @@ ERL_NIF_TERM nshutdown(ErlNifEnv* env, } + + +/* ---------------------------------------------------------------------- + * nif_setopt + * + * Description: + * Set socket option. + * Its possible to use a "raw" mode (not encoded). That is, we do not + * interpret level, opt and value. They are passed "as is" to the + * setsockopt function call (the value arguments is assumed to be a + * binary, already encoded). + * + * Arguments: + * Socket (ref) - Points to the socket descriptor. + * Encoded - Are the "arguments" encoded or not. + * Level - Level of the socket option. + * Opt - The socket option. + * Value - Value of the socket option (type depend on the option). + */ + +static +ERL_NIF_TERM nif_setopt(ErlNifEnv* env, + int argc, + const ERL_NIF_TERM argv[]) +{ + SocketDescriptor* descP; + unsigned int eIsEncoded; + BOOLEAN_T isEncoded, isOTP; + int eLevel, level = -1; + int eOpt, opt = -1; + ERL_NIF_TERM eVal; + SocketOptValue val; + + if ((argc != 5) || + !enif_get_resource(env, argv[0], sockets, (void**) &descP) || + !GET_UINT(env, argv[1], &eIsEncoded) || + !GET_INT(env, argv[2], &eLevel) || + !GET_INT(env, argv[3], &eOpt)) { + return enif_make_badarg(env); + } + eVal = argv[4]; + + isEncoded = ((eIsEncoded == 0) ? FALSE : TRUE); + + if (!elevel2level(isEncoded, eLevel, &isOTP, &level)) + return make_error(env, atom_einval); + + if (!eoptval2optval(env, isEncoded, isOTP, level, eOpt, eVal, &opt, &val)) + return make_error(env, atom_einval); + + return nsetopt(env, descP, isEncoded, isOTP, level, opt, &val); +} + + +static +ERL_NIF_TERM nsetopt(ErlNifEnv* env, + SocketDescriptor* descP, + BOOLEAN_T isEncoded, + BOOLEAN_T isOTP, + int level, + int opt, + SocketOptValue* valP) +{ + ERL_NIF_TERM result; + int res; + + if (!isEncoded) { + res = socket_setopt(descP->sock, level, opt, + valP->u.binVal.data, valP->u.binVal.size); + if (res != 0) + result = make_error2(env, res); + else + result = atom_ok; + } else { + if (isOTP) { + /* These are not actual socket options, + * but options for our implementation. + */ + result = nsetopt_otp(env, descP, opt, valP); + } else { + /* Basically, call setsockopt(...) + * + * How do we know what type each option have? tag in value type? + * + */ + result = nsetopt_gen(env, descP, level, opt, valP); + } + } + + return result; +} + + +static +ERL_NIF_TERM nsetopt_otp(ErlNifEnv* env, + SocketDescriptor* descP, + int opt, + SocketOptValue* valP) +{ + ERL_NIF_TERM result; + + /* Make an idiot check just to be on the safe side... */ + if (valP->tag == SOCKET_OPT_VALUE_UNDEF) + return make_error(env, atom_einval); + + switch (opt) { + case SOCKET_OPT_OTP_DEBUG: + descP->dbg = valP->u.boolVal; + result = atom_ok; + break; + + case SOCKET_OPT_OTP_IOW: + descP->iow = valP->u.boolVal; + result = atom_ok; + break; + + default: + result = make_error(env, atom_einval); + break; + } + + return result; +} + + +static +ERL_NIF_TERM nsetopt_gen(ErlNifEnv* env, + SocketDescriptor* descP, + int level, + int opt, + SocketOptValue* valP) +{ + socklen_t optLen; + int res; + ERL_NIF_TERM result; + + switch (valP->tag) { + case SOCKET_OPT_VALUE_INT: + { + optLen = sizeof(valP->u.intVal); + res = socket_setopt(descP->sock, level, opt, + (void*) &valP->u.intVal, optLen); + if (res != 0) + result = make_error2(env, res); + else + result = atom_ok; + } + break; + + case SOCKET_OPT_VALUE_BIN: + { + optLen = valP->u.binVal.size; + res = socket_setopt(descP->sock, level, opt, + &valP->u.binVal.data, optLen); + if (res != 0) + result = make_error2(env, res); + else + result = atom_ok; + } + break; + + default: + result = make_error(env, atom_einval); + } + + return result; +} + + + +static +BOOLEAN_T elevel2level(BOOLEAN_T isEncoded, + int eLevel, + BOOLEAN_T* isOTP, + int* level) +{ + BOOLEAN_T result; + + if (isEncoded) { + switch (eLevel) { + case SOCKET_OPT_LEVEL_OTP: + *isOTP = TRUE; + *level = -1; + result = TRUE; + break; + + case SOCKET_OPT_LEVEL_SOCKET: + *isOTP = FALSE; + *level = SOL_SOCKET; + result = TRUE; + break; + + case SOCKET_OPT_LEVEL_IP: + *isOTP = FALSE; +#if defined(SOL_IP) + *level = SOL_IP; +#else + *level = IPROTO_IP; +#endif + result = TRUE; + break; + +#if defined(SOL_IPV6) + case SOCKET_OPT_LEVEL_IPV6: + *isOTP = FALSE; + *level = SOL_IPV6; + result = TRUE; + break; +#endif + + case SOCKET_OPT_LEVEL_TCP: + *isOTP = FALSE; + *level = IPPROTO_TCP; + result = TRUE; + break; + + case SOCKET_OPT_LEVEL_UDP: + *isOTP = FALSE; + *level = IPPROTO_UDP; + result = TRUE; + break; + +#ifdef HAVE_SCTP + case SOCKET_OPT_LEVEL_SCTP: + *isOTP = FALSE; + *level = IPPROTO_SCTP; + result = TRUE; + break; +#endif + + default: + *isOTP = FALSE; + *level = -1; + result = FALSE; + break; + } + } else { + *isOTP = FALSE; + *level = eLevel; + result = TRUE; + } + + return result; +} + + +static +BOOLEAN_T eoptval2optval(ErlNifEnv* env, + BOOLEAN_T isEncoded, + BOOLEAN_T isOTP, + int level, + int eOpt, + ERL_NIF_TERM eVal, + int* opt, + SocketOptValue* valP) +{ + if (isOTP) { + return eoptval2optval_otp(env, eOpt, eVal, opt, valP); + } else if (!isEncoded) { + return eoptval2optval_plain(env, eOpt, eVal, opt, valP); + } else { + switch (level) { + case SOL_SOCKET: + return eoptval2optval_socket(env, eOpt, eVal, opt, valP); + break; + +#if defined(SOL_IP) + case SOL_IP: +#else + case IPPROTO_IP: +#endif + return eoptval2optval_ip(env, eOpt, eVal, opt, valP); + break; + +#if defined(SOL_IPV6) + case SOL_IPV6: + return eoptval2optval_ipv6(env, eOpt, eVal, opt, valP); + break; +#endif + + case IPPROTO_TCP: + return eoptval2optval_tcp(env, eOpt, eVal, opt, valP); + break; + + case IPPROTO_UDP: + return eoptval2optval_udp(env, eOpt, eVal, opt, valP); + break; + +#ifdef HAVE_SCTP + case IPPROTO_SCTP: + return eoptval2optval_sctp(env, eOpt, eVal, opt, valP); + break; +#endif + + default: + *opt = -1; + return FALSE; + } + } +} + + + +static +BOOLEAN_T eoptval2optval_otp(ErlNifEnv* env, + int eOpt, + ERL_NIF_TERM eVal, + int* opt, + SocketOptValue* valP) +{ + BOOLEAN_T result = FALSE; + + switch (eOpt) { + case SOCKET_OPT_OTP_IOW: + case SOCKET_OPT_OTP_DEBUG: + { + if (decode_bool(env, eVal, &valP->u.boolVal)) { + valP->tag = SOCKET_OPT_VALUE_BOOL; + result = TRUE; + } else { + result = FALSE; + } + *opt = eOpt; + } + break; + + default: + *opt = -1; + valP->tag = SOCKET_OPT_VALUE_UNDEF; + } + + return result; +} + + +static +BOOLEAN_T eoptval2optval_plain(ErlNifEnv* env, + int eOpt, + ERL_NIF_TERM eVal, + int* opt, + SocketOptValue* valP) +{ + if (!GET_BIN(env, eVal, &valP->u.binVal)) + return FALSE; + valP->tag = SOCKET_OPT_VALUE_BIN; + *opt = eOpt; + + return TRUE; +} + + + +static +BOOLEAN_T eoptval2optval_socket(ErlNifEnv* env, + int eOpt, + ERL_NIF_TERM eVal, + int* opt, + SocketOptValue* valP) +{ + switch (eOpt) { +#if defined(SO_KEEPALIVE) + case SOCKET_OPT_SOCK_KEEPALIVE: + { + BOOLEAN_T val; + + if (decode_bool(env, eVal, &val)) { + *opt = SO_KEEPALIVE; + valP->tag = SOCKET_OPT_VALUE_INT; + valP->u.intVal = (val) ? 1 : 0; + return TRUE; + } else { + *opt = -1; + valP->tag = SOCKET_OPT_VALUE_UNDEF; + return FALSE; + } + } + break; +#endif + +#if defined(SO_LINGER) + case SOCKET_OPT_SOCK_LINGER: + { + if (decode_sock_linger(env, eVal, &valP->u.lingerVal)) { + *opt = SO_LINGER; + valP->tag = SOCKET_OPT_VALUE_LINGER; + return TRUE; + } else { + *opt = -1; + valP->tag = SOCKET_OPT_VALUE_UNDEF; + return FALSE; + } + } + break; +#endif + + default: + *opt = -1; + valP->tag = SOCKET_OPT_VALUE_UNDEF; + return FALSE; + } +} + + + +static +BOOLEAN_T eoptval2optval_ip(ErlNifEnv* env, + int eOpt, + ERL_NIF_TERM eVal, + int* opt, + SocketOptValue* valP) +{ + switch (eOpt) { +#if defined(IP_RECVTOS) + case SOCKET_OPT_IP_RECVTOS: + { + BOOLEAN_T val; + + if (decode_bool(env, eVal, &val)) { + *opt = IP_RECVTOS; + valP->tag = SOCKET_OPT_VALUE_INT; + valP->u.intVal = (val) ? 1 : 0; + return TRUE; + } else { + *opt = -1; + valP->tag = SOCKET_OPT_VALUE_UNDEF; + return TRUE; + } + } + break; +#endif + +#if defined(IP_ROUTER_ALERT) + case SOCKET_OPT_IP_ROUTER_ALERT: + if (GET_INT(env, eVal, &valP->u.intVal)) { + valP->tag = SOCKET_OPT_VALUE_INT; + *opt = IP_ROUTER_ALERT; + return TRUE; + } else { + *opt = -1; + valP->tag = SOCKET_OPT_VALUE_UNDEF; + return FALSE; + } + break; +#endif + +#if defined(IP_TOS) + case SOCKET_OPT_IP_TOS: + { + if (decode_ip_tos(env, eVal, &valP->u.intVal)) { + valP->tag = SOCKET_OPT_VALUE_INT; + *opt = IP_TOS; + return TRUE; + } else { + *opt = -1; + return FALSE; + } + } + break; +#endif + +#if defined(IP_TTL) + case SOCKET_OPT_IP_TTL: + /* + * Should we care about the value? That is, if it is valid? + * And what is the valid range anyway for ttl? 0 - 255? + * + */ + if (!GET_INT(env, eVal, &valP->u.intVal)) + return FALSE; // PLACEHOLDER - We should really be more informative + valP->tag = SOCKET_OPT_VALUE_INT; + *opt = IP_TTL; + return TRUE; + break; +#endif + + default: + *opt = -1; + valP->tag = SOCKET_OPT_VALUE_UNDEF; + return FALSE; + } + +} + + + +#if defined(SOL_IPV6) +static +BOOLEAN_T eoptval2optval_ipv6(ErlNifEnv* env, + int eOpt, + ERL_NIF_TERM eVal, + int* opt, + SocketOptValue* valP) +{ + BOOLEAN_T result = FALSE; + + switch (eOpt) { +#if defined(IPV6_HOPLIMIT) + case SOCKET_OPT_IPV6_HOPLIMIT: + { + BOOLEAN_T val; + + if (decode_bool(env, eVal, &val)) { + valP->tag = SOCKET_OPT_VALUE_INT; + valP->u.intVal = (val) ? 1 : 0; + *opt = IPV6_HOPLIMIT; + result = TRUE; + } else { + *opt = -1; + result = FALSE; + } + } + break; +#endif + + default: + *opt = -1; + valP->tag = SOCKET_OPT_VALUE_UNDEF; + result = FALSE; + break; + } + + return result; +} +#endif + + + +static +BOOLEAN_T eoptval2optval_tcp(ErlNifEnv* env, + int eOpt, + ERL_NIF_TERM eVal, + int* opt, + SocketOptValue* valP) +{ + switch (eOpt) { +#if defined(TCP_MAXSEG) + case SOCKET_OPT_TCP_MAXSEG: + if (!GET_INT(env, eVal, &valP->u.intVal)) { + valP->tag = SOCKET_OPT_VALUE_INT; + *opt = TCP_MAXSEG; + return TRUE; + } else { + return FALSE; + } + break; +#endif + + default: + *opt = -1; + valP->tag = SOCKET_OPT_VALUE_UNDEF; + return FALSE; + } +} + + + +/* +++ decode UDP socket options +++ + * Currently there are no such options, so this function + * is just a placeholder! + */ +static +BOOLEAN_T eoptval2optval_udp(ErlNifEnv* env, + int eOpt, + ERL_NIF_TERM eVal, + int* opt, + SocketOptValue* valP) +{ + switch (eOpt) { + default: + *opt = -1; + valP->tag = SOCKET_OPT_VALUE_UNDEF; + return FALSE; + } +} + + + +#ifdef HAVE_SCTP +static +BOOLEAN_T eoptval2optval_sctp(ErlNifEnv* env, + int eOpt, + ERL_NIF_TERM eVal, + int* opt, + SocketOptValue* valP) +{ + switch (eOpt) { +#if defined(SCTP_AUTOCLOSE) + case SOCKET_OPT_SCTP_AUTOCLOSE: + if (!GET_INT(env, eVal, &valP->u.intVal)) + return FALSE; // PLACEHOLDER - We should really be more informative + valP->tag = SOCKET_OPT_VALUE_INT; + *opt = SCTP_AUTOCLOSE; + return TRUE; + break; +#endif + + default: + *opt = -1; + valP->tag = SOCKET_OPT_VALUE_UNDEF; + return FALSE; + } +} +#endif + + + +/* +++ socket_setopt +++ + * + * + * The original code here had problems that possibly + * only occur if you abuse it for non-INET sockets, but anyway: + * a) If the getsockopt for SO_PRIORITY or IP_TOS failed, the actual + * requested setsockopt was never even attempted. + * b) If {get,set}sockopt for one of IP_TOS and SO_PRIORITY failed, + * but ditto for the other worked and that was actually the requested + * option, failure was still reported to erlang. + * + * + * + * The relations between SO_PRIORITY, TOS and other options + * is not what you (or at least I) would expect...: + * If TOS is set after priority, priority is zeroed. + * If any other option is set after tos, tos might be zeroed. + * Therefore, save tos and priority. If something else is set, + * restore both after setting, if tos is set, restore only + * prio and if prio is set restore none... All to keep the + * user feeling socket options are independent. + * + */ +static +int socket_setopt(int sock, int level, int opt, + const void* optVal, const socklen_t optLen) +{ + int res; + +#if defined(IP_TOS) && defined(SOL_IP) && defined(SO_PRIORITY) + int tmpIValPRIO; + int tmpIValTOS; + int resPRIO; + int resTOS; + SOCKLEN_T tmpArgSzPRIO = sizeof(tmpIValPRIO); + SOCKLEN_T tmpArgSzTOS = sizeof(tmpIValTOS); + + resPRIO = sock_getopt(sock, SOL_SOCKET, SO_PRIORITY, + &tmpIValPRIO, &tmpArgSzPRIO); + resTOS = sock_getopt(sock, SOL_IP, IP_TOS, + &tmpIValTOS, &tmpArgSzTOS); + + res = sock_setopt(sock, level, opt, optVal, optLen); + if (res == 0) { + + /* Ok, now we *maybe* need to "maybe" restore PRIO and TOS... + * maybe, possibly, ... + */ + + if (opt != SO_PRIORITY) { + if ((opt != IP_TOS) && (resTOS == 0)) { + resTOS = sock_setopt(sock, SOL_IP, IP_TOS, + (void *) &tmpIValTOS, + tmpArgSzTOS); + res = resTOS; + } + if ((res == 0) && (resPRIO == 0)) { + resPRIO = sock_setopt(sock, SOL_SOCKET, SO_PRIORITY, + &tmpIValPRIO, + tmpArgSzPRIO); + + /* Some kernels set a SO_PRIORITY by default + * that you are not permitted to reset, + * silently ignore this error condition. + */ + + if ((resPRIO != 0) && (sock_errno() == EPERM)) { + res = 0; + } else { + res = resPRIO; + } + } + } + } + +#else + + res = sock_setopt(sock, level, opt, optVal, optLen); + +#endif + + return res; +} + + /* ---------------------------------------------------------------------- * U t i l i t y F u n c t i o n s * ---------------------------------------------------------------------- @@ -3468,6 +4290,139 @@ char* decode_address_atom(ErlNifEnv* env, } +static +BOOLEAN_T decode_bool(ErlNifEnv* env, ERL_NIF_TERM eVal, BOOLEAN_T* val) +{ + unsigned int len; + char b[16]; // Just in case... + + /* Verify that the value is actually an atom */ + if (!IS_ATOM(env, eVal)) + return FALSE; + + /* Verify that the value is of acceptable length */ + if (!(GET_ATOM_LEN(env, eVal, &len) && + (len > 0) && + (len <= sizeof("false")))) + return FALSE; + + /* And finally try to extract the value */ + if (!GET_ATOM(env, eVal, b, sizeof(b))) + return FALSE; + + if (strncmp(b, "true", len) == 0) + *val = TRUE; + else + *val = FALSE; + + return TRUE; +} + + + +/* +++ decode the linger value +++ + * The (socket) linger option is provided as a two tuple: + * + * {OnOff :: boolean(), Time :: integer()} + * + */ +static +BOOLEAN_T decode_sock_linger(ErlNifEnv* env, ERL_NIF_TERM eVal, struct linger* valP) +{ + const ERL_NIF_TERM* lt; // The array of the elements of the tuple + int sz; // The size of the tuple - should be 2 + BOOLEAN_T onOff; + int secs; + + if (!GET_TUPLE(env, eVal, &sz, <)) + return FALSE; + + if (sz != 2) + return FALSE; + + + /* So fas so good - now check the two elements of the tuple. */ + + if (!decode_bool(env, lt[0], &onOff)) + return FALSE; + + if (!GET_INT(env, lt[1], &secs)) + return FALSE; + + valP->l_onoff = (onOff) ? 1 : 0; + valP->l_linger = secs; + + return TRUE; +} + + + +/* +++ decocde the ip socket option tos +++ + * The (ip) option can be provide in two ways: + * + * atom() | integer() + * + * When its an atom it can have the values: + * + * lowdelay | throughput | reliability | mincost + * + */ +static +BOOLEAN_T decode_ip_tos(ErlNifEnv* env, ERL_NIF_TERM eVal, int* val) +{ + BOOLEAN_T result = FALSE; + + if (IS_ATOM(env, eVal)) { + unsigned int len; + char b[sizeof("reliability")+1]; // Just in case... + + if (!(GET_ATOM_LEN(env, eVal, &len) && + (len > 0) && + (len <= (sizeof("reliability"))))) { + *val = -1; + return FALSE; + } + + if (!GET_ATOM(env, eVal, b, sizeof(b))) { + *val = -1; + return FALSE; + } + + if (strncmp(b, "lowdelay", len) == 0) { + *val = IPTOS_LOWDELAY; + result = TRUE; + } else if (strncmp(b, "throughput", len) == 0) { + *val = IPTOS_THROUGHPUT; + result = TRUE; + } else if (strncmp(b, "reliability", len) == 0) { + *val = IPTOS_RELIABILITY; + result = TRUE; + } else if (strncmp(b, "mincost", len) == 0) { + *val = IPTOS_MINCOST; + result = TRUE; + } else { + *val = -1; + result = FALSE; + } + + } else if (IS_NUM(env, eVal)) { + + if (GET_INT(env, eVal, val)) { + result = TRUE; + } else { + *val = -1; + result = FALSE; + } + + } else { + *val = -1; + result = FALSE; + } + + return result; +} + + /* *** alloc_descriptor *** * Allocate and perform basic initialization of a socket descriptor. @@ -4232,8 +5187,8 @@ ErlNifFunc socket_funcs[] = {"nif_recvfrom", 2, nif_recvfrom, 0}, {"nif_close", 1, nif_close, 0}, {"nif_shutdown", 2, nif_shutdown, 0}, - {"nif_setsockopt", 3, nif_setsockopt, 0}, - {"nif_getsockopt", 2, nif_getsockopt, 0}, + {"nif_setopt", 3, nif_setopt, 0}, + {"nif_getopt", 2, nif_getopt, 0}, /* "Extra" functions to "complete" the socket interface. * For instance, the function nif_finalize_connection @@ -4260,38 +5215,38 @@ BOOLEAN_T extract_item_on_load(ErlNifEnv* env, return TRUE; } - static - BOOLEAN_T extract_debug_on_load(ErlNifEnv* env, ERL_NIF_TERM map, BOOLEAN_T def) - { - ERL_NIF_TERM dbgKey = enif_make_atom(env, "debug"); - ERL_NIF_TERM dbgVal; - unsigned int len; - char d[16]; // Just in case... +static +BOOLEAN_T extract_debug_on_load(ErlNifEnv* env, ERL_NIF_TERM map, BOOLEAN_T def) +{ + ERL_NIF_TERM dbgKey = enif_make_atom(env, "debug"); + ERL_NIF_TERM dbgVal; + unsigned int len; + char d[16]; // Just in case... - /* Extra the value of the debug property */ - if (!extract_item_on_load(env, map, dbgKey, &dbgVal)) - return def; + /* Extra the value of the debug property */ + if (!extract_item_on_load(env, map, dbgKey, &dbgVal)) + return def; - /* Verify that the value is actually an atom */ - if (!enif_is_atom(env, dbgVal)) - return def; + /* Verify that the value is actually an atom */ + if (!enif_is_atom(env, dbgVal)) + return def; - /* Verify that the value is of acceptable length */ - if (!(GET_ATOM_LEN(env, dbgVal, &len) && - (len > 0) && - (len <= sizeof("false")))) - return def; + /* Verify that the value is of acceptable length */ + if (!(GET_ATOM_LEN(env, dbgVal, &len) && + (len > 0) && + (len <= sizeof("false")))) + return def; - /* And finally try to extract the value */ - if (!GET_ATOM(env, dbgVal, d, sizeof(d))) - return def; + /* And finally try to extract the value */ + if (!GET_ATOM(env, dbgVal, d, sizeof(d))) + return def; - if (strncmp(d, "true", len) == 0) - return TRUE; - else - return FALSE; + if (strncmp(d, "true", len) == 0) + return TRUE; + else + return FALSE; - } +} static diff --git a/erts/preloaded/src/socket.erl b/erts/preloaded/src/socket.erl index 8dce86c518..1090380769 100644 --- a/erts/preloaded/src/socket.erl +++ b/erts/preloaded/src/socket.erl @@ -20,6 +20,8 @@ -module(socket). +-compile({no_auto_import,[error/1]}). + %% Administrative and "global" utility functions -export([ on_load/0, on_load/1, on_load/2, @@ -68,7 +70,18 @@ send_flags/0, send_flag/0, - shutdown_how/0 + shutdown_how/0, + + sockopt_level/0, + otp_socket_option/0, + socket_option/0, + ip_socket_option/0, + ipv6_socket_option/0, + tcp_socket_option/0, + udp_socket_option/0, + sctp_socket_option/0, + + ip_tos_flag/0 ]). @@ -108,12 +121,189 @@ %% otp - The option is internal to our (OTP) imeplementation. %% socket - The socket layer (SOL_SOCKET). -%% ip - The IP layer (SOL_IP). +%% ip - The IP layer (SOL_IP or is it IPPROTO_IP?). %% ipv6 - The IPv6 layer (SOL_IPV6). %% tcp - The TCP (Transport Control Protocol) layer (IPPROTO_TCP). %% udp - The UDP (User Datagram Protocol) layer (IPPROTO_UDP). +%% sctp - The SCTP (Stream Control Transmission Protocol) layer (IPPROTO_SCTP). %% Int - Raw level, sent down and used "as is". --type option_level() :: otp | socket | ip | ipv6 | tcp | udp | non_neg_integer(). +-type sockopt_level() :: otp | + socket | + ip | ipv6 | tcp | udp | sctp | + non_neg_integer(). + +%% There are some options that are 'read-only'. +%% Should those be included here or in a special list? +%% Should we just document it and leave it to the user? +%% Or catch it in the encode functions? +%% A setopt for a readonly option leads to einval? + +-type otp_socket_option() :: debug | + iow | + rcvbuf | + sndbuf. +%% Shall we have special treatment of linger?? +%% read-only options: +%% domain | protocol | type. +%% FreeBSD (only?): acceptfilter +-type socket_option() :: acceptconn | + acceptfilter | + bindtodevice | + broadcast | + busy_poll | + debug | + dontroute | + error | + keepalive | + linger | + mark | + oobinline | + passcred | + peek_off | + peek_cred | + priority | + rcvbuf | + rcvbufforce | + rcvlowat | sndlowat | + rcvtimeo | sndtimeo | + reuseaddr | + reuseport | + rxq_ovfl | + setfib | + sndbuf | + sndbufforce | + timestamp | + type. +%% Read-only options: +%% mtu +%% +%% Options only valid for RAW sockets: +%% nodefrag +-type ip_socket_option() :: add_membership | + add_source_membership | + block_source | + dont_frag | + drop_membership | + drop_source_membership | + freebind | + hdrincl | + minttl | + msfilter | + mtu | + mtu_discover | + multicast_all | + multicast_if | + multicast_loop | + multicast_ttl | + nodefrag | + options | + pktinfo | + recverr | + recvif | + recvdstaddr | + recvopts | + recvorigdstaddr | + recvtos | + recvttl | + retopts | + router_alert | + sndsrcaddr | + tos | + transparent | + ttl | + unblock_source. +-type ipv6_socket_option() :: + addform | + add_membership | drop_membership | + authhdr | + auth_level | + checksum | + dstopts | + esp_trans_level | + esp_network_level | + faith | + flowinfo | + hoplimit | + hopopts | + ipcomp_level | + join_group | + leave_group | + mtu | + mtu_discover | + multicast_hops | + multicast_if | + multicast_loop | + portrange | + pktinfo | + pktoptions | + recverr | + recvpktinfo | + recvtclass | + router_alert | + rthdr | + tclass | + unicast_hops | + use_min_mtu | + v6only. + +-type tcp_socket_option() :: congestion | + maxseg | + nodelay | + user_timeout. + +-type udp_socket_option() :: checksum | + maxdgram | + recvspace. +-type sctp_socket_option() :: + adaption_layer | + associnfo | + auth_active_key | + auth_asconf | + auth_chunk | + auth_key | + auth_delete_key | + autoclose | + context | + default_send_params | + delayed_ack_time | + disable_fragments | + hmac_ident | + events | + explicit_eor | + fragment_interleave | + get_peer_addr_info | + initmsg | + i_want_mapped_v4_addr | + local_auth_chunks | + maxseg | + maxburst | + nodelay | + partial_delivery_point | + peer_addr_params | + peer_auth_chunks | + primary_addr | + reset_streams | + rtoinfo | + set_peer_primary_addr | + status | + use_ext_recvinfo. + +%% -type plain_socket_options() :: integer(). +%% -type sockopts() :: otp_socket_options() | +%% socket_options() | +%% ip_socket_options() | +%% ipv6_socket_options() | +%% tcp_socket_options() | +%% udp_socket_options() | +%% sctp_socket_options() | +%% plain_socket_options(). + +%% If the integer value is used its up to the caller to ensure its valid! +-type ip_tos_flag() :: lowdeley | + throughput | + reliability | + mincost | + integer(). -type socket_info() :: map(). -record(socket, {info :: socket_info(), @@ -148,9 +338,6 @@ peek | trunc. --type setopt_key() :: foo. --type getopt_key() :: foo. - -type shutdown_how() :: read | write | read_write. -record(msg_hdr, @@ -213,24 +400,35 @@ -define(SOCKET_RECV_FLAGS_DEFAULT, []). -define(SOCKET_RECV_TIMEOUT_DEFAULT, infinity). --define(SOCKET_SETOPT_LEVEL_ENCODED, 0). --define(SOCKET_SETOPT_LEVEL_RAW, 1). --define(SOCKET_SETOPT_LEVEL_OTP, 0). --define(SOCKET_SETOPT_LEVEL_SOCKET, 1). --define(SOCKET_SETOPT_LEVEL_IP, 2). --define(SOCKET_SETOPT_LEVEL_IPV6, 3). --define(SOCKET_SETOPT_LEVEL_TCP, 4). --define(SOCKET_SETOPT_LEVEL_UDP, 5). +-define(SOCKET_OPT_LEVEL_OTP, 0). +-define(SOCKET_OPT_LEVEL_SOCKET, 1). +-define(SOCKET_OPT_LEVEL_IP, 2). +-define(SOCKET_OPT_LEVEL_IPV6, 3). +-define(SOCKET_OPT_LEVEL_TCP, 4). +-define(SOCKET_OPT_LEVEL_UDP, 5). +-define(SOCKET_OPT_LEVEL_SCTP, 6). --define(SOCKET_SETOPT_KEY_DEBUG, 0). +-define(SOCKET_OPT_OTP_DEBUG, 0). +-define(SOCKET_OPT_OTP_IOW, 1). --define(SOCKET_GETOPT_KEY_DEBUG, ?SOCKET_SETOPT_KEY_DEBUG). +-define(SOCKET_OPT_SOCK_KEEPALIVE, 0). +-define(SOCKET_OPT_SOCK_LINGER, 1). + +-define(SOCKET_OPT_IP_RECVTOS, 0). +-define(SOCKET_OPT_IP_ROUTER_ALERT, 1). +-define(SOCKET_OPT_IP_TOS, 2). +-define(SOCKET_OPT_IP_TTL, 3). + +-define(SOCKET_OPT_IPV6_HOPLIMIT, 0). + +-define(SOCKET_OPT_TCP_MAXSEG, 0). -define(SOCKET_SHUTDOWN_HOW_READ, 0). -define(SOCKET_SHUTDOWN_HOW_WRITE, 1). -define(SOCKET_SHUTDOWN_HOW_READ_WRITE, 2). + %% =========================================================================== %% %% Administrative and utility API @@ -1059,28 +1257,64 @@ shutdown(#socket{ref = SockRef}, How) -> %% %% %% -%% WE NEED TOP MAKE SURE THAT THE USER DOES NOT MAKE US BLOCKING +%% WE NEED TO MAKE SURE THAT THE USER DOES NOT MAKE US BLOCKING %% AS MUCH OF THE CODE EXPECTS TO BE NON-BLOCKING!! %% %% --spec setopt(Socket, Level, Key, Value) -> ok | {error, Reason} when +-spec setopt(Socket, Level, Opt, Value) -> ok | {error, Reason} when + Socket :: socket(), + Level :: otp, + Opt :: otp_socket_option(), + Value :: term(), + Reason :: term() + ; (Socket, Level, Opt, Value) -> ok | {error, Reason} when + Socket :: socket(), + Level :: socket, + Opt :: socket_option(), + Value :: term(), + Reason :: term() + ; (Socket, Level, Opt, Value) -> ok | {error, Reason} when + Socket :: socket(), + Level :: ip, + Opt :: ip_socket_option(), + Value :: term(), + Reason :: term() + ; (Socket, Level, Opt, Value) -> ok | {error, Reason} when + Socket :: socket(), + Level :: ipv6, + Opt :: ipv6_socket_option(), + Value :: term(), + Reason :: term() + ; (Socket, Level, Opt, Value) -> ok | {error, Reason} when + Socket :: socket(), + Level :: tcp, + Opt :: tcp_socket_option(), + Value :: term(), + Reason :: term() + ; (Socket, Level, Opt, Value) -> ok | {error, Reason} when + Socket :: socket(), + Level :: udp, + Opt :: udp_socket_option(), + Value :: term(), + Reason :: term() + ; (Socket, Level, Opt, Value) -> ok | {error, Reason} when Socket :: socket(), - Level :: option_level(), - Key :: setopt_key(), + Level :: sctp, + Opt :: sctp_socket_option(), Value :: term(), Reason :: term(). setopt(#socket{info = Info, ref = SockRef}, Level, Key, Value) -> try begin - Domain = maps:get(domain, Info), - Type = maps:get(type, Info), - Protocol = maps:get(protocol, Info), - ELevel = enc_setopt_level(Level), - EKey = enc_setopt_key(Level, Key, Domain, Type, Protocol), - EVal = enc_setopt_value(Level, Key, Value, Domain, Type, Protocol), - nif_setopt(SockRef, ELevel, EKey, EVal) + Domain = maps:get(domain, Info), + Type = maps:get(type, Info), + Protocol = maps:get(protocol, Info), + {EIsEncoded, ELevel} = enc_setopt_level(Level), + EKey = enc_setopt_key(Level, Key, Domain, Type, Protocol), + EVal = enc_setopt_value(Level, Key, Value, Domain, Type, Protocol), + nif_setopt(SockRef, EIsEncoded, ELevel, EKey, EVal) end catch throw:T -> @@ -1090,6 +1324,9 @@ setopt(#socket{info = Info, ref = SockRef}, Level, Key, Value) -> end. + +%% =========================================================================== +%% %% getopt - retrieve individual properties of a socket %% %% What properties are valid depend on what kind of socket it is @@ -1100,8 +1337,44 @@ setopt(#socket{info = Info, ref = SockRef}, Level, Key, Value) -> -spec getopt(Socket, Level, Key) -> {ok, Value} | {error, Reason} when Socket :: socket(), - Level :: option_level(), - Key :: getopt_key(), + Level :: otp, + Key :: otp_socket_option(), + Value :: term(), + Reason :: term() + ; (Socket, Level, Key) -> {ok, Value} | {error, Reason} when + Socket :: socket(), + Level :: socket, + Key :: socket_option(), + Value :: term(), + Reason :: term() + ; (Socket, Level, Key) -> {ok, Value} | {error, Reason} when + Socket :: socket(), + Level :: ip, + Key :: ip_socket_option(), + Value :: term(), + Reason :: term() + ; (Socket, Level, Key) -> {ok, Value} | {error, Reason} when + Socket :: socket(), + Level :: ipv6, + Key :: ipv6_socket_option(), + Value :: term(), + Reason :: term() + ; (Socket, Level, Key) -> {ok, Value} | {error, Reason} when + Socket :: socket(), + Level :: tcp, + Key :: tcp_socket_option(), + Value :: term(), + Reason :: term() + ; (Socket, Level, Key) -> {ok, Value} | {error, Reason} when + Socket :: socket(), + Level :: udp, + Key :: udp_socket_option(), + Value :: term(), + Reason :: term() + ; (Socket, Level, Key) -> {ok, Value} | {error, Reason} when + Socket :: socket(), + Level :: sctp, + Key :: sctp_socket_option(), Value :: term(), Reason :: term(). @@ -1111,13 +1384,14 @@ getopt(#socket{info = Info, ref = SockRef}, Level, Key) -> Domain = maps:get(domain, Info), Type = maps:get(type, Info), Protocol = maps:get(protocol, Info), - ELevel = enc_getopt_level(Level), + {EIsEncoded, ELevel} = enc_getopt_level(Level), EKey = enc_getopt_key(Level, Key, Domain, Type, Protocol), %% We may need to decode the value (for the same reason %% we needed to encode the value for setopt). - case nif_getopt(SockRef, ELevel, EKey) of + case nif_getopt(SockRef, EIsEncoded, ELevel, EKey) of {ok, EVal} -> - Val = dec_getopt_value(Level, Key, EVal, Domain, Type, Protocol), + Val = dec_getopt_value(Level, Key, EVal, + Domain, Type, Protocol), {ok, Val}; {error, _} = ERROR -> ERROR @@ -1205,51 +1479,123 @@ enc_flags(Flags, EFlags) -> end, lists:foldl(F, 0, Flags). + +%% +++ Encode setopt level +++ + +-spec enc_setopt_level(Level) -> {IsEncoded, EncodedLevel} when + Level :: sockopt_level(), + IsEncoded :: boolean(), + EncodedLevel :: integer(). + enc_setopt_level(otp) -> - {?SOCKET_SETOPT_LEVEL_ENCODED, ?SOCKET_SETOPT_LEVEL_OTP}; + {true, ?SOCKET_OPT_LEVEL_OTP}; enc_setopt_level(socket) -> - {?SOCKET_SETOPT_LEVEL_ENCODED, ?SOCKET_SETOPT_LEVEL_SOCKET}; + {true, ?SOCKET_OPT_LEVEL_SOCKET}; enc_setopt_level(ip) -> - {?SOCKET_SETOPT_LEVEL_ENCODED, ?SOCKET_SETOPT_LEVEL_IP}; + {true, ?SOCKET_OPT_LEVEL_IP}; enc_setopt_level(ipv6) -> - {?SOCKET_SETOPT_LEVEL_ENCODED, ?SOCKET_SETOPT_LEVEL_IPV6}; + {true, ?SOCKET_OPT_LEVEL_IPV6}; enc_setopt_level(tcp) -> - {?SOCKET_SETOPT_LEVEL_ENCODED, ?SOCKET_SETOPT_LEVEL_TCP}; + {true, ?SOCKET_OPT_LEVEL_TCP}; enc_setopt_level(udp) -> - {?SOCKET_SETOPT_LEVEL_ENCODED, ?SOCKET_SETOPT_LEVEL_UDP}; -%% Any option that is of an raw level must be provided as a binary + {true, ?SOCKET_OPT_LEVEL_UDP}; +%% Any option that is of an plain level must be provided as a binary %% already fully encoded! enc_setopt_level(L) when is_integer(L) -> - {?SOCKET_SETOPT_LEVEL_RAW, L}. + {false, L}. -%% We should ...really... do something with the domain, type and protocol args... -%% Also, any option which has an integer level (raw) must also be provided -%% in a raw mode, that is, as an integer. -enc_setopt_key(L, K, _, _, _) when is_integer(L) andalso is_integer(K) -> - K; -enc_setopt_key(otp, debug, _, _, _) -> - ?SOCKET_SETOPT_KEY_DEBUG. +%% +++ Encode setopt key +++ %% We should ...really... do something with the domain, type and protocol args... +%% Also, any option (key) which has an integer level (plain) must also be provided +%% in a plain mode, that is, as an integer. +%% Also, not all options are available on all platforms. That is something we +%% don't check here, but in the nif-code. + +enc_setopt_key(Level, Opt, Domain, Type, Protocol) -> + enc_sockopt_key(Level, Opt, set, Domain, Type, Protocol). + + +%% +++ Encode setopt value +++ +%% +%% For the most part this function does *not* do an actually encode, +%% it simply validates the value type. But in some cases it actually +%% encodes the value into an more manageable type. + enc_setopt_value(otp, debug, V, _, _, _) when is_boolean(V) -> V; +enc_setopt_value(otp, iow, V, _, _, _) when is_boolean(V) -> + V; +enc_setopt_value(otp = L, Opt, V, _D, _T, _P) -> + not_supported({L, Opt, V}); + +enc_setopt_value(socket, keepalive, V, _D, _T, _P) when is_boolean(V) -> + V; enc_setopt_value(socket, linger, abort, D, T, P) -> enc_setopt_value(socket, linger, {true, 0}, D, T, P); enc_setopt_value(socket, linger, {OnOff, Secs} = V, _D, _T, _P) when is_boolean(OnOff) andalso is_integer(Secs) andalso (Secs >= 0) -> V; -enc_setopt_value(L, _, V, _, _, _) when is_integer(L) andalso is_binary(V) -> +enc_setopt_value(socket = L, Opt, V, _D, _T, _P) -> + not_supported({L, Opt, V}); + +enc_setopt_value(ip, recvtos, V, _D, _T, _P) + when is_boolean(V) -> + V; +enc_setopt_value(ip, router_alert, V, _D, _T, _P) + when is_integer(V) -> + V; +enc_setopt_value(ip, tos, V, _D, _T, _P) + when (V =:= lowdelay) orelse + (V =:= throughput) orelse + (V =:= reliability) orelse + (V =:= mincost) orelse + is_integer(V) -> + V; +enc_setopt_value(ip, ttl, V, _D, _T, _P) + when is_integer(V) -> + V; +enc_setopt_value(ip = L, Opt, V, _D, _T, _P) -> + not_supported({L, Opt, V}); + +enc_setopt_value(ipv6, hoplimit, V, _D, T, _P) + when is_boolean(V) andalso ((T =:= dgram) orelse (T =:= raw)) -> + V; +enc_setopt_value(ipv6 = L, Opt, V, _D, _T, _P) -> + not_supported({L, Opt, V}); + +enc_setopt_value(tcp, maxseg, V, _D, T, P) + when is_integer(V) andalso + (T =:= stream) andalso + (P =:= tcp) -> + V; +enc_setopt_value(tcp = L, Opt, V, _D, _T, _P) -> + not_supported({L, Opt, V}); + +enc_setopt_value(udp = L, Opt, _V, _D, _T, _P) -> + not_supported({L, Opt}); + +enc_setopt_value(L, Opt, V, _, _, _) + when is_integer(L) andalso is_integer(Opt) andalso is_binary(V) -> V. + +%% +++ Encode getopt value +++ + enc_getopt_level(Level) -> enc_setopt_level(Level). -%% We should ...really... do something with the domain, type and protocol args... -enc_getopt_key(otp, debug, _, _, _) -> - ?SOCKET_GETOPT_KEY_DEBUG. + +%% +++ Encode getopt key +++ + +enc_getopt_key(Level, Opt, Domain, Type, Protocol) -> + enc_sockopt_key(Level, Opt, get, Domain, Type, Protocol). + + +%% +++ Decode getopt value +++ %% We should ...really... do something with the domain, type and protocol args... dec_getopt_value(otp, debug, B, _, _, _) when is_boolean(B) -> @@ -1257,6 +1603,260 @@ dec_getopt_value(otp, debug, B, _, _, _) when is_boolean(B) -> +%% +++ Encode socket option key +++ + +%% Most options are usable both for set and get, but some are +%% are only available for e.g. get. +-spec enc_sockopt_key(Level, Opt, + Direction, + Domain, Type, Protocol) -> non_neg_integer() when + Level :: otp, + Opt :: otp_socket_option(), + Direction :: set | get, + Domain :: domain(), + Type :: type(), + Protocol :: protocol() + ; (Level, Direction, Opt, + Domain, Type, Protocol) -> non_neg_integer() when + Level :: socket, + Opt :: socket_option(), + Direction :: set | get, + Domain :: domain(), + Type :: type(), + Protocol :: protocol() + ; (Level, Direction, Opt, + Domain, Type, Protocol) -> non_neg_integer() when + Level :: ip, + Opt :: ip_socket_option(), + Direction :: set | get, + Domain :: domain(), + Type :: type(), + Protocol :: protocol() + ; (Level, Direction, Opt, + Domain, Type, Protocol) -> non_neg_integer() when + Level :: ipv6, + Opt :: ipv6_socket_option(), + Direction :: set | get, + Domain :: domain(), + Type :: type(), + Protocol :: protocol() + ; (Level, Direction, Opt, + Domain, Type, Protocol) -> non_neg_integer() when + Level :: tcp, + Opt :: tcp_socket_option(), + Direction :: set | get, + Domain :: domain(), + Type :: type(), + Protocol :: protocol() + ; (Level, Direction, Opt, + Domain, Type, Protocol) -> non_neg_integer() when + Level :: udp, + Opt :: udp_socket_option(), + Direction :: set | get, + Domain :: domain(), + Type :: type(), + Protocol :: protocol() + ; (Level, Direction, Opt, + Domain, Type, Protocol) -> non_neg_integer() when + Level :: sctp, + Opt :: sctp_socket_option(), + Direction :: set | get, + Domain :: domain(), + Type :: type(), + Protocol :: protocol() + ; (Level, Direction, Opt, + Domain, Type, Protocol) -> non_neg_integer() when + Level :: integer(), + Opt :: integer(), + Direction :: set | get, + Domain :: domain(), + Type :: type(), + Protocol :: protocol(). + + +%% +++ OTP socket options +++ +enc_sockopt_key(otp, debug, _, _, _, _) -> + ?SOCKET_OPT_OTP_DEBUG; +enc_sockopt_key(otp, iow, _, _, _, _) -> + ?SOCKET_OPT_OTP_IOW; + +%% +++ SOCKET socket options +++ +enc_sockopt_key(socket, acceptconn = Opt, get = _Dir, _D, _T, _P) -> + not_supported(Opt); +enc_sockopt_key(socket, acceptfilter = Opt, _Dir, _D, _T, _P) -> + not_supported(Opt); +%% Before linux 3.8, this socket option could be set. +%% Size of buffer for name: IFNAMSZ +%% So, we let the implementation decide. +enc_sockopt_key(socket, bindtodevide = Opt, _Dir, _D, _T, _P) -> + not_supported(Opt); +enc_sockopt_key(socket, broadcast = Opt, _Dir, _D, dgram = _T, _P) -> + not_supported(Opt); +enc_sockopt_key(socket, busy_poll = Opt, _Dir, _D, _T, _P) -> + not_supported(Opt); +enc_sockopt_key(socket, debug = Opt, _Dir, _D, _T, _P) -> + not_supported(Opt); +enc_sockopt_key(socket, dontroute = Opt, _Dir, _D, _T, _P) -> + not_supported(Opt); +enc_sockopt_key(socket, error = Opt, get = _Dir, _D, _T, _P) -> + not_supported(Opt); +%% This is only for connection-oriented sockets, but who are those? +%% Type = stream or Protocol = tcp? +%% For now, we just let is pass and it will fail later if not ok... +enc_sockopt_key(socket, keepalive = _Opt, _Dir, _D, _T, _P) -> + ?SOCKET_OPT_SOCK_KEEPALIVE; +enc_sockopt_key(socket, linger = _Opt, _Dir, _D, _T, _P) -> + ?SOCKET_OPT_SOCK_LINGER; +enc_sockopt_key(socket, mark = Opt, _Dir, _D, _T, _P) -> + not_supported(Opt); +enc_sockopt_key(socket, oobinline = Opt, _Dir, _D, _T, _P) -> + not_supported(Opt); +enc_sockopt_key(socket, passcred = Opt, _Dir, _D, _T, _P) -> + not_supported(Opt); +enc_sockopt_key(socket, peek_off = Opt, _Dir, local = _D, _T, _P) -> + not_supported(Opt); +enc_sockopt_key(socket, peek_cred = Opt, get = _Dir, _D, _T, _P) -> + not_supported(Opt); +enc_sockopt_key(socket, priority = Opt, _Dir, _D, _T, _P) -> + not_supported(Opt); +enc_sockopt_key(socket, rcvbuf = Opt, _Dir, _D, _T, _P) -> + not_supported(Opt); +enc_sockopt_key(socket, rcvbufforce = Opt, _Dir, _D, _T, _P) -> + not_supported(Opt); +%% May not work on linux. +enc_sockopt_key(socket, rcvlowat = Opt, _Dir, _D, _T, _P) -> + not_supported(Opt); +enc_sockopt_key(socket, rcvtimeo = Opt, _Dir, _D, _T, _P) -> + not_supported(Opt); +enc_sockopt_key(socket, reuseaddr = Opt, _Dir, _D, _T, _P) -> + not_supported(Opt); +enc_sockopt_key(socket, reuseport = Opt, _Dir, D, _T, _P) + when ((D =:= inet) orelse (D =:= inet6)) -> + not_supported(Opt); +enc_sockopt_key(socket, rxq_ovfl = Opt, _Dir, _D, _T, _P) -> + not_supported(Opt); +enc_sockopt_key(socket, setfib = Opt, set = _Dir, _D, _T, _P) -> + not_supported(Opt); +enc_sockopt_key(socket, sndbuf = Opt, _Dir, _D, _T, _P) -> + not_supported(Opt); +enc_sockopt_key(socket, sndbufforce = Opt, _Dir, _D, _T, _P) -> + not_supported(Opt); +%% Not changeable on linux. +enc_sockopt_key(socket, sndlowat = Opt, _Dir, _D, _T, _P) -> + not_supported(Opt); +enc_sockopt_key(socket, sndtimeo = Opt, _Dir, _D, _T, _P) -> + not_supported(Opt); +enc_sockopt_key(socket, timestamp = Opt, _Dir, _D, _T, _P) -> + not_supported(Opt); +enc_sockopt_key(socket, UnknownOpt, _Dir, _D, _T, _P) -> + unknown(UnknownOpt); + +%% +++ IP socket options +++ +enc_sockopt_key(ip, add_membership = Opt, set = _Dir, _D, _T, _P) -> + not_supported(Opt); +enc_sockopt_key(ip, add_source_membership = Opt, set = _Dir, _D, _T, _P) -> + not_supported(Opt); +enc_sockopt_key(ip, block_source = Opt, set = _Dir, _D, _T, _P) -> + not_supported(Opt); +%% FreeBSD only? +%% Only respected on udp and raw ip (unless the hdrincl option has been set). +enc_sockopt_key(ip, dontfrag = Opt, _Dir, _D, _T, _P) -> + not_supported(Opt); +enc_sockopt_key(ip, drop_membership = Opt, set = _Dir, _D, _T, _P) -> + not_supported(Opt); +enc_sockopt_key(ip, drop_source_membership = Opt, set = _Dir, _D, _T, _P) -> + not_supported(Opt); +%% Linux only? +enc_sockopt_key(ip, free_bind = Opt, _Dir, _D, _T, _P) -> + not_supported(Opt); +enc_sockopt_key(ip, hdrincl = Opt, _Dir, _D, raw = _T, _P) -> + not_supported(Opt); +%% FreeBSD only? +enc_sockopt_key(ip, minttl = Opt, _Dir, _D, raw = _T, _P) -> + not_supported(Opt); +enc_sockopt_key(ip, msfilter = Opt, _Dir, _D, _T, _P) -> + not_supported(Opt); +enc_sockopt_key(ip, mtu = Opt, get = _Dir, _D, _T, _P) -> + not_supported(Opt); +enc_sockopt_key(ip, mtu_discover = Opt, _Dir, _D, _T, _P) -> + not_supported(Opt); +enc_sockopt_key(ip, multicast_all = Opt, _Dir, _D, _T, _P) -> + not_supported(Opt); +enc_sockopt_key(ip, multicast_if = Opt, _Dir, _D, _T, _P) -> + not_supported(Opt); +enc_sockopt_key(ip, multicast_loop = Opt, _Dir, _D, _T, _P) -> + not_supported(Opt); +enc_sockopt_key(ip, multicast_ttl = Opt, _Dir, _D, _T, _P) -> + not_supported(Opt); +enc_sockopt_key(ip, nodefrag = Opt, _Dir, _D, raw = _T, _P) -> + not_supported(Opt); +enc_sockopt_key(ip, options = Opt, _Dir, _D, _T, _P) -> + not_supported(Opt); +enc_sockopt_key(ip, pktinfo = Opt, _Dir, _D, _T, _P) -> + not_supported(Opt); +%% This require special code for accessing the errors. +%% via calling the recvmsg with the MSG_ERRQUEUE flag set, +enc_sockopt_key(ip, recverr = Opt, _Dir, _D, _T, _P) -> + not_supported(Opt); +enc_sockopt_key(ip, recvif = Opt, _Dir, _D, dgram = _T, _P) -> + not_supported(Opt); +enc_sockopt_key(ip, recvdstaddr = Opt, _Dir, _D, _T, _P) -> + not_supported(Opt); +enc_sockopt_key(ip, recvopts = Opt, _Dir, _D, T, _P) when (T =/= stream) -> + not_supported(Opt); +enc_sockopt_key(ip, recvorigdstaddr = Opt, _Dir, _D, _T, _P) -> + not_supported(Opt); +enc_sockopt_key(ip, recvtos = _Opt, _Dir, _D, _T, _P) -> + ?SOCKET_OPT_IP_RECVTOS; +enc_sockopt_key(ip, recvttl = Opt, _Dir, _D, T, _P) when (T =/= stream) -> + not_supported(Opt); +enc_sockopt_key(ip, retopts = Opt, _Dir, _D, _T, _P) -> + not_supported(Opt); +enc_sockopt_key(ip, router_alert = Opt, _Dir, _D, raw = _T, _P) -> + not_supported(Opt); +%% On FreeBSD it specifies that this option is only valid +%% for stream, dgram and "some" raw sockets... +%% No such condition on linux (in the man page)... +enc_sockopt_key(ip, tos = _Opt, _Dir, _D, _T, _P) -> + ?SOCKET_OPT_IP_TOS; +enc_sockopt_key(ip, transparent = Opt, _Dir, _D, _T, _P) -> + not_supported(Opt); +enc_sockopt_key(ip, ttl = _Opt, _Dir, _D, _T, _P) -> + ?SOCKET_OPT_IP_TTL; +enc_sockopt_key(ip, unblock_source = Opt, set = _Dir, _D, _T, _P) -> + not_supported(Opt); +enc_sockopt_key(ip, UnknownOpt, _Dir, _D, _T, _P) -> + unknown(UnknownOpt); + +%% IPv6 socket options +enc_sockopt_key(ipv6, hoplimit = _Opt, _Dir, _D, T, _P) + when (T =:= dgram) orelse (T =:= raw) -> + ?SOCKET_OPT_IPV6_HOPLIMIT; +enc_sockopt_key(ipv6, UnknownOpt, _Dir, _D, _T, _P) -> + unknown(UnknownOpt); + +%% TCP socket options +enc_sockopt_key(tcp, UnknownOpt, _Dir, _D, _T, _P) -> + unknown(UnknownOpt); + +%% UDP socket options +enc_sockopt_key(udp, UnknownOpt, _Dir, _D, _T, _P) -> + unknown(UnknownOpt); + +%% SCTP socket options +enc_sockopt_key(sctp, UnknownOpt, _Dir, _D, _T, _P) -> + unknown(UnknownOpt); + +%% +++ Plain socket options +++ +enc_sockopt_key(Level, Opt, _Dir, _D, _T, _P) + when is_integer(Level) andalso is_integer(Opt) -> + Opt; + +enc_sockopt_key(Level, Opt, _Dir, _Domain, _Type, _Protocol) -> + unknown({Level, Opt}). + + + enc_shutdown_how(read) -> ?SOCKET_SHUTDOWN_HOW_READ; enc_shutdown_how(write) -> @@ -1336,6 +1936,21 @@ tdiff(T1, T2) -> +%% =========================================================================== +%% +%% Error functions +%% +%% =========================================================================== + +not_supported(What) -> + error({not_supported, What}). + +unknown(What) -> + error({unknown, What}). + +error(Reason) -> + throw({error, Reason}). + %% =========================================================================== %% @@ -1390,8 +2005,8 @@ nif_shutdown(_SRef, _How) -> nif_finalize_close(_SRef) -> erlang:error(badarg). -nif_setopt(_Ref, _Lev, _Key, _Val) -> +nif_setopt(_Ref, _IsEnc, _Lev, _Key, _Val) -> erlang:error(badarg). -nif_getopt(_Ref, _Lev, _Key) -> +nif_getopt(_Ref, _IsEnc, _Lev, _Key) -> erlang:error(badarg). -- cgit v1.2.3 From bd7cab0e678ef3d327250a7dd7bd86faa685ec06 Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Fri, 4 May 2018 18:49:42 +0200 Subject: [socket-nif] More setopt "Handle" the linger option "fully". We still have a lot of options to handle. We also need to analyze the nif-code for setopt... --- erts/emulator/nifs/common/socket_nif.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/erts/emulator/nifs/common/socket_nif.c b/erts/emulator/nifs/common/socket_nif.c index 532ac5c211..b01f26e4d4 100644 --- a/erts/emulator/nifs/common/socket_nif.c +++ b/erts/emulator/nifs/common/socket_nif.c @@ -3121,6 +3121,18 @@ ERL_NIF_TERM nsetopt_gen(ErlNifEnv* env, } break; + case SOCKET_OPT_VALUE_LINGER: + { + optLen = sizeof(valP->u.lingerVal); + res = socket_setopt(descP->sock, level, opt, + &valP->u.lingerVal, optLen); + if (res != 0) + result = make_error2(env, res); + else + result = atom_ok; + } + break; + default: result = make_error(env, atom_einval); } -- cgit v1.2.3 From 0ab2ce1aad7596e81c1c59f71ccb3b2ad4f7f070 Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Tue, 8 May 2018 10:16:50 +0200 Subject: [socket-nif] Network interface mappings and sock addr Add functions for mapping network interface names and indexes. Also refined the types for socket addresses: in4_sockaddr and in6_sockaddr. --- erts/emulator/nifs/common/socket_nif.c | 209 +++++++++++++++++++++++++++++++++ erts/preloaded/src/socket.erl | 91 ++++++++++++-- 2 files changed, 293 insertions(+), 7 deletions(-) diff --git a/erts/emulator/nifs/common/socket_nif.c b/erts/emulator/nifs/common/socket_nif.c index b01f26e4d4..f4d2fbf021 100644 --- a/erts/emulator/nifs/common/socket_nif.c +++ b/erts/emulator/nifs/common/socket_nif.c @@ -371,6 +371,7 @@ typedef union { #define MKA(E,S) enif_make_atom((E), (S)) #define MKBIN(E,B) enif_make_binary((E), (B)) #define MKI(E,I) enif_make_int((E), (I)) +#define MKLA(E,A,L) enif_make_list_from_array((E), (A), (L)) #define MKREF(E) enif_make_ref((E)) #define MKS(E,S) enif_make_string((E), (S), ERL_NIF_LATIN1) #define MKSL(E,S,L) enif_make_string_len((E), (S), (L), ERL_NIF_LATIN1) @@ -404,6 +405,8 @@ typedef union { enif_get_atom((E), (TE), (BP), (MAX), ERL_NIF_LATIN1) #define GET_BIN(E, TE, BP) enif_inspect_iolist_as_binary((E), (TE), (BP)) #define GET_INT(E, TE, IP) enif_get_int((E), (TE), (IP)) +#define GET_STR(E, L, B, SZ) \ + enif_get_string((E), (L), (B), (SZ), ERL_NIF_LATIN1) #define GET_UINT(E, TE, IP) enif_get_uint((E), (TE), (IP)) #define GET_TUPLE(E, TE, TSZ, TA) enif_get_tuple((E), (TE), (TSZ), (TA)) @@ -700,6 +703,15 @@ static ERL_NIF_TERM nif_setopt(ErlNifEnv* env, static ERL_NIF_TERM nif_getopt(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); +static ERL_NIF_TERM nif_link_if2idx(ErlNifEnv* env, + int argc, + const ERL_NIF_TERM argv[]); +static ERL_NIF_TERM nif_link_idx2if(ErlNifEnv* env, + int argc, + const ERL_NIF_TERM argv[]); +static ERL_NIF_TERM nif_link_ifs(ErlNifEnv* env, + int argc, + const ERL_NIF_TERM argv[]); static ERL_NIF_TERM nif_finalize_connection(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); @@ -778,6 +790,12 @@ static ERL_NIF_TERM nsetopt_gen(ErlNifEnv* env, int level, int opt, SocketOptValue* valP); +static ERL_NIF_TERM nlink_if2idx(ErlNifEnv* env, + char* ifn); +static ERL_NIF_TERM nlink_idx2if(ErlNifEnv* env, + unsigned int id); +static ERL_NIF_TERM nlink_ifs(ErlNifEnv* env); +static unsigned int nlink_ifs_length(struct if_nameindex* p); static ERL_NIF_TERM send_check_result(ErlNifEnv* env, SocketDescriptor* descP, @@ -1123,6 +1141,12 @@ static SocketData socketData; * nif_setopt/3 * nif_getopt/2 * + * And some utility functions: + * ------------------------------------------------------------- + * nif_link_if2idx/1 + * nif_link_idx2if/1 + * nif_link_ifs/0 + * * And some socket admin functions: * ------------------------------------------------------------- * nif_cancel(Sock, Ref) @@ -3663,6 +3687,186 @@ int socket_setopt(int sock, int level, int opt, } + +/* ---------------------------------------------------------------------- + * nif_link_if2idx + * + * Description: + * Perform a Interface Name to Interface Index translation. + * + * Arguments: + * Ifn - Interface name to be translated. + */ + +static +ERL_NIF_TERM nif_link_if2idx(ErlNifEnv* env, + int argc, + const ERL_NIF_TERM argv[]) +{ + ERL_NIF_TERM eifn; + char ifn[IF_NAMESIZE+1]; + + if (argc != 1) { + return enif_make_badarg(env); + } + eifn = argv[0]; + + if (0 >= GET_STR(env, eifn, ifn, sizeof(ifn))) + return make_error2(env, atom_einval); + + return nlink_if2idx(env, ifn); +} + + + +static +ERL_NIF_TERM nlink_if2idx(ErlNifEnv* env, + char* ifn) +{ + unsigned int idx = if_nametoindex(ifn); + + if (idx == 0) + return make_error2(env, sock_errno()); + else + return make_ok2(env, idx); + +} + + + +/* ---------------------------------------------------------------------- + * nif_link_idx2if + * + * Description: + * Perform a Interface Index to Interface Name translation. + * + * Arguments: + * Idx - Interface index to be translated. + */ + +static +ERL_NIF_TERM nif_link_idx2if(ErlNifEnv* env, + int argc, + const ERL_NIF_TERM argv[]) +{ + unsigned int idx; + + if ((argc != 1) || + !GET_UINT(env, argv[0], &idx)) { + return enif_make_badarg(env); + } + + return nlink_idx2if(env, idx); +} + + + +static +ERL_NIF_TERM nlink_idx2if(ErlNifEnv* env, + unsigned int idx) +{ + ERL_NIF_TERM result; + char* ifn = MALLOC(IF_NAMESIZE+1); + + if (ifn == NULL) + return enif_make_badarg(env); // PLACEHOLDER + + if (NULL == if_indextoname(idx, ifn)) { + result = make_ok2(env, MKS(env, ifn)); + } else { + result = make_error2(env, sock_errno()); + } + + FREE(ifn); // OR IS THIS AUTOMATIC? + + return result; +} + + + +/* ---------------------------------------------------------------------- + * nif_link_idx2if + * + * Description: + * Get network interface names and indexes. + * + */ + +static +ERL_NIF_TERM nif_link_ifs(ErlNifEnv* env, + int argc, + const ERL_NIF_TERM argv[]) +{ + if (argc != 0) { + return enif_make_badarg(env); + } + + return nlink_ifs(env); +} + + + +static +ERL_NIF_TERM nlink_ifs(ErlNifEnv* env) +{ + ERL_NIF_TERM result; + struct if_nameindex* ifs = if_nameindex(); + + if (ifs == NULL) { + result = make_error2(env, sock_errno()); + } else { + /* + * We got some interfaces: + * 1) Calculate how many - the only way is to iterate through the list + * until its end (which is indicated by an entry with index = zero + * and if_name = NULL). + * 2) Allocate an ERL_NIF_TERM array of the calculated length. + * 3) Iterate through the array of interfaces and for each create + * a two tuple: {Idx, If} + * + * Or shall we instead build a list in reverse order and then when + * its done, reverse that? Check + */ + unsigned int len = nlink_ifs_length(ifs); + + if (len > 0) { + ERL_NIF_TERM* array = MALLOC(len * sizeof(ERL_NIF_TERM)); + unsigned int i; + + for (i = 0; i < len; i++) { + array[i] = MKT2(env, + MKI(env, ifs[i].if_index), + MKS(env, ifs[i].if_name)); + } + + result = make_ok2(env, MKLA(env, array, len)); + FREE(array); + } else { + result = make_ok2(env, enif_make_list(env, 0)); + } + } + + if (ifs != NULL) + if_freenameindex(ifs); + + return result; +} + + +static +unsigned int nlink_ifs_length(struct if_nameindex* p) +{ + unsigned int len = 0; + + while ((p[len].if_index == 0) && (p[len].if_name == NULL)) { + len++; + } + + return len; +} + + + /* ---------------------------------------------------------------------- * U t i l i t y F u n c t i o n s * ---------------------------------------------------------------------- @@ -5202,6 +5406,11 @@ ErlNifFunc socket_funcs[] = {"nif_setopt", 3, nif_setopt, 0}, {"nif_getopt", 2, nif_getopt, 0}, + /* Misc utility functions */ + {"nif_link_if2idx", 1, nif_link_if2idx, 0}, + {"nif_link_idx2if", 1, nif_link_idx2if, 0}, + {"nif_link_ifs", 0, nif_link_ifs, 0}, + /* "Extra" functions to "complete" the socket interface. * For instance, the function nif_finalize_connection * is called after the connect *select* has "completed". diff --git a/erts/preloaded/src/socket.erl b/erts/preloaded/src/socket.erl index 1090380769..5bea039783 100644 --- a/erts/preloaded/src/socket.erl +++ b/erts/preloaded/src/socket.erl @@ -49,7 +49,12 @@ shutdown/2, setopt/4, - getopt/3 + getopt/3, + + %% Some IPv6 utility functions + link_if2idx/1, + link_idx2if/1, + link_ifs/0 ]). -export_type([ @@ -61,6 +66,8 @@ ip_address/0, ip4_address/0, ip6_address/0, + in_sockaddr/0, + in4_sockaddr/0, in6_sockaddr/0, port_number/0, @@ -98,9 +105,10 @@ -type ip4_address() :: {0..255, 0..255, 0..255, 0..255}. +-type uint20() :: 0..16#FFFFF. -type uint32() :: 0..16#FFFFFFFF. --type ip6_flow_info() :: uint32(). --type ip6_scope_id() :: uint32(). +-type in6_flow_info() :: uint20(). +-type in6_scope_id() :: uint32(). -type ip6_address() :: {0..65535, @@ -111,12 +119,23 @@ 0..65535, 0..65535, 0..65535}. -%% We need to polish this further... --record(in6_sockaddr, {addr :: ip6_address(), - flowinfo = 0 :: ip6_flow_info(), - scope_id :: ip6_scope_id()}). + +%% +%% Should we do these as maps instead? +%% If we do we may need to include the family (domain) in the +%% map (as the native type do. See struct sockaddr_in6). +%% +-record(in4_sockaddr, {port = 0 :: port_number(), + addr = any :: any | loopback | ip4_address()}). +-type in4_sockaddr() :: #in4_sockaddr{}. +-record(in6_sockaddr, {port = 0 :: port_number(), + addr = any :: any | loopback | ip6_address(), + flowinfo = 0 :: in6_flow_info(), + scope_id = 0 :: in6_scope_id()}). -type in6_sockaddr() :: #in6_sockaddr{}. +-type in_sockaddr() :: in4_sockaddr() | in6_sockaddr(). + -type port_number() :: 0..65535. %% otp - The option is internal to our (OTP) imeplementation. @@ -1406,6 +1425,55 @@ getopt(#socket{info = Info, ref = SockRef}, Level, Key) -> +%% =========================================================================== +%% +%% link_if2idx - Mappings between network interface names and indexes: if -> idx +%% +%% + +-spec link_if2idx(If) -> {ok, Idx} | {error, Reason} when + If :: string(), + Idx :: non_neg_integer(), + Reason :: term(). + +link_if2idx(If) when is_list(If) -> + nif_link_if2idx(If). + + + +%% =========================================================================== +%% +%% link_idx2if - Mappings between network interface names and indexes: idx -> if +%% +%% + +-spec link_idx2if(Idx) -> {ok, If} | {error, Reason} when + Idx :: non_neg_integer(), + If :: string(), + Reason :: term(). + +link_idx2if(Idx) when is_integer(Idx) -> + nif_link_idx2if(Idx). + + + +%% =========================================================================== +%% +%% link_ifs - get network interface names and indexes +%% +%% + +-spec link_ifs() -> Names | {error, Reason} when + Names :: [{Idx, If}], + Idx :: non_neg_integer(), + If :: string(), + Reason :: term(). + +link_ifs() -> + nif_link_ifs(). + + + %% =========================================================================== %% %% Encode / decode @@ -2010,3 +2078,12 @@ nif_setopt(_Ref, _IsEnc, _Lev, _Key, _Val) -> nif_getopt(_Ref, _IsEnc, _Lev, _Key) -> erlang:error(badarg). + +nif_link_if2idx(_Name) -> + erlang:error(badarg). + +nif_link_idx2if(_Id) -> + erlang:error(badarg). + +nif_link_ifs() -> + erlang:error(badarg). -- cgit v1.2.3 From f7f70b94f90b1f500cb884be8ae722e1a22acf2e Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Tue, 8 May 2018 11:47:43 +0200 Subject: [socket-nif] setopt of (tcp) congestion --- erts/emulator/nifs/common/socket_nif.c | 54 +++++++++++++++++++++++++++++++++- erts/preloaded/src/socket.erl | 28 ++++++++++++++---- 2 files changed, 76 insertions(+), 6 deletions(-) diff --git a/erts/emulator/nifs/common/socket_nif.c b/erts/emulator/nifs/common/socket_nif.c index f4d2fbf021..11085ddab5 100644 --- a/erts/emulator/nifs/common/socket_nif.c +++ b/erts/emulator/nifs/common/socket_nif.c @@ -236,6 +236,14 @@ typedef unsigned long long llu_t; /* *** Misc macros and defines *** */ +#if defined(TCP_CA_NAME_MAX) +#define SOCKET_OPT_TCP_CONGESTION_NAME_MAX TCP_CA_NAME_MAX +#else +/* This is really excessive, but just in case... */ +#define SOCKET_OPT_TCP_CONGESTION_NAME_MAX 256 +#endif + + /* *** Socket state defs *** */ #define SOCKET_FLAG_OPEN 0x0001 @@ -349,14 +357,19 @@ typedef union { #define SOCKET_OPT_OTP_DEBUG 0 #define SOCKET_OPT_OTP_IOW 1 + #define SOCKET_OPT_SOCK_KEEPALIVE 0 #define SOCKET_OPT_SOCK_LINGER 1 + #define SOCKET_OPT_IP_RECVTOS 0 #define SOCKET_OPT_IP_ROUTER_ALERT 1 #define SOCKET_OPT_IP_TOS 2 #define SOCKET_OPT_IP_TTL 3 + #define SOCKET_OPT_IPV6_HOPLIMIT 0 -#define SOCKET_OPT_TCP_MAXSEG 0 + +#define SOCKET_OPT_TCP_CONGESTION 0 +#define SOCKET_OPT_TCP_MAXSEG 1 /* =================================================================== * @@ -601,6 +614,7 @@ typedef struct { #define SOCKET_OPT_VALUE_INT 2 #define SOCKET_OPT_VALUE_LINGER 3 #define SOCKET_OPT_VALUE_BIN 4 +#define SOCKET_OPT_VALUE_STR 5 typedef struct { unsigned int tag; @@ -609,6 +623,10 @@ typedef struct { int intVal; struct linger lingerVal; ErlNifBinary binVal; + struct { + unsigned int len; + char* str; + } strVal; } u; /* void* optValP; // Points to the actual data (above) @@ -3157,6 +3175,18 @@ ERL_NIF_TERM nsetopt_gen(ErlNifEnv* env, } break; + case SOCKET_OPT_VALUE_STR: + { + optLen = valP->u.strVal.len; + res = socket_setopt(descP->sock, level, opt, + valP->u.strVal.str, optLen); + if (res != 0) + result = make_error2(env, res); + else + result = atom_ok; + } + break; + default: result = make_error(env, atom_einval); } @@ -3531,6 +3561,26 @@ BOOLEAN_T eoptval2optval_tcp(ErlNifEnv* env, SocketOptValue* valP) { switch (eOpt) { +#if defined(TCP_CONGESTION) + case SOCKET_OPT_TCP_CONGESTION: + { + valP->u.strVal.len = SOCKET_OPT_TCP_CONGESTION_NAME_MAX+1; + valP->u.strVal.str = MALLOC(valP->u.strVal.len); + + if (GET_STR(env, eVal, valP->u.strVal.str, valP->u.strVal.len) > 0) { + valP->tag = SOCKET_OPT_VALUE_STR; + *opt = TCP_CONGESTION; + return TRUE; + } else { + FREE(valP->u.strVal.str); + *opt = -1; + valP->tag = SOCKET_OPT_VALUE_UNDEF; + return FALSE; + } + } + break; +#endif + #if defined(TCP_MAXSEG) case SOCKET_OPT_TCP_MAXSEG: if (!GET_INT(env, eVal, &valP->u.intVal)) { @@ -3538,6 +3588,8 @@ BOOLEAN_T eoptval2optval_tcp(ErlNifEnv* env, *opt = TCP_MAXSEG; return TRUE; } else { + *opt = -1; + valP->tag = SOCKET_OPT_VALUE_UNDEF; return FALSE; } break; diff --git a/erts/preloaded/src/socket.erl b/erts/preloaded/src/socket.erl index 5bea039783..1304e79c99 100644 --- a/erts/preloaded/src/socket.erl +++ b/erts/preloaded/src/socket.erl @@ -267,8 +267,7 @@ -type tcp_socket_option() :: congestion | maxseg | - nodelay | - user_timeout. + nodelay. -type udp_socket_option() :: checksum | maxdgram | @@ -440,7 +439,8 @@ -define(SOCKET_OPT_IPV6_HOPLIMIT, 0). --define(SOCKET_OPT_TCP_MAXSEG, 0). +-define(SOCKET_OPT_TCP_CONGESTION, 0). +-define(SOCKET_OPT_TCP_MAXSEG, 1). -define(SOCKET_SHUTDOWN_HOW_READ, 0). -define(SOCKET_SHUTDOWN_HOW_WRITE, 1). @@ -1633,11 +1633,21 @@ enc_setopt_value(ipv6, hoplimit, V, _D, T, _P) enc_setopt_value(ipv6 = L, Opt, V, _D, _T, _P) -> not_supported({L, Opt, V}); +enc_setopt_value(tcp, congetsion, V, _D, T, P) + when is_list(V) andalso + (T =:= stream) andalso + (P =:= tcp) -> + V; enc_setopt_value(tcp, maxseg, V, _D, T, P) when is_integer(V) andalso (T =:= stream) andalso (P =:= tcp) -> V; +enc_setopt_value(tcp, nodelay, V, _D, T, P) + when is_boolean(V) andalso + (T =:= stream) andalso + (P =:= tcp) -> + V; enc_setopt_value(tcp = L, Opt, V, _D, _T, _P) -> not_supported({L, Opt, V}); @@ -1880,8 +1890,8 @@ enc_sockopt_key(ip, recvttl = Opt, _Dir, _D, T, _P) when (T =/= stream) -> not_supported(Opt); enc_sockopt_key(ip, retopts = Opt, _Dir, _D, _T, _P) -> not_supported(Opt); -enc_sockopt_key(ip, router_alert = Opt, _Dir, _D, raw = _T, _P) -> - not_supported(Opt); +enc_sockopt_key(ip, router_alert = _Opt, _Dir, _D, raw = _T, _P) -> + ?SOCKET_OPT_IP_ROUTER_ALERT; %% On FreeBSD it specifies that this option is only valid %% for stream, dgram and "some" raw sockets... %% No such condition on linux (in the man page)... @@ -1904,6 +1914,14 @@ enc_sockopt_key(ipv6, UnknownOpt, _Dir, _D, _T, _P) -> unknown(UnknownOpt); %% TCP socket options +%% There are other options that would be useful; info, +%% but they are difficult to get portable... +enc_sockopt_key(tcp, congestion = _Opt, _Dir, _D, _T, _P) -> + ?SOCKET_OPT_TCP_CONGESTION; +enc_sockopt_key(tcp, maxseg = _Opt, _Dir, _D, _T, _P) -> + ?SOCKET_OPT_TCP_MAXSEG; +enc_sockopt_key(tcp, nodelay = Opt, _Dir, _D, _T, _P) -> + not_supported(Opt); enc_sockopt_key(tcp, UnknownOpt, _Dir, _D, _T, _P) -> unknown(UnknownOpt); -- cgit v1.2.3 From fece5e536ba4e814bcfe5896e23c2ef979468c7b Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Tue, 8 May 2018 12:16:11 +0200 Subject: [socket-nif] More setopt Add handling of nodelay tcp option setopt. Added placeholder sctp options autoclose and nodelay (since my machine does not actually have sctp installed...). --- erts/emulator/nifs/common/socket_nif.c | 64 +++++++++++++++++++++++++++------- erts/preloaded/src/socket.erl | 26 +++++++++++--- 2 files changed, 73 insertions(+), 17 deletions(-) diff --git a/erts/emulator/nifs/common/socket_nif.c b/erts/emulator/nifs/common/socket_nif.c index 11085ddab5..9887047135 100644 --- a/erts/emulator/nifs/common/socket_nif.c +++ b/erts/emulator/nifs/common/socket_nif.c @@ -366,10 +366,14 @@ typedef union { #define SOCKET_OPT_IP_TOS 2 #define SOCKET_OPT_IP_TTL 3 -#define SOCKET_OPT_IPV6_HOPLIMIT 0 +#define SOCKET_OPT_IPV6_HOPLIMIT 12 #define SOCKET_OPT_TCP_CONGESTION 0 #define SOCKET_OPT_TCP_MAXSEG 1 +#define SOCKET_OPT_TCP_NODELAY 2 + +#define SOCKET_OPT_SCTP_AUTOCLOSE 7 +#define SOCKET_OPT_SCTP_NODELAY 22 /* =================================================================== * @@ -3344,11 +3348,13 @@ BOOLEAN_T eoptval2optval_otp(ErlNifEnv* env, { if (decode_bool(env, eVal, &valP->u.boolVal)) { valP->tag = SOCKET_OPT_VALUE_BOOL; - result = TRUE; + *opt = eOpt; + result = TRUE; } else { - result = FALSE; + valP->tag = SOCKET_OPT_VALUE_UNDEF; + *opt = -1; + result = FALSE; } - *opt = eOpt; } break; @@ -3582,14 +3588,28 @@ BOOLEAN_T eoptval2optval_tcp(ErlNifEnv* env, #endif #if defined(TCP_MAXSEG) - case SOCKET_OPT_TCP_MAXSEG: - if (!GET_INT(env, eVal, &valP->u.intVal)) { - valP->tag = SOCKET_OPT_VALUE_INT; - *opt = TCP_MAXSEG; + case SOCKET_OPT_TCP_MAXSEG: + if (!GET_INT(env, eVal, &valP->u.intVal)) { + valP->tag = SOCKET_OPT_VALUE_INT; + *opt = TCP_MAXSEG; + return TRUE; + } else { + *opt = -1; + valP->tag = SOCKET_OPT_VALUE_UNDEF; + return FALSE; + } + break; +#endif + +#if defined(TCP_NODELAY) + case SOCKET_OPT_TCP_NODELAY: + if (decode_bool(env, eVal, &valP->u.boolVal)) { + valP->tag = SOCKET_OPT_VALUE_BOOL; + *opt = TCP_NODELAY; return TRUE; } else { - *opt = -1; valP->tag = SOCKET_OPT_VALUE_UNDEF; + *opt = -1; return FALSE; } break; @@ -3636,11 +3656,29 @@ BOOLEAN_T eoptval2optval_sctp(ErlNifEnv* env, switch (eOpt) { #if defined(SCTP_AUTOCLOSE) case SOCKET_OPT_SCTP_AUTOCLOSE: - if (!GET_INT(env, eVal, &valP->u.intVal)) + if (GET_INT(env, eVal, &valP->u.intVal)) { + valP->tag = SOCKET_OPT_VALUE_INT; + *opt = SCTP_AUTOCLOSE; + return TRUE; + } else { + valP->tag = SOCKET_OPT_VALUE_UNDEF; + *opt = -1; return FALSE; // PLACEHOLDER - We should really be more informative - valP->tag = SOCKET_OPT_VALUE_INT; - *opt = SCTP_AUTOCLOSE; - return TRUE; + } + break; +#endif + +#if defined(SCTP_NODELAY) + case SOCKET_OPT_SCTP_NODELAY: + if (decode_bool(env, eVal, &valP->u.boolVal)) { + valP->tag = SOCKET_OPT_VALUE_BOOL; + *opt = SCTP_NODELAY; + return TRUE; + } else { + valP->tag = SOCKET_OPT_VALUE_UNDEF; + *opt = -1; + return FALSE; + } break; #endif diff --git a/erts/preloaded/src/socket.erl b/erts/preloaded/src/socket.erl index 1304e79c99..1c811fa11a 100644 --- a/erts/preloaded/src/socket.erl +++ b/erts/preloaded/src/socket.erl @@ -233,10 +233,11 @@ unblock_source. -type ipv6_socket_option() :: addform | - add_membership | drop_membership | + add_membership | authhdr | auth_level | checksum | + drop_membership | dstopts | esp_trans_level | esp_network_level | @@ -437,10 +438,14 @@ -define(SOCKET_OPT_IP_TOS, 2). -define(SOCKET_OPT_IP_TTL, 3). --define(SOCKET_OPT_IPV6_HOPLIMIT, 0). +-define(SOCKET_OPT_IPV6_HOPLIMIT, 12). -define(SOCKET_OPT_TCP_CONGESTION, 0). -define(SOCKET_OPT_TCP_MAXSEG, 1). +-define(SOCKET_OPT_TCP_NODELAY, 2). + +-define(SOCKET_OPT_SCTP_AUTOCLOSE, 7). +-define(SOCKET_OPT_SCTP_NODELAY, 22). -define(SOCKET_SHUTDOWN_HOW_READ, 0). -define(SOCKET_SHUTDOWN_HOW_WRITE, 1). @@ -1654,6 +1659,15 @@ enc_setopt_value(tcp = L, Opt, V, _D, _T, _P) -> enc_setopt_value(udp = L, Opt, _V, _D, _T, _P) -> not_supported({L, Opt}); +enc_setopt_value(sctp, autoclose, V, _D, _T, P) + when is_integer(V) andalso + (P =:= sctp) -> + V; +enc_setopt_value(sctp, nodelay, V, _D, _T, P) + when is_boolean(V) andalso + (P =:= sctp) -> + V; + enc_setopt_value(L, Opt, V, _, _, _) when is_integer(L) andalso is_integer(Opt) andalso is_binary(V) -> V. @@ -1920,8 +1934,8 @@ enc_sockopt_key(tcp, congestion = _Opt, _Dir, _D, _T, _P) -> ?SOCKET_OPT_TCP_CONGESTION; enc_sockopt_key(tcp, maxseg = _Opt, _Dir, _D, _T, _P) -> ?SOCKET_OPT_TCP_MAXSEG; -enc_sockopt_key(tcp, nodelay = Opt, _Dir, _D, _T, _P) -> - not_supported(Opt); +enc_sockopt_key(tcp, nodelay = _Opt, _Dir, _D, _T, _P) -> + ?SOCKET_OPT_TCP_NODELAY; enc_sockopt_key(tcp, UnknownOpt, _Dir, _D, _T, _P) -> unknown(UnknownOpt); @@ -1930,6 +1944,10 @@ enc_sockopt_key(udp, UnknownOpt, _Dir, _D, _T, _P) -> unknown(UnknownOpt); %% SCTP socket options +enc_sockopt_key(sctp, autoclose = _Opt, _Dir, _D, _T, _P) -> + ?SOCKET_OPT_SCTP_AUTOCLOSE; +enc_sockopt_key(sctp, nodelay = _Opt, _Dir, _D, _T, _P) -> + ?SOCKET_OPT_SCTP_NODELAY; enc_sockopt_key(sctp, UnknownOpt, _Dir, _D, _T, _P) -> unknown(UnknownOpt); -- cgit v1.2.3 From d6b051d9b09aeba00d1bbd0d448dde6e551c4442 Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Tue, 8 May 2018 12:36:35 +0200 Subject: [socket-nif] More setopt - udp Add setopt for one udp option: cork. This is not platform independent. Aas fas as I know, it only works on linux, so for now this serves as a placeholder. --- erts/emulator/nifs/common/socket_nif.c | 21 +++++++++++++++++++-- erts/preloaded/src/socket.erl | 16 ++++++++++++---- 2 files changed, 31 insertions(+), 6 deletions(-) diff --git a/erts/emulator/nifs/common/socket_nif.c b/erts/emulator/nifs/common/socket_nif.c index 9887047135..14d7cd2611 100644 --- a/erts/emulator/nifs/common/socket_nif.c +++ b/erts/emulator/nifs/common/socket_nif.c @@ -133,6 +133,7 @@ #endif #include +#include #include #include @@ -372,6 +373,8 @@ typedef union { #define SOCKET_OPT_TCP_MAXSEG 1 #define SOCKET_OPT_TCP_NODELAY 2 +#define SOCKET_OPT_UDP_CORK 0 + #define SOCKET_OPT_SCTP_AUTOCLOSE 7 #define SOCKET_OPT_SCTP_NODELAY 22 @@ -3625,8 +3628,8 @@ BOOLEAN_T eoptval2optval_tcp(ErlNifEnv* env, /* +++ decode UDP socket options +++ - * Currently there are no such options, so this function - * is just a placeholder! + * Currently there are only one option, cork, and that may only + * work on linux... */ static BOOLEAN_T eoptval2optval_udp(ErlNifEnv* env, @@ -3636,6 +3639,20 @@ BOOLEAN_T eoptval2optval_udp(ErlNifEnv* env, SocketOptValue* valP) { switch (eOpt) { +#if defined(UDP_CORK) + case SOCKET_OPT_UDP_CORK: + if (decode_bool(env, eVal, &valP->u.boolVal)) { + valP->tag = SOCKET_OPT_VALUE_BOOL; + *opt = UDP_CORK; + return TRUE; + } else { + valP->tag = SOCKET_OPT_VALUE_UNDEF; + *opt = -1; + return FALSE; + } + break; +#endif + default: *opt = -1; valP->tag = SOCKET_OPT_VALUE_UNDEF; diff --git a/erts/preloaded/src/socket.erl b/erts/preloaded/src/socket.erl index 1c811fa11a..4a9de0dc9e 100644 --- a/erts/preloaded/src/socket.erl +++ b/erts/preloaded/src/socket.erl @@ -270,9 +270,8 @@ maxseg | nodelay. --type udp_socket_option() :: checksum | - maxdgram | - recvspace. +-type udp_socket_option() :: cork. + -type sctp_socket_option() :: adaption_layer | associnfo | @@ -444,6 +443,8 @@ -define(SOCKET_OPT_TCP_MAXSEG, 1). -define(SOCKET_OPT_TCP_NODELAY, 2). +-define(SOCKET_OPT_UDP_CORK, 1). + -define(SOCKET_OPT_SCTP_AUTOCLOSE, 7). -define(SOCKET_OPT_SCTP_NODELAY, 22). @@ -1638,7 +1639,7 @@ enc_setopt_value(ipv6, hoplimit, V, _D, T, _P) enc_setopt_value(ipv6 = L, Opt, V, _D, _T, _P) -> not_supported({L, Opt, V}); -enc_setopt_value(tcp, congetsion, V, _D, T, P) +enc_setopt_value(tcp, congestion, V, _D, T, P) when is_list(V) andalso (T =:= stream) andalso (P =:= tcp) -> @@ -1656,6 +1657,11 @@ enc_setopt_value(tcp, nodelay, V, _D, T, P) enc_setopt_value(tcp = L, Opt, V, _D, _T, _P) -> not_supported({L, Opt, V}); +enc_setopt_value(udp, cork, V, _D, T, P) + when is_boolean(V) andalso + (T =:= dgram) andalso + (P =:= udp) -> + V; enc_setopt_value(udp = L, Opt, _V, _D, _T, _P) -> not_supported({L, Opt}); @@ -1940,6 +1946,8 @@ enc_sockopt_key(tcp, UnknownOpt, _Dir, _D, _T, _P) -> unknown(UnknownOpt); %% UDP socket options +enc_sockopt_key(udp, cork = _Opt, _Dir, _D, _T, _P) -> + ?SOCKET_OPT_UDP_CORK; enc_sockopt_key(udp, UnknownOpt, _Dir, _D, _T, _P) -> unknown(UnknownOpt); -- cgit v1.2.3 From 7bc2d3e8618f561185694ae11afe4fc83b1f72f3 Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Tue, 8 May 2018 12:58:56 +0200 Subject: [socket-nif] setopt of socket option reuseaddr --- erts/emulator/nifs/common/socket_nif.c | 52 +++++++++++++++++++++++----------- erts/preloaded/src/socket.erl | 18 ++++++++---- 2 files changed, 48 insertions(+), 22 deletions(-) diff --git a/erts/emulator/nifs/common/socket_nif.c b/erts/emulator/nifs/common/socket_nif.c index 14d7cd2611..843543b42b 100644 --- a/erts/emulator/nifs/common/socket_nif.c +++ b/erts/emulator/nifs/common/socket_nif.c @@ -359,8 +359,9 @@ typedef union { #define SOCKET_OPT_OTP_DEBUG 0 #define SOCKET_OPT_OTP_IOW 1 -#define SOCKET_OPT_SOCK_KEEPALIVE 0 -#define SOCKET_OPT_SOCK_LINGER 1 +#define SOCKET_OPT_SOCK_KEEPALIVE 9 +#define SOCKET_OPT_SOCK_LINGER 10 +#define SOCKET_OPT_SOCK_REUSEADDR 21 #define SOCKET_OPT_IP_RECVTOS 0 #define SOCKET_OPT_IP_ROUTER_ALERT 1 @@ -3396,22 +3397,22 @@ BOOLEAN_T eoptval2optval_socket(ErlNifEnv* env, { switch (eOpt) { #if defined(SO_KEEPALIVE) - case SOCKET_OPT_SOCK_KEEPALIVE: - { - BOOLEAN_T val; + case SOCKET_OPT_SOCK_KEEPALIVE: + { + BOOLEAN_T val; - if (decode_bool(env, eVal, &val)) { - *opt = SO_KEEPALIVE; - valP->tag = SOCKET_OPT_VALUE_INT; - valP->u.intVal = (val) ? 1 : 0; - return TRUE; - } else { - *opt = -1; - valP->tag = SOCKET_OPT_VALUE_UNDEF; - return FALSE; - } + if (decode_bool(env, eVal, &val)) { + *opt = SO_KEEPALIVE; + valP->tag = SOCKET_OPT_VALUE_INT; + valP->u.intVal = (val) ? 1 : 0; + return TRUE; + } else { + *opt = -1; + valP->tag = SOCKET_OPT_VALUE_UNDEF; + return FALSE; } - break; + } + break; #endif #if defined(SO_LINGER) @@ -3430,6 +3431,25 @@ BOOLEAN_T eoptval2optval_socket(ErlNifEnv* env, break; #endif +#if defined(SO_REUSEADDR) + case SOCKET_OPT_SOCK_REUSEADDR: + { + BOOLEAN_T val; + + if (decode_bool(env, eVal, &val)) { + *opt = SO_REUSEADDR; + valP->tag = SOCKET_OPT_VALUE_INT; + valP->u.intVal = (val) ? 1 : 0; + return TRUE; + } else { + *opt = -1; + valP->tag = SOCKET_OPT_VALUE_UNDEF; + return FALSE; + } + } + break; +#endif + default: *opt = -1; valP->tag = SOCKET_OPT_VALUE_UNDEF; diff --git a/erts/preloaded/src/socket.erl b/erts/preloaded/src/socket.erl index 4a9de0dc9e..6b245c41f0 100644 --- a/erts/preloaded/src/socket.erl +++ b/erts/preloaded/src/socket.erl @@ -183,16 +183,19 @@ priority | rcvbuf | rcvbufforce | - rcvlowat | sndlowat | - rcvtimeo | sndtimeo | + rcvlowat | + rcvtimeo | reuseaddr | reuseport | rxq_ovfl | + sndlowat | + sndtimeo | setfib | sndbuf | sndbufforce | timestamp | type. + %% Read-only options: %% mtu %% @@ -429,8 +432,9 @@ -define(SOCKET_OPT_OTP_DEBUG, 0). -define(SOCKET_OPT_OTP_IOW, 1). --define(SOCKET_OPT_SOCK_KEEPALIVE, 0). --define(SOCKET_OPT_SOCK_LINGER, 1). +-define(SOCKET_OPT_SOCK_KEEPALIVE, 9). +-define(SOCKET_OPT_SOCK_LINGER, 10). +-define(SOCKET_OPT_SOCK_REUSEADDR, 21). -define(SOCKET_OPT_IP_RECVTOS, 0). -define(SOCKET_OPT_IP_ROUTER_ALERT, 1). @@ -1611,6 +1615,8 @@ enc_setopt_value(socket, linger, abort, D, T, P) -> enc_setopt_value(socket, linger, {OnOff, Secs} = V, _D, _T, _P) when is_boolean(OnOff) andalso is_integer(Secs) andalso (Secs >= 0) -> V; +enc_setopt_value(socket, reuseaddr, V, _D, _T, _P) when is_boolean(V) -> + V; enc_setopt_value(socket = L, Opt, V, _D, _T, _P) -> not_supported({L, Opt, V}); @@ -1826,8 +1832,8 @@ enc_sockopt_key(socket, rcvlowat = Opt, _Dir, _D, _T, _P) -> not_supported(Opt); enc_sockopt_key(socket, rcvtimeo = Opt, _Dir, _D, _T, _P) -> not_supported(Opt); -enc_sockopt_key(socket, reuseaddr = Opt, _Dir, _D, _T, _P) -> - not_supported(Opt); +enc_sockopt_key(socket, reuseaddr = _Opt, _Dir, _D, _T, _P) -> + ?SOCKET_OPT_SOCK_REUSEADDR; enc_sockopt_key(socket, reuseport = Opt, _Dir, D, _T, _P) when ((D =:= inet) orelse (D =:= inet6)) -> not_supported(Opt); -- cgit v1.2.3 From 56641fca11c50306c6c62266844f7828e325ae7d Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Tue, 8 May 2018 15:26:41 +0200 Subject: [socket-nif] setopt of socket option priority --- erts/emulator/nifs/common/socket_nif.c | 15 +++++++++++++++ erts/preloaded/src/socket.erl | 7 +++++-- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/erts/emulator/nifs/common/socket_nif.c b/erts/emulator/nifs/common/socket_nif.c index 843543b42b..905e04848c 100644 --- a/erts/emulator/nifs/common/socket_nif.c +++ b/erts/emulator/nifs/common/socket_nif.c @@ -361,6 +361,7 @@ typedef union { #define SOCKET_OPT_SOCK_KEEPALIVE 9 #define SOCKET_OPT_SOCK_LINGER 10 +#define SOCKET_OPT_SOCK_PRIORITY 16 #define SOCKET_OPT_SOCK_REUSEADDR 21 #define SOCKET_OPT_IP_RECVTOS 0 @@ -3431,6 +3432,20 @@ BOOLEAN_T eoptval2optval_socket(ErlNifEnv* env, break; #endif +#if defined(SO_PRIORITY) + case SOCKET_OPT_SOCK_PRIORITY: + if (GET_INT(env, eVal, &valP->u.intVal)) { + valP->tag = SOCKET_OPT_VALUE_INT; + *opt = SO_PRIORITY; + return TRUE; + } else { + *opt = -1; + valP->tag = SOCKET_OPT_VALUE_UNDEF; + return FALSE; + } + break; +#endif + #if defined(SO_REUSEADDR) case SOCKET_OPT_SOCK_REUSEADDR: { diff --git a/erts/preloaded/src/socket.erl b/erts/preloaded/src/socket.erl index 6b245c41f0..4c903bb759 100644 --- a/erts/preloaded/src/socket.erl +++ b/erts/preloaded/src/socket.erl @@ -434,6 +434,7 @@ -define(SOCKET_OPT_SOCK_KEEPALIVE, 9). -define(SOCKET_OPT_SOCK_LINGER, 10). +-define(SOCKET_OPT_SOCK_PRIORITY, 16). -define(SOCKET_OPT_SOCK_REUSEADDR, 21). -define(SOCKET_OPT_IP_RECVTOS, 0). @@ -1615,6 +1616,8 @@ enc_setopt_value(socket, linger, abort, D, T, P) -> enc_setopt_value(socket, linger, {OnOff, Secs} = V, _D, _T, _P) when is_boolean(OnOff) andalso is_integer(Secs) andalso (Secs >= 0) -> V; +enc_setopt_value(socket, priority, V, _D, _T, _P) when is_integer(V) -> + V; enc_setopt_value(socket, reuseaddr, V, _D, _T, _P) when is_boolean(V) -> V; enc_setopt_value(socket = L, Opt, V, _D, _T, _P) -> @@ -1821,8 +1824,8 @@ enc_sockopt_key(socket, peek_off = Opt, _Dir, local = _D, _T, _P) -> not_supported(Opt); enc_sockopt_key(socket, peek_cred = Opt, get = _Dir, _D, _T, _P) -> not_supported(Opt); -enc_sockopt_key(socket, priority = Opt, _Dir, _D, _T, _P) -> - not_supported(Opt); +enc_sockopt_key(socket, priority = _Opt, _Dir, _D, _T, _P) -> + ?SOCKET_OPT_SOCK_PRIORITY; enc_sockopt_key(socket, rcvbuf = Opt, _Dir, _D, _T, _P) -> not_supported(Opt); enc_sockopt_key(socket, rcvbufforce = Opt, _Dir, _D, _T, _P) -> -- cgit v1.2.3 From 3d719906dd0ad8d07547c3a20a2190a416dda364 Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Tue, 8 May 2018 15:40:07 +0200 Subject: [socket-nif] setopt of socket option dontroute --- erts/emulator/nifs/common/socket_nif.c | 60 ++++++++++++++++++++++------------ erts/preloaded/src/socket.erl | 13 ++++---- 2 files changed, 47 insertions(+), 26 deletions(-) diff --git a/erts/emulator/nifs/common/socket_nif.c b/erts/emulator/nifs/common/socket_nif.c index 905e04848c..d8e274341f 100644 --- a/erts/emulator/nifs/common/socket_nif.c +++ b/erts/emulator/nifs/common/socket_nif.c @@ -348,36 +348,37 @@ typedef union { #define SOCKET_SHUTDOWN_HOW_RDWR 2 -#define SOCKET_OPT_LEVEL_OTP 0 -#define SOCKET_OPT_LEVEL_SOCKET 1 -#define SOCKET_OPT_LEVEL_IP 2 -#define SOCKET_OPT_LEVEL_IPV6 3 -#define SOCKET_OPT_LEVEL_TCP 4 -#define SOCKET_OPT_LEVEL_UDP 5 -#define SOCKET_OPT_LEVEL_SCTP 6 - -#define SOCKET_OPT_OTP_DEBUG 0 -#define SOCKET_OPT_OTP_IOW 1 - +#define SOCKET_OPT_LEVEL_OTP 0 +#define SOCKET_OPT_LEVEL_SOCKET 1 +#define SOCKET_OPT_LEVEL_IP 2 +#define SOCKET_OPT_LEVEL_IPV6 3 +#define SOCKET_OPT_LEVEL_TCP 4 +#define SOCKET_OPT_LEVEL_UDP 5 +#define SOCKET_OPT_LEVEL_SCTP 6 + +#define SOCKET_OPT_OTP_DEBUG 0 +#define SOCKET_OPT_OTP_IOW 1 + +#define SOCKET_OPT_SOCK_DONTROUTE 7 #define SOCKET_OPT_SOCK_KEEPALIVE 9 #define SOCKET_OPT_SOCK_LINGER 10 #define SOCKET_OPT_SOCK_PRIORITY 16 #define SOCKET_OPT_SOCK_REUSEADDR 21 -#define SOCKET_OPT_IP_RECVTOS 0 -#define SOCKET_OPT_IP_ROUTER_ALERT 1 -#define SOCKET_OPT_IP_TOS 2 -#define SOCKET_OPT_IP_TTL 3 +#define SOCKET_OPT_IP_RECVTOS 25 +#define SOCKET_OPT_IP_ROUTER_ALERT 28 +#define SOCKET_OPT_IP_TOS 30 +#define SOCKET_OPT_IP_TTL 32 #define SOCKET_OPT_IPV6_HOPLIMIT 12 -#define SOCKET_OPT_TCP_CONGESTION 0 -#define SOCKET_OPT_TCP_MAXSEG 1 -#define SOCKET_OPT_TCP_NODELAY 2 +#define SOCKET_OPT_TCP_CONGESTION 0 +#define SOCKET_OPT_TCP_MAXSEG 1 +#define SOCKET_OPT_TCP_NODELAY 2 -#define SOCKET_OPT_UDP_CORK 0 +#define SOCKET_OPT_UDP_CORK 0 -#define SOCKET_OPT_SCTP_AUTOCLOSE 7 +#define SOCKET_OPT_SCTP_AUTOCLOSE 7 #define SOCKET_OPT_SCTP_NODELAY 22 @@ -3397,6 +3398,25 @@ BOOLEAN_T eoptval2optval_socket(ErlNifEnv* env, SocketOptValue* valP) { switch (eOpt) { +#if defined(SO_DONTROUTE) + case SOCKET_OPT_SOCK_DONTROUTE: + { + BOOLEAN_T val; + + if (decode_bool(env, eVal, &val)) { + *opt = SO_DONTROUTE; + valP->tag = SOCKET_OPT_VALUE_INT; + valP->u.intVal = (val) ? 1 : 0; + return TRUE; + } else { + *opt = -1; + valP->tag = SOCKET_OPT_VALUE_UNDEF; + return FALSE; + } + } + break; +#endif + #if defined(SO_KEEPALIVE) case SOCKET_OPT_SOCK_KEEPALIVE: { diff --git a/erts/preloaded/src/socket.erl b/erts/preloaded/src/socket.erl index 4c903bb759..f1b5623362 100644 --- a/erts/preloaded/src/socket.erl +++ b/erts/preloaded/src/socket.erl @@ -432,15 +432,16 @@ -define(SOCKET_OPT_OTP_DEBUG, 0). -define(SOCKET_OPT_OTP_IOW, 1). +-define(SOCKET_OPT_SOCK_DONTROUTE, 7). -define(SOCKET_OPT_SOCK_KEEPALIVE, 9). -define(SOCKET_OPT_SOCK_LINGER, 10). -define(SOCKET_OPT_SOCK_PRIORITY, 16). -define(SOCKET_OPT_SOCK_REUSEADDR, 21). --define(SOCKET_OPT_IP_RECVTOS, 0). --define(SOCKET_OPT_IP_ROUTER_ALERT, 1). --define(SOCKET_OPT_IP_TOS, 2). --define(SOCKET_OPT_IP_TTL, 3). +-define(SOCKET_OPT_IP_RECVTOS, 25). +-define(SOCKET_OPT_IP_ROUTER_ALERT, 28). +-define(SOCKET_OPT_IP_TOS, 30). +-define(SOCKET_OPT_IP_TTL, 32). -define(SOCKET_OPT_IPV6_HOPLIMIT, 12). @@ -1803,8 +1804,8 @@ enc_sockopt_key(socket, busy_poll = Opt, _Dir, _D, _T, _P) -> not_supported(Opt); enc_sockopt_key(socket, debug = Opt, _Dir, _D, _T, _P) -> not_supported(Opt); -enc_sockopt_key(socket, dontroute = Opt, _Dir, _D, _T, _P) -> - not_supported(Opt); +enc_sockopt_key(socket, dontroute = _Opt, _Dir, _D, _T, _P) -> + ?SOCKET_OPT_SOCK_DONTROUTE; enc_sockopt_key(socket, error = Opt, get = _Dir, _D, _T, _P) -> not_supported(Opt); %% This is only for connection-oriented sockets, but who are those? -- cgit v1.2.3 From 0f2b8a76edaa462fca388a55eaf449a36296820a Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Tue, 8 May 2018 15:45:48 +0200 Subject: [socket-nif] setopt of socket option broadcast --- erts/emulator/nifs/common/socket_nif.c | 20 ++++++++++++++++++++ erts/preloaded/src/socket.erl | 7 +++++-- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/erts/emulator/nifs/common/socket_nif.c b/erts/emulator/nifs/common/socket_nif.c index d8e274341f..f9727d41b1 100644 --- a/erts/emulator/nifs/common/socket_nif.c +++ b/erts/emulator/nifs/common/socket_nif.c @@ -359,6 +359,7 @@ typedef union { #define SOCKET_OPT_OTP_DEBUG 0 #define SOCKET_OPT_OTP_IOW 1 +#define SOCKET_OPT_SOCK_BROADCAST 4 #define SOCKET_OPT_SOCK_DONTROUTE 7 #define SOCKET_OPT_SOCK_KEEPALIVE 9 #define SOCKET_OPT_SOCK_LINGER 10 @@ -3398,6 +3399,25 @@ BOOLEAN_T eoptval2optval_socket(ErlNifEnv* env, SocketOptValue* valP) { switch (eOpt) { +#if defined(SO_BROADCAST) + case SOCKET_OPT_SOCK_BROADCAST: + { + BOOLEAN_T val; + + if (decode_bool(env, eVal, &val)) { + *opt = SO_BROADCAST; + valP->tag = SOCKET_OPT_VALUE_INT; + valP->u.intVal = (val) ? 1 : 0; + return TRUE; + } else { + *opt = -1; + valP->tag = SOCKET_OPT_VALUE_UNDEF; + return FALSE; + } + } + break; +#endif + #if defined(SO_DONTROUTE) case SOCKET_OPT_SOCK_DONTROUTE: { diff --git a/erts/preloaded/src/socket.erl b/erts/preloaded/src/socket.erl index f1b5623362..f40fd81d36 100644 --- a/erts/preloaded/src/socket.erl +++ b/erts/preloaded/src/socket.erl @@ -432,6 +432,7 @@ -define(SOCKET_OPT_OTP_DEBUG, 0). -define(SOCKET_OPT_OTP_IOW, 1). +-define(SOCKET_OPT_SOCK_BROADCAST, 4). -define(SOCKET_OPT_SOCK_DONTROUTE, 7). -define(SOCKET_OPT_SOCK_KEEPALIVE, 9). -define(SOCKET_OPT_SOCK_LINGER, 10). @@ -1610,6 +1611,8 @@ enc_setopt_value(otp, iow, V, _, _, _) when is_boolean(V) -> enc_setopt_value(otp = L, Opt, V, _D, _T, _P) -> not_supported({L, Opt, V}); +enc_setopt_value(socket, broadcast, V, _D, _T, _P) when is_boolean(V) -> + V; enc_setopt_value(socket, keepalive, V, _D, _T, _P) when is_boolean(V) -> V; enc_setopt_value(socket, linger, abort, D, T, P) -> @@ -1798,8 +1801,8 @@ enc_sockopt_key(socket, acceptfilter = Opt, _Dir, _D, _T, _P) -> %% So, we let the implementation decide. enc_sockopt_key(socket, bindtodevide = Opt, _Dir, _D, _T, _P) -> not_supported(Opt); -enc_sockopt_key(socket, broadcast = Opt, _Dir, _D, dgram = _T, _P) -> - not_supported(Opt); +enc_sockopt_key(socket, broadcast = _Opt, _Dir, _D, dgram = _T, _P) -> + ?SOCKET_OPT_SOCK_BROADCAST; enc_sockopt_key(socket, busy_poll = Opt, _Dir, _D, _T, _P) -> not_supported(Opt); enc_sockopt_key(socket, debug = Opt, _Dir, _D, _T, _P) -> -- cgit v1.2.3 From 83dd8317ea3534fb9353f0d5159a0b1cb485f38c Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Tue, 8 May 2018 16:49:58 +0200 Subject: [socket-nif] setopt for socket option(s) rcvbuf and sndnuf --- erts/emulator/nifs/common/socket_nif.c | 30 ++++++++++++++++++++++++++++++ erts/preloaded/src/socket.erl | 27 ++++++++++++++++++--------- 2 files changed, 48 insertions(+), 9 deletions(-) diff --git a/erts/emulator/nifs/common/socket_nif.c b/erts/emulator/nifs/common/socket_nif.c index f9727d41b1..eee96ee844 100644 --- a/erts/emulator/nifs/common/socket_nif.c +++ b/erts/emulator/nifs/common/socket_nif.c @@ -364,7 +364,9 @@ typedef union { #define SOCKET_OPT_SOCK_KEEPALIVE 9 #define SOCKET_OPT_SOCK_LINGER 10 #define SOCKET_OPT_SOCK_PRIORITY 16 +#define SOCKET_OPT_SOCK_RCVBUF 17 #define SOCKET_OPT_SOCK_REUSEADDR 21 +#define SOCKET_OPT_SOCK_SNDBUF 27 #define SOCKET_OPT_IP_RECVTOS 25 #define SOCKET_OPT_IP_ROUTER_ALERT 28 @@ -3486,6 +3488,20 @@ BOOLEAN_T eoptval2optval_socket(ErlNifEnv* env, break; #endif +#if defined(SO_RCVBUF) + case SOCKET_OPT_SOCK_RCVBUF: + if (GET_INT(env, eVal, &valP->u.intVal)) { + valP->tag = SOCKET_OPT_VALUE_INT; + *opt = SO_RCVBUF; + return TRUE; + } else { + *opt = -1; + valP->tag = SOCKET_OPT_VALUE_UNDEF; + return FALSE; + } + break; +#endif + #if defined(SO_REUSEADDR) case SOCKET_OPT_SOCK_REUSEADDR: { @@ -3505,6 +3521,20 @@ BOOLEAN_T eoptval2optval_socket(ErlNifEnv* env, break; #endif +#if defined(SO_SNDBUF) + case SOCKET_OPT_SOCK_SNDBUF: + if (GET_INT(env, eVal, &valP->u.intVal)) { + valP->tag = SOCKET_OPT_VALUE_INT; + *opt = SO_SNDBUF; + return TRUE; + } else { + *opt = -1; + valP->tag = SOCKET_OPT_VALUE_UNDEF; + return FALSE; + } + break; +#endif + default: *opt = -1; valP->tag = SOCKET_OPT_VALUE_UNDEF; diff --git a/erts/preloaded/src/socket.erl b/erts/preloaded/src/socket.erl index f40fd81d36..7b53f271ef 100644 --- a/erts/preloaded/src/socket.erl +++ b/erts/preloaded/src/socket.erl @@ -199,12 +199,14 @@ %% Read-only options: %% mtu %% +%% Options only valid on FreeBSD?: +%% dontfrag %% Options only valid for RAW sockets: -%% nodefrag +%% nodefrag (linux only?) -type ip_socket_option() :: add_membership | add_source_membership | block_source | - dont_frag | + dontfrag | drop_membership | drop_source_membership | freebind | @@ -437,7 +439,9 @@ -define(SOCKET_OPT_SOCK_KEEPALIVE, 9). -define(SOCKET_OPT_SOCK_LINGER, 10). -define(SOCKET_OPT_SOCK_PRIORITY, 16). +-define(SOCKET_OPT_SOCK_RCVBUF, 17). -define(SOCKET_OPT_SOCK_REUSEADDR, 21). +-define(SOCKET_OPT_SOCK_SNDBUF, 27). -define(SOCKET_OPT_IP_RECVTOS, 25). -define(SOCKET_OPT_IP_ROUTER_ALERT, 28). @@ -1600,9 +1604,10 @@ enc_setopt_key(Level, Opt, Domain, Type, Protocol) -> %% +++ Encode setopt value +++ %% -%% For the most part this function does *not* do an actually encode, -%% it simply validates the value type. But in some cases it actually -%% encodes the value into an more manageable type. +%% For the most part this function does *not* do an actual encode, +%% it simply validates the value type. But in some cases it will +%% encode the value into an more "manageable" type. +%% It also handles "aliases" (see linger). enc_setopt_value(otp, debug, V, _, _, _) when is_boolean(V) -> V; @@ -1622,8 +1627,12 @@ enc_setopt_value(socket, linger, {OnOff, Secs} = V, _D, _T, _P) V; enc_setopt_value(socket, priority, V, _D, _T, _P) when is_integer(V) -> V; +enc_setopt_value(socket, rcvbuf, V, _D, _T, _P) when is_integer(V) -> + V; enc_setopt_value(socket, reuseaddr, V, _D, _T, _P) when is_boolean(V) -> V; +enc_setopt_value(socket, sndbuf, V, _D, _T, _P) when is_integer(V) -> + V; enc_setopt_value(socket = L, Opt, V, _D, _T, _P) -> not_supported({L, Opt, V}); @@ -1830,8 +1839,8 @@ enc_sockopt_key(socket, peek_cred = Opt, get = _Dir, _D, _T, _P) -> not_supported(Opt); enc_sockopt_key(socket, priority = _Opt, _Dir, _D, _T, _P) -> ?SOCKET_OPT_SOCK_PRIORITY; -enc_sockopt_key(socket, rcvbuf = Opt, _Dir, _D, _T, _P) -> - not_supported(Opt); +enc_sockopt_key(socket, rcvbuf = _Opt, _Dir, _D, _T, _P) -> + ?SOCKET_OPT_SOCK_RCVBUF; enc_sockopt_key(socket, rcvbufforce = Opt, _Dir, _D, _T, _P) -> not_supported(Opt); %% May not work on linux. @@ -1848,8 +1857,8 @@ enc_sockopt_key(socket, rxq_ovfl = Opt, _Dir, _D, _T, _P) -> not_supported(Opt); enc_sockopt_key(socket, setfib = Opt, set = _Dir, _D, _T, _P) -> not_supported(Opt); -enc_sockopt_key(socket, sndbuf = Opt, _Dir, _D, _T, _P) -> - not_supported(Opt); +enc_sockopt_key(socket, sndbuf = _Opt, _Dir, _D, _T, _P) -> + ?SOCKET_OPT_SOCK_SNDBUF; enc_sockopt_key(socket, sndbufforce = Opt, _Dir, _D, _T, _P) -> not_supported(Opt); %% Not changeable on linux. -- cgit v1.2.3 From bbf415b274224e273a89a706ca5b0250a1523302 Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Wed, 16 May 2018 14:36:39 +0200 Subject: [socket-nif] more setopt Rewrote the setopt handling. Still not complete, but now the structure is "complete". That is, there is atleast one option handled for each level. --- erts/emulator/nifs/common/socket_nif.c | 1378 +++++++++++++++++++------------- 1 file changed, 804 insertions(+), 574 deletions(-) diff --git a/erts/emulator/nifs/common/socket_nif.c b/erts/emulator/nifs/common/socket_nif.c index eee96ee844..18375caf60 100644 --- a/erts/emulator/nifs/common/socket_nif.c +++ b/erts/emulator/nifs/common/socket_nif.c @@ -810,17 +810,169 @@ static ERL_NIF_TERM nsetopt(ErlNifEnv* env, BOOLEAN_T isEncoded, BOOLEAN_T isOTP, int level, - int opt, - SocketOptValue* valP); + int eOpt, + ERL_NIF_TERM eVal); static ERL_NIF_TERM nsetopt_otp(ErlNifEnv* env, SocketDescriptor* descP, - int opt, - SocketOptValue* valP); -static ERL_NIF_TERM nsetopt_gen(ErlNifEnv* env, - SocketDescriptor* descP, - int level, - int opt, - SocketOptValue* valP); + int eOpt, + ERL_NIF_TERM eVal); +static ERL_NIF_TERM nsetopt_otp_debug(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); +static ERL_NIF_TERM nsetopt_otp_iow(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); +static ERL_NIF_TERM nsetopt_native(ErlNifEnv* env, + SocketDescriptor* descP, + int level, + int eOpt, + ERL_NIF_TERM eVal); +static ERL_NIF_TERM nsetopt_level(ErlNifEnv* env, + SocketDescriptor* descP, + int level, + int eOpt, + ERL_NIF_TERM eVal); +static ERL_NIF_TERM nsetopt_lvl_socket(ErlNifEnv* env, + SocketDescriptor* descP, + int eOpt, + ERL_NIF_TERM eVal); +#if defined(SO_BROADCAST) +static ERL_NIF_TERM nsetopt_lvl_sock_broadcast(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); +#endif +#if defined(SO_DONTROUTE) +static ERL_NIF_TERM nsetopt_lvl_sock_dontroute(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); +#endif +#if defined(SO_KEEPALIVE) +static ERL_NIF_TERM nsetopt_lvl_sock_keepalive(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); +#endif +#if defined(SO_LINGER) +static ERL_NIF_TERM nsetopt_lvl_sock_linger(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); +#endif +#if defined(SO_PRIORITY) +static ERL_NIF_TERM nsetopt_lvl_sock_priority(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); +#endif +#if defined(SO_RCVBUF) +static ERL_NIF_TERM nsetopt_lvl_sock_rcvbuf(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); +#endif +#if defined(SO_REUSEADDR) +static ERL_NIF_TERM nsetopt_lvl_sock_reuseaddr(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); +#endif +#if defined(SO_SNDBUF) +static ERL_NIF_TERM nsetopt_lvl_sock_sndbuf(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); +#endif +static ERL_NIF_TERM nsetopt_lvl_ip(ErlNifEnv* env, + SocketDescriptor* descP, + int eOpt, + ERL_NIF_TERM eVal); +#if defined(IP_RECVTOS) +static ERL_NIF_TERM nsetopt_lvl_ip_recvtos(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); +#endif +#if defined(IP_ROUTER_ALERT) +static ERL_NIF_TERM nsetopt_lvl_ip_router_alert(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); +#endif +#if defined(IP_TOS) +static ERL_NIF_TERM nsetopt_lvl_ip_tos(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); +#endif +#if defined(IP_TTL) +static ERL_NIF_TERM nsetopt_lvl_ip_ttl(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); +#endif +#if defined(SOL_IPV6) +static ERL_NIF_TERM nsetopt_lvl_ipv6(ErlNifEnv* env, + SocketDescriptor* descP, + int eOpt, + ERL_NIF_TERM eVal); +#if defined(IPV6_HOPLIMIT) +static ERL_NIF_TERM nsetopt_lvl_ipv6_hoplimit(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); +#endif +#endif // defined(SOL_IPV6) +static ERL_NIF_TERM nsetopt_lvl_tcp(ErlNifEnv* env, + SocketDescriptor* descP, + int eOpt, + ERL_NIF_TERM eVal); +#if defined(TCP_CONGESTION) +static ERL_NIF_TERM nsetopt_lvl_tcp_congestion(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); +#endif +#if defined(TCP_MAXSEG) +static ERL_NIF_TERM nsetopt_lvl_tcp_maxseg(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); +#endif +#if defined(TCP_NODELAY) +static ERL_NIF_TERM nsetopt_lvl_tcp_nodelay(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); +#endif +static ERL_NIF_TERM nsetopt_lvl_udp(ErlNifEnv* env, + SocketDescriptor* descP, + int eOpt, + ERL_NIF_TERM eVal); +#if defined(UDP_CORK) +static ERL_NIF_TERM nsetopt_lvl_udp_cork(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); +#endif +#if defined(HAVE_SCTP) +static ERL_NIF_TERM nsetopt_lvl_sctp(ErlNifEnv* env, + SocketDescriptor* descP, + int eOpt, + ERL_NIF_TERM eVal); +#if defined(SCTP_AUTOCLOSE) +static ERL_NIF_TERM nsetopt_lvl_sctp_autoclose(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); +#endif +#if defined(SCTP_NODELAY) +static ERL_NIF_TERM nsetopt_lvl_sctp_nodelay(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); +#endif +#endif // defined(HAVE_SCTP) + +static ERL_NIF_TERM nsetopt_str_opt(ErlNifEnv* env, + SocketDescriptor* descP, + int level, + int opt, + int max, + ERL_NIF_TERM eVal); +static ERL_NIF_TERM nsetopt_bool_opt(ErlNifEnv* env, + SocketDescriptor* descP, + int level, + int opt, + ERL_NIF_TERM eVal); +static ERL_NIF_TERM nsetopt_int_opt(ErlNifEnv* env, + SocketDescriptor* descP, + int level, + int opt, + ERL_NIF_TERM eVal); + static ERL_NIF_TERM nlink_if2idx(ErlNifEnv* env, char* ifn); static ERL_NIF_TERM nlink_idx2if(ErlNifEnv* env, @@ -940,58 +1092,6 @@ static BOOLEAN_T elevel2level(BOOLEAN_T isEncoded, int eLevel, BOOLEAN_T* isOTP, int* level); -static BOOLEAN_T eoptval2optval(ErlNifEnv* env, - BOOLEAN_T isEncoded, - BOOLEAN_T isOTP, - int level, - int eOpt, - ERL_NIF_TERM eVal, - int* opt, - SocketOptValue* val); -static BOOLEAN_T eoptval2optval_otp(ErlNifEnv* env, - int eOpt, - ERL_NIF_TERM eVal, - int* opt, - SocketOptValue* valP); -static BOOLEAN_T eoptval2optval_plain(ErlNifEnv* env, - int eOpt, - ERL_NIF_TERM eVal, - int* opt, - SocketOptValue* valP); -static BOOLEAN_T eoptval2optval_socket(ErlNifEnv* env, - int eOpt, - ERL_NIF_TERM eVal, - int* opt, - SocketOptValue* valP); -static BOOLEAN_T eoptval2optval_ip(ErlNifEnv* env, - int eOpt, - ERL_NIF_TERM eVal, - int* opt, - SocketOptValue* valP); -#if defined(SOL_IPV6) -static BOOLEAN_T eoptval2optval_ipv6(ErlNifEnv* env, - int eOpt, - ERL_NIF_TERM eVal, - int* opt, - SocketOptValue* valP); -#endif -static BOOLEAN_T eoptval2optval_tcp(ErlNifEnv* env, - int eOpt, - ERL_NIF_TERM eVal, - int* opt, - SocketOptValue* valP); -static BOOLEAN_T eoptval2optval_udp(ErlNifEnv* env, - int eOpt, - ERL_NIF_TERM eVal, - int* opt, - SocketOptValue* valP); -#ifdef HAVE_SCTP -static BOOLEAN_T eoptval2optval_sctp(ErlNifEnv* env, - int eOpt, - ERL_NIF_TERM eVal, - int* opt, - SocketOptValue* valP); -#endif #ifdef HAVE_SETNS static BOOLEAN_T emap2netns(ErlNifEnv* env, ERL_NIF_TERM map, char** netns); static BOOLEAN_T change_network_namespace(char* netns, int* cns, int* err); @@ -3041,31 +3141,28 @@ ERL_NIF_TERM nif_setopt(ErlNifEnv* env, const ERL_NIF_TERM argv[]) { SocketDescriptor* descP; - unsigned int eIsEncoded; - BOOLEAN_T isEncoded, isOTP; int eLevel, level = -1; - int eOpt, opt = -1; + int eOpt; + ERL_NIF_TERM eIsEncoded; ERL_NIF_TERM eVal; - SocketOptValue val; + BOOLEAN_T isEncoded, isOTP; if ((argc != 5) || !enif_get_resource(env, argv[0], sockets, (void**) &descP) || - !GET_UINT(env, argv[1], &eIsEncoded) || !GET_INT(env, argv[2], &eLevel) || !GET_INT(env, argv[3], &eOpt)) { return enif_make_badarg(env); } - eVal = argv[4]; - - isEncoded = ((eIsEncoded == 0) ? FALSE : TRUE); + eIsEncoded = argv[1]; + eVal = argv[4]; - if (!elevel2level(isEncoded, eLevel, &isOTP, &level)) + if (!decode_bool(env, eIsEncoded, &isEncoded)) return make_error(env, atom_einval); - if (!eoptval2optval(env, isEncoded, isOTP, level, eOpt, eVal, &opt, &val)) + if (!elevel2level(isEncoded, eLevel, &isOTP, &level)) return make_error(env, atom_einval); - return nsetopt(env, descP, isEncoded, isOTP, level, opt, &val); + return nsetopt(env, descP, isEncoded, isOTP, level, eOpt, eVal); } @@ -3075,60 +3172,44 @@ ERL_NIF_TERM nsetopt(ErlNifEnv* env, BOOLEAN_T isEncoded, BOOLEAN_T isOTP, int level, - int opt, - SocketOptValue* valP) + int eOpt, + ERL_NIF_TERM eVal) { ERL_NIF_TERM result; - int res; - if (!isEncoded) { - res = socket_setopt(descP->sock, level, opt, - valP->u.binVal.data, valP->u.binVal.size); - if (res != 0) - result = make_error2(env, res); - else - result = atom_ok; + if (isOTP) { + /* These are not actual socket options, + * but options for our implementation. + */ + result = nsetopt_otp(env, descP, eOpt, eVal); + } else if (!isEncoded) { + result = nsetopt_native(env, descP, level, eOpt, eVal); } else { - if (isOTP) { - /* These are not actual socket options, - * but options for our implementation. - */ - result = nsetopt_otp(env, descP, opt, valP); - } else { - /* Basically, call setsockopt(...) - * - * How do we know what type each option have? tag in value type? - * - */ - result = nsetopt_gen(env, descP, level, opt, valP); - } + result = nsetopt_level(env, descP, level, eOpt, eVal); } return result; } + +/* nsetopt_otp - Handle OTP (level) options + */ static ERL_NIF_TERM nsetopt_otp(ErlNifEnv* env, SocketDescriptor* descP, - int opt, - SocketOptValue* valP) + int eOpt, + ERL_NIF_TERM eVal) { ERL_NIF_TERM result; - /* Make an idiot check just to be on the safe side... */ - if (valP->tag == SOCKET_OPT_VALUE_UNDEF) - return make_error(env, atom_einval); - - switch (opt) { + switch (eOpt) { case SOCKET_OPT_OTP_DEBUG: - descP->dbg = valP->u.boolVal; - result = atom_ok; + result = nsetopt_otp_debug(env, descP, eVal); break; case SOCKET_OPT_OTP_IOW: - descP->iow = valP->u.boolVal; - result = atom_ok; + result = nsetopt_otp_iow(env, descP, eVal); break; default: @@ -3140,67 +3221,18 @@ ERL_NIF_TERM nsetopt_otp(ErlNifEnv* env, } +/* nsetopt_otp_debug - Handle the OTP (level) debug options + */ static -ERL_NIF_TERM nsetopt_gen(ErlNifEnv* env, - SocketDescriptor* descP, - int level, - int opt, - SocketOptValue* valP) +ERL_NIF_TERM nsetopt_otp_debug(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) { - socklen_t optLen; - int res; ERL_NIF_TERM result; - switch (valP->tag) { - case SOCKET_OPT_VALUE_INT: - { - optLen = sizeof(valP->u.intVal); - res = socket_setopt(descP->sock, level, opt, - (void*) &valP->u.intVal, optLen); - if (res != 0) - result = make_error2(env, res); - else - result = atom_ok; - } - break; - - case SOCKET_OPT_VALUE_BIN: - { - optLen = valP->u.binVal.size; - res = socket_setopt(descP->sock, level, opt, - &valP->u.binVal.data, optLen); - if (res != 0) - result = make_error2(env, res); - else - result = atom_ok; - } - break; - - case SOCKET_OPT_VALUE_LINGER: - { - optLen = sizeof(valP->u.lingerVal); - res = socket_setopt(descP->sock, level, opt, - &valP->u.lingerVal, optLen); - if (res != 0) - result = make_error2(env, res); - else - result = atom_ok; - } - break; - - case SOCKET_OPT_VALUE_STR: - { - optLen = valP->u.strVal.len; - res = socket_setopt(descP->sock, level, opt, - valP->u.strVal.str, optLen); - if (res != 0) - result = make_error2(env, res); - else - result = atom_ok; - } - break; - - default: + if (decode_bool(env, eVal, &descP->dbg)) { + result = atom_ok; + } else { result = make_error(env, atom_einval); } @@ -3208,611 +3240,809 @@ ERL_NIF_TERM nsetopt_gen(ErlNifEnv* env, } - +/* nsetopt_otp_iow - Handle the OTP (level) iow options + */ static -BOOLEAN_T elevel2level(BOOLEAN_T isEncoded, - int eLevel, - BOOLEAN_T* isOTP, - int* level) +ERL_NIF_TERM nsetopt_otp_iow(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) { - BOOLEAN_T result; - - if (isEncoded) { - switch (eLevel) { - case SOCKET_OPT_LEVEL_OTP: - *isOTP = TRUE; - *level = -1; - result = TRUE; - break; - - case SOCKET_OPT_LEVEL_SOCKET: - *isOTP = FALSE; - *level = SOL_SOCKET; - result = TRUE; - break; + ERL_NIF_TERM result; - case SOCKET_OPT_LEVEL_IP: - *isOTP = FALSE; -#if defined(SOL_IP) - *level = SOL_IP; -#else - *level = IPROTO_IP; -#endif - result = TRUE; - break; + if (decode_bool(env, eVal, &descP->iow)) { + result = atom_ok; + } else { + result = make_error(env, atom_einval); + } -#if defined(SOL_IPV6) - case SOCKET_OPT_LEVEL_IPV6: - *isOTP = FALSE; - *level = SOL_IPV6; - result = TRUE; - break; -#endif + return result; +} - case SOCKET_OPT_LEVEL_TCP: - *isOTP = FALSE; - *level = IPPROTO_TCP; - result = TRUE; - break; - case SOCKET_OPT_LEVEL_UDP: - *isOTP = FALSE; - *level = IPPROTO_UDP; - result = TRUE; - break; -#ifdef HAVE_SCTP - case SOCKET_OPT_LEVEL_SCTP: - *isOTP = FALSE; - *level = IPPROTO_SCTP; - result = TRUE; - break; -#endif +/* The option has *not* been encoded. Instead it has been provided + * in "native mode" (option is provided as is and value as a binary). + */ +static +ERL_NIF_TERM nsetopt_native(ErlNifEnv* env, + SocketDescriptor* descP, + int level, + int opt, + ERL_NIF_TERM eVal) +{ + ErlNifBinary val; + ERL_NIF_TERM result; - default: - *isOTP = FALSE; - *level = -1; - result = FALSE; - break; - } + if (GET_BIN(env, eVal, &val)) { + int res = socket_setopt(descP->sock, level, opt, + val.data, val.size); + if (res != 0) + result = make_error2(env, res); + else + result = atom_ok; } else { - *isOTP = FALSE; - *level = eLevel; - result = TRUE; + result = make_error(env, atom_einval); } return result; } + +/* nsetopt_level - A "proper" level (option) has been specified + */ static -BOOLEAN_T eoptval2optval(ErlNifEnv* env, - BOOLEAN_T isEncoded, - BOOLEAN_T isOTP, - int level, - int eOpt, - ERL_NIF_TERM eVal, - int* opt, - SocketOptValue* valP) +ERL_NIF_TERM nsetopt_level(ErlNifEnv* env, + SocketDescriptor* descP, + int level, + int eOpt, + ERL_NIF_TERM eVal) { - if (isOTP) { - return eoptval2optval_otp(env, eOpt, eVal, opt, valP); - } else if (!isEncoded) { - return eoptval2optval_plain(env, eOpt, eVal, opt, valP); - } else { - switch (level) { - case SOL_SOCKET: - return eoptval2optval_socket(env, eOpt, eVal, opt, valP); - break; + ERL_NIF_TERM result; + + switch (level) { + case SOL_SOCKET: + result = nsetopt_lvl_socket(env, descP, eOpt, eVal); + break; #if defined(SOL_IP) - case SOL_IP: + case SOL_IP: #else - case IPPROTO_IP: + case IPPROTO_IP: #endif - return eoptval2optval_ip(env, eOpt, eVal, opt, valP); - break; + result = nsetopt_lvl_ip(env, descP, eOpt, eVal); + break; #if defined(SOL_IPV6) - case SOL_IPV6: - return eoptval2optval_ipv6(env, eOpt, eVal, opt, valP); - break; + case SOL_IPV6: + result = nsetopt_lvl_ipv6(env, descP, eOpt, eVal); + break; #endif - case IPPROTO_TCP: - return eoptval2optval_tcp(env, eOpt, eVal, opt, valP); - break; + case IPPROTO_TCP: + result = nsetopt_lvl_tcp(env, descP, eOpt, eVal); + break; - case IPPROTO_UDP: - return eoptval2optval_udp(env, eOpt, eVal, opt, valP); - break; + case IPPROTO_UDP: + result = nsetopt_lvl_udp(env, descP, eOpt, eVal); + break; -#ifdef HAVE_SCTP - case IPPROTO_SCTP: - return eoptval2optval_sctp(env, eOpt, eVal, opt, valP); - break; +#if defined(HAVE_SCTP) + case IPPROTO_SCTP: + result = nsetopt_lvl_sctp(env, descP, eOpt, eVal); + break; #endif - default: - *opt = -1; - return FALSE; - } - } -} - - - -static -BOOLEAN_T eoptval2optval_otp(ErlNifEnv* env, - int eOpt, - ERL_NIF_TERM eVal, - int* opt, - SocketOptValue* valP) -{ - BOOLEAN_T result = FALSE; - - switch (eOpt) { - case SOCKET_OPT_OTP_IOW: - case SOCKET_OPT_OTP_DEBUG: - { - if (decode_bool(env, eVal, &valP->u.boolVal)) { - valP->tag = SOCKET_OPT_VALUE_BOOL; - *opt = eOpt; - result = TRUE; - } else { - valP->tag = SOCKET_OPT_VALUE_UNDEF; - *opt = -1; - result = FALSE; - } - } - break; - default: - *opt = -1; - valP->tag = SOCKET_OPT_VALUE_UNDEF; + result = make_error(env, atom_einval); + break; } return result; } -static -BOOLEAN_T eoptval2optval_plain(ErlNifEnv* env, - int eOpt, - ERL_NIF_TERM eVal, - int* opt, - SocketOptValue* valP) -{ - if (!GET_BIN(env, eVal, &valP->u.binVal)) - return FALSE; - valP->tag = SOCKET_OPT_VALUE_BIN; - *opt = eOpt; - - return TRUE; -} - - +/* nsetopt_lvl_socket - Level *SOCKET* option + */ static -BOOLEAN_T eoptval2optval_socket(ErlNifEnv* env, - int eOpt, - ERL_NIF_TERM eVal, - int* opt, - SocketOptValue* valP) +ERL_NIF_TERM nsetopt_lvl_socket(ErlNifEnv* env, + SocketDescriptor* descP, + int eOpt, + ERL_NIF_TERM eVal) { + ERL_NIF_TERM result; + switch (eOpt) { #if defined(SO_BROADCAST) case SOCKET_OPT_SOCK_BROADCAST: - { - BOOLEAN_T val; - - if (decode_bool(env, eVal, &val)) { - *opt = SO_BROADCAST; - valP->tag = SOCKET_OPT_VALUE_INT; - valP->u.intVal = (val) ? 1 : 0; - return TRUE; - } else { - *opt = -1; - valP->tag = SOCKET_OPT_VALUE_UNDEF; - return FALSE; - } - } + result = nsetopt_lvl_sock_broadcast(env, descP, eVal); break; #endif #if defined(SO_DONTROUTE) case SOCKET_OPT_SOCK_DONTROUTE: - { - BOOLEAN_T val; - - if (decode_bool(env, eVal, &val)) { - *opt = SO_DONTROUTE; - valP->tag = SOCKET_OPT_VALUE_INT; - valP->u.intVal = (val) ? 1 : 0; - return TRUE; - } else { - *opt = -1; - valP->tag = SOCKET_OPT_VALUE_UNDEF; - return FALSE; - } - } + result = nsetopt_lvl_sock_dontroute(env, descP, eVal); break; #endif #if defined(SO_KEEPALIVE) case SOCKET_OPT_SOCK_KEEPALIVE: - { - BOOLEAN_T val; - - if (decode_bool(env, eVal, &val)) { - *opt = SO_KEEPALIVE; - valP->tag = SOCKET_OPT_VALUE_INT; - valP->u.intVal = (val) ? 1 : 0; - return TRUE; - } else { - *opt = -1; - valP->tag = SOCKET_OPT_VALUE_UNDEF; - return FALSE; - } - } + result = nsetopt_lvl_sock_keepalive(env, descP, eVal); break; #endif #if defined(SO_LINGER) case SOCKET_OPT_SOCK_LINGER: - { - if (decode_sock_linger(env, eVal, &valP->u.lingerVal)) { - *opt = SO_LINGER; - valP->tag = SOCKET_OPT_VALUE_LINGER; - return TRUE; - } else { - *opt = -1; - valP->tag = SOCKET_OPT_VALUE_UNDEF; - return FALSE; - } - } + result = nsetopt_lvl_sock_linger(env, descP, eVal); break; #endif #if defined(SO_PRIORITY) case SOCKET_OPT_SOCK_PRIORITY: - if (GET_INT(env, eVal, &valP->u.intVal)) { - valP->tag = SOCKET_OPT_VALUE_INT; - *opt = SO_PRIORITY; - return TRUE; - } else { - *opt = -1; - valP->tag = SOCKET_OPT_VALUE_UNDEF; - return FALSE; - } + result = nsetopt_lvl_sock_priority(env, descP, eVal); break; #endif #if defined(SO_RCVBUF) case SOCKET_OPT_SOCK_RCVBUF: - if (GET_INT(env, eVal, &valP->u.intVal)) { - valP->tag = SOCKET_OPT_VALUE_INT; - *opt = SO_RCVBUF; - return TRUE; - } else { - *opt = -1; - valP->tag = SOCKET_OPT_VALUE_UNDEF; - return FALSE; - } + result = nsetopt_lvl_sock_rcvbuf(env, descP, eVal); break; #endif #if defined(SO_REUSEADDR) case SOCKET_OPT_SOCK_REUSEADDR: - { - BOOLEAN_T val; - - if (decode_bool(env, eVal, &val)) { - *opt = SO_REUSEADDR; - valP->tag = SOCKET_OPT_VALUE_INT; - valP->u.intVal = (val) ? 1 : 0; - return TRUE; - } else { - *opt = -1; - valP->tag = SOCKET_OPT_VALUE_UNDEF; - return FALSE; - } - } + result = nsetopt_lvl_sock_reuseaddr(env, descP, eVal); break; #endif #if defined(SO_SNDBUF) case SOCKET_OPT_SOCK_SNDBUF: - if (GET_INT(env, eVal, &valP->u.intVal)) { - valP->tag = SOCKET_OPT_VALUE_INT; - *opt = SO_SNDBUF; - return TRUE; - } else { - *opt = -1; - valP->tag = SOCKET_OPT_VALUE_UNDEF; - return FALSE; - } + result = nsetopt_lvl_sock_sndbuf(env, descP, eVal); break; #endif default: - *opt = -1; - valP->tag = SOCKET_OPT_VALUE_UNDEF; - return FALSE; + result = make_error(env, atom_einval); + break; } + + return result; +} + + +#if defined(SO_BROADCAST) +static +ERL_NIF_TERM nsetopt_lvl_sock_broadcast(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) +{ + return nsetopt_bool_opt(env, descP, SOL_SOCKET, SO_BROADCAST, eVal); +} +#endif + + +#if defined(SO_DONTROUTE) +static +ERL_NIF_TERM nsetopt_lvl_sock_dontroute(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) +{ + return nsetopt_bool_opt(env, descP, SOL_SOCKET, SO_DONTROUTE, eVal); +} +#endif + + +#if defined(SO_KEEPALIVE) +static +ERL_NIF_TERM nsetopt_lvl_sock_keepalive(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) +{ + return nsetopt_bool_opt(env, descP, SOL_SOCKET, SO_KEEPALIVE, eVal); +} +#endif + + +#if defined(SO_LINGER) +static +ERL_NIF_TERM nsetopt_lvl_sock_linger(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) +{ + ERL_NIF_TERM result; + struct linger val; + + if (decode_sock_linger(env, eVal, &val)) { + int optLen = sizeof(val); + int res = socket_setopt(descP->sock, SOL_SOCKET, SO_LINGER, + (void*) &val, optLen); + if (res != 0) + result = make_error2(env, res); + else + result = atom_ok; + } else { + result = make_error(env, atom_einval); + } + + return result; +} +#endif + + +#if defined(SO_PRIORITY) +static +ERL_NIF_TERM nsetopt_lvl_sock_priority(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) +{ + return nsetopt_int_opt(env, descP, SOL_SOCKET, SO_PRIORITY, eVal); } +#endif +#if defined(SO_RCVBUF) +static +ERL_NIF_TERM nsetopt_lvl_sock_rcvbuf(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) +{ + return nsetopt_int_opt(env, descP, SOL_SOCKET, SO_RCVBUF, eVal); +} +#endif + +#if defined(SO_REUSEADDR) static -BOOLEAN_T eoptval2optval_ip(ErlNifEnv* env, - int eOpt, - ERL_NIF_TERM eVal, - int* opt, - SocketOptValue* valP) +ERL_NIF_TERM nsetopt_lvl_sock_reuseaddr(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) { + return nsetopt_bool_opt(env, descP, SOL_SOCKET, SO_REUSEADDR, eVal); +} +#endif + + +#if defined(SO_SNDBUF) +static +ERL_NIF_TERM nsetopt_lvl_sock_sndbuf(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) +{ + return nsetopt_int_opt(env, descP, SOL_SOCKET, SO_SNDBUF, eVal); +} +#endif + + + +/* nsetopt_lvl_ip - Level *IP* option(s) + */ +static +ERL_NIF_TERM nsetopt_lvl_ip(ErlNifEnv* env, + SocketDescriptor* descP, + int eOpt, + ERL_NIF_TERM eVal) +{ + ERL_NIF_TERM result; + switch (eOpt) { #if defined(IP_RECVTOS) case SOCKET_OPT_IP_RECVTOS: - { - BOOLEAN_T val; - - if (decode_bool(env, eVal, &val)) { - *opt = IP_RECVTOS; - valP->tag = SOCKET_OPT_VALUE_INT; - valP->u.intVal = (val) ? 1 : 0; - return TRUE; - } else { - *opt = -1; - valP->tag = SOCKET_OPT_VALUE_UNDEF; - return TRUE; - } - } + result = nsetopt_lvl_ip_recvtos(env, descP, eVal); break; #endif #if defined(IP_ROUTER_ALERT) case SOCKET_OPT_IP_ROUTER_ALERT: - if (GET_INT(env, eVal, &valP->u.intVal)) { - valP->tag = SOCKET_OPT_VALUE_INT; - *opt = IP_ROUTER_ALERT; - return TRUE; - } else { - *opt = -1; - valP->tag = SOCKET_OPT_VALUE_UNDEF; - return FALSE; - } + result = nsetopt_lvl_ip_router_alert(env, descP, eVal); break; #endif #if defined(IP_TOS) case SOCKET_OPT_IP_TOS: - { - if (decode_ip_tos(env, eVal, &valP->u.intVal)) { - valP->tag = SOCKET_OPT_VALUE_INT; - *opt = IP_TOS; - return TRUE; - } else { - *opt = -1; - return FALSE; - } - } + result = nsetopt_lvl_ip_tos(env, descP, eVal); break; #endif #if defined(IP_TTL) case SOCKET_OPT_IP_TTL: - /* - * Should we care about the value? That is, if it is valid? - * And what is the valid range anyway for ttl? 0 - 255? - * - */ - if (!GET_INT(env, eVal, &valP->u.intVal)) - return FALSE; // PLACEHOLDER - We should really be more informative - valP->tag = SOCKET_OPT_VALUE_INT; - *opt = IP_TTL; - return TRUE; + result = nsetopt_lvl_ip_ttl(env, descP, eVal); break; #endif default: - *opt = -1; - valP->tag = SOCKET_OPT_VALUE_UNDEF; - return FALSE; + result = make_error(env, atom_einval); + break; + } + + return result; +} + + +/* nsetopt_lvl_ip_recvtos - Level IP RECVTOS option + */ +#if defined(IP_RECVTOS) +static +ERL_NIF_TERM nsetopt_lvl_ip_recvtos(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) +{ +#if defined(SOL_IP) + int level = SOL_IP; +#else + int level = IPPROTO_IP; +#endif + + return nsetopt_bool_opt(env, descP, level, IP_RECVTOS, eVal); +} +#endif + + +/* nsetopt_lvl_ip_router_alert - Level IP ROUTER_ALERT option + */ +#if defined(IP_ROUTER_ALERT) +static +ERL_NIF_TERM nsetopt_lvl_ip_router_alert(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) +{ +#if defined(SOL_IP) + int level = SOL_IP; +#else + int level = IPPROTO_IP; +#endif + + return nsetopt_int_opt(env, descP, level, IP_ROUTER_ALERT, eVal); +} +#endif + + +/* nsetopt_lvl_ip_tos - Level IP TOS option + */ +#if defined(IP_TOS) +static +ERL_NIF_TERM nsetopt_lvl_ip_tos(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) +{ +#if defined(SOL_IP) + int level = SOL_IP; +#else + int level = IPPROTO_IP; +#endif + ERL_NIF_TERM result; + int val; + + if (decode_ip_tos(env, eVal, &val)) { + int res = socket_setopt(descP->sock, level, IP_TOS, &val, sizeof(val)); + + if (res != 0) + result = make_error2(env, res); + else + result = atom_ok; + + } else { + result = make_error(env, atom_einval); } + return result; +} +#endif + + +/* nsetopt_lvl_ip_ttl - Level IP TTL option + */ +#if defined(IP_TTL) +static +ERL_NIF_TERM nsetopt_lvl_ip_ttl(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) +{ +#if defined(SOL_IP) + int level = SOL_IP; +#else + int level = IPPROTO_IP; +#endif + + return nsetopt_int_opt(env, descP, level, IP_TTL, eVal); } +#endif +/* nsetopt_lvl_ipv6 - Level *IPv6* option(s) + */ #if defined(SOL_IPV6) static -BOOLEAN_T eoptval2optval_ipv6(ErlNifEnv* env, - int eOpt, - ERL_NIF_TERM eVal, - int* opt, - SocketOptValue* valP) +ERL_NIF_TERM nsetopt_lvl_ipv6(ErlNifEnv* env, + SocketDescriptor* descP, + int eOpt, + ERL_NIF_TERM eVal) { - BOOLEAN_T result = FALSE; + ERL_NIF_TERM result; switch (eOpt) { #if defined(IPV6_HOPLIMIT) case SOCKET_OPT_IPV6_HOPLIMIT: - { - BOOLEAN_T val; - - if (decode_bool(env, eVal, &val)) { - valP->tag = SOCKET_OPT_VALUE_INT; - valP->u.intVal = (val) ? 1 : 0; - *opt = IPV6_HOPLIMIT; - result = TRUE; - } else { - *opt = -1; - result = FALSE; - } - } + result = nsetopt_lvl_ipv6_hoplimit(env, descP, eVal); break; #endif default: - *opt = -1; - valP->tag = SOCKET_OPT_VALUE_UNDEF; - result = FALSE; + result = make_error(env, atom_einval); break; } return result; } + + +#if defined(IPV6_HOPLIMIT) +static +ERL_NIF_TERM nsetopt_lvl_ipv6_hoplimit(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) +{ + return nsetopt_bool_opt(env, descP, SOL_IPV6, IPV6_HOPLIMIT, eVal); +} #endif +#endif // defined(SOL_IPV6) + + +/* nsetopt_lvl_tcp - Level *TCP* option(s) + */ static -BOOLEAN_T eoptval2optval_tcp(ErlNifEnv* env, - int eOpt, - ERL_NIF_TERM eVal, - int* opt, - SocketOptValue* valP) +ERL_NIF_TERM nsetopt_lvl_tcp(ErlNifEnv* env, + SocketDescriptor* descP, + int eOpt, + ERL_NIF_TERM eVal) { + ERL_NIF_TERM result; + switch (eOpt) { #if defined(TCP_CONGESTION) case SOCKET_OPT_TCP_CONGESTION: - { - valP->u.strVal.len = SOCKET_OPT_TCP_CONGESTION_NAME_MAX+1; - valP->u.strVal.str = MALLOC(valP->u.strVal.len); - - if (GET_STR(env, eVal, valP->u.strVal.str, valP->u.strVal.len) > 0) { - valP->tag = SOCKET_OPT_VALUE_STR; - *opt = TCP_CONGESTION; - return TRUE; - } else { - FREE(valP->u.strVal.str); - *opt = -1; - valP->tag = SOCKET_OPT_VALUE_UNDEF; - return FALSE; - } - } + result = nsetopt_lvl_tcp_congestion(env, descP, eVal); break; #endif #if defined(TCP_MAXSEG) case SOCKET_OPT_TCP_MAXSEG: - if (!GET_INT(env, eVal, &valP->u.intVal)) { - valP->tag = SOCKET_OPT_VALUE_INT; - *opt = TCP_MAXSEG; - return TRUE; - } else { - *opt = -1; - valP->tag = SOCKET_OPT_VALUE_UNDEF; - return FALSE; - } + result = nsetopt_lvl_tcp_maxseg(env, descP, eVal); break; #endif #if defined(TCP_NODELAY) case SOCKET_OPT_TCP_NODELAY: - if (decode_bool(env, eVal, &valP->u.boolVal)) { - valP->tag = SOCKET_OPT_VALUE_BOOL; - *opt = TCP_NODELAY; - return TRUE; - } else { - valP->tag = SOCKET_OPT_VALUE_UNDEF; - *opt = -1; - return FALSE; - } + result = nsetopt_lvl_tcp_nodelay(env, descP, eVal); break; #endif default: - *opt = -1; - valP->tag = SOCKET_OPT_VALUE_UNDEF; - return FALSE; + result = make_error(env, atom_einval); + break; } + + return result; } +/* nsetopt_lvl_tcp_congestion - Level TCP CONGESTION option + */ +#if defined(TCP_CONGESTION) +static +ERL_NIF_TERM nsetopt_lvl_tcp_congestion(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) +{ + int max = SOCKET_OPT_TCP_CONGESTION_NAME_MAX+1; + + return nsetopt_str_opt(env, descP, IPPROTO_TCP, TCP_CONGESTION, max, eVal); +} +#endif -/* +++ decode UDP socket options +++ - * Currently there are only one option, cork, and that may only - * work on linux... + +/* nsetopt_lvl_tcp_maxseg - Level TCP MAXSEG option + */ +#if defined(TCP_MAXSEG) +static +ERL_NIF_TERM nsetopt_lvl_tcp_maxseg(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) +{ + return nsetopt_int_opt(env, descP, IPPROTO_TCP, TCP_MAXSEG, eVal); +} +#endif + + +/* nsetopt_lvl_tcp_nodelay - Level TCP NODELAY option */ +#if defined(TCP_NODELAY) static -BOOLEAN_T eoptval2optval_udp(ErlNifEnv* env, - int eOpt, - ERL_NIF_TERM eVal, - int* opt, - SocketOptValue* valP) +ERL_NIF_TERM nsetopt_lvl_tcp_nodelay(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) { + return nsetopt_bool_opt(env, descP, IPPROTO_TCP, TCP_NODELAY, eVal); +} +#endif + + + +/* nsetopt_lvl_udp - Level *UDP* option(s) + */ +static +ERL_NIF_TERM nsetopt_lvl_udp(ErlNifEnv* env, + SocketDescriptor* descP, + int eOpt, + ERL_NIF_TERM eVal) +{ + ERL_NIF_TERM result; + switch (eOpt) { #if defined(UDP_CORK) case SOCKET_OPT_UDP_CORK: - if (decode_bool(env, eVal, &valP->u.boolVal)) { - valP->tag = SOCKET_OPT_VALUE_BOOL; - *opt = UDP_CORK; - return TRUE; - } else { - valP->tag = SOCKET_OPT_VALUE_UNDEF; - *opt = -1; - return FALSE; - } + result = nsetopt_lvl_udp_cork(env, descP, eVal); break; #endif default: - *opt = -1; - valP->tag = SOCKET_OPT_VALUE_UNDEF; - return FALSE; + result = make_error(env, atom_einval); + break; } + + return result; } +/* nsetopt_lvl_udp_cork - Level UDP CORK option + */ +#if defined(UDP_CORK) +static +ERL_NIF_TERM nsetopt_lvl_udp_cork(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) +{ + return nsetopt_bool_opt(env, descP, IPPROTO_UDP, UDP_CORK, eVal); +} +#endif -#ifdef HAVE_SCTP + + + +/* nsetopt_lvl_sctp - Level *SCTP* option(s) + */ +#if defined(HAVE_SCTP) static -BOOLEAN_T eoptval2optval_sctp(ErlNifEnv* env, - int eOpt, - ERL_NIF_TERM eVal, - int* opt, - SocketOptValue* valP) +ERL_NIF_TERM nsetopt_lvl_sctp(ErlNifEnv* env, + SocketDescriptor* descP, + int eOpt, + ERL_NIF_TERM eVal) { + ERL_NIF_TERM result; + switch (eOpt) { #if defined(SCTP_AUTOCLOSE) case SOCKET_OPT_SCTP_AUTOCLOSE: - if (GET_INT(env, eVal, &valP->u.intVal)) { - valP->tag = SOCKET_OPT_VALUE_INT; - *opt = SCTP_AUTOCLOSE; - return TRUE; - } else { - valP->tag = SOCKET_OPT_VALUE_UNDEF; - *opt = -1; - return FALSE; // PLACEHOLDER - We should really be more informative - } + result = nsetopt_lvl_sctp_autoclose(env, descP, eVal); break; #endif #if defined(SCTP_NODELAY) case SOCKET_OPT_SCTP_NODELAY: - if (decode_bool(env, eVal, &valP->u.boolVal)) { - valP->tag = SOCKET_OPT_VALUE_BOOL; - *opt = SCTP_NODELAY; - return TRUE; - } else { - valP->tag = SOCKET_OPT_VALUE_UNDEF; - *opt = -1; - return FALSE; - } + result = nsetopt_lvl_sctp_nodelay(env, descP, eVal); break; #endif default: - *opt = -1; - valP->tag = SOCKET_OPT_VALUE_UNDEF; - return FALSE; + result = make_error(env, atom_einval); + break; } + + return result; +} + + +/* nsetopt_lvl_sctp_autoclose - Level SCTP AUTOCLOSE option + */ +#if defined(SCTP_AUTOCLOSE) +static +ERL_NIF_TERM nsetopt_lvl_sctp_autoclose(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) +{ + return nsetopt_int_opt(env, descP, IPPROTO_SCTP, SCTP_AUTOCLOSE, eVal); } #endif +/* nsetopt_lvl_sctp_nodelay - Level SCTP NODELAY option + */ +#if defined(SCTP_NODELAY) +static +ERL_NIF_TERM nsetopt_lvl_sctp_nodelay(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) +{ + return nsetopt_bool_opt(env, descP, IPPROTO_SCTP, SCTP_NODELAY, eVal); +} +#endif + + + +#endif // defined(HAVE_SCTP) + + + + +/* nsetopt_str_opt - set an option that has an string value + */ +static +ERL_NIF_TERM nsetopt_str_opt(ErlNifEnv* env, + SocketDescriptor* descP, + int level, + int opt, + int max, + ERL_NIF_TERM eVal) +{ + ERL_NIF_TERM result; + char* val = MALLOC(max); + + if (GET_STR(env, eVal, val, max) > 0) { + int optLen = strlen(val); + int res = socket_setopt(descP->sock, level, opt, &val, optLen); + + if (res != 0) + result = make_error2(env, res); + else + result = atom_ok; + + } else { + result = make_error(env, atom_einval); + } + + FREE(val); + + return result; +} + + +/* nsetopt_bool_opt - set an option that has an (integer) bool value + */ +static +ERL_NIF_TERM nsetopt_bool_opt(ErlNifEnv* env, + SocketDescriptor* descP, + int level, + int opt, + ERL_NIF_TERM eVal) +{ + ERL_NIF_TERM result; + BOOLEAN_T val; + + if (decode_bool(env, eVal, &val)) { + int ival = (val) ? 1 : 0; + int res = socket_setopt(descP->sock, level, opt, &ival, sizeof(ival)); + + if (res != 0) + result = make_error2(env, res); + else + result = atom_ok; + + } else { + result = make_error(env, atom_einval); + } + + return result; +} + + +/* nsetopt_int_opt - set an option that has an integer value + */ +static +ERL_NIF_TERM nsetopt_int_opt(ErlNifEnv* env, + SocketDescriptor* descP, + int level, + int opt, + ERL_NIF_TERM eVal) +{ + ERL_NIF_TERM result; + int val; + + if (GET_INT(env, eVal, &val)) { + int res = socket_setopt(descP->sock, level, opt, &val, sizeof(val)); + + if (res != 0) + result = make_error2(env, res); + else + result = atom_ok; + + } else { + result = make_error(env, atom_einval); + } + + return result; +} + + +static +BOOLEAN_T elevel2level(BOOLEAN_T isEncoded, + int eLevel, + BOOLEAN_T* isOTP, + int* level) +{ + BOOLEAN_T result; + + if (isEncoded) { + switch (eLevel) { + case SOCKET_OPT_LEVEL_OTP: + *isOTP = TRUE; + *level = -1; + result = TRUE; + break; + + case SOCKET_OPT_LEVEL_SOCKET: + *isOTP = FALSE; + *level = SOL_SOCKET; + result = TRUE; + break; + + case SOCKET_OPT_LEVEL_IP: + *isOTP = FALSE; +#if defined(SOL_IP) + *level = SOL_IP; +#else + *level = IPROTO_IP; +#endif + result = TRUE; + break; + +#if defined(SOL_IPV6) + case SOCKET_OPT_LEVEL_IPV6: + *isOTP = FALSE; + *level = SOL_IPV6; + result = TRUE; + break; +#endif + + case SOCKET_OPT_LEVEL_TCP: + *isOTP = FALSE; + *level = IPPROTO_TCP; + result = TRUE; + break; + + case SOCKET_OPT_LEVEL_UDP: + *isOTP = FALSE; + *level = IPPROTO_UDP; + result = TRUE; + break; + +#ifdef HAVE_SCTP + case SOCKET_OPT_LEVEL_SCTP: + *isOTP = FALSE; + *level = IPPROTO_SCTP; + result = TRUE; + break; +#endif + + default: + *isOTP = FALSE; + *level = -1; + result = FALSE; + break; + } + } else { + *isOTP = FALSE; + *level = eLevel; + result = TRUE; + } + + return result; +} + + /* +++ socket_setopt +++ * -- cgit v1.2.3 From 24bcbd2040fad723648e25af87cd848da8aa27bc Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Thu, 17 May 2018 14:22:52 +0200 Subject: [socket-nif] getopt partially implemented There are still many options not implemented (just as for setopt), but this will have to do for now... --- erts/emulator/nifs/common/socket_nif.c | 1334 +++++++++++++++++++++++++++++--- erts/preloaded/src/socket.erl | 71 +- 2 files changed, 1261 insertions(+), 144 deletions(-) diff --git a/erts/emulator/nifs/common/socket_nif.c b/erts/emulator/nifs/common/socket_nif.c index 18375caf60..d87492dd54 100644 --- a/erts/emulator/nifs/common/socket_nif.c +++ b/erts/emulator/nifs/common/socket_nif.c @@ -376,8 +376,9 @@ typedef union { #define SOCKET_OPT_IPV6_HOPLIMIT 12 #define SOCKET_OPT_TCP_CONGESTION 0 -#define SOCKET_OPT_TCP_MAXSEG 1 -#define SOCKET_OPT_TCP_NODELAY 2 +#define SOCKET_OPT_TCP_CORK 1 +#define SOCKET_OPT_TCP_MAXSEG 2 +#define SOCKET_OPT_TCP_NODELAY 3 #define SOCKET_OPT_UDP_CORK 0 @@ -437,6 +438,7 @@ typedef union { #define GET_TUPLE(E, TE, TSZ, TA) enif_get_tuple((E), (TE), (TSZ), (TA)) #define ALLOC_BIN(SZ, BP) enif_alloc_binary((SZ), (BP)) +#define REALLOC_BIN(SZ, BP) enif_realloc_binary((SZ), (BP)) /* =================================================================== * @@ -520,6 +522,11 @@ static unsigned long one_value = 1; # define SOCKLEN_T size_t #endif +#ifdef __WIN32__ +#define SOCKOPTLEN_T int +#else +#define SOCKOPTLEN_T SOCKLEN_T +#endif /* The general purpose sockaddr */ typedef union { @@ -805,6 +812,7 @@ static ERL_NIF_TERM nclose(ErlNifEnv* env, static ERL_NIF_TERM nshutdown(ErlNifEnv* env, SocketDescriptor* descP, int how); + static ERL_NIF_TERM nsetopt(ErlNifEnv* env, SocketDescriptor* descP, BOOLEAN_T isEncoded, @@ -956,6 +964,126 @@ static ERL_NIF_TERM nsetopt_lvl_sctp_nodelay(ErlNifEnv* env, #endif #endif // defined(HAVE_SCTP) +static ERL_NIF_TERM ngetopt(ErlNifEnv* env, + SocketDescriptor* descP, + BOOLEAN_T isEncoded, + BOOLEAN_T isOTP, + int level, + int eOpt); +static ERL_NIF_TERM ngetopt_otp(ErlNifEnv* env, + SocketDescriptor* descP, + int eOpt); +static ERL_NIF_TERM ngetopt_otp_debug(ErlNifEnv* env, + SocketDescriptor* descP); +static ERL_NIF_TERM ngetopt_otp_iow(ErlNifEnv* env, + SocketDescriptor* descP); +static ERL_NIF_TERM ngetopt_native(ErlNifEnv* env, + SocketDescriptor* descP, + int level, + int eOpt); +static ERL_NIF_TERM ngetopt_level(ErlNifEnv* env, + SocketDescriptor* descP, + int level, + int eOpt); +static ERL_NIF_TERM ngetopt_lvl_socket(ErlNifEnv* env, + SocketDescriptor* descP, + int eOpt); +#if defined(SO_BROADCAST) +static ERL_NIF_TERM ngetopt_lvl_sock_broadcast(ErlNifEnv* env, + SocketDescriptor* descP); +#endif +#if defined(SO_DONTROUTE) +static ERL_NIF_TERM ngetopt_lvl_sock_dontroute(ErlNifEnv* env, + SocketDescriptor* descP); +#endif +#if defined(SO_KEEPALIVE) +static ERL_NIF_TERM ngetopt_lvl_sock_keepalive(ErlNifEnv* env, + SocketDescriptor* descP); +#endif +#if defined(SO_LINGER) +static ERL_NIF_TERM ngetopt_lvl_sock_linger(ErlNifEnv* env, + SocketDescriptor* descP); +#endif +#if defined(SO_PRIORITY) +static ERL_NIF_TERM ngetopt_lvl_sock_priority(ErlNifEnv* env, + SocketDescriptor* descP); +#endif +#if defined(SO_RCVBUF) +static ERL_NIF_TERM ngetopt_lvl_sock_rcvbuf(ErlNifEnv* env, + SocketDescriptor* descP); +#endif +#if defined(SO_REUSEADDR) +static ERL_NIF_TERM ngetopt_lvl_sock_reuseaddr(ErlNifEnv* env, + SocketDescriptor* descP); +#endif +#if defined(SO_SNDBUF) +static ERL_NIF_TERM ngetopt_lvl_sock_sndbuf(ErlNifEnv* env, + SocketDescriptor* descP); +#endif +static ERL_NIF_TERM ngetopt_lvl_ip(ErlNifEnv* env, + SocketDescriptor* descP, + int eOpt); +#if defined(IP_RECVTOS) +static ERL_NIF_TERM ngetopt_lvl_ip_recvtos(ErlNifEnv* env, + SocketDescriptor* descP); +#endif +#if defined(IP_ROUTER_ALERT) +static ERL_NIF_TERM ngetopt_lvl_ip_router_alert(ErlNifEnv* env, + SocketDescriptor* descP); +#endif +#if defined(IP_TOS) +static ERL_NIF_TERM ngetopt_lvl_ip_tos(ErlNifEnv* env, + SocketDescriptor* descP); +#endif +#if defined(IP_TTL) +static ERL_NIF_TERM ngetopt_lvl_ip_ttl(ErlNifEnv* env, + SocketDescriptor* descP); +#endif +#if defined(SOL_IPV6) +static ERL_NIF_TERM ngetopt_lvl_ipv6(ErlNifEnv* env, + SocketDescriptor* descP, + int eOpt); +#if defined(IPV6_HOPLIMIT) +static ERL_NIF_TERM ngetopt_lvl_ipv6_hoplimit(ErlNifEnv* env, + SocketDescriptor* descP); +#endif +#endif // defined(SOL_IPV6) +static ERL_NIF_TERM ngetopt_lvl_tcp(ErlNifEnv* env, + SocketDescriptor* descP, + int eOpt); +#if defined(TCP_CONGESTION) +static ERL_NIF_TERM ngetopt_lvl_tcp_congestion(ErlNifEnv* env, + SocketDescriptor* descP); +#endif +#if defined(TCP_MAXSEG) +static ERL_NIF_TERM ngetopt_lvl_tcp_maxseg(ErlNifEnv* env, + SocketDescriptor* descP); +#endif +#if defined(TCP_NODELAY) +static ERL_NIF_TERM ngetopt_lvl_tcp_nodelay(ErlNifEnv* env, + SocketDescriptor* descP); +#endif +static ERL_NIF_TERM ngetopt_lvl_udp(ErlNifEnv* env, + SocketDescriptor* descP, + int eOpt); +#if defined(UDP_CORK) +static ERL_NIF_TERM ngetopt_lvl_udp_cork(ErlNifEnv* env, + SocketDescriptor* descP); +#endif +#if defined(HAVE_SCTP) +static ERL_NIF_TERM ngetopt_lvl_sctp(ErlNifEnv* env, + SocketDescriptor* descP, + int eOpt); +#if defined(SCTP_AUTOCLOSE) +static ERL_NIF_TERM ngetopt_lvl_sctp_autoclose(ErlNifEnv* env, + SocketDescriptor* descP); +#endif +#if defined(SCTP_NODELAY) +static ERL_NIF_TERM ngetopt_lvl_sctp_nodelay(ErlNifEnv* env, + SocketDescriptor* descP); +#endif +#endif // defined(HAVE_SCTP) + static ERL_NIF_TERM nsetopt_str_opt(ErlNifEnv* env, SocketDescriptor* descP, int level, @@ -973,6 +1101,20 @@ static ERL_NIF_TERM nsetopt_int_opt(ErlNifEnv* env, int opt, ERL_NIF_TERM eVal); +static ERL_NIF_TERM ngetopt_str_opt(ErlNifEnv* env, + SocketDescriptor* descP, + int level, + int opt, + int max); +static ERL_NIF_TERM ngetopt_bool_opt(ErlNifEnv* env, + SocketDescriptor* descP, + int level, + int opt); +static ERL_NIF_TERM ngetopt_int_opt(ErlNifEnv* env, + SocketDescriptor* descP, + int level, + int opt); + static ERL_NIF_TERM nlink_if2idx(ErlNifEnv* env, char* ifn); static ERL_NIF_TERM nlink_idx2if(ErlNifEnv* env, @@ -1059,6 +1201,12 @@ static BOOLEAN_T decode_ip_tos(ErlNifEnv* env, static BOOLEAN_T decode_bool(ErlNifEnv* env, ERL_NIF_TERM eVal, BOOLEAN_T* val); +static BOOLEAN_T decode_native_get_opt(ErlNifEnv* env, + ERL_NIF_TERM eVal, + int* opt, + int* valueSz); +static void encode_bool(BOOLEAN_T val, ERL_NIF_TERM* eVal); +static ERL_NIF_TERM encode_ip_tos(ErlNifEnv* env, int val); static void inform_waiting_procs(ErlNifEnv* env, SocketDescriptor* descP, @@ -1189,6 +1337,11 @@ static char str_timeout[] = "timeout"; static char str_true[] = "true"; static char str_undefined[] = "undefined"; +static char str_lowdelay[] = "lowdelay"; +static char str_throughput[] = "throughput"; +static char str_reliability[] = "reliability"; +static char str_mincost[] = "mincost"; + /* (special) error string constants */ static char str_eagain[] = "eagain"; static char str_eafnosupport[] = "eafnosupport"; @@ -1217,6 +1370,11 @@ static ERL_NIF_TERM atom_timeout; static ERL_NIF_TERM atom_true; static ERL_NIF_TERM atom_undefined; +static ERL_NIF_TERM atom_lowdelay; +static ERL_NIF_TERM atom_throughput; +static ERL_NIF_TERM atom_reliability; +static ERL_NIF_TERM atom_mincost; + static ERL_NIF_TERM atom_eagain; static ERL_NIF_TERM atom_eafnosupport; static ERL_NIF_TERM atom_einval; @@ -3710,9 +3868,9 @@ ERL_NIF_TERM nsetopt_lvl_tcp(ErlNifEnv* env, #endif #if defined(TCP_MAXSEG) - case SOCKET_OPT_TCP_MAXSEG: - result = nsetopt_lvl_tcp_maxseg(env, descP, eVal); - break; + case SOCKET_OPT_TCP_MAXSEG: + result = nsetopt_lvl_tcp_maxseg(env, descP, eVal); + break; #endif #if defined(TCP_NODELAY) @@ -4074,12 +4232,12 @@ int socket_setopt(int sock, int level, int opt, int res; #if defined(IP_TOS) && defined(SOL_IP) && defined(SO_PRIORITY) - int tmpIValPRIO; - int tmpIValTOS; - int resPRIO; - int resTOS; - SOCKLEN_T tmpArgSzPRIO = sizeof(tmpIValPRIO); - SOCKLEN_T tmpArgSzTOS = sizeof(tmpIValTOS); + int tmpIValPRIO; + int tmpIValTOS; + int resPRIO; + int resTOS; + SOCKOPTLEN_T tmpArgSzPRIO = sizeof(tmpIValPRIO); + SOCKOPTLEN_T tmpArgSzTOS = sizeof(tmpIValTOS); resPRIO = sock_getopt(sock, SOL_SOCKET, SO_PRIORITY, &tmpIValPRIO, &tmpArgSzPRIO); @@ -4131,177 +4289,1009 @@ int socket_setopt(int sock, int level, int opt, /* ---------------------------------------------------------------------- - * nif_link_if2idx + * nif_getopt * * Description: - * Perform a Interface Name to Interface Index translation. + * Get socket option. + * Its possible to use a "raw" mode (not encoded). That is, we do not + * interpret level and opt. They are passed "as is" to the + * getsockopt function call. The value in this case will "copied" as + * is and provided to the user in the form of a binary. * * Arguments: - * Ifn - Interface name to be translated. + * Socket (ref) - Points to the socket descriptor. + * IsEncoded - Are the "arguments" encoded or not. + * Level - Level of the socket option. + * Opt - The socket option. */ static -ERL_NIF_TERM nif_link_if2idx(ErlNifEnv* env, - int argc, - const ERL_NIF_TERM argv[]) +ERL_NIF_TERM nif_getopt(ErlNifEnv* env, + int argc, + const ERL_NIF_TERM argv[]) { - ERL_NIF_TERM eifn; - char ifn[IF_NAMESIZE+1]; + SocketDescriptor* descP; + int eLevel, level = -1; + int eOpt; + ERL_NIF_TERM eIsEncoded; + BOOLEAN_T isEncoded, isOTP; - if (argc != 1) { - return enif_make_badarg(env); - } - eifn = argv[0]; + if ((argc != 4) || + !enif_get_resource(env, argv[0], sockets, (void**) &descP) || + !GET_INT(env, argv[2], &eLevel) || + !GET_INT(env, argv[3], &eOpt)) { + return enif_make_badarg(env); + } + eIsEncoded = argv[1]; - if (0 >= GET_STR(env, eifn, ifn, sizeof(ifn))) - return make_error2(env, atom_einval); + if (!decode_bool(env, eIsEncoded, &isEncoded)) + return make_error(env, atom_einval); - return nlink_if2idx(env, ifn); + if (!elevel2level(isEncoded, eLevel, &isOTP, &level)) + return make_error(env, atom_einval); + + return ngetopt(env, descP, isEncoded, isOTP, level, eOpt); } static -ERL_NIF_TERM nlink_if2idx(ErlNifEnv* env, - char* ifn) +ERL_NIF_TERM ngetopt(ErlNifEnv* env, + SocketDescriptor* descP, + BOOLEAN_T isEncoded, + BOOLEAN_T isOTP, + int level, + int eOpt) { - unsigned int idx = if_nametoindex(ifn); + ERL_NIF_TERM result; - if (idx == 0) - return make_error2(env, sock_errno()); - else - return make_ok2(env, idx); + if (isOTP) { + /* These are not actual socket options, + * but options for our implementation. + */ + result = ngetopt_otp(env, descP, eOpt); + } else if (!isEncoded) { + result = ngetopt_native(env, descP, level, eOpt); + } else { + result = ngetopt_level(env, descP, level, eOpt); + } + return result; } -/* ---------------------------------------------------------------------- - * nif_link_idx2if - * - * Description: - * Perform a Interface Index to Interface Name translation. - * - * Arguments: - * Idx - Interface index to be translated. +/* ngetopt_otp - Handle OTP (level) options */ - static -ERL_NIF_TERM nif_link_idx2if(ErlNifEnv* env, - int argc, - const ERL_NIF_TERM argv[]) +ERL_NIF_TERM ngetopt_otp(ErlNifEnv* env, + SocketDescriptor* descP, + int eOpt) { - unsigned int idx; + ERL_NIF_TERM result; - if ((argc != 1) || - !GET_UINT(env, argv[0], &idx)) { - return enif_make_badarg(env); + switch (eOpt) { + case SOCKET_OPT_OTP_DEBUG: + result = ngetopt_otp_debug(env, descP); + break; + + case SOCKET_OPT_OTP_IOW: + result = ngetopt_otp_iow(env, descP); + break; + + default: + result = make_error(env, atom_einval); + break; } - return nlink_idx2if(env, idx); + return result; } - +/* ngetopt_otp_debug - Handle the OTP (level) debug options + */ static -ERL_NIF_TERM nlink_idx2if(ErlNifEnv* env, - unsigned int idx) +ERL_NIF_TERM ngetopt_otp_debug(ErlNifEnv* env, + SocketDescriptor* descP) { - ERL_NIF_TERM result; - char* ifn = MALLOC(IF_NAMESIZE+1); - - if (ifn == NULL) - return enif_make_badarg(env); // PLACEHOLDER - - if (NULL == if_indextoname(idx, ifn)) { - result = make_ok2(env, MKS(env, ifn)); - } else { - result = make_error2(env, sock_errno()); - } + ERL_NIF_TERM eVal; - FREE(ifn); // OR IS THIS AUTOMATIC? + encode_bool(descP->dbg, &eVal); - return result; + return make_ok2(env, eVal); } - -/* ---------------------------------------------------------------------- - * nif_link_idx2if - * - * Description: - * Get network interface names and indexes. - * +/* ngetopt_otp_iow - Handle the OTP (level) iow options */ - static -ERL_NIF_TERM nif_link_ifs(ErlNifEnv* env, - int argc, - const ERL_NIF_TERM argv[]) +ERL_NIF_TERM ngetopt_otp_iow(ErlNifEnv* env, + SocketDescriptor* descP) { - if (argc != 0) { - return enif_make_badarg(env); - } + ERL_NIF_TERM eVal; - return nlink_ifs(env); + encode_bool(descP->iow, &eVal); + + return make_ok2(env, eVal); } +/* The option has *not* been encoded. Instead it has been provided + * in "native mode" (option is provided as is). In this case it will have the + * format: {NativeOpt :: integer(), ValueSize :: non_neg_integer()} + */ static -ERL_NIF_TERM nlink_ifs(ErlNifEnv* env) +ERL_NIF_TERM ngetopt_native(ErlNifEnv* env, + SocketDescriptor* descP, + int level, + int eOpt) { - ERL_NIF_TERM result; - struct if_nameindex* ifs = if_nameindex(); - - if (ifs == NULL) { - result = make_error2(env, sock_errno()); - } else { - /* - * We got some interfaces: - * 1) Calculate how many - the only way is to iterate through the list - * until its end (which is indicated by an entry with index = zero - * and if_name = NULL). - * 2) Allocate an ERL_NIF_TERM array of the calculated length. - * 3) Iterate through the array of interfaces and for each create - * a two tuple: {Idx, If} - * - * Or shall we instead build a list in reverse order and then when - * its done, reverse that? Check - */ - unsigned int len = nlink_ifs_length(ifs); + ERL_NIF_TERM result = enif_make_badarg(env); + int opt; + SOCKOPTLEN_T valueSz; - if (len > 0) { - ERL_NIF_TERM* array = MALLOC(len * sizeof(ERL_NIF_TERM)); - unsigned int i; + /* + * We should really make it possible to specify common specific types, + * such as integer or boolean (instead of the size)... + * + */ - for (i = 0; i < len; i++) { - array[i] = MKT2(env, - MKI(env, ifs[i].if_index), - MKS(env, ifs[i].if_name)); - } + if (decode_native_get_opt(env, eOpt, &opt, (int*) &valueSz)) { + int res; - result = make_ok2(env, MKLA(env, array, len)); - FREE(array); + if (valueSz == 0) { + res = sock_getopt(descP->sock, level, opt, NULL, NULL); + if (res != 0) + result = make_error2(env, res); + else + result = atom_ok; } else { - result = make_ok2(env, enif_make_list(env, 0)); + ErlNifBinary val; + + if (ALLOC_BIN(valueSz, &val)) { + res = sock_getopt(descP->sock, level, opt, val.data, &valueSz); + if (res != 0) { + result = make_error2(env, res); + } else { + if (valueSz < val.size) { + if (REALLOC_BIN(&val, valueSz)) { + result = make_ok2(env, MKBIN(env, &val)); + } else { + result = enif_make_badarg(env); + } + } + } + } else { + result = enif_make_badarg(env); + } } - } - if (ifs != NULL) - if_freenameindex(ifs); + } else { + result = make_error(env, atom_einval); + } return result; } +/* ngetopt_level - A "proper" level (option) has been specified + */ static -unsigned int nlink_ifs_length(struct if_nameindex* p) +ERL_NIF_TERM ngetopt_level(ErlNifEnv* env, + SocketDescriptor* descP, + int level, + int eOpt) { - unsigned int len = 0; + ERL_NIF_TERM result; - while ((p[len].if_index == 0) && (p[len].if_name == NULL)) { - len++; + switch (level) { + case SOL_SOCKET: + result = ngetopt_lvl_socket(env, descP, eOpt); + break; + +#if defined(SOL_IP) + case SOL_IP: +#else + case IPPROTO_IP: +#endif + result = ngetopt_lvl_ip(env, descP, eOpt); + break; + +#if defined(SOL_IPV6) + case SOL_IPV6: + result = ngetopt_lvl_ipv6(env, descP, eOpt); + break; +#endif + + case IPPROTO_TCP: + result = ngetopt_lvl_tcp(env, descP, eOpt); + break; + + case IPPROTO_UDP: + result = ngetopt_lvl_udp(env, descP, eOpt); + break; + +#if defined(HAVE_SCTP) + case IPPROTO_SCTP: + result = ngetopt_lvl_sctp(env, descP, eOpt); + break; +#endif + + default: + result = make_error(env, atom_einval); + break; + } + + return result; +} + + +/* ngetopt_lvl_socket - Level *SOCKET* option + */ +static +ERL_NIF_TERM ngetopt_lvl_socket(ErlNifEnv* env, + SocketDescriptor* descP, + int eOpt) +{ + ERL_NIF_TERM result; + + switch (eOpt) { +#if defined(SO_BROADCAST) + case SOCKET_OPT_SOCK_BROADCAST: + result = ngetopt_lvl_sock_broadcast(env, descP); + break; +#endif + +#if defined(SO_DONTROUTE) + case SOCKET_OPT_SOCK_DONTROUTE: + result = ngetopt_lvl_sock_dontroute(env, descP); + break; +#endif + +#if defined(SO_KEEPALIVE) + case SOCKET_OPT_SOCK_KEEPALIVE: + result = ngetopt_lvl_sock_keepalive(env, descP); + break; +#endif + +#if defined(SO_LINGER) + case SOCKET_OPT_SOCK_LINGER: + result = ngetopt_lvl_sock_linger(env, descP); + break; +#endif + +#if defined(SO_PRIORITY) + case SOCKET_OPT_SOCK_PRIORITY: + result = ngetopt_lvl_sock_priority(env, descP); + break; +#endif + +#if defined(SO_RCVBUF) + case SOCKET_OPT_SOCK_RCVBUF: + result = ngetopt_lvl_sock_rcvbuf(env, descP); + break; +#endif + +#if defined(SO_REUSEADDR) + case SOCKET_OPT_SOCK_REUSEADDR: + result = ngetopt_lvl_sock_reuseaddr(env, descP); + break; +#endif + +#if defined(SO_SNDBUF) + case SOCKET_OPT_SOCK_SNDBUF: + result = ngetopt_lvl_sock_sndbuf(env, descP); + break; +#endif + + default: + result = make_error(env, atom_einval); + break; + } + + return result; +} + + +#if defined(SO_BROADCAST) +static +ERL_NIF_TERM ngetopt_lvl_sock_broadcast(ErlNifEnv* env, + SocketDescriptor* descP) +{ + return ngetopt_bool_opt(env, descP, SOL_SOCKET, SO_BROADCAST); +} +#endif + + +#if defined(SO_DONTROUTE) +static +ERL_NIF_TERM ngetopt_lvl_sock_dontroute(ErlNifEnv* env, + SocketDescriptor* descP) +{ + return ngetopt_bool_opt(env, descP, SOL_SOCKET, SO_DONTROUTE); +} +#endif + + +#if defined(SO_KEEPALIVE) +static +ERL_NIF_TERM ngetopt_lvl_sock_keepalive(ErlNifEnv* env, + SocketDescriptor* descP) +{ + return ngetopt_bool_opt(env, descP, SOL_SOCKET, SO_KEEPALIVE); +} +#endif + + +#if defined(SO_LINGER) +static +ERL_NIF_TERM ngetopt_lvl_sock_linger(ErlNifEnv* env, + SocketDescriptor* descP) +{ + ERL_NIF_TERM result; + struct linger val; + SOCKOPTLEN_T valSz = sizeof(val); + int res; + + sys_memzero((void *) &val, sizeof(val)); + + res = sock_getopt(descP->sock, SOL_SOCKET, SO_LINGER, + &val, &valSz); + + if (res != 0) { + result = make_error2(env, res); + } else { + ERL_NIF_TERM lOnOff = MKI(env, val.l_onoff); + ERL_NIF_TERM lSecs = MKI(env, val.l_linger); + ERL_NIF_TERM linger = MKT2(env, lOnOff, lSecs); + + result = make_ok2(env, linger); + } + + return result; +} +#endif + + +#if defined(SO_PRIORITY) +static +ERL_NIF_TERM ngetopt_lvl_sock_priority(ErlNifEnv* env, + SocketDescriptor* descP) +{ + return ngetopt_int_opt(env, descP, SOL_SOCKET, SO_PRIORITY); +} +#endif + + +#if defined(SO_RCVBUF) +static +ERL_NIF_TERM ngetopt_lvl_sock_rcvbuf(ErlNifEnv* env, + SocketDescriptor* descP) +{ + return ngetopt_int_opt(env, descP, SOL_SOCKET, SO_RCVBUF); +} +#endif + + +#if defined(SO_REUSEADDR) +static +ERL_NIF_TERM ngetopt_lvl_sock_reuseaddr(ErlNifEnv* env, + SocketDescriptor* descP) +{ + return ngetopt_bool_opt(env, descP, SOL_SOCKET, SO_REUSEADDR); +} +#endif + + +#if defined(SO_SNDBUF) +static +ERL_NIF_TERM ngetopt_lvl_sock_sndbuf(ErlNifEnv* env, + SocketDescriptor* descP) +{ + return ngetopt_int_opt(env, descP, SOL_SOCKET, SO_SNDBUF); +} +#endif + + +/* ngetopt_lvl_ip - Level *IP* option(s) + */ +static +ERL_NIF_TERM ngetopt_lvl_ip(ErlNifEnv* env, + SocketDescriptor* descP, + int eOpt) +{ + ERL_NIF_TERM result; + + switch (eOpt) { +#if defined(IP_RECVTOS) + case SOCKET_OPT_IP_RECVTOS: + result = ngetopt_lvl_ip_recvtos(env, descP); + break; +#endif + +#if defined(IP_ROUTER_ALERT) + case SOCKET_OPT_IP_ROUTER_ALERT: + result = ngetopt_lvl_ip_router_alert(env, descP); + break; +#endif + +#if defined(IP_TOS) + case SOCKET_OPT_IP_TOS: + result = ngetopt_lvl_ip_tos(env, descP); + break; +#endif + +#if defined(IP_TTL) + case SOCKET_OPT_IP_TTL: + result = ngetopt_lvl_ip_ttl(env, descP); + break; +#endif + + default: + result = make_error(env, atom_einval); + break; + } + + return result; +} + + +/* ngetopt_lvl_ip_recvtos - Level IP RECVTOS option + */ +#if defined(IP_RECVTOS) +static +ERL_NIF_TERM ngetopt_lvl_ip_recvtos(ErlNifEnv* env, + SocketDescriptor* descP) +{ +#if defined(SOL_IP) + int level = SOL_IP; +#else + int level = IPPROTO_IP; +#endif + + return ngetopt_bool_opt(env, descP, level, IP_RECVTOS); +} +#endif + + +/* ngetopt_lvl_ip_router_alert - Level IP ROUTER_ALERT option + */ +#if defined(IP_ROUTER_ALERT) +static +ERL_NIF_TERM ngetopt_lvl_ip_router_alert(ErlNifEnv* env, + SocketDescriptor* descP) +{ +#if defined(SOL_IP) + int level = SOL_IP; +#else + int level = IPPROTO_IP; +#endif + + return ngetopt_int_opt(env, descP, level, IP_ROUTER_ALERT); +} +#endif + + +/* ngetopt_lvl_ip_tos - Level IP TOS option + */ +#if defined(IP_TOS) +static +ERL_NIF_TERM ngetopt_lvl_ip_tos(ErlNifEnv* env, + SocketDescriptor* descP) +{ +#if defined(SOL_IP) + int level = SOL_IP; +#else + int level = IPPROTO_IP; +#endif + ERL_NIF_TERM result; + int val; + SOCKOPTLEN_T valSz = sizeof(val); + int res; + + res = sock_getopt(descP->sock, level, IP_TOS, &val, &valSz); + + if (res != 0) { + result = make_error2(env, res); + } else { + result = encode_ip_tos(env, val); + } + + return result; +} +#endif + + +/* ngetopt_lvl_ip_ttl - Level IP TTL option + */ +#if defined(IP_TTL) +static +ERL_NIF_TERM ngetopt_lvl_ip_ttl(ErlNifEnv* env, + SocketDescriptor* descP) +{ +#if defined(SOL_IP) + int level = SOL_IP; +#else + int level = IPPROTO_IP; +#endif + + return ngetopt_int_opt(env, descP, level, IP_TTL); +} +#endif + + + +/* ngetopt_lvl_ipv6 - Level *IPv6* option(s) + */ +#if defined(SOL_IPV6) +static +ERL_NIF_TERM ngetopt_lvl_ipv6(ErlNifEnv* env, + SocketDescriptor* descP, + int eOpt) +{ + ERL_NIF_TERM result; + + switch (eOpt) { +#if defined(IPV6_HOPLIMIT) + case SOCKET_OPT_IPV6_HOPLIMIT: + result = ngetopt_lvl_ipv6_hoplimit(env, descP); + break; +#endif + + default: + result = make_error(env, atom_einval); + break; + } + + return result; +} + + +#if defined(IPV6_HOPLIMIT) +static +ERL_NIF_TERM ngetopt_lvl_ipv6_hoplimit(ErlNifEnv* env, + SocketDescriptor* descP) +{ + return ngetopt_bool_opt(env, descP, SOL_IPV6, IPV6_HOPLIMIT); +} +#endif + + +#endif // defined(SOL_IPV6) + + + +/* ngetopt_lvl_tcp - Level *TCP* option(s) + */ +static +ERL_NIF_TERM ngetopt_lvl_tcp(ErlNifEnv* env, + SocketDescriptor* descP, + int eOpt) +{ + ERL_NIF_TERM result; + + switch (eOpt) { +#if defined(TCP_CONGESTION) + case SOCKET_OPT_TCP_CONGESTION: + result = ngetopt_lvl_tcp_congestion(env, descP); + break; +#endif + +#if defined(TCP_MAXSEG) + case SOCKET_OPT_TCP_MAXSEG: + result = ngetopt_lvl_tcp_maxseg(env, descP); + break; +#endif + +#if defined(TCP_NODELAY) + case SOCKET_OPT_TCP_NODELAY: + result = ngetopt_lvl_tcp_nodelay(env, descP); + break; +#endif + + default: + result = make_error(env, atom_einval); + break; + } + + return result; +} + + +/* ngetopt_lvl_tcp_congestion - Level TCP CONGESTION option + */ +#if defined(TCP_CONGESTION) +static +ERL_NIF_TERM ngetopt_lvl_tcp_congestion(ErlNifEnv* env, + SocketDescriptor* descP) +{ + int max = SOCKET_OPT_TCP_CONGESTION_NAME_MAX+1; + + return ngetopt_str_opt(env, descP, IPPROTO_TCP, TCP_CONGESTION, max); +} +#endif + + +/* ngetopt_lvl_tcp_maxseg - Level TCP MAXSEG option + */ +#if defined(TCP_MAXSEG) +static +ERL_NIF_TERM ngetopt_lvl_tcp_maxseg(ErlNifEnv* env, + SocketDescriptor* descP) +{ + return ngetopt_int_opt(env, descP, IPPROTO_TCP, TCP_MAXSEG); +} +#endif + + +/* ngetopt_lvl_tcp_nodelay - Level TCP NODELAY option + */ +#if defined(TCP_NODELAY) +static +ERL_NIF_TERM ngetopt_lvl_tcp_nodelay(ErlNifEnv* env, + SocketDescriptor* descP) +{ + return ngetopt_bool_opt(env, descP, IPPROTO_TCP, TCP_NODELAY); +} +#endif + + + +/* ngetopt_lvl_udp - Level *UDP* option(s) + */ +static +ERL_NIF_TERM ngetopt_lvl_udp(ErlNifEnv* env, + SocketDescriptor* descP, + int eOpt) +{ + ERL_NIF_TERM result; + + switch (eOpt) { +#if defined(UDP_CORK) + case SOCKET_OPT_UDP_CORK: + result = ngetopt_lvl_udp_cork(env, descP); + break; +#endif + + default: + result = make_error(env, atom_einval); + break; + } + + return result; +} + + +/* ngetopt_lvl_udp_cork - Level UDP CORK option + */ +#if defined(UDP_CORK) +static +ERL_NIF_TERM ngetopt_lvl_udp_cork(ErlNifEnv* env, + SocketDescriptor* descP) +{ + return ngetopt_bool_opt(env, descP, IPPROTO_UDP, UDP_CORK); +} +#endif + + + +/* ngetopt_lvl_sctp - Level *SCTP* option(s) + */ +#if defined(HAVE_SCTP) +static +ERL_NIF_TERM ngetopt_lvl_sctp(ErlNifEnv* env, + SocketDescriptor* descP, + int eOpt) +{ + ERL_NIF_TERM result; + + switch (eOpt) { +#if defined(SCTP_AUTOCLOSE) + case SOCKET_OPT_SCTP_AUTOCLOSE: + result = ngetopt_lvl_sctp_autoclose(env, descP); + break; +#endif + +#if defined(SCTP_NODELAY) + case SOCKET_OPT_SCTP_NODELAY: + result = ngetopt_lvl_sctp_nodelay(env, descP); + break; +#endif + + default: + result = make_error(env, atom_einval); + break; + } + + return result; +} + + +/* ngetopt_lvl_sctp_autoclose - Level SCTP AUTOCLOSE option + */ +#if defined(SCTP_AUTOCLOSE) +static +ERL_NIF_TERM ngetopt_lvl_sctp_autoclose(ErlNifEnv* env, + SocketDescriptor* descP) +{ + return ngetopt_int_opt(env, descP, IPPROTO_SCTP, SCTP_AUTOCLOSE); +} +#endif + + +/* ngetopt_lvl_sctp_nodelay - Level SCTP NODELAY option + */ +#if defined(SCTP_NODELAY) +static +ERL_NIF_TERM ngetopt_lvl_sctp_nodelay(ErlNifEnv* env, + SocketDescriptor* descP) +{ + return ngetopt_bool_opt(env, descP, IPPROTO_SCTP, SCTP_NODELAY); +} +#endif + + +#endif // defined(HAVE_SCTP) + + + +/* ngetopt_str_opt - get an string option + */ +static +ERL_NIF_TERM ngetopt_str_opt(ErlNifEnv* env, + SocketDescriptor* descP, + int level, + int opt, + int max) +{ + ERL_NIF_TERM result; + char* val = MALLOC(max); + SOCKOPTLEN_T valSz = max; + int res; + + res = sock_getopt(descP->sock, level, opt, &val, &valSz); + + if (res != 0) { + result = make_error2(env, res); + } else { + ERL_NIF_TERM sval = MKSL(env, val, valSz); + + result = make_ok2(env, sval); + } + + FREE(val); + + return result; +} + + +/* nsetopt_bool_opt - get an (integer) bool option + */ +static +ERL_NIF_TERM ngetopt_bool_opt(ErlNifEnv* env, + SocketDescriptor* descP, + int level, + int opt) +{ + ERL_NIF_TERM result; + int val; + SOCKOPTLEN_T valSz = sizeof(val); + int res; + + res = sock_getopt(descP->sock, level, opt, &val, &valSz); + + if (res != 0) { + result = make_error2(env, res); + } else { + ERL_NIF_TERM bval = ((val) ? atom_true : atom_false); + + result = make_ok2(env, bval); + } + + return result; +} + + +/* nsetopt_int_opt - get an integer option + */ +static +ERL_NIF_TERM ngetopt_int_opt(ErlNifEnv* env, + SocketDescriptor* descP, + int level, + int opt) +{ + ERL_NIF_TERM result; + int val; + SOCKOPTLEN_T valSz = sizeof(val); + int res; + + res = sock_getopt(descP->sock, level, opt, &val, &valSz); + + if (res != 0) { + result = make_error2(env, res); + } else { + result = make_ok2(env, MKI(env, val)); + } + + return result; +} + + + + +/* ---------------------------------------------------------------------- + * nif_link_if2idx + * + * Description: + * Perform a Interface Name to Interface Index translation. + * + * Arguments: + * Ifn - Interface name to be translated. + */ + +static +ERL_NIF_TERM nif_link_if2idx(ErlNifEnv* env, + int argc, + const ERL_NIF_TERM argv[]) +{ + ERL_NIF_TERM eifn; + char ifn[IF_NAMESIZE+1]; + + if (argc != 1) { + return enif_make_badarg(env); + } + eifn = argv[0]; + + if (0 >= GET_STR(env, eifn, ifn, sizeof(ifn))) + return make_error2(env, atom_einval); + + return nlink_if2idx(env, ifn); +} + + + +static +ERL_NIF_TERM nlink_if2idx(ErlNifEnv* env, + char* ifn) +{ + unsigned int idx = if_nametoindex(ifn); + + if (idx == 0) + return make_error2(env, sock_errno()); + else + return make_ok2(env, idx); + +} + + + +/* ---------------------------------------------------------------------- + * nif_link_idx2if + * + * Description: + * Perform a Interface Index to Interface Name translation. + * + * Arguments: + * Idx - Interface index to be translated. + */ + +static +ERL_NIF_TERM nif_link_idx2if(ErlNifEnv* env, + int argc, + const ERL_NIF_TERM argv[]) +{ + unsigned int idx; + + if ((argc != 1) || + !GET_UINT(env, argv[0], &idx)) { + return enif_make_badarg(env); + } + + return nlink_idx2if(env, idx); +} + + + +static +ERL_NIF_TERM nlink_idx2if(ErlNifEnv* env, + unsigned int idx) +{ + ERL_NIF_TERM result; + char* ifn = MALLOC(IF_NAMESIZE+1); + + if (ifn == NULL) + return enif_make_badarg(env); // PLACEHOLDER + + if (NULL == if_indextoname(idx, ifn)) { + result = make_ok2(env, MKS(env, ifn)); + } else { + result = make_error2(env, sock_errno()); + } + + FREE(ifn); // OR IS THIS AUTOMATIC? + + return result; +} + + + +/* ---------------------------------------------------------------------- + * nif_link_idx2if + * + * Description: + * Get network interface names and indexes. + * + */ + +static +ERL_NIF_TERM nif_link_ifs(ErlNifEnv* env, + int argc, + const ERL_NIF_TERM argv[]) +{ + if (argc != 0) { + return enif_make_badarg(env); + } + + return nlink_ifs(env); +} + + + +static +ERL_NIF_TERM nlink_ifs(ErlNifEnv* env) +{ + ERL_NIF_TERM result; + struct if_nameindex* ifs = if_nameindex(); + + if (ifs == NULL) { + result = make_error2(env, sock_errno()); + } else { + /* + * We got some interfaces: + * 1) Calculate how many - the only way is to iterate through the list + * until its end (which is indicated by an entry with index = zero + * and if_name = NULL). + * 2) Allocate an ERL_NIF_TERM array of the calculated length. + * 3) Iterate through the array of interfaces and for each create + * a two tuple: {Idx, If} + * + * Or shall we instead build a list in reverse order and then when + * its done, reverse that? Check + */ + unsigned int len = nlink_ifs_length(ifs); + + if (len > 0) { + ERL_NIF_TERM* array = MALLOC(len * sizeof(ERL_NIF_TERM)); + unsigned int i; + + for (i = 0; i < len; i++) { + array[i] = MKT2(env, + MKI(env, ifs[i].if_index), + MKS(env, ifs[i].if_name)); + } + + result = make_ok2(env, MKLA(env, array, len)); + FREE(array); + } else { + result = make_ok2(env, enif_make_list(env, 0)); + } + } + + if (ifs != NULL) + if_freenameindex(ifs); + + return result; +} + + +static +unsigned int nlink_ifs_length(struct if_nameindex* p) +{ + unsigned int len = 0; + + while ((p[len].if_index == 0) && (p[len].if_name == NULL)) { + len++; } return len; @@ -4977,7 +5967,6 @@ BOOLEAN_T decode_bool(ErlNifEnv* env, ERL_NIF_TERM eVal, BOOLEAN_T* val) } - /* +++ decode the linger value +++ * The (socket) linger option is provided as a two tuple: * @@ -5015,7 +6004,7 @@ BOOLEAN_T decode_sock_linger(ErlNifEnv* env, ERL_NIF_TERM eVal, struct linger* v -/* +++ decocde the ip socket option tos +++ +/* +++ decode the ip socket option tos +++ * The (ip) option can be provide in two ways: * * atom() | integer() @@ -5032,11 +6021,11 @@ BOOLEAN_T decode_ip_tos(ErlNifEnv* env, ERL_NIF_TERM eVal, int* val) if (IS_ATOM(env, eVal)) { unsigned int len; - char b[sizeof("reliability")+1]; // Just in case... + char b[sizeof(str_reliability)+1]; // Just in case... if (!(GET_ATOM_LEN(env, eVal, &len) && (len > 0) && - (len <= (sizeof("reliability"))))) { + (len <= (sizeof(str_reliability))))) { *val = -1; return FALSE; } @@ -5046,16 +6035,16 @@ BOOLEAN_T decode_ip_tos(ErlNifEnv* env, ERL_NIF_TERM eVal, int* val) return FALSE; } - if (strncmp(b, "lowdelay", len) == 0) { + if (strncmp(b, str_lowdelay, len) == 0) { *val = IPTOS_LOWDELAY; result = TRUE; - } else if (strncmp(b, "throughput", len) == 0) { + } else if (strncmp(b, str_throughput, len) == 0) { *val = IPTOS_THROUGHPUT; result = TRUE; - } else if (strncmp(b, "reliability", len) == 0) { + } else if (strncmp(b, str_reliability, len) == 0) { *val = IPTOS_RELIABILITY; result = TRUE; - } else if (strncmp(b, "mincost", len) == 0) { + } else if (strncmp(b, str_mincost, len) == 0) { *val = IPTOS_MINCOST; result = TRUE; } else { @@ -5082,6 +6071,92 @@ BOOLEAN_T decode_ip_tos(ErlNifEnv* env, ERL_NIF_TERM eVal, int* val) +/* +++ decocde the native getopt option +++ + * The option is in this case provide in the form of a two tuple: + * + * {NativeOpt, ValueSize} + * + * NativeOpt :: integer() + * ValueSize :: non_neg_integer() + * + */ +static +BOOLEAN_T decode_native_get_opt(ErlNifEnv* env, ERL_NIF_TERM eVal, + int* opt, int* valueSz) +{ + const ERL_NIF_TERM* nativeOptT; + int nativeOptTSz; + + /* First, get the tuple and verify its size (2) */ + + if (!GET_TUPLE(env, eVal, &nativeOptTSz, &nativeOptT)) + return FALSE; + + if (nativeOptTSz != 2) + return FALSE; + + /* So far so good. Both elements should be integers */ + + if (!GET_INT(env, nativeOptT[0], opt)) + return FALSE; + + if (!GET_INT(env, nativeOptT[1], valueSz)) + return FALSE; + + return TRUE; +} + + +static +void encode_bool(BOOLEAN_T val, ERL_NIF_TERM* eVal) +{ + if (val) + *eVal = atom_true; + else + *eVal = atom_false; +} + + +/* +++ encode the ip socket option tos +++ + * The (ip) option can be provide as: + * + * lowdelay | throughput | reliability | mincost | integer() + * + */ +static +ERL_NIF_TERM encode_ip_tos(ErlNifEnv* env, int val) +{ + ERL_NIF_TERM result; + + switch (val) { + case IPTOS_LOWDELAY: + result = make_ok2(env, atom_lowdelay); + break; + + case IPTOS_THROUGHPUT: + result = make_ok2(env, atom_throughput); + break; + + case IPTOS_RELIABILITY: + result = make_ok2(env, atom_reliability); + break; + + case IPTOS_MINCOST: + result = make_ok2(env, atom_mincost); + break; + + default: + result = make_ok2(env, MKI(env, val)); + break; + } + + return result; +} + + + + + /* *** alloc_descriptor *** * Allocate and perform basic initialization of a socket descriptor. * @@ -5999,6 +7074,11 @@ int on_load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info) atom_undefined = MKA(env, str_undefined); // atom_version = MKA(env, str_version); + atom_lowdelay = MKA(env, str_lowdelay); + atom_throughput = MKA(env, str_throughput); + atom_reliability = MKA(env, str_reliability); + atom_mincost = MKA(env, str_mincost); + /* Error codes */ atom_eagain = MKA(env, str_eagain); atom_eafnosupport = MKA(env, str_eafnosupport); diff --git a/erts/preloaded/src/socket.erl b/erts/preloaded/src/socket.erl index 7b53f271ef..f8aa75bf8f 100644 --- a/erts/preloaded/src/socket.erl +++ b/erts/preloaded/src/socket.erl @@ -272,6 +272,7 @@ v6only. -type tcp_socket_option() :: congestion | + cork | maxseg | nodelay. @@ -451,10 +452,11 @@ -define(SOCKET_OPT_IPV6_HOPLIMIT, 12). -define(SOCKET_OPT_TCP_CONGESTION, 0). --define(SOCKET_OPT_TCP_MAXSEG, 1). --define(SOCKET_OPT_TCP_NODELAY, 2). +-define(SOCKET_OPT_TCP_CORK, 2). +-define(SOCKET_OPT_TCP_MAXSEG, 3). +-define(SOCKET_OPT_TCP_NODELAY, 4). --define(SOCKET_OPT_UDP_CORK, 1). +-define(SOCKET_OPT_UDP_CORK, 0). -define(SOCKET_OPT_SCTP_AUTOCLOSE, 7). -define(SOCKET_OPT_SCTP_NODELAY, 22). @@ -1370,6 +1372,10 @@ setopt(#socket{info = Info, ref = SockRef}, Level, Key, Value) -> %% If its an "invalid" option, we should not crash but return some %% useful error... %% +%% When specifying level as an integer, and therefor using "native mode", +%% we should make it possible to specify common types instead of the +%% value size. Example: int | bool | {string, pos_integer()} | non_neg_integer() +%% -spec getopt(Socket, Level, Key) -> {ok, Value} | {error, Reason} when Socket :: socket(), @@ -1412,7 +1418,15 @@ setopt(#socket{info = Info, ref = SockRef}, Level, Key, Value) -> Level :: sctp, Key :: sctp_socket_option(), Value :: term(), - Reason :: term(). + Reason :: term() + ; (Socket, Level, Key) -> ok | {ok, Value} | {error, Reason} when + Socket :: socket(), + Level :: integer(), + Key :: {NativeOpt, ValueSize}, + NativeOpt :: integer(), + ValueSize :: non_neg_integer(), + Value :: term(), + Reason :: term(). getopt(#socket{info = Info, ref = SockRef}, Level, Key) -> try @@ -1425,6 +1439,8 @@ getopt(#socket{info = Info, ref = SockRef}, Level, Key) -> %% We may need to decode the value (for the same reason %% we needed to encode the value for setopt). case nif_getopt(SockRef, EIsEncoded, ELevel, EKey) of + ok -> + ok; {ok, EVal} -> Val = dec_getopt_value(Level, Key, EVal, Domain, Type, Protocol), @@ -1716,10 +1732,14 @@ enc_getopt_key(Level, Opt, Domain, Type, Protocol) -> %% +++ Decode getopt value +++ +%% +%% For the most part, we simply let the value pass through, but for some +%% values we do an actual decode. +%% -%% We should ...really... do something with the domain, type and protocol args... -dec_getopt_value(otp, debug, B, _, _, _) when is_boolean(B) -> - B. +%% Let the user deal with this... +dec_getopt_value(_L, _Opt, V, _D, _T, _P) -> + V. @@ -1731,64 +1751,74 @@ dec_getopt_value(otp, debug, B, _, _, _) when is_boolean(B) -> Direction, Domain, Type, Protocol) -> non_neg_integer() when Level :: otp, - Opt :: otp_socket_option(), Direction :: set | get, + Opt :: otp_socket_option(), Domain :: domain(), Type :: type(), Protocol :: protocol() ; (Level, Direction, Opt, Domain, Type, Protocol) -> non_neg_integer() when Level :: socket, - Opt :: socket_option(), Direction :: set | get, + Opt :: socket_option(), Domain :: domain(), Type :: type(), Protocol :: protocol() ; (Level, Direction, Opt, Domain, Type, Protocol) -> non_neg_integer() when Level :: ip, - Opt :: ip_socket_option(), Direction :: set | get, + Opt :: ip_socket_option(), Domain :: domain(), Type :: type(), Protocol :: protocol() ; (Level, Direction, Opt, Domain, Type, Protocol) -> non_neg_integer() when Level :: ipv6, - Opt :: ipv6_socket_option(), Direction :: set | get, + Opt :: ipv6_socket_option(), Domain :: domain(), Type :: type(), Protocol :: protocol() ; (Level, Direction, Opt, Domain, Type, Protocol) -> non_neg_integer() when Level :: tcp, - Opt :: tcp_socket_option(), Direction :: set | get, + Opt :: tcp_socket_option(), Domain :: domain(), Type :: type(), Protocol :: protocol() ; (Level, Direction, Opt, Domain, Type, Protocol) -> non_neg_integer() when Level :: udp, - Opt :: udp_socket_option(), Direction :: set | get, + Opt :: udp_socket_option(), Domain :: domain(), Type :: type(), Protocol :: protocol() ; (Level, Direction, Opt, Domain, Type, Protocol) -> non_neg_integer() when Level :: sctp, - Opt :: sctp_socket_option(), Direction :: set | get, + Opt :: sctp_socket_option(), Domain :: domain(), Type :: type(), Protocol :: protocol() ; (Level, Direction, Opt, Domain, Type, Protocol) -> non_neg_integer() when Level :: integer(), + Direction :: set, Opt :: integer(), - Direction :: set | get, + Domain :: domain(), + Type :: type(), + Protocol :: protocol() + ; (Level, Direction, Opt, + Domain, Type, Protocol) -> non_neg_integer() when + Level :: integer(), + Direction :: get, + Opt :: {NativeOpt, ValueSize}, + NativeOpt :: integer(), + ValueSize :: non_neg_integer(), Domain :: domain(), Type :: type(), Protocol :: protocol(). @@ -1960,6 +1990,8 @@ enc_sockopt_key(ipv6, UnknownOpt, _Dir, _D, _T, _P) -> %% but they are difficult to get portable... enc_sockopt_key(tcp, congestion = _Opt, _Dir, _D, _T, _P) -> ?SOCKET_OPT_TCP_CONGESTION; +enc_sockopt_key(tcp, cork = Opt, _Dir, _D, _T, _P) -> + not_supported(Opt); enc_sockopt_key(tcp, maxseg = _Opt, _Dir, _D, _T, _P) -> ?SOCKET_OPT_TCP_MAXSEG; enc_sockopt_key(tcp, nodelay = _Opt, _Dir, _D, _T, _P) -> @@ -1981,10 +2013,15 @@ enc_sockopt_key(sctp, nodelay = _Opt, _Dir, _D, _T, _P) -> enc_sockopt_key(sctp, UnknownOpt, _Dir, _D, _T, _P) -> unknown(UnknownOpt); -%% +++ Plain socket options +++ -enc_sockopt_key(Level, Opt, _Dir, _D, _T, _P) +%% +++ "Native" socket options +++ +enc_sockopt_key(Level, Opt, set = _Dir, _D, _T, _P) when is_integer(Level) andalso is_integer(Opt) -> Opt; +enc_sockopt_key(Level, {NativeOpt, ValueSize} = Opt, get = _Dir, _D, _T, _P) + when is_integer(Level) andalso + is_integer(NativeOpt) andalso + is_integer(ValueSize) andalso (ValueSize >= 0) -> + Opt; enc_sockopt_key(Level, Opt, _Dir, _Domain, _Type, _Protocol) -> unknown({Level, Opt}). -- cgit v1.2.3 From 993f22cab851d704c40f04cda5d5c3730539a6f8 Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Thu, 17 May 2018 15:16:57 +0200 Subject: [socket-nif] More getopt Its now possible to specify some "base" type(s) when retreiving an "native" option (base type instead of value size). --- erts/emulator/nifs/common/socket_nif.c | 135 +++++++++++++++++++++++++-------- 1 file changed, 105 insertions(+), 30 deletions(-) diff --git a/erts/emulator/nifs/common/socket_nif.c b/erts/emulator/nifs/common/socket_nif.c index d87492dd54..c63eff40ec 100644 --- a/erts/emulator/nifs/common/socket_nif.c +++ b/erts/emulator/nifs/common/socket_nif.c @@ -295,6 +295,10 @@ typedef unsigned long long llu_t; #define SOCKET_RECV_BUFFER_SIZE_DEFAULT 2048 +#define SOCKET_OPT_VALLUE_TYPE_UNSPEC 0 +#define SOCKET_OPT_VALLUE_TYPE_INT 1 +#define SOCKET_OPT_VALLUE_TYPE_BOOL 2 + typedef union { struct { unsigned int open:1; @@ -981,6 +985,11 @@ static ERL_NIF_TERM ngetopt_native(ErlNifEnv* env, SocketDescriptor* descP, int level, int eOpt); +static ERL_NIF_TERM ngetopt_native_unspec(ErlNifEnv* env, + SocketDescriptor* descP, + int level, + int opt, + SOCKOPTLEN_T valueSz); static ERL_NIF_TERM ngetopt_level(ErlNifEnv* env, SocketDescriptor* descP, int level, @@ -1204,6 +1213,7 @@ static BOOLEAN_T decode_bool(ErlNifEnv* env, static BOOLEAN_T decode_native_get_opt(ErlNifEnv* env, ERL_NIF_TERM eVal, int* opt, + uint16_t* valueType, int* valueSz); static void encode_bool(BOOLEAN_T val, ERL_NIF_TERM* eVal); static ERL_NIF_TERM encode_ip_tos(ErlNifEnv* env, int val); @@ -4429,6 +4439,7 @@ ERL_NIF_TERM ngetopt_native(ErlNifEnv* env, { ERL_NIF_TERM result = enif_make_badarg(env); int opt; + uint16_t valueType; SOCKOPTLEN_T valueSz; /* @@ -4437,44 +4448,71 @@ ERL_NIF_TERM ngetopt_native(ErlNifEnv* env, * */ - if (decode_native_get_opt(env, eOpt, &opt, (int*) &valueSz)) { - int res; + if (decode_native_get_opt(env, eOpt, &opt, &valueType, (int*) &valueSz)) { + switch (valueType) { + case SOCKET_OPT_VALLUE_TYPE_UNSPEC: + result = ngetopt_native_unspec(env, descP, level, opt, valueSz); + break; + case SOCKET_OPT_VALLUE_TYPE_INT: + result = ngetopt_int_opt(env, descP, level, opt); + break; + case SOCKET_OPT_VALLUE_TYPE_BOOL: + result = ngetopt_bool_opt(env, descP, level, opt); + break; + default: + result = make_error(env, atom_einval); + break; + } + } else { + result = make_error(env, atom_einval); + } - if (valueSz == 0) { - res = sock_getopt(descP->sock, level, opt, NULL, NULL); - if (res != 0) - result = make_error2(env, res); - else - result = atom_ok; - } else { - ErlNifBinary val; + return result; +} - if (ALLOC_BIN(valueSz, &val)) { - res = sock_getopt(descP->sock, level, opt, val.data, &valueSz); - if (res != 0) { - result = make_error2(env, res); - } else { - if (valueSz < val.size) { - if (REALLOC_BIN(&val, valueSz)) { - result = make_ok2(env, MKBIN(env, &val)); - } else { - result = enif_make_badarg(env); - } + +static +ERL_NIF_TERM ngetopt_native_unspec(ErlNifEnv* env, + SocketDescriptor* descP, + int level, + int opt, + SOCKOPTLEN_T valueSz) +{ + ERL_NIF_TERM result = enif_make_badarg(env); + int res; + + if (valueSz == 0) { + res = sock_getopt(descP->sock, level, opt, NULL, NULL); + if (res != 0) + result = make_error2(env, res); + else + result = atom_ok; + } else { + ErlNifBinary val; + + if (ALLOC_BIN(valueSz, &val)) { + res = sock_getopt(descP->sock, level, opt, val.data, &valueSz); + if (res != 0) { + result = make_error2(env, res); + } else { + if (valueSz < val.size) { + if (REALLOC_BIN(&val, valueSz)) { + result = make_ok2(env, MKBIN(env, &val)); + } else { + result = enif_make_badarg(env); } } - } else { - result = enif_make_badarg(env); } + } else { + result = enif_make_badarg(env); } - - } else { - result = make_error(env, atom_einval); } return result; } + /* ngetopt_level - A "proper" level (option) has been specified */ static @@ -6077,12 +6115,12 @@ BOOLEAN_T decode_ip_tos(ErlNifEnv* env, ERL_NIF_TERM eVal, int* val) * {NativeOpt, ValueSize} * * NativeOpt :: integer() - * ValueSize :: non_neg_integer() + * ValueSize :: int | bool | non_neg_integer() * */ static BOOLEAN_T decode_native_get_opt(ErlNifEnv* env, ERL_NIF_TERM eVal, - int* opt, int* valueSz) + int* opt, uint16_t* valueType, int* valueSz) { const ERL_NIF_TERM* nativeOptT; int nativeOptTSz; @@ -6095,13 +6133,50 @@ BOOLEAN_T decode_native_get_opt(ErlNifEnv* env, ERL_NIF_TERM eVal, if (nativeOptTSz != 2) return FALSE; - /* So far so good. Both elements should be integers */ + /* So far so good. + * First element is an integer. + * Second element is an atom or an integer. + * The only "types" that we support at the moment are: + * + * bool - Which is actually a integer + * (but will be *returned* as a boolean()) + * int - Just short for integer + */ if (!GET_INT(env, nativeOptT[0], opt)) return FALSE; - if (!GET_INT(env, nativeOptT[1], valueSz)) + if (IS_ATOM(env, nativeOptT[1])) { + unsigned int len; + char t[16]; // Just in case + + if (!(GET_ATOM_LEN(env, nativeOptT[1], &len) && + (len > 0) && + (len <= (sizeof("bool"))))) + return FALSE; + + if (!GET_ATOM(env, nativeOptT[1], t, sizeof(t))) + return FALSE; + + if (strncmp(t, "bool", len) == 0) { + *valueType = SOCKET_OPT_VALLUE_TYPE_BOOL; + *valueSz = sizeof(int); // Just to be sure + } else if (strncmp(t, "int", len) == 0) { + *valueType = SOCKET_OPT_VALLUE_TYPE_INT; + *valueSz = sizeof(int); // Just to be sure + } else { + return FALSE; + } + + } else if (IS_NUM(env, nativeOptT[1])) { + if (GET_INT(env, nativeOptT[1], valueSz)) { + *valueType = SOCKET_OPT_VALLUE_TYPE_UNSPEC; + } else { + return FALSE; + } + } else { return FALSE; + } return TRUE; } -- cgit v1.2.3 From 4f165a244cb23405ed7f607836098f180f1be08b Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Thu, 17 May 2018 15:21:59 +0200 Subject: [socket-nif] More getopt Added guards and type specs for native getopt. --- erts/preloaded/src/socket.erl | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/erts/preloaded/src/socket.erl b/erts/preloaded/src/socket.erl index f8aa75bf8f..a67a019b80 100644 --- a/erts/preloaded/src/socket.erl +++ b/erts/preloaded/src/socket.erl @@ -1424,7 +1424,7 @@ setopt(#socket{info = Info, ref = SockRef}, Level, Key, Value) -> Level :: integer(), Key :: {NativeOpt, ValueSize}, NativeOpt :: integer(), - ValueSize :: non_neg_integer(), + ValueSize :: int | bool | non_neg_integer(), Value :: term(), Reason :: term(). @@ -1437,7 +1437,7 @@ getopt(#socket{info = Info, ref = SockRef}, Level, Key) -> {EIsEncoded, ELevel} = enc_getopt_level(Level), EKey = enc_getopt_key(Level, Key, Domain, Type, Protocol), %% We may need to decode the value (for the same reason - %% we needed to encode the value for setopt). + %% we (may have) needed to encode the value for setopt). case nif_getopt(SockRef, EIsEncoded, ELevel, EKey) of ok -> ok; @@ -2020,7 +2020,8 @@ enc_sockopt_key(Level, Opt, set = _Dir, _D, _T, _P) enc_sockopt_key(Level, {NativeOpt, ValueSize} = Opt, get = _Dir, _D, _T, _P) when is_integer(Level) andalso is_integer(NativeOpt) andalso - is_integer(ValueSize) andalso (ValueSize >= 0) -> + ((is_integer(ValueSize) andalso (ValueSize >= 0)) orelse + ((ValueSize =:= int) orelse (ValueSize =:= bool))) -> Opt; enc_sockopt_key(Level, Opt, _Dir, _Domain, _Type, _Protocol) -> -- cgit v1.2.3 From 7ce277e9635830c6d5c56ba02e3346a6496dec09 Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Tue, 12 Jun 2018 15:59:32 +0200 Subject: [socket-nif] Updated socket preloaded beam --- erts/preloaded/ebin/socket.beam | Bin 0 -> 19820 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 erts/preloaded/ebin/socket.beam diff --git a/erts/preloaded/ebin/socket.beam b/erts/preloaded/ebin/socket.beam new file mode 100644 index 0000000000..532048ac84 Binary files /dev/null and b/erts/preloaded/ebin/socket.beam differ -- cgit v1.2.3 From 63338250778d2caad08aa3180b372e5260f22aa7 Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Thu, 31 May 2018 10:58:19 +0200 Subject: [socket-nif-doc] Add preliminary doc for socket The doc now builds. Had to update the code (spec and types) to match. Though, te result is less then stellar. OTP-14831 --- erts/doc/src/Makefile | 6 +- erts/doc/src/ref_man.xml | 3 +- erts/doc/src/socket.xml | 308 +++++++++++++++++++ erts/doc/src/specs.xml | 1 + erts/emulator/nifs/common/socket_nif.c | 535 +++++++++++++++++++++++++++------ erts/preloaded/src/socket.erl | 152 +++++----- 6 files changed, 821 insertions(+), 184 deletions(-) create mode 100644 erts/doc/src/socket.xml diff --git a/erts/doc/src/Makefile b/erts/doc/src/Makefile index 21aa3db864..1540344fde 100644 --- a/erts/doc/src/Makefile +++ b/erts/doc/src/Makefile @@ -52,7 +52,8 @@ XML_REF3_EFILES = \ erlang.xml \ erl_tracer.xml \ init.xml \ - zlib.xml + zlib.xml \ + socket.xml XML_REF3_FILES = \ driver_entry.xml \ @@ -63,7 +64,8 @@ XML_REF3_FILES = \ erlang.xml \ erts_alloc.xml \ init.xml \ - zlib.xml + zlib.xml \ + socket.xml XML_PART_FILES = \ part.xml diff --git a/erts/doc/src/ref_man.xml b/erts/doc/src/ref_man.xml index 0617463a7b..da099dd5bb 100644 --- a/erts/doc/src/ref_man.xml +++ b/erts/doc/src/ref_man.xml @@ -4,7 +4,7 @@
- 19962016 + 19962018 Ericsson AB. All Rights Reserved. @@ -35,6 +35,7 @@ + diff --git a/erts/doc/src/socket.xml b/erts/doc/src/socket.xml new file mode 100644 index 0000000000..9b487172c5 --- /dev/null +++ b/erts/doc/src/socket.xml @@ -0,0 +1,308 @@ + + + + +
+ + 20182018 + Ericsson AB. All Rights Reserved. + + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + + + socket + + + + + socket.xml +
+ socket + Socket interface. + +

This module provides an API for the socket interface. + It is used to create, delete and manipulate sockets.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Accept a connection on a socket. + +

Accept a connection on a socket.

+

This call is used with connection-based socket types + (stream or seqpacket). It extracs the first pending connection + request for the listen socket and returns the (newly) connected + socket.

+
+
+ + + + Bind a name to a socket. + +

Bind a name to a socket.

+

When a socket is created + (with open), + it has no address assigned to it. bind assigns the + address specified by the Addr argument.

+

The rules used for name binding vary between domains.

+
+
+ + + + Close a socket. + +

Closes the socket.

+
+
+ + + + + Initiate a connection on a socket. + +

This function connects the socket to the address + specied by the Addr argument.

+
+
+ + + + + + + + + + + Get an option on a socket. + +

Get an option on a socket.

+

What properties are valid depend on what kind of socket + it is (domain, type and protocol).

+

When specifying Level as an integer, and therefor + using "native mode", it is *currently* up to the caller to + know how to interpret the result.

+ +

Not all options are valid on all platforms. That is, + even if "we" support an option, that does not mean that the + underlying OS does.

+
+
+ + + + + Listen for connections on a socket. + +

Listen for connections on a socket.

+
+
+ + + + + + Create an endpoint for communication. + +

Creates an endpoint (socket) for communication.

+

For some types there is a default protocol, which will + be used if no protocol is specified:

+ + stream + +

tcp

+
+ dgram + +

udp

+
+ seqpacket + +

sctp

+
+
+
+
+ + + + + + Receive a message from a socket. + +

Receive a message from a socket.

+

There is a special case for the argument Length. + If it is set to zero (0), it means "give me everything you + currently have".

+
+
+ + + + + + + Receive a message from a socket. + +

Receive a message from a socket.

+

This function reads "messages", which means that regardless of + how much we want to read, it returns when we get a message.

+

The MaxSize argument basically defines the size of the + receive buffer. By setting the value to zero (0), the configured + size (setopt) is used.

+

It may be impossible to know what (buffer) size is appropriate + "in advance", and in those cases it may be convenient to use the + (recv) 'peek' flag. When this flag is provided the message is *not* + "consumed" from the underlying buffers, so another recvfrom call + is needed, possibly with a then adjusted buffer size.

+
+
+ + + + + + Send a message on a socket. + +

Send a message on a connected socket.

+
+
+ + + + + Send a message on a socket. + +

Send a message on a socket, to the specified destination.

+
+
+ + + + + + + + + + Set options on a socket. + +

Set options on a socket.

+

What properties are valid depend on what kind of socket + it is (domain, type and protocol).

+ +

Not all options are valid on all platforms. That is, + even if "we" support an option, that does not mean that the + underlying OS does.

+ +

Sockets are set 'non-blocking' when created, so this option + is *not* available (as it would adversely effect the Erlang VM + to set a socket 'blocking').

+
+
+ + + + Shut down part of a full-duplex connection. + +

Shut down all or part of a full-duplex connection.

+
+
+ +
+
+ diff --git a/erts/doc/src/specs.xml b/erts/doc/src/specs.xml index ed6be650e5..4f6951a44b 100644 --- a/erts/doc/src/specs.xml +++ b/erts/doc/src/specs.xml @@ -4,5 +4,6 @@ + diff --git a/erts/emulator/nifs/common/socket_nif.c b/erts/emulator/nifs/common/socket_nif.c index c63eff40ec..ed961d691d 100644 --- a/erts/emulator/nifs/common/socket_nif.c +++ b/erts/emulator/nifs/common/socket_nif.c @@ -23,6 +23,8 @@ * */ +#define STATIC_ERLANG_NIF 1 + /* #include */ /* #include */ /* #include */ @@ -425,6 +427,8 @@ typedef union { if (enif_select((E), (FD), (M), (O), (P), (R)) < 0) \ return enif_make_badarg((E)); +#define COMPARE(A, B) enif_compare((A), (B)) + #define IS_ATOM(E, TE) enif_is_atom((E), (TE)) #define IS_BIN(E, TE) enif_is_binary((E), (TE)) #define IS_NUM(E, TE) enif_is_number((E), (TE)) @@ -581,6 +585,7 @@ typedef struct { unsigned int state; SocketAddress remote; + unsigned int addrLen; /* +++ Controller (owner) process +++ */ @@ -710,8 +715,8 @@ static ERL_NIF_TERM nif_open(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); static ERL_NIF_TERM nif_bind(ErlNifEnv* env, - int argc, - const ERL_NIF_TERM argv[]); + int argc, + const ERL_NIF_TERM argv[]); static ERL_NIF_TERM nif_connect(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); @@ -760,9 +765,11 @@ static ERL_NIF_TERM nif_finalize_connection(ErlNifEnv* env, static ERL_NIF_TERM nif_finalize_close(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); +/* static ERL_NIF_TERM nif_cancel(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); +*/ static ERL_NIF_TERM nopen(ErlNifEnv* env, @@ -773,10 +780,8 @@ static ERL_NIF_TERM nopen(ErlNifEnv* env, static ERL_NIF_TERM nbind(ErlNifEnv* env, SocketDescriptor* descP, ERL_NIF_TERM addr); -static ERL_NIF_TERM nconnect(ErlNifEnv* env, - SocketDescriptor* descP, - const ERL_NIF_TERM* addr, - int port); +static ERL_NIF_TERM nconnect(ErlNifEnv* env, + SocketDescriptor* descP); static ERL_NIF_TERM nlisten(ErlNifEnv* env, SocketDescriptor* descP, int backlog); @@ -1156,6 +1161,43 @@ static ERL_NIF_TERM nfinalize_close(ErlNifEnv* env, SocketDescriptor* descP); +static BOOLEAN_T decode_in_sockaddr(ErlNifEnv* env, + ERL_NIF_TERM eSockAddr, + SocketAddress* sockAddrP, + unsigned int* addrLenP); +static BOOLEAN_T decode_in4_sockaddr(ErlNifEnv* env, + const ERL_NIF_TERM* eIn4SockAddr, + SocketAddress* sockAddrP, + unsigned int* addrLenP); +static BOOLEAN_T decode_in4_sockaddr_atomaddr(ErlNifEnv* env, + ERL_NIF_TERM eAddr, + int port, + SocketAddress* sockAddrP, + unsigned int* addrLenP); +static BOOLEAN_T decode_in4_sockaddr_addr(ErlNifEnv* env, + ERL_NIF_TERM eAddr, + int port, + SocketAddress* sockAddrP, + unsigned int* addrLenP); +static BOOLEAN_T decode_in6_sockaddr(ErlNifEnv* env, + const ERL_NIF_TERM* eIn6SockAddr, + SocketAddress* sockAddrP, + unsigned int* addrLenP); +static BOOLEAN_T decode_in6_sockaddr_atomaddr(ErlNifEnv* env, + ERL_NIF_TERM eAddr, + int port, + unsigned int flowInfo, + unsigned int scopeId, + SocketAddress* sockAddrP, + unsigned int* addrLenP); +/* Decode an in6_sockaddr where the address field is a tuple */ +static BOOLEAN_T decode_in6_sockaddr_addr(ErlNifEnv* env, + ERL_NIF_TERM eAddr, + int port, + unsigned int flowInfo, + unsigned int scopeId, + SocketAddress* sockAddrP, + unsigned int* addrLenP); static char* decode_laddress(ErlNifEnv* env, int domain, ERL_NIF_TERM localAddr, @@ -1184,18 +1226,22 @@ static char* decode_address_atom(ErlNifEnv* env, int port, SocketAddress* localP, unsigned int* addrLenP); +/* static char* decode_send_addr(ErlNifEnv* env, int domain, ERL_NIF_TERM addr, int port, SocketAddress** toAddrP, unsigned int* addrLenP); +*/ +/* static char* decode_send_addr_tuple(ErlNifEnv* env, int domain, ERL_NIF_TERM addr, int port, SocketAddress* toAddrP, unsigned int* addrLenP); +*/ static void encode_address(ErlNifEnv* env, SocketAddress* fromAddrP, unsigned int fromAddrLen, @@ -1278,11 +1324,15 @@ static ERL_NIF_TERM make_error(ErlNifEnv* env, ERL_NIF_TERM reason); static ERL_NIF_TERM make_error1(ErlNifEnv* env, char* reason); static ERL_NIF_TERM make_error2(ErlNifEnv* env, int err); +/* static char* send_msg_error_closed(ErlNifEnv* env, ErlNifPid* pid); +*/ +/* static char* send_msg_error(ErlNifEnv* env, ERL_NIF_TERM reason, ErlNifPid* pid); +*/ static char* send_msg_nif_abort(ErlNifEnv* env, ERL_NIF_TERM ref, ERL_NIF_TERM reason, @@ -1335,22 +1385,26 @@ static const struct in6_addr in6addr_loopback = /* *** String constants *** */ -static char str_close[] = "close"; -static char str_closed[] = "closed"; -static char str_closing[] = "closing"; -static char str_error[] = "error"; -static char str_false[] = "false"; -static char str_nif_abort[] = "nif_abort"; -static char str_ok[] = "ok"; -static char str_select[] = "select"; -static char str_timeout[] = "timeout"; -static char str_true[] = "true"; -static char str_undefined[] = "undefined"; - -static char str_lowdelay[] = "lowdelay"; -static char str_throughput[] = "throughput"; -static char str_reliability[] = "reliability"; -static char str_mincost[] = "mincost"; +static char str_any[] = "any"; +static char str_close[] = "close"; +static char str_closed[] = "closed"; +static char str_closing[] = "closing"; +static char str_error[] = "error"; +static char str_false[] = "false"; +static char str_in4_sockaddr[] = "in4_sockaddr"; +static char str_in6_sockaddr[] = "in6_sockaddr"; +static char str_loopback[] = "loopback"; +static char str_nif_abort[] = "nif_abort"; +static char str_ok[] = "ok"; +static char str_select[] = "select"; +static char str_timeout[] = "timeout"; +static char str_true[] = "true"; +static char str_undefined[] = "undefined"; + +static char str_lowdelay[] = "lowdelay"; +static char str_throughput[] = "throughput"; +static char str_reliability[] = "reliability"; +static char str_mincost[] = "mincost"; /* (special) error string constants */ static char str_eagain[] = "eagain"; @@ -1368,11 +1422,15 @@ static char str_exsend[] = "exsend"; // failed send /* *** Atoms *** */ +static ERL_NIF_TERM atom_any; static ERL_NIF_TERM atom_close; static ERL_NIF_TERM atom_closed; static ERL_NIF_TERM atom_closing; static ERL_NIF_TERM atom_error; static ERL_NIF_TERM atom_false; +static ERL_NIF_TERM atom_in4_sockaddr; +static ERL_NIF_TERM atom_in6_sockaddr; +static ERL_NIF_TERM atom_loopback; static ERL_NIF_TERM atom_nif_abort; static ERL_NIF_TERM atom_ok; static ERL_NIF_TERM atom_select; @@ -1425,11 +1483,11 @@ static SocketData socketData; * ------------------------------ * nif_open(Domain, Type, Protocol, Extra) * nif_bind(Sock, LocalAddr) - * nif_connect(Sock, Addr, Port) + * nif_connect(Sock, SockAddr) * nif_listen(Sock, Backlog) * nif_accept(LSock, Ref) * nif_send(Sock, SendRef, Data, Flags) - * nif_sendto(Sock, SendRef, Data, Flags, DstAddr, DstPort) + * nif_sendto(Sock, SendRef, Data, Flags, DstSockAddr) * nif_recv(Sock, RecvRef, Length, Flags) * nif_recvfrom(Sock, Flags) * nif_close(Sock) @@ -1437,8 +1495,8 @@ static SocketData socketData; * * And some functions to manipulate and retrieve socket options: * ------------------------------------------------------------- - * nif_setopt/3 - * nif_getopt/2 + * nif_setopt/5 + * nif_getopt/4 * * And some utility functions: * ------------------------------------------------------------- @@ -1795,7 +1853,7 @@ ERL_NIF_TERM nbind(ErlNifEnv* env, ERL_NIF_TERM addr) { SocketAddress local; - unsigned int addrLen; + unsigned int addrLen = 0; char* err; int port; @@ -2010,60 +2068,41 @@ char* decode_laddress_tuple(ErlNifEnv* env, * * Arguments: * Socket (ref) - Points to the socket descriptor. - * Addr - Address of "remote" host. - * A tuple of size 4 or 8 (depending on domain) - * Port - Port number of "remote" host. + * SockAddr - Socket Address of "remote" host. + * This is in_sockaddr(), which is either + * in4_sockaddr (#in4_sockaddr{}) or + * in6_sockaddr (#in6_sockaddr{}). */ static ERL_NIF_TERM nif_connect(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { - SocketDescriptor* descP; - int addrSz; - const ERL_NIF_TERM* addr; - int port; + SocketDescriptor* descP; + ERL_NIF_TERM eSockAddr; /* Extract arguments and perform preliminary validation */ - if ((argc != 3) || - !enif_get_resource(env, argv[0], sockets, (void**) &descP) || - !GET_TUPLE(env, argv[1], &addrSz, &addr) || - !GET_INT(env, argv[2], &port)) { + if ((argc != 2) || + !enif_get_resource(env, argv[0], sockets, (void**) &descP)) { return enif_make_badarg(env); } + eSockAddr = argv[1]; - switch (descP->domain) { - case AF_INET: - if (addrSz != 4) - return make_error(env, atom_einval); - break; - -#if defined(HAVE_IN6) && defined(AF_INET6) - case AF_INET6: - if (addrSz != 8) - return make_error(env, atom_einval); - break; -#endif - - default: - return make_error(env, atom_eafnosupport); - break; - } // switch (descP->domain) + if (!decode_in_sockaddr(env, eSockAddr, + &descP->remote, &descP->addrLen)) { + return enif_make_badarg(env); + } - return nconnect(env, descP, addr, port); + return nconnect(env, descP); } static -ERL_NIF_TERM nconnect(ErlNifEnv* env, - SocketDescriptor* descP, - const ERL_NIF_TERM* addr, - int port) +ERL_NIF_TERM nconnect(ErlNifEnv* env, + SocketDescriptor* descP) { - unsigned int addrLen; - int code; - char* xerr; + int code; /* Verify that we are where in the proper state */ @@ -2076,13 +2115,9 @@ ERL_NIF_TERM nconnect(ErlNifEnv* env, if (IS_CONNECTING(descP)) return make_error(env, atom_einval); - if ((xerr = decode_address_tuple(env, - descP->domain, addr, port, - &descP->remote, &addrLen)) != NULL) - return make_error1(env, xerr); - code = sock_connect(descP->sock, - (struct sockaddr*) &descP->remote, addrLen); + (struct sockaddr*) &descP->remote, + descP->addrLen); if (IS_SOCKET_ERROR(code) && ((sock_errno() == ERRNO_BLOCK) || /* Winsock2 */ @@ -2661,8 +2696,7 @@ ERL_NIF_TERM nsend(ErlNifEnv* env, * SendRef - A unique id for this (send) request. * Data - The data to send in the form of a IOVec. * Flags - Send flags. - * DestAddr - Destination address. - * DestPort - Destination Port. + * DestSockAddr - Destination (socket) address. */ static @@ -2675,40 +2709,34 @@ ERL_NIF_TERM nif_sendto(ErlNifEnv* env, ErlNifBinary data; unsigned int eflags; int flags; - ERL_NIF_TERM addr; - int port; + ERL_NIF_TERM eSockAddr; SocketAddress remoteAddr; - SocketAddress* remoteAddrP = &remoteAddr; unsigned int remoteAddrLen; - char* xerr; - // ERL_NIF_TERM res; /* Extract arguments and perform preliminary validation */ if ((argc != 6) || !enif_get_resource(env, argv[0], sockets, (void**) &descP) || !GET_BIN(env, argv[2], &data) || - !GET_UINT(env, argv[3], &eflags) || - !GET_INT(env, argv[5], &port)) { + !GET_UINT(env, argv[3], &eflags)) { return enif_make_badarg(env); } - sendRef = argv[1]; - addr = argv[4]; + sendRef = argv[1]; + eSockAddr = argv[4]; /* THIS TEST IS NOT CORRECT!!! */ if (!IS_OPEN(descP)) return make_error(env, atom_einval); if (!esendflags2sendflags(eflags, &flags)) - return enif_make_badarg(env); + return make_error(env, atom_einval); - if ((xerr = decode_send_addr(env, descP->domain, - addr, port, - &remoteAddrP, - &remoteAddrLen)) != NULL) - return make_error1(env, xerr); + if (!decode_in_sockaddr(env, eSockAddr, + &remoteAddr, + &remoteAddrLen)) + return make_error(env, atom_einval); - return nsendto(env, descP, sendRef, &data, flags, remoteAddrP, remoteAddrLen); + return nsendto(env, descP, sendRef, &data, flags, &remoteAddr, remoteAddrLen); } @@ -5619,6 +5647,7 @@ ERL_NIF_TERM recvfrom_check_result(ErlNifEnv* env, * This function whouls really have a char* return value * type!! */ +/* static char* decode_send_addr(ErlNifEnv* env, int domain, @@ -5631,7 +5660,7 @@ char* decode_send_addr(ErlNifEnv* env, unsigned int len; char a[16]; // Just in case... - /* The only acceptable value is the atom 'null' */ + / * The only acceptable value is the atom 'null' * / if (!(GET_ATOM_LEN(env, addr, &len) && (len > 0) && @@ -5648,15 +5677,16 @@ char* decode_send_addr(ErlNifEnv* env, return str_einval; } else if (IS_TUPLE(env, addr)) { - /* We now know that the we have a proper address. */ + / * We now know that the we have a proper address. * / return decode_send_addr_tuple(env, domain, addr, port, *toAddrP, toAddrLenP); } else { return str_einval; } } +*/ - +/* static char* decode_send_addr_tuple(ErlNifEnv* env, int domain, @@ -5665,10 +5695,10 @@ char* decode_send_addr_tuple(ErlNifEnv* env, SocketAddress* toAddrP, unsigned int* toAddrLenP) { - /* We handle two different tuples: + / * We handle two different tuples: * - size 4 (INET) * - size 8 (INET6) - */ + * / const ERL_NIF_TERM* addrt; int addrtSz; @@ -5699,6 +5729,304 @@ char* decode_send_addr_tuple(ErlNifEnv* env, toAddrP, toAddrLenP); } +*/ + + +/* Decode an in_sockaddr(). This is either a: + * in4_sockaddr: #in4_sockaddr{} = 3 tuple => + * 1: The tag in4_sockaddr + * 2: Port number (an integer()) + * 3: The address: any | loopback | ip4_address() + * in6_sockaddr: #in6_sockaddr{} = 5 tuple => + * 1: The tag in6_sockaddr + * 2: Port number: integer() + * 3: The address: any | loopback | ip6_address() + * 4: Flow info: integer() + * 5: Scope Id: integer() + * + */ +static +BOOLEAN_T decode_in_sockaddr(ErlNifEnv* env, + ERL_NIF_TERM eSockAddr, + SocketAddress* sockAddrP, + unsigned int* addrLenP) +{ + const ERL_NIF_TERM* addrt; + int addrtSz; + ERL_NIF_TERM result; + + if (!GET_TUPLE(env, eSockAddr, &addrtSz, &addrt)) + return FALSE; + + /* + * We use the tuple size to figure out which + * of the records this is. + */ + switch (addrtSz) { + case 3: + result = decode_in4_sockaddr(env, addrt, sockAddrP, addrLenP); + break; + +#if defined(HAVE_IN6) && defined(AF_INET6) + case 5: + result = decode_in6_sockaddr(env, addrt, sockAddrP, addrLenP); + break; +#endif + + default: + result = FALSE; + break; + } + + return result; +} + + + +/* Decode an in4_sockaddr(). + * The first element should be the atom in4_sockaddr + * The second, the port number integer . + * The third and final, the ip4_address tuple. + */ +static +BOOLEAN_T decode_in4_sockaddr(ErlNifEnv* env, + const ERL_NIF_TERM* eIn4SockAddr, + SocketAddress* sockAddrP, + unsigned int* addrLenP) +{ + int port; + + /* 1: Ensure that the tuple has the correct tag: in4_sockaddr */ + if (COMPARE(atom_in4_sockaddr, eIn4SockAddr[0]) != 0) + return FALSE; + + /* 2: Get the port number */ + if (!GET_INT(env, eIn4SockAddr[1], &port)) + return FALSE; + + /* 3: Get the address. + * It can either be the atoms: any | loopback, + * or the IPv4 address tuple (size 4). + */ + if (IS_ATOM(env, eIn4SockAddr[2])) { + return decode_in4_sockaddr_atomaddr(env, eIn4SockAddr[2], port, + sockAddrP, addrLenP); + } else if (IS_TUPLE(env, eIn4SockAddr[2])) { + return decode_in4_sockaddr_addr(env, eIn4SockAddr[2], port, + sockAddrP, addrLenP); + } else { + return FALSE; + } +} + + + +static +BOOLEAN_T decode_in4_sockaddr_atomaddr(ErlNifEnv* env, + ERL_NIF_TERM eAddr, + int port, + SocketAddress* sockAddrP, + unsigned int* addrLenP) +{ + struct in_addr addr; + + if (COMPARE(atom_loopback, eAddr) == 0) { + addr.s_addr = sock_htonl(INADDR_LOOPBACK); + } else if (COMPARE(atom_any, eAddr) == 0) { + addr.s_addr = sock_htonl(INADDR_ANY); + } else { + return FALSE; + } + + sys_memzero((char*) sockAddrP, sizeof(struct sockaddr_in)); +#ifndef NO_SA_LEN + sockAddrP->sai.sin_len = sizeof(struct sockaddr_in6); +#endif + sockAddrP->sai.sin_family = AF_INET; + sockAddrP->sai.sin_port = sock_htons(port); + sockAddrP->sai.sin_addr.s_addr = addr.s_addr; + *addrLenP = sizeof(struct sockaddr_in); + + return TRUE; +} + + +/* Decode an in4_sockaddr where the address field is a tuple. + * Its *supposed* to be an ip4_address (tuple). + */ +static +BOOLEAN_T decode_in4_sockaddr_addr(ErlNifEnv* env, + ERL_NIF_TERM eAddr, + int port, + SocketAddress* sockAddrP, + unsigned int* addrLenP) +{ + const ERL_NIF_TERM* ip4AddrT; + int ip4AddrTSz; + int a, v; + char addr[4]; + + /* This shall be a 4 tuple */ + if (!GET_TUPLE(env, eAddr, &ip4AddrTSz, &ip4AddrT)) + return FALSE; + + if (ip4AddrTSz != 4) + return FALSE; + + sys_memzero((char*)sockAddrP, sizeof(struct sockaddr_in)); +#ifndef NO_SA_LEN + sockAddrP->sai.sin_len = sizeof(struct sockaddr_in); +#endif + sockAddrP->sai.sin_family = AF_INET; + sockAddrP->sai.sin_port = sock_htons(port); + for (a = 0; a < 4; a++) { + if (!GET_INT(env, ip4AddrT[a], &v)) + return FALSE; + addr[a] = v; + } + sys_memcpy(&sockAddrP->sai.sin_addr, &addr, sizeof(addr)); + *addrLenP = sizeof(struct sockaddr_in); + + return TRUE; +} + + + +/* Decode an in6_sockaddr(). + * The first element should be the atom in4_sockaddr + * The second, the port number integer . + * The third, the ip4_address tuple. + * The forth, the flowinfo integer. + * The fifth and final, the scope_id integer. + */ +#if defined(HAVE_IN6) && defined(AF_INET6) +static +BOOLEAN_T decode_in6_sockaddr(ErlNifEnv* env, + const ERL_NIF_TERM* eIn6SockAddr, + SocketAddress* sockAddrP, + unsigned int* addrLenP) +{ + int port; + unsigned int flowInfo, scopeId; + + /* 1: Ensure that the tuple has the correct tag: in6_sockaddr */ + if (COMPARE(atom_in6_sockaddr, eIn6SockAddr[0]) != 0) + return FALSE; + + /* 2: Get the port number */ + if (!GET_INT(env, eIn6SockAddr[1], &port)) + return FALSE; + + /* 4: Get the flowinfo */ + if (!GET_UINT(env, eIn6SockAddr[3], &flowInfo)) + return FALSE; + + /* 5: Get the scope_id */ + if (!GET_UINT(env, eIn6SockAddr[4], &scopeId)) + return FALSE; + + /* 3: Get the address. + * It can either be the atoms: any | loopback, + * or the IPv6 address tuple (size 8). + */ + if (IS_ATOM(env, eIn6SockAddr[2])) { + return decode_in6_sockaddr_atomaddr(env, eIn6SockAddr[2], port, + flowInfo, scopeId, + sockAddrP, addrLenP); + } else if (IS_TUPLE(env, eIn6SockAddr[2])) { + return decode_in6_sockaddr_addr(env, eIn6SockAddr[2], port, + flowInfo, scopeId, + sockAddrP, addrLenP); + } else { + return FALSE; + } +} +#endif + + +#if defined(HAVE_IN6) && defined(AF_INET6) +static +BOOLEAN_T decode_in6_sockaddr_atomaddr(ErlNifEnv* env, + ERL_NIF_TERM eAddr, + int port, + unsigned int flowInfo, + unsigned int scopeId, + SocketAddress* sockAddrP, + unsigned int* addrLenP) +{ + const struct in6_addr* addr; + + if (COMPARE(atom_loopback, eAddr) == 0) { + addr = &in6addr_loopback; + } else if (COMPARE(atom_any, eAddr) == 0) { + addr = &in6addr_any; + } else { + return FALSE; + } + + sys_memzero((char*)sockAddrP, sizeof(struct sockaddr_in6)); +#ifndef NO_SA_LEN + sockAddrP->sai6.sin6_len = sizeof(struct sockaddr_in6); +#endif + sockAddrP->sai6.sin6_family = AF_INET6; + sockAddrP->sai6.sin6_port = sock_htons(port); + sockAddrP->sai6.sin6_flowinfo = flowInfo; + sockAddrP->sai6.sin6_scope_id = scopeId; + sockAddrP->sai6.sin6_addr = *addr; + *addrLenP = sizeof(struct sockaddr_in6); + + return TRUE; +} +#endif + + + +#if defined(HAVE_IN6) && defined(AF_INET6) +/* Decode an in6_sockaddr where the address field is a tuple */ +static +BOOLEAN_T decode_in6_sockaddr_addr(ErlNifEnv* env, + ERL_NIF_TERM eAddr, + int port, + unsigned int flowInfo, + unsigned int scopeId, + SocketAddress* sockAddrP, + unsigned int* addrLenP) +{ + const ERL_NIF_TERM* ip6AddrT; + int ip6AddrTSz; + int a, v; + char addr[16]; + + /* This shall be a 8 tuple */ + if (!GET_TUPLE(env, eAddr, &ip6AddrTSz, &ip6AddrT)) + return FALSE; + + if (ip6AddrTSz != 8) + return FALSE; + + sys_memzero((char*)sockAddrP, sizeof(struct sockaddr_in6)); +#ifndef NO_SA_LEN + sockAddrP->sai6.sin6_len = sizeof(struct sockaddr_in6); +#endif + sockAddrP->sai6.sin6_family = AF_INET6; + sockAddrP->sai6.sin6_port = sock_htons(port); + sockAddrP->sai6.sin6_flowinfo = flowInfo; + sockAddrP->sai6.sin6_scope_id = scopeId; + /* The address tuple is of size 8 + * and each element is a two byte integer + */ + for (a = 0; a < 8; a++) { + if (!GET_INT(env, ip6AddrT[a], &v)) + return FALSE; + addr[a*2 ] = ((v >> 8) & 0xFF); + addr[a*2+1] = (v & 0xFF); + } + sys_memcpy(&sockAddrP->sai6.sin6_addr, &addr, sizeof(addr)); + *addrLenP = sizeof(struct sockaddr_in6); + + return TRUE; +} +#endif /* Decode the 4- or 8-element address tuple @@ -6651,13 +6979,14 @@ ERL_NIF_TERM make_error2(ErlNifEnv* env, int err) * This message is for processes that are waiting in the * erlang API functions for a select message. */ +/* static char* send_msg_error_closed(ErlNifEnv* env, ErlNifPid* pid) { return send_msg_error(env, atom_closed, pid); } - +*/ /* Send an error message to the specified process: * A message in the form: @@ -6667,6 +6996,7 @@ char* send_msg_error_closed(ErlNifEnv* env, * This message is for processes that are waiting in the * erlang API functions for a select message. */ +/* static char* send_msg_error(ErlNifEnv* env, ERL_NIF_TERM reason, @@ -6676,6 +7006,7 @@ char* send_msg_error(ErlNifEnv* env, return send_msg(env, msg, pid); } +*/ /* Send an (nif-) abort message to the specified process: @@ -6985,18 +7316,18 @@ ErlNifFunc socket_funcs[] = // The proper "socket" interface {"nif_open", 4, nif_open, 0}, - {"nif_bind", 3, nif_bind, 0}, - {"nif_connect", 3, nif_connect, 0}, + {"nif_bind", 2, nif_bind, 0}, + {"nif_connect", 2, nif_connect, 0}, {"nif_listen", 2, nif_listen, 0}, {"nif_accept", 2, nif_accept, 0}, {"nif_send", 4, nif_send, 0}, - {"nif_sendto", 6, nif_sendto, 0}, + {"nif_sendto", 5, nif_sendto, 0}, {"nif_recv", 4, nif_recv, 0}, - {"nif_recvfrom", 2, nif_recvfrom, 0}, + {"nif_recvfrom", 4, nif_recvfrom, 0}, {"nif_close", 1, nif_close, 0}, {"nif_shutdown", 2, nif_shutdown, 0}, - {"nif_setopt", 3, nif_setopt, 0}, - {"nif_getopt", 2, nif_getopt, 0}, + {"nif_setopt", 5, nif_setopt, 0}, + {"nif_getopt", 4, nif_getopt, 0}, /* Misc utility functions */ {"nif_link_if2idx", 1, nif_link_if2idx, 0}, @@ -7008,7 +7339,7 @@ ErlNifFunc socket_funcs[] = * is called after the connect *select* has "completed". */ {"nif_finalize_connection", 1, nif_finalize_connection, 0}, - {"nif_cancel", 2, nif_cancel, 0}, + // {"nif_cancel", 2, nif_cancel, 0}, {"nif_finalize_close", 1, nif_finalize_close, ERL_NIF_DIRTY_JOB_IO_BOUND} }; @@ -7125,6 +7456,7 @@ int on_load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info) socketData.numProtoSCTP = 0; /* +++ Misc atoms +++ */ + atom_any = MKA(env, str_any); // atom_active = MKA(env, str_active); // atom_active_n = MKA(env, str_active_n); // atom_active_once = MKA(env, str_active_once); @@ -7135,6 +7467,9 @@ int on_load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info) atom_closing = MKA(env, str_closing); atom_error = MKA(env, str_error); atom_false = MKA(env, str_false); + atom_in4_sockaddr = MKA(env, str_in4_sockaddr); + atom_in6_sockaddr = MKA(env, str_in6_sockaddr); + atom_loopback = MKA(env, str_loopback); // atom_list = MKA(env, str_list); // atom_mode = MKA(env, str_mode); atom_nif_abort = MKA(env, str_nif_abort); diff --git a/erts/preloaded/src/socket.erl b/erts/preloaded/src/socket.erl index a67a019b80..2942f26505 100644 --- a/erts/preloaded/src/socket.erl +++ b/erts/preloaded/src/socket.erl @@ -30,13 +30,13 @@ -export([ open/2, open/3, open/4, - bind/2, bind/3, - connect/3, + bind/2, + connect/2, connect/3, listen/1, listen/2, accept/1, accept/2, send/2, send/3, send/4, - sendto/5, + sendto/4, sendto/5, %% sendmsg/4, %% writev/4, OR SENDV? It will be strange for recv then: recvv (instead of readv) @@ -125,6 +125,8 @@ %% If we do we may need to include the family (domain) in the %% map (as the native type do. See struct sockaddr_in6). %% +-type in_sockaddr() :: in4_sockaddr() | in6_sockaddr(). + -record(in4_sockaddr, {port = 0 :: port_number(), addr = any :: any | loopback | ip4_address()}). -type in4_sockaddr() :: #in4_sockaddr{}. @@ -134,8 +136,6 @@ scope_id = 0 :: in6_scope_id()}). -type in6_sockaddr() :: #in6_sockaddr{}. --type in_sockaddr() :: in4_sockaddr() | in6_sockaddr(). - -type port_number() :: 0..65535. %% otp - The option is internal to our (OTP) imeplementation. @@ -369,7 +369,7 @@ %% Optional address %% On an unconnected socket this is used to specify the target %% address for a datagram. - %% For a connected socket, this field should be specified []. + %% For a connected socket, this field should be specifiedset to []. name :: list(), %% Scatter/gather array @@ -495,7 +495,8 @@ on_load(Path, Extra) when is_list(Path) andalso is_map(Extra) -> on_load(true, _Path, _Extra) -> ok; on_load(false, Path, Extra) -> - ok = erlang:load_nif(Path, maps:put(timestamp, formated_timestamp(), Extra)). + %% ok = erlang:load_nif(Path, maps:put(timestamp, formated_timestamp(), Extra)). + ok = erlang:load_nif(Path, Extra). @@ -582,7 +583,7 @@ default_protocol(Protocol, _) -> Protocol. -spec bind(Socket, FileOrAddr) -> ok | {error, Reason} when Socket :: socket(), - FileOrAddr :: binary() | string() | ip_address() | any | loopback, + FileOrAddr :: binary() | string() | in_sockaddr() | any | loopback, Reason :: term(). bind(Socket, File) when is_binary(File) -> @@ -602,26 +603,11 @@ bind(Socket, File) when is_list(File) andalso (File =/= []) -> true -> {error, einval} end; -bind(Socket, Addr) when is_tuple(Addr) orelse - (Addr =:= any) orelse - (Addr =:= loopback) -> - bind(Socket, Addr, 0). - - --spec bind(Socket, Address, Port) -> ok | {ok, NewPort} | {error, Reason} when - Socket :: socket(), - Address :: ip_address() | any | loopback, - Port :: port_number(), - NewPort :: port_number(), - Reason :: term(). - -%% Shall we keep info about domain so that we can verify address? -bind(#socket{ref = SockRef}, Addr, Port) - when (is_tuple(Addr) andalso - ((size(Addr) =:= 4) orelse (size(Addr) =:= 8))) orelse - ((Addr =:= any) orelse (Addr =:= loopback)) andalso - (is_integer(Port) andalso (Port >= 0)) -> - nif_bind(SockRef, {Addr, Port}). +bind(#socket{ref = SockRef} = _Socket, SockAddr) + when is_record(SockAddr, in4_sockaddr) orelse + is_record(SockAddr, in6_sockaddr) orelse + (SockAddr =:= any) orelse (SockAddr =:= loopback) -> + nif_bind(SockRef, SockAddr). @@ -630,32 +616,29 @@ bind(#socket{ref = SockRef}, Addr, Port) %% connect - initiate a connection on a socket %% --spec connect(Socket, Addr, Port) -> ok | {error, Reason} when - Socket :: socket(), - Addr :: ip_address(), - Port :: port_number(), - Reason :: term(). +-spec connect(Socket, SockAddr) -> ok | {error, Reason} when + Socket :: socket(), + SockAddr :: in_sockaddr(), + Reason :: term(). -connect(Socket, Addr, Port) -> - connect(Socket, Addr, Port, infinity). +connect(Socket, SockAddr) -> + connect(Socket, SockAddr, infinity). --spec connect(Socket, Addr, Port, Timeout) -> ok | {error, Reason} when - Socket :: socket(), - Addr :: ip_address(), - Port :: port_number(), - Timeout :: timeout(), - Reason :: term(). +-spec connect(Socket, SockAddr, Timeout) -> ok | {error, Reason} when + Socket :: socket(), + SockAddr :: in_sockaddr(), + Timeout :: timeout(), + Reason :: term(). -connect(_Socket, _Addr, _Port, Timeout) +connect(_Socket, _SockAddr, Timeout) when (is_integer(Timeout) andalso (Timeout =< 0)) -> {error, timeout}; -connect(#socket{ref = SockRef}, Addr, Port, Timeout) - when (is_tuple(Addr) andalso - ((size(Addr) =:= 4) orelse (size(Addr) =:= 8))) andalso - (is_integer(Port) andalso (Port >= 0)) andalso +connect(#socket{ref = SockRef}, SockAddr, Timeout) + when (is_record(SockAddr, in4_sockaddr) orelse + is_record(SockAddr, in6_sockaddr)) andalso ((Timeout =:= infinity) orelse is_integer(Timeout)) -> TS = timestamp(Timeout), - case nif_connect(SockRef, Addr, Port) of + case nif_connect(SockRef, SockAddr) of ok -> %% Connected! ok; @@ -680,14 +663,18 @@ connect(#socket{ref = SockRef}, Addr, Port, Timeout) %% listen - listen for connections on a socket %% --spec listen(Socket, Backlog) -> ok | {error, Reason} when +-spec listen(Socket) -> ok | {error, Reason} when Socket :: socket(), - Backlog :: pos_integer(), Reason :: term(). listen(Socket) -> listen(Socket, ?SOCKET_LISTEN_BACKLOG_DEFAULT). +-spec listen(Socket, Backlog) -> ok | {error, Reason} when + Socket :: socket(), + Backlog :: pos_integer(), + Reason :: term(). + listen(#socket{ref = SockRef}, Backlog) when (is_integer(Backlog) andalso (Backlog >= 0)) -> nif_listen(SockRef, Backlog). @@ -700,15 +687,20 @@ listen(#socket{ref = SockRef}, Backlog) %% accept, accept4 - accept a connection on a socket %% --spec accept(LSocket, Timeout) -> {ok, Socket} | {error, Reason} when +-spec accept(LSocket) -> {ok, Socket} | {error, Reason} when LSocket :: socket(), - Timeout :: timeout(), Socket :: socket(), Reason :: term(). accept(Socket) -> accept(Socket, ?SOCKET_ACCEPT_TIMEOUT_DEFAULT). +-spec accept(LSocket, Timeout) -> {ok, Socket} | {error, Reason} when + LSocket :: socket(), + Timeout :: timeout(), + Socket :: socket(), + Reason :: term(). + %% Do we really need this optimization? accept(_, Timeout) when is_integer(Timeout) andalso (Timeout =< 0) -> {error, timeout}; @@ -824,35 +816,35 @@ do_send(SockRef, Data, EFlags, Timeout) -> %% --------------------------------------------------------------------------- %% -sendto(Socket, Data, Flags, DestAddr, DestPort) -> - sendto(Socket, Data, Flags, DestAddr, DestPort, ?SOCKET_SENDTO_TIMEOUT_DEFAULT). +sendto(Socket, Data, Flags, DestSockAddr) -> + sendto(Socket, Data, Flags, DestSockAddr, ?SOCKET_SENDTO_TIMEOUT_DEFAULT). --spec sendto(Socket, Data, Flags, DestAddr, DestPort, Timeout) -> +-spec sendto(Socket, Data, Flags, DestSockAddr, Timeout) -> ok | {error, Reason} when - Socket :: socket(), - Data :: binary(), - Flags :: send_flags(), - DestAddr :: null | ip_address(), - DestPort :: port_number(), - Timeout :: timeout(), - Reason :: term(). - -sendto(Socket, Data, Flags, DestAddr, DestPort, Timeout) when is_list(Data) -> + Socket :: socket(), + Data :: binary(), + Flags :: send_flags(), + DestSockAddr :: null | in_sockaddr(), + Timeout :: timeout(), + Reason :: term(). + +sendto(Socket, Data, Flags, DestSockAddr, Timeout) when is_list(Data) -> Bin = erlang:list_to_binary(Data), - sendto(Socket, Bin, Flags, DestAddr, DestPort, Timeout); -sendto(#socket{ref = SockRef}, Data, Flags, DestAddr, DestPort, Timeout) + sendto(Socket, Bin, Flags, DestSockAddr, Timeout); +sendto(#socket{ref = SockRef}, Data, Flags, DestSockAddr, Timeout) when is_binary(Data) andalso is_list(Flags) andalso - (is_tuple(DestAddr) orelse (DestAddr =:= null)) andalso - is_integer(DestPort) andalso + (is_record(DestSockAddr, in4_sockaddr) orelse + is_record(DestSockAddr, in6_sockaddr) orelse + (DestSockAddr =:= null)) andalso (is_integer(Timeout) orelse (Timeout =:= infinity)) -> EFlags = enc_send_flags(Flags), - do_sendto(SockRef, Data, EFlags, DestAddr, DestPort, Timeout). + do_sendto(SockRef, Data, EFlags, DestSockAddr, Timeout). -do_sendto(SockRef, Data, EFlags, DestAddr, DestPort, Timeout) -> +do_sendto(SockRef, Data, EFlags, DestSockAddr, Timeout) -> TS = timestamp(Timeout), SendRef = make_ref(), - case nif_sendto(SockRef, SendRef, Data, EFlags, DestAddr, DestPort) of + case nif_sendto(SockRef, SendRef, Data, EFlags, DestSockAddr) of ok -> %% We are done ok; @@ -862,10 +854,10 @@ do_sendto(SockRef, Data, EFlags, DestAddr, DestPort, Timeout) -> receive {select, SockRef, SendRef, ready_output} when (Written > 0) -> <<_:Written/binary, Rest/binary>> = Data, - do_sendto(SockRef, Rest, EFlags, DestAddr, DestPort, + do_sendto(SockRef, Rest, EFlags, DestSockAddr, next_timeout(TS, Timeout)); {select, SockRef, SendRef, ready_output} -> - do_sendto(SockRef, Data, EFlags, DestAddr, DestPort, + do_sendto(SockRef, Data, EFlags, DestSockAddr, next_timeout(TS, Timeout)); {nif_abort, SendRef, Reason} -> @@ -880,7 +872,7 @@ do_sendto(SockRef, Data, EFlags, DestAddr, DestPort, Timeout) -> {error, eagain} -> receive {select, SockRef, SendRef, ready_output} -> - do_sendto(SockRef, Data, EFlags, DestAddr, DestPort, + do_sendto(SockRef, Data, EFlags, DestSockAddr, next_timeout(TS, Timeout)) after Timeout -> nif_cancel(SockRef, sendto, SendRef), @@ -1158,13 +1150,12 @@ recvfrom(Socket, BufSz, Flags) when is_list(Flags) -> recvfrom(Socket, BufSz, Timeout) -> recvfrom(Socket, BufSz, ?SOCKET_RECV_FLAGS_DEFAULT, Timeout). --spec recvfrom(Socket, BufSz, Flags, Timeout) -> {ok, {SrcDomain, Source, Data}} | {error, Reason} when +-spec recvfrom(Socket, BufSz, Flags, Timeout) -> {ok, {Source, Data}} | {error, Reason} when Socket :: socket(), BufSz :: non_neg_integer(), Flags :: recv_flags(), Timeout :: timeout(), - SrcDomain :: domain() | undefined, - Source :: {ip_address(), port_number()} | string() | undefined, + Source :: in_sockaddr() | string() | undefined, Data :: binary(), Reason :: term(). @@ -1179,7 +1170,7 @@ do_recvfrom(SockRef, BufSz, EFlags, Timeout) -> TS = timestamp(Timeout), RecvRef = make_ref(), case nif_recvfrom(SockRef, RecvRef, BufSz, EFlags) of - {ok, {_Domain, _Source, _NewData}} = OK -> + {ok, {_Source, _NewData}} = OK -> OK; {error, eagain} -> @@ -1473,7 +1464,6 @@ link_if2idx(If) when is_list(If) -> nif_link_if2idx(If). - %% =========================================================================== %% %% link_idx2if - Mappings between network interface names and indexes: idx -> if @@ -2138,10 +2128,10 @@ nif_info() -> nif_open(_Domain, _Type, _Protocol, _Extra) -> erlang:error(badarg). -nif_bind(_SRef, _LocalAddr) -> +nif_bind(_SRef, _SockAddr) -> erlang:error(badarg). -nif_connect(_SRef, _Addr, _Port) -> +nif_connect(_SRef, _SockAddr) -> erlang:error(badarg). nif_finalize_connection(_SRef) -> @@ -2156,7 +2146,7 @@ nif_accept(_SRef, _Ref) -> nif_send(_SockRef, _SendRef, _Data, _Flags) -> erlang:error(badarg). -nif_sendto(_SRef, _SendRef, _Data, _Flags, _Dest, _Port) -> +nif_sendto(_SRef, _SendRef, _Data, _Flags, _DestSockAddr) -> erlang:error(badarg). nif_recv(_SRef, _RecvRef, _Length, _Flags) -> -- cgit v1.2.3 From 9e0acc8f442549f1f5ee4271816cbfbeb25d8719 Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Thu, 7 Jun 2018 11:03:27 +0200 Subject: [socket-nif-doc] Fixed socket type and function open The doc for type socket was missing (as it is opaque), so instead its replaced with a simple text referring to the functions open and accept. The open function missed a spec (for open/2), the text for default protocol needed some omrpvement and finally the Extra argument needed explaining. --- erts/doc/src/socket.xml | 39 ++++++++++++++++--------------- erts/preloaded/src/socket.erl | 54 ++++++++++++++++++++++++------------------- 2 files changed, 50 insertions(+), 43 deletions(-) diff --git a/erts/doc/src/socket.xml b/erts/doc/src/socket.xml index 9b487172c5..d0a316c4de 100644 --- a/erts/doc/src/socket.xml +++ b/erts/doc/src/socket.xml @@ -47,7 +47,11 @@ - + socket() +

As returned by + open/2,3,4 and + accept/1,2.

+
@@ -134,7 +138,7 @@

Bind a name to a socket.

When a socket is created - (with open), + (with open), it has no address assigned to it. bind assigns the address specified by the Addr argument.

The rules used for name binding vary between domains.

@@ -199,22 +203,18 @@ Create an endpoint for communication.

Creates an endpoint (socket) for communication.

-

For some types there is a default protocol, which will +

For some types there is a default protocol, which will be used if no protocol is specified:

- - stream - -

tcp

-
- dgram - -

udp

-
- seqpacket - -

sctp

-
-
+ + +

stream: tcp

+

dgram: udp

+

seqpacket: sctp

+
+ +

The Extra argument is intended for "obscure" options. + Currently the only supported option is netns, which + is only supported on the linux platform.

@@ -282,8 +282,9 @@ Set options on a socket.

Set options on a socket.

-

What properties are valid depend on what kind of socket - it is (domain, type and protocol).

+

What properties are valid depend both on Level and on + what kind of socket it is (domain, type and + protocol).

Not all options are valid on all platforms. That is, even if "we" support an option, that does not mean that the diff --git a/erts/preloaded/src/socket.erl b/erts/preloaded/src/socket.erl index 2942f26505..932048ba75 100644 --- a/erts/preloaded/src/socket.erl +++ b/erts/preloaded/src/socket.erl @@ -518,6 +518,12 @@ info() -> %% open - create an endpoint for communication %% +-spec open(Domain, Type) -> {ok, Socket} | {error, Reason} when + Domain :: domain(), + Type :: type(), + Socket :: socket(), + Reason :: term(). + open(Domain, Type) -> open(Domain, Type, null). @@ -2044,30 +2050,30 @@ flush_select_msgs(LSRef, Ref) -> end. -formated_timestamp() -> - format_timestamp(os:timestamp()). - -format_timestamp(Now) -> - N2T = fun(N) -> calendar:now_to_local_time(N) end, - format_timestamp(Now, N2T, true). - -format_timestamp({_N1, _N2, N3} = N, N2T, true) -> - FormatExtra = ".~.2.0w", - ArgsExtra = [N3 div 10000], - format_timestamp(N, N2T, FormatExtra, ArgsExtra); -format_timestamp({_N1, _N2, _N3} = N, N2T, false) -> - FormatExtra = "", - ArgsExtra = [], - format_timestamp(N, N2T, FormatExtra, ArgsExtra). - -format_timestamp(N, N2T, FormatExtra, ArgsExtra) -> - {Date, Time} = N2T(N), - {YYYY,MM,DD} = Date, - {Hour,Min,Sec} = Time, - FormatDate = - io_lib:format("~.4w-~.2.0w-~.2.0w ~.2.0w:~.2.0w:~.2.0w" ++ FormatExtra, - [YYYY, MM, DD, Hour, Min, Sec] ++ ArgsExtra), - lists:flatten(FormatDate). +%% formated_timestamp() -> +%% format_timestamp(os:timestamp()). + +%% format_timestamp(Now) -> +%% N2T = fun(N) -> calendar:now_to_local_time(N) end, +%% format_timestamp(Now, N2T, true). + +%% format_timestamp({_N1, _N2, N3} = N, N2T, true) -> +%% FormatExtra = ".~.2.0w", +%% ArgsExtra = [N3 div 10000], +%% format_timestamp(N, N2T, FormatExtra, ArgsExtra); +%% format_timestamp({_N1, _N2, _N3} = N, N2T, false) -> +%% FormatExtra = "", +%% ArgsExtra = [], +%% format_timestamp(N, N2T, FormatExtra, ArgsExtra). + +%% format_timestamp(N, N2T, FormatExtra, ArgsExtra) -> +%% {Date, Time} = N2T(N), +%% {YYYY,MM,DD} = Date, +%% {Hour,Min,Sec} = Time, +%% FormatDate = +%% io_lib:format("~.4w-~.2.0w-~.2.0w ~.2.0w:~.2.0w:~.2.0w" ++ FormatExtra, +%% [YYYY, MM, DD, Hour, Min, Sec] ++ ArgsExtra), +%% lists:flatten(FormatDate). %% A timestamp in ms -- cgit v1.2.3 From 70b74f57a360c2b2a1edfe46c61d2ea170d73f91 Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Thu, 7 Jun 2018 17:50:38 +0200 Subject: [socket-nif-doc] More polishing Also added (a very) temporary example. OTP-14831 --- erts/doc/src/socket.xml | 250 ++++++++++++++++++++++++++++++++- erts/emulator/nifs/common/socket_nif.c | 2 + erts/preloaded/src/socket.erl | 136 ++++++++++++------ 3 files changed, 341 insertions(+), 47 deletions(-) diff --git a/erts/doc/src/socket.xml b/erts/doc/src/socket.xml index d0a316c4de..3efa412b8a 100644 --- a/erts/doc/src/socket.xml +++ b/erts/doc/src/socket.xml @@ -149,7 +149,17 @@ Close a socket. -

Closes the socket.

+

Closes the socket.

+ + +

Note that for e.g. protocol = tcp, most implementations + doing a close does not guarantee that any data sent is delivered to + the recipient before the close is detected at the remote side.

+

One way to handle this is to use the + shutdown function + (socket:shutdown(Socket, write)) to signal that no more data is + to be sent and then wait for the read side of the socket to be closed.

+
@@ -159,7 +169,7 @@ Initiate a connection on a socket.

This function connects the socket to the address - specied by the Addr argument.

+ specied by the SockAddr argument.

@@ -171,12 +181,27 @@ + Get an option on a socket. + +

Get an option on a socket.

+

What properties are valid depend both on Level and + on what kind of socket it is (domain, type and + protocol).

+ +

Not all options are valid on all platforms. That is, + even if "we" support an option, that does not mean that the + underlying OS does.

+
+ + + Get an option on a socket.

Get an option on a socket.

-

What properties are valid depend on what kind of socket - it is (domain, type and protocol).

+

What properties are valid depend both on Level and + on what kind of socket it is (domain, type and + protocol).

When specifying Level as an integer, and therefor using "native mode", it is *currently* up to the caller to know how to interpret the result.

@@ -241,12 +266,12 @@

Receive a message from a socket.

This function reads "messages", which means that regardless of how much we want to read, it returns when we get a message.

-

The MaxSize argument basically defines the size of the +

The BufSz argument basically defines the size of the receive buffer. By setting the value to zero (0), the configured - size (setopt) is used.

+ size (setopt with Level = otp) is used.

It may be impossible to know what (buffer) size is appropriate "in advance", and in those cases it may be convenient to use the - (recv) 'peek' flag. When this flag is provided the message is *not* + (recv) 'peek' flag. When this flag is provided, the message is *not* "consumed" from the underlying buffers, so another recvfrom call is needed, possibly with a then adjusted buffer size.

@@ -305,5 +330,216 @@
+
+ Examples + +

TBD: Need to implement a receiver process in order to be able to + implement active!

+

x_tcp.erl:

+ +listen(Addr, Port) -> + try + begin + Socket = case socket:open(inet, stream, tcp) of + {ok, Socket} -> + Socket; + {error, _} = OERROR -> + throw(OERROR) + end, + SockAddr = #in4_sockaddr{port = Port, + addr = Addr}, + ok = case socket:bind(Socket, SockAddr) of + ok -> + ok; + {error, _} = BERROR -> + throw(BERROR) + end, + case socket:listen(Socket, 10) of + ok -> + {ok, Socket}; + {error, _} = LERROR -> + throw(LERROR) + end + end + catch + throw:ERROR -> + ERROR +end. + + + +connect(Addr, Port) -> + try + begin + Socket = case socket:open(inet, stream, tcp) of + {ok, Socket} -> + Socket; + {error, _} = OERROR -> + throw(OERROR) + end, + BSockAddr = #in4_sockaddr{port = 0, + addr = any}, + ok = case socket:bind(Socket, BSockAddr) of + ok -> + ok; + {error, _} = BERROR -> + throw(BERROR) + end, + CSockAddr = #in4_sockaddr{port = Port, + addr = Addr}, + Socket = case socket:connect(Socket, CSockAddr) of + {ok, Sock} -> + Sock; + {error, _} = CERROR -> + throw(CERROR) + end, + case start_receiver(Socket) of + {ok, Pid} -> + ok = socket:ts_add(Socket, receiver, Pid), + {ok, Socket}; + {error, _} = RERROR -> + socket:close(Socket), + throw(RERROR) + end + end + catch + throw:ERROR -> + ERROR +end. + + + +accept(LSocket) -> + case socket:accept(LSocket) of + {ok, Socket} -> + case start_receiver(Socket) of + {ok, Pid} -> + ok = socket:ts_add(Socket, receiver, Pid), + {ok, Socket}; + {error, _} = RERROR -> + socket:close(Socket), + throw(RERROR) + end, + {error, _} = AERROR -> + AERROR + end. + + + +send(Socket, Data) -> + socket:send(Socket, Data). + + + +setopt(Socket, controlling_process = Item, Pid) when is_pid(Pid) -> + case socket:setopt(Socket, otp, Item, Pid) of + ok -> + {ok, Receiver} = socket:ts_get(Socket, receiver), + update_receiver(Receiver, {controlling_process, Pid), + ok; + {error, _} = ERROR -> + ERROR + end; +setopt(Socket, active, Active) -> + {ok, Receiver} = socket:ts_get(Socket, receiver), + receiver_active(Receiver, Active), + ok; +%% This is just to indicate that there will be more options +%% that needs to be handled +setopt(Socket, Item, Pid) when is_pid(Pid) -> + socket:setopt(Socket, which_level(Item), Item, Pid). + + +

The receiver process:

+ +start_receiver(Socket) -> + CtrlProc = self(), + Ref = make_ref(), + Receiver = proc_lib:spawn_link(fun() -> receiver_init(CtrlProc, Ref) end), + receive + {?MODULE, started, Ref} -> + Receiver ! {?MODULE, receiver, Ref, Sock, true}, + unlink(Receiver), + {ok, Receiver}; + {'EXIT', Receiver, Reason} -> + {error, Reason} + end. + +receiver_active(Receiver, Active) + when (Active =:= false) orelse + (Active =:= true) orelse + (Active =:= once) orelse + is_integer(Active) -> + Receiver ! {?MODULE, active, What}. + +receiver_update(Receiver, What) -> + Ref = make_ref(), + Receiver ! {?MODULE, receiver_update, Ref, What}, + receive + {?MODULE, receiver_upadate, Ref, Result} -> + Result + end. + +-record(receiver, {sock :: socket:socket(), + ctrl :: pid(), + num_packages :: infinity | non_neg_ineteger()}). + +receiver_init(Pid, Ref) -> + receive + {?MODULE, receiver, Ref, stop} -> + i("received stop"), + exit(normal); + + {?MODULE, receiver, Ref, Sock, InitialMode} -> + i("received socket: ~p", [Sock]), + NumPackages = mode2pkgcount(InitialMode), + receiver(#receiver{sock = Sock, ctrl = Pid}) + end. + +mode2pkgcount(true) -> + infinity; +mode2pkgcount(false) -> + 0; +mode2pkgcount(N) when is_integer(N) andalso (N >= 0) -> + N. + +receiver(#receiver{num_packages = 0} = State) -> + receive + {?MODULE, active, false} -> + receiver(State); + {?MODULE, active, true} -> + receiver(State#receiver{num_packages = infinity}); + {?MODULE, active, once} -> + receiver(State#receiver{num_packages = 1}); + {?MODULE, active, N} when (N > 0) -> + receiver(State#receiver{num_packages = N}) + end; +receiver(#receiver{num_packages = N, sock = Sock, ctrl = Pid} = State) -> + case socket:recv(Sock, 0) of + {ok, Package} when size(Package) > 0 -> + Pid ! {tcp, Sock, Packege}, + case next_active(N) of + 0 -> + Pid ! {tcp_passive, Sock}, + receiver(State#{num_packages = 0}); + NextN -> + receiver(State#{num_packages = NextN}) + end; + {ok, Package} when size(Package) =:= 0 -> + receiver(State); + + {error, closed} -> + i("closed"), + Pid ! {tcp_closed, Sock}, + exit(normal); + + {error, Reason} -> + i("error: ~p", [Reason]), + Pid ! {tcp_error, Sock, Reason}, + exit(normal) + end. + + +
diff --git a/erts/emulator/nifs/common/socket_nif.c b/erts/emulator/nifs/common/socket_nif.c index ed961d691d..7bafe38273 100644 --- a/erts/emulator/nifs/common/socket_nif.c +++ b/erts/emulator/nifs/common/socket_nif.c @@ -7315,6 +7315,8 @@ ErlNifFunc socket_funcs[] = // {"nif_debug", 1, nif_debug_, 0}, // The proper "socket" interface + // This is used when we already have a file descriptor + // {"nif_open", 1, nif_open, 0}, {"nif_open", 4, nif_open, 0}, {"nif_bind", 2, nif_bind, 0}, {"nif_connect", 2, nif_connect, 0}, diff --git a/erts/preloaded/src/socket.erl b/erts/preloaded/src/socket.erl index 932048ba75..772c733bc7 100644 --- a/erts/preloaded/src/socket.erl +++ b/erts/preloaded/src/socket.erl @@ -159,6 +159,7 @@ -type otp_socket_option() :: debug | iow | + controlling_process | rcvbuf | sndbuf. %% Shall we have special treatment of linger?? @@ -517,6 +518,63 @@ info() -> %% %% open - create an endpoint for communication %% +%% Extra: netns +%% +%% +%% +%% How do we handle the case when an fd has beem created (somehow) +%% and we shall create a socket "from it". +%% Can we figure out Domain, Type and Protocol from fd? +%% Yes we can: SO_DOMAIN, SO_PROTOCOL, SO_TYPE +%% +%% +%% +%% +%% +%% +%% Start a controller process here, *before* the nif_open call. +%% If that call is successful, update with owner process (controlling +%% process) and SockRef. If the open fails, kill the process. +%% "Register" the process on success: +%% +%% nif_register(SockRef, self()). +%% +%% The nif sets up a monitor to this process, and if it dies the socket +%% is closed. It is also used if someone wants to monitor the socket. +%% +%% We therefor need monitor function(s): +%% +%% socket:monitor(Socket) +%% socket:demonitor(Socket) +%% +%% These are basically used to monitor the controller process. +%% +%% +%% + +%% -spec open(FD) -> {ok, Socket} | {error, Reason} when +%% Socket :: socket(), +%% Reason :: term(). + +%% open(FD) -> +%% try +%% begin +%% case nif_open(FD) of +%% {ok, {SockRef, Domain, Type, Protocol}} -> +%% SocketInfo = #{domain => Domain, +%% type => Type, +%% protocol => Protocol}, +%% Socket = #socket{info = SocketInfo, +%% ref = SockRef}, +%% {ok, Socket}; +%% {error, _} = ERROR -> +%% ERROR +%% end +%% end +%% catch +%% _:_ -> % This must be improved!! +%% {error, einval} +%% end. -spec open(Domain, Type) -> {ok, Socket} | {error, Reason} when Domain :: domain(), @@ -653,6 +711,20 @@ connect(#socket{ref = SockRef}, SockAddr, Timeout) NewTimeout = next_timeout(TS, Timeout), receive {select, SockRef, Ref, ready_output} -> + %% + %% + %% See open above!! + %% + %% * Here we should start and *register* the reader process + %% (This will cause the nif code to create a monitor to + %% the process) + %% * The reader is basically used to implement the active-X + %% feature! + %% * If the reader dies for whatever reason, then the socket + %% (resource) closes and the owner (controlling) process + %% is informed (closed message). + %% + %% nif_finalize_connection(SockRef) after NewTimeout -> nif_cancel(SockRef, connect, Ref), @@ -719,6 +791,16 @@ do_accept(LSockRef, SI, Timeout) -> AccRef = make_ref(), case nif_accept(LSockRef, AccRef) of {ok, SockRef} -> + %% + %% + %% * Here we should start and *register* the reader process + %% (This will cause the nif code to create a monitor to the process) + %% * The reader is basically used to implement the active-X feature! + %% * If the reader dies for whatever reason, then the socket (resource) + %% closes and the owner (controlling) process is informed (closed + %% message). + %% + %% SocketInfo = #{domain => maps:get(domain, SI), type => maps:get(type, SI), protocol => maps:get(protocol, SI)}, @@ -1297,46 +1379,32 @@ shutdown(#socket{ref = SockRef}, How) -> %% %% --spec setopt(Socket, Level, Opt, Value) -> ok | {error, Reason} when +-spec setopt(Socket, otp, otp_socket_option(), Value) -> ok | {error, Reason} when Socket :: socket(), - Level :: otp, - Opt :: otp_socket_option(), Value :: term(), Reason :: term() - ; (Socket, Level, Opt, Value) -> ok | {error, Reason} when + ; (Socket, socket, socket_option(), Value) -> ok | {error, Reason} when Socket :: socket(), - Level :: socket, - Opt :: socket_option(), Value :: term(), Reason :: term() - ; (Socket, Level, Opt, Value) -> ok | {error, Reason} when + ; (Socket, ip, ip_socket_option(), Value) -> ok | {error, Reason} when Socket :: socket(), - Level :: ip, - Opt :: ip_socket_option(), Value :: term(), Reason :: term() - ; (Socket, Level, Opt, Value) -> ok | {error, Reason} when + ; (Socket, ipv6, ipv6_socket_option(), Value) -> ok | {error, Reason} when Socket :: socket(), - Level :: ipv6, - Opt :: ipv6_socket_option(), Value :: term(), Reason :: term() - ; (Socket, Level, Opt, Value) -> ok | {error, Reason} when + ; (Socket, tcp, tcp_socket_option(), Value) -> ok | {error, Reason} when Socket :: socket(), - Level :: tcp, - Opt :: tcp_socket_option(), Value :: term(), Reason :: term() - ; (Socket, Level, Opt, Value) -> ok | {error, Reason} when + ; (Socket, udp, udp_socket_option(), Value) -> ok | {error, Reason} when Socket :: socket(), - Level :: udp, - Opt :: udp_socket_option(), Value :: term(), Reason :: term() - ; (Socket, Level, Opt, Value) -> ok | {error, Reason} when + ; (Socket, sctp, sctp_socket_option(), Value) -> ok | {error, Reason} when Socket :: socket(), - Level :: sctp, - Opt :: sctp_socket_option(), Value :: term(), Reason :: term(). @@ -1374,46 +1442,34 @@ setopt(#socket{info = Info, ref = SockRef}, Level, Key, Value) -> %% value size. Example: int | bool | {string, pos_integer()} | non_neg_integer() %% --spec getopt(Socket, Level, Key) -> {ok, Value} | {error, Reason} when +-spec getopt(Socket, otp, otp_socket_option()) -> {ok, Value} | {error, Reason} when Socket :: socket(), - Level :: otp, - Key :: otp_socket_option(), Value :: term(), Reason :: term() - ; (Socket, Level, Key) -> {ok, Value} | {error, Reason} when + ; (Socket, socket, socket_option()) -> {ok, Value} | {error, Reason} when Socket :: socket(), - Level :: socket, - Key :: socket_option(), Value :: term(), Reason :: term() - ; (Socket, Level, Key) -> {ok, Value} | {error, Reason} when + ; (Socket, ip, ip_socket_option()) -> {ok, Value} | {error, Reason} when Socket :: socket(), Level :: ip, Key :: ip_socket_option(), Value :: term(), Reason :: term() - ; (Socket, Level, Key) -> {ok, Value} | {error, Reason} when + ; (Socket, ipv6, ipv6_socket_option()) -> {ok, Value} | {error, Reason} when Socket :: socket(), - Level :: ipv6, - Key :: ipv6_socket_option(), Value :: term(), Reason :: term() - ; (Socket, Level, Key) -> {ok, Value} | {error, Reason} when + ; (Socket, tcp, tcp_socket_option()) -> {ok, Value} | {error, Reason} when Socket :: socket(), - Level :: tcp, - Key :: tcp_socket_option(), Value :: term(), Reason :: term() - ; (Socket, Level, Key) -> {ok, Value} | {error, Reason} when + ; (Socket, udp, udp_socket_option()) -> {ok, Value} | {error, Reason} when Socket :: socket(), - Level :: udp, - Key :: udp_socket_option(), Value :: term(), Reason :: term() - ; (Socket, Level, Key) -> {ok, Value} | {error, Reason} when + ; (Socket, sctp, sctp_socket_option()) -> {ok, Value} | {error, Reason} when Socket :: socket(), - Level :: sctp, - Key :: sctp_socket_option(), Value :: term(), Reason :: term() ; (Socket, Level, Key) -> ok | {ok, Value} | {error, Reason} when -- cgit v1.2.3 From 348677edc60a33bf31210a0ddfeb327e1be6f9c2 Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Tue, 12 Jun 2018 16:57:53 +0200 Subject: [socket-nif] Fixed getopt function spec --- erts/preloaded/ebin/socket.beam | Bin 19820 -> 39248 bytes erts/preloaded/src/socket.erl | 2 -- 2 files changed, 2 deletions(-) diff --git a/erts/preloaded/ebin/socket.beam b/erts/preloaded/ebin/socket.beam index 532048ac84..2fc479f2d7 100644 Binary files a/erts/preloaded/ebin/socket.beam and b/erts/preloaded/ebin/socket.beam differ diff --git a/erts/preloaded/src/socket.erl b/erts/preloaded/src/socket.erl index 772c733bc7..90fe5b9f36 100644 --- a/erts/preloaded/src/socket.erl +++ b/erts/preloaded/src/socket.erl @@ -1452,8 +1452,6 @@ setopt(#socket{info = Info, ref = SockRef}, Level, Key, Value) -> Reason :: term() ; (Socket, ip, ip_socket_option()) -> {ok, Value} | {error, Reason} when Socket :: socket(), - Level :: ip, - Key :: ip_socket_option(), Value :: term(), Reason :: term() ; (Socket, ipv6, ipv6_socket_option()) -> {ok, Value} | {error, Reason} when -- cgit v1.2.3 From cb858ed68f2cc21014f37c8f6c1cb0dfc20f6184 Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Wed, 13 Jun 2018 10:54:01 +0200 Subject: [socket-nif] The info function now returns a non-empty map The map returned by the info function is now populated with the "global stuff". That is, debug, iow and the (global) counters (which are still not actually incremented). Also added debug functions and macros (not yet used). OTP-14831 --- erts/emulator/nifs/common/socket_nif.c | 436 +++++++++++++++------------------ erts/preloaded/ebin/socket.beam | Bin 39248 -> 38344 bytes erts/preloaded/src/socket.erl | 75 +----- 3 files changed, 208 insertions(+), 303 deletions(-) diff --git a/erts/emulator/nifs/common/socket_nif.c b/erts/emulator/nifs/common/socket_nif.c index 7bafe38273..3205ed1720 100644 --- a/erts/emulator/nifs/common/socket_nif.c +++ b/erts/emulator/nifs/common/socket_nif.c @@ -62,6 +62,7 @@ #include #include #include +#include #ifdef HAVE_UNISTD_H #include @@ -405,6 +406,7 @@ typedef union { #define MKBIN(E,B) enif_make_binary((E), (B)) #define MKI(E,I) enif_make_int((E), (I)) #define MKLA(E,A,L) enif_make_list_from_array((E), (A), (L)) +#define MKMA(E,KA,VA,L,M) enif_make_map_from_arrays((E), (KA), (VA), (L), (M)) #define MKREF(E) enif_make_ref((E)) #define MKS(E,S) enif_make_string((E), (S), ERL_NIF_LATIN1) #define MKSL(E,S,L) enif_make_string_len((E), (S), (L), ERL_NIF_LATIN1) @@ -449,6 +451,14 @@ typedef union { #define REALLOC_BIN(SZ, BP) enif_realloc_binary((SZ), (BP)) +#define SDEBUG( ___COND___ , proto ) \ + if ( ___COND___ ) { \ + dbg_printf proto; \ + fflush(stdout); \ + } +#define SDBG( proto ) SDEBUG( data.dbg , proto ) + + /* =================================================================== * * * * Basic socket operations * @@ -672,13 +682,13 @@ typedef struct { ERL_NIF_TERM version; ERL_NIF_TERM buildDate; BOOLEAN_T dbg; + BOOLEAN_T iow; ErlNifMutex* cntMtx; - BOOLEAN_T iow; uint32_t numSockets; uint32_t numTypeDGrams; uint32_t numTypeStreams; - uint32_t numTypeSeqPkg; + uint32_t numTypeSeqPkgs; uint32_t numDomainLocal; uint32_t numDomainInet; uint32_t numDomainInet6; @@ -750,15 +760,6 @@ static ERL_NIF_TERM nif_setopt(ErlNifEnv* env, static ERL_NIF_TERM nif_getopt(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); -static ERL_NIF_TERM nif_link_if2idx(ErlNifEnv* env, - int argc, - const ERL_NIF_TERM argv[]); -static ERL_NIF_TERM nif_link_idx2if(ErlNifEnv* env, - int argc, - const ERL_NIF_TERM argv[]); -static ERL_NIF_TERM nif_link_ifs(ErlNifEnv* env, - int argc, - const ERL_NIF_TERM argv[]); static ERL_NIF_TERM nif_finalize_connection(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); @@ -1129,13 +1130,6 @@ static ERL_NIF_TERM ngetopt_int_opt(ErlNifEnv* env, int level, int opt); -static ERL_NIF_TERM nlink_if2idx(ErlNifEnv* env, - char* ifn); -static ERL_NIF_TERM nlink_idx2if(ErlNifEnv* env, - unsigned int id); -static ERL_NIF_TERM nlink_ifs(ErlNifEnv* env); -static unsigned int nlink_ifs_length(struct if_nameindex* p); - static ERL_NIF_TERM send_check_result(ErlNifEnv* env, SocketDescriptor* descP, ssize_t written, @@ -1346,6 +1340,10 @@ static void xabort(const char* expr, const char* file, int line); +static void dbg_printf( const char* format, ... ); +static int dbg_realtime(struct timespec* tsP); +static int dbg_timespec2str(char *buf, unsigned int len, struct timespec *ts); + static BOOLEAN_T extract_item_on_load(ErlNifEnv* env, ERL_NIF_TERM map, ERL_NIF_TERM key, @@ -1389,14 +1387,28 @@ static char str_any[] = "any"; static char str_close[] = "close"; static char str_closed[] = "closed"; static char str_closing[] = "closing"; +static char str_debug[] = "debug"; static char str_error[] = "error"; static char str_false[] = "false"; +static char str_global_counters[] = "global_counters"; static char str_in4_sockaddr[] = "in4_sockaddr"; static char str_in6_sockaddr[] = "in6_sockaddr"; +static char str_iow[] = "iow"; static char str_loopback[] = "loopback"; static char str_nif_abort[] = "nif_abort"; static char str_ok[] = "ok"; static char str_select[] = "select"; +static char str_num_dlocal[] = "num_domain_local"; +static char str_num_dinet[] = "num_domain_inet"; +static char str_num_dinet6[] = "num_domain_inet6"; +static char str_num_pip[] = "num_proto_ip"; +static char str_num_psctp[] = "num_proto_sctp"; +static char str_num_ptcp[] = "num_proto_tcp"; +static char str_num_pudp[] = "num_proto_udp"; +static char str_num_sockets[] = "num_sockets"; +static char str_num_tdgrams[] = "num_type_dgram"; +static char str_num_tseqpkgs[] = "num_type_seqpacket"; +static char str_num_tstreams[] = "num_type_stream"; static char str_timeout[] = "timeout"; static char str_true[] = "true"; static char str_undefined[] = "undefined"; @@ -1426,12 +1438,26 @@ static ERL_NIF_TERM atom_any; static ERL_NIF_TERM atom_close; static ERL_NIF_TERM atom_closed; static ERL_NIF_TERM atom_closing; +static ERL_NIF_TERM atom_debug; static ERL_NIF_TERM atom_error; static ERL_NIF_TERM atom_false; +static ERL_NIF_TERM atom_global_counters; static ERL_NIF_TERM atom_in4_sockaddr; static ERL_NIF_TERM atom_in6_sockaddr; +static ERL_NIF_TERM atom_iow; static ERL_NIF_TERM atom_loopback; static ERL_NIF_TERM atom_nif_abort; +static ERL_NIF_TERM atom_num_dinet; +static ERL_NIF_TERM atom_num_dinet6; +static ERL_NIF_TERM atom_num_dlocal; +static ERL_NIF_TERM atom_num_pip; +static ERL_NIF_TERM atom_num_psctp; +static ERL_NIF_TERM atom_num_ptcp; +static ERL_NIF_TERM atom_num_pudp; +static ERL_NIF_TERM atom_num_sockets; +static ERL_NIF_TERM atom_num_tdgrams; +static ERL_NIF_TERM atom_num_tseqpkgs; +static ERL_NIF_TERM atom_num_tstreams; static ERL_NIF_TERM atom_ok; static ERL_NIF_TERM atom_select; static ERL_NIF_TERM atom_timeout; @@ -1466,7 +1492,7 @@ static ErlNifResourceTypeInit socketInit = { }; // Initiated when the nif is loaded -static SocketData socketData; +static SocketData data; /* ---------------------------------------------------------------------- @@ -1500,9 +1526,6 @@ static SocketData socketData; * * And some utility functions: * ------------------------------------------------------------- - * nif_link_if2idx/1 - * nif_link_idx2if/1 - * nif_link_ifs/0 * * And some socket admin functions: * ------------------------------------------------------------- @@ -1537,13 +1560,46 @@ ERL_NIF_TERM nif_is_loaded(ErlNifEnv* env, * Description: * This is currently just a placeholder... */ +#define MKCT(E, T, C) MKT2((E), (T), MKI((E), (C))) + static ERL_NIF_TERM nif_info(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { - ERL_NIF_TERM info = enif_make_new_map(env); - return info; + if (argc != 0) { + return enif_make_badarg(env); + } else { + ERL_NIF_TERM numSockets = MKCT(env, atom_num_sockets, data.numSockets); + ERL_NIF_TERM numTypeDGrams = MKCT(env, atom_num_tdgrams, data.numTypeDGrams); + ERL_NIF_TERM numTypeStreams = MKCT(env, atom_num_tstreams, data.numTypeStreams); + ERL_NIF_TERM numTypeSeqPkgs = MKCT(env, atom_num_tseqpkgs, data.numTypeSeqPkgs); + ERL_NIF_TERM numDomLocal = MKCT(env, atom_num_dlocal, data.numDomainLocal); + ERL_NIF_TERM numDomInet = MKCT(env, atom_num_dinet, data.numDomainInet); + ERL_NIF_TERM numDomInet6 = MKCT(env, atom_num_dinet6, data.numDomainInet6); + ERL_NIF_TERM numProtoIP = MKCT(env, atom_num_pip, data.numProtoIP); + ERL_NIF_TERM numProtoTCP = MKCT(env, atom_num_ptcp, data.numProtoTCP); + ERL_NIF_TERM numProtoUDP = MKCT(env, atom_num_pudp, data.numProtoUDP); + ERL_NIF_TERM numProtoSCTP = MKCT(env, atom_num_psctp, data.numProtoSCTP); + ERL_NIF_TERM gcnt[] = {numSockets, + numTypeDGrams, numTypeStreams, numTypeSeqPkgs, + numDomLocal, numDomInet, numDomInet6, + numProtoIP, numProtoTCP, numProtoUDP, numProtoSCTP}; + unsigned int lenGCnt = sizeof(gcnt) / sizeof(ERL_NIF_TERM); + ERL_NIF_TERM lgcnt = MKLA(env, gcnt, lenGCnt); + ERL_NIF_TERM keys[] = {atom_debug, atom_iow, atom_global_counters}; + ERL_NIF_TERM vals[] = {BOOL2ATOM(data.dbg), BOOL2ATOM(data.iow), lgcnt}; + ERL_NIF_TERM info; + unsigned int numKeys = sizeof(keys) / sizeof(ERL_NIF_TERM); + unsigned int numVals = sizeof(keys) / sizeof(ERL_NIF_TERM); + + SASSERT( (numKeys == numVals) ); + + if (!MKMA(env, keys, vals, numKeys, &info)) + return enif_make_badarg(env); + + return info; + } } @@ -5185,186 +5241,6 @@ ERL_NIF_TERM ngetopt_int_opt(ErlNifEnv* env, - -/* ---------------------------------------------------------------------- - * nif_link_if2idx - * - * Description: - * Perform a Interface Name to Interface Index translation. - * - * Arguments: - * Ifn - Interface name to be translated. - */ - -static -ERL_NIF_TERM nif_link_if2idx(ErlNifEnv* env, - int argc, - const ERL_NIF_TERM argv[]) -{ - ERL_NIF_TERM eifn; - char ifn[IF_NAMESIZE+1]; - - if (argc != 1) { - return enif_make_badarg(env); - } - eifn = argv[0]; - - if (0 >= GET_STR(env, eifn, ifn, sizeof(ifn))) - return make_error2(env, atom_einval); - - return nlink_if2idx(env, ifn); -} - - - -static -ERL_NIF_TERM nlink_if2idx(ErlNifEnv* env, - char* ifn) -{ - unsigned int idx = if_nametoindex(ifn); - - if (idx == 0) - return make_error2(env, sock_errno()); - else - return make_ok2(env, idx); - -} - - - -/* ---------------------------------------------------------------------- - * nif_link_idx2if - * - * Description: - * Perform a Interface Index to Interface Name translation. - * - * Arguments: - * Idx - Interface index to be translated. - */ - -static -ERL_NIF_TERM nif_link_idx2if(ErlNifEnv* env, - int argc, - const ERL_NIF_TERM argv[]) -{ - unsigned int idx; - - if ((argc != 1) || - !GET_UINT(env, argv[0], &idx)) { - return enif_make_badarg(env); - } - - return nlink_idx2if(env, idx); -} - - - -static -ERL_NIF_TERM nlink_idx2if(ErlNifEnv* env, - unsigned int idx) -{ - ERL_NIF_TERM result; - char* ifn = MALLOC(IF_NAMESIZE+1); - - if (ifn == NULL) - return enif_make_badarg(env); // PLACEHOLDER - - if (NULL == if_indextoname(idx, ifn)) { - result = make_ok2(env, MKS(env, ifn)); - } else { - result = make_error2(env, sock_errno()); - } - - FREE(ifn); // OR IS THIS AUTOMATIC? - - return result; -} - - - -/* ---------------------------------------------------------------------- - * nif_link_idx2if - * - * Description: - * Get network interface names and indexes. - * - */ - -static -ERL_NIF_TERM nif_link_ifs(ErlNifEnv* env, - int argc, - const ERL_NIF_TERM argv[]) -{ - if (argc != 0) { - return enif_make_badarg(env); - } - - return nlink_ifs(env); -} - - - -static -ERL_NIF_TERM nlink_ifs(ErlNifEnv* env) -{ - ERL_NIF_TERM result; - struct if_nameindex* ifs = if_nameindex(); - - if (ifs == NULL) { - result = make_error2(env, sock_errno()); - } else { - /* - * We got some interfaces: - * 1) Calculate how many - the only way is to iterate through the list - * until its end (which is indicated by an entry with index = zero - * and if_name = NULL). - * 2) Allocate an ERL_NIF_TERM array of the calculated length. - * 3) Iterate through the array of interfaces and for each create - * a two tuple: {Idx, If} - * - * Or shall we instead build a list in reverse order and then when - * its done, reverse that? Check - */ - unsigned int len = nlink_ifs_length(ifs); - - if (len > 0) { - ERL_NIF_TERM* array = MALLOC(len * sizeof(ERL_NIF_TERM)); - unsigned int i; - - for (i = 0; i < len; i++) { - array[i] = MKT2(env, - MKI(env, ifs[i].if_index), - MKS(env, ifs[i].if_name)); - } - - result = make_ok2(env, MKLA(env, array, len)); - FREE(array); - } else { - result = make_ok2(env, enif_make_list(env, 0)); - } - } - - if (ifs != NULL) - if_freenameindex(ifs); - - return result; -} - - -static -unsigned int nlink_ifs_length(struct if_nameindex* p) -{ - unsigned int len = 0; - - while ((p[len].if_index == 0) && (p[len].if_name == NULL)) { - len++; - } - - return len; -} - - - /* ---------------------------------------------------------------------- * U t i l i t y F u n c t i o n s * ---------------------------------------------------------------------- @@ -7301,6 +7177,89 @@ void socket_down(ErlNifEnv* env, +/* ---------------------------------------------------------------------- + * D e b u g F u n c t i o n s + * ---------------------------------------------------------------------- + */ + +/* + * Print a debug format string *with* both a timestamp and the + * the name of the *current* thread. + */ +static +void dbg_printf( const char* format, ... ) +{ + va_list args; + char f[512 + sizeof(format)]; // This has to suffice... + char stamp[30]; + struct timespec ts; + int res; + + /* + * We should really include self in the printout, so we can se which process + * are executing the code. But then I must change the API.... + * ....something for later. + */ + + if (!dbg_realtime(&ts)) { + if (dbg_timespec2str(stamp, sizeof(stamp), &ts) != 0) { + // res = enif_snprintf(f, sizeof(f), "NET [%s] %s", TSNAME(), format); + res = enif_snprintf(f, sizeof(f), "NET [%s]", format); + } else { + // res = enif_snprintf(f, sizeof(f), "NET[%s] [%s] %s", stamp, TSNAME(), format); + res = enif_snprintf(f, sizeof(f), "NET [%s] %s", stamp, format); + } + + if (res > 0) { + va_start (args, format); + erts_vfprintf (stdout, f, args); // TMP: use enif_vfprintf + va_end (args); + fflush(stdout); + } + } + + return; +} + + +static +int dbg_realtime(struct timespec* tsP) +{ + return clock_gettime(CLOCK_REALTIME, tsP); +} + + + + +/* + * Convert a timespec struct into a readable/printable string + */ +static +int dbg_timespec2str(char *buf, unsigned int len, struct timespec *ts) +{ + int ret, buflen; + struct tm t; + + tzset(); + if (localtime_r(&(ts->tv_sec), &t) == NULL) + return 1; + + ret = strftime(buf, len, "%F %T", &t); + if (ret == 0) + return 2; + len -= ret - 1; + buflen = strlen(buf); + + ret = snprintf(&buf[buflen], len, ".%06ld", ts->tv_nsec/1000); + if (ret >= len) + return 3; + + return 0; +} + + + + /* ---------------------------------------------------------------------- * L o a d / u n l o a d / u p g r a d e F u n c t i o n s * ---------------------------------------------------------------------- @@ -7332,9 +7291,6 @@ ErlNifFunc socket_funcs[] = {"nif_getopt", 4, nif_getopt, 0}, /* Misc utility functions */ - {"nif_link_if2idx", 1, nif_link_if2idx, 0}, - {"nif_link_idx2if", 1, nif_link_idx2if, 0}, - {"nif_link_ifs", 0, nif_link_ifs, 0}, /* "Extra" functions to "complete" the socket interface. * For instance, the function nif_finalize_connection @@ -7437,54 +7393,55 @@ BOOLEAN_T extract_iow_on_load(ErlNifEnv* env, ERL_NIF_TERM map, BOOLEAN_T def) static int on_load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info) { - socketData.dbg = extract_debug_on_load(env, load_info, - SOCKET_NIF_DEBUG_DEFAULT); + data.dbg = extract_debug_on_load(env, load_info, + SOCKET_NIF_DEBUG_DEFAULT); + data.iow = extract_iow_on_load(env, load_info, + SOCKET_NIF_IOW_DEFAULT); /* +++ Global Counters +++ */ - socketData.cntMtx = MCREATE("socket[gcnt]"); - socketData.iow = extract_iow_on_load(env, - load_info, - SOCKET_NIF_IOW_DEFAULT); - socketData.numSockets = 0; - socketData.numTypeDGrams = 0; - socketData.numTypeStreams = 0; - socketData.numTypeSeqPkg = 0; - socketData.numDomainLocal = 0; - socketData.numDomainInet = 0; - socketData.numDomainInet6 = 0; - socketData.numProtoIP = 0; - socketData.numProtoTCP = 0; - socketData.numProtoUDP = 0; - socketData.numProtoSCTP = 0; + data.cntMtx = MCREATE("socket[gcnt]"); + data.numSockets = 0; + data.numTypeDGrams = 0; + data.numTypeStreams = 0; + data.numTypeSeqPkgs = 0; + data.numDomainLocal = 0; + data.numDomainInet = 0; + data.numDomainInet6 = 0; + data.numProtoIP = 0; + data.numProtoTCP = 0; + data.numProtoUDP = 0; + data.numProtoSCTP = 0; /* +++ Misc atoms +++ */ atom_any = MKA(env, str_any); - // atom_active = MKA(env, str_active); - // atom_active_n = MKA(env, str_active_n); - // atom_active_once = MKA(env, str_active_once); - // atom_binary = MKA(env, str_binary); - // atom_buildDate = MKA(env, str_buildDate); atom_close = MKA(env, str_close); atom_closed = MKA(env, str_closed); atom_closing = MKA(env, str_closing); + atom_debug = MKA(env, str_debug); atom_error = MKA(env, str_error); atom_false = MKA(env, str_false); + atom_global_counters = MKA(env, str_global_counters); atom_in4_sockaddr = MKA(env, str_in4_sockaddr); atom_in6_sockaddr = MKA(env, str_in6_sockaddr); + atom_iow = MKA(env, str_iow); atom_loopback = MKA(env, str_loopback); - // atom_list = MKA(env, str_list); - // atom_mode = MKA(env, str_mode); atom_nif_abort = MKA(env, str_nif_abort); + atom_num_dinet = MKA(env, str_num_dinet); + atom_num_dinet6 = MKA(env, str_num_dinet6); + atom_num_dlocal = MKA(env, str_num_dlocal); + atom_num_pip = MKA(env, str_num_pip); + atom_num_psctp = MKA(env, str_num_psctp); + atom_num_ptcp = MKA(env, str_num_ptcp); + atom_num_pudp = MKA(env, str_num_pudp); + atom_num_sockets = MKA(env, str_num_sockets); + atom_num_tdgrams = MKA(env, str_num_tdgrams); + atom_num_tseqpkgs = MKA(env, str_num_tseqpkgs); + atom_num_tstreams = MKA(env, str_num_tstreams); atom_ok = MKA(env, str_ok); - // atom_once = MKA(env, str_once); - // atom_passive = MKA(env, str_passive); - // atom_receiver = MKA(env, str_receiver); atom_select = MKA(env, str_select); - // atom_tcp_closed = MKA(env, str_tcp_closed); atom_timeout = MKA(env, str_timeout); atom_true = MKA(env, str_true); atom_undefined = MKA(env, str_undefined); - // atom_version = MKA(env, str_version); atom_lowdelay = MKA(env, str_lowdelay); atom_throughput = MKA(env, str_throughput); @@ -7501,15 +7458,14 @@ int on_load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info) atom_exalloc = MKA(env, str_exalloc); atom_exbadstate = MKA(env, str_exbadstate); atom_exbusy = MKA(env, str_exbusy); - // atom_exnotopen = MKA(env, str_exnotopen); atom_exmon = MKA(env, str_exmon); atom_exself = MKA(env, str_exself); atom_exsend = MKA(env, str_exsend); // For storing "global" things... - // socketData.env = enif_alloc_env(); // We should really check - // socketData.version = MKA(env, ERTS_VERSION); - // socketData.buildDate = MKA(env, ERTS_BUILD_DATE); + // data.env = enif_alloc_env(); // We should really check + // data.version = MKA(env, ERTS_VERSION); + // data.buildDate = MKA(env, ERTS_BUILD_DATE); sockets = enif_open_resource_type_x(env, "sockets", diff --git a/erts/preloaded/ebin/socket.beam b/erts/preloaded/ebin/socket.beam index 2fc479f2d7..33784de074 100644 Binary files a/erts/preloaded/ebin/socket.beam and b/erts/preloaded/ebin/socket.beam differ diff --git a/erts/preloaded/src/socket.erl b/erts/preloaded/src/socket.erl index 90fe5b9f36..bab4fce3f3 100644 --- a/erts/preloaded/src/socket.erl +++ b/erts/preloaded/src/socket.erl @@ -49,12 +49,7 @@ shutdown/2, setopt/4, - getopt/3, - - %% Some IPv6 utility functions - link_if2idx/1, - link_idx2if/1, - link_ifs/0 + getopt/3 ]). -export_type([ @@ -91,12 +86,11 @@ ip_tos_flag/0 ]). - %% We support only a subset of all domains. -type domain() :: local | inet | inet6. %% We support only a subset of all types. --type type() :: stream | dgram | raw | seqpacket. +-type type() :: stream | dgram | raw | rdm | seqpacket. %% We support only a subset of all protocols: -type protocol() :: ip | tcp | udp | sctp. @@ -539,6 +533,15 @@ info() -> %% %% nif_register(SockRef, self()). %% +%% +%% +%% Maybe register the process under a name? +%% Something like: +%% +%% list_to_atom(lists:flatten(io_lib:format("socket-~p", [SockRef]))). +%% +%% +%% %% The nif sets up a monitor to this process, and if it dies the socket %% is closed. It is also used if someone wants to monitor the socket. %% @@ -548,6 +551,7 @@ info() -> %% socket:demonitor(Socket) %% %% These are basically used to monitor the controller process. +%% Should the socket record therefor contain the pid of the controller process? %% %% %% @@ -1509,53 +1513,6 @@ getopt(#socket{info = Info, ref = SockRef}, Level, Key) -> -%% =========================================================================== -%% -%% link_if2idx - Mappings between network interface names and indexes: if -> idx -%% -%% - --spec link_if2idx(If) -> {ok, Idx} | {error, Reason} when - If :: string(), - Idx :: non_neg_integer(), - Reason :: term(). - -link_if2idx(If) when is_list(If) -> - nif_link_if2idx(If). - - -%% =========================================================================== -%% -%% link_idx2if - Mappings between network interface names and indexes: idx -> if -%% -%% - --spec link_idx2if(Idx) -> {ok, If} | {error, Reason} when - Idx :: non_neg_integer(), - If :: string(), - Reason :: term(). - -link_idx2if(Idx) when is_integer(Idx) -> - nif_link_idx2if(Idx). - - - -%% =========================================================================== -%% -%% link_ifs - get network interface names and indexes -%% -%% - --spec link_ifs() -> Names | {error, Reason} when - Names :: [{Idx, If}], - Idx :: non_neg_integer(), - If :: string(), - Reason :: term(). - -link_ifs() -> - nif_link_ifs(). - - %% =========================================================================== %% @@ -2233,11 +2190,3 @@ nif_setopt(_Ref, _IsEnc, _Lev, _Key, _Val) -> nif_getopt(_Ref, _IsEnc, _Lev, _Key) -> erlang:error(badarg). -nif_link_if2idx(_Name) -> - erlang:error(badarg). - -nif_link_idx2if(_Id) -> - erlang:error(badarg). - -nif_link_ifs() -> - erlang:error(badarg). -- cgit v1.2.3 From 1f4a0eb6629d813be8656db239ee5b98a78088a9 Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Wed, 13 Jun 2018 12:42:56 +0200 Subject: [socket-nif] Added some use of debug and fixed bind Added (some) use of the debug printouts in (nif-) open and bind. Also fixed handling of the address argument in the bind function(s) (since it was changed to be of the in_sockaddr()). OTP-14831 --- erts/emulator/nifs/common/socket_nif.c | 426 +++++++++++++++------------------ erts/preloaded/ebin/socket.beam | Bin 38344 -> 38764 bytes erts/preloaded/src/socket.erl | 19 +- 3 files changed, 207 insertions(+), 238 deletions(-) diff --git a/erts/emulator/nifs/common/socket_nif.c b/erts/emulator/nifs/common/socket_nif.c index 3205ed1720..badaa8d988 100644 --- a/erts/emulator/nifs/common/socket_nif.c +++ b/erts/emulator/nifs/common/socket_nif.c @@ -433,6 +433,7 @@ typedef union { #define IS_ATOM(E, TE) enif_is_atom((E), (TE)) #define IS_BIN(E, TE) enif_is_binary((E), (TE)) +#define IS_MAP(E, TE) enif_is_map((E), (TE)) #define IS_NUM(E, TE) enif_is_number((E), (TE)) #define IS_TUPLE(E, TE) enif_is_tuple((E), (TE)) @@ -456,7 +457,8 @@ typedef union { dbg_printf proto; \ fflush(stdout); \ } -#define SDBG( proto ) SDEBUG( data.dbg , proto ) +#define SGDBG( proto ) SDEBUG( data.dbg , proto ) +#define SSDBG( __D__ , proto ) SDEBUG( (__D__)->dbg , proto ) /* =================================================================== * @@ -1155,43 +1157,45 @@ static ERL_NIF_TERM nfinalize_close(ErlNifEnv* env, SocketDescriptor* descP); -static BOOLEAN_T decode_in_sockaddr(ErlNifEnv* env, - ERL_NIF_TERM eSockAddr, - SocketAddress* sockAddrP, - unsigned int* addrLenP); -static BOOLEAN_T decode_in4_sockaddr(ErlNifEnv* env, - const ERL_NIF_TERM* eIn4SockAddr, - SocketAddress* sockAddrP, - unsigned int* addrLenP); -static BOOLEAN_T decode_in4_sockaddr_atomaddr(ErlNifEnv* env, - ERL_NIF_TERM eAddr, - int port, - SocketAddress* sockAddrP, - unsigned int* addrLenP); -static BOOLEAN_T decode_in4_sockaddr_addr(ErlNifEnv* env, +static char* decode_in_sockaddr(ErlNifEnv* env, + ERL_NIF_TERM eSockAddr, + SocketAddress* sockAddrP, + unsigned int* addrLenP); +static char* decode_in4_sockaddr(ErlNifEnv* env, + const ERL_NIF_TERM* eIn4SockAddr, + SocketAddress* sockAddrP, + unsigned int* addrLenP); +static char* decode_in4_sockaddr_atomaddr(ErlNifEnv* env, ERL_NIF_TERM eAddr, int port, SocketAddress* sockAddrP, unsigned int* addrLenP); -static BOOLEAN_T decode_in6_sockaddr(ErlNifEnv* env, - const ERL_NIF_TERM* eIn6SockAddr, - SocketAddress* sockAddrP, - unsigned int* addrLenP); -static BOOLEAN_T decode_in6_sockaddr_atomaddr(ErlNifEnv* env, - ERL_NIF_TERM eAddr, - int port, - unsigned int flowInfo, - unsigned int scopeId, - SocketAddress* sockAddrP, - unsigned int* addrLenP); -/* Decode an in6_sockaddr where the address field is a tuple */ -static BOOLEAN_T decode_in6_sockaddr_addr(ErlNifEnv* env, +static char* decode_in4_sockaddr_addr(ErlNifEnv* env, + ERL_NIF_TERM eAddr, + int port, + SocketAddress* sockAddrP, + unsigned int* addrLenP); +#if defined(HAVE_IN6) && defined(AF_INET6) +static char* decode_in6_sockaddr(ErlNifEnv* env, + const ERL_NIF_TERM* eIn6SockAddr, + SocketAddress* sockAddrP, + unsigned int* addrLenP); +static char* decode_in6_sockaddr_atomaddr(ErlNifEnv* env, ERL_NIF_TERM eAddr, int port, unsigned int flowInfo, unsigned int scopeId, SocketAddress* sockAddrP, unsigned int* addrLenP); +/* Decode an in6_sockaddr where the address field is a tuple */ +static char* decode_in6_sockaddr_addr(ErlNifEnv* env, + ERL_NIF_TERM eAddr, + int port, + unsigned int flowInfo, + unsigned int scopeId, + SocketAddress* sockAddrP, + unsigned int* addrLenP); +#endif static char* decode_laddress(ErlNifEnv* env, int domain, ERL_NIF_TERM localAddr, @@ -1202,11 +1206,7 @@ static char* decode_laddress_binary(ErlNifEnv* env, ERL_NIF_TERM localAddr, SocketAddress* localP, unsigned int* addrLenP); -static char* decode_laddress_tuple(ErlNifEnv* env, - int domain, - ERL_NIF_TERM laddr, - SocketAddress* localP, - unsigned int* addrLenP); +/* static char* decode_address_tuple(ErlNifEnv* env, int domain, const ERL_NIF_TERM* addrt, @@ -1220,6 +1220,7 @@ static char* decode_address_atom(ErlNifEnv* env, int port, SocketAddress* localP, unsigned int* addrLenP); +*/ /* static char* decode_send_addr(ErlNifEnv* env, int domain, @@ -1610,10 +1611,10 @@ ERL_NIF_TERM nif_info(ErlNifEnv* env, * Create an endpoint for communication. * * Arguments: - * Domain - * Type - * Protocol - * Extra - A map with obscure options. + * Domain - The domain, for example 'inet' + * Type - Type of socket, for example 'stream' + * Protocol - The protocol, for example 'tcp' + * Extra - A map with "obscure" options. * Currently the only allowed option is netns (network namespace). * This is *only* allowed on linux! */ @@ -1626,18 +1627,28 @@ ERL_NIF_TERM nif_open(ErlNifEnv* env, int domain, type, proto; char* netns; ERL_NIF_TERM emap; + ERL_NIF_TERM result; + SGDBG( ("nif_open -> entry with %d args\r\n", argc) ); + /* Extract arguments and perform preliminary validation */ if ((argc != 4) || - !enif_get_int(env, argv[0], &edomain) || - !enif_get_int(env, argv[1], &etype) || - !enif_get_int(env, argv[2], &eproto) || - !enif_is_map(env, argv[3])) { + !GET_INT(env, argv[0], &edomain) || + !GET_INT(env, argv[1], &etype) || + !GET_INT(env, argv[2], &eproto) || + !IS_MAP(env, argv[3])) { return enif_make_badarg(env); } emap = argv[3]; + SGDBG( ("nif_open -> " + "\r\n edomain: %T" + "\r\n etype: %T" + "\r\n eproto: %T" + "\r\n extra: %T" + "\r\n", argv[0], argv[1], argv[2], argv[3]) ); + if (!edomain2domain(edomain, &domain)) return enif_make_badarg(env); @@ -1655,13 +1666,23 @@ ERL_NIF_TERM nif_open(ErlNifEnv* env, netns = NULL; #endif - return nopen(env, domain, type, proto, netns); + result = nopen(env, domain, type, proto, netns); + + SGDBG( ("nif_open -> done with result: " + "\r\n %T" + "\r\n", result) ); + + return result; } /* nopen - create an endpoint for communication * * Assumes the input has been validated. + * + * Normally we want debugging on (individual) sockets to be controlled + * by the sockets own debug flag. But since we don't even have a socket + * yet, we must use the global debug flag. */ static ERL_NIF_TERM nopen(ErlNifEnv* env, @@ -1677,9 +1698,16 @@ ERL_NIF_TERM nopen(ErlNifEnv* env, int current_ns; #endif + SGDBG( ("nopen -> entry with" + "\r\n domain: %d" + "\r\n type: %d" + "\r\n protocol: %d" + "\r\n netns: %s" + "\r\n", domain, type, protocol, ((netns == NULL) ? "NULL" : netns)) ); #ifdef HAVE_SETNS - if (!change_network_namespace(netns, ¤t_ns, &save_errno)) + if ((netns != NULL) && + !change_network_namespace(netns, ¤t_ns, &save_errno)) return make_error2(env, save_errno); #endif @@ -1687,7 +1715,8 @@ ERL_NIF_TERM nopen(ErlNifEnv* env, return make_error2(env, sock_errno()); #ifdef HAVE_SETNS - if (!restore_network_namespace(current_ns, sock, &save_errno)) + if ((netns != NULL) && + !restore_network_namespace(current_ns, sock, &save_errno)) return make_error2(env, save_errno); if (netns != NULL) @@ -1736,12 +1765,16 @@ ERL_NIF_TERM nopen(ErlNifEnv* env, #ifdef __WIN32__ - /* What is the point of this? + /* */ SELECT(env, event, @@ -1769,6 +1802,9 @@ BOOLEAN_T change_network_namespace(char* netns, int* cns, int* err) int current_ns = 0; int new_ns = 0; + SGDBG( ("change_network_namespace -> entry with" + "\r\n new ns: %s", netns) ); + if (netns != NULL) { current_ns = open("/proc/self/ns/net", O_RDONLY); if (current_ns == INVALID_SOCKET) { @@ -1816,6 +1852,10 @@ static BOOLEAN_T restore_network_namespace(int ns, SOCKET sock, int* err) { int save_errno; + + SGDBG( ("restore_network_namespace -> entry with" + "\r\n ns: %d", ns) ); + if (ns != INVALID_SOCKET) { if (setns(ns, CLONE_NEWNET) != 0) { /* XXX Failed to restore network namespace. @@ -1879,6 +1919,12 @@ ERL_NIF_TERM nif_bind(ErlNifEnv* env, return enif_make_badarg(env); } + SSDBG( descP, + ("nif_bind -> " + "\r\n Socket: %T" + "\r\n Addr: %T" + "\r\n", argv[0], argv[1]) ); + /* Basic arg validation: * - if binary domain must be local (unix) * - if tuple domain must be either inet or inet6 @@ -1910,11 +1956,19 @@ ERL_NIF_TERM nbind(ErlNifEnv* env, { SocketAddress local; unsigned int addrLen = 0; - char* err; + char* xerr; int port; - if ((err = decode_laddress(env, descP->domain, addr, &local, &addrLen)) != NULL) - return make_error1(env, err); + SSDBG( descP, + ("nbind -> entry with" + "\r\n addr: %T" + "\r\n", addr) ); + + if ((xerr = decode_laddress(env, + descP->domain, addr, &local, &addrLen)) != NULL) + return make_error1(env, xerr); + + SSDBG( descP, ("nbind -> try bind\r\n") ); if (IS_SOCKET_ERROR(sock_bind(descP->sock, (struct sockaddr*) &local, addrLen))) { @@ -1931,15 +1985,17 @@ ERL_NIF_TERM nbind(ErlNifEnv* env, port = 0; } - return make_ok2(env, enif_make_int(env, port)); + SSDBG( descP, ("nbind -> done with port = %d\r\n", port) ); + + return make_ok2(env, MKI(env, port)); } /* Decode the (local) address. The format of the address should - * either be an binary (domain = local) or a two tuple (first - * part the actual address tuple and the second part the port - * number) if the domain is either INET or INET6. + * either be an binary (domain = local) or an in_sockaddr(), which + * is either a in4_sockaddr record or a int6_sockaddr record + * (if domain is either inet or inet6). */ static char* decode_laddress(ErlNifEnv* env, @@ -1951,7 +2007,7 @@ char* decode_laddress(ErlNifEnv* env, if (IS_BIN(env, localAddr)) { return decode_laddress_binary(env, domain, localAddr, localP, addrLenP); } else if (IS_TUPLE(env, localAddr)) { - return decode_laddress_tuple(env, domain, localAddr, localP, addrLenP); + return decode_in_sockaddr(env, localAddr, localP, addrLenP); } else { return str_einval; } @@ -1960,7 +2016,7 @@ char* decode_laddress(ErlNifEnv* env, /* Only for domain = local (unix) - * The erlang interface module ensures that the size of the + * The erlang interface module (socket) ensures that the size of the * binary is > 0, so we need not do that here. */ static @@ -2014,107 +2070,6 @@ char* decode_laddress_binary(ErlNifEnv* env, } -/* Only for domain = INET and INET6 - * The (local) address here is a two tuple: - * {Addr, Port} - * where - * Addr: - * - a tuple of size 4 (INET) or size 8 (INET6) - * - the atoms 'any' or 'loopback' - * Port: - * - the port number (int) - * - */ -static -char* decode_laddress_tuple(ErlNifEnv* env, - int domain, - ERL_NIF_TERM laddr, - SocketAddress* localP, - unsigned int* addrLenP) -{ - const ERL_NIF_TERM* laddrt; - int laddrtSz; - int port; - - /* First, get the tuple and verify its size (2) */ - - if (!GET_TUPLE(env, laddr, &laddrtSz, &laddrt)) - return str_einval; - - if (laddrtSz != 2) - return str_einval; - - /* So far so good. The first element is either a tuple or an atom */ - - if (IS_TUPLE(env, laddrt[0]) && - IS_NUM(env, laddrt[1])) { - - /* We handle two different tuples: - * - size 4 (INET) - * - size 8 (INET6) - */ - - const ERL_NIF_TERM* addrt; - int addrtSz; - - if (!GET_TUPLE(env, laddrt[0], &addrtSz, &addrt)) - return str_einval; // PLACEHOLDER - - if (!GET_INT(env, laddrt[1], &port)) - return str_einval; // PLACEHOLDER - - switch (domain) { - case AF_INET: - if (addrtSz != 4) - return str_einval; - break; - -#if defined(HAVE_IN6) && defined(AF_INET6) - case AF_INET6: - if (addrtSz != 8) - return str_einval; - break; -#endif - - default: - return str_eafnosupport; - break; - } - - return decode_address_tuple(env, - domain, addrt, port, - localP, addrLenP); - - } else if (IS_ATOM(env, laddrt[0]) && - IS_NUM(env, laddrt[1])) { - - /* There are only two atoms we handle: - * - any - * - loopback - */ - - unsigned int len; - char a[16]; // Just in case... - - if (!(GET_ATOM_LEN(env, laddrt[0], &len) && - (len > 0) && - (len <= (sizeof("loopback"))))) - return str_einval; - - if (!GET_ATOM(env, laddrt[0], a, sizeof(a))) - return str_einval; - - return decode_address_atom(env, - domain, a, len, port, - localP, addrLenP); - - } else { - return str_einval; - } - -} - - /* ---------------------------------------------------------------------- * nif_connect @@ -2136,6 +2091,7 @@ ERL_NIF_TERM nif_connect(ErlNifEnv* env, { SocketDescriptor* descP; ERL_NIF_TERM eSockAddr; + char* xres; /* Extract arguments and perform preliminary validation */ @@ -2145,9 +2101,9 @@ ERL_NIF_TERM nif_connect(ErlNifEnv* env, } eSockAddr = argv[1]; - if (!decode_in_sockaddr(env, eSockAddr, - &descP->remote, &descP->addrLen)) { - return enif_make_badarg(env); + if ((xres = decode_in_sockaddr(env, eSockAddr, + &descP->remote, &descP->addrLen)) != NULL) { + return make_error1(env, xres); } return nconnect(env, descP); @@ -2768,6 +2724,7 @@ ERL_NIF_TERM nif_sendto(ErlNifEnv* env, ERL_NIF_TERM eSockAddr; SocketAddress remoteAddr; unsigned int remoteAddrLen; + char* xres; /* Extract arguments and perform preliminary validation */ @@ -2787,10 +2744,10 @@ ERL_NIF_TERM nif_sendto(ErlNifEnv* env, if (!esendflags2sendflags(eflags, &flags)) return make_error(env, atom_einval); - if (!decode_in_sockaddr(env, eSockAddr, - &remoteAddr, - &remoteAddrLen)) - return make_error(env, atom_einval); + if ((xres = decode_in_sockaddr(env, eSockAddr, + &remoteAddr, + &remoteAddrLen)) != NULL) + return make_error1(env, xres); return nsendto(env, descP, sendRef, &data, flags, &remoteAddr, remoteAddrLen); } @@ -5622,17 +5579,17 @@ char* decode_send_addr_tuple(ErlNifEnv* env, * */ static -BOOLEAN_T decode_in_sockaddr(ErlNifEnv* env, - ERL_NIF_TERM eSockAddr, - SocketAddress* sockAddrP, - unsigned int* addrLenP) +char* decode_in_sockaddr(ErlNifEnv* env, + ERL_NIF_TERM eSockAddr, + SocketAddress* sockAddrP, + unsigned int* addrLenP) { const ERL_NIF_TERM* addrt; int addrtSz; - ERL_NIF_TERM result; + char* result = NULL; if (!GET_TUPLE(env, eSockAddr, &addrtSz, &addrt)) - return FALSE; + return str_einval; /* * We use the tuple size to figure out which @@ -5650,7 +5607,7 @@ BOOLEAN_T decode_in_sockaddr(ErlNifEnv* env, #endif default: - result = FALSE; + result = str_eafnosupport; break; } @@ -5665,20 +5622,20 @@ BOOLEAN_T decode_in_sockaddr(ErlNifEnv* env, * The third and final, the ip4_address tuple. */ static -BOOLEAN_T decode_in4_sockaddr(ErlNifEnv* env, - const ERL_NIF_TERM* eIn4SockAddr, - SocketAddress* sockAddrP, - unsigned int* addrLenP) +char* decode_in4_sockaddr(ErlNifEnv* env, + const ERL_NIF_TERM* eIn4SockAddr, + SocketAddress* sockAddrP, + unsigned int* addrLenP) { int port; /* 1: Ensure that the tuple has the correct tag: in4_sockaddr */ if (COMPARE(atom_in4_sockaddr, eIn4SockAddr[0]) != 0) - return FALSE; + return str_einval; /* 2: Get the port number */ if (!GET_INT(env, eIn4SockAddr[1], &port)) - return FALSE; + return str_einval; /* 3: Get the address. * It can either be the atoms: any | loopback, @@ -5691,18 +5648,18 @@ BOOLEAN_T decode_in4_sockaddr(ErlNifEnv* env, return decode_in4_sockaddr_addr(env, eIn4SockAddr[2], port, sockAddrP, addrLenP); } else { - return FALSE; + return str_einval; } } static -BOOLEAN_T decode_in4_sockaddr_atomaddr(ErlNifEnv* env, - ERL_NIF_TERM eAddr, - int port, - SocketAddress* sockAddrP, - unsigned int* addrLenP) +char* decode_in4_sockaddr_atomaddr(ErlNifEnv* env, + ERL_NIF_TERM eAddr, + int port, + SocketAddress* sockAddrP, + unsigned int* addrLenP) { struct in_addr addr; @@ -5711,7 +5668,7 @@ BOOLEAN_T decode_in4_sockaddr_atomaddr(ErlNifEnv* env, } else if (COMPARE(atom_any, eAddr) == 0) { addr.s_addr = sock_htonl(INADDR_ANY); } else { - return FALSE; + return str_einval; } sys_memzero((char*) sockAddrP, sizeof(struct sockaddr_in)); @@ -5721,9 +5678,9 @@ BOOLEAN_T decode_in4_sockaddr_atomaddr(ErlNifEnv* env, sockAddrP->sai.sin_family = AF_INET; sockAddrP->sai.sin_port = sock_htons(port); sockAddrP->sai.sin_addr.s_addr = addr.s_addr; - *addrLenP = sizeof(struct sockaddr_in); + *addrLenP = sizeof(struct sockaddr_in); - return TRUE; + return NULL; } @@ -5731,11 +5688,11 @@ BOOLEAN_T decode_in4_sockaddr_atomaddr(ErlNifEnv* env, * Its *supposed* to be an ip4_address (tuple). */ static -BOOLEAN_T decode_in4_sockaddr_addr(ErlNifEnv* env, - ERL_NIF_TERM eAddr, - int port, - SocketAddress* sockAddrP, - unsigned int* addrLenP) +char* decode_in4_sockaddr_addr(ErlNifEnv* env, + ERL_NIF_TERM eAddr, + int port, + SocketAddress* sockAddrP, + unsigned int* addrLenP) { const ERL_NIF_TERM* ip4AddrT; int ip4AddrTSz; @@ -5744,10 +5701,10 @@ BOOLEAN_T decode_in4_sockaddr_addr(ErlNifEnv* env, /* This shall be a 4 tuple */ if (!GET_TUPLE(env, eAddr, &ip4AddrTSz, &ip4AddrT)) - return FALSE; + return str_einval; if (ip4AddrTSz != 4) - return FALSE; + return str_einval; sys_memzero((char*)sockAddrP, sizeof(struct sockaddr_in)); #ifndef NO_SA_LEN @@ -5757,13 +5714,13 @@ BOOLEAN_T decode_in4_sockaddr_addr(ErlNifEnv* env, sockAddrP->sai.sin_port = sock_htons(port); for (a = 0; a < 4; a++) { if (!GET_INT(env, ip4AddrT[a], &v)) - return FALSE; + return str_einval; addr[a] = v; } sys_memcpy(&sockAddrP->sai.sin_addr, &addr, sizeof(addr)); *addrLenP = sizeof(struct sockaddr_in); - return TRUE; + return NULL; } @@ -5777,29 +5734,29 @@ BOOLEAN_T decode_in4_sockaddr_addr(ErlNifEnv* env, */ #if defined(HAVE_IN6) && defined(AF_INET6) static -BOOLEAN_T decode_in6_sockaddr(ErlNifEnv* env, - const ERL_NIF_TERM* eIn6SockAddr, - SocketAddress* sockAddrP, - unsigned int* addrLenP) +char* decode_in6_sockaddr(ErlNifEnv* env, + const ERL_NIF_TERM* eIn6SockAddr, + SocketAddress* sockAddrP, + unsigned int* addrLenP) { int port; unsigned int flowInfo, scopeId; /* 1: Ensure that the tuple has the correct tag: in6_sockaddr */ if (COMPARE(atom_in6_sockaddr, eIn6SockAddr[0]) != 0) - return FALSE; + return str_einval; /* 2: Get the port number */ if (!GET_INT(env, eIn6SockAddr[1], &port)) - return FALSE; + return str_einval; /* 4: Get the flowinfo */ if (!GET_UINT(env, eIn6SockAddr[3], &flowInfo)) - return FALSE; + return str_einval; /* 5: Get the scope_id */ if (!GET_UINT(env, eIn6SockAddr[4], &scopeId)) - return FALSE; + return str_einval; /* 3: Get the address. * It can either be the atoms: any | loopback, @@ -5814,7 +5771,7 @@ BOOLEAN_T decode_in6_sockaddr(ErlNifEnv* env, flowInfo, scopeId, sockAddrP, addrLenP); } else { - return FALSE; + return str_einval; } } #endif @@ -5822,13 +5779,13 @@ BOOLEAN_T decode_in6_sockaddr(ErlNifEnv* env, #if defined(HAVE_IN6) && defined(AF_INET6) static -BOOLEAN_T decode_in6_sockaddr_atomaddr(ErlNifEnv* env, - ERL_NIF_TERM eAddr, - int port, - unsigned int flowInfo, - unsigned int scopeId, - SocketAddress* sockAddrP, - unsigned int* addrLenP) +char* decode_in6_sockaddr_atomaddr(ErlNifEnv* env, + ERL_NIF_TERM eAddr, + int port, + unsigned int flowInfo, + unsigned int scopeId, + SocketAddress* sockAddrP, + unsigned int* addrLenP) { const struct in6_addr* addr; @@ -5837,7 +5794,7 @@ BOOLEAN_T decode_in6_sockaddr_atomaddr(ErlNifEnv* env, } else if (COMPARE(atom_any, eAddr) == 0) { addr = &in6addr_any; } else { - return FALSE; + return str_einval; } sys_memzero((char*)sockAddrP, sizeof(struct sockaddr_in6)); @@ -5851,7 +5808,7 @@ BOOLEAN_T decode_in6_sockaddr_atomaddr(ErlNifEnv* env, sockAddrP->sai6.sin6_addr = *addr; *addrLenP = sizeof(struct sockaddr_in6); - return TRUE; + return NULL; } #endif @@ -5860,13 +5817,13 @@ BOOLEAN_T decode_in6_sockaddr_atomaddr(ErlNifEnv* env, #if defined(HAVE_IN6) && defined(AF_INET6) /* Decode an in6_sockaddr where the address field is a tuple */ static -BOOLEAN_T decode_in6_sockaddr_addr(ErlNifEnv* env, - ERL_NIF_TERM eAddr, - int port, - unsigned int flowInfo, - unsigned int scopeId, - SocketAddress* sockAddrP, - unsigned int* addrLenP) +char* decode_in6_sockaddr_addr(ErlNifEnv* env, + ERL_NIF_TERM eAddr, + int port, + unsigned int flowInfo, + unsigned int scopeId, + SocketAddress* sockAddrP, + unsigned int* addrLenP) { const ERL_NIF_TERM* ip6AddrT; int ip6AddrTSz; @@ -5875,10 +5832,10 @@ BOOLEAN_T decode_in6_sockaddr_addr(ErlNifEnv* env, /* This shall be a 8 tuple */ if (!GET_TUPLE(env, eAddr, &ip6AddrTSz, &ip6AddrT)) - return FALSE; + return str_einval; if (ip6AddrTSz != 8) - return FALSE; + return str_einval; sys_memzero((char*)sockAddrP, sizeof(struct sockaddr_in6)); #ifndef NO_SA_LEN @@ -5893,14 +5850,14 @@ BOOLEAN_T decode_in6_sockaddr_addr(ErlNifEnv* env, */ for (a = 0; a < 8; a++) { if (!GET_INT(env, ip6AddrT[a], &v)) - return FALSE; + return str_einval; addr[a*2 ] = ((v >> 8) & 0xFF); addr[a*2+1] = (v & 0xFF); } sys_memcpy(&sockAddrP->sai6.sin6_addr, &addr, sizeof(addr)); *addrLenP = sizeof(struct sockaddr_in6); - return TRUE; + return NULL; } #endif @@ -5908,6 +5865,7 @@ BOOLEAN_T decode_in6_sockaddr_addr(ErlNifEnv* env, /* Decode the 4- or 8-element address tuple * and initiate the socket address structure. */ +/* static char* decode_address_tuple(ErlNifEnv* env, int domain, @@ -5917,9 +5875,9 @@ char* decode_address_tuple(ErlNifEnv* env, unsigned int* addrLenP) { - /* We now *know* that the size of the tuple is correct, + / * We now *know* that the size of the tuple is correct, * so we don't need to check anything here, just unpack. - */ + * / switch (domain) { case AF_INET: @@ -5957,9 +5915,9 @@ char* decode_address_tuple(ErlNifEnv* env, addrP->sai6.sin6_family = domain; addrP->sai6.sin6_port = sock_htons(port); addrP->sai6.sin6_flowinfo = 0; - /* The address tuple is of size 8 + / * The address tuple is of size 8 * and each element is a two byte integer - */ + * / for (a = 0; a < 8; a++) { if (!GET_INT(env, addrt[a], &v)) return str_einval; @@ -5973,11 +5931,12 @@ char* decode_address_tuple(ErlNifEnv* env, break; #endif - } /* switch (domain) */ + } / * switch (domain) * / return str_eafnosupport; } +*/ /* Encode the 4- or 8-element address tuple from the socket address structure. * @@ -6108,6 +6067,7 @@ void encode_address(ErlNifEnv* env, /* Decode the address when its an atom. * Currently we only accept two atoms: 'any' and 'loopback' */ +/* static char* decode_address_atom(ErlNifEnv* env, int domain, @@ -6127,7 +6087,7 @@ char* decode_address_atom(ErlNifEnv* env, return str_einval; } - /* If we get this far, we *know* its either 'any' or 'loopback' */ + / * If we get this far, we *know* its either 'any' or 'loopback' * / switch (domain) { case AF_INET: @@ -6178,7 +6138,7 @@ char* decode_address_atom(ErlNifEnv* env, return NULL; } - +*/ static BOOLEAN_T decode_bool(ErlNifEnv* env, ERL_NIF_TERM eVal, BOOLEAN_T* val) @@ -7203,11 +7163,11 @@ void dbg_printf( const char* format, ... ) if (!dbg_realtime(&ts)) { if (dbg_timespec2str(stamp, sizeof(stamp), &ts) != 0) { - // res = enif_snprintf(f, sizeof(f), "NET [%s] %s", TSNAME(), format); - res = enif_snprintf(f, sizeof(f), "NET [%s]", format); + // res = enif_snprintf(f, sizeof(f), "SOCKET [%s] %s", TSNAME(), format); + res = enif_snprintf(f, sizeof(f), "SOCKET [%s]", format); } else { - // res = enif_snprintf(f, sizeof(f), "NET[%s] [%s] %s", stamp, TSNAME(), format); - res = enif_snprintf(f, sizeof(f), "NET [%s] %s", stamp, format); + // res = enif_snprintf(f, sizeof(f), "SOCKET [%s] [%s] %s", stamp, TSNAME(), format); + res = enif_snprintf(f, sizeof(f), "SOCKET [%s] %s", stamp, format); } if (res > 0) { diff --git a/erts/preloaded/ebin/socket.beam b/erts/preloaded/ebin/socket.beam index 33784de074..c4f0390120 100644 Binary files a/erts/preloaded/ebin/socket.beam and b/erts/preloaded/ebin/socket.beam differ diff --git a/erts/preloaded/src/socket.erl b/erts/preloaded/src/socket.erl index bab4fce3f3..5a0748e8fb 100644 --- a/erts/preloaded/src/socket.erl +++ b/erts/preloaded/src/socket.erl @@ -324,7 +324,9 @@ mincost | integer(). --type socket_info() :: map(). +-type socket_info() :: #{domain => domain(), + type => type(), + protocol => protocol()}. -record(socket, {info :: socket_info(), ref :: reference()}). %% -opaque socket() :: {socket, socket_info(), reference()}. @@ -671,10 +673,17 @@ bind(Socket, File) when is_list(File) andalso (File =/= []) -> true -> {error, einval} end; -bind(#socket{ref = SockRef} = _Socket, SockAddr) - when is_record(SockAddr, in4_sockaddr) orelse - is_record(SockAddr, in6_sockaddr) orelse - (SockAddr =:= any) orelse (SockAddr =:= loopback) -> +bind(#socket{info = #{domain := inet}} = Socket, Addr) + when ((Addr =:= any) orelse (Addr =:= loopback)) -> + bind(Socket, #in4_sockaddr{addr = Addr}); +bind(#socket{info = #{domain := inet6}} = Socket, Addr) + when ((Addr =:= any) orelse (Addr =:= loopback)) -> + bind(Socket, #in6_sockaddr{addr = Addr}); +bind(#socket{info = #{domain := inet}, ref = SockRef} = _Socket, SockAddr) + when is_record(SockAddr, in4_sockaddr) -> + nif_bind(SockRef, SockAddr); +bind(#socket{info = #{domain := inet6}, ref = SockRef} = _Socket, SockAddr) + when is_record(SockAddr, in6_sockaddr) -> nif_bind(SockRef, SockAddr). -- cgit v1.2.3 From 2d6517fe41bebdd73dfc2d24a29b417587234ef1 Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Tue, 29 May 2018 10:42:51 +0200 Subject: [net-nif] Erlang interface module for net This is part of the nififying of the inet driver. OTP-14831 --- erts/preloaded/src/Makefile | 1 + erts/preloaded/src/net.erl | 357 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 358 insertions(+) create mode 100644 erts/preloaded/src/net.erl diff --git a/erts/preloaded/src/Makefile b/erts/preloaded/src/Makefile index aa9390b038..213dc2a1a2 100644 --- a/erts/preloaded/src/Makefile +++ b/erts/preloaded/src/Makefile @@ -36,6 +36,7 @@ include $(ERL_TOP)/lib/kernel/vsn.mk PRE_LOADED_ERL_MODULES = \ erl_prim_loader \ init \ + net \ prim_buffer \ prim_file \ prim_inet \ diff --git a/erts/preloaded/src/net.erl b/erts/preloaded/src/net.erl new file mode 100644 index 0000000000..39c907eca3 --- /dev/null +++ b/erts/preloaded/src/net.erl @@ -0,0 +1,357 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2018-2018. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% + +-module(net). + +%% Administrative and "global" utility functions +-export([ + on_load/0, on_load/1, on_load/2, + info/0 + ]). + +-export([ + getnameinfo/1, getnameinfo/2, + getaddrinfo/2, + + if_name2index/1, + if_index2name/1, + if_names/0 + ]). + +-export_type([ + ip_address/0, + ip4_address/0, + ip6_address/0, + in_sockaddr/0, + in4_sockaddr/0, + in6_sockaddr/0, + port_number/0, + + address_info/0, + name_info/0, + + name_info_flags/0, + name_info_flag/0, + name_info_flag_ext/0, + + network_interface_name/0, + network_interface_index/0 + ]). + +-record(name_info, {flags, host, service}). +-record(address_info, {flags, family, socket_type, protocol, addr}). + +%% Many of these should be moved to the socket module. +-type ip_address() :: ip4_address() | ip6_address(). +-type ip4_address() :: {0..255, 0..255, 0..255, 0..255}. +-type ip6_address() :: + {0..65535, + 0..65535, + 0..65535, + 0..65535, + 0..65535, + 0..65535, + 0..65535, + 0..65535}. +-type uint20() :: 0..16#FFFFF. +-type uint32() :: 0..16#FFFFFFFF. +-type in6_flow_info() :: uint20(). +-type in6_scope_id() :: uint32(). +-record(in4_sockaddr, {port = 0 :: port_number(), + addr = any :: any | loopback | ip4_address()}). +-type in4_sockaddr() :: #in4_sockaddr{}. +-record(in6_sockaddr, {port = 0 :: port_number(), + addr = any :: any | loopback | ip6_address(), + flowinfo = 0 :: in6_flow_info(), + scope_id = 0 :: in6_scope_id()}). +-type in6_sockaddr() :: #in6_sockaddr{}. + +-type in_sockaddr() :: in4_sockaddr() | in6_sockaddr(). + +-type port_number() :: 0..65535. + +-type name_info_flags() :: [name_info_flag()|name_info_flag_ext()]. +-type name_info_flag() :: namereqd | + dgram | + nofqdn | + numerichost | + nomericserv. +-type name_info_flag_ext() :: idn | + idna_allow_unassigned | + idna_use_std3_ascii_rules. +-type name_info() :: #name_info{}. +-type address_info() :: #address_info{}. +-type network_interface_name() :: string(). +-type network_interface_index() :: non_neg_integer(). + + +-define(NET_NAME_INFO_NAMEREQD, 0). +-define(NET_NAME_INFO_DGRAM, 1). +-define(NET_NAME_INFO_NOFQDN, 2). +-define(NET_NAME_INFO_NUMERICHOST, 3). +-define(NET_NAME_INFO_NUMERICSERV, 4). +-define(NET_NAME_INFO_IDN, 5). +-define(NET_NAME_INFO_IDNA_ALLOW_UNASSIGNED, 6). +-define(NET_NAME_INFO_IDNA_USE_STD3_ASCII_RULES, 7). + + +%% =========================================================================== +%% +%% Administrative and utility API +%% +%% =========================================================================== + +-spec on_load() -> ok. + +%% Should we require that the Extra arg is a map? +on_load() -> + on_load(#{}). + +-spec on_load(Extra) -> ok when + Extra :: maps:map(). + +on_load(Extra) when is_map(Extra) -> + on_load(atom_to_list(?MODULE), Extra). + +-spec on_load(Path, Extra) -> ok when + Path :: string(), + Extra :: maps:map(). + +on_load(Path, Extra) when is_list(Path) andalso is_map(Extra) -> + on_load(nif_is_loaded(), Path, Extra). + +on_load(true, _Path, _Extra) -> + ok; +on_load(false, Path, Extra) -> + ok = erlang:load_nif(Path, maps:put(timestamp, formated_timestamp(), Extra)). + + + +-spec info() -> list(). + +info() -> + nif_info(). + + + +%% =========================================================================== +%% +%% The proper net API +%% +%% =========================================================================== + +%% =========================================================================== +%% +%% getnameinfo - Address-to-name translation in protocol-independent manner. +%% +%% + +-spec getnameinfo(SockAddr) -> {ok, Info} | {error, Reason} when + SockAddr :: in_sockaddr(), + Info :: name_info(), + Reason :: term(). + +getnameinfo(SockAddr) + when is_record(SockAddr, in4_sockaddr) orelse + is_record(SockAddr, in6_sockaddr) -> + getnameinfo(SockAddr, []). + +-spec getnameinfo(SockAddr, Flags) -> {ok, Info} | {error, Reason} when + SockAddr :: in_sockaddr(), + Flags :: name_info_flags(), + Info :: name_info(), + Reason :: term(). + +getnameinfo(SockAddr, Flags) + when (is_record(SockAddr, in4_sockaddr) orelse + is_record(SockAddr, in6_sockaddr)) andalso + is_list(Flags) -> + try + begin + EFlags = enc_name_info_flags(Flags), + nif_getnameinfo(SockAddr, EFlags) + end + catch + throw:T -> + T; + error:Reason -> + {error, Reason} + end. + + +enc_name_info_flags([]) -> + 0; +enc_name_info_flags(Flags) -> + EFlags = [{namereqd, ?NET_NAME_INFO_NAMEREQD}, + {dgram, ?NET_NAME_INFO_DGRAM}, + {nofqdn, ?NET_NAME_INFO_NOFQDN}, + {numerichost, ?NET_NAME_INFO_NUMERICHOST}, + {numericserv, ?NET_NAME_INFO_NUMERICSERV}, + + %% The below flags was introduce with glibc 2.3.4. + {idn, ?NET_NAME_INFO_IDN}, + {idna_allow_unassigned, ?NET_NAME_INFO_IDNA_ALLOW_UNASSIGNED}, + {idna_use_std3_ascii_rules,?NET_NAME_INFO_IDNA_USE_STD3_ASCII_RULES}], + enc_flags(Flags, EFlags). + + +enc_flags([], _) -> + 0; +enc_flags(Flags, EFlags) -> + F = fun(Flag, Acc) -> + case lists:keysearch(Flag, 1, EFlags) of + {value, {Flag, EFlag}} -> + Acc bor (1 bsl EFlag); + false -> + throw({error, {unknown_flag, Flag}}) + end + end, + lists:foldl(F, 0, Flags). + + +%% =========================================================================== +%% +%% getaddrinfo - Network address and service translation +%% +%% There is also a "hint" argument that we "at some point" should implement. + +-spec getaddrinfo(Host, Service) -> {ok, Info} | {error, Reason} when + Host :: string(), + Service :: string(), + Info :: [address_info()], + Reason :: term(). + +getaddrinfo(Host, Service) + when (is_list(Host) orelse (Host =:= undefined)) andalso + (is_list(Service) orelse (Service =:= undefined)) andalso + (not ((Service =:= undefined) andalso (Host =:= undefined))) -> + nif_getaddrinfo(Host, Service, undefined). + + + +%% =========================================================================== +%% +%% if_name2index - Mappings between network interface names and indexes: +%% name -> idx +%% +%% + +-spec if_name2index(Name) -> {ok, Idx} | {error, Reason} when + Name :: string(), + Idx :: non_neg_integer(), + Reason :: term(). + +if_name2index(If) when is_list(If) -> + nif_if_name2index(If). + + + +%% =========================================================================== +%% +%% if_index2name - Mappings between network interface names and indexes: +%% idx -> name +%% +%% + +-spec if_index2name(Idx) -> {ok, Name} | {error, Reason} when + Idx :: non_neg_integer(), + Name :: string(), + Reason :: term(). + +if_index2name(Idx) when is_integer(Idx) -> + nif_if_index2name(Idx). + + + +%% =========================================================================== +%% +%% if_names - Get network interface names and indexes +%% +%% + +-spec if_names() -> Names | {error, Reason} when + Names :: [{Idx, If}], + Idx :: non_neg_integer(), + If :: string(), + Reason :: term(). + +if_names() -> + nif_if_names(). + + + +%% =========================================================================== +%% +%% Misc utility functions +%% +%% =========================================================================== + +formated_timestamp() -> + format_timestamp(os:timestamp()). + +format_timestamp(Now) -> + N2T = fun(N) -> calendar:now_to_local_time(N) end, + format_timestamp(Now, N2T, true). + +format_timestamp({_N1, _N2, N3} = N, N2T, true) -> + FormatExtra = ".~.2.0w", + ArgsExtra = [N3 div 10000], + format_timestamp(N, N2T, FormatExtra, ArgsExtra); +format_timestamp({_N1, _N2, _N3} = N, N2T, false) -> + FormatExtra = "", + ArgsExtra = [], + format_timestamp(N, N2T, FormatExtra, ArgsExtra). + +format_timestamp(N, N2T, FormatExtra, ArgsExtra) -> + {Date, Time} = N2T(N), + {YYYY,MM,DD} = Date, + {Hour,Min,Sec} = Time, + FormatDate = + io_lib:format("~.4w-~.2.0w-~.2.0w ~.2.0w:~.2.0w:~.2.0w" ++ FormatExtra, + [YYYY, MM, DD, Hour, Min, Sec] ++ ArgsExtra), + lists:flatten(FormatDate). + + +%% =========================================================================== +%% +%% The actual NIF-functions. +%% +%% =========================================================================== + +nif_is_loaded() -> + false. + +nif_info() -> + erlang:error(badarg). + +nif_getnameinfo(_Addr, _Flags) -> + erlang:error(badarg). + +nif_getaddrinfo(_Host, _Service, _Hints) -> + erlang:error(badarg). + +nif_if_name2index(_Name) -> + erlang:error(badarg). + +nif_if_index2name(_Id) -> + erlang:error(badarg). + +nif_if_names() -> + erlang:error(badarg). -- cgit v1.2.3 From 6b2c750c53a288d999c0f63dc6fe26a22d63ed00 Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Tue, 29 May 2018 10:54:55 +0200 Subject: [net-nif] Added preliminary net nif file OTP-14831 --- erts/emulator/Makefile.in | 3 + erts/emulator/nifs/common/net_nif.c | 1788 +++++++++++++++++++++++++++++++++++ erts/preloaded/src/net.erl | 55 +- 3 files changed, 1794 insertions(+), 52 deletions(-) create mode 100644 erts/emulator/nifs/common/net_nif.c diff --git a/erts/emulator/Makefile.in b/erts/emulator/Makefile.in index 6ef36ed3e4..3bfe393a12 100644 --- a/erts/emulator/Makefile.in +++ b/erts/emulator/Makefile.in @@ -1,3 +1,4 @@ + # # %CopyrightBegin% # @@ -636,6 +637,7 @@ GENERATE += $(TTF_DIR)/driver_tab.c PRELOAD_BEAM = $(ERL_TOP)/erts/preloaded/ebin/otp_ring0.beam \ $(ERL_TOP)/erts/preloaded/ebin/erts_code_purger.beam \ $(ERL_TOP)/erts/preloaded/ebin/init.beam \ + $(ERL_TOP)/erts/preloaded/ebin/net.beam \ $(ERL_TOP)/erts/preloaded/ebin/prim_buffer.beam \ $(ERL_TOP)/erts/preloaded/ebin/prim_eval.beam \ $(ERL_TOP)/erts/preloaded/ebin/prim_inet.beam \ @@ -883,6 +885,7 @@ NIF_OBJS = \ $(OBJDIR)/prim_buffer_nif.o \ $(OBJDIR)/prim_file_nif.o \ $(OBJDIR)/socket_nif.o \ + $(OBJDIR)/net_nif.o \ $(OBJDIR)/zlib_nif.o ifeq ($(TARGET),win32) diff --git a/erts/emulator/nifs/common/net_nif.c b/erts/emulator/nifs/common/net_nif.c new file mode 100644 index 0000000000..b8fa6628a7 --- /dev/null +++ b/erts/emulator/nifs/common/net_nif.c @@ -0,0 +1,1788 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 2018-2018. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * %CopyrightEnd% + * + * ---------------------------------------------------------------------- + * Purpose : The NIF (C) part of the net interface + * This is a module of miscellaneous functions. + * ---------------------------------------------------------------------- + * + */ + +/* #include */ +/* #include */ +/* #include */ +/* #include */ +/* #include */ +/* #include */ +/* #include */ +/* #include */ +/* #include */ +/* #include */ +/* #include */ +/* #include */ +/* #include */ +/* #include */ + + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +/* If we HAVE_SCTP_H and Solaris, we need to define the following in + * order to get SCTP working: + */ +#if (defined(HAVE_SCTP_H) && defined(__sun) && defined(__SVR4)) +#define SOLARIS10 1 +/* WARNING: This is not quite correct, it may also be Solaris 11! */ +#define _XPG4_2 +#define __EXTENSIONS__ +#endif + +#include +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_UNISTD_H +#include +#endif +#ifdef HAVE_SYS_UIO_H +#include +#endif + +#ifdef HAVE_NET_IF_DL_H +#include +#endif + +#ifdef HAVE_IFADDRS_H +#include +#endif + +#ifdef HAVE_NETPACKET_PACKET_H +#include +#endif + +#ifdef HAVE_SYS_UN_H +#include +#endif + +/* SENDFILE STUFF HERE IF WE NEED IT... */ + +#if defined(__APPLE__) && defined(__MACH__) && !defined(__DARWIN__) +#define __DARWIN__ 1 +#endif + + +#ifdef __WIN32__ +#define STRNCASECMP strncasecmp +#define INCL_WINSOCK_API_TYPEDEFS 1 + +#ifndef WINDOWS_H_INCLUDES_WINSOCK2_H +#include +#endif +#include +#include /* NEED VC 6.0 or higher */ + +/* Visual studio 2008+: NTDDI_VERSION needs to be set for iphlpapi.h + * to define the right structures. It needs to be set to WINXP (or LONGHORN) + * for IPV6 to work and it's set lower by default, so we need to change it. + */ +#ifdef HAVE_SDKDDKVER_H +# include +# ifdef NTDDI_VERSION +# undef NTDDI_VERSION +# endif +# define NTDDI_VERSION NTDDI_WINXP +#endif +#include + +#undef WANT_NONBLOCKING +#include "sys.h" + +#else /* !__WIN32__ */ + +#include +#ifdef NETDB_H_NEEDS_IN_H +#include +#endif +#include + +#include +#include + +#ifdef DEF_INADDR_LOOPBACK_IN_RPC_TYPES_H +#include +#endif + +#include +#include +#include + +#include +#ifdef HAVE_ARPA_NAMESER_H +#include +#endif + +#ifdef HAVE_SYS_SOCKIO_H +#include +#endif + +#ifdef HAVE_SYS_IOCTL_H +#include +#endif + +#include + +#ifdef HAVE_SCHED_H +#include +#endif + +#ifdef HAVE_SETNS_H +#include +#endif + +#define HAVE_UDP + +#ifndef WANT_NONBLOCKING +#define WANT_NONBLOCKING +#endif +#include "sys.h" + +#endif + +#include + + +/* All platforms fail on malloc errors. */ +#define FATAL_MALLOC + + + +/* *** Boolean *type* stuff... *** */ +typedef unsigned int BOOLEAN_T; +#define TRUE 1 +#define FALSE 0 +#define BOOL2STR(__B__) ((__B__) ? "true" : "false") +#define BOOL2ATOM(__B__) ((__B__) ? atom_true : atom_false) + +/* Two byte integer decoding */ +#define get_int16(s) ((((unsigned char*) (s))[0] << 8) | \ + (((unsigned char*) (s))[1])) + +#define SASSERT(e) \ + ((void) ((e) ? 1 : (xabort(#e, __func__, __FILE__, __LINE__), 0))) + + +/* Debug stuff... */ +#define SOCKET_NIF_DEBUG_DEFAULT TRUE + +/* Various defaults... */ +#define SOCKET_DEBUG_DEFAULT TRUE +#define SOCKET_IOW_DEFAULT FALSE + +/* Counters and stuff (Don't know where to sent this stuff anyway) */ +#define SOCKET_NIF_IOW_DEFAULT FALSE + + +/* Used in debug printouts */ +#ifdef __WIN32__ +#define LLU "%I64u" +#else +#define LLU "%llu" +#endif +typedef unsigned long long llu_t; + + + +/* Socket stuff */ +// #define INVALID_SOCKET -1 +// #define INVALID_EVENT -1 +// #define SOCKET_ERROR -1 + +// #define SOCKET int +// #define HANDLE long int + + +/* *** Misc macros and defines *** */ + +#ifdef __WIN32__ +#define get_errno() WSAGetLastError() +#else +#define get_errno() errno +#endif + +// #if defined(TCP_CA_NAME_MAX) +// #define SOCKET_OPT_TCP_CONGESTION_NAME_MAX TCP_CA_NAME_MAX +// #else +/* This is really excessive, but just in case... */ +// #define SOCKET_OPT_TCP_CONGESTION_NAME_MAX 256 +// #endif + +#define HOSTNAME_LEN 256 +#define SERVICE_LEN 256 + + +/* =================================================================== * + * * + * Various enif macros * + * * + * =================================================================== */ + +#define MALLOC(SZ) enif_alloc((SZ)) +#define FREE(P) enif_free((P)) + +#define MKA(E,S) enif_make_atom((E), (S)) +#define MKBIN(E,B) enif_make_binary((E), (B)) +#define MKI(E,I) enif_make_int((E), (I)) +#define MKLA(E,A,L) enif_make_list_from_array((E), (A), (L)) +#define MKEL(E) enif_make_list((E), 0) +#define MKREF(E) enif_make_ref((E)) +#define MKS(E,S) enif_make_string((E), (S), ERL_NIF_LATIN1) +#define MKSL(E,S,L) enif_make_string_len((E), (S), (L), ERL_NIF_LATIN1) +#define MKSBIN(E,B,ST,SZ) enif_make_sub_binary((E), (B), (ST), (SZ)) +#define MKT2(E,E1,E2) enif_make_tuple2((E), (E1), (E2)) +#define MKT3(E,E1,E2,E3) enif_make_tuple3((E), (E1), (E2), (E3)) +#define MKT4(E,E1,E2,E3,E4) enif_make_tuple4((E), (E1), (E2), (E3), (E4)) +#define MKT8(E,E1,E2,E3,E4,E5,E6,E7,E8) \ + enif_make_tuple8((E), (E1), (E2), (E3), (E4), (E5), (E6), (E7), (E8)) + +#define MCREATE(N) enif_mutex_create((N)) +#define MDESTROY(M) enif_mutex_destroy((M)) +#define MLOCK(M) enif_mutex_lock((M)) +#define MUNLOCK(M) enif_mutex_unlock((M)) + +#define MONP(E,D,P,M) enif_monitor_process((E), (D), (P), (M)) +#define DEMONP(E,D,M) enif_demonitor_process((E), (D), (M)) + +#define SELECT(E,FD,M,O,P,R) \ + enif_select((E), (FD), (M), (O), (P), (R)) +#define SELECT_READ(E, DP, P, R) \ + SELECT((E), (DP)->sock, (ERL_NIF_SELECT_READ), (DP), (P), (R)) +#define SELECT_WRITE(E, DP, P, R) \ + SELECT((E), (DP)->sock, (ERL_NIF_SELECT_WRITE), (DP), (P), (R)) +#define SELECT_STOP(E, DP) \ + enif_select((E), (DP)->sock, (ERL_NIF_SELECT_STOP), (DP), NULL, atom_undefined) + +#define IS_ATOM(E, TE) enif_is_atom((E), (TE)) +#define IS_BIN(E, TE) enif_is_binary((E), (TE)) +#define IS_NUM(E, TE) enif_is_number((E), (TE)) +#define IS_TUPLE(E, TE) enif_is_tuple((E), (TE)) +#define IS_LIST(E, TE) enif_is_list((E), (TE)) + +#define COMPARE(L, R) enif_compare((L), (R)) + +#define GET_ATOM_LEN(E, TE, LP) \ + enif_get_atom_length((E), (TE), (LP), ERL_NIF_LATIN1) +#define GET_ATOM(E, TE, BP, MAX) \ + enif_get_atom((E), (TE), (BP), (MAX), ERL_NIF_LATIN1) +#define GET_BIN(E, TE, BP) enif_inspect_iolist_as_binary((E), (TE), (BP)) +#define GET_INT(E, TE, IP) enif_get_int((E), (TE), (IP)) +#define GET_LIST_ELEM(E, L, HP, TP) enif_get_list_cell((E), (L), (HP), (TP)) +#define GET_LIST_LEN(E, L, LP) enif_get_list_length((E), (L), (LP)) +#define GET_STR(E, L, B, SZ) \ + enif_get_string((E), (L), (B), (SZ), ERL_NIF_LATIN1) +#define GET_UINT(E, TE, IP) enif_get_uint((E), (TE), (IP)) +#define GET_TUPLE(E, TE, TSZ, TA) enif_get_tuple((E), (TE), (TSZ), (TA)) + +#define ALLOC_BIN(SZ, BP) enif_alloc_binary((SZ), (BP)) +#define REALLOC_BIN(SZ, BP) enif_realloc_binary((SZ), (BP)) + + +#ifdef HAVE_SOCKLEN_T +# define SOCKLEN_T socklen_t +#else +# define SOCKLEN_T size_t +#endif + + +/* The general purpose socket address */ +typedef union { + struct sockaddr sa; + + struct sockaddr_in in; + +#ifdef HAVE_IN6 + struct sockaddr_in6 in6; +#endif + +} SockAddress; + + + +/* ---------------------------------------------------------------------- + * F o r w a r d s + * ---------------------------------------------------------------------- + */ + +/* THIS IS JUST TEMPORARY */ +extern char* erl_errno_id(int error); + + +static ERL_NIF_TERM nif_is_loaded(ErlNifEnv* env, + int argc, + const ERL_NIF_TERM argv[]); +static ERL_NIF_TERM nif_info(ErlNifEnv* env, + int argc, + const ERL_NIF_TERM argv[]); + +static ERL_NIF_TERM nif_getnameinfo(ErlNifEnv* env, + int argc, + const ERL_NIF_TERM argv[]); + +static ERL_NIF_TERM nif_getaddrinfo(ErlNifEnv* env, + int argc, + const ERL_NIF_TERM argv[]); + +static ERL_NIF_TERM nif_if_name2index(ErlNifEnv* env, + int argc, + const ERL_NIF_TERM argv[]); +static ERL_NIF_TERM nif_if_index2name(ErlNifEnv* env, + int argc, + const ERL_NIF_TERM argv[]); +static ERL_NIF_TERM nif_if_names(ErlNifEnv* env, + int argc, + const ERL_NIF_TERM argv[]); + +static ERL_NIF_TERM ngetnameinfo(ErlNifEnv* env, + const SockAddress* saP, + socklen_t saLen, + int flags); +static ERL_NIF_TERM nif_name2index(ErlNifEnv* env, + char* ifn); +static ERL_NIF_TERM nif_index2name(ErlNifEnv* env, + unsigned int id); +static ERL_NIF_TERM nif_names(ErlNifEnv* env); +static unsigned int nif_names_length(struct if_nameindex* p); + +/* +static void net_dtor(ErlNifEnv* env, void* obj); +static void net_stop(ErlNifEnv* env, + void* obj, + int fd, + int is_direct_call); +static void net_down(ErlNifEnv* env, + void* obj, + const ErlNifPid* pid, + const ErlNifMonitor* mon); +*/ +static BOOLEAN_T decode_in_sockaddr(ErlNifEnv* env, + const ERL_NIF_TERM eAddr, + SockAddress* saP, + socklen_t* saLen); +static BOOLEAN_T decode_in4_sockaddr(ErlNifEnv* env, + const ERL_NIF_TERM* addrt, + SockAddress* saP, + socklen_t* saLen); +#if defined(HAVE_IN6) && defined(AF_INET6) +static BOOLEAN_T decode_in6_sockaddr(ErlNifEnv* env, + const ERL_NIF_TERM* addrt, + SockAddress* saP, + socklen_t* saLen); +#endif +static BOOLEAN_T decode_nameinfo_flags(ErlNifEnv* env, + const ERL_NIF_TERM eflags, + int* flags); +static BOOLEAN_T decode_nameinfo_flags_list(ErlNifEnv* env, + const ERL_NIF_TERM eflags, + int* flags); +static ERL_NIF_TERM encode_address_info(ErlNifEnv* env, + struct addrinfo* addrInfo); +static unsigned int address_info_length(struct addrinfo* addrInfoP); + +static ERL_NIF_TERM make_ok2(ErlNifEnv* env, ERL_NIF_TERM val); +// static ERL_NIF_TERM make_ok3(ErlNifEnv* env, ERL_NIF_TERM val1, ERL_NIF_TERM val2); +static ERL_NIF_TERM make_error(ErlNifEnv* env, ERL_NIF_TERM reason); +static ERL_NIF_TERM make_error1(ErlNifEnv* env, char* reason); +static ERL_NIF_TERM make_error2(ErlNifEnv* env, int err); + +/* +static void xabort(const char* expr, + const char* func, + const char* file, + int line); +*/ + +static int on_load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info); + + +#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 */ + + + +/* *** String constants *** */ +static char str_dgram[] = "dgram"; +static char str_error[] = "error"; +static char str_idn[] = "idn"; +static char str_idna_allow_unassigned[] = "idna_allow_unassigned"; +static char str_idna_use_std3_ascii_rules[] = "idna_use_std3_ascii_rules"; +static char str_namereqd[] = "namereqd"; +static char str_name_info[] = "name_info"; +static char str_nofqdn[] = "nofqdn"; +static char str_numerichost[] = "numerichost"; +static char str_numericserv[] = "numericserv"; +static char str_ok[] = "ok"; +static char str_true[] = "true"; +static char str_undefined[] = "undefined"; + +// static char str_lowdelay[] = "lowdelay"; +// static char str_throughput[] = "throughput"; +// static char str_reliability[] = "reliability"; +// static char str_mincost[] = "mincost"; + +/* (special) error string constants */ +// static char str_eafnosupport[] = "eafnosupport"; +static char str_eagain[] = "eagain"; +static char str_ebadflags[] = "ebadflags"; +static char str_efail[] = "efail"; +static char str_efamily[] = "efamily"; +static char str_einval[] = "einval"; +// static char str_eisconn[] = "eisconn"; +static char str_emem[] = "emem"; +static char str_enoname[] = "enoname"; +// static char str_enotclosing[] = "enotclosing"; +// static char str_enotconn[] = "enotconn"; +static char str_eoverflow[] = "eoverflow"; +// static char str_exalloc[] = "exalloc"; +// static char str_exbadstate[] = "exbadstate"; +// static char str_exbusy[] = "exbusy"; +// static char str_exmon[] = "exmonitor"; // failed monitor +// static char str_exself[] = "exself"; // failed self +// static char str_exsend[] = "exsend"; // failed send + + +/* *** Atoms *** */ + +static ERL_NIF_TERM atom_error; +static ERL_NIF_TERM atom_dgram; +static ERL_NIF_TERM atom_idn; +static ERL_NIF_TERM atom_idna_allow_unassigned; +static ERL_NIF_TERM atom_idna_use_std3_ascii_rules; +static ERL_NIF_TERM atom_namereqd; +static ERL_NIF_TERM atom_name_info; +static ERL_NIF_TERM atom_nofqdn; +static ERL_NIF_TERM atom_numerichost; +static ERL_NIF_TERM atom_numericserv; +static ERL_NIF_TERM atom_ok; +// static ERL_NIF_TERM atom_select; +// static ERL_NIF_TERM atom_timeout; +static ERL_NIF_TERM atom_true; +static ERL_NIF_TERM atom_undefined; + +// static ERL_NIF_TERM atom_lowdelay; +// static ERL_NIF_TERM atom_throughput; +// static ERL_NIF_TERM atom_reliability; +// static ERL_NIF_TERM atom_mincost; + +// static ERL_NIF_TERM atom_eafnosupport; +static ERL_NIF_TERM atom_eagain; +static ERL_NIF_TERM atom_ebadflags; +static ERL_NIF_TERM atom_efail; +static ERL_NIF_TERM atom_efamily; +static ERL_NIF_TERM atom_einval; +// static ERL_NIF_TERM atom_eisconn; +static ERL_NIF_TERM atom_emem; +static ERL_NIF_TERM atom_enoname; +// static ERL_NIF_TERM atom_enotclosing; +// static ERL_NIF_TERM atom_enotconn; +static ERL_NIF_TERM atom_eoverflow; +// static ERL_NIF_TERM atom_exalloc; +// static ERL_NIF_TERM atom_exbadstate; +// static ERL_NIF_TERM atom_exbusy; +// static ERL_NIF_TERM atom_exmon; +// static ERL_NIF_TERM atom_exself; +// static ERL_NIF_TERM atom_exsend; + +/* *** net *** */ +static ErlNifResourceType* net; +/* Maybe all of these whould be NULL? */ +static ErlNifResourceTypeInit netInit = { + NULL, // net_dtor, + NULL, // net_stop, + NULL // (ErlNifResourceDown*) net_down +}; + + + +/* ---------------------------------------------------------------------- + * N I F F u n c t i o n s + * ---------------------------------------------------------------------- + * + * Utility and admin functions: + * ---------------------------- + * nif_is_loaded/0 + * nif_info/0 + * + * The "proper" net functions: + * ------------------------------ + * nif_getnameinfo/2 + * nif_getaddrinfo/3 + * nif_if_name2index/1 + * nif_if_index2name/1 + * nif_if_names/0 + * + */ + + +/* ---------------------------------------------------------------------- + * nif_is_loaded + * + * Description: + * This functions only purpose is to return the atom 'true'. + * This will happen *if* the (socket) nif library is loaded. + * If its not, the erlang (nif_is_loaded) will instead return + * 'false'. + */ +static +ERL_NIF_TERM nif_is_loaded(ErlNifEnv* env, + int argc, + const ERL_NIF_TERM argv[]) +{ + if (argc != 0) + return enif_make_badarg(env); + + return atom_true; +} + + +/* ---------------------------------------------------------------------- + * nif_info + * + * Description: + * This is currently just a placeholder... + */ +static +ERL_NIF_TERM nif_info(ErlNifEnv* env, + int argc, + const ERL_NIF_TERM argv[]) +{ + ERL_NIF_TERM info = enif_make_new_map(env); + return info; +} + + + +/* ---------------------------------------------------------------------- + * nif_getnameinfo + * + * Description: + * Address-to-name translation in protocol-independent manner. + * + * Arguments: + * SockAddr - Socket Address (address and port) + * Flags - The flags argument modifies the behavior of getnameinfo(). + * Not used! + */ + +static +ERL_NIF_TERM nif_getnameinfo(ErlNifEnv* env, + int argc, + const ERL_NIF_TERM argv[]) +{ + ERL_NIF_TERM eSockAddr; + unsigned int eFlags; + int flags = 0; // Just in case... + SockAddress sa; + socklen_t saLen = 0; // Just in case... + + if ((argc != 2) || + !GET_UINT(env, argv[1], &eFlags)) { + return enif_make_badarg(env); + } + eSockAddr = argv[0]; + + if (!decode_nameinfo_flags(env, eFlags, &flags)) + return enif_make_badarg(env); + + if (decode_in_sockaddr(env, eSockAddr, &sa, &saLen)) + return enif_make_badarg(env); + + return ngetnameinfo(env, &sa, saLen, flags); +} + + + +/* Given the provided sock(et) address (and honts), retreive the host and + * service info. + */ +static +ERL_NIF_TERM ngetnameinfo(ErlNifEnv* env, + const SockAddress* saP, + socklen_t saLen, + int flags) +{ + ERL_NIF_TERM result; + char host[HOSTNAME_LEN]; + socklen_t hostLen = sizeof(host); + char serv[SERVICE_LEN]; + socklen_t servLen = sizeof(serv); + + int res = getnameinfo((struct sockaddr*) saP, saLen, + host, hostLen, + serv, servLen, + flags); + + switch (res) { + case 0: + { + ERL_NIF_TERM info = MKT3(env, + atom_name_info, + MKS(env, host), + MKS(env, serv)); + result = make_ok2(env, info); + } + break; + + case EAI_AGAIN: + result = make_error(env, atom_eagain); + break; + + case EAI_BADFLAGS: + result = make_error(env, atom_ebadflags); + break; + + case EAI_FAIL: + result = make_error(env, atom_efail); + break; + + case EAI_FAMILY: + result = make_error(env, atom_efamily); + break; + + case EAI_MEMORY: + result = make_error(env, atom_emem); + break; + + case EAI_NONAME: + result = make_error(env, atom_enoname); + break; + + case EAI_OVERFLOW: + result = make_error(env, atom_eoverflow); + break; + + case EAI_SYSTEM: + result = make_error2(env, get_errno()); + break; + + default: + result = make_error(env, atom_einval); + break; + } + + return result; +} + + + +/* ---------------------------------------------------------------------- + * nif_getaddrinfo + * + * Description: + * Network address and service translation. + * + * Arguments: + * Host - Host name (either a string or the atom undefined) + * Service - Service name (either a string or the atom undefined) + * Hints - Hints for the lookup (address info record) (currently *ignored*) + */ + +static +ERL_NIF_TERM nif_getaddrinfo(ErlNifEnv* env, + int argc, + const ERL_NIF_TERM argv[]) +{ + ERL_NIF_TERM result; + char* hostName; + char* servName; + // struct addrinfo* hints; + + if (argc != 3) { + return enif_make_badarg(env); + } + eHostName = argv[0]; + eServName = argv[1]; + eHints = argv[2]; + + if (!decode_addrinfo_string(env, eHostName, &hostName)) + return enif_make_badarg(env); + + if (!decode_addrinfo_string(env, eServName, &servName)) + return enif_make_badarg(env); + + /* + if (decode_addrinfo_hints(env, eHints, &hints)) + return enif_make_badarg(env); + */ + + if ((hostName == NULL) && (servName == NULL)) + return enif_make_badarg(env); + + result = ngetaddrinfo(env, hostName, servName); + + if (hostName != NULL) + FREE(hostName); + + if (servName != NULL) + FREE(servName); + + /* + if (hints != NULL) + FREE(hints); + */ + + return result; +} + + +static +ERL_NIF_TERM nifgetaddrinfo(ErlNifEnv* env, + char* host, + char* serv) +{ + struct addrinfo* addrInfoP; + int res; + + res = getaddrinfo(host, serv, NULL, &addrInfoP); + + switch (res) { + case 0: + { + ERL_NIF_TERM addrInfo = encode_address_info(env, addrInfoP); + freeaddrinfo(addrInfoP); + result = make_ok2(env, addrInfo); + } + break; + + case EAI_ADDRFAMILY: + result = make_error(env, atom_eaddrfamily); + break; + + case EAI_AGAIN: + result = make_error(env, atom_eagain); + break; + + case EAI_BADFLAGS: + result = make_error(env, atom_ebadflags); + break; + + case EAI_FAIL: + result = make_error(env, atom_efail); + break; + + case EAI_FAMILY: + result = make_error(env, atom_efamily); + break; + + case EAI_MEMORY: + result = make_error(env, atom_emem); + break; + + case EAI_NODATA: + result = make_error(env, atom_enodata); + break; + + case EAI_NONAME: + result = make_error(env, atom_enoname); + break; + + case EAI_SERCVICE: + result = make_error(env, atom_eservice); + break; + + case EAI_SOCKTYPE: + result = make_error(env, atom_esocktype); + break; + + case EAI_SYSTEM: + result = make_error(env, atom_esystem); + break; + + default: + result = make_error(env, atom_einval); + break; + } + + return result; +} + + +/* ---------------------------------------------------------------------- + * nif_if_name2index + * + * Description: + * Perform a Interface Name to Interface Index translation. + * + * Arguments: + * Ifn - Interface name to be translated. + */ + +static +ERL_NIF_TERM nif_if_name2index(ErlNifEnv* env, + int argc, + const ERL_NIF_TERM argv[]) +{ + ERL_NIF_TERM eifn; + char ifn[IF_NAMESIZE+1]; + + if (argc != 1) { + return enif_make_badarg(env); + } + eifn = argv[0]; + + if (0 >= GET_STR(env, eifn, ifn, sizeof(ifn))) + return make_error2(env, atom_einval); + + return nif_name2index(env, ifn); +} + + + +static +ERL_NIF_TERM nif_name2index(ErlNifEnv* env, + char* ifn) +{ + unsigned int idx = if_nametoindex(ifn); + + if (idx == 0) + return make_error2(env, get_errno()); + else + return make_ok2(env, idx); + +} + + + +/* ---------------------------------------------------------------------- + * nif_if_index2name + * + * Description: + * Perform a Interface Index to Interface Name translation. + * + * Arguments: + * Idx - Interface index to be translated. + */ + +static +ERL_NIF_TERM nif_if_index2name(ErlNifEnv* env, + int argc, + const ERL_NIF_TERM argv[]) +{ + unsigned int idx; + + if ((argc != 1) || + !GET_UINT(env, argv[0], &idx)) { + return enif_make_badarg(env); + } + + return nif_index2name(env, idx); +} + + + +static +ERL_NIF_TERM nif_index2name(ErlNifEnv* env, + unsigned int idx) +{ + ERL_NIF_TERM result; + char* ifn = MALLOC(IF_NAMESIZE+1); + + if (ifn == NULL) + return enif_make_badarg(env); // PLACEHOLDER + + if (NULL == if_indextoname(idx, ifn)) { + result = make_ok2(env, MKS(env, ifn)); + } else { + result = make_error2(env, get_errno()); + } + + FREE(ifn); + + return result; +} + + + +/* ---------------------------------------------------------------------- + * nif_if_names + * + * Description: + * Get network interface names and indexes. + * + */ + +static +ERL_NIF_TERM nif_if_names(ErlNifEnv* env, + int argc, + const ERL_NIF_TERM argv[]) +{ + if (argc != 0) { + return enif_make_badarg(env); + } + + return nif_names(env); +} + + + +static +ERL_NIF_TERM nif_names(ErlNifEnv* env) +{ + ERL_NIF_TERM result; + struct if_nameindex* ifs = if_nameindex(); + + if (ifs == NULL) { + result = make_error2(env, get_errno()); + } else { + /* + * We got some interfaces: + * 1) Calculate how many - the only way is to iterate through the list + * until its end (which is indicated by an entry with index = zero + * and if_name = NULL). + * 2) Allocate an ERL_NIF_TERM array of the calculated length. + * 3) Iterate through the array of interfaces and for each create + * a two tuple: {Idx, If} + * + * Or shall we instead build a list in reverse order and then when + * its done, reverse that? Check + */ + unsigned int len = nif_names_length(ifs); + + if (len > 0) { + ERL_NIF_TERM* array = MALLOC(len * sizeof(ERL_NIF_TERM)); + unsigned int i; + + for (i = 0; i < len; i++) { + array[i] = MKT2(env, + MKI(env, ifs[i].if_index), + MKS(env, ifs[i].if_name)); + } + + result = make_ok2(env, MKLA(env, array, len)); + FREE(array); + } else { + result = make_ok2(env, enif_make_list(env, 0)); + } + } + + if (ifs != NULL) + if_freenameindex(ifs); + + return result; +} + + +static +unsigned int nif_names_length(struct if_nameindex* p) +{ + unsigned int len = 0; + + while ((p[len].if_index == 0) && (p[len].if_name == NULL)) { + len++; + } + + return len; +} + + + +/* ---------------------------------------------------------------------- + * U t i l i t y F u n c t i o n s + * ---------------------------------------------------------------------- + */ + +/* Decode an in_sockaddr (the socket address). + * This is the (erlang) type: in_sockaddr(), which is either + * a in4_sockaddr() (tuple of size 3) or a in6_sockaddr() + * (tuple of size 5). See the net erlang module for details. + * + * We first detect which which of the tuples it is: + * - Size 3: maybe in4_sockaddr + * - Size 5: maybe in6_sockaddr + */ +static +BOOLEAN_T decode_in_sockaddr(ErlNifEnv* env, + const ERL_NIF_TERM eAddr, + SockAddress* saP, + socklen_t* saLen) +{ + const ERL_NIF_TERM* addrt; + int addrtSz; + + if (!GET_TUPLE(env, eAddr, &addrtSz, &addrt)) + return FALSE; + + switch (addrtSz) { + case 3: + return decode_in4_sockaddr(env, addrt, saP, saLen); + break; + +#ifdef HAVE_IN6 + case 5: + return decode_in6_sockaddr(env, addrt, saP, saLen); + break; +#endif + + default: + return FALSE; + break; + } + +} + + +/* Decode an in4_sockaddr record. This is a tuple of size 3. + * The size has already been verified, but not its content. + * So, the first element should be the atom 'in4_sockaddr'. + * The second the port number, an integer. And the third, + * the actual ip address, a 4-tuple. + */ +static +BOOLEAN_T decode_in4_sockaddr(ErlNifEnv* env, + const ERL_NIF_TERM* addrt, + SockAddress* saP, + socklen_t* saLen) +{ + unsigned int len; + char tag[16]; // Just in case... + int port; + int ipAddrSz; + const ERL_NIF_TERM* ipAddrT; + int a, v; + char addr[4]; + + /* The first element: Verify record tag (atom()): in4_sockaddr */ + + if (!(GET_ATOM_LEN(env, addrt[0], &len) && + (len > 0) && + (len <= (sizeof(tag))))) + return FALSE; + + if (!GET_ATOM(env, addrt[0], tag, sizeof(tag))) + return FALSE; + + if (strncmp(tag, "in4_sockaddr", len) != 0) + return FALSE; + + + /* Get second element: port number (integer) */ + + if (!GET_INT(env, addrt[1], &port)) + return FALSE; + + + /* And finally, get the third element, the ip address (a 4 tuple) */ + + if (!GET_TUPLE(env, addrt[2], &ipAddrSz, &ipAddrT)) + return FALSE; + + if (ipAddrSz != 4) + return FALSE; + + + /* And finally initialize the sockaddr structure (and size) */ + sys_memzero((char*)saP, sizeof(struct sockaddr_in)); + saP->in.sin_family = AF_INET; + saP->in.sin_port = htons(port); + for (a = 0; a < 4; a++) { + if (!GET_INT(env, ipAddrT[a], &v)) + return FALSE; + addr[a] = v; + } + sys_memcpy(&saP->in.sin_addr, &addr, sizeof(addr)); + *saLen = sizeof(struct sockaddr_in); + return TRUE; +} + + + +#if defined(HAVE_IN6) && defined(AF_INET6) +/* Decode an in6_sockaddr record. This is a tuple of size 5. + * The size has already been verified, but not its content. + * So, the first element should be the atom 'in6_sockaddr'. + * The second the port number, an integer. The third, the + * actual ip address, a 8-tuple. The forth, the flowinfo, + * an integer. And finally, the fifth element, the scope_id, + * also an integer. *Not used here*. + */ +static +BOOLEAN_T decode_in6_sockaddr(ErlNifEnv* env, + const ERL_NIF_TERM* addrt, + SockAddress* saP, + socklen_t* saLen) +{ + unsigned int len; + char tag[16]; // Just in case... + int port; + int ipAddrSz; + const ERL_NIF_TERM* ipAddrT; + int flowInfo; + int a, v; + char addr[16]; + + + /* The first element: Verify record tag (atom()): in6_sockaddr */ + + if (!(GET_ATOM_LEN(env, addrt[0], &len) && + (len > 0) && + (len <= (sizeof(tag))))) + return FALSE; + + if (!GET_ATOM(env, addrt[0], tag, sizeof(tag))) + return FALSE; + + if (strncmp(tag, "in6_sockaddr", len) != 0) + return FALSE; + + + /* Get second element: port number (integer) */ + + if (!GET_INT(env, addrt[1], &port)) + return FALSE; + + + /* Get the third element, the ip address (a 8 tuple) */ + + if (!GET_TUPLE(env, addrt[2], &ipAddrSz, &ipAddrT)) + return FALSE; + + if (ipAddrSz != 8) + return FALSE; + + + /* And finally, get the forth element, the flowinfo (integer) */ + + if (!GET_INT(env, addrt[3], &flowInfo)) + return FALSE; + + + /* And finally initialize the sockaddr structure (and size) */ + + sys_memzero((char*)saP, sizeof(struct sockaddr_in6)); + saP->in6.sin6_family = AF_INET6; + saP->in6.sin6_port = htons(port); + saP->in6.sin6_flowinfo = flowInfo; + /* The address tuple is of size 8 + * and each element is a two byte integer + */ + for (a = 0; a < 8; a++) { + if (!GET_INT(env, addrt[a], &v)) + return FALSE; + addr[a*2 ] = ((v >> 8) & 0xFF); + addr[a*2+1] = (v & 0xFF); + } + sys_memcpy(&saP->in6.sin6_addr, &addr, sizeof(addr)); + *saLen = sizeof(struct sockaddr_in6); + + return TRUE; +} +#endif + + + +/* The erlang format for a set of flags is a list of atoms. + * A special case is when there is no flags, which is + * represented by the atom undefined. + */ +static +BOOLEAN_T decode_nameinfo_flags(ErlNifEnv* env, + const ERL_NIF_TERM eflags, + int* flags) +{ + BOOLEAN_T result; + + if (IS_ATOM(env, eflags)) { + if (COMPARE(eflags, atom_undefined) == 0) { + *flags = 0; + result = TRUE; + } else { + result = FALSE; + } + } else if (IS_LIST(env, eflags)) { + result = decode_nameinfo_flags_list(env, eflags, flags); + } else { + result = FALSE; + } + + return result; +} + + + +static +BOOLEAN_T decode_nameinfo_flags_list(ErlNifEnv* env, + const ERL_NIF_TERM eflags, + int* flags) +{ + ERL_NIF_TERM elem, tail, list = eflags; + int tmp = 0; + BOOLEAN_T done = FALSE; + + while (!done) { + if (GET_LIST_ELEM(env, list, &elem, &tail)) { + if (COMPARE(elem, atom_namereqd) == 0) { + tmp |= NI_NAMEREQD; + } else if (COMPARE(elem, atom_dgram) == 0) { + tmp |= NI_DGRAM; + } else if (COMPARE(elem, atom_nofqdn) == 0) { + tmp |= NI_NOFQDN; + } else if (COMPARE(elem, atom_numerichost) == 0) { + tmp |= NI_NUMERICHOST; + } else if (COMPARE(elem, atom_numericserv) == 0) { + tmp |= NI_NUMERICSERV; + + /* Starting with glibc 2.3.4: */ + +#if defined(NI_IDN) + } else if (COMPARE(elem, atom_idn) == 0) { + tmp |= NI_IDN; +#endif + +#if defined(NI_IDN_ALLOW_UNASSIGNED) + } else if (COMPARE(elem, atom_idna_allow_unassigned) == 0) { + tmp |= NI_IDN_ALLOW_UNASSIGNED; +#endif + +#if defined(NI_IDN_USE_STD3_ASCII_RULES) + } else if (COMPARE(elem, atom_idna_use_std3_ascii_rules) == 0) { + tmp |= NI_IDN_USE_STD3_ASCII_RULES; +#endif + + } else { + return FALSE; + } + + list = tail; + + } else { + done = TRUE; + } + } + + *flags = tmp; + + return TRUE; +} + + + +/* Decode the address info string (hostname or service name) + * The string is either the atom undefined or an actual string. + */ +static +BOOLEAN_T decode_addrinfo_string(ErlNifEnv* env, + const ERL_NIF_TERM eString, + char** stringP) +{ + BOOLEAN_T result; + + if (IS_ATOM(env, eString)) { + if (COMPARE(eString, atom_undefined) == 0) { + *stringP = NULL; + result = TRUE; + } else { + *stringP = NULL; + result = FALSE; + } + } else { + unsigned int len; + char* bufP; + + if (!GET_LIST_LEN(env, eString, &len) && (len != 0)) { + *stringP = NULL; + result = FALSE; + } + + bufP = ALLOC(len); + + if (GET_STR(env, eString, bufP, len)) { + *stringP = bufP; + result = TRUE; + } else { + *stringP = NULL; + result = FALSE; + } + } + + return result; + +} + + + +/* Encode the address info + * The address info is a linked list och address info, which + * will result in the result being a list of zero or more length. + */ +static +ERL_NIF_TERM encode_address_info(ErlNifEnv* env, + struct addrinfo* addrInfo) +{ + ERL_NIF_TERM result; + unsigned int len = address_info_length(addrInfo); + + if (len > 0) { + ERL_NIF_TERM* array = MALLOC(len * sizeof(ERL_NIF_TERM)); + unsigned int i; + + for (i = 0; i < len; i++) { + array[i] = make_address_info(env, &addrInfo[i]); + } + + result = mkake_ok2(env, MKLA(env, array, len)); + } else { + result = MKEL(env); + } + + return result; +} + + + +/* Calculate the length of the adress info linked list + * The list is NULL-terminated, so the only way is to + * iterate through the list until we find next = NULL. + */ +static +unsigned int address_info_length(struct addrinfo* addrInfoP) +{ + unsigned int len; + struct addrinfo* tmp = addrInfoP; + + if (tmp != NULL) { + len = 1; + while (tmp->ai_next != NULL) { + tmp = tmp->ai_next; + len++; + } + } else { + len = 0; + } + + return len; +} + + + +/* Create one (erlang) instance of the address info record + * Should we have address info as a record or as a map? + * + * {address_info, Fam, Type, Proto, Addr} + */ +static +ERL_NIF_TERM make_adress_info(ErlNifEnv* env, + struct addrinfo* addrInfoP) +{ + ERL_NIF_TERM Fam = make_addrinfo_family(env, addrInfoP->ai_family); + ERL_NIF_TERM Type = make_addrinfo_type(env, addrInfoP->ai_socktype); + ERL_NIF_TERM Proto = make_addrinfo_proto(env, addrInfoP->ai_protocol); + ERL_NIF_TERM Addr = make_addrinfo_addr(env, + addrInfoP->ai_addr, + addrInfoP->ai_addrlen); + + return MKT5(env, atom_address_info, Fam, Type, Proto, Addr); + +} + + +/* Convert an "native" family to an erlang family + * Note that this is not currently exhaustive, but only supports + * inet and inet6. Other values will be returned as is, that is + * in the form of an integer. + */ +static +ERL_NIF_TERM make_addrinfo_family(ErlNifEnv* env, + int family) +{ + ERL_NIF_TERM efam; + + switch (family) { + case AF_INET: + efam = atom_inet; + break; + +#if defined(HAVE_IN6) && defined(AF_INET6) + case AF_INET6: + efam = atom_inet6; + break; +#endif + + default: + efam = MKI(env, family); + break; + } + + return efam; +} + + + +/* Convert an "native" socket type to an erlang socket type + * Note that this is not currently exhaustive, but only supports + * stream and dgram. Other values will be returned as is, that is + * in the form of an integer. + */ +static +ERL_NIF_TERM make_addrinfo_type(ErlNifEnv* env, + int socktype) +{ + ERL_NIF_TERM etype; + + switch (socktype) { + case SOCK_STREAM: + etype = atom_stream; + break; + + case SOCK_DGRAM: + etype = atom_dgram; + break; + + default: + etype = MKI(env, socktype); + break; + } + + return etype; +} + + + +/* Convert an "native" protocol to an erlang protocol + * Note that this is not currently exhaustive, but only supports + * tcp and udp. Other values will be returned as is, that is + * in the form of an integer. + */ +static +ERL_NIF_TERM make_addrinfo_proto(ErlNifEnv* env, + int proto) +{ + ERL_NIF_TERM eproto; + + switch (proto) { +#if defined(SOL_IP) + case SOL_IP: +#else + case IPPROTO_IP: +#endif + eproto = atom_ip; + break; + + case IPPROTO_TCP: + eproto = atom_tcp; + break; + + case IPPROTO_UDP: + eproto = atom_udp; + break; + + default: + eproto = MKI(env, proto); + break; + } + + return eproto; +} + + + +/* Convert an "native" address to an erlang address + * Note that this is not currently exhaustive, but only supports + * IPv4 and IPv6 addresses. Values of other families will be + * returned as an undefined. + */ +static +ERL_NIF_TERM make_addrinfo_addr(ErlNifEnv* env, + struct sockaddr* addrP, + socklen_t addrLen) +{ + ERL_NIF_TERM port, addr, eaddr; + SockAddress* p = (SockAddress*) addrP; + + switch (addrP->sa_family) { + case AF_INET: + port = ntohs(p->in.sin_port); + addr = MKT4(env, + MKI(env, p->in.sin_addr[0]), + MKI(env, p->in.sin_addr[1]), + MKI(env, p->in.sin_addr[2]), + MKI(env, p->in.sin_addr[3])); + eaddr = MKT2(env, port, addr); + break; + +#if defined(HAVE_IN6) && defined(AF_INET6) + case AF_INET6: + port = ntohs(p->in6.sin6_port); + addr = MKT8(env, + MKI(env, p->in6.sin_addr[0]), + MKI(env, p->in6.sin_addr[1]), + MKI(env, p->in6.sin_addr[2]), + MKI(env, p->in6.sin_addr[3]), + MKI(env, p->in6.sin_addr[3]), + MKI(env, p->in6.sin_addr[3]), + MKI(env, p->in6.sin_addr[3]), + MKI(env, p->in6.sin_addr[3])); + eaddr = MKT2(env, port, addr); + break; +#endif + + default: + eaddr = atom_undefined; + break; + } + + return eaddr; +} + + + +/* Create an ok two (2) tuple in the form: {ok, Any}. + * The second element (Any) is already in the form of an + * ERL_NIF_TERM so all we have to do is create the tuple. + */ +static +ERL_NIF_TERM make_ok2(ErlNifEnv* env, ERL_NIF_TERM any) +{ + return MKT2(env, atom_ok, any); +} + + +/* Create an ok three (3) tuple in the form: {ok, Val1, Val2}. + * The second (Val1) and third (Val2) elements are already in + * the form of an ERL_NIF_TERM so all we have to do is create + * the tuple. + */ +/* +static +ERL_NIF_TERM make_ok3(ErlNifEnv* env, ERL_NIF_TERM val1, ERL_NIF_TERM val2) +{ + return MKT3(env, atom_ok, val1, val2); +} +*/ + + +/* Create an error two (2) tuple in the form: {error, Reason}. + * The second element (Reason) is already in the form of an + * ERL_NIF_TERM so all we have to do is create the tuple. + */ +static +ERL_NIF_TERM make_error(ErlNifEnv* env, ERL_NIF_TERM reason) +{ + return MKT2(env, atom_error, reason); +} + + +/* Create an error two (2) tuple in the form: {error, Reason}. + * The second element, Reason, is a string to be converted into + * an atom. + */ +static +ERL_NIF_TERM make_error1(ErlNifEnv* env, char* reason) +{ + return make_error(env, MKA(env, reason)); +} + + +/* Create an error two (2) tuple in the form: {error, Reason}. + * The second element, Reason, is the errno value in its + * basic form (integer) which will (eventually) be converted + * into an atom. + */ +static +ERL_NIF_TERM make_error2(ErlNifEnv* env, int err) +{ + return make_error1(env, erl_errno_id(err)); +} + + +/* +static +void xabort(const char* expr, + const char* func, + const char* file, + int line) +{ + fflush(stdout); + fprintf(stderr, "%s:%d:%s() Assertion failed: %s\n", + file, line, func, expr); + fflush(stderr); + abort(); +} +*/ + + +/* ---------------------------------------------------------------------- + * C o u n t e r F u n c t i o n s + * ---------------------------------------------------------------------- + */ + +/* ---------------------------------------------------------------------- + * C a l l b a c k F u n c t i o n s + * ---------------------------------------------------------------------- + */ + +/* ========================================================================= + * net_dtor - Callback function for resource destructor + * + */ +/* +static +void net_dtor(ErlNifEnv* env, void* obj) +{ +} +*/ + + +/* ========================================================================= + * net_stop - Callback function for resource stop + * + */ +/* +static +void net_stop(ErlNifEnv* env, void* obj, int fd, int is_direct_call) +{ +} +*/ + + + + +/* ========================================================================= + * net_down - Callback function for resource down (monitored processes) + * + */ +/* +static +void net_down(ErlNifEnv* env, + void* obj, + const ErlNifPid* pid, + const ErlNifMonitor* mon) +{ +} +*/ + + + +/* ---------------------------------------------------------------------- + * L o a d / u n l o a d / u p g r a d e F u n c t i o n s + * ---------------------------------------------------------------------- + */ + +static +ErlNifFunc net_funcs[] = +{ + // Some utility functions + {"nif_is_loaded", 0, nif_is_loaded, 0}, + {"nif_info", 0, nif_info, 0}, + + /* address and name translation in protocol-independent manner */ + {"nif_getnameinfo", 2, nif_getnameinfo, 0}, + + /* Network interface (name and/or index) functions */ + {"nif_if_name2index", 1, nif_if_name2index, 0}, + {"nif_if_index2name", 1, nif_if_index2name, 0}, + {"nif_if_names", 0, nif_if_names, 0} +}; + + + +/* ======================================================================= + * load_info - A map of misc info (e.g global debug) + */ + +static +int on_load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info) +{ + /* +++ Misc atoms +++ */ + atom_dgram = MKA(env, str_dgram); + atom_error = MKA(env, str_error); + atom_idn = MKA(env, str_idn); + atom_idna_allow_unassigned = MKA(env, str_idna_allow_unassigned); + atom_idna_use_std3_ascii_rules = MKA(env, str_idna_use_std3_ascii_rules); + atom_namereqd = MKA(env, str_namereqd); + atom_name_info = MKA(env, str_name_info); + atom_nofqdn = MKA(env, str_nofqdn); + atom_numerichost = MKA(env, str_numerichost); + atom_numericserv = MKA(env, str_numericserv); + atom_ok = MKA(env, str_ok); + atom_true = MKA(env, str_true); + atom_undefined = MKA(env, str_undefined); + // atom_version = MKA(env, str_version); + + // atom_lowdelay = MKA(env, str_lowdelay); + // atom_throughput = MKA(env, str_throughput); + // atom_reliability = MKA(env, str_reliability); + // atom_mincost = MKA(env, str_mincost); + + /* Error codes */ + // atom_eafnosupport = MKA(env, str_eafnosupport); + atom_eagain = MKA(env, str_eagain); + atom_ebadflags = MKA(env, str_ebadflags); + atom_efail = MKA(env, str_efail); + atom_efamily = MKA(env, str_efamily); + atom_einval = MKA(env, str_einval); + // atom_eisconn = MKA(env, str_eisconn); + atom_emem = MKA(env, str_emem); + atom_enoname = MKA(env, str_enoname); + // atom_enotclosing = MKA(env, str_enotclosing); + // atom_enotconn = MKA(env, str_enotconn); + atom_eoverflow = MKA(env, str_eoverflow); + // atom_exalloc = MKA(env, str_exalloc); + // atom_exbadstate = MKA(env, str_exbadstate); + // atom_exbusy = MKA(env, str_exbusy); + // atom_exnotopen = MKA(env, str_exnotopen); + // atom_exmon = MKA(env, str_exmon); + // atom_exself = MKA(env, str_exself); + // atom_exsend = MKA(env, str_exsend); + + // For storing "global" things... + // socketData.env = enif_alloc_env(); // We should really check + // socketData.version = MKA(env, ERTS_VERSION); + // socketData.buildDate = MKA(env, ERTS_BUILD_DATE); + + net = enif_open_resource_type_x(env, + "net", + &netInit, + ERL_NIF_RT_CREATE, + NULL); + + return !net; +} + +ERL_NIF_INIT(net, net_funcs, on_load, NULL, NULL, NULL) diff --git a/erts/preloaded/src/net.erl b/erts/preloaded/src/net.erl index 39c907eca3..bdd81ea93a 100644 --- a/erts/preloaded/src/net.erl +++ b/erts/preloaded/src/net.erl @@ -55,8 +55,6 @@ network_interface_index/0 ]). --record(name_info, {flags, host, service}). --record(address_info, {flags, family, socket_type, protocol, addr}). %% Many of these should be moved to the socket module. -type ip_address() :: ip4_address() | ip6_address(). @@ -96,21 +94,14 @@ -type name_info_flag_ext() :: idn | idna_allow_unassigned | idna_use_std3_ascii_rules. +-record(name_info, {host, service}). -type name_info() :: #name_info{}. +-record(address_info, {family, socktype, protocol, addr}). -type address_info() :: #address_info{}. -type network_interface_name() :: string(). -type network_interface_index() :: non_neg_integer(). --define(NET_NAME_INFO_NAMEREQD, 0). --define(NET_NAME_INFO_DGRAM, 1). --define(NET_NAME_INFO_NOFQDN, 2). --define(NET_NAME_INFO_NUMERICHOST, 3). --define(NET_NAME_INFO_NUMERICSERV, 4). --define(NET_NAME_INFO_IDN, 5). --define(NET_NAME_INFO_IDNA_ALLOW_UNASSIGNED, 6). --define(NET_NAME_INFO_IDNA_USE_STD3_ASCII_RULES, 7). - %% =========================================================================== %% @@ -183,47 +174,7 @@ getnameinfo(SockAddr, Flags) when (is_record(SockAddr, in4_sockaddr) orelse is_record(SockAddr, in6_sockaddr)) andalso is_list(Flags) -> - try - begin - EFlags = enc_name_info_flags(Flags), - nif_getnameinfo(SockAddr, EFlags) - end - catch - throw:T -> - T; - error:Reason -> - {error, Reason} - end. - - -enc_name_info_flags([]) -> - 0; -enc_name_info_flags(Flags) -> - EFlags = [{namereqd, ?NET_NAME_INFO_NAMEREQD}, - {dgram, ?NET_NAME_INFO_DGRAM}, - {nofqdn, ?NET_NAME_INFO_NOFQDN}, - {numerichost, ?NET_NAME_INFO_NUMERICHOST}, - {numericserv, ?NET_NAME_INFO_NUMERICSERV}, - - %% The below flags was introduce with glibc 2.3.4. - {idn, ?NET_NAME_INFO_IDN}, - {idna_allow_unassigned, ?NET_NAME_INFO_IDNA_ALLOW_UNASSIGNED}, - {idna_use_std3_ascii_rules,?NET_NAME_INFO_IDNA_USE_STD3_ASCII_RULES}], - enc_flags(Flags, EFlags). - - -enc_flags([], _) -> - 0; -enc_flags(Flags, EFlags) -> - F = fun(Flag, Acc) -> - case lists:keysearch(Flag, 1, EFlags) of - {value, {Flag, EFlag}} -> - Acc bor (1 bsl EFlag); - false -> - throw({error, {unknown_flag, Flag}}) - end - end, - lists:foldl(F, 0, Flags). + nif_getnameinfo(SockAddr, EFlags). %% =========================================================================== -- cgit v1.2.3 From 5b7ac9423f07171e4cd682a216cc86b1d32c660a Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Fri, 8 Jun 2018 15:25:51 +0200 Subject: [net-nif] The net-module now actually loads The net (nif) module now actually loads (automatically) when the VM is started (*on linux*). Now we must make sure it *actually* works, and implement the rest of the stuff... --- erts/emulator/nifs/common/net_nif.c | 149 ++++++++++++++++++++++++++---------- erts/preloaded/ebin/init.beam | Bin 51508 -> 51544 bytes erts/preloaded/ebin/net.beam | Bin 0 -> 4792 bytes erts/preloaded/src/init.erl | 1 + erts/preloaded/src/net.erl | 52 ++++++------- 5 files changed, 135 insertions(+), 67 deletions(-) create mode 100644 erts/preloaded/ebin/net.beam diff --git a/erts/emulator/nifs/common/net_nif.c b/erts/emulator/nifs/common/net_nif.c index b8fa6628a7..edcf944c66 100644 --- a/erts/emulator/nifs/common/net_nif.c +++ b/erts/emulator/nifs/common/net_nif.c @@ -24,6 +24,8 @@ * */ +#define STATIC_ERLANG_NIF 1 + /* #include */ /* #include */ /* #include */ @@ -262,6 +264,8 @@ typedef unsigned long long llu_t; #define MKT2(E,E1,E2) enif_make_tuple2((E), (E1), (E2)) #define MKT3(E,E1,E2,E3) enif_make_tuple3((E), (E1), (E2), (E3)) #define MKT4(E,E1,E2,E3,E4) enif_make_tuple4((E), (E1), (E2), (E3), (E4)) +#define MKT5(E,E1,E2,E3,E4,E5) \ + enif_make_tuple5((E), (E1), (E2), (E3), (E4), (E5)) #define MKT8(E,E1,E2,E3,E4,E5,E6,E7,E8) \ enif_make_tuple8((E), (E1), (E2), (E3), (E4), (E5), (E6), (E7), (E8)) @@ -364,8 +368,11 @@ static ERL_NIF_TERM nif_if_names(ErlNifEnv* env, static ERL_NIF_TERM ngetnameinfo(ErlNifEnv* env, const SockAddress* saP, - socklen_t saLen, + SOCKLEN_T saLen, int flags); +static ERL_NIF_TERM ngetaddrinfo(ErlNifEnv* env, + char* host, + char* serv); static ERL_NIF_TERM nif_name2index(ErlNifEnv* env, char* ifn); static ERL_NIF_TERM nif_index2name(ErlNifEnv* env, @@ -387,16 +394,16 @@ static void net_down(ErlNifEnv* env, static BOOLEAN_T decode_in_sockaddr(ErlNifEnv* env, const ERL_NIF_TERM eAddr, SockAddress* saP, - socklen_t* saLen); + SOCKLEN_T* saLen); static BOOLEAN_T decode_in4_sockaddr(ErlNifEnv* env, const ERL_NIF_TERM* addrt, SockAddress* saP, - socklen_t* saLen); + SOCKLEN_T* saLen); #if defined(HAVE_IN6) && defined(AF_INET6) static BOOLEAN_T decode_in6_sockaddr(ErlNifEnv* env, const ERL_NIF_TERM* addrt, SockAddress* saP, - socklen_t* saLen); + SOCKLEN_T* saLen); #endif static BOOLEAN_T decode_nameinfo_flags(ErlNifEnv* env, const ERL_NIF_TERM eflags, @@ -404,10 +411,26 @@ static BOOLEAN_T decode_nameinfo_flags(ErlNifEnv* env, static BOOLEAN_T decode_nameinfo_flags_list(ErlNifEnv* env, const ERL_NIF_TERM eflags, int* flags); +static +BOOLEAN_T decode_addrinfo_string(ErlNifEnv* env, + const ERL_NIF_TERM eString, + char** stringP); static ERL_NIF_TERM encode_address_info(ErlNifEnv* env, struct addrinfo* addrInfo); static unsigned int address_info_length(struct addrinfo* addrInfoP); +static ERL_NIF_TERM make_address_info(ErlNifEnv* env, + struct addrinfo* addrInfoP); +static ERL_NIF_TERM make_addrinfo_family(ErlNifEnv* env, + int family); +static ERL_NIF_TERM make_addrinfo_type(ErlNifEnv* env, + int socktype); +static ERL_NIF_TERM make_addrinfo_proto(ErlNifEnv* env, + int proto); +static ERL_NIF_TERM make_addrinfo_addr(ErlNifEnv* env, + struct sockaddr* addrP, + SOCKLEN_T addrLen); + static ERL_NIF_TERM make_ok2(ErlNifEnv* env, ERL_NIF_TERM val); // static ERL_NIF_TERM make_ok3(ErlNifEnv* env, ERL_NIF_TERM val1, ERL_NIF_TERM val2); static ERL_NIF_TERM make_error(ErlNifEnv* env, ERL_NIF_TERM reason); @@ -448,18 +471,25 @@ static const struct in6_addr in6addr_loopback = /* *** String constants *** */ +static char str_address_info[] = "address_info"; static char str_dgram[] = "dgram"; static char str_error[] = "error"; static char str_idn[] = "idn"; static char str_idna_allow_unassigned[] = "idna_allow_unassigned"; static char str_idna_use_std3_ascii_rules[] = "idna_use_std3_ascii_rules"; +static char str_inet[] = "inet"; +static char str_inet6[] = "inet6"; +static char str_ip[] = "ip"; static char str_namereqd[] = "namereqd"; static char str_name_info[] = "name_info"; static char str_nofqdn[] = "nofqdn"; static char str_numerichost[] = "numerichost"; static char str_numericserv[] = "numericserv"; static char str_ok[] = "ok"; +static char str_stream[] = "stream"; +static char str_tcp[] = "tcp"; static char str_true[] = "true"; +static char str_udp[] = "udp"; static char str_undefined[] = "undefined"; // static char str_lowdelay[] = "lowdelay"; @@ -469,6 +499,7 @@ static char str_undefined[] = "undefined"; /* (special) error string constants */ // static char str_eafnosupport[] = "eafnosupport"; +static char str_eaddrfamily[] = "eaddrfamily"; static char str_eagain[] = "eagain"; static char str_ebadflags[] = "ebadflags"; static char str_efail[] = "efail"; @@ -476,10 +507,14 @@ static char str_efamily[] = "efamily"; static char str_einval[] = "einval"; // static char str_eisconn[] = "eisconn"; static char str_emem[] = "emem"; +static char str_enodata[] = "enodata"; static char str_enoname[] = "enoname"; // static char str_enotclosing[] = "enotclosing"; // static char str_enotconn[] = "enotconn"; static char str_eoverflow[] = "eoverflow"; +static char str_eservice[] = "eservice"; +static char str_esocktype[] = "esocktype"; +static char str_esystem[] = "esystem"; // static char str_exalloc[] = "exalloc"; // static char str_exbadstate[] = "exbadstate"; // static char str_exbusy[] = "exbusy"; @@ -490,11 +525,15 @@ static char str_eoverflow[] = "eoverflow"; /* *** Atoms *** */ -static ERL_NIF_TERM atom_error; +static ERL_NIF_TERM atom_address_info; static ERL_NIF_TERM atom_dgram; +static ERL_NIF_TERM atom_error; static ERL_NIF_TERM atom_idn; static ERL_NIF_TERM atom_idna_allow_unassigned; static ERL_NIF_TERM atom_idna_use_std3_ascii_rules; +static ERL_NIF_TERM atom_inet; +static ERL_NIF_TERM atom_inet6; +static ERL_NIF_TERM atom_ip; static ERL_NIF_TERM atom_namereqd; static ERL_NIF_TERM atom_name_info; static ERL_NIF_TERM atom_nofqdn; @@ -502,8 +541,11 @@ static ERL_NIF_TERM atom_numerichost; static ERL_NIF_TERM atom_numericserv; static ERL_NIF_TERM atom_ok; // static ERL_NIF_TERM atom_select; +static ERL_NIF_TERM atom_stream; // static ERL_NIF_TERM atom_timeout; +static ERL_NIF_TERM atom_tcp; static ERL_NIF_TERM atom_true; +static ERL_NIF_TERM atom_udp; static ERL_NIF_TERM atom_undefined; // static ERL_NIF_TERM atom_lowdelay; @@ -512,6 +554,7 @@ static ERL_NIF_TERM atom_undefined; // static ERL_NIF_TERM atom_mincost; // static ERL_NIF_TERM atom_eafnosupport; +static ERL_NIF_TERM atom_eaddrfamily; static ERL_NIF_TERM atom_eagain; static ERL_NIF_TERM atom_ebadflags; static ERL_NIF_TERM atom_efail; @@ -519,10 +562,14 @@ static ERL_NIF_TERM atom_efamily; static ERL_NIF_TERM atom_einval; // static ERL_NIF_TERM atom_eisconn; static ERL_NIF_TERM atom_emem; +static ERL_NIF_TERM atom_enodata; static ERL_NIF_TERM atom_enoname; // static ERL_NIF_TERM atom_enotclosing; // static ERL_NIF_TERM atom_enotconn; static ERL_NIF_TERM atom_eoverflow; +static ERL_NIF_TERM atom_eservice; +static ERL_NIF_TERM atom_esocktype; +static ERL_NIF_TERM atom_esystem; // static ERL_NIF_TERM atom_exalloc; // static ERL_NIF_TERM atom_exbadstate; // static ERL_NIF_TERM atom_exbusy; @@ -620,7 +667,7 @@ ERL_NIF_TERM nif_getnameinfo(ErlNifEnv* env, unsigned int eFlags; int flags = 0; // Just in case... SockAddress sa; - socklen_t saLen = 0; // Just in case... + SOCKLEN_T saLen = 0; // Just in case... if ((argc != 2) || !GET_UINT(env, argv[1], &eFlags)) { @@ -645,14 +692,14 @@ ERL_NIF_TERM nif_getnameinfo(ErlNifEnv* env, static ERL_NIF_TERM ngetnameinfo(ErlNifEnv* env, const SockAddress* saP, - socklen_t saLen, + SOCKLEN_T saLen, int flags) { ERL_NIF_TERM result; char host[HOSTNAME_LEN]; - socklen_t hostLen = sizeof(host); + SOCKLEN_T hostLen = sizeof(host); char serv[SERVICE_LEN]; - socklen_t servLen = sizeof(serv); + SOCKLEN_T servLen = sizeof(serv); int res = getnameinfo((struct sockaddr*) saP, saLen, host, hostLen, @@ -729,7 +776,7 @@ ERL_NIF_TERM nif_getaddrinfo(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { - ERL_NIF_TERM result; + ERL_NIF_TERM result, eHostName, eServName; //, eHints; char* hostName; char* servName; // struct addrinfo* hints; @@ -739,7 +786,7 @@ ERL_NIF_TERM nif_getaddrinfo(ErlNifEnv* env, } eHostName = argv[0]; eServName = argv[1]; - eHints = argv[2]; + // eHints = argv[2]; if (!decode_addrinfo_string(env, eHostName, &hostName)) return enif_make_badarg(env); @@ -773,10 +820,11 @@ ERL_NIF_TERM nif_getaddrinfo(ErlNifEnv* env, static -ERL_NIF_TERM nifgetaddrinfo(ErlNifEnv* env, - char* host, - char* serv) +ERL_NIF_TERM ngetaddrinfo(ErlNifEnv* env, + char* host, + char* serv) { + ERL_NIF_TERM result; struct addrinfo* addrInfoP; int res; @@ -823,7 +871,7 @@ ERL_NIF_TERM nifgetaddrinfo(ErlNifEnv* env, result = make_error(env, atom_enoname); break; - case EAI_SERCVICE: + case EAI_SERVICE: result = make_error(env, atom_eservice); break; @@ -1041,7 +1089,7 @@ static BOOLEAN_T decode_in_sockaddr(ErlNifEnv* env, const ERL_NIF_TERM eAddr, SockAddress* saP, - socklen_t* saLen) + SOCKLEN_T* saLen) { const ERL_NIF_TERM* addrt; int addrtSz; @@ -1078,7 +1126,7 @@ static BOOLEAN_T decode_in4_sockaddr(ErlNifEnv* env, const ERL_NIF_TERM* addrt, SockAddress* saP, - socklen_t* saLen) + SOCKLEN_T* saLen) { unsigned int len; char tag[16]; // Just in case... @@ -1146,7 +1194,7 @@ static BOOLEAN_T decode_in6_sockaddr(ErlNifEnv* env, const ERL_NIF_TERM* addrt, SockAddress* saP, - socklen_t* saLen) + SOCKLEN_T* saLen) { unsigned int len; char tag[16]; // Just in case... @@ -1330,7 +1378,7 @@ BOOLEAN_T decode_addrinfo_string(ErlNifEnv* env, result = FALSE; } - bufP = ALLOC(len); + bufP = MALLOC(len); if (GET_STR(env, eString, bufP, len)) { *stringP = bufP; @@ -1366,7 +1414,7 @@ ERL_NIF_TERM encode_address_info(ErlNifEnv* env, array[i] = make_address_info(env, &addrInfo[i]); } - result = mkake_ok2(env, MKLA(env, array, len)); + result = make_ok2(env, MKLA(env, array, len)); } else { result = MKEL(env); } @@ -1407,8 +1455,8 @@ unsigned int address_info_length(struct addrinfo* addrInfoP) * {address_info, Fam, Type, Proto, Addr} */ static -ERL_NIF_TERM make_adress_info(ErlNifEnv* env, - struct addrinfo* addrInfoP) +ERL_NIF_TERM make_address_info(ErlNifEnv* env, + struct addrinfo* addrInfoP) { ERL_NIF_TERM Fam = make_addrinfo_family(env, addrInfoP->ai_family); ERL_NIF_TERM Type = make_addrinfo_type(env, addrInfoP->ai_socktype); @@ -1530,35 +1578,41 @@ ERL_NIF_TERM make_addrinfo_proto(ErlNifEnv* env, static ERL_NIF_TERM make_addrinfo_addr(ErlNifEnv* env, struct sockaddr* addrP, - socklen_t addrLen) + SOCKLEN_T addrLen) { ERL_NIF_TERM port, addr, eaddr; SockAddress* p = (SockAddress*) addrP; switch (addrP->sa_family) { case AF_INET: - port = ntohs(p->in.sin_port); - addr = MKT4(env, - MKI(env, p->in.sin_addr[0]), - MKI(env, p->in.sin_addr[1]), - MKI(env, p->in.sin_addr[2]), - MKI(env, p->in.sin_addr[3])); - eaddr = MKT2(env, port, addr); + { + unsigned char* a = (unsigned char*) &p->in.sin_addr; + port = ntohs(p->in.sin_port); + addr = MKT4(env, + MKI(env, a[0]), + MKI(env, a[1]), + MKI(env, a[2]), + MKI(env, a[3])); + eaddr = MKT2(env, port, addr); + } break; #if defined(HAVE_IN6) && defined(AF_INET6) case AF_INET6: - port = ntohs(p->in6.sin6_port); - addr = MKT8(env, - MKI(env, p->in6.sin_addr[0]), - MKI(env, p->in6.sin_addr[1]), - MKI(env, p->in6.sin_addr[2]), - MKI(env, p->in6.sin_addr[3]), - MKI(env, p->in6.sin_addr[3]), - MKI(env, p->in6.sin_addr[3]), - MKI(env, p->in6.sin_addr[3]), - MKI(env, p->in6.sin_addr[3])); - eaddr = MKT2(env, port, addr); + { + unsigned char* a = (unsigned char*) &p->in6.sin6_addr; + port = ntohs(p->in6.sin6_port); + addr = MKT8(env, + MKI(env, get_int16(a)), + MKI(env, get_int16(&a[ 2])), + MKI(env, get_int16(&a[ 4])), + MKI(env, get_int16(&a[ 6])), + MKI(env, get_int16(&a[ 8])), + MKI(env, get_int16(&a[10])), + MKI(env, get_int16(&a[12])), + MKI(env, get_int16(&a[14]))); + eaddr = MKT2(env, port, addr); + } break; #endif @@ -1713,6 +1767,7 @@ ErlNifFunc net_funcs[] = /* address and name translation in protocol-independent manner */ {"nif_getnameinfo", 2, nif_getnameinfo, 0}, + {"nif_getaddrinfo", 3, nif_getaddrinfo, 0}, /* Network interface (name and/or index) functions */ {"nif_if_name2index", 1, nif_if_name2index, 0}, @@ -1730,18 +1785,25 @@ static int on_load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info) { /* +++ Misc atoms +++ */ + atom_address_info = MKA(env, str_address_info); atom_dgram = MKA(env, str_dgram); atom_error = MKA(env, str_error); atom_idn = MKA(env, str_idn); atom_idna_allow_unassigned = MKA(env, str_idna_allow_unassigned); atom_idna_use_std3_ascii_rules = MKA(env, str_idna_use_std3_ascii_rules); + atom_inet = MKA(env, str_inet); + atom_inet6 = MKA(env, str_inet6); + atom_ip = MKA(env, str_ip); atom_namereqd = MKA(env, str_namereqd); atom_name_info = MKA(env, str_name_info); atom_nofqdn = MKA(env, str_nofqdn); atom_numerichost = MKA(env, str_numerichost); atom_numericserv = MKA(env, str_numericserv); atom_ok = MKA(env, str_ok); + atom_stream = MKA(env, str_stream); + atom_tcp = MKA(env, str_tcp); atom_true = MKA(env, str_true); + atom_udp = MKA(env, str_udp); atom_undefined = MKA(env, str_undefined); // atom_version = MKA(env, str_version); @@ -1752,6 +1814,7 @@ int on_load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info) /* Error codes */ // atom_eafnosupport = MKA(env, str_eafnosupport); + atom_eaddrfamily = MKA(env, str_eaddrfamily); atom_eagain = MKA(env, str_eagain); atom_ebadflags = MKA(env, str_ebadflags); atom_efail = MKA(env, str_efail); @@ -1759,10 +1822,14 @@ int on_load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info) atom_einval = MKA(env, str_einval); // atom_eisconn = MKA(env, str_eisconn); atom_emem = MKA(env, str_emem); + atom_enodata = MKA(env, str_enodata); atom_enoname = MKA(env, str_enoname); // atom_enotclosing = MKA(env, str_enotclosing); // atom_enotconn = MKA(env, str_enotconn); atom_eoverflow = MKA(env, str_eoverflow); + atom_eservice = MKA(env, str_eservice); + atom_esocktype = MKA(env, str_esocktype); + atom_esystem = MKA(env, str_esystem); // atom_exalloc = MKA(env, str_exalloc); // atom_exbadstate = MKA(env, str_exbadstate); // atom_exbusy = MKA(env, str_exbusy); diff --git a/erts/preloaded/ebin/init.beam b/erts/preloaded/ebin/init.beam index 6658ff1a33..9c8973845b 100644 Binary files a/erts/preloaded/ebin/init.beam and b/erts/preloaded/ebin/init.beam differ diff --git a/erts/preloaded/ebin/net.beam b/erts/preloaded/ebin/net.beam new file mode 100644 index 0000000000..a5746fb8c9 Binary files /dev/null and b/erts/preloaded/ebin/net.beam differ diff --git a/erts/preloaded/src/init.erl b/erts/preloaded/src/init.erl index 303301cbff..b02e5dce85 100644 --- a/erts/preloaded/src/init.erl +++ b/erts/preloaded/src/init.erl @@ -206,6 +206,7 @@ boot(BootArgs) -> erl_tracer:on_load(), prim_buffer:on_load(), prim_file:on_load(), + net:on_load(), {Start0,Flags,Args} = parse_boot_args(BootArgs), %% We don't get to profile parsing of BootArgs diff --git a/erts/preloaded/src/net.erl b/erts/preloaded/src/net.erl index bdd81ea93a..f145797b97 100644 --- a/erts/preloaded/src/net.erl +++ b/erts/preloaded/src/net.erl @@ -131,7 +131,7 @@ on_load(Path, Extra) when is_list(Path) andalso is_map(Extra) -> on_load(true, _Path, _Extra) -> ok; on_load(false, Path, Extra) -> - ok = erlang:load_nif(Path, maps:put(timestamp, formated_timestamp(), Extra)). + ok = erlang:load_nif(Path, Extra). @@ -174,7 +174,7 @@ getnameinfo(SockAddr, Flags) when (is_record(SockAddr, in4_sockaddr) orelse is_record(SockAddr, in6_sockaddr)) andalso is_list(Flags) -> - nif_getnameinfo(SockAddr, EFlags). + nif_getnameinfo(SockAddr, Flags). %% =========================================================================== @@ -254,30 +254,30 @@ if_names() -> %% %% =========================================================================== -formated_timestamp() -> - format_timestamp(os:timestamp()). - -format_timestamp(Now) -> - N2T = fun(N) -> calendar:now_to_local_time(N) end, - format_timestamp(Now, N2T, true). - -format_timestamp({_N1, _N2, N3} = N, N2T, true) -> - FormatExtra = ".~.2.0w", - ArgsExtra = [N3 div 10000], - format_timestamp(N, N2T, FormatExtra, ArgsExtra); -format_timestamp({_N1, _N2, _N3} = N, N2T, false) -> - FormatExtra = "", - ArgsExtra = [], - format_timestamp(N, N2T, FormatExtra, ArgsExtra). - -format_timestamp(N, N2T, FormatExtra, ArgsExtra) -> - {Date, Time} = N2T(N), - {YYYY,MM,DD} = Date, - {Hour,Min,Sec} = Time, - FormatDate = - io_lib:format("~.4w-~.2.0w-~.2.0w ~.2.0w:~.2.0w:~.2.0w" ++ FormatExtra, - [YYYY, MM, DD, Hour, Min, Sec] ++ ArgsExtra), - lists:flatten(FormatDate). +%% formated_timestamp() -> +%% format_timestamp(os:timestamp()). + +%% format_timestamp(Now) -> +%% N2T = fun(N) -> calendar:now_to_local_time(N) end, +%% format_timestamp(Now, N2T, true). + +%% format_timestamp({_N1, _N2, N3} = N, N2T, true) -> +%% FormatExtra = ".~.2.0w", +%% ArgsExtra = [N3 div 10000], +%% format_timestamp(N, N2T, FormatExtra, ArgsExtra); +%% format_timestamp({_N1, _N2, _N3} = N, N2T, false) -> +%% FormatExtra = "", +%% ArgsExtra = [], +%% format_timestamp(N, N2T, FormatExtra, ArgsExtra). + +%% format_timestamp(N, N2T, FormatExtra, ArgsExtra) -> +%% {Date, Time} = N2T(N), +%% {YYYY,MM,DD} = Date, +%% {Hour,Min,Sec} = Time, +%% FormatDate = +%% io_lib:format("~.4w-~.2.0w-~.2.0w ~.2.0w:~.2.0w:~.2.0w" ++ FormatExtra, +%% [YYYY, MM, DD, Hour, Min, Sec] ++ ArgsExtra), +%% lists:flatten(FormatDate). %% =========================================================================== -- cgit v1.2.3 From cc08971508f3515f9f3b6c406ff0f6a186b24f6b Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Mon, 11 Jun 2018 12:34:23 +0200 Subject: [net-nif] Added debugging Added a command function, which is currently only used to enable and disable debug, which is also added. --- erts/emulator/nifs/common/net_nif.c | 285 +++++++++++++++++++++++++++++++++++- erts/preloaded/ebin/net.beam | Bin 4792 -> 5000 bytes erts/preloaded/src/net.erl | 12 +- 3 files changed, 289 insertions(+), 8 deletions(-) diff --git a/erts/emulator/nifs/common/net_nif.c b/erts/emulator/nifs/common/net_nif.c index edcf944c66..134b02d178 100644 --- a/erts/emulator/nifs/common/net_nif.c +++ b/erts/emulator/nifs/common/net_nif.c @@ -63,6 +63,7 @@ #include #include #include +#include #ifdef HAVE_UNISTD_H #include @@ -317,6 +318,12 @@ typedef unsigned long long llu_t; # define SOCKLEN_T size_t #endif +#define NDEBUG( ___COND___ , proto ) \ + if ( ___COND___ ) { \ + dbg_printf proto; \ + fflush(stdout); \ + } +#define NDBG( proto ) NDEBUG( data.debug , proto ) /* The general purpose socket address */ typedef union { @@ -330,6 +337,12 @@ typedef union { } SockAddress; +typedef struct { + BOOLEAN_T debug; +} NetData; + + +static NetData data; /* ---------------------------------------------------------------------- @@ -347,6 +360,9 @@ static ERL_NIF_TERM nif_is_loaded(ErlNifEnv* env, static ERL_NIF_TERM nif_info(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); +static ERL_NIF_TERM nif_command(ErlNifEnv* env, + int argc, + const ERL_NIF_TERM argv[]); static ERL_NIF_TERM nif_getnameinfo(ErlNifEnv* env, int argc, @@ -366,6 +382,8 @@ static ERL_NIF_TERM nif_if_names(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); +static ERL_NIF_TERM ncommand(ErlNifEnv* env, + ERL_NIF_TERM cmd); static ERL_NIF_TERM ngetnameinfo(ErlNifEnv* env, const SockAddress* saP, SOCKLEN_T saLen, @@ -415,6 +433,9 @@ static BOOLEAN_T decode_addrinfo_string(ErlNifEnv* env, const ERL_NIF_TERM eString, char** stringP); +static ERL_NIF_TERM decode_bool(ErlNifEnv* env, + ERL_NIF_TERM eBool, + BOOLEAN_T* bool); static ERL_NIF_TERM encode_address_info(ErlNifEnv* env, struct addrinfo* addrInfo); static unsigned int address_info_length(struct addrinfo* addrInfoP); @@ -437,6 +458,10 @@ static ERL_NIF_TERM make_error(ErlNifEnv* env, ERL_NIF_TERM reason); static ERL_NIF_TERM make_error1(ErlNifEnv* env, char* reason); static ERL_NIF_TERM make_error2(ErlNifEnv* env, int err); +static void dbg_printf( const char* format, ... ); +static int dbg_realtime(struct timespec* tsP); +static int dbg_timespec2str(char *buf, unsigned int len, struct timespec *ts); + /* static void xabort(const char* expr, const char* func, @@ -472,8 +497,10 @@ static const struct in6_addr in6addr_loopback = /* *** String constants *** */ static char str_address_info[] = "address_info"; +static char str_debug[] = "debug"; static char str_dgram[] = "dgram"; static char str_error[] = "error"; +static char str_false[] = "false"; static char str_idn[] = "idn"; static char str_idna_allow_unassigned[] = "idna_allow_unassigned"; static char str_idna_use_std3_ascii_rules[] = "idna_use_std3_ascii_rules"; @@ -526,8 +553,10 @@ static char str_esystem[] = "esystem"; /* *** Atoms *** */ static ERL_NIF_TERM atom_address_info; +static ERL_NIF_TERM atom_debug; static ERL_NIF_TERM atom_dgram; static ERL_NIF_TERM atom_error; +static ERL_NIF_TERM atom_false; static ERL_NIF_TERM atom_idn; static ERL_NIF_TERM atom_idna_allow_unassigned; static ERL_NIF_TERM atom_idna_use_std3_ascii_rules; @@ -640,12 +669,90 @@ ERL_NIF_TERM nif_info(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { - ERL_NIF_TERM info = enif_make_new_map(env); + ERL_NIF_TERM info; + + NDBG( ("info -> entry\r\n") ); + + info = enif_make_new_map(env); + + NDBG( ("info -> done\r\n") ); + return info; } +/* ---------------------------------------------------------------------- + * nif_command + * + * Description: + * This is a general purpose utility function. + * + * Arguments: + * Command - This is a general purpose command, of any type. + * Currently, the only supported command is: + * + * {debug, boolean()} + */ +static +ERL_NIF_TERM nif_command(ErlNifEnv* env, + int argc, + const ERL_NIF_TERM argv[]) +{ + ERL_NIF_TERM ecmd, result; + + NDBG( ("command -> entry (%d)\r\n", argc) ); + + if (argc != 1) + return enif_make_badarg(env); + + ecmd = argv[0]; + + NDBG( ("command -> ecmd: %T\r\n", ecmd) ); + + result = ncommand(env, ecmd); + + NDBG( ("command -> result: %T\r\n", result) ); + + return result; +} + + + +/* + * The command can, in principle, be anything, though currently we only + * support a debug command. + */ +static +ERL_NIF_TERM ncommand(ErlNifEnv* env, + ERL_NIF_TERM cmd) +{ + const ERL_NIF_TERM* t; + int tsz; + + if (IS_TUPLE(env, cmd)) { + /* Could be the debug tuple */ + if (!GET_TUPLE(env, cmd, &tsz, &t)) + return make_error(env, atom_einval); + + if (tsz != 2) + return make_error(env, atom_einval); + + /* First element should be the atom 'debug' */ + if (COMPARE(t[0], atom_debug) != 0) + return make_error(env, atom_einval); + + return decode_bool(env, t[1], &data.debug); + + } else { + return make_error(env, atom_einval); + } + +} + + + + /* ---------------------------------------------------------------------- * nif_getnameinfo * @@ -663,25 +770,36 @@ ERL_NIF_TERM nif_getnameinfo(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { - ERL_NIF_TERM eSockAddr; + ERL_NIF_TERM result, eSockAddr; unsigned int eFlags; int flags = 0; // Just in case... SockAddress sa; SOCKLEN_T saLen = 0; // Just in case... + NDBG( ("nif_getnameinfo -> entry (%d)\r\n", argc) ); + if ((argc != 2) || !GET_UINT(env, argv[1], &eFlags)) { return enif_make_badarg(env); } eSockAddr = argv[0]; + NDBG( ("nif_getnameinfo -> " + "\r\n SockAddr: %T" + "\r\n Flags: %T" + "\r\n", argv[0], argv[1]) ); + if (!decode_nameinfo_flags(env, eFlags, &flags)) return enif_make_badarg(env); if (decode_in_sockaddr(env, eSockAddr, &sa, &saLen)) return enif_make_badarg(env); - return ngetnameinfo(env, &sa, saLen, flags); + result = ngetnameinfo(env, &sa, saLen, flags); + + NDBG( ("nif_getnameinfo -> done when result: %T\r\n", result) ); + + return result; } @@ -781,6 +899,8 @@ ERL_NIF_TERM nif_getaddrinfo(ErlNifEnv* env, char* servName; // struct addrinfo* hints; + NDBG( ("nif_getaddrinfo -> entry (%d)\r\n", argc) ); + if (argc != 3) { return enif_make_badarg(env); } @@ -788,6 +908,12 @@ ERL_NIF_TERM nif_getaddrinfo(ErlNifEnv* env, eServName = argv[1]; // eHints = argv[2]; + NDBG( ("nif_getaddrinfo -> " + "\r\n Host: %T" + "\r\n Service: %T" + "\r\n Hints: %T" + "\r\n", argv[0], argv[1], argv[2]) ); + if (!decode_addrinfo_string(env, eHostName, &hostName)) return enif_make_badarg(env); @@ -815,6 +941,8 @@ ERL_NIF_TERM nif_getaddrinfo(ErlNifEnv* env, FREE(hints); */ + NDBG( ("nif_getaddrinfo -> done when result: %T\r\n", result) ); + return result; } @@ -907,18 +1035,28 @@ ERL_NIF_TERM nif_if_name2index(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { - ERL_NIF_TERM eifn; + ERL_NIF_TERM eifn, result; char ifn[IF_NAMESIZE+1]; + NDBG( ("nif_if_name2index -> entry (%d)\r\n", argc) ); + if (argc != 1) { return enif_make_badarg(env); } eifn = argv[0]; + NDBG( ("nif_name2index -> " + "\r\n Ifn: %T" + "\r\n", argv[0]) ); + if (0 >= GET_STR(env, eifn, ifn, sizeof(ifn))) return make_error2(env, atom_einval); - return nif_name2index(env, ifn); + result = nif_name2index(env, ifn); + + NDBG( ("nif_if_name2index -> done when result: %T\r\n", result) ); + + return result; } @@ -953,14 +1091,25 @@ ERL_NIF_TERM nif_if_index2name(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { + ERL_NIF_TERM result; unsigned int idx; + NDBG( ("nif_if_index2name -> entry (%d)\r\n", argc) ); + if ((argc != 1) || !GET_UINT(env, argv[0], &idx)) { return enif_make_badarg(env); } - return nif_index2name(env, idx); + NDBG( ("nif_index2name -> " + "\r\n Idx: %T" + "\r\n", argv[0]) ); + + result = nif_index2name(env, idx); + + NDBG( ("nif_if_index2name -> done when result: %T\r\n", result) ); + + return result; } @@ -1001,11 +1150,19 @@ ERL_NIF_TERM nif_if_names(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { + ERL_NIF_TERM result; + + NDBG( ("nif_if_names -> entry (%d)\r\n", argc) ); + if (argc != 0) { return enif_make_badarg(env); } - return nif_names(env); + result = nif_names(env); + + NDBG( ("nif_if_names -> done when result: %T\r\n", result) ); + + return result; } @@ -1016,6 +1173,8 @@ ERL_NIF_TERM nif_names(ErlNifEnv* env) ERL_NIF_TERM result; struct if_nameindex* ifs = if_nameindex(); + NDBG( ("nif_names -> ifs: 0x%lX\r\n", ifs) ); + if (ifs == NULL) { result = make_error2(env, get_errno()); } else { @@ -1033,6 +1192,8 @@ ERL_NIF_TERM nif_names(ErlNifEnv* env) */ unsigned int len = nif_names_length(ifs); + NDBG( ("nif_names -> len: %d\r\n", len) ); + if (len > 0) { ERL_NIF_TERM* array = MALLOC(len * sizeof(ERL_NIF_TERM)); unsigned int i; @@ -1395,6 +1556,24 @@ BOOLEAN_T decode_addrinfo_string(ErlNifEnv* env, +static +ERL_NIF_TERM decode_bool(ErlNifEnv* env, + ERL_NIF_TERM eBool, + BOOLEAN_T* bool) +{ + if (COMPARE(eBool, atom_true) == 0) { + *bool = TRUE; + return atom_ok; + } else if (COMPARE(eBool, atom_false) == 0) { + *bool = FALSE; + return atom_ok; + } else { + return make_error(env, atom_einval); + } +} + + + /* Encode the address info * The address info is a linked list och address info, which * will result in the result being a list of zero or more length. @@ -1753,6 +1932,88 @@ void net_down(ErlNifEnv* env, +/* ---------------------------------------------------------------------- + * D e b u g F u n c t i o n s + * ---------------------------------------------------------------------- + */ + +/* + * Print a debug format string *with* both a timestamp and the + * the name of the *current* thread. + */ +static +void dbg_printf( const char* format, ... ) +{ + va_list args; + char f[512 + sizeof(format)]; // This has to suffice... + char stamp[30]; + struct timespec ts; + int res; + + /* + * We should really include self in the printout, so we can se which process + * are executing the code. But then I must change the API.... + * ....something for later. + */ + + if (!dbg_realtime(&ts)) { + if (dbg_timespec2str(stamp, sizeof(stamp), &ts) != 0) { + // res = enif_snprintf(f, sizeof(f), "NET [%s] %s", TSNAME(), format); + res = enif_snprintf(f, sizeof(f), "NET [%s]", format); + } else { + // res = enif_snprintf(f, sizeof(f), "NET[%s] [%s] %s", stamp, TSNAME(), format); + res = enif_snprintf(f, sizeof(f), "NET [%s] %s", stamp, format); + } + + if (res > 0) { + va_start (args, format); + erts_vfprintf (stdout, f, args); // TMP: use enif_vfprintf + va_end (args); + fflush(stdout); + } + } + + return; +} + + +static +int dbg_realtime(struct timespec* tsP) +{ + return clock_gettime(CLOCK_REALTIME, tsP); +} + + + + +/* + * Convert a timespec struct into a readable/printable string + */ +static +int dbg_timespec2str(char *buf, unsigned int len, struct timespec *ts) +{ + int ret, buflen; + struct tm t; + + tzset(); + if (localtime_r(&(ts->tv_sec), &t) == NULL) + return 1; + + ret = strftime(buf, len, "%F %T", &t); + if (ret == 0) + return 2; + len -= ret - 1; + buflen = strlen(buf); + + ret = snprintf(&buf[buflen], len, ".%06ld", ts->tv_nsec/1000); + if (ret >= len) + return 3; + + return 0; +} + + + /* ---------------------------------------------------------------------- * L o a d / u n l o a d / u p g r a d e F u n c t i o n s * ---------------------------------------------------------------------- @@ -1764,6 +2025,7 @@ ErlNifFunc net_funcs[] = // Some utility functions {"nif_is_loaded", 0, nif_is_loaded, 0}, {"nif_info", 0, nif_info, 0}, + {"nif_command", 1, nif_command, 0}, // Shall we let this be dirty? /* address and name translation in protocol-independent manner */ {"nif_getnameinfo", 2, nif_getnameinfo, 0}, @@ -1784,10 +2046,17 @@ ErlNifFunc net_funcs[] = static int on_load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info) { + // We should make it possible to use load_info to get default values + data.debug = FALSE; + + // NDBG( ("on_load -> entry\r\n") ); + /* +++ Misc atoms +++ */ atom_address_info = MKA(env, str_address_info); + atom_debug = MKA(env, str_debug); atom_dgram = MKA(env, str_dgram); atom_error = MKA(env, str_error); + atom_false = MKA(env, str_false); atom_idn = MKA(env, str_idn); atom_idna_allow_unassigned = MKA(env, str_idna_allow_unassigned); atom_idna_use_std3_ascii_rules = MKA(env, str_idna_use_std3_ascii_rules); @@ -1849,6 +2118,8 @@ int on_load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info) ERL_NIF_RT_CREATE, NULL); + // NDBG( ("on_load -> done\r\n") ); + return !net; } diff --git a/erts/preloaded/ebin/net.beam b/erts/preloaded/ebin/net.beam index a5746fb8c9..89f14a86d1 100644 Binary files a/erts/preloaded/ebin/net.beam and b/erts/preloaded/ebin/net.beam differ diff --git a/erts/preloaded/src/net.erl b/erts/preloaded/src/net.erl index f145797b97..3c010871c0 100644 --- a/erts/preloaded/src/net.erl +++ b/erts/preloaded/src/net.erl @@ -23,7 +23,8 @@ %% Administrative and "global" utility functions -export([ on_load/0, on_load/1, on_load/2, - info/0 + info/0, + command/1 ]). -export([ @@ -141,6 +142,12 @@ info() -> nif_info(). +-spec command(Cmd :: term()) -> term(). + +command(Cmd) -> + nif_command(Cmd). + + %% =========================================================================== %% @@ -292,6 +299,9 @@ nif_is_loaded() -> nif_info() -> erlang:error(badarg). +nif_command(_Cmd) -> + erlang:error(badarg). + nif_getnameinfo(_Addr, _Flags) -> erlang:error(badarg). -- cgit v1.2.3 From f2a28200a826af65596bb554b014d2c93b6314a7 Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Mon, 11 Jun 2018 14:40:58 +0200 Subject: [net-nif] Removed the old net module The old net module (in kernel) (deprecated) was removed and its function(s) has been moved into the new module. Also a minor updated to the info function. OTP-14831 --- bootstrap/lib/kernel/ebin/net.beam | Bin 608 -> 0 bytes erts/emulator/nifs/common/net_nif.c | 8 +++++--- erts/preloaded/src/net.erl | 22 ++++++++++++++++++++ lib/kernel/src/Makefile | 1 - lib/kernel/src/net.erl | 40 ------------------------------------ lib/stdlib/src/otp_internal.erl | 15 ++++++++++++-- 6 files changed, 40 insertions(+), 46 deletions(-) delete mode 100644 bootstrap/lib/kernel/ebin/net.beam delete mode 100644 lib/kernel/src/net.erl diff --git a/bootstrap/lib/kernel/ebin/net.beam b/bootstrap/lib/kernel/ebin/net.beam deleted file mode 100644 index 3e971eb0e1..0000000000 Binary files a/bootstrap/lib/kernel/ebin/net.beam and /dev/null differ diff --git a/erts/emulator/nifs/common/net_nif.c b/erts/emulator/nifs/common/net_nif.c index 134b02d178..328fb52ac3 100644 --- a/erts/emulator/nifs/common/net_nif.c +++ b/erts/emulator/nifs/common/net_nif.c @@ -669,13 +669,15 @@ ERL_NIF_TERM nif_info(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { - ERL_NIF_TERM info; + ERL_NIF_TERM info, tmp; NDBG( ("info -> entry\r\n") ); - info = enif_make_new_map(env); + tmp = enif_make_new_map(env); + if (!enif_make_map_put(env, tmp, atom_debug, BOOL2ATOM(data.debug), &info)) + info = tmp; - NDBG( ("info -> done\r\n") ); + NDBG( ("info -> done: %T\r\n", info) ); return info; } diff --git a/erts/preloaded/src/net.erl b/erts/preloaded/src/net.erl index 3c010871c0..54b3790dd5 100644 --- a/erts/preloaded/src/net.erl +++ b/erts/preloaded/src/net.erl @@ -36,6 +36,14 @@ if_names/0 ]). +%% Deprecated functions from the "old" net module +-export([call/4, + cast/4, + broadcast/3, + ping/1, + relay/1, + sleep/1]). + -export_type([ ip_address/0, ip4_address/0, @@ -103,6 +111,20 @@ -type network_interface_index() :: non_neg_integer(). +%% =========================================================================== +%% +%% D E P R E C A T E D F U N C T I O N S +%% +%% =========================================================================== + +call(N,M,F,A) -> rpc:call(N,M,F,A). +cast(N,M,F,A) -> rpc:cast(N,M,F,A). +broadcast(M,F,A) -> rpc:eval_everywhere(M,F,A). +ping(Node) -> net_adm:ping(Node). +sleep(T) -> receive after T -> ok end. +relay(X) -> slave:relay(X). + + %% =========================================================================== %% diff --git a/lib/kernel/src/Makefile b/lib/kernel/src/Makefile index 57f17defc8..7db05ebc90 100644 --- a/lib/kernel/src/Makefile +++ b/lib/kernel/src/Makefile @@ -121,7 +121,6 @@ MODULES = \ logger_server \ logger_simple_h \ logger_sup \ - net \ net_adm \ net_kernel \ os \ diff --git a/lib/kernel/src/net.erl b/lib/kernel/src/net.erl deleted file mode 100644 index 2d0ae2ed0c..0000000000 --- a/lib/kernel/src/net.erl +++ /dev/null @@ -1,40 +0,0 @@ -%% -%% %CopyrightBegin% -%% -%% Copyright Ericsson AB 1996-2016. All Rights Reserved. -%% -%% Licensed under the Apache License, Version 2.0 (the "License"); -%% you may not use this file except in compliance with the License. -%% You may obtain a copy of the License at -%% -%% http://www.apache.org/licenses/LICENSE-2.0 -%% -%% Unless required by applicable law or agreed to in writing, software -%% distributed under the License is distributed on an "AS IS" BASIS, -%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -%% See the License for the specific language governing permissions and -%% limitations under the License. -%% -%% %CopyrightEnd% -%% --module(net). - -%% Various network functions, kept here for compatibility - --export([call/4, - cast/4, - broadcast/3, - ping/1, - relay/1, - sleep/1]). - --deprecated(module). - -call(N,M,F,A) -> rpc:call(N,M,F,A). -cast(N,M,F,A) -> rpc:cast(N,M,F,A). -broadcast(M,F,A) -> rpc:eval_everywhere(M,F,A). -ping(Node) -> net_adm:ping(Node). -sleep(T) -> receive after T -> ok end. -relay(X) -> slave:relay(X). - - diff --git a/lib/stdlib/src/otp_internal.erl b/lib/stdlib/src/otp_internal.erl index aaed13ba3a..95d99f0367 100644 --- a/lib/stdlib/src/otp_internal.erl +++ b/lib/stdlib/src/otp_internal.erl @@ -44,8 +44,19 @@ obsolete(Module, Name, Arity) -> no end. -obsolete_1(net, _, _) -> - {deprecated, "module 'net' obsolete; use 'net_adm'"}; +obsolete_1(net, call, 4) -> + {deprecated, {rpc, call, 4}}; +obsolete_1(net, cast, 4) -> + {deprecated, {rpc, cast, 4}}; +obsolete_1(net, broadcast, 3) -> + {deprecated, {rpc, eval_everywhere, 3}}; +obsolete_1(net, ping, 1) -> + {deprecated, {net_adm, ping, 1}}; +obsolete_1(net, sleep, 1) -> + {deprecated, "Use 'receive after T -> ok end' instead"}; +obsolete_1(net, relay, 1) -> + {deprecated, {slave, relay, 1}}; + obsolete_1(erlang, now, 0) -> {deprecated, -- cgit v1.2.3 From 8c6d495f54207d019e645e5ff726418677f92ab9 Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Mon, 11 Jun 2018 18:14:42 +0200 Subject: [net-nif] Implemented gethostname --- erts/emulator/nifs/common/net_nif.c | 93 +++++++++++++++++++++++++++++++++++- erts/preloaded/ebin/net.beam | Bin 5000 -> 5892 bytes erts/preloaded/src/net.erl | 18 +++++++ 3 files changed, 110 insertions(+), 1 deletion(-) diff --git a/erts/emulator/nifs/common/net_nif.c b/erts/emulator/nifs/common/net_nif.c index 328fb52ac3..d623c6f246 100644 --- a/erts/emulator/nifs/common/net_nif.c +++ b/erts/emulator/nifs/common/net_nif.c @@ -215,6 +215,13 @@ typedef unsigned int BOOLEAN_T; typedef unsigned long long llu_t; +#ifdef __WIN32__ +#define net_gethostname(__buf__, __bufSz__) gethostname((__buf__), (__bufSz__)) +#else +#define net_gethostname(__buf__, __bufSz__) gethostname((__buf__), (__bufSz__)) +#endif // __WIN32__ + + /* Socket stuff */ // #define INVALID_SOCKET -1 @@ -244,6 +251,12 @@ typedef unsigned long long llu_t; #define SERVICE_LEN 256 +/* MAXHOSTNAMELEN could be 64 or 255 depending + * on the platform. Instead, use INET_MAXHOSTNAMELEN + * which is always 255 across all platforms + */ +#define NET_MAXHOSTNAMELEN 255 + /* =================================================================== * * * * Various enif macros * @@ -364,10 +377,13 @@ static ERL_NIF_TERM nif_command(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); -static ERL_NIF_TERM nif_getnameinfo(ErlNifEnv* env, +static ERL_NIF_TERM nif_gethostname(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); +static ERL_NIF_TERM nif_getnameinfo(ErlNifEnv* env, + int argc, + const ERL_NIF_TERM argv[]); static ERL_NIF_TERM nif_getaddrinfo(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); @@ -384,6 +400,7 @@ static ERL_NIF_TERM nif_if_names(ErlNifEnv* env, static ERL_NIF_TERM ncommand(ErlNifEnv* env, ERL_NIF_TERM cmd); +static ERL_NIF_TERM ngethostname(ErlNifEnv* env); static ERL_NIF_TERM ngetnameinfo(ErlNifEnv* env, const SockAddress* saP, SOCKLEN_T saLen, @@ -531,9 +548,11 @@ static char str_eagain[] = "eagain"; static char str_ebadflags[] = "ebadflags"; static char str_efail[] = "efail"; static char str_efamily[] = "efamily"; +static char str_efault[] = "efault"; static char str_einval[] = "einval"; // static char str_eisconn[] = "eisconn"; static char str_emem[] = "emem"; +static char str_enametoolong[] = "enametoolong"; static char str_enodata[] = "enodata"; static char str_enoname[] = "enoname"; // static char str_enotclosing[] = "enotclosing"; @@ -588,9 +607,11 @@ static ERL_NIF_TERM atom_eagain; static ERL_NIF_TERM atom_ebadflags; static ERL_NIF_TERM atom_efail; static ERL_NIF_TERM atom_efamily; +static ERL_NIF_TERM atom_efault; static ERL_NIF_TERM atom_einval; // static ERL_NIF_TERM atom_eisconn; static ERL_NIF_TERM atom_emem; +static ERL_NIF_TERM atom_enametoolong; static ERL_NIF_TERM atom_enodata; static ERL_NIF_TERM atom_enoname; // static ERL_NIF_TERM atom_enotclosing; @@ -754,6 +775,71 @@ ERL_NIF_TERM ncommand(ErlNifEnv* env, +/* ---------------------------------------------------------------------- + * nif_gethostname + * + * Description: + * Access the hostname of the current processor. + * + */ +static +ERL_NIF_TERM nif_gethostname(ErlNifEnv* env, + int argc, + const ERL_NIF_TERM argv[]) +{ + ERL_NIF_TERM result; + + NDBG( ("nif_gethostname -> entry (%d)\r\n", argc) ); + + if (argc != 0) + return enif_make_badarg(env); + + result = ngethostname(env); + + NDBG( ("nif_gethostname -> done when result: %T\r\n", result) ); + + return result; +} + + +static +ERL_NIF_TERM ngethostname(ErlNifEnv* env) +{ + ERL_NIF_TERM result; + char buf[NET_MAXHOSTNAMELEN + 1]; + int res; + + res = net_gethostname(buf, sizeof(buf)); + + NDBG( ("ngethostname -> gethostname res: %d\r\n", res) ); + + switch (res) { + case 0: + result = make_ok2(env, MKS(env, buf)); + break; + + case EFAULT: + result = make_error(env, atom_efault); + break; + + case EINVAL: + result = make_error(env, atom_einval); + break; + + case ENAMETOOLONG: + result = make_error(env, atom_enametoolong); + break; + + default: + result = make_error(env, MKI(env, res)); + break; + } + + return result; +} + + + /* ---------------------------------------------------------------------- * nif_getnameinfo @@ -2029,6 +2115,9 @@ ErlNifFunc net_funcs[] = {"nif_info", 0, nif_info, 0}, {"nif_command", 1, nif_command, 0}, // Shall we let this be dirty? + /* get/set hostname */ + {"nif_gethostname", 0, nif_gethostname, 0}, + /* address and name translation in protocol-independent manner */ {"nif_getnameinfo", 2, nif_getnameinfo, 0}, {"nif_getaddrinfo", 3, nif_getaddrinfo, 0}, @@ -2090,9 +2179,11 @@ int on_load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info) atom_ebadflags = MKA(env, str_ebadflags); atom_efail = MKA(env, str_efail); atom_efamily = MKA(env, str_efamily); + atom_efault = MKA(env, str_efault); atom_einval = MKA(env, str_einval); // atom_eisconn = MKA(env, str_eisconn); atom_emem = MKA(env, str_emem); + atom_enametoolong = MKA(env, str_enametoolong); atom_enodata = MKA(env, str_enodata); atom_enoname = MKA(env, str_enoname); // atom_enotclosing = MKA(env, str_enotclosing); diff --git a/erts/preloaded/ebin/net.beam b/erts/preloaded/ebin/net.beam index 89f14a86d1..c83cafe209 100644 Binary files a/erts/preloaded/ebin/net.beam and b/erts/preloaded/ebin/net.beam differ diff --git a/erts/preloaded/src/net.erl b/erts/preloaded/src/net.erl index 54b3790dd5..823f11e2b8 100644 --- a/erts/preloaded/src/net.erl +++ b/erts/preloaded/src/net.erl @@ -28,6 +28,7 @@ ]). -export([ + gethostname/0, getnameinfo/1, getnameinfo/2, getaddrinfo/2, @@ -177,6 +178,20 @@ command(Cmd) -> %% %% =========================================================================== +%% =========================================================================== +%% +%% gethostname - Get the name of the current host. +%% +%% + +-spec gethostname() -> {ok, HostName} | {error, Reason} when + HostName :: string(), + Reason :: term(). + +gethostname() -> + nif_gethostname(). + + %% =========================================================================== %% %% getnameinfo - Address-to-name translation in protocol-independent manner. @@ -324,6 +339,9 @@ nif_info() -> nif_command(_Cmd) -> erlang:error(badarg). +nif_gethostname() -> + erlang:error(badarg). + nif_getnameinfo(_Addr, _Flags) -> erlang:error(badarg). -- cgit v1.2.3 From d892681a4554b08442db219c3ac161027e9a4c4f Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Tue, 12 Jun 2018 15:06:03 +0200 Subject: [net-nif] Fixed things... Fixed functions getaddrinfo, if_index2name, if_name2index and if_names. OTP-14831 --- erts/emulator/nifs/common/net_nif.c | 162 +++++++++++++++++++++++++++++------- 1 file changed, 130 insertions(+), 32 deletions(-) diff --git a/erts/emulator/nifs/common/net_nif.c b/erts/emulator/nifs/common/net_nif.c index d623c6f246..900828c7b2 100644 --- a/erts/emulator/nifs/common/net_nif.c +++ b/erts/emulator/nifs/common/net_nif.c @@ -514,6 +514,7 @@ static const struct in6_addr in6addr_loopback = /* *** String constants *** */ static char str_address_info[] = "address_info"; +static char str_dccp[] = "dccp"; static char str_debug[] = "debug"; static char str_dgram[] = "dgram"; static char str_error[] = "error"; @@ -530,6 +531,9 @@ static char str_nofqdn[] = "nofqdn"; static char str_numerichost[] = "numerichost"; static char str_numericserv[] = "numericserv"; static char str_ok[] = "ok"; +static char str_raw[] = "raw"; +static char str_rdm[] = "rdm"; +static char str_seqpacket[] = "seqpacket"; static char str_stream[] = "stream"; static char str_tcp[] = "tcp"; static char str_true[] = "true"; @@ -557,6 +561,7 @@ static char str_enodata[] = "enodata"; static char str_enoname[] = "enoname"; // static char str_enotclosing[] = "enotclosing"; // static char str_enotconn[] = "enotconn"; +static char str_enxio[] = "enxio"; static char str_eoverflow[] = "eoverflow"; static char str_eservice[] = "eservice"; static char str_esocktype[] = "esocktype"; @@ -572,6 +577,7 @@ static char str_esystem[] = "esystem"; /* *** Atoms *** */ static ERL_NIF_TERM atom_address_info; +static ERL_NIF_TERM atom_dccp; static ERL_NIF_TERM atom_debug; static ERL_NIF_TERM atom_dgram; static ERL_NIF_TERM atom_error; @@ -588,8 +594,11 @@ static ERL_NIF_TERM atom_nofqdn; static ERL_NIF_TERM atom_numerichost; static ERL_NIF_TERM atom_numericserv; static ERL_NIF_TERM atom_ok; +static ERL_NIF_TERM atom_raw; +static ERL_NIF_TERM atom_rdm; // static ERL_NIF_TERM atom_select; static ERL_NIF_TERM atom_stream; +static ERL_NIF_TERM atom_seqpacket; // static ERL_NIF_TERM atom_timeout; static ERL_NIF_TERM atom_tcp; static ERL_NIF_TERM atom_true; @@ -616,6 +625,7 @@ static ERL_NIF_TERM atom_enodata; static ERL_NIF_TERM atom_enoname; // static ERL_NIF_TERM atom_enotclosing; // static ERL_NIF_TERM atom_enotconn; +static ERL_NIF_TERM atom_enxio; static ERL_NIF_TERM atom_eoverflow; static ERL_NIF_TERM atom_eservice; static ERL_NIF_TERM atom_esocktype; @@ -1029,7 +1039,8 @@ ERL_NIF_TERM nif_getaddrinfo(ErlNifEnv* env, FREE(hints); */ - NDBG( ("nif_getaddrinfo -> done when result: %T\r\n", result) ); + NDBG( ("nif_getaddrinfo -> done when result: " + "\r\n %T\r\n", result) ); return result; } @@ -1044,8 +1055,17 @@ ERL_NIF_TERM ngetaddrinfo(ErlNifEnv* env, struct addrinfo* addrInfoP; int res; + NDBG( ("ngetaddrinfo -> entry with" + "\r\n host: %s" + "\r\n serv: %s" + "\r\n", + ((host == NULL) ? "NULL" : host), + ((serv == NULL) ? "NULL" : serv)) ); + res = getaddrinfo(host, serv, NULL, &addrInfoP); + NDBG( ("ngetaddrinfo -> res: %d\r\n", res) ); + switch (res) { case 0: { @@ -1133,7 +1153,7 @@ ERL_NIF_TERM nif_if_name2index(ErlNifEnv* env, } eifn = argv[0]; - NDBG( ("nif_name2index -> " + NDBG( ("nif_if_name2index -> " "\r\n Ifn: %T" "\r\n", argv[0]) ); @@ -1153,12 +1173,18 @@ static ERL_NIF_TERM nif_name2index(ErlNifEnv* env, char* ifn) { - unsigned int idx = if_nametoindex(ifn); + unsigned int idx; + + NDBG( ("nif_name2index -> entry with ifn: %s\r\n", ifn) ); + + idx = if_nametoindex(ifn); - if (idx == 0) + NDBG( ("nif_name2index -> idx: %d\r\n", idx) ); + + if (idx == 0) return make_error2(env, get_errno()); else - return make_ok2(env, idx); + return make_ok2(env, MKI(env, idx)); } @@ -1212,10 +1238,10 @@ ERL_NIF_TERM nif_index2name(ErlNifEnv* env, if (ifn == NULL) return enif_make_badarg(env); // PLACEHOLDER - if (NULL == if_indextoname(idx, ifn)) { + if (NULL != if_indextoname(idx, ifn)) { result = make_ok2(env, MKS(env, ifn)); } else { - result = make_error2(env, get_errno()); + result = make_error(env, atom_enxio); } FREE(ifn); @@ -1310,9 +1336,19 @@ static unsigned int nif_names_length(struct if_nameindex* p) { unsigned int len = 0; + BOOLEAN_T done = FALSE; + + while (!done) { + + NDBG( ("nif_names_length -> %d: " + "\r\n if_index: %d" + "\r\n if_name: 0x%lX" + "\r\n", len, p[len].if_index, p[len].if_name) ); - while ((p[len].if_index == 0) && (p[len].if_name == NULL)) { - len++; + if ((p[len].if_index == 0) && (p[len].if_name == NULL)) + done = TRUE; + else + len++; } return len; @@ -1627,9 +1663,13 @@ BOOLEAN_T decode_addrinfo_string(ErlNifEnv* env, result = FALSE; } - bufP = MALLOC(len); + NDBG( ("decode_addrinfo_string -> len: %d\r\n", len) ); + + bufP = MALLOC(len + 1); // We shall NULL-terminate - if (GET_STR(env, eString, bufP, len)) { + if (GET_STR(env, eString, bufP, len+1)) { + NDBG( ("decode_addrinfo_string -> buf: %s\r\n", bufP) ); + // bufP[len] = '\0'; *stringP = bufP; result = TRUE; } else { @@ -1673,19 +1713,32 @@ ERL_NIF_TERM encode_address_info(ErlNifEnv* env, ERL_NIF_TERM result; unsigned int len = address_info_length(addrInfo); - if (len > 0) { - ERL_NIF_TERM* array = MALLOC(len * sizeof(ERL_NIF_TERM)); - unsigned int i; + NDBG( ("encode_address_info -> len: %d\r\n", len) ); + if (len > 0) { + ERL_NIF_TERM* array = MALLOC(len * sizeof(ERL_NIF_TERM)); + unsigned int i = 0; + struct addrinfo* p = addrInfo; + + while (i < len) { + array[i] = make_address_info(env, p); + p = p->ai_next; + i++; + } + /* for (i = 0; i < len; i++) { array[i] = make_address_info(env, &addrInfo[i]); } + */ - result = make_ok2(env, MKLA(env, array, len)); + result = MKLA(env, array, len); } else { result = MKEL(env); } + NDBG( ("encode_address_info -> result: " + "\r\n %T\r\n", result) ); + return result; } @@ -1698,17 +1751,19 @@ ERL_NIF_TERM encode_address_info(ErlNifEnv* env, static unsigned int address_info_length(struct addrinfo* addrInfoP) { - unsigned int len; - struct addrinfo* tmp = addrInfoP; + unsigned int len = 1; + struct addrinfo* tmp; + BOOLEAN_T done = FALSE; - if (tmp != NULL) { - len = 1; - while (tmp->ai_next != NULL) { - tmp = tmp->ai_next; + tmp = addrInfoP; + + while (!done) { + if (tmp->ai_next != NULL) { len++; + tmp = tmp->ai_next; + } else { + done = TRUE; } - } else { - len = 0; } return len; @@ -1725,14 +1780,27 @@ static ERL_NIF_TERM make_address_info(ErlNifEnv* env, struct addrinfo* addrInfoP) { - ERL_NIF_TERM Fam = make_addrinfo_family(env, addrInfoP->ai_family); - ERL_NIF_TERM Type = make_addrinfo_type(env, addrInfoP->ai_socktype); - ERL_NIF_TERM Proto = make_addrinfo_proto(env, addrInfoP->ai_protocol); - ERL_NIF_TERM Addr = make_addrinfo_addr(env, - addrInfoP->ai_addr, - addrInfoP->ai_addrlen); + ERL_NIF_TERM result, fam, type, proto, addr; + + fam = make_addrinfo_family(env, addrInfoP->ai_family); + // NDBG( ("make_address_info -> fam: %T\r\n", fam) ); + type = make_addrinfo_type(env, addrInfoP->ai_socktype); + // NDBG( ("make_address_info -> type: %T\r\n", type) ); + proto = make_addrinfo_proto(env, addrInfoP->ai_protocol); + // NDBG( ("make_address_info -> proto: %T\r\n", proto) ); + addr = make_addrinfo_addr(env, + addrInfoP->ai_addr, + addrInfoP->ai_addrlen); + // NDBG( ("make_address_info -> addr: %T\r\n", addr) ); + + result = MKT5(env, atom_address_info, fam, type, proto, addr); - return MKT5(env, atom_address_info, Fam, Type, Proto, Addr); + /* + NDBG( ("make_address_info -> result: " + "\r\n %T\r\n", result) ); + */ + + return result; } @@ -1789,6 +1857,22 @@ ERL_NIF_TERM make_addrinfo_type(ErlNifEnv* env, etype = atom_dgram; break; + case SOCK_RAW: + etype = atom_raw; + break; + + case SOCK_RDM: + etype = atom_rdm; + break; + + case SOCK_SEQPACKET: + etype = atom_seqpacket; + break; + + case SOCK_DCCP: + etype = atom_dccp; + break; + default: etype = MKI(env, socktype); break; @@ -1850,11 +1934,16 @@ ERL_NIF_TERM make_addrinfo_addr(ErlNifEnv* env, ERL_NIF_TERM port, addr, eaddr; SockAddress* p = (SockAddress*) addrP; + NDBG( ("make_addrinfo_addr -> entry with" + "\r\n family: %d" + "\r\n addrLen: %d" + "\r\n", addrP->sa_family, addrLen) ); + switch (addrP->sa_family) { case AF_INET: { unsigned char* a = (unsigned char*) &p->in.sin_addr; - port = ntohs(p->in.sin_port); + port = MKI(env, ntohs(p->in.sin_port)); addr = MKT4(env, MKI(env, a[0]), MKI(env, a[1]), @@ -1868,7 +1957,7 @@ ERL_NIF_TERM make_addrinfo_addr(ErlNifEnv* env, case AF_INET6: { unsigned char* a = (unsigned char*) &p->in6.sin6_addr; - port = ntohs(p->in6.sin6_port); + port = MKI(env, ntohs(p->in6.sin6_port)); addr = MKT8(env, MKI(env, get_int16(a)), MKI(env, get_int16(&a[ 2])), @@ -1888,6 +1977,9 @@ ERL_NIF_TERM make_addrinfo_addr(ErlNifEnv* env, break; } + NDBG( ("make_addrinfo_addr -> eaddr: " + "\r\n %T\r\n", eaddr) ); + return eaddr; } @@ -1948,6 +2040,7 @@ ERL_NIF_TERM make_error1(ErlNifEnv* env, char* reason) static ERL_NIF_TERM make_error2(ErlNifEnv* env, int err) { + NDBG( ("make_error2 -> err: %d\r\n", err) ); return make_error1(env, erl_errno_id(err)); } @@ -2144,6 +2237,7 @@ int on_load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info) /* +++ Misc atoms +++ */ atom_address_info = MKA(env, str_address_info); + atom_dccp = MKA(env, str_dccp); atom_debug = MKA(env, str_debug); atom_dgram = MKA(env, str_dgram); atom_error = MKA(env, str_error); @@ -2160,6 +2254,9 @@ int on_load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info) atom_numerichost = MKA(env, str_numerichost); atom_numericserv = MKA(env, str_numericserv); atom_ok = MKA(env, str_ok); + atom_raw = MKA(env, str_raw); + atom_rdm = MKA(env, str_rdm); + atom_seqpacket = MKA(env, str_seqpacket); atom_stream = MKA(env, str_stream); atom_tcp = MKA(env, str_tcp); atom_true = MKA(env, str_true); @@ -2188,6 +2285,7 @@ int on_load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info) atom_enoname = MKA(env, str_enoname); // atom_enotclosing = MKA(env, str_enotclosing); // atom_enotconn = MKA(env, str_enotconn); + atom_enxio = MKA(env, str_enxio); atom_eoverflow = MKA(env, str_eoverflow); atom_eservice = MKA(env, str_eservice); atom_esocktype = MKA(env, str_esocktype); -- cgit v1.2.3 From ef34944c970f842a7406f59c827243c8be77fdc2 Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Tue, 12 Jun 2018 15:47:29 +0200 Subject: [net-nif] Fixed getnameinfo And now fixed the getnameinfo function. OTP-14831 --- erts/emulator/nifs/common/net_nif.c | 19 +++++++++++-------- erts/preloaded/ebin/net.beam | Bin 5892 -> 5980 bytes erts/preloaded/src/net.erl | 8 +++++--- 3 files changed, 16 insertions(+), 11 deletions(-) diff --git a/erts/emulator/nifs/common/net_nif.c b/erts/emulator/nifs/common/net_nif.c index 900828c7b2..e0129aec69 100644 --- a/erts/emulator/nifs/common/net_nif.c +++ b/erts/emulator/nifs/common/net_nif.c @@ -659,6 +659,7 @@ static ErlNifResourceTypeInit netInit = { * * The "proper" net functions: * ------------------------------ + * nif_gethostname/0 * nif_getnameinfo/2 * nif_getaddrinfo/3 * nif_if_name2index/1 @@ -860,7 +861,6 @@ ERL_NIF_TERM ngethostname(ErlNifEnv* env) * Arguments: * SockAddr - Socket Address (address and port) * Flags - The flags argument modifies the behavior of getnameinfo(). - * Not used! */ static @@ -869,33 +869,33 @@ ERL_NIF_TERM nif_getnameinfo(ErlNifEnv* env, const ERL_NIF_TERM argv[]) { ERL_NIF_TERM result, eSockAddr; - unsigned int eFlags; + ERL_NIF_TERM eFlags; int flags = 0; // Just in case... SockAddress sa; SOCKLEN_T saLen = 0; // Just in case... NDBG( ("nif_getnameinfo -> entry (%d)\r\n", argc) ); - if ((argc != 2) || - !GET_UINT(env, argv[1], &eFlags)) { + if (argc != 2) return enif_make_badarg(env); - } eSockAddr = argv[0]; + eFlags = argv[1]; NDBG( ("nif_getnameinfo -> " "\r\n SockAddr: %T" "\r\n Flags: %T" - "\r\n", argv[0], argv[1]) ); + "\r\n", eSockAddr, eFlags) ); if (!decode_nameinfo_flags(env, eFlags, &flags)) return enif_make_badarg(env); - if (decode_in_sockaddr(env, eSockAddr, &sa, &saLen)) + if (!decode_in_sockaddr(env, eSockAddr, &sa, &saLen)) return enif_make_badarg(env); result = ngetnameinfo(env, &sa, saLen, flags); - NDBG( ("nif_getnameinfo -> done when result: %T\r\n", result) ); + NDBG( ("nif_getnameinfo -> done when result: " + "\r\n %T\r\n", result) ); return result; } @@ -922,6 +922,8 @@ ERL_NIF_TERM ngetnameinfo(ErlNifEnv* env, serv, servLen, flags); + NDBG( ("ngetnameinfo -> res: %d\r\n", res) ); + switch (res) { case 0: { @@ -1569,6 +1571,7 @@ BOOLEAN_T decode_nameinfo_flags(ErlNifEnv* env, result = FALSE; } } else if (IS_LIST(env, eflags)) { + NDBG( ("decode_nameinfo_flags -> is atom\r\n") ); result = decode_nameinfo_flags_list(env, eflags, flags); } else { result = FALSE; diff --git a/erts/preloaded/ebin/net.beam b/erts/preloaded/ebin/net.beam index c83cafe209..66391317a2 100644 Binary files a/erts/preloaded/ebin/net.beam and b/erts/preloaded/ebin/net.beam differ diff --git a/erts/preloaded/src/net.erl b/erts/preloaded/src/net.erl index 823f11e2b8..29739f4510 100644 --- a/erts/preloaded/src/net.erl +++ b/erts/preloaded/src/net.erl @@ -206,18 +206,20 @@ gethostname() -> getnameinfo(SockAddr) when is_record(SockAddr, in4_sockaddr) orelse is_record(SockAddr, in6_sockaddr) -> - getnameinfo(SockAddr, []). + getnameinfo(SockAddr, undefined). -spec getnameinfo(SockAddr, Flags) -> {ok, Info} | {error, Reason} when SockAddr :: in_sockaddr(), - Flags :: name_info_flags(), + Flags :: name_info_flags() | undefined, Info :: name_info(), Reason :: term(). +getnameinfo(SockAddr, [] = _Flags) -> + getnameinfo(SockAddr, undefined); getnameinfo(SockAddr, Flags) when (is_record(SockAddr, in4_sockaddr) orelse is_record(SockAddr, in6_sockaddr)) andalso - is_list(Flags) -> + (is_list(Flags) orelse (Flags =:= undefined)) -> nif_getnameinfo(SockAddr, Flags). -- cgit v1.2.3 From 1b31432a2c60364dc3e7b2a18fa8494475344271 Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Fri, 15 Jun 2018 18:51:48 +0200 Subject: [socket+net-nif] Introduced a couple of common files Started to move the common stuff, such as common utility functions, debug and encode / decode of basic types. OTP-14831 --- erts/emulator/Makefile.in | 7 +- erts/emulator/nifs/common/net_nif.c | 511 ++++++++++---- erts/emulator/nifs/common/socket_dbg.c | 116 ++++ erts/emulator/nifs/common/socket_dbg.h | 41 ++ erts/emulator/nifs/common/socket_int.h | 193 ++++++ erts/emulator/nifs/common/socket_nif.c | 1100 +++++++++++++++++++------------ erts/emulator/nifs/common/socket_util.c | 659 ++++++++++++++++++ erts/emulator/nifs/common/socket_util.h | 108 +++ erts/preloaded/src/Makefile | 2 +- erts/preloaded/src/net.erl | 87 +-- erts/preloaded/src/socket.erl | 209 ++++-- 11 files changed, 2372 insertions(+), 661 deletions(-) create mode 100644 erts/emulator/nifs/common/socket_dbg.c create mode 100644 erts/emulator/nifs/common/socket_dbg.h create mode 100644 erts/emulator/nifs/common/socket_int.h create mode 100644 erts/emulator/nifs/common/socket_util.c create mode 100644 erts/emulator/nifs/common/socket_util.h diff --git a/erts/emulator/Makefile.in b/erts/emulator/Makefile.in index 3bfe393a12..60f9b36491 100644 --- a/erts/emulator/Makefile.in +++ b/erts/emulator/Makefile.in @@ -637,12 +637,12 @@ GENERATE += $(TTF_DIR)/driver_tab.c PRELOAD_BEAM = $(ERL_TOP)/erts/preloaded/ebin/otp_ring0.beam \ $(ERL_TOP)/erts/preloaded/ebin/erts_code_purger.beam \ $(ERL_TOP)/erts/preloaded/ebin/init.beam \ - $(ERL_TOP)/erts/preloaded/ebin/net.beam \ $(ERL_TOP)/erts/preloaded/ebin/prim_buffer.beam \ $(ERL_TOP)/erts/preloaded/ebin/prim_eval.beam \ $(ERL_TOP)/erts/preloaded/ebin/prim_inet.beam \ $(ERL_TOP)/erts/preloaded/ebin/prim_file.beam \ $(ERL_TOP)/erts/preloaded/ebin/socket.beam \ + $(ERL_TOP)/erts/preloaded/ebin/net.beam \ $(ERL_TOP)/erts/preloaded/ebin/zlib.beam \ $(ERL_TOP)/erts/preloaded/ebin/prim_zip.beam \ $(ERL_TOP)/erts/preloaded/ebin/erl_prim_loader.beam \ @@ -863,7 +863,7 @@ RUN_OBJS += \ $(OBJDIR)/register.o $(OBJDIR)/break.o \ $(OBJDIR)/erl_async.o $(OBJDIR)/erl_lock_check.o \ $(OBJDIR)/erl_gc.o $(OBJDIR)/erl_lock_count.o \ - $(OBJDIR)/erl_posix_str.o \ + $(OBJDIR)/erl_posix_str.o \ $(OBJDIR)/erl_bits.o $(OBJDIR)/erl_math.o \ $(OBJDIR)/erl_fun.o $(OBJDIR)/erl_bif_port.o \ $(OBJDIR)/erl_term.o $(OBJDIR)/erl_node_tables.o \ @@ -877,7 +877,8 @@ RUN_OBJS += \ $(OBJDIR)/erl_thr_queue.o $(OBJDIR)/erl_sched_spec_pre_alloc.o \ $(OBJDIR)/erl_ptab.o $(OBJDIR)/erl_map.o \ $(OBJDIR)/erl_msacc.o $(OBJDIR)/erl_lock_flags.o \ - $(OBJDIR)/erl_io_queue.o + $(OBJDIR)/erl_io_queue.o \ + $(OBJDIR)/socket_dbg.o $(OBJDIR)/socket_util.o LTTNG_OBJS = $(OBJDIR)/erlang_lttng.o NIF_OBJS = \ diff --git a/erts/emulator/nifs/common/net_nif.c b/erts/emulator/nifs/common/net_nif.c index e0129aec69..90263d11c2 100644 --- a/erts/emulator/nifs/common/net_nif.c +++ b/erts/emulator/nifs/common/net_nif.c @@ -174,6 +174,9 @@ #include +#include "socket_dbg.h" +#include "socket_int.h" + /* All platforms fail on malloc errors. */ #define FATAL_MALLOC @@ -263,66 +266,67 @@ typedef unsigned long long llu_t; * * * =================================================================== */ -#define MALLOC(SZ) enif_alloc((SZ)) -#define FREE(P) enif_free((P)) - -#define MKA(E,S) enif_make_atom((E), (S)) -#define MKBIN(E,B) enif_make_binary((E), (B)) -#define MKI(E,I) enif_make_int((E), (I)) -#define MKLA(E,A,L) enif_make_list_from_array((E), (A), (L)) -#define MKEL(E) enif_make_list((E), 0) -#define MKREF(E) enif_make_ref((E)) -#define MKS(E,S) enif_make_string((E), (S), ERL_NIF_LATIN1) -#define MKSL(E,S,L) enif_make_string_len((E), (S), (L), ERL_NIF_LATIN1) -#define MKSBIN(E,B,ST,SZ) enif_make_sub_binary((E), (B), (ST), (SZ)) -#define MKT2(E,E1,E2) enif_make_tuple2((E), (E1), (E2)) -#define MKT3(E,E1,E2,E3) enif_make_tuple3((E), (E1), (E2), (E3)) -#define MKT4(E,E1,E2,E3,E4) enif_make_tuple4((E), (E1), (E2), (E3), (E4)) -#define MKT5(E,E1,E2,E3,E4,E5) \ - enif_make_tuple5((E), (E1), (E2), (E3), (E4), (E5)) -#define MKT8(E,E1,E2,E3,E4,E5,E6,E7,E8) \ - enif_make_tuple8((E), (E1), (E2), (E3), (E4), (E5), (E6), (E7), (E8)) - -#define MCREATE(N) enif_mutex_create((N)) -#define MDESTROY(M) enif_mutex_destroy((M)) -#define MLOCK(M) enif_mutex_lock((M)) -#define MUNLOCK(M) enif_mutex_unlock((M)) - -#define MONP(E,D,P,M) enif_monitor_process((E), (D), (P), (M)) -#define DEMONP(E,D,M) enif_demonitor_process((E), (D), (M)) - -#define SELECT(E,FD,M,O,P,R) \ - enif_select((E), (FD), (M), (O), (P), (R)) -#define SELECT_READ(E, DP, P, R) \ - SELECT((E), (DP)->sock, (ERL_NIF_SELECT_READ), (DP), (P), (R)) -#define SELECT_WRITE(E, DP, P, R) \ - SELECT((E), (DP)->sock, (ERL_NIF_SELECT_WRITE), (DP), (P), (R)) -#define SELECT_STOP(E, DP) \ - enif_select((E), (DP)->sock, (ERL_NIF_SELECT_STOP), (DP), NULL, atom_undefined) - -#define IS_ATOM(E, TE) enif_is_atom((E), (TE)) -#define IS_BIN(E, TE) enif_is_binary((E), (TE)) -#define IS_NUM(E, TE) enif_is_number((E), (TE)) -#define IS_TUPLE(E, TE) enif_is_tuple((E), (TE)) -#define IS_LIST(E, TE) enif_is_list((E), (TE)) - -#define COMPARE(L, R) enif_compare((L), (R)) - -#define GET_ATOM_LEN(E, TE, LP) \ - enif_get_atom_length((E), (TE), (LP), ERL_NIF_LATIN1) -#define GET_ATOM(E, TE, BP, MAX) \ - enif_get_atom((E), (TE), (BP), (MAX), ERL_NIF_LATIN1) -#define GET_BIN(E, TE, BP) enif_inspect_iolist_as_binary((E), (TE), (BP)) -#define GET_INT(E, TE, IP) enif_get_int((E), (TE), (IP)) -#define GET_LIST_ELEM(E, L, HP, TP) enif_get_list_cell((E), (L), (HP), (TP)) -#define GET_LIST_LEN(E, L, LP) enif_get_list_length((E), (L), (LP)) -#define GET_STR(E, L, B, SZ) \ - enif_get_string((E), (L), (B), (SZ), ERL_NIF_LATIN1) -#define GET_UINT(E, TE, IP) enif_get_uint((E), (TE), (IP)) -#define GET_TUPLE(E, TE, TSZ, TA) enif_get_tuple((E), (TE), (TSZ), (TA)) - -#define ALLOC_BIN(SZ, BP) enif_alloc_binary((SZ), (BP)) -#define REALLOC_BIN(SZ, BP) enif_realloc_binary((SZ), (BP)) +/* #define MALLOC(SZ) enif_alloc((SZ)) */ +/* #define FREE(P) enif_free((P)) */ + +/* #define MKA(E,S) enif_make_atom((E), (S)) */ +/* #define MKBIN(E,B) enif_make_binary((E), (B)) */ +/* #define MKI(E,I) enif_make_int((E), (I)) */ +/* #define MKLA(E,A,L) enif_make_list_from_array((E), (A), (L)) */ +/* #define MKEL(E) enif_make_list((E), 0) */ +/* #define MKREF(E) enif_make_ref((E)) */ +/* #define MKS(E,S) enif_make_string((E), (S), ERL_NIF_LATIN1) */ +/* #define MKSL(E,S,L) enif_make_string_len((E), (S), (L), ERL_NIF_LATIN1) */ +/* #define MKSBIN(E,B,ST,SZ) enif_make_sub_binary((E), (B), (ST), (SZ)) */ +/* #define MKT2(E,E1,E2) enif_make_tuple2((E), (E1), (E2)) */ +/* #define MKT3(E,E1,E2,E3) enif_make_tuple3((E), (E1), (E2), (E3)) */ +/* #define MKT4(E,E1,E2,E3,E4) enif_make_tuple4((E), (E1), (E2), (E3), (E4)) */ +/* #define MKT5(E,E1,E2,E3,E4,E5) \ */ +/* enif_make_tuple5((E), (E1), (E2), (E3), (E4), (E5)) */ +/* #define MKT8(E,E1,E2,E3,E4,E5,E6,E7,E8) \ */ +/* enif_make_tuple8((E), (E1), (E2), (E3), (E4), (E5), (E6), (E7), (E8)) */ +/* #define MKTA(E, A, AL) enif_make_tuple_from_array((E), (A), (AL)) */ + +/* #define MCREATE(N) enif_mutex_create((N)) */ +/* #define MDESTROY(M) enif_mutex_destroy((M)) */ +/* #define MLOCK(M) enif_mutex_lock((M)) */ +/* #define MUNLOCK(M) enif_mutex_unlock((M)) */ + +/* #define MONP(E,D,P,M) enif_monitor_process((E), (D), (P), (M)) */ +/* #define DEMONP(E,D,M) enif_demonitor_process((E), (D), (M)) */ + +/* #define SELECT(E,FD,M,O,P,R) \ */ +/* enif_select((E), (FD), (M), (O), (P), (R)) */ +/* #define SELECT_READ(E, DP, P, R) \ */ +/* SELECT((E), (DP)->sock, (ERL_NIF_SELECT_READ), (DP), (P), (R)) */ +/* #define SELECT_WRITE(E, DP, P, R) \ */ +/* SELECT((E), (DP)->sock, (ERL_NIF_SELECT_WRITE), (DP), (P), (R)) */ +/* #define SELECT_STOP(E, DP) \ */ +/* enif_select((E), (DP)->sock, (ERL_NIF_SELECT_STOP), (DP), NULL, atom_undefined) */ + +/* #define IS_ATOM(E, TE) enif_is_atom((E), (TE)) */ +/* #define IS_BIN(E, TE) enif_is_binary((E), (TE)) */ +/* #define IS_NUM(E, TE) enif_is_number((E), (TE)) */ +/* #define IS_TUPLE(E, TE) enif_is_tuple((E), (TE)) */ +/* #define IS_LIST(E, TE) enif_is_list((E), (TE)) */ + +/* #define COMPARE(L, R) enif_compare((L), (R)) */ + +/* #define GET_ATOM_LEN(E, TE, LP) \ */ +/* enif_get_atom_length((E), (TE), (LP), ERL_NIF_LATIN1) */ +/* #define GET_ATOM(E, TE, BP, MAX) \ */ +/* enif_get_atom((E), (TE), (BP), (MAX), ERL_NIF_LATIN1) */ +/* #define GET_BIN(E, TE, BP) enif_inspect_iolist_as_binary((E), (TE), (BP)) */ +/* #define GET_INT(E, TE, IP) enif_get_int((E), (TE), (IP)) */ +/* #define GET_LIST_ELEM(E, L, HP, TP) enif_get_list_cell((E), (L), (HP), (TP)) */ +/* #define GET_LIST_LEN(E, L, LP) enif_get_list_length((E), (L), (LP)) */ +/* #define GET_STR(E, L, B, SZ) \ */ +/* enif_get_string((E), (L), (B), (SZ), ERL_NIF_LATIN1) */ +/* #define GET_UINT(E, TE, IP) enif_get_uint((E), (TE), (IP)) */ +/* #define GET_TUPLE(E, TE, TSZ, TA) enif_get_tuple((E), (TE), (TSZ), (TA)) */ + +/* #define ALLOC_BIN(SZ, BP) enif_alloc_binary((SZ), (BP)) */ +/* #define REALLOC_BIN(SZ, BP) enif_realloc_binary((SZ), (BP)) */ #ifdef HAVE_SOCKLEN_T @@ -342,12 +346,16 @@ typedef unsigned long long llu_t; typedef union { struct sockaddr sa; - struct sockaddr_in in; + struct sockaddr_in in4; #ifdef HAVE_IN6 struct sockaddr_in6 in6; #endif +#ifdef HAVE_SYS_UN_H + struct sockaddr_un un; +#endif + } SockAddress; typedef struct { @@ -440,6 +448,22 @@ static BOOLEAN_T decode_in6_sockaddr(ErlNifEnv* env, SockAddress* saP, SOCKLEN_T* saLen); #endif +static ERL_NIF_TERM encode_in_sockaddr(ErlNifEnv* env, + struct sockaddr* addrP, + SOCKLEN_T addrLen); +static ERL_NIF_TERM encode_in4_sockaddr(ErlNifEnv* env, + struct sockaddr_in* addrP, + SOCKLEN_T addrLen); +#if defined(HAVE_IN6) && defined(AF_INET6) +static ERL_NIF_TERM encode_in6_sockaddr(ErlNifEnv* env, + struct sockaddr_in6* addrP, + SOCKLEN_T addrLen); +#endif +#ifdef HAVE_SYS_UN_H +static ERL_NIF_TERM encode_un_sockaddr(ErlNifEnv* env, + struct sockaddr_un* addrP, + SOCKLEN_T addrLen); +#endif static BOOLEAN_T decode_nameinfo_flags(ErlNifEnv* env, const ERL_NIF_TERM eflags, int* flags); @@ -453,28 +477,50 @@ BOOLEAN_T decode_addrinfo_string(ErlNifEnv* env, static ERL_NIF_TERM decode_bool(ErlNifEnv* env, ERL_NIF_TERM eBool, BOOLEAN_T* bool); +static ERL_NIF_TERM encode_address_infos(ErlNifEnv* env, + struct addrinfo* addrInfo); static ERL_NIF_TERM encode_address_info(ErlNifEnv* env, - struct addrinfo* addrInfo); + struct addrinfo* addrInfoP); static unsigned int address_info_length(struct addrinfo* addrInfoP); -static ERL_NIF_TERM make_address_info(ErlNifEnv* env, - struct addrinfo* addrInfoP); -static ERL_NIF_TERM make_addrinfo_family(ErlNifEnv* env, +static ERL_NIF_TERM encode_address_info_family(ErlNifEnv* env, int family); -static ERL_NIF_TERM make_addrinfo_type(ErlNifEnv* env, +static ERL_NIF_TERM encode_address_info_type(ErlNifEnv* env, int socktype); -static ERL_NIF_TERM make_addrinfo_proto(ErlNifEnv* env, +static ERL_NIF_TERM encode_address_info_proto(ErlNifEnv* env, int proto); -static ERL_NIF_TERM make_addrinfo_addr(ErlNifEnv* env, - struct sockaddr* addrP, - SOCKLEN_T addrLen); +/* static ERL_NIF_TERM make_address_info(ErlNifEnv* env, */ +/* struct addrinfo* addrInfoP); */ +/* static ERL_NIF_TERM make_addrinfo_addr(ErlNifEnv* env, */ +/* struct sockaddr* addrP, */ +/* SOCKLEN_T addrLen); */ + +static ERL_NIF_TERM make_in4_sockaddr(ErlNifEnv* env, + ERL_NIF_TERM port, + ERL_NIF_TERM addr); +#if defined(HAVE_IN6) && defined(AF_INET6) +static ERL_NIF_TERM make_in6_sockaddr(ErlNifEnv* env, + ERL_NIF_TERM port, + ERL_NIF_TERM addr, + ERL_NIF_TERM flowInfo, + ERL_NIF_TERM scopeId); +#endif +static ERL_NIF_TERM make_address_info(ErlNifEnv* env, + ERL_NIF_TERM fam, + ERL_NIF_TERM sockType, + ERL_NIF_TERM proto, + ERL_NIF_TERM addr); static ERL_NIF_TERM make_ok2(ErlNifEnv* env, ERL_NIF_TERM val); // static ERL_NIF_TERM make_ok3(ErlNifEnv* env, ERL_NIF_TERM val1, ERL_NIF_TERM val2); static ERL_NIF_TERM make_error(ErlNifEnv* env, ERL_NIF_TERM reason); static ERL_NIF_TERM make_error1(ErlNifEnv* env, char* reason); static ERL_NIF_TERM make_error2(ErlNifEnv* env, int err); +#if defined(HAVE_SYS_UN_H) || defined(SO_BINDTODEVICE) +static size_t my_strnlen(const char *s, size_t maxlen); +#endif + static void dbg_printf( const char* format, ... ); static int dbg_realtime(struct timespec* tsP); static int dbg_timespec2str(char *buf, unsigned int len, struct timespec *ts); @@ -522,9 +568,12 @@ static char str_false[] = "false"; static char str_idn[] = "idn"; static char str_idna_allow_unassigned[] = "idna_allow_unassigned"; static char str_idna_use_std3_ascii_rules[] = "idna_use_std3_ascii_rules"; +static char str_in4_sockaddr[] = "in4_sockaddr"; +static char str_in6_sockaddr[] = "in6_sockaddr"; static char str_inet[] = "inet"; static char str_inet6[] = "inet6"; static char str_ip[] = "ip"; +static char str_ipv6[] = "ipv6"; static char str_namereqd[] = "namereqd"; static char str_name_info[] = "name_info"; static char str_nofqdn[] = "nofqdn"; @@ -585,9 +634,12 @@ static ERL_NIF_TERM atom_false; static ERL_NIF_TERM atom_idn; static ERL_NIF_TERM atom_idna_allow_unassigned; static ERL_NIF_TERM atom_idna_use_std3_ascii_rules; +static ERL_NIF_TERM atom_in4_sockaddr; +static ERL_NIF_TERM atom_in6_sockaddr; static ERL_NIF_TERM atom_inet; static ERL_NIF_TERM atom_inet6; static ERL_NIF_TERM atom_ip; +static ERL_NIF_TERM atom_ipv6; static ERL_NIF_TERM atom_namereqd; static ERL_NIF_TERM atom_name_info; static ERL_NIF_TERM atom_nofqdn; @@ -1009,9 +1061,9 @@ ERL_NIF_TERM nif_getaddrinfo(ErlNifEnv* env, // eHints = argv[2]; NDBG( ("nif_getaddrinfo -> " - "\r\n Host: %T" - "\r\n Service: %T" - "\r\n Hints: %T" + "\r\n ehost: %T" + "\r\n eservice: %T" + "\r\n ehints: %T" "\r\n", argv[0], argv[1], argv[2]) ); if (!decode_addrinfo_string(env, eHostName, &hostName)) @@ -1071,7 +1123,7 @@ ERL_NIF_TERM ngetaddrinfo(ErlNifEnv* env, switch (res) { case 0: { - ERL_NIF_TERM addrInfo = encode_address_info(env, addrInfoP); + ERL_NIF_TERM addrInfo = encode_address_infos(env, addrInfoP); freeaddrinfo(addrInfoP); result = make_ok2(env, addrInfo); } @@ -1454,14 +1506,14 @@ BOOLEAN_T decode_in4_sockaddr(ErlNifEnv* env, /* And finally initialize the sockaddr structure (and size) */ sys_memzero((char*)saP, sizeof(struct sockaddr_in)); - saP->in.sin_family = AF_INET; - saP->in.sin_port = htons(port); + saP->in4.sin_family = AF_INET; + saP->in4.sin_port = htons(port); for (a = 0; a < 4; a++) { if (!GET_INT(env, ipAddrT[a], &v)) return FALSE; addr[a] = v; } - sys_memcpy(&saP->in.sin_addr, &addr, sizeof(addr)); + sys_memcpy(&saP->in4.sin_addr, &addr, sizeof(addr)); *saLen = sizeof(struct sockaddr_in); return TRUE; } @@ -1552,6 +1604,168 @@ BOOLEAN_T decode_in6_sockaddr(ErlNifEnv* env, +static +ERL_NIF_TERM encode_in_sockaddr(ErlNifEnv* env, + struct sockaddr* addrP, + SOCKLEN_T addrLen) +{ + SockAddress* sockAddrP = (SockAddress*) addrP; + ERL_NIF_TERM sockAddr; + + switch (sockAddrP->sa.sa_family) { + case AF_INET: + sockAddr = encode_in4_sockaddr(env, &sockAddrP->in4, addrLen); + break; + +#if defined(HAVE_IN6) && defined(AF_INET6) + case AF_INET6: + sockAddr = encode_in6_sockaddr(env, &sockAddrP->in6, addrLen); + break; +#endif + +#ifdef HAVE_SYS_UN_H + case AF_UNIX: + sockAddr = encode_un_sockaddr(env, &sockAddrP->un, addrLen); + break; +#endif + + default: + sockAddr = atom_undefined; + break; + } + + return sockAddr; +} + + + +/* Encode an IPv4 socket address; in4_sockaddr */ +static +ERL_NIF_TERM encode_in4_sockaddr(ErlNifEnv* env, + struct sockaddr_in* addrP, + SOCKLEN_T addrLen) +{ + ERL_NIF_TERM sockAddr; + short port; + ERL_NIF_TERM ePort, eAddr; + + + if (addrLen >= sizeof(struct sockaddr_in)) { + unsigned int i; + ERL_NIF_TERM at[4]; + unsigned int atLen = sizeof(at) / sizeof(ERL_NIF_TERM); + unsigned char* a = (unsigned char*) &addrP->sin_addr; + + /* The port */ + port = ntohs(addrP->sin_port); + ePort = MKI(env, port); + + /* The address */ + for (i = 0; i < atLen; i++) { + at[i] = MKI(env, a[i]); + } + eAddr = MKTA(env, at, atLen); + // eAddr = MKT4(env, at[0], at[1], at[2], at[3]); + + /* And finally construct the in4_sockaddr record */ + sockAddr = make_in4_sockaddr(env, ePort, eAddr); + + } else { + sockAddr = atom_undefined; + } + + return sockAddr; +} + + + +/* Encode an IPv6 socket address; in6_sockaddr */ +#if defined(HAVE_IN6) && defined(AF_INET6) +static +ERL_NIF_TERM encode_in6_sockaddr(ErlNifEnv* env, + struct sockaddr_in6* addrP, + SOCKLEN_T addrLen) +{ + ERL_NIF_TERM sockAddr; + short port; + ERL_NIF_TERM ePort, eAddr, eFlowInfo, eScopeId; + + + if (addrLen >= sizeof(struct sockaddr_in6)) { + unsigned int i; + ERL_NIF_TERM at[8]; + unsigned int atLen = sizeof(at) / sizeof(ERL_NIF_TERM); + unsigned char* a = (unsigned char*) &addrP->sin6_addr; + + /* The port */ + port = ntohs(addrP->sin6_port); + ePort = MKI(env, port); + + /* The address */ + for (i = 0; i < atLen; i++) { + at[i] = MKI(env, get_int16(a + i*2)); + } + eAddr = MKTA(env, at, atLen); + + /* The flowInfo */ + eFlowInfo = MKI(env, addrP->sin6_flowinfo); + + /* The scopeId */ + eScopeId = MKI(env, addrP->sin6_scope_id); + + /* And finally construct the in6_sockaddr record */ + sockAddr = make_in6_sockaddr(env, ePort, eAddr, eFlowInfo, eScopeId); + + } else { + sockAddr = atom_undefined; + } + + return sockAddr; +} +#endif + + + +/* Encode an Unix Domain socket address: string() */ +#ifdef HAVE_SYS_UN_H +static +ERL_NIF_TERM encode_un_sockaddr(ErlNifEnv* env, + struct sockaddr_un* addrP, + SOCKLEN_T addrLen) +{ + ERL_NIF_TERM sockAddr; + size_t n, m; + + if (addrLen >= offsetof(struct sockaddr_un, sun_path)) { + n = addrLen - offsetof(struct sockaddr_un, sun_path); + if (255 < n) { + sockAddr = atom_undefined; + } else { + m = my_strnlen(addrP->sun_path, n); +#ifdef __linux__ + /* Assume that the address is a zero terminated string, + * except when the first byte is \0 i.e the string length is 0, + * then use the reported length instead. + * This fix handles Linux's nonportable + * abstract socket address extension. + */ + if (m == 0) { + m = n; + } +#endif + + sockAddr = MKSL(env, addrP->sun_path, m); + } + } else { + sockAddr = atom_undefined; + } + + return sockAddr; +} +#endif + + + /* The erlang format for a set of flags is a list of atoms. * A special case is when there is no flags, which is * represented by the atom undefined. @@ -1710,13 +1924,13 @@ ERL_NIF_TERM decode_bool(ErlNifEnv* env, * will result in the result being a list of zero or more length. */ static -ERL_NIF_TERM encode_address_info(ErlNifEnv* env, - struct addrinfo* addrInfo) +ERL_NIF_TERM encode_address_infos(ErlNifEnv* env, + struct addrinfo* addrInfo) { ERL_NIF_TERM result; unsigned int len = address_info_length(addrInfo); - NDBG( ("encode_address_info -> len: %d\r\n", len) ); + NDBG( ("encode_address_infos -> len: %d\r\n", len) ); if (len > 0) { ERL_NIF_TERM* array = MALLOC(len * sizeof(ERL_NIF_TERM)); @@ -1724,22 +1938,17 @@ ERL_NIF_TERM encode_address_info(ErlNifEnv* env, struct addrinfo* p = addrInfo; while (i < len) { - array[i] = make_address_info(env, p); + array[i] = encode_address_info(env, p); p = p->ai_next; i++; } - /* - for (i = 0; i < len; i++) { - array[i] = make_address_info(env, &addrInfo[i]); - } - */ result = MKLA(env, array, len); } else { result = MKEL(env); } - NDBG( ("encode_address_info -> result: " + NDBG( ("encode_address_infos -> result: " "\r\n %T\r\n", result) ); return result; @@ -1780,28 +1989,17 @@ unsigned int address_info_length(struct addrinfo* addrInfoP) * {address_info, Fam, Type, Proto, Addr} */ static -ERL_NIF_TERM make_address_info(ErlNifEnv* env, - struct addrinfo* addrInfoP) +ERL_NIF_TERM encode_address_info(ErlNifEnv* env, + struct addrinfo* addrInfoP) { ERL_NIF_TERM result, fam, type, proto, addr; - fam = make_addrinfo_family(env, addrInfoP->ai_family); - // NDBG( ("make_address_info -> fam: %T\r\n", fam) ); - type = make_addrinfo_type(env, addrInfoP->ai_socktype); - // NDBG( ("make_address_info -> type: %T\r\n", type) ); - proto = make_addrinfo_proto(env, addrInfoP->ai_protocol); - // NDBG( ("make_address_info -> proto: %T\r\n", proto) ); - addr = make_addrinfo_addr(env, - addrInfoP->ai_addr, - addrInfoP->ai_addrlen); - // NDBG( ("make_address_info -> addr: %T\r\n", addr) ); + fam = encode_address_info_family(env, addrInfoP->ai_family); + type = encode_address_info_type(env, addrInfoP->ai_socktype); + proto = encode_address_info_proto(env, addrInfoP->ai_protocol); + addr = encode_in_sockaddr(env, addrInfoP->ai_addr, addrInfoP->ai_addrlen); - result = MKT5(env, atom_address_info, fam, type, proto, addr); - - /* - NDBG( ("make_address_info -> result: " - "\r\n %T\r\n", result) ); - */ + result = make_address_info(env, fam, type, proto, addr); return result; @@ -1814,23 +2012,29 @@ ERL_NIF_TERM make_address_info(ErlNifEnv* env, * in the form of an integer. */ static -ERL_NIF_TERM make_addrinfo_family(ErlNifEnv* env, - int family) +ERL_NIF_TERM encode_address_info_family(ErlNifEnv* env, + int family) { ERL_NIF_TERM efam; switch (family) { case AF_INET: - efam = atom_inet; + efam = esock_atom_inet; break; #if defined(HAVE_IN6) && defined(AF_INET6) case AF_INET6: - efam = atom_inet6; + efam = esock_atom_inet6; break; #endif - default: + #ifdef HAVE_SYS_UN_H + case AF_UNIX: + efam = esock_atom_local; + break; +#endif + + default: efam = MKI(env, family); break; } @@ -1846,8 +2050,8 @@ ERL_NIF_TERM make_addrinfo_family(ErlNifEnv* env, * in the form of an integer. */ static -ERL_NIF_TERM make_addrinfo_type(ErlNifEnv* env, - int socktype) +ERL_NIF_TERM encode_address_info_type(ErlNifEnv* env, + int socktype) { ERL_NIF_TERM etype; @@ -1872,10 +2076,6 @@ ERL_NIF_TERM make_addrinfo_type(ErlNifEnv* env, etype = atom_seqpacket; break; - case SOCK_DCCP: - etype = atom_dccp; - break; - default: etype = MKI(env, socktype); break; @@ -1892,8 +2092,8 @@ ERL_NIF_TERM make_addrinfo_type(ErlNifEnv* env, * in the form of an integer. */ static -ERL_NIF_TERM make_addrinfo_proto(ErlNifEnv* env, - int proto) +ERL_NIF_TERM encode_address_info_proto(ErlNifEnv* env, + int proto) { ERL_NIF_TERM eproto; @@ -1906,6 +2106,12 @@ ERL_NIF_TERM make_addrinfo_proto(ErlNifEnv* env, eproto = atom_ip; break; +#if defined(SOL_IPV6) + case SOL_IPV6: + eproto = atom_ipv6; + break; +#endif + case IPPROTO_TCP: eproto = atom_tcp; break; @@ -1914,6 +2120,12 @@ ERL_NIF_TERM make_addrinfo_proto(ErlNifEnv* env, eproto = atom_udp; break; +#if defined(HAVE_SCTP) + case IPPROTO_SCTP: + eproto = atom_sctp; + break; +#endif + default: eproto = MKI(env, proto); break; @@ -1929,15 +2141,16 @@ ERL_NIF_TERM make_addrinfo_proto(ErlNifEnv* env, * IPv4 and IPv6 addresses. Values of other families will be * returned as an undefined. */ +/* static -ERL_NIF_TERM make_addrinfo_addr(ErlNifEnv* env, - struct sockaddr* addrP, - SOCKLEN_T addrLen) +ERL_NIF_TERM encode_address_info_addr(ErlNifEnv* env, + struct sockaddr* addrP, + SOCKLEN_T addrLen) { ERL_NIF_TERM port, addr, eaddr; SockAddress* p = (SockAddress*) addrP; - NDBG( ("make_addrinfo_addr -> entry with" + NDBG( ("encode_address_info_addr -> entry with" "\r\n family: %d" "\r\n addrLen: %d" "\r\n", addrP->sa_family, addrLen) ); @@ -1985,6 +2198,57 @@ ERL_NIF_TERM make_addrinfo_addr(ErlNifEnv* env, return eaddr; } +*/ + + + +#if defined(HAVE_SYS_UN_H) || defined(SO_BINDTODEVICE) +/* strnlen doesn't exist everywhere */ +static +size_t my_strnlen(const char *s, size_t maxlen) +{ + size_t i = 0; + while (i < maxlen && s[i] != '\0') + i++; + return i; +} +#endif + + + +/* Construct the IPv4 socket address record: in4_sockaddr */ +static +ERL_NIF_TERM make_in4_sockaddr(ErlNifEnv* env, + ERL_NIF_TERM port, + ERL_NIF_TERM addr) +{ + return MKT3(env, atom_in4_sockaddr, port, addr); +} + + + +/* Construct the IPv6 socket address record: in6_sockaddr */ +static +ERL_NIF_TERM make_in6_sockaddr(ErlNifEnv* env, + ERL_NIF_TERM port, + ERL_NIF_TERM addr, + ERL_NIF_TERM flowInfo, + ERL_NIF_TERM scopeId) +{ + return MKT5(env, atom_in6_sockaddr, port, addr, flowInfo, scopeId); +} + + + +static +ERL_NIF_TERM make_address_info(ErlNifEnv* env, + ERL_NIF_TERM fam, + ERL_NIF_TERM sockType, + ERL_NIF_TERM proto, + ERL_NIF_TERM addr) +{ + return MKT5(env, atom_address_info, fam, sockType, proto, addr); +} @@ -2248,9 +2512,12 @@ int on_load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info) atom_idn = MKA(env, str_idn); atom_idna_allow_unassigned = MKA(env, str_idna_allow_unassigned); atom_idna_use_std3_ascii_rules = MKA(env, str_idna_use_std3_ascii_rules); + atom_in4_sockaddr = MKA(env, str_in4_sockaddr); + atom_in6_sockaddr = MKA(env, str_in6_sockaddr); atom_inet = MKA(env, str_inet); atom_inet6 = MKA(env, str_inet6); atom_ip = MKA(env, str_ip); + atom_ipv6 = MKA(env, str_ipv6); atom_namereqd = MKA(env, str_namereqd); atom_name_info = MKA(env, str_name_info); atom_nofqdn = MKA(env, str_nofqdn); diff --git a/erts/emulator/nifs/common/socket_dbg.c b/erts/emulator/nifs/common/socket_dbg.c new file mode 100644 index 0000000000..dd11fbca9b --- /dev/null +++ b/erts/emulator/nifs/common/socket_dbg.c @@ -0,0 +1,116 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 2018-2018. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * %CopyrightEnd% + * + * ---------------------------------------------------------------------- + * Purpose : Debug functions for the socket and net NIF(s). + * ---------------------------------------------------------------------- + * + */ + +#include +#include +#include +#include +#include + +#include +#include "socket_dbg.h" + +#define TSELF() enif_thread_self() +#define TNAME(__T__) enif_thread_name( __T__ ) +#define TSNAME() TNAME(TSELF()) + +static int realtime(struct timespec* tsP); +static int timespec2str(char *buf, unsigned int len, struct timespec *ts); + + +/* + * Print a debug format string *with* both a timestamp and the + * the name of the *current* thread. + */ +extern +void esock_dbg_printf( const char* prefix, const char* format, ... ) +{ + va_list args; + char f[512 + sizeof(format)]; // This has to suffice... + char stamp[30]; + struct timespec ts; + int res; + + /* + * We should really include self in the printout, so we can se which process + * are executing the code. But then I must change the API.... + * ....something for later. + */ + + if (!realtime(&ts)) { + if (timespec2str(stamp, sizeof(stamp), &ts) != 0) { + res = enif_snprintf(f, sizeof(f), "%s [%s] %s", prefix, TSNAME(), format); + // res = enif_snprintf(f, sizeof(f), "%s [%s]", prefix, format); + } else { + res = enif_snprintf(f, sizeof(f), "%s [%s] [%s] %s", prefix, stamp, TSNAME(), format); + // res = enif_snprintf(f, sizeof(f), "%s [%s] %s", prefix, stamp, format); + } + + if (res > 0) { + va_start (args, format); + enif_vfprintf (stdout, f, args); + va_end (args); + fflush(stdout); + } + } + + return; +} + + +static +int realtime(struct timespec* tsP) +{ + return clock_gettime(CLOCK_REALTIME, tsP); +} + + + + +/* + * Convert a timespec struct into a readable/printable string + */ +static +int timespec2str(char *buf, unsigned int len, struct timespec *ts) +{ + int ret, buflen; + struct tm t; + + tzset(); + if (localtime_r(&(ts->tv_sec), &t) == NULL) + return 1; + + ret = strftime(buf, len, "%F %T", &t); + if (ret == 0) + return 2; + len -= ret - 1; + buflen = strlen(buf); + + ret = snprintf(&buf[buflen], len, ".%06ld", ts->tv_nsec/1000); + if (ret >= len) + return 3; + + return 0; +} diff --git a/erts/emulator/nifs/common/socket_dbg.h b/erts/emulator/nifs/common/socket_dbg.h new file mode 100644 index 0000000000..36e2be1679 --- /dev/null +++ b/erts/emulator/nifs/common/socket_dbg.h @@ -0,0 +1,41 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 2018-2018. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * %CopyrightEnd% + * + * ---------------------------------------------------------------------- + * Purpose : Defines and macros for the debug util of the socket and + * net NIF(s). + * ---------------------------------------------------------------------- + * + */ + +#ifndef SOCKET_DBG_H__ +#define SOCKET_DBG_H__ + + +#define ESOCK_DBG_PRINTF( ___COND___ , proto ) \ + if ( ___COND___ ) { \ + esock_dbg_printf proto; \ + fflush(stdout); \ + } + + +extern +void esock_dbg_printf( const char* prefix, const char* format, ... ); + +#endif // SOCKET_DBG_H__ diff --git a/erts/emulator/nifs/common/socket_int.h b/erts/emulator/nifs/common/socket_int.h new file mode 100644 index 0000000000..83794f0b95 --- /dev/null +++ b/erts/emulator/nifs/common/socket_int.h @@ -0,0 +1,193 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 2018-2018. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * %CopyrightEnd% + * + * ---------------------------------------------------------------------- + * Purpose : Utility "stuff" for socket and net. + * ---------------------------------------------------------------------- + * + */ + +#ifndef SOCKET_INT_H__ +#define SOCKET_INT_H__ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifdef __WIN32__ + +/* All this just to replace sys/socket.h, netinet/in.h and sys/un.h??? */ +#define INCL_WINSOCK_API_TYPEDEFS 1 +#ifndef WINDOWS_H_INCLUDES_WINSOCK2_H +#include +#endif +#include +#include /* NEED VC 6.0 or higher */ +/* Visual studio 2008+: NTDDI_VERSION needs to be set for iphlpapi.h + * to define the right structures. It needs to be set to WINXP (or LONGHORN) + * for IPV6 to work and it's set lower by default, so we need to change it. + */ +#ifdef HAVE_SDKDDKVER_H +# include +# ifdef NTDDI_VERSION +# undef NTDDI_VERSION +# endif +# define NTDDI_VERSION NTDDI_WINXP +#endif +#include + +#else /* !__WIN32__ */ + +#include +#include +#ifdef HAVE_SYS_UN_H +#include +#endif + +#endif + +#include + +/* The general purpose sockaddr */ +typedef union { + /* General sockaddr */ + struct sockaddr in; + + /* IPv4 sockaddr */ + struct sockaddr_in in4; + + /* IPv6 sockaddr */ +#if defined(HAVE_IN6) && defined(AF_INET6) + struct sockaddr_in6 in6; +#endif + + /* Unix Domain Socket sockaddr */ +#if defined(HAVE_SYS_UN_H) + struct sockaddr_un un; +#endif + +} SocketAddress; + + +typedef unsigned int BOOLEAN_T; +#define TRUE 1 +#define FALSE 0 + + +/* Misc error strings */ +#define ESOCK_STR_EINVAL "einval" +#define ESOCK_STR_EAFNOSUPPORT "eafnosupport" + + +/* ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + * Misc atoms + */ +extern ERL_NIF_TERM esock_atom_addr; +extern ERL_NIF_TERM esock_atom_any; +extern ERL_NIF_TERM esock_atom_dgram; +extern ERL_NIF_TERM esock_atom_error; +extern ERL_NIF_TERM esock_atom_family; +extern ERL_NIF_TERM esock_atom_flowinfo; +extern ERL_NIF_TERM esock_atom_inet; +extern ERL_NIF_TERM esock_atom_inet6; +extern ERL_NIF_TERM esock_atom_local; +extern ERL_NIF_TERM esock_atom_loopback; +extern ERL_NIF_TERM esock_atom_ok; +extern ERL_NIF_TERM esock_atom_path; +extern ERL_NIF_TERM esock_atom_port; +extern ERL_NIF_TERM esock_atom_raw; +extern ERL_NIF_TERM esock_atom_scope_id; +extern ERL_NIF_TERM esock_atom_seqpacket; +extern ERL_NIF_TERM esock_atom_stream; +extern ERL_NIF_TERM esock_atom_undefined; + + +/* ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + * Error value (=reason) atoms + */ +extern ERL_NIF_TERM esock_atom_eagain; +extern ERL_NIF_TERM esock_atom_eafnosupport; +extern ERL_NIF_TERM esock_atom_einval; + + +/* ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + * Various wrapper macros for enif functions + */ +#define MALLOC(SZ) enif_alloc((SZ)) +#define FREE(P) enif_free((P)) + +#define MKA(E,S) enif_make_atom((E), (S)) +#define MKBIN(E,B) enif_make_binary((E), (B)) +#define MKI(E,I) enif_make_int((E), (I)) +#define MKLA(E,A,L) enif_make_list_from_array((E), (A), (L)) +#define MKEL(E) enif_make_list((E), 0) +#define MKMA(E,KA,VA,L,M) enif_make_map_from_arrays((E), (KA), (VA), (L), (M)) +#define MKREF(E) enif_make_ref((E)) +#define MKS(E,S) enif_make_string((E), (S), ERL_NIF_LATIN1) +#define MKSL(E,S,L) enif_make_string_len((E), (S), (L), ERL_NIF_LATIN1) +#define MKSBIN(E,B,ST,SZ) enif_make_sub_binary((E), (B), (ST), (SZ)) +#define MKT2(E,E1,E2) enif_make_tuple2((E), (E1), (E2)) +#define MKT3(E,E1,E2,E3) enif_make_tuple3((E), (E1), (E2), (E3)) +#define MKT4(E,E1,E2,E3,E4) enif_make_tuple4((E), (E1), (E2), (E3), (E4)) +#define MKT5(E,E1,E2,E3,E4,E5) \ + enif_make_tuple5((E), (E1), (E2), (E3), (E4), (E5)) +#define MKT8(E,E1,E2,E3,E4,E5,E6,E7,E8) \ + enif_make_tuple8((E), (E1), (E2), (E3), (E4), (E5), (E6), (E7), (E8)) +#define MKTA(E, A, AL) enif_make_tuple_from_array((E), (A), (AL)) + +#define MCREATE(N) enif_mutex_create((N)) +#define MDESTROY(M) enif_mutex_destroy((M)) +#define MLOCK(M) enif_mutex_lock((M)) +#define MUNLOCK(M) enif_mutex_unlock((M)) + +#define MONP(E,D,P,M) enif_monitor_process((E), (D), (P), (M)) +#define DEMONP(E,D,M) enif_demonitor_process((E), (D), (M)) + +#define SELECT(E,FD,M,O,P,R) \ + if (enif_select((E), (FD), (M), (O), (P), (R)) < 0) \ + return enif_make_badarg((E)); + +#define COMPARE(A, B) enif_compare((A), (B)) + +#define IS_ATOM(E, TE) enif_is_atom((E), (TE)) +#define IS_BIN(E, TE) enif_is_binary((E), (TE)) +#define IS_LIST(E, TE) enif_is_list((E), (TE)) +#define IS_MAP(E, TE) enif_is_map((E), (TE)) +#define IS_NUM(E, TE) enif_is_number((E), (TE)) +#define IS_TUPLE(E, TE) enif_is_tuple((E), (TE)) + +#define GET_ATOM_LEN(E, TE, LP) \ + enif_get_atom_length((E), (TE), (LP), ERL_NIF_LATIN1) +#define GET_ATOM(E, TE, BP, MAX) \ + enif_get_atom((E), (TE), (BP), (MAX), ERL_NIF_LATIN1) +#define GET_BIN(E, TE, BP) enif_inspect_iolist_as_binary((E), (TE), (BP)) +#define GET_INT(E, TE, IP) enif_get_int((E), (TE), (IP)) +#define GET_LIST_ELEM(E, L, HP, TP) enif_get_list_cell((E), (L), (HP), (TP)) +#define GET_LIST_LEN(E, L, LP) enif_get_list_length((E), (L), (LP)) +#define GET_STR(E, L, B, SZ) \ + enif_get_string((E), (L), (B), (SZ), ERL_NIF_LATIN1) +#define GET_UINT(E, TE, IP) enif_get_uint((E), (TE), (IP)) +#define GET_TUPLE(E, TE, TSZ, TA) enif_get_tuple((E), (TE), (TSZ), (TA)) +#define GET_MAP_VAL(E, M, K, V) enif_get_map_value((E), (M), (K), (V)) + +#define ALLOC_BIN(SZ, BP) enif_alloc_binary((SZ), (BP)) +#define REALLOC_BIN(SZ, BP) enif_realloc_binary((SZ), (BP)) + + +#endif // SOCKET_INT_H__ diff --git a/erts/emulator/nifs/common/socket_nif.c b/erts/emulator/nifs/common/socket_nif.c index badaa8d988..335d773f89 100644 --- a/erts/emulator/nifs/common/socket_nif.c +++ b/erts/emulator/nifs/common/socket_nif.c @@ -173,6 +173,9 @@ #include +#include "socket_dbg.h" +#include "socket_int.h" +#include "socket_util.h" /* All platforms fail on malloc errors. */ #define FATAL_MALLOC @@ -399,66 +402,61 @@ typedef union { * * * =================================================================== */ -#define MALLOC(SZ) enif_alloc((SZ)) -#define FREE(P) enif_free((P)) - -#define MKA(E,S) enif_make_atom((E), (S)) -#define MKBIN(E,B) enif_make_binary((E), (B)) -#define MKI(E,I) enif_make_int((E), (I)) -#define MKLA(E,A,L) enif_make_list_from_array((E), (A), (L)) -#define MKMA(E,KA,VA,L,M) enif_make_map_from_arrays((E), (KA), (VA), (L), (M)) -#define MKREF(E) enif_make_ref((E)) -#define MKS(E,S) enif_make_string((E), (S), ERL_NIF_LATIN1) -#define MKSL(E,S,L) enif_make_string_len((E), (S), (L), ERL_NIF_LATIN1) -#define MKSBIN(E,B,ST,SZ) enif_make_sub_binary((E), (B), (ST), (SZ)) -#define MKT2(E,E1,E2) enif_make_tuple2((E), (E1), (E2)) -#define MKT3(E,E1,E2,E3) enif_make_tuple3((E), (E1), (E2), (E3)) -#define MKT4(E,E1,E2,E3,E4) enif_make_tuple4((E), (E1), (E2), (E3), (E4)) -#define MKT8(E,E1,E2,E3,E4,E5,E6,E7,E8) \ - enif_make_tuple8((E), (E1), (E2), (E3), (E4), (E5), (E6), (E7), (E8)) - -#define MCREATE(N) enif_mutex_create((N)) -#define MDESTROY(M) enif_mutex_destroy((M)) -#define MLOCK(M) enif_mutex_lock((M)) -#define MUNLOCK(M) enif_mutex_unlock((M)) - -#define MONP(E,D,P,M) enif_monitor_process((E), (D), (P), (M)) -#define DEMONP(E,D,M) enif_demonitor_process((E), (D), (M)) - -#define SELECT(E,FD,M,O,P,R) \ - if (enif_select((E), (FD), (M), (O), (P), (R)) < 0) \ - return enif_make_badarg((E)); - -#define COMPARE(A, B) enif_compare((A), (B)) - -#define IS_ATOM(E, TE) enif_is_atom((E), (TE)) -#define IS_BIN(E, TE) enif_is_binary((E), (TE)) -#define IS_MAP(E, TE) enif_is_map((E), (TE)) -#define IS_NUM(E, TE) enif_is_number((E), (TE)) -#define IS_TUPLE(E, TE) enif_is_tuple((E), (TE)) - -#define GET_ATOM_LEN(E, TE, LP) \ - enif_get_atom_length((E), (TE), (LP), ERL_NIF_LATIN1) -#define GET_ATOM(E, TE, BP, MAX) \ - enif_get_atom((E), (TE), (BP), (MAX), ERL_NIF_LATIN1) -#define GET_BIN(E, TE, BP) enif_inspect_iolist_as_binary((E), (TE), (BP)) -#define GET_INT(E, TE, IP) enif_get_int((E), (TE), (IP)) -#define GET_STR(E, L, B, SZ) \ - enif_get_string((E), (L), (B), (SZ), ERL_NIF_LATIN1) -#define GET_UINT(E, TE, IP) enif_get_uint((E), (TE), (IP)) -#define GET_TUPLE(E, TE, TSZ, TA) enif_get_tuple((E), (TE), (TSZ), (TA)) - -#define ALLOC_BIN(SZ, BP) enif_alloc_binary((SZ), (BP)) -#define REALLOC_BIN(SZ, BP) enif_realloc_binary((SZ), (BP)) - - -#define SDEBUG( ___COND___ , proto ) \ - if ( ___COND___ ) { \ - dbg_printf proto; \ - fflush(stdout); \ - } -#define SGDBG( proto ) SDEBUG( data.dbg , proto ) -#define SSDBG( __D__ , proto ) SDEBUG( (__D__)->dbg , proto ) +/* #define MALLOC(SZ) enif_alloc((SZ)) */ +/* #define FREE(P) enif_free((P)) */ + +/* #define MKA(E,S) enif_make_atom((E), (S)) */ +/* #define MKBIN(E,B) enif_make_binary((E), (B)) */ +/* #define MKI(E,I) enif_make_int((E), (I)) */ +/* #define MKLA(E,A,L) enif_make_list_from_array((E), (A), (L)) */ +/* #define MKMA(E,KA,VA,L,M) enif_make_map_from_arrays((E), (KA), (VA), (L), (M)) */ +/* #define MKREF(E) enif_make_ref((E)) */ +/* #define MKS(E,S) enif_make_string((E), (S), ERL_NIF_LATIN1) */ +/* #define MKSL(E,S,L) enif_make_string_len((E), (S), (L), ERL_NIF_LATIN1) */ +/* #define MKSBIN(E,B,ST,SZ) enif_make_sub_binary((E), (B), (ST), (SZ)) */ +/* #define MKT2(E,E1,E2) enif_make_tuple2((E), (E1), (E2)) */ +/* #define MKT3(E,E1,E2,E3) enif_make_tuple3((E), (E1), (E2), (E3)) */ +/* #define MKT4(E,E1,E2,E3,E4) enif_make_tuple4((E), (E1), (E2), (E3), (E4)) */ +/* #define MKT8(E,E1,E2,E3,E4,E5,E6,E7,E8) \ */ +/* enif_make_tuple8((E), (E1), (E2), (E3), (E4), (E5), (E6), (E7), (E8)) */ + +/* #define MCREATE(N) enif_mutex_create((N)) */ +/* #define MDESTROY(M) enif_mutex_destroy((M)) */ +/* #define MLOCK(M) enif_mutex_lock((M)) */ +/* #define MUNLOCK(M) enif_mutex_unlock((M)) */ + +/* #define MONP(E,D,P,M) enif_monitor_process((E), (D), (P), (M)) */ +/* #define DEMONP(E,D,M) enif_demonitor_process((E), (D), (M)) */ + +/* #define SELECT(E,FD,M,O,P,R) \ */ +/* if (enif_select((E), (FD), (M), (O), (P), (R)) < 0) \ */ +/* return enif_make_badarg((E)); */ + +/* #define COMPARE(A, B) enif_compare((A), (B)) */ + +/* #define IS_ATOM(E, TE) enif_is_atom((E), (TE)) */ +/* #define IS_BIN(E, TE) enif_is_binary((E), (TE)) */ +/* #define IS_MAP(E, TE) enif_is_map((E), (TE)) */ +/* #define IS_NUM(E, TE) enif_is_number((E), (TE)) */ +/* #define IS_TUPLE(E, TE) enif_is_tuple((E), (TE)) */ + +/* #define GET_ATOM_LEN(E, TE, LP) \ */ +/* enif_get_atom_length((E), (TE), (LP), ERL_NIF_LATIN1) */ +/* #define GET_ATOM(E, TE, BP, MAX) \ */ +/* enif_get_atom((E), (TE), (BP), (MAX), ERL_NIF_LATIN1) */ +/* #define GET_BIN(E, TE, BP) enif_inspect_iolist_as_binary((E), (TE), (BP)) */ +/* #define GET_INT(E, TE, IP) enif_get_int((E), (TE), (IP)) */ +/* #define GET_STR(E, L, B, SZ) \ */ +/* enif_get_string((E), (L), (B), (SZ), ERL_NIF_LATIN1) */ +/* #define GET_UINT(E, TE, IP) enif_get_uint((E), (TE), (IP)) */ +/* #define GET_TUPLE(E, TE, TSZ, TA) enif_get_tuple((E), (TE), (TSZ), (TA)) */ + +/* #define ALLOC_BIN(SZ, BP) enif_alloc_binary((SZ), (BP)) */ +/* #define REALLOC_BIN(SZ, BP) enif_realloc_binary((SZ), (BP)) */ + + +#define SGDBG( proto ) ESOCK_DBG_PRINTF( data.dbg , proto ) +#define SSDBG( __D__ , proto ) ESOCK_DBG_PRINTF( (__D__)->dbg , proto ) /* =================================================================== * @@ -548,24 +546,28 @@ static unsigned long one_value = 1; #define SOCKOPTLEN_T SOCKLEN_T #endif -/* The general purpose sockaddr */ +/* The general purpose sockaddr * / typedef union { - struct sockaddr sa; - struct sockaddr_in sai; + struct sockaddr in; + struct sockaddr_in in4; #ifdef HAVE_IN6 - struct sockaddr_in6 sai6; + struct sockaddr_in6 in6; #endif #ifdef HAVE_SYS_UN_H - struct sockaddr_un sal; + struct sockaddr_un un; #endif + } SocketAddress; +*/ +/* We can use the IPv4 def for this since the beginning + * is the same for INET and INET6 */ #define which_address_port(sap) \ - ((((sap)->sai.sin_family == AF_INET) || \ - ((sap)->sai.sin_family == AF_INET6)) ? \ - ((sap)->sai.sin_port) : -1) + ((((sap)->in4.sin_family == AF_INET) || \ + ((sap)->in4.sin_family == AF_INET6)) ? \ + ((sap)->in4.sin_port) : -1) typedef struct { @@ -706,8 +708,6 @@ typedef struct { * ---------------------------------------------------------------------- */ -/* THIS IS JUST TEMPORARY */ -extern char* erl_errno_id(int error); static ERL_NIF_TERM nif_is_loaded(ErlNifEnv* env, @@ -1313,12 +1313,6 @@ static void socket_down(ErlNifEnv* env, const ErlNifPid* pid, const ErlNifMonitor* mon); -static ERL_NIF_TERM make_ok2(ErlNifEnv* env, ERL_NIF_TERM val); -static ERL_NIF_TERM make_ok3(ErlNifEnv* env, ERL_NIF_TERM val1, ERL_NIF_TERM val2); -static ERL_NIF_TERM make_error(ErlNifEnv* env, ERL_NIF_TERM reason); -static ERL_NIF_TERM make_error1(ErlNifEnv* env, char* reason); -static ERL_NIF_TERM make_error2(ErlNifEnv* env, int err); - /* static char* send_msg_error_closed(ErlNifEnv* env, ErlNifPid* pid); @@ -1341,10 +1335,6 @@ static void xabort(const char* expr, const char* file, int line); -static void dbg_printf( const char* format, ... ); -static int dbg_realtime(struct timespec* tsP); -static int dbg_timespec2str(char *buf, unsigned int len, struct timespec *ts); - static BOOLEAN_T extract_item_on_load(ErlNifEnv* env, ERL_NIF_TERM map, ERL_NIF_TERM key, @@ -1384,20 +1374,18 @@ static const struct in6_addr in6addr_loopback = /* *** String constants *** */ -static char str_any[] = "any"; +// static char str_any[] = "any"; static char str_close[] = "close"; static char str_closed[] = "closed"; static char str_closing[] = "closing"; static char str_debug[] = "debug"; -static char str_error[] = "error"; static char str_false[] = "false"; static char str_global_counters[] = "global_counters"; static char str_in4_sockaddr[] = "in4_sockaddr"; static char str_in6_sockaddr[] = "in6_sockaddr"; static char str_iow[] = "iow"; -static char str_loopback[] = "loopback"; +// static char str_loopback[] = "loopback"; static char str_nif_abort[] = "nif_abort"; -static char str_ok[] = "ok"; static char str_select[] = "select"; static char str_num_dlocal[] = "num_domain_local"; static char str_num_dinet[] = "num_domain_inet"; @@ -1412,7 +1400,6 @@ static char str_num_tseqpkgs[] = "num_type_seqpacket"; static char str_num_tstreams[] = "num_type_stream"; static char str_timeout[] = "timeout"; static char str_true[] = "true"; -static char str_undefined[] = "undefined"; static char str_lowdelay[] = "lowdelay"; static char str_throughput[] = "throughput"; @@ -1434,19 +1421,37 @@ static char str_exself[] = "exself"; // failed self static char str_exsend[] = "exsend"; // failed send +/* *** "Global" Atoms *** */ +ERL_NIF_TERM esock_atom_addr; +ERL_NIF_TERM esock_atom_any; +ERL_NIF_TERM esock_atom_dgram; +ERL_NIF_TERM esock_atom_error; +ERL_NIF_TERM esock_atom_family; +ERL_NIF_TERM esock_atom_flowinfo; +ERL_NIF_TERM esock_atom_inet; +ERL_NIF_TERM esock_atom_inet6; +ERL_NIF_TERM esock_atom_local; +ERL_NIF_TERM esock_atom_loopback; +ERL_NIF_TERM esock_atom_ok; +ERL_NIF_TERM esock_atom_path; +ERL_NIF_TERM esock_atom_port; +ERL_NIF_TERM esock_atom_raw; +ERL_NIF_TERM esock_atom_scope_id; +ERL_NIF_TERM esock_atom_seqpacket; +ERL_NIF_TERM esock_atom_stream; +ERL_NIF_TERM esock_atom_undefined; + + /* *** Atoms *** */ -static ERL_NIF_TERM atom_any; static ERL_NIF_TERM atom_close; static ERL_NIF_TERM atom_closed; static ERL_NIF_TERM atom_closing; static ERL_NIF_TERM atom_debug; -static ERL_NIF_TERM atom_error; static ERL_NIF_TERM atom_false; static ERL_NIF_TERM atom_global_counters; static ERL_NIF_TERM atom_in4_sockaddr; static ERL_NIF_TERM atom_in6_sockaddr; static ERL_NIF_TERM atom_iow; -static ERL_NIF_TERM atom_loopback; static ERL_NIF_TERM atom_nif_abort; static ERL_NIF_TERM atom_num_dinet; static ERL_NIF_TERM atom_num_dinet6; @@ -1459,11 +1464,9 @@ static ERL_NIF_TERM atom_num_sockets; static ERL_NIF_TERM atom_num_tdgrams; static ERL_NIF_TERM atom_num_tseqpkgs; static ERL_NIF_TERM atom_num_tstreams; -static ERL_NIF_TERM atom_ok; static ERL_NIF_TERM atom_select; static ERL_NIF_TERM atom_timeout; static ERL_NIF_TERM atom_true; -static ERL_NIF_TERM atom_undefined; static ERL_NIF_TERM atom_lowdelay; static ERL_NIF_TERM atom_throughput; @@ -1629,7 +1632,7 @@ ERL_NIF_TERM nif_open(ErlNifEnv* env, ERL_NIF_TERM emap; ERL_NIF_TERM result; - SGDBG( ("nif_open -> entry with %d args\r\n", argc) ); + SGDBG( ("SOCKET", "nif_open -> entry with %d args\r\n", argc) ); /* Extract arguments and perform preliminary validation */ @@ -1642,7 +1645,7 @@ ERL_NIF_TERM nif_open(ErlNifEnv* env, } emap = argv[3]; - SGDBG( ("nif_open -> " + SGDBG( ("SOCKET", "nif_open -> " "\r\n edomain: %T" "\r\n etype: %T" "\r\n eproto: %T" @@ -1668,7 +1671,7 @@ ERL_NIF_TERM nif_open(ErlNifEnv* env, result = nopen(env, domain, type, proto, netns); - SGDBG( ("nif_open -> done with result: " + SGDBG( ("SOCKET", "nif_open -> done with result: " "\r\n %T" "\r\n", result) ); @@ -1698,7 +1701,7 @@ ERL_NIF_TERM nopen(ErlNifEnv* env, int current_ns; #endif - SGDBG( ("nopen -> entry with" + SGDBG( ("SOCKET", "nopen -> entry with" "\r\n domain: %d" "\r\n type: %d" "\r\n protocol: %d" @@ -1708,16 +1711,16 @@ ERL_NIF_TERM nopen(ErlNifEnv* env, #ifdef HAVE_SETNS if ((netns != NULL) && !change_network_namespace(netns, ¤t_ns, &save_errno)) - return make_error2(env, save_errno); + return esock_make_error_errno(env, save_errno); #endif if ((sock = sock_open(domain, type, protocol)) == INVALID_SOCKET) - return make_error2(env, sock_errno()); + return esock_make_error_errno(env, sock_errno()); #ifdef HAVE_SETNS if ((netns != NULL) && !restore_network_namespace(current_ns, sock, &save_errno)) - return make_error2(env, save_errno); + return esock_make_error_errno(env, save_errno); if (netns != NULL) FREE(netns); @@ -1727,7 +1730,7 @@ ERL_NIF_TERM nopen(ErlNifEnv* env, if ((event = sock_create_event(sock)) == INVALID_EVENT) { save_errno = sock_errno(); while ((sock_close(sock) == INVALID_SOCKET) && (sock_errno() == EINTR)); - return make_error2(env, save_errno); + return esock_make_error_errno(env, save_errno); } @@ -1756,12 +1759,12 @@ ERL_NIF_TERM nopen(ErlNifEnv* env, * of environment... */ if (enif_self(env, &descP->ctrlPid) == NULL) - return make_error(env, atom_exself); + return esock_make_error(env, atom_exself); if (MONP(env, descP, &descP->ctrlPid, &descP->ctrlMon) > 0) - return make_error(env, atom_exmon); + return esock_make_error(env, atom_exmon); #ifdef __WIN32__ @@ -1779,11 +1782,11 @@ ERL_NIF_TERM nopen(ErlNifEnv* env, SELECT(env, event, (ERL_NIF_SELECT_READ), - descP, NULL, atom_undefined); + descP, NULL, esock_atom_undefined); #endif - return make_ok2(env, res); + return esock_make_ok2(env, res); } @@ -1802,7 +1805,7 @@ BOOLEAN_T change_network_namespace(char* netns, int* cns, int* err) int current_ns = 0; int new_ns = 0; - SGDBG( ("change_network_namespace -> entry with" + SGDBG( ("SOCKET", "change_network_namespace -> entry with" "\r\n new ns: %s", netns) ); if (netns != NULL) { @@ -1853,7 +1856,7 @@ BOOLEAN_T restore_network_namespace(int ns, SOCKET sock, int* err) { int save_errno; - SGDBG( ("restore_network_namespace -> entry with" + SGDBG( ("SOCKET", "restore_network_namespace -> entry with" "\r\n ns: %d", ns) ); if (ns != INVALID_SOCKET) { @@ -1920,7 +1923,7 @@ ERL_NIF_TERM nif_bind(ErlNifEnv* env, } SSDBG( descP, - ("nif_bind -> " + ("SOCKET", "nif_bind -> " "\r\n Socket: %T" "\r\n Addr: %T" "\r\n", argv[0], argv[1]) ); @@ -1943,7 +1946,7 @@ ERL_NIF_TERM nif_bind(ErlNifEnv* env, */ /* WHY NOT !IS_OPEN(...) */ if (descP->state != SOCKET_STATE_OPEN) - return make_error(env, atom_exbadstate); + return esock_make_error(env, atom_exbadstate); return nbind(env, descP, argv[1]); } @@ -1960,34 +1963,34 @@ ERL_NIF_TERM nbind(ErlNifEnv* env, int port; SSDBG( descP, - ("nbind -> entry with" + ("SOCKET", "nbind -> entry with" "\r\n addr: %T" "\r\n", addr) ); if ((xerr = decode_laddress(env, descP->domain, addr, &local, &addrLen)) != NULL) - return make_error1(env, xerr); + return esock_make_error_str(env, xerr); - SSDBG( descP, ("nbind -> try bind\r\n") ); + SSDBG( descP, ("SOCKET", "nbind -> try bind\r\n") ); if (IS_SOCKET_ERROR(sock_bind(descP->sock, (struct sockaddr*) &local, addrLen))) { - return make_error2(env, sock_errno()); + return esock_make_error_errno(env, sock_errno()); } port = which_address_port(&local); if (port == 0) { SOCKLEN_T len = sizeof(local); sys_memzero((char *) &local, len); - sock_name(descP->sock, &local.sa, &len); + sock_name(descP->sock, &local.in, &len); port = which_address_port(&local); } else if (port == -1) { port = 0; } - SSDBG( descP, ("nbind -> done with port = %d\r\n", port) ); + SSDBG( descP, ("SOCKET", "nbind -> done with port = %d\r\n", port) ); - return make_ok2(env, MKI(env, port)); + return esock_make_ok2(env, MKI(env, port)); } @@ -2050,15 +2053,15 @@ char* decode_laddress_binary(ErlNifEnv* env, #else 1 #endif - ) > sizeof(localP->sal.sun_path)) + ) > sizeof(localP->un.sun_path)) return str_einval; sys_memzero((char*)localP, sizeof(struct sockaddr_un)); - localP->sal.sun_family = domain; - sys_memcpy(localP->sal.sun_path, bin.data, bin.size); + localP->un.sun_family = domain; + sys_memcpy(localP->un.sun_path, bin.data, bin.size); addrLen = offsetof(struct sockaddr_un, sun_path) + bin.size; #ifndef NO_SA_LEN - localP->u.sal.sun_len = addrLen; + localP->un.sun_len = addrLen; #endif *addrLenP = addrLen; return NULL; @@ -2103,7 +2106,7 @@ ERL_NIF_TERM nif_connect(ErlNifEnv* env, if ((xres = decode_in_sockaddr(env, eSockAddr, &descP->remote, &descP->addrLen)) != NULL) { - return make_error1(env, xres); + return esock_make_error_str(env, xres); } return nconnect(env, descP); @@ -2119,13 +2122,13 @@ ERL_NIF_TERM nconnect(ErlNifEnv* env, /* Verify that we are where in the proper state */ if (!IS_OPEN(descP)) - return make_error(env, atom_exbadstate); + return esock_make_error(env, atom_exbadstate); if (IS_CONNECTED(descP)) - return make_error(env, atom_eisconn); + return esock_make_error(env, atom_eisconn); if (IS_CONNECTING(descP)) - return make_error(env, atom_einval); + return esock_make_error(env, atom_einval); code = sock_connect(descP->sock, (struct sockaddr*) &descP->remote, @@ -2140,15 +2143,15 @@ ERL_NIF_TERM nconnect(ErlNifEnv* env, descP->sock, (ERL_NIF_SELECT_WRITE), descP, NULL, ref); - return make_ok2(env, ref); + return esock_make_ok2(env, ref); } else if (code == 0) { /* ok we are connected */ descP->state = SOCKET_STATE_CONNECTED; /* Do we need to do somthing for "active" mode? * Is there even such a thing *here*? */ - return atom_ok; + return esock_atom_ok; } else { - return make_error2(env, sock_errno()); + return esock_make_error_errno(env, sock_errno()); } } @@ -2192,16 +2195,16 @@ ERL_NIF_TERM nfinalize_connection(ErlNifEnv* env, int error; if (descP->state != SOCKET_STATE_CONNECTING) - return make_error(env, atom_enotconn); + return esock_make_error(env, atom_enotconn); if (!verify_is_connected(descP, &error)) { descP->state = SOCKET_STATE_OPEN; /* restore state */ - return make_error2(env, error); + return esock_make_error_errno(env, error); } descP->state = SOCKET_STATE_CONNECTED; - return atom_ok; + return esock_atom_ok; } @@ -2294,17 +2297,17 @@ ERL_NIF_TERM nlisten(ErlNifEnv* env, int backlog) { if (descP->state == SOCKET_STATE_CLOSED) - return make_error(env, atom_exbadstate); + return esock_make_error(env, atom_exbadstate); if (!IS_OPEN(descP)) - return make_error(env, atom_exbadstate); + return esock_make_error(env, atom_exbadstate); if (IS_SOCKET_ERROR(sock_listen(descP->sock, backlog))) - return make_error2(env, sock_errno()); + return esock_make_error_errno(env, sock_errno()); descP->state = SOCKET_STATE_LISTENING; - return atom_ok; + return esock_atom_ok; } @@ -2361,7 +2364,7 @@ ERL_NIF_TERM naccept(ErlNifEnv* env, break; default: - res = make_error(env, atom_einval); + res = esock_make_error(env, atom_einval); break; } @@ -2385,7 +2388,7 @@ ERL_NIF_TERM naccept_listening(ErlNifEnv* env, ErlNifPid caller; if (enif_self(env, &caller) == NULL) - return make_error(env, atom_exself); + return esock_make_error(env, atom_exself); n = sizeof(remote); sys_memzero((char *) &remote, n); @@ -2400,7 +2403,7 @@ ERL_NIF_TERM naccept_listening(ErlNifEnv* env, if (MONP(env, descP, &descP->currentAcceptor.pid, &descP->currentAcceptor.mon) > 0) - return make_error(env, atom_exmon); + return esock_make_error(env, atom_exmon); descP->currentAcceptor.ref = ref; @@ -2434,10 +2437,10 @@ ERL_NIF_TERM naccept_listening(ErlNifEnv* env, descP->state = SOCKET_STATE_ACCEPTING; - return make_error(env, atom_eagain); + return esock_make_error(env, atom_eagain); } else { - return make_error2(env, save_errno); + return esock_make_error_errno(env, save_errno); } } else { @@ -2452,7 +2455,7 @@ ERL_NIF_TERM naccept_listening(ErlNifEnv* env, save_errno = sock_errno(); while ((sock_close(accSock) == INVALID_SOCKET) && (sock_errno() == EINTR)); - return make_error2(env, save_errno); + return esock_make_error_errno(env, save_errno); } if ((accDescP = alloc_descriptor(accSock, accEvent)) == NULL) { @@ -2472,7 +2475,7 @@ ERL_NIF_TERM naccept_listening(ErlNifEnv* env, &accDescP->ctrlPid, &accDescP->ctrlMon) > 0) { sock_close(accSock); - return make_error(env, atom_exmon); + return esock_make_error(env, atom_exmon); } accDescP->remote = remote; @@ -2483,12 +2486,12 @@ ERL_NIF_TERM naccept_listening(ErlNifEnv* env, SELECT(env, descP->sock, (ERL_NIF_SELECT_READ), - descP, NULL, atom_undefined); + descP, NULL, esock_atom_undefined); #endif accDescP->state = SOCKET_STATE_CONNECTED; - return make_ok2(env, accRef); + return esock_make_ok2(env, accRef); } } @@ -2510,7 +2513,7 @@ ERL_NIF_TERM naccept_accepting(ErlNifEnv* env, int save_errno; if (enif_self(env, &caller) == NULL) - return make_error(env, atom_exself); + return esock_make_error(env, atom_exself); if (compare_pids(env, &descP->currentAcceptor.pid, &caller) != 0) { /* This will have to do until we implement the queue. @@ -2518,7 +2521,7 @@ ERL_NIF_TERM naccept_accepting(ErlNifEnv* env, * and instead return with eagain (the caller will then wait * for the select message). */ - return make_error(env, atom_exbusy); + return esock_make_error(env, atom_exbusy); } n = sizeof(descP->remote); @@ -2537,9 +2540,9 @@ ERL_NIF_TERM naccept_accepting(ErlNifEnv* env, (ERL_NIF_SELECT_READ), descP, NULL, ref); - return make_error(env, atom_eagain); + return esock_make_error(env, atom_eagain); } else { - return make_error2(env, save_errno); + return esock_make_error_errno(env, save_errno); } } else { SocketDescriptor* accDescP; @@ -2553,7 +2556,7 @@ ERL_NIF_TERM naccept_accepting(ErlNifEnv* env, save_errno = sock_errno(); while ((sock_close(accSock) == INVALID_SOCKET) && (sock_errno() == EINTR)); - return make_error2(env, save_errno); + return esock_make_error_errno(env, save_errno); } if ((accDescP = alloc_descriptor(accSock, accEvent)) == NULL) { @@ -2573,7 +2576,7 @@ ERL_NIF_TERM naccept_accepting(ErlNifEnv* env, &accDescP->ctrlPid, &accDescP->ctrlMon) > 0) { sock_close(accSock); - return make_error(env, atom_exmon); + return esock_make_error(env, atom_exmon); } accDescP->remote = remote; @@ -2584,7 +2587,7 @@ ERL_NIF_TERM naccept_accepting(ErlNifEnv* env, SELECT(env, descP->sock, (ERL_NIF_SELECT_READ), - descP, NULL, atom_undefined); + descP, NULL, esock_atom_undefined); #endif accDescP->state = SOCKET_STATE_CONNECTED; @@ -2596,7 +2599,7 @@ ERL_NIF_TERM naccept_accepting(ErlNifEnv* env, */ descP->state = SOCKET_STATE_LISTENING; - return make_ok2(env, accRef); + return esock_make_ok2(env, accRef); } } @@ -2638,7 +2641,7 @@ ERL_NIF_TERM nif_send(ErlNifEnv* env, sendRef = argv[1]; if (!IS_CONNECTED(descP)) - return make_error(env, atom_enotconn); + return esock_make_error(env, atom_enotconn); if (!esendflags2sendflags(eflags, &flags)) return enif_make_badarg(env); @@ -2739,15 +2742,15 @@ ERL_NIF_TERM nif_sendto(ErlNifEnv* env, /* THIS TEST IS NOT CORRECT!!! */ if (!IS_OPEN(descP)) - return make_error(env, atom_einval); + return esock_make_error(env, atom_einval); if (!esendflags2sendflags(eflags, &flags)) - return make_error(env, atom_einval); + return esock_make_error(env, atom_einval); if ((xres = decode_in_sockaddr(env, eSockAddr, &remoteAddr, &remoteAddrLen)) != NULL) - return make_error1(env, xres); + return esock_make_error_str(env, xres); return nsendto(env, descP, sendRef, &data, flags, &remoteAddr, remoteAddrLen); } @@ -2775,7 +2778,7 @@ ERL_NIF_TERM nsendto(ErlNifEnv* env, if (toAddrP != NULL) { written = sock_sendto(descP->sock, dataP->data, dataP->size, flags, - &toAddrP->sa, toAddrLen); + &toAddrP->in, toAddrLen); } else { written = sock_sendto(descP->sock, dataP->data, dataP->size, flags, @@ -2893,7 +2896,7 @@ ERL_NIF_TERM nif_recv(ErlNifEnv* env, recvRef = argv[1]; if (!IS_CONNECTED(descP)) - return make_error(env, atom_enotconn); + return esock_make_error(env, atom_enotconn); if (!erecvflags2recvflags(eflags, &flags)) return enif_make_badarg(env); @@ -2945,7 +2948,7 @@ ERL_NIF_TERM nrecv(ErlNifEnv* env, * size (what has been configured). */ if (!ALLOC_BIN((len ? len : descP->rBufSz), &buf)) - return make_error(env, atom_exalloc); + return esock_make_error(env, atom_exalloc); /* We ignore the wrap for the moment. * Maybe we should issue a wrap-message to controlling process... @@ -2999,7 +3002,7 @@ ERL_NIF_TERM nif_recvfrom(ErlNifEnv* env, recvRef = argv[1]; /* if (IS_OPEN(descP)) */ - /* return make_error(env, atom_enotconn); */ + /* return esock_make_error(env, atom_enotconn); */ if (!erecvflags2recvflags(eflags, &flags)) return enif_make_badarg(env); @@ -3055,7 +3058,7 @@ ERL_NIF_TERM nrecvfrom(ErlNifEnv* env, * size (what has been configured). */ if (!ALLOC_BIN((bufSz ? bufSz : descP->rBufSz), &buf)) - return make_error(env, atom_exalloc); + return esock_make_error(env, atom_exalloc); /* We ignore the wrap for the moment. * Maybe we should issue a wrap-message to controlling process... @@ -3066,7 +3069,7 @@ ERL_NIF_TERM nrecvfrom(ErlNifEnv* env, sys_memzero((char*) &fromAddr, addrLen); read = sock_recvfrom(descP->sock, buf.data, buf.size, flags, - &fromAddr.sa, &addrLen); + &fromAddr.in, &addrLen); return recvfrom_check_result(env, descP, read, @@ -3129,7 +3132,7 @@ ERL_NIF_TERM nclose(ErlNifEnv* env, if (enif_self(env, &descP->closerPid) == NULL) { MUNLOCK(descP->closeMtx); - return make_error(env, atom_exself); + return esock_make_error(env, atom_exself); } /* Monitor the caller, since we should complete this operation even if @@ -3140,7 +3143,7 @@ ERL_NIF_TERM nclose(ErlNifEnv* env, &descP->closerPid, &descP->closerMon) > 0) { MUNLOCK(descP->closeMtx); - return make_error(env, atom_exmon); + return esock_make_error(env, atom_exmon); } descP->closeLocal = TRUE; @@ -3156,11 +3159,11 @@ ERL_NIF_TERM nclose(ErlNifEnv* env, descP, NULL, descP->closeRef); if (selectRes & ERL_NIF_SELECT_STOP_CALLED) { /* Prep done - inform the caller it can finalize (close) directly */ - reply = atom_ok; + reply = esock_atom_ok; } else if (selectRes & ERL_NIF_SELECT_STOP_SCHEDULED) { /* The stop callback function has been *scheduled* which means that we * have to wait for it to complete. */ - reply = make_ok2(env, descP->closeRef); + reply = esock_make_ok2(env, descP->closeRef); } else { /* * @@ -3172,10 +3175,10 @@ ERL_NIF_TERM nclose(ErlNifEnv* env, * */ reason = MKT2(env, atom_select, MKI(env, selectRes)); - reply = make_error(env, reason); + reply = esock_make_error(env, reason); } } else { - reply = make_error(env, reason); + reply = esock_make_error(env, reason); } return reply; @@ -3222,10 +3225,10 @@ ERL_NIF_TERM nfinalize_close(ErlNifEnv* env, ERL_NIF_TERM reply; if (descP->state == SOCKET_STATE_CLOSED) - return atom_ok; + return esock_atom_ok; if (descP->state != SOCKET_STATE_CLOSING) - return make_error(env, atom_enotclosing); + return esock_make_error(env, atom_enotclosing); /* This nif is executed in a dirty scheduler just so that * it can "hang" (whith minumum effect on the VM) while the @@ -3242,12 +3245,12 @@ ERL_NIF_TERM nfinalize_close(ErlNifEnv* env, /* Not all data in the buffers where sent, * make sure the caller gets this. */ - reply = make_error(env, atom_timeout); + reply = esock_make_error(env, atom_timeout); } else { - reply = make_error2(env, save_errno); + reply = esock_make_error_errno(env, save_errno); } } else { - reply = atom_ok; + reply = esock_atom_ok; } sock_close_event(descP->event); @@ -3315,9 +3318,9 @@ ERL_NIF_TERM nshutdown(ErlNifEnv* env, descP->isWritable = FALSE; break; } - reply = atom_ok; + reply = esock_atom_ok; } else { - reply = make_error2(env, sock_errno()); + reply = esock_make_error_errno(env, sock_errno()); } return reply; @@ -3366,10 +3369,10 @@ ERL_NIF_TERM nif_setopt(ErlNifEnv* env, eVal = argv[4]; if (!decode_bool(env, eIsEncoded, &isEncoded)) - return make_error(env, atom_einval); + return esock_make_error(env, atom_einval); if (!elevel2level(isEncoded, eLevel, &isOTP, &level)) - return make_error(env, atom_einval); + return esock_make_error(env, atom_einval); return nsetopt(env, descP, isEncoded, isOTP, level, eOpt, eVal); } @@ -3422,7 +3425,7 @@ ERL_NIF_TERM nsetopt_otp(ErlNifEnv* env, break; default: - result = make_error(env, atom_einval); + result = esock_make_error(env, atom_einval); break; } @@ -3440,9 +3443,9 @@ ERL_NIF_TERM nsetopt_otp_debug(ErlNifEnv* env, ERL_NIF_TERM result; if (decode_bool(env, eVal, &descP->dbg)) { - result = atom_ok; + result = esock_atom_ok; } else { - result = make_error(env, atom_einval); + result = esock_make_error(env, atom_einval); } return result; @@ -3459,9 +3462,9 @@ ERL_NIF_TERM nsetopt_otp_iow(ErlNifEnv* env, ERL_NIF_TERM result; if (decode_bool(env, eVal, &descP->iow)) { - result = atom_ok; + result = esock_atom_ok; } else { - result = make_error(env, atom_einval); + result = esock_make_error(env, atom_einval); } return result; @@ -3486,11 +3489,11 @@ ERL_NIF_TERM nsetopt_native(ErlNifEnv* env, int res = socket_setopt(descP->sock, level, opt, val.data, val.size); if (res != 0) - result = make_error2(env, res); + result = esock_make_error_errno(env, res); else - result = atom_ok; + result = esock_atom_ok; } else { - result = make_error(env, atom_einval); + result = esock_make_error(env, atom_einval); } return result; @@ -3543,7 +3546,7 @@ ERL_NIF_TERM nsetopt_level(ErlNifEnv* env, #endif default: - result = make_error(env, atom_einval); + result = esock_make_error(env, atom_einval); break; } @@ -3612,7 +3615,7 @@ ERL_NIF_TERM nsetopt_lvl_socket(ErlNifEnv* env, #endif default: - result = make_error(env, atom_einval); + result = esock_make_error(env, atom_einval); break; } @@ -3667,11 +3670,11 @@ ERL_NIF_TERM nsetopt_lvl_sock_linger(ErlNifEnv* env, int res = socket_setopt(descP->sock, SOL_SOCKET, SO_LINGER, (void*) &val, optLen); if (res != 0) - result = make_error2(env, res); + result = esock_make_error_errno(env, res); else - result = atom_ok; + result = esock_atom_ok; } else { - result = make_error(env, atom_einval); + result = esock_make_error(env, atom_einval); } return result; @@ -3760,7 +3763,7 @@ ERL_NIF_TERM nsetopt_lvl_ip(ErlNifEnv* env, #endif default: - result = make_error(env, atom_einval); + result = esock_make_error(env, atom_einval); break; } @@ -3826,12 +3829,12 @@ ERL_NIF_TERM nsetopt_lvl_ip_tos(ErlNifEnv* env, int res = socket_setopt(descP->sock, level, IP_TOS, &val, sizeof(val)); if (res != 0) - result = make_error2(env, res); + result = esock_make_error_errno(env, res); else - result = atom_ok; + result = esock_atom_ok; } else { - result = make_error(env, atom_einval); + result = esock_make_error(env, atom_einval); } return result; @@ -3878,7 +3881,7 @@ ERL_NIF_TERM nsetopt_lvl_ipv6(ErlNifEnv* env, #endif default: - result = make_error(env, atom_einval); + result = esock_make_error(env, atom_einval); break; } @@ -3931,7 +3934,7 @@ ERL_NIF_TERM nsetopt_lvl_tcp(ErlNifEnv* env, #endif default: - result = make_error(env, atom_einval); + result = esock_make_error(env, atom_einval); break; } @@ -3999,7 +4002,7 @@ ERL_NIF_TERM nsetopt_lvl_udp(ErlNifEnv* env, #endif default: - result = make_error(env, atom_einval); + result = esock_make_error(env, atom_einval); break; } @@ -4047,7 +4050,7 @@ ERL_NIF_TERM nsetopt_lvl_sctp(ErlNifEnv* env, #endif default: - result = make_error(env, atom_einval); + result = esock_make_error(env, atom_einval); break; } @@ -4105,12 +4108,12 @@ ERL_NIF_TERM nsetopt_str_opt(ErlNifEnv* env, int res = socket_setopt(descP->sock, level, opt, &val, optLen); if (res != 0) - result = make_error2(env, res); + result = esock_make_error_errno(env, res); else - result = atom_ok; + result = esock_atom_ok; } else { - result = make_error(env, atom_einval); + result = esock_make_error(env, atom_einval); } FREE(val); @@ -4136,12 +4139,12 @@ ERL_NIF_TERM nsetopt_bool_opt(ErlNifEnv* env, int res = socket_setopt(descP->sock, level, opt, &ival, sizeof(ival)); if (res != 0) - result = make_error2(env, res); + result = esock_make_error_errno(env, res); else - result = atom_ok; + result = esock_atom_ok; } else { - result = make_error(env, atom_einval); + result = esock_make_error(env, atom_einval); } return result; @@ -4164,12 +4167,12 @@ ERL_NIF_TERM nsetopt_int_opt(ErlNifEnv* env, int res = socket_setopt(descP->sock, level, opt, &val, sizeof(val)); if (res != 0) - result = make_error2(env, res); + result = esock_make_error_errno(env, res); else - result = atom_ok; + result = esock_atom_ok; } else { - result = make_error(env, atom_einval); + result = esock_make_error(env, atom_einval); } return result; @@ -4376,10 +4379,10 @@ ERL_NIF_TERM nif_getopt(ErlNifEnv* env, eIsEncoded = argv[1]; if (!decode_bool(env, eIsEncoded, &isEncoded)) - return make_error(env, atom_einval); + return esock_make_error(env, atom_einval); if (!elevel2level(isEncoded, eLevel, &isOTP, &level)) - return make_error(env, atom_einval); + return esock_make_error(env, atom_einval); return ngetopt(env, descP, isEncoded, isOTP, level, eOpt); } @@ -4431,7 +4434,7 @@ ERL_NIF_TERM ngetopt_otp(ErlNifEnv* env, break; default: - result = make_error(env, atom_einval); + result = esock_make_error(env, atom_einval); break; } @@ -4449,7 +4452,7 @@ ERL_NIF_TERM ngetopt_otp_debug(ErlNifEnv* env, encode_bool(descP->dbg, &eVal); - return make_ok2(env, eVal); + return esock_make_ok2(env, eVal); } @@ -4463,7 +4466,7 @@ ERL_NIF_TERM ngetopt_otp_iow(ErlNifEnv* env, encode_bool(descP->iow, &eVal); - return make_ok2(env, eVal); + return esock_make_ok2(env, eVal); } @@ -4501,11 +4504,11 @@ ERL_NIF_TERM ngetopt_native(ErlNifEnv* env, result = ngetopt_bool_opt(env, descP, level, opt); break; default: - result = make_error(env, atom_einval); + result = esock_make_error(env, atom_einval); break; } } else { - result = make_error(env, atom_einval); + result = esock_make_error(env, atom_einval); } return result; @@ -4525,20 +4528,20 @@ ERL_NIF_TERM ngetopt_native_unspec(ErlNifEnv* env, if (valueSz == 0) { res = sock_getopt(descP->sock, level, opt, NULL, NULL); if (res != 0) - result = make_error2(env, res); + result = esock_make_error_errno(env, res); else - result = atom_ok; + result = esock_atom_ok; } else { ErlNifBinary val; if (ALLOC_BIN(valueSz, &val)) { res = sock_getopt(descP->sock, level, opt, val.data, &valueSz); if (res != 0) { - result = make_error2(env, res); + result = esock_make_error_errno(env, res); } else { if (valueSz < val.size) { if (REALLOC_BIN(&val, valueSz)) { - result = make_ok2(env, MKBIN(env, &val)); + result = esock_make_ok2(env, MKBIN(env, &val)); } else { result = enif_make_badarg(env); } @@ -4598,7 +4601,7 @@ ERL_NIF_TERM ngetopt_level(ErlNifEnv* env, #endif default: - result = make_error(env, atom_einval); + result = esock_make_error(env, atom_einval); break; } @@ -4665,7 +4668,7 @@ ERL_NIF_TERM ngetopt_lvl_socket(ErlNifEnv* env, #endif default: - result = make_error(env, atom_einval); + result = esock_make_error(env, atom_einval); break; } @@ -4719,13 +4722,13 @@ ERL_NIF_TERM ngetopt_lvl_sock_linger(ErlNifEnv* env, &val, &valSz); if (res != 0) { - result = make_error2(env, res); + result = esock_make_error_errno(env, res); } else { ERL_NIF_TERM lOnOff = MKI(env, val.l_onoff); ERL_NIF_TERM lSecs = MKI(env, val.l_linger); ERL_NIF_TERM linger = MKT2(env, lOnOff, lSecs); - result = make_ok2(env, linger); + result = esock_make_ok2(env, linger); } return result; @@ -4808,7 +4811,7 @@ ERL_NIF_TERM ngetopt_lvl_ip(ErlNifEnv* env, #endif default: - result = make_error(env, atom_einval); + result = esock_make_error(env, atom_einval); break; } @@ -4872,7 +4875,7 @@ ERL_NIF_TERM ngetopt_lvl_ip_tos(ErlNifEnv* env, res = sock_getopt(descP->sock, level, IP_TOS, &val, &valSz); if (res != 0) { - result = make_error2(env, res); + result = esock_make_error_errno(env, res); } else { result = encode_ip_tos(env, val); } @@ -4919,7 +4922,7 @@ ERL_NIF_TERM ngetopt_lvl_ipv6(ErlNifEnv* env, #endif default: - result = make_error(env, atom_einval); + result = esock_make_error(env, atom_einval); break; } @@ -4970,7 +4973,7 @@ ERL_NIF_TERM ngetopt_lvl_tcp(ErlNifEnv* env, #endif default: - result = make_error(env, atom_einval); + result = esock_make_error(env, atom_einval); break; } @@ -5034,7 +5037,7 @@ ERL_NIF_TERM ngetopt_lvl_udp(ErlNifEnv* env, #endif default: - result = make_error(env, atom_einval); + result = esock_make_error(env, atom_einval); break; } @@ -5079,7 +5082,7 @@ ERL_NIF_TERM ngetopt_lvl_sctp(ErlNifEnv* env, #endif default: - result = make_error(env, atom_einval); + result = esock_make_error(env, atom_einval); break; } @@ -5132,11 +5135,11 @@ ERL_NIF_TERM ngetopt_str_opt(ErlNifEnv* env, res = sock_getopt(descP->sock, level, opt, &val, &valSz); if (res != 0) { - result = make_error2(env, res); + result = esock_make_error_errno(env, res); } else { ERL_NIF_TERM sval = MKSL(env, val, valSz); - result = make_ok2(env, sval); + result = esock_make_ok2(env, sval); } FREE(val); @@ -5161,11 +5164,11 @@ ERL_NIF_TERM ngetopt_bool_opt(ErlNifEnv* env, res = sock_getopt(descP->sock, level, opt, &val, &valSz); if (res != 0) { - result = make_error2(env, res); + result = esock_make_error_errno(env, res); } else { ERL_NIF_TERM bval = ((val) ? atom_true : atom_false); - result = make_ok2(env, bval); + result = esock_make_ok2(env, bval); } return result; @@ -5188,9 +5191,9 @@ ERL_NIF_TERM ngetopt_int_opt(ErlNifEnv* env, res = sock_getopt(descP->sock, level, opt, &val, &valSz); if (res != 0) { - result = make_error2(env, res); + result = esock_make_error_errno(env, res); } else { - result = make_ok2(env, MKI(env, val)); + result = esock_make_ok2(env, MKI(env, val)); } return result; @@ -5215,7 +5218,7 @@ ERL_NIF_TERM send_check_result(ErlNifEnv* env, cnt_inc(&descP->writePkgCnt, 1); cnt_inc(&descP->writeByteCnt, written); - return atom_ok; + return esock_atom_ok; } else if (written < 0) { @@ -5226,7 +5229,7 @@ ERL_NIF_TERM send_check_result(ErlNifEnv* env, cnt_inc(&descP->writeFails, 1); - return make_error2(env, save_errno); + return esock_make_error_errno(env, save_errno); } else { @@ -5251,7 +5254,7 @@ ERL_NIF_TERM send_check_result(ErlNifEnv* env, SELECT(env, descP->sock, (ERL_NIF_SELECT_WRITE), descP, NULL, sendRef); - return make_ok2(env, enif_make_int(env, written)); + return esock_make_ok2(env, enif_make_int(env, written)); } @@ -5295,7 +5298,7 @@ ERL_NIF_TERM recv_check_result(ErlNifEnv* env, data = MKBIN(env, bufP); - return make_ok3(env, atom_false, data); + return esock_make_ok3(env, atom_false, data); } else { @@ -5308,7 +5311,7 @@ ERL_NIF_TERM recv_check_result(ErlNifEnv* env, data = MKBIN(env, bufP); - return make_ok3(env, atom_true, data); + return esock_make_ok3(env, atom_true, data); } @@ -5345,13 +5348,13 @@ ERL_NIF_TERM recv_check_result(ErlNifEnv* env, (ERL_NIF_SELECT_STOP), descP, NULL, recvRef); - return make_error(env, atom_closed); + return esock_make_error(env, atom_closed); } else if ((save_errno == ERRNO_BLOCK) || (save_errno == EAGAIN)) { - return make_error(env, atom_eagain); + return esock_make_error(env, atom_eagain); } else { - return make_error2(env, save_errno); + return esock_make_error_errno(env, save_errno); } } else { @@ -5369,14 +5372,14 @@ ERL_NIF_TERM recv_check_result(ErlNifEnv* env, data = MKBIN(env, bufP); data = MKSBIN(env, data, 0, read); - return make_ok3(env, atom_true, data); + return esock_make_ok3(env, atom_true, data); } else { /* +++ We got only a part of what was expected +++ * +++ => receive more later. +++ */ - return make_ok3(env, atom_false, MKBIN(env, bufP)); + return esock_make_ok3(env, atom_false, MKBIN(env, bufP)); } } } @@ -5430,13 +5433,13 @@ ERL_NIF_TERM recvfrom_check_result(ErlNifEnv* env, (ERL_NIF_SELECT_STOP), descP, NULL, recvRef); - return make_error(env, atom_closed); + return esock_make_error(env, atom_closed); } else if ((save_errno == ERRNO_BLOCK) || (save_errno == EAGAIN)) { - return make_error(env, atom_eagain); + return esock_make_error(env, atom_eagain); } else { - return make_error2(env, save_errno); + return esock_make_error_errno(env, save_errno); } } else { @@ -5463,7 +5466,7 @@ ERL_NIF_TERM recvfrom_check_result(ErlNifEnv* env, data = MKSBIN(env, data, 0, read); } - return make_ok2(env, MKT3(env, fromDomainT, fromSourceT, data)); + return esock_make_ok2(env, MKT3(env, fromDomainT, fromSourceT, data)); } } @@ -5663,9 +5666,9 @@ char* decode_in4_sockaddr_atomaddr(ErlNifEnv* env, { struct in_addr addr; - if (COMPARE(atom_loopback, eAddr) == 0) { + if (COMPARE(esock_atom_loopback, eAddr) == 0) { addr.s_addr = sock_htonl(INADDR_LOOPBACK); - } else if (COMPARE(atom_any, eAddr) == 0) { + } else if (COMPARE(esock_atom_any, eAddr) == 0) { addr.s_addr = sock_htonl(INADDR_ANY); } else { return str_einval; @@ -5673,11 +5676,11 @@ char* decode_in4_sockaddr_atomaddr(ErlNifEnv* env, sys_memzero((char*) sockAddrP, sizeof(struct sockaddr_in)); #ifndef NO_SA_LEN - sockAddrP->sai.sin_len = sizeof(struct sockaddr_in6); + sockAddrP->sai.sin_len = sizeof(struct sockaddr_in); #endif - sockAddrP->sai.sin_family = AF_INET; - sockAddrP->sai.sin_port = sock_htons(port); - sockAddrP->sai.sin_addr.s_addr = addr.s_addr; + sockAddrP->in4.sin_family = AF_INET; + sockAddrP->in4.sin_port = sock_htons(port); + sockAddrP->in4.sin_addr.s_addr = addr.s_addr; *addrLenP = sizeof(struct sockaddr_in); return NULL; @@ -5708,16 +5711,16 @@ char* decode_in4_sockaddr_addr(ErlNifEnv* env, sys_memzero((char*)sockAddrP, sizeof(struct sockaddr_in)); #ifndef NO_SA_LEN - sockAddrP->sai.sin_len = sizeof(struct sockaddr_in); + sockAddrP->in4.sin_len = sizeof(struct sockaddr_in); #endif - sockAddrP->sai.sin_family = AF_INET; - sockAddrP->sai.sin_port = sock_htons(port); + sockAddrP->in4.sin_family = AF_INET; + sockAddrP->in4.sin_port = sock_htons(port); for (a = 0; a < 4; a++) { if (!GET_INT(env, ip4AddrT[a], &v)) return str_einval; addr[a] = v; } - sys_memcpy(&sockAddrP->sai.sin_addr, &addr, sizeof(addr)); + sys_memcpy(&sockAddrP->in4.sin_addr, &addr, sizeof(addr)); *addrLenP = sizeof(struct sockaddr_in); return NULL; @@ -5789,9 +5792,9 @@ char* decode_in6_sockaddr_atomaddr(ErlNifEnv* env, { const struct in6_addr* addr; - if (COMPARE(atom_loopback, eAddr) == 0) { + if (COMPARE(esock_atom_loopback, eAddr) == 0) { addr = &in6addr_loopback; - } else if (COMPARE(atom_any, eAddr) == 0) { + } else if (COMPARE(esock_atom_any, eAddr) == 0) { addr = &in6addr_any; } else { return str_einval; @@ -5799,13 +5802,13 @@ char* decode_in6_sockaddr_atomaddr(ErlNifEnv* env, sys_memzero((char*)sockAddrP, sizeof(struct sockaddr_in6)); #ifndef NO_SA_LEN - sockAddrP->sai6.sin6_len = sizeof(struct sockaddr_in6); + sockAddrP->in6.sin6_len = sizeof(struct sockaddr_in6); #endif - sockAddrP->sai6.sin6_family = AF_INET6; - sockAddrP->sai6.sin6_port = sock_htons(port); - sockAddrP->sai6.sin6_flowinfo = flowInfo; - sockAddrP->sai6.sin6_scope_id = scopeId; - sockAddrP->sai6.sin6_addr = *addr; + sockAddrP->in6.sin6_family = AF_INET6; + sockAddrP->in6.sin6_port = sock_htons(port); + sockAddrP->in6.sin6_flowinfo = flowInfo; + sockAddrP->in6.sin6_scope_id = scopeId; + sockAddrP->in6.sin6_addr = *addr; *addrLenP = sizeof(struct sockaddr_in6); return NULL; @@ -5839,12 +5842,12 @@ char* decode_in6_sockaddr_addr(ErlNifEnv* env, sys_memzero((char*)sockAddrP, sizeof(struct sockaddr_in6)); #ifndef NO_SA_LEN - sockAddrP->sai6.sin6_len = sizeof(struct sockaddr_in6); + sockAddrP->in6.sin6_len = sizeof(struct sockaddr_in6); #endif - sockAddrP->sai6.sin6_family = AF_INET6; - sockAddrP->sai6.sin6_port = sock_htons(port); - sockAddrP->sai6.sin6_flowinfo = flowInfo; - sockAddrP->sai6.sin6_scope_id = scopeId; + sockAddrP->in6.sin6_family = AF_INET6; + sockAddrP->in6.sin6_port = sock_htons(port); + sockAddrP->in6.sin6_flowinfo = flowInfo; + sockAddrP->in6.sin6_scope_id = scopeId; /* The address tuple is of size 8 * and each element is a two byte integer */ @@ -5854,7 +5857,7 @@ char* decode_in6_sockaddr_addr(ErlNifEnv* env, addr[a*2 ] = ((v >> 8) & 0xFF); addr[a*2+1] = (v & 0xFF); } - sys_memcpy(&sockAddrP->sai6.sin6_addr, &addr, sizeof(addr)); + sys_memcpy(&sockAddrP->in6.sin6_addr, &addr, sizeof(addr)); *addrLenP = sizeof(struct sockaddr_in6); return NULL; @@ -5960,7 +5963,7 @@ void encode_address(ErlNifEnv* env, { short port; - switch (addrP->sa.sa_family) { + switch (addrP->in.sa_family) { /* +++ inet (IPv4) +++ */ @@ -5969,9 +5972,9 @@ void encode_address(ErlNifEnv* env, ERL_NIF_TERM addrT, portT; unsigned int i; ERL_NIF_TERM at4[4]; - char* a4 = (char*) &addrP->sai.sin_addr; + char* a4 = (char*) &addrP->in4.sin_addr; - port = sock_ntohs(addrP->sai.sin_port); + port = sock_ntohs(addrP->in4.sin_port); for (i = 0; i < 4; i++) { at4[i] = MKI(env, a4[i]); } @@ -5981,8 +5984,8 @@ void encode_address(ErlNifEnv* env, portT = MKI(env, port); *sourceT = MKT2(env, addrT, portT); } else { - *domainT = atom_undefined; - *sourceT = atom_undefined; + *domainT = esock_atom_undefined; + *sourceT = esock_atom_undefined; } break; @@ -5995,9 +5998,9 @@ void encode_address(ErlNifEnv* env, ERL_NIF_TERM addrT, portT; unsigned int i; ERL_NIF_TERM at6[8]; - char* a16 = (char*) &addrP->sai6.sin6_addr; + char* a16 = (char*) &addrP->in6.sin6_addr; - port = sock_ntohs(addrP->sai6.sin6_port); + port = sock_ntohs(addrP->in6.sin6_port); /* The address tuple is of size 8 * and each element is a two byte integer */ @@ -6013,8 +6016,8 @@ void encode_address(ErlNifEnv* env, portT = MKI(env, port); *sourceT = MKT2(env, addrT, portT); } else { - *domainT = atom_undefined; - *sourceT = atom_undefined; + *domainT = esock_atom_undefined; + *sourceT = esock_atom_undefined; } break; #endif @@ -6028,13 +6031,13 @@ void encode_address(ErlNifEnv* env, *domainT = MKA(env, "local"); if (addrLen < offsetof(struct sockaddr_un, sun_path)) { - *sourceT = atom_undefined; + *sourceT = esock_atom_undefined; } else { n = addrLen - offsetof(struct sockaddr_un, sun_path); if (255 < n) { - *sourceT = atom_undefined; + *sourceT = esock_atom_undefined; } else { - m = my_strnlen(addrP->sal.sun_path, n); + m = my_strnlen(addrP->un.sun_path, n); #ifdef __linux__ /* Assume that the address is a zero terminated string, * except when the first byte is \0 i.e the string length is 0, @@ -6047,7 +6050,7 @@ void encode_address(ErlNifEnv* env, } #endif - *sourceT = MKSL(env, addrP->sal.sun_path, m); + *sourceT = MKSL(env, addrP->un.sun_path, m); } } } @@ -6055,8 +6058,8 @@ void encode_address(ErlNifEnv* env, #endif default: - *domainT = atom_undefined; - *sourceT = atom_undefined; + *domainT = esock_atom_undefined; + *sourceT = esock_atom_undefined; break; } /* switch (addrP->sa.sa_family) */ @@ -6369,23 +6372,23 @@ ERL_NIF_TERM encode_ip_tos(ErlNifEnv* env, int val) switch (val) { case IPTOS_LOWDELAY: - result = make_ok2(env, atom_lowdelay); + result = esock_make_ok2(env, atom_lowdelay); break; case IPTOS_THROUGHPUT: - result = make_ok2(env, atom_throughput); + result = esock_make_ok2(env, atom_throughput); break; case IPTOS_RELIABILITY: - result = make_ok2(env, atom_reliability); + result = esock_make_ok2(env, atom_reliability); break; case IPTOS_MINCOST: - result = make_ok2(env, atom_mincost); + result = esock_make_ok2(env, atom_mincost); break; default: - result = make_ok2(env, MKI(env, val)); + result = esock_make_ok2(env, MKI(env, val)); break; } @@ -6468,6 +6471,11 @@ int compare_pids(ErlNifEnv* env, } +/* ---------------------------------------------------------------------- + * D e c o d e / E n c o d e F u n c t i o n s + * ---------------------------------------------------------------------- + */ + /* edomain2domain - convert internal (erlang) domain to (proper) domain * * Note that only a subset is supported. @@ -6587,7 +6595,7 @@ BOOLEAN_T emap2netns(ErlNifEnv* env, ERL_NIF_TERM map, char** netns) /* The currently only supported extra option is: netns */ key = enif_make_atom(env, "netns"); - if (!enif_get_map_value(env, map, key, &value)) { + if (!GET_MAP_VAL(env, map, key, &value)) { *netns = NULL; // Just in case... return FALSE; } @@ -6737,77 +6745,409 @@ BOOLEAN_T ehow2how(unsigned int ehow, int* how) } return TRUE; - } +} -#if defined(HAVE_SYS_UN_H) || defined(SO_BINDTODEVICE) -/* strnlen doesn't exist everywhere */ + +/* +++ decode_sockaddr +++ + * + * Decode a socket address - sockaddr. In erlang its represented by + * a map, which has a specific set of attributes, depending on one + * mandatory attribute; family. So depending on the value of the family + * attribute: + * + * local - sockaddr_un: path + * inet - sockaddr_in4: port, addr + * inet6 - sockaddr_in6: port, addr, flowinfo, scope_id + */ +/* static -size_t my_strnlen(const char *s, size_t maxlen) +char* decode_sockaddr(ErlNifEnv* env, + ERL_NIF_TERM eSockAddr, + SocketAddress* sockAddrP, + unsigned int* addrLen) { - size_t i = 0; - while (i < maxlen && s[i] != '\0') - i++; - return i; -} + ERL_NIF_TERM efam; + int fam; + char* res; + + if (!IS_MAP(env, eSockAddr)) + return str_einval; + + if (!GET_MAP_VAL(env, eSockAddr, esock_atom_family, &efam)) + return str_einval; + + if (!decode_domain(env, efam, &fam)) + return str_einval; + + switch (fam) { + case AF_INET: + res = decode_sockaddr_in4(env, eSockAddr, &sockAddrP->in6, addrLen); + break; + +#if defined(HAVE_IN6) && defined(AF_INET6) + case AF_INET6: + res = decode_sockaddr_in6(env, eSockAddr, &sockAddrP->in6, addrLen); + break; #endif +#ifdef HAVE_SYS_UN_H + case AF_UNIX: + res = decode_sockaddr_un(env, eSockAddr, &sockAddrP->un, addrLen); + break; +#endif + + default: + result = str_eafnosupport; + break; -/* Create an ok two (2) tuple in the form: {ok, Any}. - * The second element (Any) is already in the form of an - * ERL_NIF_TERM so all we have to do is create the tuple. + } + + return res; +} +*/ + + + +/* +++ decode_sockaddr_in4 +++ + * + * Decode a IPv4 socket address - sockaddr_in4. In erlang its represented by + * a map, which has a specific set of attributes: + * + * port :: port_numbber() + * addr :: ip4_address() + * + * The erlang module ensures that both of these has values exist, so there + * is no need for any elaborate error handling. */ +/* static -ERL_NIF_TERM make_ok2(ErlNifEnv* env, ERL_NIF_TERM any) +char* decode_sockaddr_in4(ErlNifEnv* env, + ERL_NIF_TERM eSockAddr, + struct sockaddr_in* sockAddrP, + unsigned int* addrLen) { - return MKT2(env, atom_ok, any); + ERL_NIF_TERM eport, eaddr; + short port; + + / * Basic init * / + sys_memzero((char*) sockAddrP, sizeof(struct sockaddr_in)); + +#ifndef NO_SA_LEN + sockAddrP->sin_len = sizeof(struct sockaddr_in); +#endif + + sockAddrP->sin_family = AF_INET; + + / * Extract (e) port number from map * / + if (!GET_MAP_VAL(env, eSockAddr, atom_port, &eport)) + return str_einval; + + / * Decode port number * / + if (!GET_INT(env, eport, &port)) + return str_einval; + sockAddrP->sin_port = sock_htons(port); + + / * Extract (e) address from map * / + if (!GET_MAP_VAL(env, eSockAddr, atom_addr, &eaddr)) + return str_einval; + + / * Decode address * / + if (!decode_ip4_address(env, eaddr, sockAddrP, addrLen)) + return str_einval; + + return NULL; } +*/ -/* Create an ok three (3) tuple in the form: {ok, Val1, Val2}. - * The second (Val1) and third (Val2) elements are already in - * the form of an ERL_NIF_TERM so all we have to do is create - * the tuple. + +/* +++ decode_sockaddr_in6 +++ + * + * Decode a IPv6 socket address - sockaddr_in6. In erlang its represented by + * a map, which has a specific set of attributes: + * + * port :: port_numbber() (integer) + * addr :: ip6_address() (tuple) + * flowinfo :: in6_flow_info() (integer) + * scope_id :: in6_scope_id() (integer) + * + * The erlang module ensures that all of these has values exist, so there + * is no need for any elaborate error handling. */ +/* +#if defined(HAVE_IN6) && defined(AF_INET6) static -ERL_NIF_TERM make_ok3(ErlNifEnv* env, ERL_NIF_TERM val1, ERL_NIF_TERM val2) +char* decode_sockaddr_in6(ErlNifEnv* env, + ERL_NIF_TERM eSockAddr, + struct sockaddr_in6* sockAddrP, + unsigned int* addrLen) { - return MKT3(env, atom_ok, val1, val2); + ERL_NIF_TERM eport, eaddr; + short port; + + / * Basic init * / + sys_memzero((char*) sockAddrP, sizeof(struct sockaddr_in6)); +#ifndef NO_SA_LEN + sockAddrP->sin6_len = sizeof(struct sockaddr_in); +#endif + + sockAddrP->sin6_family = AF_INET6; + + / * *** Extract (e) port number from map *** * / + if (!GET_MAP_VAL(env, eSockAddr, atom_port, &eport)) + return str_einval; + + / * Decode port number * / + if (!GET_INT(env, eport, &port)) + return str_einval; + + sockAddrP->sin6_port = sock_htons(port); + + / * *** Extract (e) flowinfo from map *** * / + if (!GET_MAP_VAL(env, eSockAddr, atom_flowinfo, &eflowInfo)) + return str_einval; + + / * 4: Get the flowinfo * / + if (!GET_UINT(env, eflowInfo, &flowInfo)) + return str_einval; + + sockAddrP->sin6_flowinfo = flowInfo; + + / * *** Extract (e) scope_id from map *** * / + if (!GET_MAP_VAL(env, eSockAddr, atom_scope_id, &escopeId)) + return str_einval; + + / * *** Get the scope_id *** * / + if (!GET_UINT(env, escopeId, &scopeId)) + return str_einval; + + sockAddrP->sin6_scope_id = scopeId; + + / * *** Extract (e) address from map *** * / + if (!GET_MAP_VAL(env, eSockAddr, atom_addr, &eaddr)) + return str_einval; + + / * Decode address * / + if (!decode_ip6_address(env, eaddr, sockAddrP, addrLen)) + return str_einval; + + return NULL; } +#endif +*/ -/* Create an error two (2) tuple in the form: {error, Reason}. - * The second element (Reason) is already in the form of an - * ERL_NIF_TERM so all we have to do is create the tuple. + + +/* +++ decode_sockaddr_un +++ + * + * Decode a Unix Domain socket address - sockaddr_un. In erlang its represented by + * a map, which has a specific set of attributes: + * + * path :: binary() + * + * The erlang module ensures that this value exist, so there + * is no need for any elaborate error handling. */ +/* +#ifdef HAVE_SYS_UN_H static -ERL_NIF_TERM make_error(ErlNifEnv* env, ERL_NIF_TERM reason) +char* decode_sockaddr_un(ErlNifEnv* env, + ERL_NIF_TERM eSockAddr, + struct sockaddr_un* sockAddrP, + unsigned int* addrLen) { - return MKT2(env, atom_error, reason); + ErlNifBinary bin; + ERL_NIF_TERM epath; + unsigned int len; + + / * *** Extract (e) path (a binary) from map *** * / + if (!GET_MAP_VAL(env, eSockAddr, atom_port, &epath)) + return str_einval; + + / * Get the path * / + if (!GET_BIN(env, epath, &bin)) + return str_einval; + + if ((bin.size + +#ifdef __linux__ + / * Make sure the address gets zero terminated + * except when the first byte is \0 because then it is + * sort of zero terminated although the zero termination + * comes before the address... + * This fix handles Linux's nonportable + * abstract socket address extension. + * / + (bin.data[0] == '\0' ? 0 : 1) +#else + 1 +#endif + ) > sizeof(sockaAddrP->sun_path)) + return str_einval; + + + sys_memzero((char*) sockAddrP, sizeof(struct sockaddr_un)); + sockAddrP->sun_family = AF_UNIX; + + sys_memcpy(sockAddrP->sun_path, bin.data, bin.size); + len = offsetof(struct sockaddr_un, sun_path) + bin.size; + +#ifndef NO_SA_LEN + sockAddrP->sun_len = len; +#endif + *addrLen = len: + + return NULL; } +#endif +*/ -/* Create an error two (2) tuple in the form: {error, Reason}. - * The second element, Reason, is a string to be converted into - * an atom. + +/* +++ decode_ip4_address +++ + * + * Decode a IPv4 address. This can be three things: + * + * + Then atom 'any' + * + Then atom 'loopback' + * + An ip4_address() (4 tuple) + * + * Note that this *only* decodes the "address" part of a + * (IPv4) socket address. There are several other things (port). */ +/* static -ERL_NIF_TERM make_error1(ErlNifEnv* env, char* reason) +char* decode_ip4_address(ErlNifEnv* env, + ERL_NIF_TERM eAddr, + struct sockaddr_in* sockAddrP, + unsigned int* addrLen) { - return make_error(env, MKA(env, reason)); + if (IS_ATOM(env, eAddr)) { + / * This is either 'any' or 'loopback' * / + struct in_addr addr; + + if (COMPARE(esock_atom_loopback, eAddr) == 0) { + addr.s_addr = sock_htonl(INADDR_LOOPBACK); + } else if (COMPARE(esock_atom_any, eAddr) == 0) { + addr.s_addr = sock_htonl(INADDR_ANY); + } else { + return str_einval; + } + + sockAddrP->sin_addr.s_addr = addr.s_addr; + *addrLen = sizeof(struct sockaddr_in); + + } else { + / * This is a 4-tuple * / + + const ERL_NIF_TERM* addrt; + int addrtSz; + int a, v; + char addr[4]; + + if (!GET_TUPLE(env, eAddr, &addrtSz, &addrt)) + return str_einval; + + if (addrtSz != 4) + return str_einval; + + for (a = 0; a < 4; a++) { + if (!GET_INT(env, addrt[a], &v)) + return str_einval; + addr[a] = v; + } + + sys_memcpy(&sockAddrP->sin_addr, &addr, sizeof(addr)); + *addrLenP = sizeof(struct sockaddr_in); + + } + + return NULL; } +*/ + -/* Create an error two (2) tuple in the form: {error, Reason}. - * The second element, Reason, is the errno value in its - * basic form (integer) which will (eventually) be converted - * into an atom. +/* +++ decode_ip6_address +++ + * + * Decode a IPv6 address. This can be three things: + * + * + Then atom 'any' + * + Then atom 'loopback' + * + An ip6_address() (8 tuple) + * + * Note that this *only* decodes the "address" part of a + * (IPv6) socket address. There are several other things + * (port, flowinfo and scope_id) that are handled elsewhere). */ +/* +#if defined(HAVE_IN6) && defined(AF_INET6) +static +char* decode_ip6_address(ErlNifEnv* env, + ERL_NIF_TERM eAddr, + struct sockaddr_in6* sockAddrP, + unsigned int* addrLen) +{ + if (IS_ATOM(env, eAddr)) { + / * This is either 'any' or 'loopback' * / + const struct in6_addr* addr; + + if (COMPARE(esock_atom_loopback, eAddr) == 0) { + addr = &in6addr_loopback; + } else if (COMPARE(esock_atom_any, eAddr) == 0) { + addr = &in6addr_any; + } else { + return str_einval; + } + + sockAddrP->sin6_addr = *addr; + *addrLen = sizeof(struct sockaddr_in6); + + } else { + / * This is a 8-tuple * / + + const ERL_NIF_TERM* addrt; + int addrtSz; + int a, v; + char addr[16]; + + if (!GET_TUPLE(env, eAddr, &addrtSz, &addrt)) + return str_einval; + + if (addrtSz != 8) + return str_einval; + + for (a = 0; a < 8; a++) { + if (!GET_INT(env, addrt[a], &v)) + return str_einval; + addr[a*2 ] = ((v >> 8) & 0xFF); + addr[a*2+1] = (v & 0xFF); + } + + sys_memcpy(&sockAddrP->sin6_addr, &addr, sizeof(addr)); + *addrLen = sizeof(struct sockaddr_in6); + + } + + return NULL; +} +#endif +*/ + + + +#if defined(HAVE_SYS_UN_H) || defined(SO_BINDTODEVICE) +/* strnlen doesn't exist everywhere */ static -ERL_NIF_TERM make_error2(ErlNifEnv* env, int err) +size_t my_strnlen(const char *s, size_t maxlen) { - return make_error1(env, erl_errno_id(err)); + size_t i = 0; + while (i < maxlen && s[i] != '\0') + i++; + return i; } +#endif /* Send an error closed message to the specified process: @@ -6893,6 +7233,7 @@ void xabort(const char* expr, } + /* ---------------------------------------------------------------------- * C o u n t e r F u n c t i o n s * ---------------------------------------------------------------------- @@ -7137,89 +7478,6 @@ void socket_down(ErlNifEnv* env, -/* ---------------------------------------------------------------------- - * D e b u g F u n c t i o n s - * ---------------------------------------------------------------------- - */ - -/* - * Print a debug format string *with* both a timestamp and the - * the name of the *current* thread. - */ -static -void dbg_printf( const char* format, ... ) -{ - va_list args; - char f[512 + sizeof(format)]; // This has to suffice... - char stamp[30]; - struct timespec ts; - int res; - - /* - * We should really include self in the printout, so we can se which process - * are executing the code. But then I must change the API.... - * ....something for later. - */ - - if (!dbg_realtime(&ts)) { - if (dbg_timespec2str(stamp, sizeof(stamp), &ts) != 0) { - // res = enif_snprintf(f, sizeof(f), "SOCKET [%s] %s", TSNAME(), format); - res = enif_snprintf(f, sizeof(f), "SOCKET [%s]", format); - } else { - // res = enif_snprintf(f, sizeof(f), "SOCKET [%s] [%s] %s", stamp, TSNAME(), format); - res = enif_snprintf(f, sizeof(f), "SOCKET [%s] %s", stamp, format); - } - - if (res > 0) { - va_start (args, format); - erts_vfprintf (stdout, f, args); // TMP: use enif_vfprintf - va_end (args); - fflush(stdout); - } - } - - return; -} - - -static -int dbg_realtime(struct timespec* tsP) -{ - return clock_gettime(CLOCK_REALTIME, tsP); -} - - - - -/* - * Convert a timespec struct into a readable/printable string - */ -static -int dbg_timespec2str(char *buf, unsigned int len, struct timespec *ts) -{ - int ret, buflen; - struct tm t; - - tzset(); - if (localtime_r(&(ts->tv_sec), &t) == NULL) - return 1; - - ret = strftime(buf, len, "%F %T", &t); - if (ret == 0) - return 2; - len -= ret - 1; - buflen = strlen(buf); - - ret = snprintf(&buf[buflen], len, ".%06ld", ts->tv_nsec/1000); - if (ret >= len) - return 3; - - return 0; -} - - - - /* ---------------------------------------------------------------------- * L o a d / u n l o a d / u p g r a d e F u n c t i o n s * ---------------------------------------------------------------------- @@ -7271,7 +7529,7 @@ BOOLEAN_T extract_item_on_load(ErlNifEnv* env, if (!enif_is_map(env, map)) return FALSE; - if (!enif_get_map_value(env, map, key, val)) + if (!GET_MAP_VAL(env, map, key, val)) return FALSE; return TRUE; @@ -7373,18 +7631,15 @@ int on_load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info) data.numProtoSCTP = 0; /* +++ Misc atoms +++ */ - atom_any = MKA(env, str_any); atom_close = MKA(env, str_close); atom_closed = MKA(env, str_closed); atom_closing = MKA(env, str_closing); atom_debug = MKA(env, str_debug); - atom_error = MKA(env, str_error); atom_false = MKA(env, str_false); atom_global_counters = MKA(env, str_global_counters); atom_in4_sockaddr = MKA(env, str_in4_sockaddr); atom_in6_sockaddr = MKA(env, str_in6_sockaddr); atom_iow = MKA(env, str_iow); - atom_loopback = MKA(env, str_loopback); atom_nif_abort = MKA(env, str_nif_abort); atom_num_dinet = MKA(env, str_num_dinet); atom_num_dinet6 = MKA(env, str_num_dinet6); @@ -7397,11 +7652,28 @@ int on_load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info) atom_num_tdgrams = MKA(env, str_num_tdgrams); atom_num_tseqpkgs = MKA(env, str_num_tseqpkgs); atom_num_tstreams = MKA(env, str_num_tstreams); - atom_ok = MKA(env, str_ok); atom_select = MKA(env, str_select); atom_timeout = MKA(env, str_timeout); atom_true = MKA(env, str_true); - atom_undefined = MKA(env, str_undefined); + + esock_atom_addr = MKA(env, "addr"); + esock_atom_any = MKA(env, "any"); + esock_atom_dgram = MKA(env, "dgram"); + esock_atom_error = MKA(env, "error"); + esock_atom_family = MKA(env, "family"); + esock_atom_flowinfo = MKA(env, "flowinfo"); + esock_atom_inet = MKA(env, "inet"); + esock_atom_inet6 = MKA(env, "inet6"); + esock_atom_local = MKA(env, "local"); + esock_atom_loopback = MKA(env, "loopback"); + esock_atom_ok = MKA(env, "ok"); + esock_atom_path = MKA(env, "path"); + esock_atom_port = MKA(env, "port"); + esock_atom_raw = MKA(env, "raw"); + esock_atom_scope_id = MKA(env, "scope_id"); + esock_atom_seqpacket = MKA(env, "seqpacket"); + esock_atom_stream = MKA(env, "stream"); + esock_atom_undefined = MKA(env, "undefined"); atom_lowdelay = MKA(env, str_lowdelay); atom_throughput = MKA(env, str_throughput); diff --git a/erts/emulator/nifs/common/socket_util.c b/erts/emulator/nifs/common/socket_util.c new file mode 100644 index 0000000000..818e259ae8 --- /dev/null +++ b/erts/emulator/nifs/common/socket_util.c @@ -0,0 +1,659 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 2018-2018. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * %CopyrightEnd% + * + * ---------------------------------------------------------------------- + * Purpose : Utility functions for the socket and net NIF(s). + * ---------------------------------------------------------------------- + * + */ + +#include +#include "socket_int.h" +#include "socket_util.h" +#include "sys.h" + + +/* THIS IS JUST TEMPORARY */ +extern char* erl_errno_id(int error); + + +/* +++ esock_decode_sockaddr +++ + * + * Decode a socket address - sockaddr. In erlang its represented as + * a map, which has a specific set of attributes, depending on one + * mandatory attribute; family. So depending on the value of the family + * attribute: + * + * local - sockaddr_un: path + * inet - sockaddr_in4: port, addr + * inet6 - sockaddr_in6: port, addr, flowinfo, scope_id + */ + +extern +char* esock_decode_sockaddr(ErlNifEnv* env, + ERL_NIF_TERM eSockAddr, + SocketAddress* sockAddrP, + unsigned int* addrLen) +{ + ERL_NIF_TERM efam; + int fam; + char* xres; + + if (!IS_MAP(env, eSockAddr)) + return ESOCK_STR_EINVAL; + + if (!GET_MAP_VAL(env, eSockAddr, esock_atom_family, &efam)) + return ESOCK_STR_EINVAL; + + if ((xres = esock_decode_domain(env, efam, &fam)) != NULL) + return xres; + + switch (fam) { + case AF_INET: + xres = esock_decode_sockaddr_in4(env, eSockAddr, + &sockAddrP->in4, addrLen); + break; + +#if defined(HAVE_IN6) && defined(AF_INET6) + case AF_INET6: + xres = esock_decode_sockaddr_in6(env, eSockAddr, + &sockAddrP->in6, addrLen); + break; +#endif + +#ifdef HAVE_SYS_UN_H + case AF_UNIX: + xres = esock_decode_sockaddr_un(env, eSockAddr, + &sockAddrP->un, addrLen); + break; +#endif + + default: + xres = ESOCK_STR_EAFNOSUPPORT; + break; + + } + + return xres; +} + + + +/* +++ esock_decode_sockaddr_in4 +++ + * + * Decode a IPv4 socket address - sockaddr_in4. In erlang its represented as + * a map, which has a specific set of attributes: + * + * port :: port_numbber() + * addr :: ip4_address() + * + * The erlang module ensures that both of these has values exist, so there + * is no need for any elaborate error handling. + */ + +extern +char* esock_decode_sockaddr_in4(ErlNifEnv* env, + ERL_NIF_TERM eSockAddr, + struct sockaddr_in* sockAddrP, + unsigned int* addrLen) +{ + ERL_NIF_TERM eport, eaddr; + int port; + + /* Basic init */ + sys_memzero((char*) sockAddrP, sizeof(struct sockaddr_in)); + +#ifndef NO_SA_LEN + sockAddrP->sin_len = sizeof(struct sockaddr_in); +#endif + + sockAddrP->sin_family = AF_INET; + + /* Extract (e) port number from map */ + if (!GET_MAP_VAL(env, eSockAddr, esock_atom_port, &eport)) + return ESOCK_STR_EINVAL; + + /* Decode port number */ + if (!GET_INT(env, eport, &port)) + return ESOCK_STR_EINVAL; + + sockAddrP->sin_port = htons(port); + + /* Extract (e) address from map */ + if (!GET_MAP_VAL(env, eSockAddr, esock_atom_addr, &eaddr)) + return ESOCK_STR_EINVAL; + + /* Decode address */ + if (!esock_decode_ip4_address(env, eaddr, sockAddrP, addrLen)) + return ESOCK_STR_EINVAL; + + return NULL; +} + + +/* +++ decode_sockaddr_in6 +++ + * + * Decode a IPv6 socket address - sockaddr_in6. In erlang its represented as + * a map, which has a specific set of attributes: + * + * port :: port_numbber() (integer) + * addr :: ip6_address() (tuple) + * flowinfo :: in6_flow_info() (integer) + * scope_id :: in6_scope_id() (integer) + * + * The erlang module ensures that all of these has values exist, so there + * is no need for any elaborate error handling here. + */ + +#if defined(HAVE_IN6) && defined(AF_INET6) +extern +char* esock_decode_sockaddr_in6(ErlNifEnv* env, + ERL_NIF_TERM eSockAddr, + struct sockaddr_in6* sockAddrP, + unsigned int* addrLen) +{ + ERL_NIF_TERM eport, eaddr, eflowInfo, escopeId; + int port; + unsigned int flowInfo, scopeId; + + /* Basic init */ + sys_memzero((char*) sockAddrP, sizeof(struct sockaddr_in6)); +#ifndef NO_SA_LEN + sockAddrP->sin6_len = sizeof(struct sockaddr_in); +#endif + + sockAddrP->sin6_family = AF_INET6; + + /* *** Extract (e) port number from map *** */ + if (!GET_MAP_VAL(env, eSockAddr, esock_atom_port, &eport)) + return ESOCK_STR_EINVAL; + + /* Decode port number */ + if (!GET_INT(env, eport, &port)) + return ESOCK_STR_EINVAL; + + sockAddrP->sin6_port = htons(port); + + /* *** Extract (e) flowinfo from map *** */ + if (!GET_MAP_VAL(env, eSockAddr, esock_atom_flowinfo, &eflowInfo)) + return ESOCK_STR_EINVAL; + + /* 4: Get the flowinfo */ + if (!GET_UINT(env, eflowInfo, &flowInfo)) + return ESOCK_STR_EINVAL; + + sockAddrP->sin6_flowinfo = flowInfo; + + /* *** Extract (e) scope_id from map *** */ + if (!GET_MAP_VAL(env, eSockAddr, esock_atom_scope_id, &escopeId)) + return ESOCK_STR_EINVAL; + + /* *** Get the scope_id *** */ + if (!GET_UINT(env, escopeId, &scopeId)) + return ESOCK_STR_EINVAL; + + sockAddrP->sin6_scope_id = scopeId; + + /* *** Extract (e) address from map *** */ + if (!GET_MAP_VAL(env, eSockAddr, esock_atom_addr, &eaddr)) + return ESOCK_STR_EINVAL; + + /* Decode address */ + if (!esock_decode_ip6_address(env, eaddr, sockAddrP, addrLen)) + return ESOCK_STR_EINVAL; + + return NULL; +} +#endif + + + +/* +++ esock_decode_sockaddr_un +++ + * + * Decode a Unix Domain socket address - sockaddr_un. In erlang its + * represented as a map, which has a specific set of attributes: + * + * path :: binary() + * + * The erlang module ensures that this value exist, so there + * is no need for any elaborate error handling here. + */ + +#ifdef HAVE_SYS_UN_H +extern +char* esock_decode_sockaddr_un(ErlNifEnv* env, + ERL_NIF_TERM eSockAddr, + struct sockaddr_un* sockAddrP, + unsigned int* addrLen) +{ + ErlNifBinary bin; + ERL_NIF_TERM epath; + unsigned int len; + + /* *** Extract (e) path (a binary) from map *** */ + if (!GET_MAP_VAL(env, eSockAddr, esock_atom_path, &epath)) + return ESOCK_STR_EINVAL; + + /* Get the path */ + if (!GET_BIN(env, epath, &bin)) + return ESOCK_STR_EINVAL; + + if ((bin.size + +#ifdef __linux__ + /* Make sure the address gets zero terminated + * except when the first byte is \0 because then it is + * sort of zero terminated although the zero termination + * comes before the address... + * This fix handles Linux's nonportable + * abstract socket address extension. + */ + (bin.data[0] == '\0' ? 0 : 1) +#else + 1 +#endif + ) > sizeof(sockAddrP->sun_path)) + return ESOCK_STR_EINVAL; + + + sys_memzero((char*) sockAddrP, sizeof(struct sockaddr_un)); + sockAddrP->sun_family = AF_UNIX; + + sys_memcpy(sockAddrP->sun_path, bin.data, bin.size); + len = offsetof(struct sockaddr_un, sun_path) + bin.size; + +#ifndef NO_SA_LEN + sockAddrP->sun_len = len; +#endif + *addrLen = len; + + return NULL; +} +#endif + + + +/* +++ decode_ip4_address +++ + * + * Decode a IPv4 address. This can be three things: + * + * + Then atom 'any' + * + Then atom 'loopback' + * + An ip4_address() (4 tuple) + * + * Note that this *only* decodes the "address" part of a + * (IPv4) socket address. There are several other things (port). + */ + +extern +char* esock_decode_ip4_address(ErlNifEnv* env, + ERL_NIF_TERM eAddr, + struct sockaddr_in* sockAddrP, + unsigned int* addrLen) +{ + if (IS_ATOM(env, eAddr)) { + /* This is either 'any' or 'loopback' */ + struct in_addr addr; + + if (COMPARE(esock_atom_loopback, eAddr) == 0) { + addr.s_addr = htonl(INADDR_LOOPBACK); + } else if (COMPARE(esock_atom_any, eAddr) == 0) { + addr.s_addr = htonl(INADDR_ANY); + } else { + return ESOCK_STR_EINVAL; + } + + sockAddrP->sin_addr.s_addr = addr.s_addr; + *addrLen = sizeof(struct sockaddr_in); + + } else { + /* This is a 4-tuple */ + + const ERL_NIF_TERM* addrt; + int addrtSz; + int a, v; + char addr[4]; + + if (!GET_TUPLE(env, eAddr, &addrtSz, &addrt)) + return ESOCK_STR_EINVAL; + + if (addrtSz != 4) + return ESOCK_STR_EINVAL; + + for (a = 0; a < 4; a++) { + if (!GET_INT(env, addrt[a], &v)) + return ESOCK_STR_EINVAL; + addr[a] = v; + } + + sys_memcpy(&sockAddrP->sin_addr, &addr, sizeof(addr)); + *addrLen = sizeof(struct sockaddr_in); + + } + + return NULL; +} + + + +/* +++ decode_ip6_address +++ + * + * Decode a IPv6 address. This can be three things: + * + * + Then atom 'any' + * + Then atom 'loopback' + * + An ip6_address() (8 tuple) + * + * Note that this *only* decodes the "address" part of a + * (IPv6) socket address. There are several other things + * (port, flowinfo and scope_id) that are handled elsewhere). + */ + +#if defined(HAVE_IN6) && defined(AF_INET6) +extern +char* esock_decode_ip6_address(ErlNifEnv* env, + ERL_NIF_TERM eAddr, + struct sockaddr_in6* sockAddrP, + unsigned int* addrLen) +{ + if (IS_ATOM(env, eAddr)) { + /* This is either 'any' or 'loopback' */ + const struct in6_addr* addr; + + if (COMPARE(esock_atom_loopback, eAddr) == 0) { + addr = &in6addr_loopback; + } else if (COMPARE(esock_atom_any, eAddr) == 0) { + addr = &in6addr_any; + } else { + return ESOCK_STR_EINVAL; + } + + sockAddrP->sin6_addr = *addr; + *addrLen = sizeof(struct sockaddr_in6); + + } else { + /* This is a 8-tuple */ + + const ERL_NIF_TERM* addrt; + int addrtSz; + int a, v; + char addr[16]; + + if (!GET_TUPLE(env, eAddr, &addrtSz, &addrt)) + return ESOCK_STR_EINVAL; + + if (addrtSz != 8) + return ESOCK_STR_EINVAL; + + for (a = 0; a < 8; a++) { + if (!GET_INT(env, addrt[a], &v)) + return ESOCK_STR_EINVAL; + addr[a*2 ] = ((v >> 8) & 0xFF); + addr[a*2+1] = (v & 0xFF); + } + + sys_memcpy(&sockAddrP->sin6_addr, &addr, sizeof(addr)); + *addrLen = sizeof(struct sockaddr_in6); + + } + + return NULL; +} +#endif + + +/* +++ esock_decode_domain +++ + * + * Decode the Erlang form of the 'domain' type, that is: + * + * inet => AF_INET + * inet6 => AF_INET6 + * local => AF_UNIX + * + */ +extern +char* esock_decode_domain(ErlNifEnv* env, + ERL_NIF_TERM eDomain, + int* domain) +{ + char* xres = NULL; + + if (COMPARE(esock_atom_inet, eDomain) == 0) { + *domain = AF_INET; + +#if defined(HAVE_IN6) && defined(AF_INET6) + } else if (COMPARE(esock_atom_inet6, eDomain) == 0) { + *domain = AF_INET6; +#endif + +#ifdef HAVE_SYS_UN_H + } else if (COMPARE(esock_atom_local, eDomain) == 0) { + *domain = AF_UNIX; +#endif + + } else { + *domain = -1; + xres = ESOCK_STR_EAFNOSUPPORT; + } + + return xres; +} + + + +/* +++ esock_encode_domain +++ + * + * Encode the native domain to the Erlang form, that is: + * + * AF_INET => inet + * AF_INET6 => inet6 + * AF_UNIX => local + * + */ +extern +char* esock_encode_domain(ErlNifEnv* env, + int domain, + ERL_NIF_TERM* eDomain) +{ + char* xres = NULL; + + switch (domain) { + case AF_INET: + *eDomain = esock_atom_inet; + break; + +#if defined(HAVE_IN6) && defined(AF_INET6) + case AF_INET6: + *eDomain = esock_atom_inet6; + break; +#endif + +#ifdef HAVE_SYS_UN_H + case AF_UNIX: + *eDomain = esock_atom_local; + break; +#endif + + default: + *eDomain = esock_atom_undefined; // Just in case + xres = ESOCK_STR_EAFNOSUPPORT; + } + + return xres; +} + + + +/* +++ esock_decode_type +++ + * + * Decode the Erlang form of the 'type' type, that is: + * + * stream => SOCK_STREAM + * dgram => SOCK_DGRAM + * raw => SOCK_RAW + * seqpacket => SOCK_SEQPACKET + * + */ +extern +char* esock_decode_type(ErlNifEnv* env, + ERL_NIF_TERM eType, + int* type) +{ + char* xres = NULL; + + if (COMPARE(esock_atom_stream, eType) == 0) { + *type = SOCK_STREAM; + } else if (COMPARE(esock_atom_dgram, eType) == 0) { + *type = SOCK_DGRAM; + } else if (COMPARE(esock_atom_raw, eType) == 0) { + *type = SOCK_RAW; + +#if defined(HAVE_SCTP) + } else if (COMPARE(esock_atom_seqpacket, eType) == 0) { + *type = SOCK_SEQPACKET; +#endif + + } else { + *type = -1; + xres = ESOCK_STR_EAFNOSUPPORT; + } + + return xres; +} + + + +/* +++ esock_decode_domain +++ + * + * Encode the native type to the Erlang form, that is: + * + * SOCK_STREAM => stream + * SOCK_DGRAM => dgram + * SOCK_RAW => raw + * SOCK_SEQPACKET => seqpacket + * + */ +extern +char* esock_encode_type(ErlNifEnv* env, + int type, + ERL_NIF_TERM* eType) +{ + char* xres = NULL; + + switch (type) { + case SOCK_STREAM: + *eType = esock_atom_stream; + break; + + case SOCK_DGRAM: + *eType = esock_atom_dgram; + break; + + case SOCK_RAW: + *eType = esock_atom_raw; + break; + +#if defined(HAVE_SCTP) + case SOCK_SEQPACKET: + *eType = esock_atom_seqpacket; + break; +#endif + + default: + *eType = esock_atom_undefined; // Just in case + xres = ESOCK_STR_EAFNOSUPPORT; + } + + return xres; +} + + + +/* Create an ok two (2) tuple in the form: + * + * {ok, Any} + * + * The second element (Any) is already in the form of an + * ERL_NIF_TERM so all we have to do is create the tuple. + */ +extern +ERL_NIF_TERM esock_make_ok2(ErlNifEnv* env, ERL_NIF_TERM any) +{ + return MKT2(env, esock_atom_ok, any); +} + + +/* Create an ok three (3) tuple in the form: + * + * {ok, Val1, Val2} + * + * The second (Val1) and third (Val2) elements are already in + * the form of an ERL_NIF_TERM so all we have to do is create + * the tuple. + */ +extern +ERL_NIF_TERM esock_make_ok3(ErlNifEnv* env, ERL_NIF_TERM val1, ERL_NIF_TERM val2) +{ + return MKT3(env, esock_atom_ok, val1, val2); +} + + + +/* Create an error two (2) tuple in the form: + * + * {error, Reason} + * + * The second element (Reason) is already in the form of an + * ERL_NIF_TERM so all we have to do is create the tuple. + */ +extern +ERL_NIF_TERM esock_make_error(ErlNifEnv* env, ERL_NIF_TERM reason) +{ + return MKT2(env, esock_atom_error, reason); +} + + + +/* Create an error two (2) tuple in the form: {error, Reason}. + * + * {error, Reason} + * + * The second element, Reason, is the reason string that has + * converted into an atom. + */ +extern +ERL_NIF_TERM esock_make_error_str(ErlNifEnv* env, char* reason) +{ + return esock_make_error(env, MKA(env, reason)); +} + + +/* Create an error two (2) tuple in the form: + * + * {error, Reason} + * + * The second element, Reason, is the errno value in its + * basic form (integer) which has been converted into an atom. + */ +extern +ERL_NIF_TERM esock_make_error_errno(ErlNifEnv* env, int err) +{ + return esock_make_error_str(env, erl_errno_id(err)); +} + + diff --git a/erts/emulator/nifs/common/socket_util.h b/erts/emulator/nifs/common/socket_util.h new file mode 100644 index 0000000000..babfebc17b --- /dev/null +++ b/erts/emulator/nifs/common/socket_util.h @@ -0,0 +1,108 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 2018-2018. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * %CopyrightEnd% + * + * ---------------------------------------------------------------------- + * Purpose : Utility "stuff" for socket and net. + * ---------------------------------------------------------------------- + * + */ + +#ifndef SOCKET_UTIL_H__ +#define SOCKET_UTIL_H__ + +#include +#include "socket_int.h" + +extern +char* esock_decode_sockaddr(ErlNifEnv* env, + ERL_NIF_TERM eSockAddr, + SocketAddress* sockAddrP, + unsigned int* addrLen); + +extern +char* esock_decode_sockaddr_in4(ErlNifEnv* env, + ERL_NIF_TERM eSockAddr, + struct sockaddr_in* sockAddrP, + unsigned int* addrLen); + +#if defined(HAVE_IN6) && defined(AF_INET6) +extern +char* esock_decode_sockaddr_in6(ErlNifEnv* env, + ERL_NIF_TERM eSockAddr, + struct sockaddr_in6* sockAddrP, + unsigned int* addrLen); +#endif + +#ifdef HAVE_SYS_UN_H +extern +char* esock_decode_sockaddr_un(ErlNifEnv* env, + ERL_NIF_TERM eSockAddr, + struct sockaddr_un* sockAddrP, + unsigned int* addrLen); +#endif + +extern +char* esock_decode_ip4_address(ErlNifEnv* env, + ERL_NIF_TERM eAddr, + struct sockaddr_in* sockAddrP, + unsigned int* addrLen); + +#if defined(HAVE_IN6) && defined(AF_INET6) +extern +char* esock_decode_ip6_address(ErlNifEnv* env, + ERL_NIF_TERM eAddr, + struct sockaddr_in6* sockAddrP, + unsigned int* addrLen); +#endif + +extern +char* esock_decode_domain(ErlNifEnv* env, + ERL_NIF_TERM eDomain, + int* domain); + +extern +char* esock_encode_domain(ErlNifEnv* env, + int domain, + ERL_NIF_TERM* eDomain); + +extern +char* esock_decode_type(ErlNifEnv* env, + ERL_NIF_TERM eType, + int* type); + +extern +char* esock_encode_type(ErlNifEnv* env, + int type, + ERL_NIF_TERM* eType); + + +extern +ERL_NIF_TERM esock_make_ok2(ErlNifEnv* env, ERL_NIF_TERM any); +extern +ERL_NIF_TERM esock_make_ok3(ErlNifEnv* env, ERL_NIF_TERM val1, ERL_NIF_TERM val2); + +extern +ERL_NIF_TERM esock_make_error(ErlNifEnv* env, ERL_NIF_TERM reason); +extern +ERL_NIF_TERM esock_make_error_str(ErlNifEnv* env, char* reason); +extern +ERL_NIF_TERM esock_make_error_errno(ErlNifEnv* env, int err); + + +#endif // SOCKET_UTIL_H__ diff --git a/erts/preloaded/src/Makefile b/erts/preloaded/src/Makefile index 213dc2a1a2..fae60a4491 100644 --- a/erts/preloaded/src/Makefile +++ b/erts/preloaded/src/Makefile @@ -36,11 +36,11 @@ include $(ERL_TOP)/lib/kernel/vsn.mk PRE_LOADED_ERL_MODULES = \ erl_prim_loader \ init \ - net \ prim_buffer \ prim_file \ prim_inet \ socket \ + net \ zlib \ prim_zip \ otp_ring0 \ diff --git a/erts/preloaded/src/net.erl b/erts/preloaded/src/net.erl index 29739f4510..6abfc22d3f 100644 --- a/erts/preloaded/src/net.erl +++ b/erts/preloaded/src/net.erl @@ -30,7 +30,7 @@ -export([ gethostname/0, getnameinfo/1, getnameinfo/2, - getaddrinfo/2, + getaddrinfo/1, getaddrinfo/2, if_name2index/1, if_index2name/1, @@ -46,14 +46,6 @@ sleep/1]). -export_type([ - ip_address/0, - ip4_address/0, - ip6_address/0, - in_sockaddr/0, - in4_sockaddr/0, - in6_sockaddr/0, - port_number/0, - address_info/0, name_info/0, @@ -66,35 +58,6 @@ ]). -%% Many of these should be moved to the socket module. --type ip_address() :: ip4_address() | ip6_address(). --type ip4_address() :: {0..255, 0..255, 0..255, 0..255}. --type ip6_address() :: - {0..65535, - 0..65535, - 0..65535, - 0..65535, - 0..65535, - 0..65535, - 0..65535, - 0..65535}. --type uint20() :: 0..16#FFFFF. --type uint32() :: 0..16#FFFFFFFF. --type in6_flow_info() :: uint20(). --type in6_scope_id() :: uint32(). --record(in4_sockaddr, {port = 0 :: port_number(), - addr = any :: any | loopback | ip4_address()}). --type in4_sockaddr() :: #in4_sockaddr{}. --record(in6_sockaddr, {port = 0 :: port_number(), - addr = any :: any | loopback | ip6_address(), - flowinfo = 0 :: in6_flow_info(), - scope_id = 0 :: in6_scope_id()}). --type in6_sockaddr() :: #in6_sockaddr{}. - --type in_sockaddr() :: in4_sockaddr() | in6_sockaddr(). - --type port_number() :: 0..65535. - -type name_info_flags() :: [name_info_flag()|name_info_flag_ext()]. -type name_info_flag() :: namereqd | dgram | @@ -104,10 +67,19 @@ -type name_info_flag_ext() :: idn | idna_allow_unassigned | idna_use_std3_ascii_rules. --record(name_info, {host, service}). --type name_info() :: #name_info{}. --record(address_info, {family, socktype, protocol, addr}). --type address_info() :: #address_info{}. +-type name_info() :: #{host := string(), + service := string()}. +%% -record(name_info, {host, service}). +%% -type name_info() :: #name_info{}. +-type address_info() :: #{family := socket:domain(), + socktype := socket:type(), + protocol := socket:protocol(), + address := socket:sockaddr()}. +%% -record(address_info, {family :: socket:domain(), +%% socktype :: socket:type(), +%% protocol :: socket:protocol(), +%% addr :: undefined | socket:in_sockaddr() | string()}). +%% -type address_info() :: #address_info{}. -type network_interface_name() :: string(). -type network_interface_index() :: non_neg_integer(). @@ -199,38 +171,51 @@ gethostname() -> %% -spec getnameinfo(SockAddr) -> {ok, Info} | {error, Reason} when - SockAddr :: in_sockaddr(), + SockAddr :: socket:sockaddr(), Info :: name_info(), Reason :: term(). -getnameinfo(SockAddr) - when is_record(SockAddr, in4_sockaddr) orelse - is_record(SockAddr, in6_sockaddr) -> +getnameinfo(SockAddr) -> getnameinfo(SockAddr, undefined). -spec getnameinfo(SockAddr, Flags) -> {ok, Info} | {error, Reason} when - SockAddr :: in_sockaddr(), + SockAddr :: socket:sockaddr(), Flags :: name_info_flags() | undefined, Info :: name_info(), Reason :: term(). getnameinfo(SockAddr, [] = _Flags) -> getnameinfo(SockAddr, undefined); -getnameinfo(SockAddr, Flags) - when (is_record(SockAddr, in4_sockaddr) orelse - is_record(SockAddr, in6_sockaddr)) andalso +getnameinfo(#{family := Fam, addr := _Addr} = SockAddr, Flags) + when ((Fam =:= inet) orelse (Fam =:= inet6)) andalso (is_list(Flags) orelse (Flags =:= undefined)) -> + nif_getnameinfo(socket:ensure_sockaddr(SockAddr), Flags); +getnameinfo(#{family := Fam, path := _Path} = SockAddr, Flags) + when (Fam =:= local) andalso (is_list(Flags) orelse (Flags =:= undefined)) -> nif_getnameinfo(SockAddr, Flags). + %% =========================================================================== %% %% getaddrinfo - Network address and service translation %% %% There is also a "hint" argument that we "at some point" should implement. --spec getaddrinfo(Host, Service) -> {ok, Info} | {error, Reason} when +-spec getaddrinfo(Host) -> {ok, Info} | {error, Reason} when + Host :: string(), + Info :: [address_info()], + Reason :: term(). + +getaddrinfo(Host) when is_list(Host) -> + getaddrinfo(Host, undefined). + + +-spec getaddrinfo(Host, undefined) -> {ok, Info} | {error, Reason} when Host :: string(), + Info :: [address_info()], + Reason :: term() + ; (undefined, Service) -> {ok, Info} | {error, Reason} when Service :: string(), Info :: [address_info()], Reason :: term(). diff --git a/erts/preloaded/src/socket.erl b/erts/preloaded/src/socket.erl index 5a0748e8fb..b89fa06da8 100644 --- a/erts/preloaded/src/socket.erl +++ b/erts/preloaded/src/socket.erl @@ -25,7 +25,8 @@ %% Administrative and "global" utility functions -export([ on_load/0, on_load/1, on_load/2, - info/0 + info/0, + ensure_sockaddr/1 ]). -export([ @@ -58,13 +59,14 @@ protocol/0, socket/0, + port_number/0, ip_address/0, ip4_address/0, ip6_address/0, - in_sockaddr/0, - in4_sockaddr/0, - in6_sockaddr/0, - port_number/0, + sockaddr/0, + sockaddr_in4/0, + sockaddr_in6/0, + sockaddr_un/0, accept_flags/0, accept_flag/0, @@ -95,6 +97,8 @@ %% We support only a subset of all protocols: -type protocol() :: ip | tcp | udp | sctp. +-type port_number() :: 0..65535. + -type ip_address() :: ip4_address() | ip6_address(). -type ip4_address() :: {0..255, 0..255, 0..255, 0..255}. @@ -115,22 +119,39 @@ 0..65535}. %% +%% %% Should we do these as maps instead? %% If we do we may need to include the family (domain) in the %% map (as the native type do. See struct sockaddr_in6). +%% +%% What about default values? Such as for port (=0) and addr (=any)? +%% %% --type in_sockaddr() :: in4_sockaddr() | in6_sockaddr(). --record(in4_sockaddr, {port = 0 :: port_number(), - addr = any :: any | loopback | ip4_address()}). --type in4_sockaddr() :: #in4_sockaddr{}. --record(in6_sockaddr, {port = 0 :: port_number(), - addr = any :: any | loopback | ip6_address(), - flowinfo = 0 :: in6_flow_info(), - scope_id = 0 :: in6_scope_id()}). --type in6_sockaddr() :: #in6_sockaddr{}. - --type port_number() :: 0..65535. +-type sockaddr_un() :: #{family := local, + path := binary() | string()}. +-type sockaddr_in4() :: #{family := inet, + port := port_number(), + addr := any | loopback | ip4_address()}. +-type sockaddr_in6() :: #{family := inet6, + port := port_number(), + addr := any | loopback | ip6_address(), + flowinfo := in6_flow_info(), + scope_id := in6_scope_id()}. +-type sockaddr() :: sockaddr_in4() | + sockaddr_in6() | + sockaddr_un(). + +-define(SOCKADDR_IN4_DEFAULTS(A), #{port => 0, + addr => A}). +-define(SOCKADDR_IN4_DEFAULTS, ?SOCKADDR_IN4_DEFAULTS(any)). +-define(SOCKADDR_IN4_DEFAULT(A), (?SOCKADDR_IN4_DEFAULTS(A))#{family => inet}). +-define(SOCKADDR_IN6_DEFAULTS(A), #{port => 0, + addr => A, + flowinfo => 0, + scope_id => 0}). +-define(SOCKADDR_IN6_DEFAULTS, ?SOCKADDR_IN6_DEFAULTS(any)). +-define(SOCKADDR_IN6_DEFAULT(A), (?SOCKADDR_IN6_DEFAULTS(A))#{family => inet6}). %% otp - The option is internal to our (OTP) imeplementation. %% socket - The socket layer (SOL_SOCKET). @@ -651,40 +672,61 @@ default_protocol(Protocol, _) -> Protocol. %% bind - bind a name to a socket %% --spec bind(Socket, FileOrAddr) -> ok | {error, Reason} when - Socket :: socket(), - FileOrAddr :: binary() | string() | in_sockaddr() | any | loopback, - Reason :: term(). +-spec bind(Socket, Addr) -> ok | {error, Reason} when + Socket :: socket(), + Addr :: any | loopback | sockaddr(), + Reason :: term(). -bind(Socket, File) when is_binary(File) -> - if - byte_size(File) =:= 0 -> - {error, einval}; - byte_size(File) =< 255 -> - nif_bind(Socket, File); - true -> - {error, einval} - end; -bind(Socket, File) when is_list(File) andalso (File =/= []) -> - Bin = unicode:characters_to_binary(File, file:native_name_encoding()), - if - byte_size(Bin) =< 255 -> - nif_bind(Socket, Bin); - true -> - {error, einval} - end; bind(#socket{info = #{domain := inet}} = Socket, Addr) when ((Addr =:= any) orelse (Addr =:= loopback)) -> - bind(Socket, #in4_sockaddr{addr = Addr}); + bind(Socket, ?SOCKADDR_IN4_DEFAULT(Addr)); bind(#socket{info = #{domain := inet6}} = Socket, Addr) when ((Addr =:= any) orelse (Addr =:= loopback)) -> - bind(Socket, #in6_sockaddr{addr = Addr}); -bind(#socket{info = #{domain := inet}, ref = SockRef} = _Socket, SockAddr) - when is_record(SockAddr, in4_sockaddr) -> - nif_bind(SockRef, SockAddr); -bind(#socket{info = #{domain := inet6}, ref = SockRef} = _Socket, SockAddr) - when is_record(SockAddr, in6_sockaddr) -> - nif_bind(SockRef, SockAddr). + bind(Socket, ?SOCKADDR_IN6_DEFAULT(Addr)); +bind(Socket, Addr) -> + try + begin + nif_bind(Socket, ensure_sockaddr(Addr)) + end + catch + throw:ERROR -> + ERROR + end. + +%% -spec bind(Socket, FileOrAddr) -> ok | {error, Reason} when +%% Socket :: socket(), +%% FileOrAddr :: binary() | string() | in_sockaddr() | any | loopback, +%% Reason :: term(). + +%% bind(Socket, File) when is_binary(File) -> +%% if +%% byte_size(File) =:= 0 -> +%% {error, einval}; +%% byte_size(File) =< 255 -> +%% nif_bind(Socket, File); +%% true -> +%% {error, einval} +%% end; +%% bind(Socket, File) when is_list(File) andalso (File =/= []) -> +%% Bin = unicode:characters_to_binary(File, file:native_name_encoding()), +%% if +%% byte_size(Bin) =< 255 -> +%% nif_bind(Socket, Bin); +%% true -> +%% {error, einval} +%% end; +%% bind(#socket{info = #{domain := inet}} = Socket, Addr) +%% when ((Addr =:= any) orelse (Addr =:= loopback)) -> +%% bind(Socket, #in4_sockaddr{addr = Addr}); +%% bind(#socket{info = #{domain := inet6}} = Socket, Addr) +%% when ((Addr =:= any) orelse (Addr =:= loopback)) -> +%% bind(Socket, #in6_sockaddr{addr = Addr}); +%% bind(#socket{info = #{domain := inet}, ref = SockRef} = _Socket, SockAddr) +%% when is_record(SockAddr, in4_sockaddr) -> +%% nif_bind(SockRef, SockAddr); +%% bind(#socket{info = #{domain := inet6}, ref = SockRef} = _Socket, SockAddr) +%% when is_record(SockAddr, in6_sockaddr) -> +%% nif_bind(SockRef, SockAddr). @@ -695,7 +737,7 @@ bind(#socket{info = #{domain := inet6}, ref = SockRef} = _Socket, SockAddr) -spec connect(Socket, SockAddr) -> ok | {error, Reason} when Socket :: socket(), - SockAddr :: in_sockaddr(), + SockAddr :: sockaddr(), Reason :: term(). connect(Socket, SockAddr) -> @@ -703,16 +745,15 @@ connect(Socket, SockAddr) -> -spec connect(Socket, SockAddr, Timeout) -> ok | {error, Reason} when Socket :: socket(), - SockAddr :: in_sockaddr(), + SockAddr :: sockaddr(), Timeout :: timeout(), Reason :: term(). connect(_Socket, _SockAddr, Timeout) when (is_integer(Timeout) andalso (Timeout =< 0)) -> {error, timeout}; -connect(#socket{ref = SockRef}, SockAddr, Timeout) - when (is_record(SockAddr, in4_sockaddr) orelse - is_record(SockAddr, in6_sockaddr)) andalso +connect(#socket{ref = SockRef}, #{family := Fam} = SockAddr, Timeout) + when ((Fam =:= inet) orelse (Fam =:= inet6) orelse (Fam =:= local)) andalso ((Timeout =:= infinity) orelse is_integer(Timeout)) -> TS = timestamp(Timeout), case nif_connect(SockRef, SockAddr) of @@ -917,35 +958,40 @@ do_send(SockRef, Data, EFlags, Timeout) -> %% --------------------------------------------------------------------------- %% -sendto(Socket, Data, Flags, DestSockAddr) -> - sendto(Socket, Data, Flags, DestSockAddr, ?SOCKET_SENDTO_TIMEOUT_DEFAULT). +sendto(Socket, Data, Flags, Dest) -> + sendto(Socket, Data, Flags, Dest, ?SOCKET_SENDTO_TIMEOUT_DEFAULT). --spec sendto(Socket, Data, Flags, DestSockAddr, Timeout) -> +-spec sendto(Socket, Data, Flags, Dest, Timeout) -> ok | {error, Reason} when - Socket :: socket(), - Data :: binary(), - Flags :: send_flags(), - DestSockAddr :: null | in_sockaddr(), - Timeout :: timeout(), - Reason :: term(). + Socket :: socket(), + Data :: binary(), + Flags :: send_flags(), + Dest :: null | sockaddr(), + Timeout :: timeout(), + Reason :: term(). sendto(Socket, Data, Flags, DestSockAddr, Timeout) when is_list(Data) -> Bin = erlang:list_to_binary(Data), sendto(Socket, Bin, Flags, DestSockAddr, Timeout); -sendto(#socket{ref = SockRef}, Data, Flags, DestSockAddr, Timeout) +sendto(#socket{ref = SockRef}, Data, Flags, Dest, Timeout) + when is_binary(Data) andalso + is_list(Flags) andalso + (Dest =:= null) andalso + (is_integer(Timeout) orelse (Timeout =:= infinity)) -> + EFlags = enc_send_flags(Flags), + do_sendto(SockRef, Data, EFlags, Dest, Timeout); +sendto(#socket{ref = SockRef}, Data, Flags, #{family := Fam} = Dest, Timeout) when is_binary(Data) andalso is_list(Flags) andalso - (is_record(DestSockAddr, in4_sockaddr) orelse - is_record(DestSockAddr, in6_sockaddr) orelse - (DestSockAddr =:= null)) andalso + ((Fam =:= inet) orelse (Fam =:= inet6) orelse (Fam =:= local)) andalso (is_integer(Timeout) orelse (Timeout =:= infinity)) -> EFlags = enc_send_flags(Flags), - do_sendto(SockRef, Data, EFlags, DestSockAddr, Timeout). + do_sendto(SockRef, Data, EFlags, Dest, Timeout). -do_sendto(SockRef, Data, EFlags, DestSockAddr, Timeout) -> +do_sendto(SockRef, Data, EFlags, Dest, Timeout) -> TS = timestamp(Timeout), SendRef = make_ref(), - case nif_sendto(SockRef, SendRef, Data, EFlags, DestSockAddr) of + case nif_sendto(SockRef, SendRef, Data, EFlags, Dest) of ok -> %% We are done ok; @@ -955,10 +1001,10 @@ do_sendto(SockRef, Data, EFlags, DestSockAddr, Timeout) -> receive {select, SockRef, SendRef, ready_output} when (Written > 0) -> <<_:Written/binary, Rest/binary>> = Data, - do_sendto(SockRef, Rest, EFlags, DestSockAddr, + do_sendto(SockRef, Rest, EFlags, Dest, next_timeout(TS, Timeout)); {select, SockRef, SendRef, ready_output} -> - do_sendto(SockRef, Data, EFlags, DestSockAddr, + do_sendto(SockRef, Data, EFlags, Dest, next_timeout(TS, Timeout)); {nif_abort, SendRef, Reason} -> @@ -973,7 +1019,7 @@ do_sendto(SockRef, Data, EFlags, DestSockAddr, Timeout) -> {error, eagain} -> receive {select, SockRef, SendRef, ready_output} -> - do_sendto(SockRef, Data, EFlags, DestSockAddr, + do_sendto(SockRef, Data, EFlags, Dest, next_timeout(TS, Timeout)) after Timeout -> nif_cancel(SockRef, sendto, SendRef), @@ -1256,7 +1302,7 @@ recvfrom(Socket, BufSz, Timeout) -> BufSz :: non_neg_integer(), Flags :: recv_flags(), Timeout :: timeout(), - Source :: in_sockaddr() | string() | undefined, + Source :: sockaddr() | undefined, Data :: binary(), Reason :: term(). @@ -2061,6 +2107,26 @@ enc_shutdown_how(read_write) -> %% %% =========================================================================== +ensure_sockaddr(#{family := inet} = SockAddr) -> + maps:merge(?SOCKADDR_IN4_DEFAULTS, SockAddr); +ensure_sockaddr(#{family := inet6} = SockAddr) -> + maps:merge(?SOCKADDR_IN6_DEFAULTS, SockAddr); +ensure_sockaddr(#{family := local, path := Path} = SockAddr) + when is_list(Path) andalso + (length(Path) > 0) andalso + (length(Path) =< 255) -> + BinPath = unicode:characters_to_binary(Path, file:native_name_encoding()), + ensure_sockaddr(SockAddr#{path => BinPath}); +ensure_sockaddr(#{family := local, path := Path} = SockAddr) + when is_binary(Path) andalso + (byte_size(Path) > 0) andalso + (byte_size(Path) =< 255) -> + SockAddr; +ensure_sockaddr(_SockAddr) -> + einval(). + + + flush_select_msgs(LSRef, Ref) -> receive {select, LSRef, Ref, _} -> @@ -2136,6 +2202,9 @@ not_supported(What) -> unknown(What) -> error({unknown, What}). +einval() -> + error(einval). + error(Reason) -> throw({error, Reason}). -- cgit v1.2.3 From 978a8a855c57bdfb20d3bcd8a6055968f3be3887 Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Mon, 18 Jun 2018 15:29:53 +0200 Subject: [socket+net-nif] Backup --- erts/emulator/nifs/common/net_nif.c | 1203 ++++--------------------------- erts/emulator/nifs/common/socket_dbg.h | 10 + erts/emulator/nifs/common/socket_int.h | 20 +- erts/emulator/nifs/common/socket_nif.c | 416 +++++------ erts/emulator/nifs/common/socket_util.c | 474 +++++++++++- erts/emulator/nifs/common/socket_util.h | 47 ++ erts/preloaded/ebin/net.beam | Bin 5980 -> 5928 bytes erts/preloaded/ebin/socket.beam | Bin 38764 -> 39704 bytes 8 files changed, 858 insertions(+), 1312 deletions(-) diff --git a/erts/emulator/nifs/common/net_nif.c b/erts/emulator/nifs/common/net_nif.c index 90263d11c2..9a96eff654 100644 --- a/erts/emulator/nifs/common/net_nif.c +++ b/erts/emulator/nifs/common/net_nif.c @@ -26,21 +26,6 @@ #define STATIC_ERLANG_NIF 1 -/* #include */ -/* #include */ -/* #include */ -/* #include */ -/* #include */ -/* #include */ -/* #include */ -/* #include */ -/* #include */ -/* #include */ -/* #include */ -/* #include */ -/* #include */ -/* #include */ - #ifdef HAVE_CONFIG_H #include "config.h" @@ -176,48 +161,13 @@ #include "socket_dbg.h" #include "socket_int.h" +#include "socket_util.h" /* All platforms fail on malloc errors. */ #define FATAL_MALLOC - -/* *** Boolean *type* stuff... *** */ -typedef unsigned int BOOLEAN_T; -#define TRUE 1 -#define FALSE 0 -#define BOOL2STR(__B__) ((__B__) ? "true" : "false") -#define BOOL2ATOM(__B__) ((__B__) ? atom_true : atom_false) - -/* Two byte integer decoding */ -#define get_int16(s) ((((unsigned char*) (s))[0] << 8) | \ - (((unsigned char*) (s))[1])) - -#define SASSERT(e) \ - ((void) ((e) ? 1 : (xabort(#e, __func__, __FILE__, __LINE__), 0))) - - -/* Debug stuff... */ -#define SOCKET_NIF_DEBUG_DEFAULT TRUE - -/* Various defaults... */ -#define SOCKET_DEBUG_DEFAULT TRUE -#define SOCKET_IOW_DEFAULT FALSE - -/* Counters and stuff (Don't know where to sent this stuff anyway) */ -#define SOCKET_NIF_IOW_DEFAULT FALSE - - -/* Used in debug printouts */ -#ifdef __WIN32__ -#define LLU "%I64u" -#else -#define LLU "%llu" -#endif -typedef unsigned long long llu_t; - - #ifdef __WIN32__ #define net_gethostname(__buf__, __bufSz__) gethostname((__buf__), (__bufSz__)) #else @@ -226,15 +176,6 @@ typedef unsigned long long llu_t; -/* Socket stuff */ -// #define INVALID_SOCKET -1 -// #define INVALID_EVENT -1 -// #define SOCKET_ERROR -1 - -// #define SOCKET int -// #define HANDLE long int - - /* *** Misc macros and defines *** */ #ifdef __WIN32__ @@ -243,12 +184,6 @@ typedef unsigned long long llu_t; #define get_errno() errno #endif -// #if defined(TCP_CA_NAME_MAX) -// #define SOCKET_OPT_TCP_CONGESTION_NAME_MAX TCP_CA_NAME_MAX -// #else -/* This is really excessive, but just in case... */ -// #define SOCKET_OPT_TCP_CONGESTION_NAME_MAX 256 -// #endif #define HOSTNAME_LEN 256 #define SERVICE_LEN 256 @@ -260,74 +195,13 @@ typedef unsigned long long llu_t; */ #define NET_MAXHOSTNAMELEN 255 + /* =================================================================== * * * * Various enif macros * * * * =================================================================== */ -/* #define MALLOC(SZ) enif_alloc((SZ)) */ -/* #define FREE(P) enif_free((P)) */ - -/* #define MKA(E,S) enif_make_atom((E), (S)) */ -/* #define MKBIN(E,B) enif_make_binary((E), (B)) */ -/* #define MKI(E,I) enif_make_int((E), (I)) */ -/* #define MKLA(E,A,L) enif_make_list_from_array((E), (A), (L)) */ -/* #define MKEL(E) enif_make_list((E), 0) */ -/* #define MKREF(E) enif_make_ref((E)) */ -/* #define MKS(E,S) enif_make_string((E), (S), ERL_NIF_LATIN1) */ -/* #define MKSL(E,S,L) enif_make_string_len((E), (S), (L), ERL_NIF_LATIN1) */ -/* #define MKSBIN(E,B,ST,SZ) enif_make_sub_binary((E), (B), (ST), (SZ)) */ -/* #define MKT2(E,E1,E2) enif_make_tuple2((E), (E1), (E2)) */ -/* #define MKT3(E,E1,E2,E3) enif_make_tuple3((E), (E1), (E2), (E3)) */ -/* #define MKT4(E,E1,E2,E3,E4) enif_make_tuple4((E), (E1), (E2), (E3), (E4)) */ -/* #define MKT5(E,E1,E2,E3,E4,E5) \ */ -/* enif_make_tuple5((E), (E1), (E2), (E3), (E4), (E5)) */ -/* #define MKT8(E,E1,E2,E3,E4,E5,E6,E7,E8) \ */ -/* enif_make_tuple8((E), (E1), (E2), (E3), (E4), (E5), (E6), (E7), (E8)) */ -/* #define MKTA(E, A, AL) enif_make_tuple_from_array((E), (A), (AL)) */ - -/* #define MCREATE(N) enif_mutex_create((N)) */ -/* #define MDESTROY(M) enif_mutex_destroy((M)) */ -/* #define MLOCK(M) enif_mutex_lock((M)) */ -/* #define MUNLOCK(M) enif_mutex_unlock((M)) */ - -/* #define MONP(E,D,P,M) enif_monitor_process((E), (D), (P), (M)) */ -/* #define DEMONP(E,D,M) enif_demonitor_process((E), (D), (M)) */ - -/* #define SELECT(E,FD,M,O,P,R) \ */ -/* enif_select((E), (FD), (M), (O), (P), (R)) */ -/* #define SELECT_READ(E, DP, P, R) \ */ -/* SELECT((E), (DP)->sock, (ERL_NIF_SELECT_READ), (DP), (P), (R)) */ -/* #define SELECT_WRITE(E, DP, P, R) \ */ -/* SELECT((E), (DP)->sock, (ERL_NIF_SELECT_WRITE), (DP), (P), (R)) */ -/* #define SELECT_STOP(E, DP) \ */ -/* enif_select((E), (DP)->sock, (ERL_NIF_SELECT_STOP), (DP), NULL, atom_undefined) */ - -/* #define IS_ATOM(E, TE) enif_is_atom((E), (TE)) */ -/* #define IS_BIN(E, TE) enif_is_binary((E), (TE)) */ -/* #define IS_NUM(E, TE) enif_is_number((E), (TE)) */ -/* #define IS_TUPLE(E, TE) enif_is_tuple((E), (TE)) */ -/* #define IS_LIST(E, TE) enif_is_list((E), (TE)) */ - -/* #define COMPARE(L, R) enif_compare((L), (R)) */ - -/* #define GET_ATOM_LEN(E, TE, LP) \ */ -/* enif_get_atom_length((E), (TE), (LP), ERL_NIF_LATIN1) */ -/* #define GET_ATOM(E, TE, BP, MAX) \ */ -/* enif_get_atom((E), (TE), (BP), (MAX), ERL_NIF_LATIN1) */ -/* #define GET_BIN(E, TE, BP) enif_inspect_iolist_as_binary((E), (TE), (BP)) */ -/* #define GET_INT(E, TE, IP) enif_get_int((E), (TE), (IP)) */ -/* #define GET_LIST_ELEM(E, L, HP, TP) enif_get_list_cell((E), (L), (HP), (TP)) */ -/* #define GET_LIST_LEN(E, L, LP) enif_get_list_length((E), (L), (LP)) */ -/* #define GET_STR(E, L, B, SZ) \ */ -/* enif_get_string((E), (L), (B), (SZ), ERL_NIF_LATIN1) */ -/* #define GET_UINT(E, TE, IP) enif_get_uint((E), (TE), (IP)) */ -/* #define GET_TUPLE(E, TE, TSZ, TA) enif_get_tuple((E), (TE), (TSZ), (TA)) */ - -/* #define ALLOC_BIN(SZ, BP) enif_alloc_binary((SZ), (BP)) */ -/* #define REALLOC_BIN(SZ, BP) enif_realloc_binary((SZ), (BP)) */ - #ifdef HAVE_SOCKLEN_T # define SOCKLEN_T socklen_t @@ -335,37 +209,26 @@ typedef unsigned long long llu_t; # define SOCKLEN_T size_t #endif -#define NDEBUG( ___COND___ , proto ) \ - if ( ___COND___ ) { \ - dbg_printf proto; \ - fflush(stdout); \ - } -#define NDBG( proto ) NDEBUG( data.debug , proto ) - -/* The general purpose socket address */ -typedef union { - struct sockaddr sa; - - struct sockaddr_in in4; - -#ifdef HAVE_IN6 - struct sockaddr_in6 in6; -#endif - -#ifdef HAVE_SYS_UN_H - struct sockaddr_un un; -#endif +#define NDBG( proto ) ESOCK_DBG_PRINTF( data.debug , proto ) -} SockAddress; typedef struct { BOOLEAN_T debug; } NetData; + +/* =================================================================== * + * * + * Static data * + * * + * =================================================================== */ + + static NetData data; + /* ---------------------------------------------------------------------- * F o r w a r d s * ---------------------------------------------------------------------- @@ -409,10 +272,10 @@ static ERL_NIF_TERM nif_if_names(ErlNifEnv* env, static ERL_NIF_TERM ncommand(ErlNifEnv* env, ERL_NIF_TERM cmd); static ERL_NIF_TERM ngethostname(ErlNifEnv* env); -static ERL_NIF_TERM ngetnameinfo(ErlNifEnv* env, - const SockAddress* saP, - SOCKLEN_T saLen, - int flags); +static ERL_NIF_TERM ngetnameinfo(ErlNifEnv* env, + const SocketAddress* saP, + SOCKLEN_T saLen, + int flags); static ERL_NIF_TERM ngetaddrinfo(ErlNifEnv* env, char* host, char* serv); @@ -434,36 +297,7 @@ static void net_down(ErlNifEnv* env, const ErlNifPid* pid, const ErlNifMonitor* mon); */ -static BOOLEAN_T decode_in_sockaddr(ErlNifEnv* env, - const ERL_NIF_TERM eAddr, - SockAddress* saP, - SOCKLEN_T* saLen); -static BOOLEAN_T decode_in4_sockaddr(ErlNifEnv* env, - const ERL_NIF_TERM* addrt, - SockAddress* saP, - SOCKLEN_T* saLen); -#if defined(HAVE_IN6) && defined(AF_INET6) -static BOOLEAN_T decode_in6_sockaddr(ErlNifEnv* env, - const ERL_NIF_TERM* addrt, - SockAddress* saP, - SOCKLEN_T* saLen); -#endif -static ERL_NIF_TERM encode_in_sockaddr(ErlNifEnv* env, - struct sockaddr* addrP, - SOCKLEN_T addrLen); -static ERL_NIF_TERM encode_in4_sockaddr(ErlNifEnv* env, - struct sockaddr_in* addrP, - SOCKLEN_T addrLen); -#if defined(HAVE_IN6) && defined(AF_INET6) -static ERL_NIF_TERM encode_in6_sockaddr(ErlNifEnv* env, - struct sockaddr_in6* addrP, - SOCKLEN_T addrLen); -#endif -#ifdef HAVE_SYS_UN_H -static ERL_NIF_TERM encode_un_sockaddr(ErlNifEnv* env, - struct sockaddr_un* addrP, - SOCKLEN_T addrLen); -#endif + static BOOLEAN_T decode_nameinfo_flags(ErlNifEnv* env, const ERL_NIF_TERM eflags, int* flags); @@ -490,47 +324,11 @@ static ERL_NIF_TERM encode_address_info_type(ErlNifEnv* env, static ERL_NIF_TERM encode_address_info_proto(ErlNifEnv* env, int proto); -/* static ERL_NIF_TERM make_address_info(ErlNifEnv* env, */ -/* struct addrinfo* addrInfoP); */ -/* static ERL_NIF_TERM make_addrinfo_addr(ErlNifEnv* env, */ -/* struct sockaddr* addrP, */ -/* SOCKLEN_T addrLen); */ - -static ERL_NIF_TERM make_in4_sockaddr(ErlNifEnv* env, - ERL_NIF_TERM port, - ERL_NIF_TERM addr); -#if defined(HAVE_IN6) && defined(AF_INET6) -static ERL_NIF_TERM make_in6_sockaddr(ErlNifEnv* env, - ERL_NIF_TERM port, - ERL_NIF_TERM addr, - ERL_NIF_TERM flowInfo, - ERL_NIF_TERM scopeId); -#endif static ERL_NIF_TERM make_address_info(ErlNifEnv* env, ERL_NIF_TERM fam, ERL_NIF_TERM sockType, ERL_NIF_TERM proto, ERL_NIF_TERM addr); -static ERL_NIF_TERM make_ok2(ErlNifEnv* env, ERL_NIF_TERM val); -// static ERL_NIF_TERM make_ok3(ErlNifEnv* env, ERL_NIF_TERM val1, ERL_NIF_TERM val2); -static ERL_NIF_TERM make_error(ErlNifEnv* env, ERL_NIF_TERM reason); -static ERL_NIF_TERM make_error1(ErlNifEnv* env, char* reason); -static ERL_NIF_TERM make_error2(ErlNifEnv* env, int err); - -#if defined(HAVE_SYS_UN_H) || defined(SO_BINDTODEVICE) -static size_t my_strnlen(const char *s, size_t maxlen); -#endif - -static void dbg_printf( const char* format, ... ); -static int dbg_realtime(struct timespec* tsP); -static int dbg_timespec2str(char *buf, unsigned int len, struct timespec *ts); - -/* -static void xabort(const char* expr, - const char* func, - const char* file, - int line); -*/ static int on_load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info); @@ -560,134 +358,63 @@ static const struct in6_addr in6addr_loopback = /* *** String constants *** */ static char str_address_info[] = "address_info"; -static char str_dccp[] = "dccp"; static char str_debug[] = "debug"; -static char str_dgram[] = "dgram"; -static char str_error[] = "error"; -static char str_false[] = "false"; static char str_idn[] = "idn"; static char str_idna_allow_unassigned[] = "idna_allow_unassigned"; static char str_idna_use_std3_ascii_rules[] = "idna_use_std3_ascii_rules"; -static char str_in4_sockaddr[] = "in4_sockaddr"; -static char str_in6_sockaddr[] = "in6_sockaddr"; -static char str_inet[] = "inet"; -static char str_inet6[] = "inet6"; -static char str_ip[] = "ip"; -static char str_ipv6[] = "ipv6"; static char str_namereqd[] = "namereqd"; static char str_name_info[] = "name_info"; static char str_nofqdn[] = "nofqdn"; static char str_numerichost[] = "numerichost"; static char str_numericserv[] = "numericserv"; -static char str_ok[] = "ok"; -static char str_raw[] = "raw"; -static char str_rdm[] = "rdm"; -static char str_seqpacket[] = "seqpacket"; -static char str_stream[] = "stream"; -static char str_tcp[] = "tcp"; -static char str_true[] = "true"; -static char str_udp[] = "udp"; -static char str_undefined[] = "undefined"; - -// static char str_lowdelay[] = "lowdelay"; -// static char str_throughput[] = "throughput"; -// static char str_reliability[] = "reliability"; -// static char str_mincost[] = "mincost"; /* (special) error string constants */ -// static char str_eafnosupport[] = "eafnosupport"; static char str_eaddrfamily[] = "eaddrfamily"; -static char str_eagain[] = "eagain"; static char str_ebadflags[] = "ebadflags"; static char str_efail[] = "efail"; static char str_efamily[] = "efamily"; static char str_efault[] = "efault"; -static char str_einval[] = "einval"; -// static char str_eisconn[] = "eisconn"; static char str_emem[] = "emem"; static char str_enametoolong[] = "enametoolong"; static char str_enodata[] = "enodata"; static char str_enoname[] = "enoname"; -// static char str_enotclosing[] = "enotclosing"; -// static char str_enotconn[] = "enotconn"; static char str_enxio[] = "enxio"; static char str_eoverflow[] = "eoverflow"; static char str_eservice[] = "eservice"; static char str_esocktype[] = "esocktype"; static char str_esystem[] = "esystem"; -// static char str_exalloc[] = "exalloc"; -// static char str_exbadstate[] = "exbadstate"; -// static char str_exbusy[] = "exbusy"; -// static char str_exmon[] = "exmonitor"; // failed monitor -// static char str_exself[] = "exself"; // failed self -// static char str_exsend[] = "exsend"; // failed send /* *** Atoms *** */ static ERL_NIF_TERM atom_address_info; -static ERL_NIF_TERM atom_dccp; static ERL_NIF_TERM atom_debug; -static ERL_NIF_TERM atom_dgram; -static ERL_NIF_TERM atom_error; -static ERL_NIF_TERM atom_false; static ERL_NIF_TERM atom_idn; static ERL_NIF_TERM atom_idna_allow_unassigned; static ERL_NIF_TERM atom_idna_use_std3_ascii_rules; -static ERL_NIF_TERM atom_in4_sockaddr; -static ERL_NIF_TERM atom_in6_sockaddr; -static ERL_NIF_TERM atom_inet; -static ERL_NIF_TERM atom_inet6; -static ERL_NIF_TERM atom_ip; -static ERL_NIF_TERM atom_ipv6; static ERL_NIF_TERM atom_namereqd; static ERL_NIF_TERM atom_name_info; static ERL_NIF_TERM atom_nofqdn; static ERL_NIF_TERM atom_numerichost; static ERL_NIF_TERM atom_numericserv; -static ERL_NIF_TERM atom_ok; -static ERL_NIF_TERM atom_raw; -static ERL_NIF_TERM atom_rdm; -// static ERL_NIF_TERM atom_select; -static ERL_NIF_TERM atom_stream; -static ERL_NIF_TERM atom_seqpacket; -// static ERL_NIF_TERM atom_timeout; -static ERL_NIF_TERM atom_tcp; -static ERL_NIF_TERM atom_true; -static ERL_NIF_TERM atom_udp; -static ERL_NIF_TERM atom_undefined; - -// static ERL_NIF_TERM atom_lowdelay; -// static ERL_NIF_TERM atom_throughput; -// static ERL_NIF_TERM atom_reliability; -// static ERL_NIF_TERM atom_mincost; - -// static ERL_NIF_TERM atom_eafnosupport; + + static ERL_NIF_TERM atom_eaddrfamily; -static ERL_NIF_TERM atom_eagain; +// static ERL_NIF_TERM atom_eagain; static ERL_NIF_TERM atom_ebadflags; static ERL_NIF_TERM atom_efail; static ERL_NIF_TERM atom_efamily; static ERL_NIF_TERM atom_efault; -static ERL_NIF_TERM atom_einval; -// static ERL_NIF_TERM atom_eisconn; static ERL_NIF_TERM atom_emem; static ERL_NIF_TERM atom_enametoolong; static ERL_NIF_TERM atom_enodata; static ERL_NIF_TERM atom_enoname; -// static ERL_NIF_TERM atom_enotclosing; -// static ERL_NIF_TERM atom_enotconn; static ERL_NIF_TERM atom_enxio; static ERL_NIF_TERM atom_eoverflow; static ERL_NIF_TERM atom_eservice; static ERL_NIF_TERM atom_esocktype; static ERL_NIF_TERM atom_esystem; -// static ERL_NIF_TERM atom_exalloc; -// static ERL_NIF_TERM atom_exbadstate; -// static ERL_NIF_TERM atom_exbusy; -// static ERL_NIF_TERM atom_exmon; -// static ERL_NIF_TERM atom_exself; -// static ERL_NIF_TERM atom_exsend; + /* *** net *** */ static ErlNifResourceType* net; @@ -738,7 +465,7 @@ ERL_NIF_TERM nif_is_loaded(ErlNifEnv* env, if (argc != 0) return enif_make_badarg(env); - return atom_true; + return esock_atom_true; } @@ -755,13 +482,13 @@ ERL_NIF_TERM nif_info(ErlNifEnv* env, { ERL_NIF_TERM info, tmp; - NDBG( ("info -> entry\r\n") ); + NDBG( ("NET", "info -> entry\r\n") ); tmp = enif_make_new_map(env); if (!enif_make_map_put(env, tmp, atom_debug, BOOL2ATOM(data.debug), &info)) info = tmp; - NDBG( ("info -> done: %T\r\n", info) ); + NDBG( ("NET", "info -> done: %T\r\n", info) ); return info; } @@ -787,18 +514,18 @@ ERL_NIF_TERM nif_command(ErlNifEnv* env, { ERL_NIF_TERM ecmd, result; - NDBG( ("command -> entry (%d)\r\n", argc) ); + NDBG( ("NET", "command -> entry (%d)\r\n", argc) ); if (argc != 1) return enif_make_badarg(env); ecmd = argv[0]; - NDBG( ("command -> ecmd: %T\r\n", ecmd) ); + NDBG( ("NET", "command -> ecmd: %T\r\n", ecmd) ); result = ncommand(env, ecmd); - NDBG( ("command -> result: %T\r\n", result) ); + NDBG( ("NET", "command -> result: %T\r\n", result) ); return result; } @@ -819,19 +546,19 @@ ERL_NIF_TERM ncommand(ErlNifEnv* env, if (IS_TUPLE(env, cmd)) { /* Could be the debug tuple */ if (!GET_TUPLE(env, cmd, &tsz, &t)) - return make_error(env, atom_einval); + return esock_make_error(env, esock_atom_einval); if (tsz != 2) - return make_error(env, atom_einval); + return esock_make_error(env, esock_atom_einval); /* First element should be the atom 'debug' */ if (COMPARE(t[0], atom_debug) != 0) - return make_error(env, atom_einval); + return esock_make_error(env, esock_atom_einval); return decode_bool(env, t[1], &data.debug); } else { - return make_error(env, atom_einval); + return esock_make_error(env, esock_atom_einval); } } @@ -852,14 +579,14 @@ ERL_NIF_TERM nif_gethostname(ErlNifEnv* env, { ERL_NIF_TERM result; - NDBG( ("nif_gethostname -> entry (%d)\r\n", argc) ); + NDBG( ("NET", "nif_gethostname -> entry (%d)\r\n", argc) ); if (argc != 0) return enif_make_badarg(env); result = ngethostname(env); - NDBG( ("nif_gethostname -> done when result: %T\r\n", result) ); + NDBG( ("NET", "nif_gethostname -> done when result: %T\r\n", result) ); return result; } @@ -874,27 +601,27 @@ ERL_NIF_TERM ngethostname(ErlNifEnv* env) res = net_gethostname(buf, sizeof(buf)); - NDBG( ("ngethostname -> gethostname res: %d\r\n", res) ); + NDBG( ("NET", "ngethostname -> gethostname res: %d\r\n", res) ); switch (res) { case 0: - result = make_ok2(env, MKS(env, buf)); + result = esock_make_ok2(env, MKS(env, buf)); break; case EFAULT: - result = make_error(env, atom_efault); + result = esock_make_error(env, atom_efault); break; case EINVAL: - result = make_error(env, atom_einval); + result = esock_make_error(env, esock_atom_einval); break; case ENAMETOOLONG: - result = make_error(env, atom_enametoolong); + result = esock_make_error(env, atom_enametoolong); break; default: - result = make_error(env, MKI(env, res)); + result = esock_make_error(env, MKI(env, res)); break; } @@ -920,33 +647,35 @@ ERL_NIF_TERM nif_getnameinfo(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { - ERL_NIF_TERM result, eSockAddr; - ERL_NIF_TERM eFlags; - int flags = 0; // Just in case... - SockAddress sa; - SOCKLEN_T saLen = 0; // Just in case... + ERL_NIF_TERM result; + ERL_NIF_TERM eSockAddr, eFlags; + int flags = 0; // Just in case... + SocketAddress sa; + SOCKLEN_T saLen = 0; // Just in case... - NDBG( ("nif_getnameinfo -> entry (%d)\r\n", argc) ); + NDBG( ("NET", "nif_getnameinfo -> entry (%d)\r\n", argc) ); if (argc != 2) return enif_make_badarg(env); eSockAddr = argv[0]; eFlags = argv[1]; - NDBG( ("nif_getnameinfo -> " + NDBG( ("NET", + "nif_getnameinfo -> " "\r\n SockAddr: %T" "\r\n Flags: %T" "\r\n", eSockAddr, eFlags) ); - if (!decode_nameinfo_flags(env, eFlags, &flags)) + if (!esock_decode_sockaddr(env, eSockAddr, &sa, &saLen)) return enif_make_badarg(env); - if (!decode_in_sockaddr(env, eSockAddr, &sa, &saLen)) + if (!decode_nameinfo_flags(env, eFlags, &flags)) return enif_make_badarg(env); result = ngetnameinfo(env, &sa, saLen, flags); - NDBG( ("nif_getnameinfo -> done when result: " + NDBG( ("NET", + "nif_getnameinfo -> done when result: " "\r\n %T\r\n", result) ); return result; @@ -958,10 +687,10 @@ ERL_NIF_TERM nif_getnameinfo(ErlNifEnv* env, * service info. */ static -ERL_NIF_TERM ngetnameinfo(ErlNifEnv* env, - const SockAddress* saP, - SOCKLEN_T saLen, - int flags) +ERL_NIF_TERM ngetnameinfo(ErlNifEnv* env, + const SocketAddress* saP, + SOCKLEN_T saLen, + int flags) { ERL_NIF_TERM result; char host[HOSTNAME_LEN]; @@ -974,7 +703,7 @@ ERL_NIF_TERM ngetnameinfo(ErlNifEnv* env, serv, servLen, flags); - NDBG( ("ngetnameinfo -> res: %d\r\n", res) ); + NDBG( ("NET", "ngetnameinfo -> res: %d\r\n", res) ); switch (res) { case 0: @@ -983,44 +712,44 @@ ERL_NIF_TERM ngetnameinfo(ErlNifEnv* env, atom_name_info, MKS(env, host), MKS(env, serv)); - result = make_ok2(env, info); + result = esock_make_ok2(env, info); } break; case EAI_AGAIN: - result = make_error(env, atom_eagain); + result = esock_make_error(env, esock_atom_eagain); break; case EAI_BADFLAGS: - result = make_error(env, atom_ebadflags); + result = esock_make_error(env, atom_ebadflags); break; case EAI_FAIL: - result = make_error(env, atom_efail); + result = esock_make_error(env, atom_efail); break; case EAI_FAMILY: - result = make_error(env, atom_efamily); + result = esock_make_error(env, atom_efamily); break; case EAI_MEMORY: - result = make_error(env, atom_emem); + result = esock_make_error(env, atom_emem); break; case EAI_NONAME: - result = make_error(env, atom_enoname); + result = esock_make_error(env, atom_enoname); break; case EAI_OVERFLOW: - result = make_error(env, atom_eoverflow); + result = esock_make_error(env, atom_eoverflow); break; case EAI_SYSTEM: - result = make_error2(env, get_errno()); + result = esock_make_error_errno(env, get_errno()); break; default: - result = make_error(env, atom_einval); + result = esock_make_error(env, esock_atom_einval); break; } @@ -1051,7 +780,7 @@ ERL_NIF_TERM nif_getaddrinfo(ErlNifEnv* env, char* servName; // struct addrinfo* hints; - NDBG( ("nif_getaddrinfo -> entry (%d)\r\n", argc) ); + NDBG( ("NET", "nif_getaddrinfo -> entry (%d)\r\n", argc) ); if (argc != 3) { return enif_make_badarg(env); @@ -1060,7 +789,8 @@ ERL_NIF_TERM nif_getaddrinfo(ErlNifEnv* env, eServName = argv[1]; // eHints = argv[2]; - NDBG( ("nif_getaddrinfo -> " + NDBG( ("NET", + "nif_getaddrinfo -> " "\r\n ehost: %T" "\r\n eservice: %T" "\r\n ehints: %T" @@ -1093,7 +823,8 @@ ERL_NIF_TERM nif_getaddrinfo(ErlNifEnv* env, FREE(hints); */ - NDBG( ("nif_getaddrinfo -> done when result: " + NDBG( ("NET", + "nif_getaddrinfo -> done when result: " "\r\n %T\r\n", result) ); return result; @@ -1109,7 +840,7 @@ ERL_NIF_TERM ngetaddrinfo(ErlNifEnv* env, struct addrinfo* addrInfoP; int res; - NDBG( ("ngetaddrinfo -> entry with" + NDBG( ("NET", "ngetaddrinfo -> entry with" "\r\n host: %s" "\r\n serv: %s" "\r\n", @@ -1118,63 +849,63 @@ ERL_NIF_TERM ngetaddrinfo(ErlNifEnv* env, res = getaddrinfo(host, serv, NULL, &addrInfoP); - NDBG( ("ngetaddrinfo -> res: %d\r\n", res) ); + NDBG( ("NET", "ngetaddrinfo -> res: %d\r\n", res) ); switch (res) { case 0: { ERL_NIF_TERM addrInfo = encode_address_infos(env, addrInfoP); freeaddrinfo(addrInfoP); - result = make_ok2(env, addrInfo); + result = esock_make_ok2(env, addrInfo); } break; case EAI_ADDRFAMILY: - result = make_error(env, atom_eaddrfamily); + result = esock_make_error(env, atom_eaddrfamily); break; case EAI_AGAIN: - result = make_error(env, atom_eagain); + result = esock_make_error(env, esock_atom_eagain); break; case EAI_BADFLAGS: - result = make_error(env, atom_ebadflags); + result = esock_make_error(env, atom_ebadflags); break; case EAI_FAIL: - result = make_error(env, atom_efail); + result = esock_make_error(env, atom_efail); break; case EAI_FAMILY: - result = make_error(env, atom_efamily); + result = esock_make_error(env, atom_efamily); break; case EAI_MEMORY: - result = make_error(env, atom_emem); + result = esock_make_error(env, atom_emem); break; case EAI_NODATA: - result = make_error(env, atom_enodata); + result = esock_make_error(env, atom_enodata); break; case EAI_NONAME: - result = make_error(env, atom_enoname); + result = esock_make_error(env, atom_enoname); break; case EAI_SERVICE: - result = make_error(env, atom_eservice); + result = esock_make_error(env, atom_eservice); break; case EAI_SOCKTYPE: - result = make_error(env, atom_esocktype); + result = esock_make_error(env, atom_esocktype); break; case EAI_SYSTEM: - result = make_error(env, atom_esystem); + result = esock_make_error(env, atom_esystem); break; default: - result = make_error(env, atom_einval); + result = esock_make_error(env, esock_atom_einval); break; } @@ -1200,23 +931,24 @@ ERL_NIF_TERM nif_if_name2index(ErlNifEnv* env, ERL_NIF_TERM eifn, result; char ifn[IF_NAMESIZE+1]; - NDBG( ("nif_if_name2index -> entry (%d)\r\n", argc) ); + NDBG( ("NET", "nif_if_name2index -> entry (%d)\r\n", argc) ); if (argc != 1) { return enif_make_badarg(env); } eifn = argv[0]; - NDBG( ("nif_if_name2index -> " + NDBG( ("NET", + "nif_if_name2index -> " "\r\n Ifn: %T" "\r\n", argv[0]) ); if (0 >= GET_STR(env, eifn, ifn, sizeof(ifn))) - return make_error2(env, atom_einval); + return esock_make_error(env, esock_atom_einval); result = nif_name2index(env, ifn); - NDBG( ("nif_if_name2index -> done when result: %T\r\n", result) ); + NDBG( ("NET", "nif_if_name2index -> done when result: %T\r\n", result) ); return result; } @@ -1229,16 +961,16 @@ ERL_NIF_TERM nif_name2index(ErlNifEnv* env, { unsigned int idx; - NDBG( ("nif_name2index -> entry with ifn: %s\r\n", ifn) ); + NDBG( ("NET", "nif_name2index -> entry with ifn: %s\r\n", ifn) ); idx = if_nametoindex(ifn); - NDBG( ("nif_name2index -> idx: %d\r\n", idx) ); + NDBG( ("NET", "nif_name2index -> idx: %d\r\n", idx) ); if (idx == 0) - return make_error2(env, get_errno()); + return esock_make_error_errno(env, get_errno()); else - return make_ok2(env, MKI(env, idx)); + return esock_make_ok2(env, MKI(env, idx)); } @@ -1262,20 +994,20 @@ ERL_NIF_TERM nif_if_index2name(ErlNifEnv* env, ERL_NIF_TERM result; unsigned int idx; - NDBG( ("nif_if_index2name -> entry (%d)\r\n", argc) ); + NDBG( ("NET", "nif_if_index2name -> entry (%d)\r\n", argc) ); if ((argc != 1) || !GET_UINT(env, argv[0], &idx)) { return enif_make_badarg(env); } - NDBG( ("nif_index2name -> " + NDBG( ("NET", "nif_index2name -> " "\r\n Idx: %T" "\r\n", argv[0]) ); result = nif_index2name(env, idx); - NDBG( ("nif_if_index2name -> done when result: %T\r\n", result) ); + NDBG( ("NET", "nif_if_index2name -> done when result: %T\r\n", result) ); return result; } @@ -1293,9 +1025,9 @@ ERL_NIF_TERM nif_index2name(ErlNifEnv* env, return enif_make_badarg(env); // PLACEHOLDER if (NULL != if_indextoname(idx, ifn)) { - result = make_ok2(env, MKS(env, ifn)); + result = esock_make_ok2(env, MKS(env, ifn)); } else { - result = make_error(env, atom_enxio); + result = esock_make_error(env, atom_enxio); } FREE(ifn); @@ -1320,7 +1052,7 @@ ERL_NIF_TERM nif_if_names(ErlNifEnv* env, { ERL_NIF_TERM result; - NDBG( ("nif_if_names -> entry (%d)\r\n", argc) ); + NDBG( ("NET", "nif_if_names -> entry (%d)\r\n", argc) ); if (argc != 0) { return enif_make_badarg(env); @@ -1328,7 +1060,7 @@ ERL_NIF_TERM nif_if_names(ErlNifEnv* env, result = nif_names(env); - NDBG( ("nif_if_names -> done when result: %T\r\n", result) ); + NDBG( ("NET", "nif_if_names -> done when result: %T\r\n", result) ); return result; } @@ -1341,10 +1073,10 @@ ERL_NIF_TERM nif_names(ErlNifEnv* env) ERL_NIF_TERM result; struct if_nameindex* ifs = if_nameindex(); - NDBG( ("nif_names -> ifs: 0x%lX\r\n", ifs) ); + NDBG( ("NET", "nif_names -> ifs: 0x%lX\r\n", ifs) ); if (ifs == NULL) { - result = make_error2(env, get_errno()); + result = esock_make_error_errno(env, get_errno()); } else { /* * We got some interfaces: @@ -1360,7 +1092,7 @@ ERL_NIF_TERM nif_names(ErlNifEnv* env) */ unsigned int len = nif_names_length(ifs); - NDBG( ("nif_names -> len: %d\r\n", len) ); + NDBG( ("NET", "nif_names -> len: %d\r\n", len) ); if (len > 0) { ERL_NIF_TERM* array = MALLOC(len * sizeof(ERL_NIF_TERM)); @@ -1372,10 +1104,10 @@ ERL_NIF_TERM nif_names(ErlNifEnv* env) MKS(env, ifs[i].if_name)); } - result = make_ok2(env, MKLA(env, array, len)); + result = esock_make_ok2(env, MKLA(env, array, len)); FREE(array); } else { - result = make_ok2(env, enif_make_list(env, 0)); + result = esock_make_ok2(env, enif_make_list(env, 0)); } } @@ -1394,7 +1126,7 @@ unsigned int nif_names_length(struct if_nameindex* p) while (!done) { - NDBG( ("nif_names_length -> %d: " + NDBG( ("NET", "nif_names_length -> %d: " "\r\n if_index: %d" "\r\n if_name: 0x%lX" "\r\n", len, p[len].if_index, p[len].if_name) ); @@ -1415,357 +1147,6 @@ unsigned int nif_names_length(struct if_nameindex* p) * ---------------------------------------------------------------------- */ -/* Decode an in_sockaddr (the socket address). - * This is the (erlang) type: in_sockaddr(), which is either - * a in4_sockaddr() (tuple of size 3) or a in6_sockaddr() - * (tuple of size 5). See the net erlang module for details. - * - * We first detect which which of the tuples it is: - * - Size 3: maybe in4_sockaddr - * - Size 5: maybe in6_sockaddr - */ -static -BOOLEAN_T decode_in_sockaddr(ErlNifEnv* env, - const ERL_NIF_TERM eAddr, - SockAddress* saP, - SOCKLEN_T* saLen) -{ - const ERL_NIF_TERM* addrt; - int addrtSz; - - if (!GET_TUPLE(env, eAddr, &addrtSz, &addrt)) - return FALSE; - - switch (addrtSz) { - case 3: - return decode_in4_sockaddr(env, addrt, saP, saLen); - break; - -#ifdef HAVE_IN6 - case 5: - return decode_in6_sockaddr(env, addrt, saP, saLen); - break; -#endif - - default: - return FALSE; - break; - } - -} - - -/* Decode an in4_sockaddr record. This is a tuple of size 3. - * The size has already been verified, but not its content. - * So, the first element should be the atom 'in4_sockaddr'. - * The second the port number, an integer. And the third, - * the actual ip address, a 4-tuple. - */ -static -BOOLEAN_T decode_in4_sockaddr(ErlNifEnv* env, - const ERL_NIF_TERM* addrt, - SockAddress* saP, - SOCKLEN_T* saLen) -{ - unsigned int len; - char tag[16]; // Just in case... - int port; - int ipAddrSz; - const ERL_NIF_TERM* ipAddrT; - int a, v; - char addr[4]; - - /* The first element: Verify record tag (atom()): in4_sockaddr */ - - if (!(GET_ATOM_LEN(env, addrt[0], &len) && - (len > 0) && - (len <= (sizeof(tag))))) - return FALSE; - - if (!GET_ATOM(env, addrt[0], tag, sizeof(tag))) - return FALSE; - - if (strncmp(tag, "in4_sockaddr", len) != 0) - return FALSE; - - - /* Get second element: port number (integer) */ - - if (!GET_INT(env, addrt[1], &port)) - return FALSE; - - - /* And finally, get the third element, the ip address (a 4 tuple) */ - - if (!GET_TUPLE(env, addrt[2], &ipAddrSz, &ipAddrT)) - return FALSE; - - if (ipAddrSz != 4) - return FALSE; - - - /* And finally initialize the sockaddr structure (and size) */ - sys_memzero((char*)saP, sizeof(struct sockaddr_in)); - saP->in4.sin_family = AF_INET; - saP->in4.sin_port = htons(port); - for (a = 0; a < 4; a++) { - if (!GET_INT(env, ipAddrT[a], &v)) - return FALSE; - addr[a] = v; - } - sys_memcpy(&saP->in4.sin_addr, &addr, sizeof(addr)); - *saLen = sizeof(struct sockaddr_in); - return TRUE; -} - - - -#if defined(HAVE_IN6) && defined(AF_INET6) -/* Decode an in6_sockaddr record. This is a tuple of size 5. - * The size has already been verified, but not its content. - * So, the first element should be the atom 'in6_sockaddr'. - * The second the port number, an integer. The third, the - * actual ip address, a 8-tuple. The forth, the flowinfo, - * an integer. And finally, the fifth element, the scope_id, - * also an integer. *Not used here*. - */ -static -BOOLEAN_T decode_in6_sockaddr(ErlNifEnv* env, - const ERL_NIF_TERM* addrt, - SockAddress* saP, - SOCKLEN_T* saLen) -{ - unsigned int len; - char tag[16]; // Just in case... - int port; - int ipAddrSz; - const ERL_NIF_TERM* ipAddrT; - int flowInfo; - int a, v; - char addr[16]; - - - /* The first element: Verify record tag (atom()): in6_sockaddr */ - - if (!(GET_ATOM_LEN(env, addrt[0], &len) && - (len > 0) && - (len <= (sizeof(tag))))) - return FALSE; - - if (!GET_ATOM(env, addrt[0], tag, sizeof(tag))) - return FALSE; - - if (strncmp(tag, "in6_sockaddr", len) != 0) - return FALSE; - - - /* Get second element: port number (integer) */ - - if (!GET_INT(env, addrt[1], &port)) - return FALSE; - - - /* Get the third element, the ip address (a 8 tuple) */ - - if (!GET_TUPLE(env, addrt[2], &ipAddrSz, &ipAddrT)) - return FALSE; - - if (ipAddrSz != 8) - return FALSE; - - - /* And finally, get the forth element, the flowinfo (integer) */ - - if (!GET_INT(env, addrt[3], &flowInfo)) - return FALSE; - - - /* And finally initialize the sockaddr structure (and size) */ - - sys_memzero((char*)saP, sizeof(struct sockaddr_in6)); - saP->in6.sin6_family = AF_INET6; - saP->in6.sin6_port = htons(port); - saP->in6.sin6_flowinfo = flowInfo; - /* The address tuple is of size 8 - * and each element is a two byte integer - */ - for (a = 0; a < 8; a++) { - if (!GET_INT(env, addrt[a], &v)) - return FALSE; - addr[a*2 ] = ((v >> 8) & 0xFF); - addr[a*2+1] = (v & 0xFF); - } - sys_memcpy(&saP->in6.sin6_addr, &addr, sizeof(addr)); - *saLen = sizeof(struct sockaddr_in6); - - return TRUE; -} -#endif - - - -static -ERL_NIF_TERM encode_in_sockaddr(ErlNifEnv* env, - struct sockaddr* addrP, - SOCKLEN_T addrLen) -{ - SockAddress* sockAddrP = (SockAddress*) addrP; - ERL_NIF_TERM sockAddr; - - switch (sockAddrP->sa.sa_family) { - case AF_INET: - sockAddr = encode_in4_sockaddr(env, &sockAddrP->in4, addrLen); - break; - -#if defined(HAVE_IN6) && defined(AF_INET6) - case AF_INET6: - sockAddr = encode_in6_sockaddr(env, &sockAddrP->in6, addrLen); - break; -#endif - -#ifdef HAVE_SYS_UN_H - case AF_UNIX: - sockAddr = encode_un_sockaddr(env, &sockAddrP->un, addrLen); - break; -#endif - - default: - sockAddr = atom_undefined; - break; - } - - return sockAddr; -} - - - -/* Encode an IPv4 socket address; in4_sockaddr */ -static -ERL_NIF_TERM encode_in4_sockaddr(ErlNifEnv* env, - struct sockaddr_in* addrP, - SOCKLEN_T addrLen) -{ - ERL_NIF_TERM sockAddr; - short port; - ERL_NIF_TERM ePort, eAddr; - - - if (addrLen >= sizeof(struct sockaddr_in)) { - unsigned int i; - ERL_NIF_TERM at[4]; - unsigned int atLen = sizeof(at) / sizeof(ERL_NIF_TERM); - unsigned char* a = (unsigned char*) &addrP->sin_addr; - - /* The port */ - port = ntohs(addrP->sin_port); - ePort = MKI(env, port); - - /* The address */ - for (i = 0; i < atLen; i++) { - at[i] = MKI(env, a[i]); - } - eAddr = MKTA(env, at, atLen); - // eAddr = MKT4(env, at[0], at[1], at[2], at[3]); - - /* And finally construct the in4_sockaddr record */ - sockAddr = make_in4_sockaddr(env, ePort, eAddr); - - } else { - sockAddr = atom_undefined; - } - - return sockAddr; -} - - - -/* Encode an IPv6 socket address; in6_sockaddr */ -#if defined(HAVE_IN6) && defined(AF_INET6) -static -ERL_NIF_TERM encode_in6_sockaddr(ErlNifEnv* env, - struct sockaddr_in6* addrP, - SOCKLEN_T addrLen) -{ - ERL_NIF_TERM sockAddr; - short port; - ERL_NIF_TERM ePort, eAddr, eFlowInfo, eScopeId; - - - if (addrLen >= sizeof(struct sockaddr_in6)) { - unsigned int i; - ERL_NIF_TERM at[8]; - unsigned int atLen = sizeof(at) / sizeof(ERL_NIF_TERM); - unsigned char* a = (unsigned char*) &addrP->sin6_addr; - - /* The port */ - port = ntohs(addrP->sin6_port); - ePort = MKI(env, port); - - /* The address */ - for (i = 0; i < atLen; i++) { - at[i] = MKI(env, get_int16(a + i*2)); - } - eAddr = MKTA(env, at, atLen); - - /* The flowInfo */ - eFlowInfo = MKI(env, addrP->sin6_flowinfo); - - /* The scopeId */ - eScopeId = MKI(env, addrP->sin6_scope_id); - - /* And finally construct the in6_sockaddr record */ - sockAddr = make_in6_sockaddr(env, ePort, eAddr, eFlowInfo, eScopeId); - - } else { - sockAddr = atom_undefined; - } - - return sockAddr; -} -#endif - - - -/* Encode an Unix Domain socket address: string() */ -#ifdef HAVE_SYS_UN_H -static -ERL_NIF_TERM encode_un_sockaddr(ErlNifEnv* env, - struct sockaddr_un* addrP, - SOCKLEN_T addrLen) -{ - ERL_NIF_TERM sockAddr; - size_t n, m; - - if (addrLen >= offsetof(struct sockaddr_un, sun_path)) { - n = addrLen - offsetof(struct sockaddr_un, sun_path); - if (255 < n) { - sockAddr = atom_undefined; - } else { - m = my_strnlen(addrP->sun_path, n); -#ifdef __linux__ - /* Assume that the address is a zero terminated string, - * except when the first byte is \0 i.e the string length is 0, - * then use the reported length instead. - * This fix handles Linux's nonportable - * abstract socket address extension. - */ - if (m == 0) { - m = n; - } -#endif - - sockAddr = MKSL(env, addrP->sun_path, m); - } - } else { - sockAddr = atom_undefined; - } - - return sockAddr; -} -#endif - - - /* The erlang format for a set of flags is a list of atoms. * A special case is when there is no flags, which is * represented by the atom undefined. @@ -1778,14 +1159,14 @@ BOOLEAN_T decode_nameinfo_flags(ErlNifEnv* env, BOOLEAN_T result; if (IS_ATOM(env, eflags)) { - if (COMPARE(eflags, atom_undefined) == 0) { + if (COMPARE(eflags, esock_atom_undefined) == 0) { *flags = 0; result = TRUE; } else { result = FALSE; } } else if (IS_LIST(env, eflags)) { - NDBG( ("decode_nameinfo_flags -> is atom\r\n") ); + NDBG( ("NET", "decode_nameinfo_flags -> is atom\r\n") ); result = decode_nameinfo_flags_list(env, eflags, flags); } else { result = FALSE; @@ -1809,7 +1190,7 @@ BOOLEAN_T decode_nameinfo_flags_list(ErlNifEnv* env, if (GET_LIST_ELEM(env, list, &elem, &tail)) { if (COMPARE(elem, atom_namereqd) == 0) { tmp |= NI_NAMEREQD; - } else if (COMPARE(elem, atom_dgram) == 0) { + } else if (COMPARE(elem, esock_atom_dgram) == 0) { tmp |= NI_DGRAM; } else if (COMPARE(elem, atom_nofqdn) == 0) { tmp |= NI_NOFQDN; @@ -1864,7 +1245,7 @@ BOOLEAN_T decode_addrinfo_string(ErlNifEnv* env, BOOLEAN_T result; if (IS_ATOM(env, eString)) { - if (COMPARE(eString, atom_undefined) == 0) { + if (COMPARE(eString, esock_atom_undefined) == 0) { *stringP = NULL; result = TRUE; } else { @@ -1880,12 +1261,12 @@ BOOLEAN_T decode_addrinfo_string(ErlNifEnv* env, result = FALSE; } - NDBG( ("decode_addrinfo_string -> len: %d\r\n", len) ); + NDBG( ("NET", "decode_addrinfo_string -> len: %d\r\n", len) ); bufP = MALLOC(len + 1); // We shall NULL-terminate if (GET_STR(env, eString, bufP, len+1)) { - NDBG( ("decode_addrinfo_string -> buf: %s\r\n", bufP) ); + NDBG( ("NET", "decode_addrinfo_string -> buf: %s\r\n", bufP) ); // bufP[len] = '\0'; *stringP = bufP; result = TRUE; @@ -1906,14 +1287,14 @@ ERL_NIF_TERM decode_bool(ErlNifEnv* env, ERL_NIF_TERM eBool, BOOLEAN_T* bool) { - if (COMPARE(eBool, atom_true) == 0) { + if (COMPARE(eBool, esock_atom_true) == 0) { *bool = TRUE; - return atom_ok; - } else if (COMPARE(eBool, atom_false) == 0) { + return esock_atom_ok; + } else if (COMPARE(eBool, esock_atom_false) == 0) { *bool = FALSE; - return atom_ok; + return esock_atom_ok; } else { - return make_error(env, atom_einval); + return esock_make_error(env, esock_atom_einval); } } @@ -1930,10 +1311,10 @@ ERL_NIF_TERM encode_address_infos(ErlNifEnv* env, ERL_NIF_TERM result; unsigned int len = address_info_length(addrInfo); - NDBG( ("encode_address_infos -> len: %d\r\n", len) ); + NDBG( ("NET", "encode_address_infos -> len: %d\r\n", len) ); if (len > 0) { - ERL_NIF_TERM* array = MALLOC(len * sizeof(ERL_NIF_TERM)); + ERL_NIF_TERM* array = MALLOC(len * sizeof(ERL_NIF_TERM)); // LEAK? unsigned int i = 0; struct addrinfo* p = addrInfo; @@ -1948,7 +1329,7 @@ ERL_NIF_TERM encode_address_infos(ErlNifEnv* env, result = MKEL(env); } - NDBG( ("encode_address_infos -> result: " + NDBG( ("NET", "encode_address_infos -> result: " "\r\n %T\r\n", result) ); return result; @@ -1997,7 +1378,10 @@ ERL_NIF_TERM encode_address_info(ErlNifEnv* env, fam = encode_address_info_family(env, addrInfoP->ai_family); type = encode_address_info_type(env, addrInfoP->ai_socktype); proto = encode_address_info_proto(env, addrInfoP->ai_protocol); - addr = encode_in_sockaddr(env, addrInfoP->ai_addr, addrInfoP->ai_addrlen); + esock_encode_sockaddr(env, + (SocketAddress*) addrInfoP->ai_addr, + addrInfoP->ai_addrlen, + &addr); result = make_address_info(env, fam, type, proto, addr); @@ -2057,23 +1441,23 @@ ERL_NIF_TERM encode_address_info_type(ErlNifEnv* env, switch (socktype) { case SOCK_STREAM: - etype = atom_stream; + etype = esock_atom_stream; break; case SOCK_DGRAM: - etype = atom_dgram; + etype = esock_atom_dgram; break; case SOCK_RAW: - etype = atom_raw; + etype = esock_atom_raw; break; case SOCK_RDM: - etype = atom_rdm; + etype = esock_atom_rdm; break; case SOCK_SEQPACKET: - etype = atom_seqpacket; + etype = esock_atom_seqpacket; break; default: @@ -2103,26 +1487,26 @@ ERL_NIF_TERM encode_address_info_proto(ErlNifEnv* env, #else case IPPROTO_IP: #endif - eproto = atom_ip; + eproto = esock_atom_ip; break; #if defined(SOL_IPV6) case SOL_IPV6: - eproto = atom_ipv6; + eproto = esock_atom_ipv6; break; #endif case IPPROTO_TCP: - eproto = atom_tcp; + eproto = esock_atom_tcp; break; case IPPROTO_UDP: - eproto = atom_udp; + eproto = esock_atom_udp; break; #if defined(HAVE_SCTP) case IPPROTO_SCTP: - eproto = atom_sctp; + eproto = esock_atom_sctp; break; #endif @@ -2136,110 +1520,6 @@ ERL_NIF_TERM encode_address_info_proto(ErlNifEnv* env, -/* Convert an "native" address to an erlang address - * Note that this is not currently exhaustive, but only supports - * IPv4 and IPv6 addresses. Values of other families will be - * returned as an undefined. - */ -/* -static -ERL_NIF_TERM encode_address_info_addr(ErlNifEnv* env, - struct sockaddr* addrP, - SOCKLEN_T addrLen) -{ - ERL_NIF_TERM port, addr, eaddr; - SockAddress* p = (SockAddress*) addrP; - - NDBG( ("encode_address_info_addr -> entry with" - "\r\n family: %d" - "\r\n addrLen: %d" - "\r\n", addrP->sa_family, addrLen) ); - - switch (addrP->sa_family) { - case AF_INET: - { - unsigned char* a = (unsigned char*) &p->in.sin_addr; - port = MKI(env, ntohs(p->in.sin_port)); - addr = MKT4(env, - MKI(env, a[0]), - MKI(env, a[1]), - MKI(env, a[2]), - MKI(env, a[3])); - eaddr = MKT2(env, port, addr); - } - break; - -#if defined(HAVE_IN6) && defined(AF_INET6) - case AF_INET6: - { - unsigned char* a = (unsigned char*) &p->in6.sin6_addr; - port = MKI(env, ntohs(p->in6.sin6_port)); - addr = MKT8(env, - MKI(env, get_int16(a)), - MKI(env, get_int16(&a[ 2])), - MKI(env, get_int16(&a[ 4])), - MKI(env, get_int16(&a[ 6])), - MKI(env, get_int16(&a[ 8])), - MKI(env, get_int16(&a[10])), - MKI(env, get_int16(&a[12])), - MKI(env, get_int16(&a[14]))); - eaddr = MKT2(env, port, addr); - } - break; -#endif - - default: - eaddr = atom_undefined; - break; - } - - NDBG( ("make_addrinfo_addr -> eaddr: " - "\r\n %T\r\n", eaddr) ); - - return eaddr; -} -*/ - - - -#if defined(HAVE_SYS_UN_H) || defined(SO_BINDTODEVICE) -/* strnlen doesn't exist everywhere */ -static -size_t my_strnlen(const char *s, size_t maxlen) -{ - size_t i = 0; - while (i < maxlen && s[i] != '\0') - i++; - return i; -} -#endif - - - -/* Construct the IPv4 socket address record: in4_sockaddr */ -static -ERL_NIF_TERM make_in4_sockaddr(ErlNifEnv* env, - ERL_NIF_TERM port, - ERL_NIF_TERM addr) -{ - return MKT3(env, atom_in4_sockaddr, port, addr); -} - - - -/* Construct the IPv6 socket address record: in6_sockaddr */ -static -ERL_NIF_TERM make_in6_sockaddr(ErlNifEnv* env, - ERL_NIF_TERM port, - ERL_NIF_TERM addr, - ERL_NIF_TERM flowInfo, - ERL_NIF_TERM scopeId) -{ - return MKT5(env, atom_in6_sockaddr, port, addr, flowInfo, scopeId); -} - - - static ERL_NIF_TERM make_address_info(ErlNifEnv* env, ERL_NIF_TERM fam, @@ -2252,87 +1532,6 @@ ERL_NIF_TERM make_address_info(ErlNifEnv* env, -/* Create an ok two (2) tuple in the form: {ok, Any}. - * The second element (Any) is already in the form of an - * ERL_NIF_TERM so all we have to do is create the tuple. - */ -static -ERL_NIF_TERM make_ok2(ErlNifEnv* env, ERL_NIF_TERM any) -{ - return MKT2(env, atom_ok, any); -} - - -/* Create an ok three (3) tuple in the form: {ok, Val1, Val2}. - * The second (Val1) and third (Val2) elements are already in - * the form of an ERL_NIF_TERM so all we have to do is create - * the tuple. - */ -/* -static -ERL_NIF_TERM make_ok3(ErlNifEnv* env, ERL_NIF_TERM val1, ERL_NIF_TERM val2) -{ - return MKT3(env, atom_ok, val1, val2); -} -*/ - - -/* Create an error two (2) tuple in the form: {error, Reason}. - * The second element (Reason) is already in the form of an - * ERL_NIF_TERM so all we have to do is create the tuple. - */ -static -ERL_NIF_TERM make_error(ErlNifEnv* env, ERL_NIF_TERM reason) -{ - return MKT2(env, atom_error, reason); -} - - -/* Create an error two (2) tuple in the form: {error, Reason}. - * The second element, Reason, is a string to be converted into - * an atom. - */ -static -ERL_NIF_TERM make_error1(ErlNifEnv* env, char* reason) -{ - return make_error(env, MKA(env, reason)); -} - - -/* Create an error two (2) tuple in the form: {error, Reason}. - * The second element, Reason, is the errno value in its - * basic form (integer) which will (eventually) be converted - * into an atom. - */ -static -ERL_NIF_TERM make_error2(ErlNifEnv* env, int err) -{ - NDBG( ("make_error2 -> err: %d\r\n", err) ); - return make_error1(env, erl_errno_id(err)); -} - - -/* -static -void xabort(const char* expr, - const char* func, - const char* file, - int line) -{ - fflush(stdout); - fprintf(stderr, "%s:%d:%s() Assertion failed: %s\n", - file, line, func, expr); - fflush(stderr); - abort(); -} -*/ - - -/* ---------------------------------------------------------------------- - * C o u n t e r F u n c t i o n s - * ---------------------------------------------------------------------- - */ - /* ---------------------------------------------------------------------- * C a l l b a c k F u n c t i o n s * ---------------------------------------------------------------------- @@ -2380,88 +1579,6 @@ void net_down(ErlNifEnv* env, -/* ---------------------------------------------------------------------- - * D e b u g F u n c t i o n s - * ---------------------------------------------------------------------- - */ - -/* - * Print a debug format string *with* both a timestamp and the - * the name of the *current* thread. - */ -static -void dbg_printf( const char* format, ... ) -{ - va_list args; - char f[512 + sizeof(format)]; // This has to suffice... - char stamp[30]; - struct timespec ts; - int res; - - /* - * We should really include self in the printout, so we can se which process - * are executing the code. But then I must change the API.... - * ....something for later. - */ - - if (!dbg_realtime(&ts)) { - if (dbg_timespec2str(stamp, sizeof(stamp), &ts) != 0) { - // res = enif_snprintf(f, sizeof(f), "NET [%s] %s", TSNAME(), format); - res = enif_snprintf(f, sizeof(f), "NET [%s]", format); - } else { - // res = enif_snprintf(f, sizeof(f), "NET[%s] [%s] %s", stamp, TSNAME(), format); - res = enif_snprintf(f, sizeof(f), "NET [%s] %s", stamp, format); - } - - if (res > 0) { - va_start (args, format); - erts_vfprintf (stdout, f, args); // TMP: use enif_vfprintf - va_end (args); - fflush(stdout); - } - } - - return; -} - - -static -int dbg_realtime(struct timespec* tsP) -{ - return clock_gettime(CLOCK_REALTIME, tsP); -} - - - - -/* - * Convert a timespec struct into a readable/printable string - */ -static -int dbg_timespec2str(char *buf, unsigned int len, struct timespec *ts) -{ - int ret, buflen; - struct tm t; - - tzset(); - if (localtime_r(&(ts->tv_sec), &t) == NULL) - return 1; - - ret = strftime(buf, len, "%F %T", &t); - if (ret == 0) - return 2; - len -= ret - 1; - buflen = strlen(buf); - - ret = snprintf(&buf[buflen], len, ".%06ld", ts->tv_nsec/1000); - if (ret >= len) - return 3; - - return 0; -} - - - /* ---------------------------------------------------------------------- * L o a d / u n l o a d / u p g r a d e F u n c t i o n s * ---------------------------------------------------------------------- @@ -2500,78 +1617,40 @@ int on_load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info) // We should make it possible to use load_info to get default values data.debug = FALSE; - // NDBG( ("on_load -> entry\r\n") ); + NDBG( ("NET", "on_load -> entry\r\n") ); /* +++ Misc atoms +++ */ atom_address_info = MKA(env, str_address_info); - atom_dccp = MKA(env, str_dccp); atom_debug = MKA(env, str_debug); - atom_dgram = MKA(env, str_dgram); - atom_error = MKA(env, str_error); - atom_false = MKA(env, str_false); atom_idn = MKA(env, str_idn); atom_idna_allow_unassigned = MKA(env, str_idna_allow_unassigned); atom_idna_use_std3_ascii_rules = MKA(env, str_idna_use_std3_ascii_rules); - atom_in4_sockaddr = MKA(env, str_in4_sockaddr); - atom_in6_sockaddr = MKA(env, str_in6_sockaddr); - atom_inet = MKA(env, str_inet); - atom_inet6 = MKA(env, str_inet6); - atom_ip = MKA(env, str_ip); - atom_ipv6 = MKA(env, str_ipv6); atom_namereqd = MKA(env, str_namereqd); atom_name_info = MKA(env, str_name_info); atom_nofqdn = MKA(env, str_nofqdn); atom_numerichost = MKA(env, str_numerichost); atom_numericserv = MKA(env, str_numericserv); - atom_ok = MKA(env, str_ok); - atom_raw = MKA(env, str_raw); - atom_rdm = MKA(env, str_rdm); - atom_seqpacket = MKA(env, str_seqpacket); - atom_stream = MKA(env, str_stream); - atom_tcp = MKA(env, str_tcp); - atom_true = MKA(env, str_true); - atom_udp = MKA(env, str_udp); - atom_undefined = MKA(env, str_undefined); - // atom_version = MKA(env, str_version); - - // atom_lowdelay = MKA(env, str_lowdelay); - // atom_throughput = MKA(env, str_throughput); - // atom_reliability = MKA(env, str_reliability); - // atom_mincost = MKA(env, str_mincost); /* Error codes */ - // atom_eafnosupport = MKA(env, str_eafnosupport); atom_eaddrfamily = MKA(env, str_eaddrfamily); - atom_eagain = MKA(env, str_eagain); atom_ebadflags = MKA(env, str_ebadflags); atom_efail = MKA(env, str_efail); atom_efamily = MKA(env, str_efamily); atom_efault = MKA(env, str_efault); - atom_einval = MKA(env, str_einval); - // atom_eisconn = MKA(env, str_eisconn); atom_emem = MKA(env, str_emem); atom_enametoolong = MKA(env, str_enametoolong); atom_enodata = MKA(env, str_enodata); atom_enoname = MKA(env, str_enoname); - // atom_enotclosing = MKA(env, str_enotclosing); - // atom_enotconn = MKA(env, str_enotconn); atom_enxio = MKA(env, str_enxio); atom_eoverflow = MKA(env, str_eoverflow); atom_eservice = MKA(env, str_eservice); atom_esocktype = MKA(env, str_esocktype); atom_esystem = MKA(env, str_esystem); - // atom_exalloc = MKA(env, str_exalloc); - // atom_exbadstate = MKA(env, str_exbadstate); - // atom_exbusy = MKA(env, str_exbusy); - // atom_exnotopen = MKA(env, str_exnotopen); - // atom_exmon = MKA(env, str_exmon); - // atom_exself = MKA(env, str_exself); - // atom_exsend = MKA(env, str_exsend); // For storing "global" things... - // socketData.env = enif_alloc_env(); // We should really check - // socketData.version = MKA(env, ERTS_VERSION); - // socketData.buildDate = MKA(env, ERTS_BUILD_DATE); + // data.env = enif_alloc_env(); // We should really check + // data.version = MKA(env, ERTS_VERSION); + // data.buildDate = MKA(env, ERTS_BUILD_DATE); net = enif_open_resource_type_x(env, "net", @@ -2579,7 +1658,7 @@ int on_load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info) ERL_NIF_RT_CREATE, NULL); - // NDBG( ("on_load -> done\r\n") ); + NDBG( ("NET", "on_load -> done\r\n") ); return !net; } diff --git a/erts/emulator/nifs/common/socket_dbg.h b/erts/emulator/nifs/common/socket_dbg.h index 36e2be1679..ad0fcdada9 100644 --- a/erts/emulator/nifs/common/socket_dbg.h +++ b/erts/emulator/nifs/common/socket_dbg.h @@ -28,6 +28,16 @@ #define SOCKET_DBG_H__ +/* Used in debug printouts */ +#ifdef __WIN32__ +#define LLU "%I64u" +#else +#define LLU "%llu" +#endif +typedef unsigned long long llu_t; + + + #define ESOCK_DBG_PRINTF( ___COND___ , proto ) \ if ( ___COND___ ) { \ esock_dbg_printf proto; \ diff --git a/erts/emulator/nifs/common/socket_int.h b/erts/emulator/nifs/common/socket_int.h index 83794f0b95..d6a612cab6 100644 --- a/erts/emulator/nifs/common/socket_int.h +++ b/erts/emulator/nifs/common/socket_int.h @@ -67,7 +67,7 @@ /* The general purpose sockaddr */ typedef union { /* General sockaddr */ - struct sockaddr in; + struct sockaddr sa; /* IPv4 sockaddr */ struct sockaddr_in in4; @@ -85,44 +85,56 @@ typedef union { } SocketAddress; +/* *** Boolean *type* stuff... *** */ typedef unsigned int BOOLEAN_T; #define TRUE 1 #define FALSE 0 +#define BOOL2ATOM(__B__) ((__B__) ? esock_atom_true : esock_atom_false) + /* Misc error strings */ -#define ESOCK_STR_EINVAL "einval" #define ESOCK_STR_EAFNOSUPPORT "eafnosupport" +#define ESOCK_STR_EAGAIN "eagain" +#define ESOCK_STR_EINVAL "einval" /* ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - * Misc atoms + * "Global" atoms */ extern ERL_NIF_TERM esock_atom_addr; extern ERL_NIF_TERM esock_atom_any; extern ERL_NIF_TERM esock_atom_dgram; extern ERL_NIF_TERM esock_atom_error; +extern ERL_NIF_TERM esock_atom_false; extern ERL_NIF_TERM esock_atom_family; extern ERL_NIF_TERM esock_atom_flowinfo; extern ERL_NIF_TERM esock_atom_inet; extern ERL_NIF_TERM esock_atom_inet6; +extern ERL_NIF_TERM esock_atom_ip; +extern ERL_NIF_TERM esock_atom_ipv6; extern ERL_NIF_TERM esock_atom_local; extern ERL_NIF_TERM esock_atom_loopback; extern ERL_NIF_TERM esock_atom_ok; extern ERL_NIF_TERM esock_atom_path; extern ERL_NIF_TERM esock_atom_port; extern ERL_NIF_TERM esock_atom_raw; +extern ERL_NIF_TERM esock_atom_rdm; extern ERL_NIF_TERM esock_atom_scope_id; +extern ERL_NIF_TERM esock_atom_sctp; extern ERL_NIF_TERM esock_atom_seqpacket; extern ERL_NIF_TERM esock_atom_stream; +extern ERL_NIF_TERM esock_atom_tcp; +extern ERL_NIF_TERM esock_atom_true; +extern ERL_NIF_TERM esock_atom_udp; extern ERL_NIF_TERM esock_atom_undefined; /* ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ * Error value (=reason) atoms */ -extern ERL_NIF_TERM esock_atom_eagain; extern ERL_NIF_TERM esock_atom_eafnosupport; +extern ERL_NIF_TERM esock_atom_eagain; extern ERL_NIF_TERM esock_atom_einval; diff --git a/erts/emulator/nifs/common/socket_nif.c b/erts/emulator/nifs/common/socket_nif.c index 335d773f89..d2455a7b3a 100644 --- a/erts/emulator/nifs/common/socket_nif.c +++ b/erts/emulator/nifs/common/socket_nif.c @@ -181,22 +181,6 @@ #define FATAL_MALLOC - -/* *** Boolean *type* stuff... *** */ -typedef unsigned int BOOLEAN_T; -#define TRUE 1 -#define FALSE 0 -#define BOOL2STR(__B__) ((__B__) ? "true" : "false") -#define BOOL2ATOM(__B__) ((__B__) ? atom_true : atom_false) - -/* Two byte integer decoding */ -#define get_int16(s) ((((unsigned char*) (s))[0] << 8) | \ - (((unsigned char*) (s))[1])) - -#define SASSERT(e) \ - ((void) ((e) ? 1 : (xabort(#e, __func__, __FILE__, __LINE__), 0))) - - /* Debug stuff... */ #define SOCKET_NIF_DEBUG_DEFAULT TRUE #define SOCKET_DEBUG_DEFAULT TRUE @@ -205,15 +189,6 @@ typedef unsigned int BOOLEAN_T; #define SOCKET_NIF_IOW_DEFAULT FALSE -/* Used in debug printouts */ -#ifdef __WIN32__ -#define LLU "%I64u" -#else -#define LLU "%llu" -#endif -typedef unsigned long long llu_t; - - /* Socket stuff */ #define INVALID_SOCKET -1 @@ -301,9 +276,9 @@ typedef unsigned long long llu_t; #define SOCKET_RECV_BUFFER_SIZE_DEFAULT 2048 -#define SOCKET_OPT_VALLUE_TYPE_UNSPEC 0 -#define SOCKET_OPT_VALLUE_TYPE_INT 1 -#define SOCKET_OPT_VALLUE_TYPE_BOOL 2 +#define SOCKET_OPT_VALUE_TYPE_UNSPEC 0 +#define SOCKET_OPT_VALUE_TYPE_INT 1 +#define SOCKET_OPT_VALUE_TYPE_BOOL 2 typedef union { struct { @@ -1248,15 +1223,17 @@ static BOOLEAN_T decode_sock_linger(ErlNifEnv* env, static BOOLEAN_T decode_ip_tos(ErlNifEnv* env, ERL_NIF_TERM eVal, int* val); +/* static BOOLEAN_T decode_bool(ErlNifEnv* env, ERL_NIF_TERM eVal, BOOLEAN_T* val); +*/ static BOOLEAN_T decode_native_get_opt(ErlNifEnv* env, ERL_NIF_TERM eVal, int* opt, uint16_t* valueType, int* valueSz); -static void encode_bool(BOOLEAN_T val, ERL_NIF_TERM* eVal); +// static void encode_bool(BOOLEAN_T val, ERL_NIF_TERM* eVal); static ERL_NIF_TERM encode_ip_tos(ErlNifEnv* env, int val); static void inform_waiting_procs(ErlNifEnv* env, @@ -1330,11 +1307,6 @@ static char* send_msg(ErlNifEnv* env, ERL_NIF_TERM msg, ErlNifPid* pid); -static void xabort(const char* expr, - const char* func, - const char* file, - int line); - static BOOLEAN_T extract_item_on_load(ErlNifEnv* env, ERL_NIF_TERM map, ERL_NIF_TERM key, @@ -1407,9 +1379,6 @@ static char str_reliability[] = "reliability"; static char str_mincost[] = "mincost"; /* (special) error string constants */ -static char str_eagain[] = "eagain"; -static char str_eafnosupport[] = "eafnosupport"; -static char str_einval[] = "einval"; static char str_eisconn[] = "eisconn"; static char str_enotclosing[] = "enotclosing"; static char str_enotconn[] = "enotconn"; @@ -1426,21 +1395,33 @@ ERL_NIF_TERM esock_atom_addr; ERL_NIF_TERM esock_atom_any; ERL_NIF_TERM esock_atom_dgram; ERL_NIF_TERM esock_atom_error; +ERL_NIF_TERM esock_atom_false; ERL_NIF_TERM esock_atom_family; ERL_NIF_TERM esock_atom_flowinfo; ERL_NIF_TERM esock_atom_inet; ERL_NIF_TERM esock_atom_inet6; +ERL_NIF_TERM esock_atom_ip; +ERL_NIF_TERM esock_atom_ipv6; ERL_NIF_TERM esock_atom_local; ERL_NIF_TERM esock_atom_loopback; ERL_NIF_TERM esock_atom_ok; ERL_NIF_TERM esock_atom_path; ERL_NIF_TERM esock_atom_port; ERL_NIF_TERM esock_atom_raw; +ERL_NIF_TERM esock_atom_rdm; ERL_NIF_TERM esock_atom_scope_id; +ERL_NIF_TERM esock_atom_sctp; ERL_NIF_TERM esock_atom_seqpacket; ERL_NIF_TERM esock_atom_stream; +ERL_NIF_TERM esock_atom_tcp; +ERL_NIF_TERM esock_atom_true; +ERL_NIF_TERM esock_atom_udp; ERL_NIF_TERM esock_atom_undefined; +/* *** "Global" error (=reason) atoms *** */ +ERL_NIF_TERM esock_atom_eagain; +ERL_NIF_TERM esock_atom_eafnosupport; +ERL_NIF_TERM esock_atom_einval; /* *** Atoms *** */ static ERL_NIF_TERM atom_close; @@ -1473,9 +1454,6 @@ static ERL_NIF_TERM atom_throughput; static ERL_NIF_TERM atom_reliability; static ERL_NIF_TERM atom_mincost; -static ERL_NIF_TERM atom_eagain; -static ERL_NIF_TERM atom_eafnosupport; -static ERL_NIF_TERM atom_einval; static ERL_NIF_TERM atom_eisconn; static ERL_NIF_TERM atom_enotclosing; static ERL_NIF_TERM atom_enotconn; @@ -1597,7 +1575,7 @@ ERL_NIF_TERM nif_info(ErlNifEnv* env, unsigned int numKeys = sizeof(keys) / sizeof(ERL_NIF_TERM); unsigned int numVals = sizeof(keys) / sizeof(ERL_NIF_TERM); - SASSERT( (numKeys == numVals) ); + ESOCK_ASSERT( (numKeys == numVals) ); if (!MKMA(env, keys, vals, numKeys, &info)) return enif_make_badarg(env); @@ -1982,7 +1960,7 @@ ERL_NIF_TERM nbind(ErlNifEnv* env, if (port == 0) { SOCKLEN_T len = sizeof(local); sys_memzero((char *) &local, len); - sock_name(descP->sock, &local.in, &len); + sock_name(descP->sock, &local.sa, &len); port = which_address_port(&local); } else if (port == -1) { port = 0; @@ -2012,7 +1990,7 @@ char* decode_laddress(ErlNifEnv* env, } else if (IS_TUPLE(env, localAddr)) { return decode_in_sockaddr(env, localAddr, localP, addrLenP); } else { - return str_einval; + return ESOCK_STR_EINVAL; } } @@ -2035,10 +2013,10 @@ char* decode_laddress_binary(ErlNifEnv* env, ErlNifBinary bin; if (domain != AF_UNIX) - return str_einval; + return ESOCK_STR_EINVAL; if (!GET_BIN(env, localAddr, &bin)) - return str_einval; + return ESOCK_STR_EINVAL; if ((bin.size + #ifdef __linux__ @@ -2054,7 +2032,7 @@ char* decode_laddress_binary(ErlNifEnv* env, 1 #endif ) > sizeof(localP->un.sun_path)) - return str_einval; + return ESOCK_STR_EINVAL; sys_memzero((char*)localP, sizeof(struct sockaddr_un)); localP->un.sun_family = domain; @@ -2128,7 +2106,7 @@ ERL_NIF_TERM nconnect(ErlNifEnv* env, return esock_make_error(env, atom_eisconn); if (IS_CONNECTING(descP)) - return esock_make_error(env, atom_einval); + return esock_make_error(env, esock_atom_einval); code = sock_connect(descP->sock, (struct sockaddr*) &descP->remote, @@ -2364,7 +2342,7 @@ ERL_NIF_TERM naccept(ErlNifEnv* env, break; default: - res = esock_make_error(env, atom_einval); + res = esock_make_error(env, esock_atom_einval); break; } @@ -2437,7 +2415,7 @@ ERL_NIF_TERM naccept_listening(ErlNifEnv* env, descP->state = SOCKET_STATE_ACCEPTING; - return esock_make_error(env, atom_eagain); + return esock_make_error(env, esock_atom_eagain); } else { return esock_make_error_errno(env, save_errno); @@ -2540,7 +2518,7 @@ ERL_NIF_TERM naccept_accepting(ErlNifEnv* env, (ERL_NIF_SELECT_READ), descP, NULL, ref); - return esock_make_error(env, atom_eagain); + return esock_make_error(env, esock_atom_eagain); } else { return esock_make_error_errno(env, save_errno); } @@ -2742,10 +2720,10 @@ ERL_NIF_TERM nif_sendto(ErlNifEnv* env, /* THIS TEST IS NOT CORRECT!!! */ if (!IS_OPEN(descP)) - return esock_make_error(env, atom_einval); + return esock_make_error(env, esock_atom_einval); if (!esendflags2sendflags(eflags, &flags)) - return esock_make_error(env, atom_einval); + return esock_make_error(env, esock_atom_einval); if ((xres = decode_in_sockaddr(env, eSockAddr, &remoteAddr, @@ -2778,7 +2756,7 @@ ERL_NIF_TERM nsendto(ErlNifEnv* env, if (toAddrP != NULL) { written = sock_sendto(descP->sock, dataP->data, dataP->size, flags, - &toAddrP->in, toAddrLen); + &toAddrP->sa, toAddrLen); } else { written = sock_sendto(descP->sock, dataP->data, dataP->size, flags, @@ -3069,7 +3047,7 @@ ERL_NIF_TERM nrecvfrom(ErlNifEnv* env, sys_memzero((char*) &fromAddr, addrLen); read = sock_recvfrom(descP->sock, buf.data, buf.size, flags, - &fromAddr.in, &addrLen); + &fromAddr.sa, &addrLen); return recvfrom_check_result(env, descP, read, @@ -3368,11 +3346,10 @@ ERL_NIF_TERM nif_setopt(ErlNifEnv* env, eIsEncoded = argv[1]; eVal = argv[4]; - if (!decode_bool(env, eIsEncoded, &isEncoded)) - return esock_make_error(env, atom_einval); + isEncoded = esock_decode_bool(eIsEncoded); if (!elevel2level(isEncoded, eLevel, &isOTP, &level)) - return esock_make_error(env, atom_einval); + return esock_make_error(env, esock_atom_einval); return nsetopt(env, descP, isEncoded, isOTP, level, eOpt, eVal); } @@ -3425,7 +3402,7 @@ ERL_NIF_TERM nsetopt_otp(ErlNifEnv* env, break; default: - result = esock_make_error(env, atom_einval); + result = esock_make_error(env, esock_atom_einval); break; } @@ -3440,15 +3417,9 @@ ERL_NIF_TERM nsetopt_otp_debug(ErlNifEnv* env, SocketDescriptor* descP, ERL_NIF_TERM eVal) { - ERL_NIF_TERM result; + descP->dbg = esock_decode_bool(eVal); - if (decode_bool(env, eVal, &descP->dbg)) { - result = esock_atom_ok; - } else { - result = esock_make_error(env, atom_einval); - } - - return result; + return esock_atom_ok; } @@ -3459,15 +3430,9 @@ ERL_NIF_TERM nsetopt_otp_iow(ErlNifEnv* env, SocketDescriptor* descP, ERL_NIF_TERM eVal) { - ERL_NIF_TERM result; + descP->iow = esock_decode_bool(eVal); - if (decode_bool(env, eVal, &descP->iow)) { - result = esock_atom_ok; - } else { - result = esock_make_error(env, atom_einval); - } - - return result; + return esock_atom_ok; } @@ -3493,7 +3458,7 @@ ERL_NIF_TERM nsetopt_native(ErlNifEnv* env, else result = esock_atom_ok; } else { - result = esock_make_error(env, atom_einval); + result = esock_make_error(env, esock_atom_einval); } return result; @@ -3546,7 +3511,7 @@ ERL_NIF_TERM nsetopt_level(ErlNifEnv* env, #endif default: - result = esock_make_error(env, atom_einval); + result = esock_make_error(env, esock_atom_einval); break; } @@ -3615,7 +3580,7 @@ ERL_NIF_TERM nsetopt_lvl_socket(ErlNifEnv* env, #endif default: - result = esock_make_error(env, atom_einval); + result = esock_make_error(env, esock_atom_einval); break; } @@ -3674,7 +3639,7 @@ ERL_NIF_TERM nsetopt_lvl_sock_linger(ErlNifEnv* env, else result = esock_atom_ok; } else { - result = esock_make_error(env, atom_einval); + result = esock_make_error(env, esock_atom_einval); } return result; @@ -3763,7 +3728,7 @@ ERL_NIF_TERM nsetopt_lvl_ip(ErlNifEnv* env, #endif default: - result = esock_make_error(env, atom_einval); + result = esock_make_error(env, esock_atom_einval); break; } @@ -3834,7 +3799,7 @@ ERL_NIF_TERM nsetopt_lvl_ip_tos(ErlNifEnv* env, result = esock_atom_ok; } else { - result = esock_make_error(env, atom_einval); + result = esock_make_error(env, esock_atom_einval); } return result; @@ -3881,7 +3846,7 @@ ERL_NIF_TERM nsetopt_lvl_ipv6(ErlNifEnv* env, #endif default: - result = esock_make_error(env, atom_einval); + result = esock_make_error(env, esock_atom_einval); break; } @@ -3934,7 +3899,7 @@ ERL_NIF_TERM nsetopt_lvl_tcp(ErlNifEnv* env, #endif default: - result = esock_make_error(env, atom_einval); + result = esock_make_error(env, esock_atom_einval); break; } @@ -4002,7 +3967,7 @@ ERL_NIF_TERM nsetopt_lvl_udp(ErlNifEnv* env, #endif default: - result = esock_make_error(env, atom_einval); + result = esock_make_error(env, esock_atom_einval); break; } @@ -4050,7 +4015,7 @@ ERL_NIF_TERM nsetopt_lvl_sctp(ErlNifEnv* env, #endif default: - result = esock_make_error(env, atom_einval); + result = esock_make_error(env, esock_atom_einval); break; } @@ -4113,7 +4078,7 @@ ERL_NIF_TERM nsetopt_str_opt(ErlNifEnv* env, result = esock_atom_ok; } else { - result = esock_make_error(env, atom_einval); + result = esock_make_error(env, esock_atom_einval); } FREE(val); @@ -4133,19 +4098,17 @@ ERL_NIF_TERM nsetopt_bool_opt(ErlNifEnv* env, { ERL_NIF_TERM result; BOOLEAN_T val; + int ival, res; - if (decode_bool(env, eVal, &val)) { - int ival = (val) ? 1 : 0; - int res = socket_setopt(descP->sock, level, opt, &ival, sizeof(ival)); - - if (res != 0) - result = esock_make_error_errno(env, res); - else - result = esock_atom_ok; + val = esock_decode_bool(eVal); + + ival = (val) ? 1 : 0; + res = socket_setopt(descP->sock, level, opt, &ival, sizeof(ival)); - } else { - result = esock_make_error(env, atom_einval); - } + if (res != 0) + result = esock_make_error_errno(env, res); + else + result = esock_atom_ok; return result; } @@ -4172,7 +4135,7 @@ ERL_NIF_TERM nsetopt_int_opt(ErlNifEnv* env, result = esock_atom_ok; } else { - result = esock_make_error(env, atom_einval); + result = esock_make_error(env, esock_atom_einval); } return result; @@ -4378,11 +4341,10 @@ ERL_NIF_TERM nif_getopt(ErlNifEnv* env, } eIsEncoded = argv[1]; - if (!decode_bool(env, eIsEncoded, &isEncoded)) - return esock_make_error(env, atom_einval); + isEncoded = esock_decode_bool(eIsEncoded); if (!elevel2level(isEncoded, eLevel, &isOTP, &level)) - return esock_make_error(env, atom_einval); + return esock_make_error(env, esock_atom_einval); return ngetopt(env, descP, isEncoded, isOTP, level, eOpt); } @@ -4434,7 +4396,7 @@ ERL_NIF_TERM ngetopt_otp(ErlNifEnv* env, break; default: - result = esock_make_error(env, atom_einval); + result = esock_make_error(env, esock_atom_einval); break; } @@ -4448,9 +4410,7 @@ static ERL_NIF_TERM ngetopt_otp_debug(ErlNifEnv* env, SocketDescriptor* descP) { - ERL_NIF_TERM eVal; - - encode_bool(descP->dbg, &eVal); + ERL_NIF_TERM eVal = esock_encode_bool(descP->dbg); return esock_make_ok2(env, eVal); } @@ -4462,9 +4422,7 @@ static ERL_NIF_TERM ngetopt_otp_iow(ErlNifEnv* env, SocketDescriptor* descP) { - ERL_NIF_TERM eVal; - - encode_bool(descP->iow, &eVal); + ERL_NIF_TERM eVal = esock_encode_bool(descP->iow); return esock_make_ok2(env, eVal); } @@ -4494,21 +4452,21 @@ ERL_NIF_TERM ngetopt_native(ErlNifEnv* env, if (decode_native_get_opt(env, eOpt, &opt, &valueType, (int*) &valueSz)) { switch (valueType) { - case SOCKET_OPT_VALLUE_TYPE_UNSPEC: + case SOCKET_OPT_VALUE_TYPE_UNSPEC: result = ngetopt_native_unspec(env, descP, level, opt, valueSz); break; - case SOCKET_OPT_VALLUE_TYPE_INT: + case SOCKET_OPT_VALUE_TYPE_INT: result = ngetopt_int_opt(env, descP, level, opt); break; - case SOCKET_OPT_VALLUE_TYPE_BOOL: + case SOCKET_OPT_VALUE_TYPE_BOOL: result = ngetopt_bool_opt(env, descP, level, opt); break; default: - result = esock_make_error(env, atom_einval); + result = esock_make_error(env, esock_atom_einval); break; } } else { - result = esock_make_error(env, atom_einval); + result = esock_make_error(env, esock_atom_einval); } return result; @@ -4601,7 +4559,7 @@ ERL_NIF_TERM ngetopt_level(ErlNifEnv* env, #endif default: - result = esock_make_error(env, atom_einval); + result = esock_make_error(env, esock_atom_einval); break; } @@ -4668,7 +4626,7 @@ ERL_NIF_TERM ngetopt_lvl_socket(ErlNifEnv* env, #endif default: - result = esock_make_error(env, atom_einval); + result = esock_make_error(env, esock_atom_einval); break; } @@ -4811,7 +4769,7 @@ ERL_NIF_TERM ngetopt_lvl_ip(ErlNifEnv* env, #endif default: - result = esock_make_error(env, atom_einval); + result = esock_make_error(env, esock_atom_einval); break; } @@ -4922,7 +4880,7 @@ ERL_NIF_TERM ngetopt_lvl_ipv6(ErlNifEnv* env, #endif default: - result = esock_make_error(env, atom_einval); + result = esock_make_error(env, esock_atom_einval); break; } @@ -4973,7 +4931,7 @@ ERL_NIF_TERM ngetopt_lvl_tcp(ErlNifEnv* env, #endif default: - result = esock_make_error(env, atom_einval); + result = esock_make_error(env, esock_atom_einval); break; } @@ -5037,7 +4995,7 @@ ERL_NIF_TERM ngetopt_lvl_udp(ErlNifEnv* env, #endif default: - result = esock_make_error(env, atom_einval); + result = esock_make_error(env, esock_atom_einval); break; } @@ -5082,7 +5040,7 @@ ERL_NIF_TERM ngetopt_lvl_sctp(ErlNifEnv* env, #endif default: - result = esock_make_error(env, atom_einval); + result = esock_make_error(env, esock_atom_einval); break; } @@ -5352,7 +5310,7 @@ ERL_NIF_TERM recv_check_result(ErlNifEnv* env, } else if ((save_errno == ERRNO_BLOCK) || (save_errno == EAGAIN)) { - return esock_make_error(env, atom_eagain); + return esock_make_error(env, esock_atom_eagain); } else { return esock_make_error_errno(env, save_errno); } @@ -5437,7 +5395,7 @@ ERL_NIF_TERM recvfrom_check_result(ErlNifEnv* env, } else if ((save_errno == ERRNO_BLOCK) || (save_errno == EAGAIN)) { - return esock_make_error(env, atom_eagain); + return esock_make_error(env, esock_atom_eagain); } else { return esock_make_error_errno(env, save_errno); } @@ -5501,23 +5459,23 @@ char* decode_send_addr(ErlNifEnv* env, if (!(GET_ATOM_LEN(env, addr, &len) && (len > 0) && (len <= (sizeof("null"))))) - return str_einval; + return ESOCK_STR_EINVAL; if (!GET_ATOM(env, addr, a, sizeof(a))) - return str_einval; + return ESOCK_STR_EINVAL; *toAddrP = NULL; if (strncmp(a, "null", len) == 0) return NULL; else - return str_einval; + return ESOCK_STR_EINVAL; } else if (IS_TUPLE(env, addr)) { / * We now know that the we have a proper address. * / return decode_send_addr_tuple(env, domain, addr, port, *toAddrP, toAddrLenP); } else { - return str_einval; + return ESOCK_STR_EINVAL; } } */ @@ -5540,18 +5498,18 @@ char* decode_send_addr_tuple(ErlNifEnv* env, int addrtSz; if (!GET_TUPLE(env, addr, &addrtSz, &addrt)) - return str_einval; // PLACEHOLDER + return ESOCK_STR_EINVAL; // PLACEHOLDER switch (domain) { case AF_INET: if (addrtSz != 4) - return str_einval; + return ESOCK_STR_EINVAL; break; #if defined(HAVE_IN6) && defined(AF_INET6) case AF_INET6: if (addrtSz != 8) - return str_einval; + return ESOCK_STR_EINVAL; break; #endif @@ -5592,7 +5550,7 @@ char* decode_in_sockaddr(ErlNifEnv* env, char* result = NULL; if (!GET_TUPLE(env, eSockAddr, &addrtSz, &addrt)) - return str_einval; + return ESOCK_STR_EINVAL; /* * We use the tuple size to figure out which @@ -5610,7 +5568,7 @@ char* decode_in_sockaddr(ErlNifEnv* env, #endif default: - result = str_eafnosupport; + result = ESOCK_STR_EAFNOSUPPORT; break; } @@ -5634,11 +5592,11 @@ char* decode_in4_sockaddr(ErlNifEnv* env, /* 1: Ensure that the tuple has the correct tag: in4_sockaddr */ if (COMPARE(atom_in4_sockaddr, eIn4SockAddr[0]) != 0) - return str_einval; + return ESOCK_STR_EINVAL; /* 2: Get the port number */ if (!GET_INT(env, eIn4SockAddr[1], &port)) - return str_einval; + return ESOCK_STR_EINVAL; /* 3: Get the address. * It can either be the atoms: any | loopback, @@ -5651,7 +5609,7 @@ char* decode_in4_sockaddr(ErlNifEnv* env, return decode_in4_sockaddr_addr(env, eIn4SockAddr[2], port, sockAddrP, addrLenP); } else { - return str_einval; + return ESOCK_STR_EINVAL; } } @@ -5671,7 +5629,7 @@ char* decode_in4_sockaddr_atomaddr(ErlNifEnv* env, } else if (COMPARE(esock_atom_any, eAddr) == 0) { addr.s_addr = sock_htonl(INADDR_ANY); } else { - return str_einval; + return ESOCK_STR_EINVAL; } sys_memzero((char*) sockAddrP, sizeof(struct sockaddr_in)); @@ -5704,10 +5662,10 @@ char* decode_in4_sockaddr_addr(ErlNifEnv* env, /* This shall be a 4 tuple */ if (!GET_TUPLE(env, eAddr, &ip4AddrTSz, &ip4AddrT)) - return str_einval; + return ESOCK_STR_EINVAL; if (ip4AddrTSz != 4) - return str_einval; + return ESOCK_STR_EINVAL; sys_memzero((char*)sockAddrP, sizeof(struct sockaddr_in)); #ifndef NO_SA_LEN @@ -5717,7 +5675,7 @@ char* decode_in4_sockaddr_addr(ErlNifEnv* env, sockAddrP->in4.sin_port = sock_htons(port); for (a = 0; a < 4; a++) { if (!GET_INT(env, ip4AddrT[a], &v)) - return str_einval; + return ESOCK_STR_EINVAL; addr[a] = v; } sys_memcpy(&sockAddrP->in4.sin_addr, &addr, sizeof(addr)); @@ -5747,19 +5705,19 @@ char* decode_in6_sockaddr(ErlNifEnv* env, /* 1: Ensure that the tuple has the correct tag: in6_sockaddr */ if (COMPARE(atom_in6_sockaddr, eIn6SockAddr[0]) != 0) - return str_einval; + return ESOCK_STR_EINVAL; /* 2: Get the port number */ if (!GET_INT(env, eIn6SockAddr[1], &port)) - return str_einval; + return ESOCK_STR_EINVAL; /* 4: Get the flowinfo */ if (!GET_UINT(env, eIn6SockAddr[3], &flowInfo)) - return str_einval; + return ESOCK_STR_EINVAL; /* 5: Get the scope_id */ if (!GET_UINT(env, eIn6SockAddr[4], &scopeId)) - return str_einval; + return ESOCK_STR_EINVAL; /* 3: Get the address. * It can either be the atoms: any | loopback, @@ -5774,7 +5732,7 @@ char* decode_in6_sockaddr(ErlNifEnv* env, flowInfo, scopeId, sockAddrP, addrLenP); } else { - return str_einval; + return ESOCK_STR_EINVAL; } } #endif @@ -5797,7 +5755,7 @@ char* decode_in6_sockaddr_atomaddr(ErlNifEnv* env, } else if (COMPARE(esock_atom_any, eAddr) == 0) { addr = &in6addr_any; } else { - return str_einval; + return ESOCK_STR_EINVAL; } sys_memzero((char*)sockAddrP, sizeof(struct sockaddr_in6)); @@ -5835,10 +5793,10 @@ char* decode_in6_sockaddr_addr(ErlNifEnv* env, /* This shall be a 8 tuple */ if (!GET_TUPLE(env, eAddr, &ip6AddrTSz, &ip6AddrT)) - return str_einval; + return ESOCK_STR_EINVAL; if (ip6AddrTSz != 8) - return str_einval; + return ESOCK_STR_EINVAL; sys_memzero((char*)sockAddrP, sizeof(struct sockaddr_in6)); #ifndef NO_SA_LEN @@ -5853,7 +5811,7 @@ char* decode_in6_sockaddr_addr(ErlNifEnv* env, */ for (a = 0; a < 8; a++) { if (!GET_INT(env, ip6AddrT[a], &v)) - return str_einval; + return ESOCK_STR_EINVAL; addr[a*2 ] = ((v >> 8) & 0xFF); addr[a*2+1] = (v & 0xFF); } @@ -5896,7 +5854,7 @@ char* decode_address_tuple(ErlNifEnv* env, addrP->sai.sin_port = sock_htons(port); for (a = 0; a < 4; a++) { if (!GET_INT(env, addrt[a], &v)) - return str_einval; + return ESOCK_STR_EINVAL; laddr[a] = v; } sys_memcpy(&addrP->sai.sin_addr, &laddr, sizeof(laddr)); @@ -5923,7 +5881,7 @@ char* decode_address_tuple(ErlNifEnv* env, * / for (a = 0; a < 8; a++) { if (!GET_INT(env, addrt[a], &v)) - return str_einval; + return ESOCK_STR_EINVAL; laddr[a*2 ] = ((v >> 8) & 0xFF); laddr[a*2+1] = (v & 0xFF); } @@ -5936,7 +5894,7 @@ char* decode_address_tuple(ErlNifEnv* env, } / * switch (domain) * / - return str_eafnosupport; + return ESOCK_STR_EAFNOSUPPORT; } */ @@ -5956,14 +5914,14 @@ char* decode_address_tuple(ErlNifEnv* env, */ static void encode_address(ErlNifEnv* env, - SocketAddress* addrP, + SocketAddress* sockAddrP, unsigned int addrLen, ERL_NIF_TERM* domainT, ERL_NIF_TERM* sourceT) { short port; - switch (addrP->in.sa_family) { + switch (sockAddrP->sa.sa_family) { /* +++ inet (IPv4) +++ */ @@ -5972,9 +5930,9 @@ void encode_address(ErlNifEnv* env, ERL_NIF_TERM addrT, portT; unsigned int i; ERL_NIF_TERM at4[4]; - char* a4 = (char*) &addrP->in4.sin_addr; + char* a4 = (char*) &sockAddrP->in4.sin_addr; - port = sock_ntohs(addrP->in4.sin_port); + port = sock_ntohs(sockAddrP->in4.sin_port); for (i = 0; i < 4; i++) { at4[i] = MKI(env, a4[i]); } @@ -5998,9 +5956,9 @@ void encode_address(ErlNifEnv* env, ERL_NIF_TERM addrT, portT; unsigned int i; ERL_NIF_TERM at6[8]; - char* a16 = (char*) &addrP->in6.sin6_addr; + char* a16 = (char*) &sockAddrP->in6.sin6_addr; - port = sock_ntohs(addrP->in6.sin6_port); + port = sock_ntohs(sockAddrP->in6.sin6_port); /* The address tuple is of size 8 * and each element is a two byte integer */ @@ -6037,7 +5995,7 @@ void encode_address(ErlNifEnv* env, if (255 < n) { *sourceT = esock_atom_undefined; } else { - m = my_strnlen(addrP->un.sun_path, n); + m = my_strnlen(sockAddrP->un.sun_path, n); #ifdef __linux__ /* Assume that the address is a zero terminated string, * except when the first byte is \0 i.e the string length is 0, @@ -6050,7 +6008,7 @@ void encode_address(ErlNifEnv* env, } #endif - *sourceT = MKSL(env, addrP->un.sun_path, m); + *sourceT = MKSL(env, sockAddrP->un.sun_path, m); } } } @@ -6087,7 +6045,7 @@ char* decode_address_atom(ErlNifEnv* env, } if (strncmp(addr, "loopback", addrLen) == 0) { any = FALSE; } else { - return str_einval; + return ESOCK_STR_EINVAL; } / * If we get this far, we *know* its either 'any' or 'loopback' * / @@ -6135,7 +6093,7 @@ char* decode_address_atom(ErlNifEnv* env, #endif default: - return str_einval; + return ESOCK_STR_EINVAL; break; } @@ -6143,23 +6101,24 @@ char* decode_address_atom(ErlNifEnv* env, } */ +/* static BOOLEAN_T decode_bool(ErlNifEnv* env, ERL_NIF_TERM eVal, BOOLEAN_T* val) { unsigned int len; char b[16]; // Just in case... - /* Verify that the value is actually an atom */ + / * Verify that the value is actually an atom * / if (!IS_ATOM(env, eVal)) return FALSE; - /* Verify that the value is of acceptable length */ + / * Verify that the value is of acceptable length * / if (!(GET_ATOM_LEN(env, eVal, &len) && (len > 0) && (len <= sizeof("false")))) return FALSE; - /* And finally try to extract the value */ + / * And finally try to extract the value * / if (!GET_ATOM(env, eVal, b, sizeof(b))) return FALSE; @@ -6170,6 +6129,7 @@ BOOLEAN_T decode_bool(ErlNifEnv* env, ERL_NIF_TERM eVal, BOOLEAN_T* val) return TRUE; } +*/ /* +++ decode the linger value +++ @@ -6195,8 +6155,7 @@ BOOLEAN_T decode_sock_linger(ErlNifEnv* env, ERL_NIF_TERM eVal, struct linger* v /* So fas so good - now check the two elements of the tuple. */ - if (!decode_bool(env, lt[0], &onOff)) - return FALSE; + onOff = esock_decode_bool(lt[0]); if (!GET_INT(env, lt[1], &secs)) return FALSE; @@ -6326,10 +6285,10 @@ BOOLEAN_T decode_native_get_opt(ErlNifEnv* env, ERL_NIF_TERM eVal, return FALSE; if (strncmp(t, "bool", len) == 0) { - *valueType = SOCKET_OPT_VALLUE_TYPE_BOOL; + *valueType = SOCKET_OPT_VALUE_TYPE_BOOL; *valueSz = sizeof(int); // Just to be sure } else if (strncmp(t, "int", len) == 0) { - *valueType = SOCKET_OPT_VALLUE_TYPE_INT; + *valueType = SOCKET_OPT_VALUE_TYPE_INT; *valueSz = sizeof(int); // Just to be sure } else { return FALSE; @@ -6337,7 +6296,7 @@ BOOLEAN_T decode_native_get_opt(ErlNifEnv* env, ERL_NIF_TERM eVal, } else if (IS_NUM(env, nativeOptT[1])) { if (GET_INT(env, nativeOptT[1], valueSz)) { - *valueType = SOCKET_OPT_VALLUE_TYPE_UNSPEC; + *valueType = SOCKET_OPT_VALUE_TYPE_UNSPEC; } else { return FALSE; } @@ -6349,14 +6308,16 @@ BOOLEAN_T decode_native_get_opt(ErlNifEnv* env, ERL_NIF_TERM eVal, } +/* static void encode_bool(BOOLEAN_T val, ERL_NIF_TERM* eVal) { if (val) - *eVal = atom_true; + *eVal = esock_atom_true; else - *eVal = atom_false; + *eVal = esock_atom_false; } +*/ /* +++ encode the ip socket option tos +++ @@ -6772,13 +6733,13 @@ char* decode_sockaddr(ErlNifEnv* env, char* res; if (!IS_MAP(env, eSockAddr)) - return str_einval; + return ESOCK_STR_EINVAL; if (!GET_MAP_VAL(env, eSockAddr, esock_atom_family, &efam)) - return str_einval; + return ESOCK_STR_EINVAL; if (!decode_domain(env, efam, &fam)) - return str_einval; + return ESOCK_STR_EINVAL; switch (fam) { case AF_INET: @@ -6798,7 +6759,7 @@ char* decode_sockaddr(ErlNifEnv* env, #endif default: - result = str_eafnosupport; + result = ESOCK_STR_EAFNOSUPPORT; break; } @@ -6841,20 +6802,20 @@ char* decode_sockaddr_in4(ErlNifEnv* env, / * Extract (e) port number from map * / if (!GET_MAP_VAL(env, eSockAddr, atom_port, &eport)) - return str_einval; + return ESOCK_STR_EINVAL; / * Decode port number * / if (!GET_INT(env, eport, &port)) - return str_einval; + return ESOCK_STR_EINVAL; sockAddrP->sin_port = sock_htons(port); / * Extract (e) address from map * / if (!GET_MAP_VAL(env, eSockAddr, atom_addr, &eaddr)) - return str_einval; + return ESOCK_STR_EINVAL; / * Decode address * / if (!decode_ip4_address(env, eaddr, sockAddrP, addrLen)) - return str_einval; + return ESOCK_STR_EINVAL; return NULL; } @@ -6896,41 +6857,41 @@ char* decode_sockaddr_in6(ErlNifEnv* env, / * *** Extract (e) port number from map *** * / if (!GET_MAP_VAL(env, eSockAddr, atom_port, &eport)) - return str_einval; + return ESOCK_STR_EINVAL; / * Decode port number * / if (!GET_INT(env, eport, &port)) - return str_einval; + return ESOCK_STR_EINVAL; sockAddrP->sin6_port = sock_htons(port); / * *** Extract (e) flowinfo from map *** * / if (!GET_MAP_VAL(env, eSockAddr, atom_flowinfo, &eflowInfo)) - return str_einval; + return ESOCK_STR_EINVAL; / * 4: Get the flowinfo * / if (!GET_UINT(env, eflowInfo, &flowInfo)) - return str_einval; + return ESOCK_STR_EINVAL; sockAddrP->sin6_flowinfo = flowInfo; / * *** Extract (e) scope_id from map *** * / if (!GET_MAP_VAL(env, eSockAddr, atom_scope_id, &escopeId)) - return str_einval; + return ESOCK_STR_EINVAL; / * *** Get the scope_id *** * / if (!GET_UINT(env, escopeId, &scopeId)) - return str_einval; + return ESOCK_STR_EINVAL; sockAddrP->sin6_scope_id = scopeId; / * *** Extract (e) address from map *** * / if (!GET_MAP_VAL(env, eSockAddr, atom_addr, &eaddr)) - return str_einval; + return ESOCK_STR_EINVAL; / * Decode address * / if (!decode_ip6_address(env, eaddr, sockAddrP, addrLen)) - return str_einval; + return ESOCK_STR_EINVAL; return NULL; } @@ -6964,11 +6925,11 @@ char* decode_sockaddr_un(ErlNifEnv* env, / * *** Extract (e) path (a binary) from map *** * / if (!GET_MAP_VAL(env, eSockAddr, atom_port, &epath)) - return str_einval; + return ESOCK_STR_EINVAL; / * Get the path * / if (!GET_BIN(env, epath, &bin)) - return str_einval; + return ESOCK_STR_EINVAL; if ((bin.size + #ifdef __linux__ @@ -6984,7 +6945,7 @@ char* decode_sockaddr_un(ErlNifEnv* env, 1 #endif ) > sizeof(sockaAddrP->sun_path)) - return str_einval; + return ESOCK_STR_EINVAL; sys_memzero((char*) sockAddrP, sizeof(struct sockaddr_un)); @@ -7032,7 +6993,7 @@ char* decode_ip4_address(ErlNifEnv* env, } else if (COMPARE(esock_atom_any, eAddr) == 0) { addr.s_addr = sock_htonl(INADDR_ANY); } else { - return str_einval; + return ESOCK_STR_EINVAL; } sockAddrP->sin_addr.s_addr = addr.s_addr; @@ -7047,14 +7008,14 @@ char* decode_ip4_address(ErlNifEnv* env, char addr[4]; if (!GET_TUPLE(env, eAddr, &addrtSz, &addrt)) - return str_einval; + return ESOCK_STR_EINVAL; if (addrtSz != 4) - return str_einval; + return ESOCK_STR_EINVAL; for (a = 0; a < 4; a++) { if (!GET_INT(env, addrt[a], &v)) - return str_einval; + return ESOCK_STR_EINVAL; addr[a] = v; } @@ -7098,7 +7059,7 @@ char* decode_ip6_address(ErlNifEnv* env, } else if (COMPARE(esock_atom_any, eAddr) == 0) { addr = &in6addr_any; } else { - return str_einval; + return ESOCK_STR_EINVAL; } sockAddrP->sin6_addr = *addr; @@ -7113,14 +7074,14 @@ char* decode_ip6_address(ErlNifEnv* env, char addr[16]; if (!GET_TUPLE(env, eAddr, &addrtSz, &addrt)) - return str_einval; + return ESOCK_STR_EINVAL; if (addrtSz != 8) - return str_einval; + return ESOCK_STR_EINVAL; for (a = 0; a < 8; a++) { if (!GET_INT(env, addrt[a], &v)) - return str_einval; + return ESOCK_STR_EINVAL; addr[a*2 ] = ((v >> 8) & 0xFF); addr[a*2+1] = (v & 0xFF); } @@ -7219,20 +7180,6 @@ char* send_msg(ErlNifEnv* env, } -static -void xabort(const char* expr, - const char* func, - const char* file, - int line) -{ - fflush(stdout); - fprintf(stderr, "%s:%d:%s() Assertion failed: %s\n", - file, line, func, expr); - fflush(stderr); - abort(); -} - - /* ---------------------------------------------------------------------- * C o u n t e r F u n c t i o n s @@ -7332,10 +7279,10 @@ void socket_stop(ErlNifEnv* env, void* obj, int fd, int is_direct_call) * writers waiting. */ - SASSERT( (NULL == send_msg_nif_abort(env, - descP->currentWriter.ref, - atom_closed, - &descP->currentWriter.pid)) ); + ESOCK_ASSERT( (NULL == send_msg_nif_abort(env, + descP->currentWriter.ref, + atom_closed, + &descP->currentWriter.pid)) ); /* And also deal with the waiting writers (in the same way) */ inform_waiting_procs(env, descP, &descP->writersQ, TRUE, atom_closed); @@ -7347,10 +7294,10 @@ void socket_stop(ErlNifEnv* env, void* obj, int fd, int is_direct_call) * readers waiting. */ - SASSERT( (NULL == send_msg_nif_abort(env, - descP->currentReader.ref, - atom_closed, - &descP->currentReader.pid)) ); + ESOCK_ASSERT( (NULL == send_msg_nif_abort(env, + descP->currentReader.ref, + atom_closed, + &descP->currentReader.pid)) ); /* And also deal with the waiting readers (in the same way) */ inform_waiting_procs(env, descP, &descP->readersQ, TRUE, atom_closed); @@ -7361,10 +7308,10 @@ void socket_stop(ErlNifEnv* env, void* obj, int fd, int is_direct_call) * acceptors waiting. */ - SASSERT( (NULL == send_msg_nif_abort(env, - descP->currentAcceptor.ref, - atom_closed, - &descP->currentAcceptor.pid)) ); + ESOCK_ASSERT( (NULL == send_msg_nif_abort(env, + descP->currentAcceptor.ref, + atom_closed, + &descP->currentAcceptor.pid)) ); /* And also deal with the waiting acceptors (in the same way) */ inform_waiting_procs(env, descP, &descP->acceptorsQ, TRUE, atom_closed); @@ -7446,10 +7393,10 @@ void inform_waiting_procs(ErlNifEnv* env, * */ - SASSERT( (NULL == send_msg_nif_abort(env, - currentP->data.ref, - reason, - ¤tP->data.pid)) ); + ESOCK_ASSERT( (NULL == send_msg_nif_abort(env, + currentP->data.ref, + reason, + ¤tP->data.pid)) ); DEMONP(env, descP, ¤tP->data.mon); nextP = currentP->nextP; if (free) FREE(currentP); @@ -7656,34 +7603,45 @@ int on_load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info) atom_timeout = MKA(env, str_timeout); atom_true = MKA(env, str_true); + /* Global atom(s) */ esock_atom_addr = MKA(env, "addr"); esock_atom_any = MKA(env, "any"); esock_atom_dgram = MKA(env, "dgram"); esock_atom_error = MKA(env, "error"); + esock_atom_false = MKA(env, "famlse"); esock_atom_family = MKA(env, "family"); esock_atom_flowinfo = MKA(env, "flowinfo"); esock_atom_inet = MKA(env, "inet"); esock_atom_inet6 = MKA(env, "inet6"); + esock_atom_ip = MKA(env, "ip"); + esock_atom_ipv6 = MKA(env, "ipvp"); esock_atom_local = MKA(env, "local"); esock_atom_loopback = MKA(env, "loopback"); esock_atom_ok = MKA(env, "ok"); esock_atom_path = MKA(env, "path"); esock_atom_port = MKA(env, "port"); esock_atom_raw = MKA(env, "raw"); + esock_atom_rdm = MKA(env, "rdm"); esock_atom_scope_id = MKA(env, "scope_id"); + esock_atom_sctp = MKA(env, "sctp"); esock_atom_seqpacket = MKA(env, "seqpacket"); esock_atom_stream = MKA(env, "stream"); + esock_atom_tcp = MKA(env, "tcp"); + esock_atom_true = MKA(env, "true"); + esock_atom_udp = MKA(env, "udp"); esock_atom_undefined = MKA(env, "undefined"); + /* Global error codes */ + esock_atom_eafnosupport = MKA(env, ESOCK_STR_EAFNOSUPPORT); + esock_atom_eagain = MKA(env, ESOCK_STR_EAGAIN); + esock_atom_einval = MKA(env, ESOCK_STR_EINVAL); + atom_lowdelay = MKA(env, str_lowdelay); atom_throughput = MKA(env, str_throughput); atom_reliability = MKA(env, str_reliability); atom_mincost = MKA(env, str_mincost); /* Error codes */ - atom_eagain = MKA(env, str_eagain); - atom_eafnosupport = MKA(env, str_eafnosupport); - atom_einval = MKA(env, str_einval); atom_eisconn = MKA(env, str_eisconn); atom_enotclosing = MKA(env, str_enotclosing); atom_enotconn = MKA(env, str_enotconn); diff --git a/erts/emulator/nifs/common/socket_util.c b/erts/emulator/nifs/common/socket_util.c index 818e259ae8..37adee682b 100644 --- a/erts/emulator/nifs/common/socket_util.c +++ b/erts/emulator/nifs/common/socket_util.c @@ -29,8 +29,22 @@ #include "sys.h" -/* THIS IS JUST TEMPORARY */ -extern char* erl_errno_id(int error); + +extern char* erl_errno_id(int error); /* THIS IS JUST TEMPORARY??? */ + +static char* make_sockaddr_in4(ErlNifEnv* env, + ERL_NIF_TERM port, + ERL_NIF_TERM addr, + ERL_NIF_TERM* sa); +static char* make_sockaddr_in6(ErlNifEnv* env, + ERL_NIF_TERM port, + ERL_NIF_TERM addr, + ERL_NIF_TERM flowInfo, + ERL_NIF_TERM scopeId, + ERL_NIF_TERM* sa); +static char* make_sockaddr_un(ErlNifEnv* env, + ERL_NIF_TERM path, + ERL_NIF_TERM* sa); /* +++ esock_decode_sockaddr +++ @@ -95,10 +109,59 @@ char* esock_decode_sockaddr(ErlNifEnv* env, +/* +++ esock_encode_sockaddr +++ + * + * Encode a socket address - sockaddr. In erlang its represented as + * a map, which has a specific set of attributes, depending on one + * mandatory attribute; family. So depending on the value of the family + * attribute: + * + * local - sockaddr_un: path + * inet - sockaddr_in4: port, addr + * inet6 - sockaddr_in6: port, addr, flowinfo, scope_id + */ + +extern +char* esock_encode_sockaddr(ErlNifEnv* env, + SocketAddress* sockAddrP, + unsigned int addrLen, + ERL_NIF_TERM* eSockAddr) +{ + char* xres; + + switch (sockAddrP->sa.sa_family) { + case AF_INET: + xres = esock_encode_sockaddr_in4(env, &sockAddrP->in4, addrLen, eSockAddr); + break; + +#if defined(HAVE_IN6) && defined(AF_INET6) + case AF_INET6: + xres = esock_encode_sockaddr_in6(env, &sockAddrP->in6, addrLen, eSockAddr); + break; +#endif + +#ifdef HAVE_SYS_UN_H + case AF_UNIX: + xres = esock_encode_sockaddr_un(env, &sockAddrP->un, addrLen, eSockAddr); + break; +#endif + + default: + xres = ESOCK_STR_EAFNOSUPPORT; + break; + + } + + return xres; +} + + + /* +++ esock_decode_sockaddr_in4 +++ * * Decode a IPv4 socket address - sockaddr_in4. In erlang its represented as - * a map, which has a specific set of attributes: + * a map, which has a specific set of attributes (beside the mandatory family + * attribute, which is "inherited" from the "sockaddr" type): * * port :: port_numbber() * addr :: ip4_address() @@ -147,10 +210,58 @@ char* esock_decode_sockaddr_in4(ErlNifEnv* env, } -/* +++ decode_sockaddr_in6 +++ + +/* +++ esock_encode_sockaddr_in4 +++ + * + * Encode a IPv4 socket address - sockaddr_in4. In erlang its represented as + * a map, which has a specific set of attributes (beside the mandatory family + * attribute, which is "inherited" from the "sockaddr" type): + * + * port :: port_numbber() + * addr :: ip4_address() + * + */ + +extern +char* esock_encode_sockaddr_in4(ErlNifEnv* env, + struct sockaddr_in* sockAddrP, + unsigned int addrLen, + ERL_NIF_TERM* eSockAddr) +{ + ERL_NIF_TERM ePort, eAddr; + int port; + char* xres = NULL; + + if (addrLen >= sizeof(struct sockaddr_in)) { + /* The port */ + port = ntohs(sockAddrP->sin_port); + ePort = MKI(env, port); + + /* The address */ + if ((xres = esock_encode_ip4_address(env, &sockAddrP->sin_addr, + &eAddr)) != NULL) { + /* And finally construct the in4_sockaddr record */ + xres = make_sockaddr_in4(env, ePort, eAddr, eSockAddr); + } else { + *eSockAddr = esock_atom_undefined; + xres = ESOCK_STR_EINVAL; + } + + } else { + *eSockAddr = esock_atom_undefined; + xres = ESOCK_STR_EINVAL; + } + + return xres; +} + + + +/* +++ esock_decode_sockaddr_in6 +++ * * Decode a IPv6 socket address - sockaddr_in6. In erlang its represented as - * a map, which has a specific set of attributes: + * a map, which has a specific set of attributes (beside the mandatory family + * attribute, which is "inherited" from the "sockaddr" type): * * port :: port_numbber() (integer) * addr :: ip6_address() (tuple) @@ -224,10 +335,67 @@ char* esock_decode_sockaddr_in6(ErlNifEnv* env, +/* +++ esock_encode_sockaddr_in6 +++ + * + * Encode a IPv6 socket address - sockaddr_in6. In erlang its represented as + * a map, which has a specific set of attributes (beside the mandatory family + * attribute, which is "inherited" from the "sockaddr" type): + * + * port :: port_numbber() (integer) + * addr :: ip6_address() (tuple) + * flowinfo :: in6_flow_info() (integer) + * scope_id :: in6_scope_id() (integer) + * + */ + +#if defined(HAVE_IN6) && defined(AF_INET6) +extern +char* esock_encode_sockaddr_in6(ErlNifEnv* env, + struct sockaddr_in6* sockAddrP, + unsigned int addrLen, + ERL_NIF_TERM* eSockAddr) +{ + ERL_NIF_TERM ePort, eAddr, eFlowInfo, eScopeId; + char* xres; + + if (addrLen >= sizeof(struct sockaddr_in6)) { + /* The port */ + ePort = MKI(env, ntohs(sockAddrP->sin6_port)); + + /* The flowInfo */ + eFlowInfo = MKI(env, sockAddrP->sin6_flowinfo); + + /* The scopeId */ + eScopeId = MKI(env, sockAddrP->sin6_scope_id); + + /* The address */ + if ((xres = esock_encode_ip6_address(env, &sockAddrP->sin6_addr, + &eAddr)) != NULL) { + /* And finally construct the in6_sockaddr record */ + xres = make_sockaddr_in6(env, + ePort, eAddr, eFlowInfo, eScopeId, eSockAddr); + } else { + *eSockAddr = esock_atom_undefined; + xres = ESOCK_STR_EINVAL; + } + + } else { + *eSockAddr = esock_atom_undefined; + xres = ESOCK_STR_EINVAL; + } + + return xres; +} +#endif + + + /* +++ esock_decode_sockaddr_un +++ * * Decode a Unix Domain socket address - sockaddr_un. In erlang its - * represented as a map, which has a specific set of attributes: + * represented as a map, which has a specific set of attributes + * (beside the mandatory family attribute, which is "inherited" from + * the "sockaddr" type): * * path :: binary() * @@ -256,16 +424,16 @@ char* esock_decode_sockaddr_un(ErlNifEnv* env, if ((bin.size + #ifdef __linux__ - /* Make sure the address gets zero terminated - * except when the first byte is \0 because then it is - * sort of zero terminated although the zero termination - * comes before the address... - * This fix handles Linux's nonportable - * abstract socket address extension. - */ - (bin.data[0] == '\0' ? 0 : 1) + /* Make sure the address gets zero terminated + * except when the first byte is \0 because then it is + * sort of zero terminated although the zero termination + * comes before the address... + * This fix handles Linux's nonportable + * abstract socket address extension. + */ + (bin.data[0] == '\0' ? 0 : 1) #else - 1 + 1 #endif ) > sizeof(sockAddrP->sun_path)) return ESOCK_STR_EINVAL; @@ -288,7 +456,65 @@ char* esock_decode_sockaddr_un(ErlNifEnv* env, -/* +++ decode_ip4_address +++ +/* +++ esock_encode_sockaddr_un +++ + * + * Encode a Unix Domain socket address - sockaddr_un. In erlang its + * represented as a map, which has a specific set of attributes + * (beside the mandatory family attribute, which is "inherited" from + * the "sockaddr" type): + * + * path :: binary() + * + */ + +#ifdef HAVE_SYS_UN_H +extern +char* esock_encode_sockaddr_un(ErlNifEnv* env, + struct sockaddr_un* sockAddrP, + unsigned int addrLen, + ERL_NIF_TERM* eSockAddr) +{ + ERL_NIF_TERM ePath; + size_t n, m; + char* xres; + + if (addrLen >= offsetof(struct sockaddr_un, sun_path)) { + n = addrLen - offsetof(struct sockaddr_un, sun_path); + if (255 < n) { + *eSockAddr = esock_atom_undefined; + xres = ESOCK_STR_EINVAL; + } else { + m = esock_strnlen(sockAddrP->sun_path, n); +#ifdef __linux__ + /* Assume that the address is a zero terminated string, + * except when the first byte is \0 i.e the string length is 0, + * then use the reported length instead. + * This fix handles Linux's nonportable + * abstract socket address extension. + */ + if (m == 0) { + m = n; + } +#endif + + /* And finally build the 'path' attribute */ + ePath = MKSL(env, sockAddrP->sun_path, m); + + /* And the socket address */ + xres = make_sockaddr_un(env, ePath, eSockAddr); + } + } else { + *eSockAddr = esock_atom_undefined; + xres = ESOCK_STR_EINVAL; + } + + return xres; +} +#endif + + + +/* +++ esock_decode_ip4_address +++ * * Decode a IPv4 address. This can be three things: * @@ -351,7 +577,39 @@ char* esock_decode_ip4_address(ErlNifEnv* env, -/* +++ decode_ip6_address +++ +/* +++ esock_encode_ip4_address +++ + * + * Encode a IPv4 address: + * + * + An ip4_address() (4 tuple) + * + * Note that this *only* decodes the "address" part of a + * (IPv4) socket address. There are several other things (port). + */ + +extern +char* esock_encode_ip4_address(ErlNifEnv* env, + struct in_addr* addrP, + ERL_NIF_TERM* eAddr) +{ + unsigned int i; + ERL_NIF_TERM at[4]; + unsigned int atLen = sizeof(at) / sizeof(ERL_NIF_TERM); + unsigned char* a = (unsigned char*) addrP; + + /* The address */ + for (i = 0; i < atLen; i++) { + at[i] = MKI(env, a[i]); + } + + *eAddr = MKTA(env, at, atLen); + + return NULL; +} + + + +/* +++ esock_decode_ip6_address +++ * * Decode a IPv6 address. This can be three things: * @@ -417,6 +675,42 @@ char* esock_decode_ip6_address(ErlNifEnv* env, #endif + +/* +++ esock_encode_ip6_address +++ + * + * Encode a IPv6 address: + * + * + An ip6_address() (8 tuple) + * + * Note that this *only* encodes the "address" part of a + * (IPv6) socket address. There are several other things + * (port, flowinfo and scope_id) that are handled elsewhere). + */ + +#if defined(HAVE_IN6) && defined(AF_INET6) +extern +char* esock_encode_ip6_address(ErlNifEnv* env, + struct in6_addr* addrP, + ERL_NIF_TERM* eAddr) +{ + unsigned int i; + ERL_NIF_TERM at[8]; + unsigned int atLen = sizeof(at) / sizeof(ERL_NIF_TERM); + unsigned char* a = (unsigned char*) &addrP; + + /* The address */ + for (i = 0; i < atLen; i++) { + at[i] = MKI(env, get_int16(a + i*2)); + } + + *eAddr = MKTA(env, at, atLen); + + return NULL; +} +#endif + + + /* +++ esock_decode_domain +++ * * Decode the Erlang form of the 'domain' type, that is: @@ -584,6 +878,36 @@ char* esock_encode_type(ErlNifEnv* env, +/* *** esock_decode_bool *** + * + * Decode a boolean value. + * + */ +extern +BOOLEAN_T esock_decode_bool(ERL_NIF_TERM val) +{ + if (COMPARE(esock_atom_true, val) == 0) + return TRUE; + else + return FALSE; +} + + +/* *** esock_encode_bool *** + * + * Encode a boolean value. + * + */ +extern +ERL_NIF_TERM esock_encode_bool(BOOLEAN_T val) +{ + if (val) + return esock_atom_true; + else + return esock_atom_false; +} + + /* Create an ok two (2) tuple in the form: * * {ok, Any} @@ -657,3 +981,119 @@ ERL_NIF_TERM esock_make_error_errno(ErlNifEnv* env, int err) } + +/* strnlen doesn't exist everywhere */ +extern +size_t esock_strnlen(const char *s, size_t maxlen) +{ + size_t i = 0; + while (i < maxlen && s[i] != '\0') + i++; + return i; +} + + + +/* *** esock_abort *** + * + * Generate an abort with "extra" info. This should be called + * via the ESOCK_ABORT macro. + * Basically it prints the extra info onto stderr before aborting. + * + */ +extern +void esock_abort(const char* expr, + const char* func, + const char* file, + int line) +{ + fflush(stdout); + fprintf(stderr, "%s:%d:%s() Assertion failed: %s\n", + file, line, func, expr); + fflush(stderr); + abort(); +} + + + + +/* =================================================================== * + * * + * Various utility functions * + * * + * =================================================================== */ + +/* Construct the IPv4 socket address */ +static +char* make_sockaddr_in4(ErlNifEnv* env, + ERL_NIF_TERM port, + ERL_NIF_TERM addr, + ERL_NIF_TERM* sa) +{ + ERL_NIF_TERM keys[] = {esock_atom_family, esock_atom_port, esock_atom_addr}; + ERL_NIF_TERM vals[] = {esock_atom_inet, port, addr}; + unsigned int numKeys = sizeof(keys) / sizeof(ERL_NIF_TERM); + unsigned int numVals = sizeof(vals) / sizeof(ERL_NIF_TERM); + + ESOCK_ASSERT( (numKeys == numVals) ); + + if (!MKMA(env, keys, vals, numKeys, sa)) { + *sa = esock_atom_undefined; + return ESOCK_STR_EINVAL; + } else { + return NULL; + } +} + + +/* Construct the IPv6 socket address */ +static +char* make_sockaddr_in6(ErlNifEnv* env, + ERL_NIF_TERM port, + ERL_NIF_TERM addr, + ERL_NIF_TERM flowInfo, + ERL_NIF_TERM scopeId, + ERL_NIF_TERM* sa) +{ + ERL_NIF_TERM keys[] = {esock_atom_family, + esock_atom_port, + esock_atom_addr, + esock_atom_flowinfo, + esock_atom_scope_id}; + ERL_NIF_TERM vals[] = {esock_atom_inet6, port, addr, flowInfo, scopeId}; + unsigned int numKeys = sizeof(keys) / sizeof(ERL_NIF_TERM); + unsigned int numVals = sizeof(vals) / sizeof(ERL_NIF_TERM); + + ESOCK_ASSERT( (numKeys == numVals) ); + + if (!MKMA(env, keys, vals, numKeys, sa)) { + *sa = esock_atom_undefined; + return ESOCK_STR_EINVAL; + } else { + return NULL; + } +} + + +/* Construct the Unix Domain socket address */ +static +char* make_sockaddr_un(ErlNifEnv* env, + ERL_NIF_TERM path, + ERL_NIF_TERM* sa) +{ + ERL_NIF_TERM keys[] = {esock_atom_family, esock_atom_path}; + ERL_NIF_TERM vals[] = {esock_atom_inet, path}; + unsigned int numKeys = sizeof(keys) / sizeof(ERL_NIF_TERM); + unsigned int numVals = sizeof(vals) / sizeof(ERL_NIF_TERM); + + ESOCK_ASSERT( (numKeys == numVals) ); + + if (!MKMA(env, keys, vals, numKeys, sa)) { + *sa = esock_atom_undefined; + return ESOCK_STR_EINVAL; + } else { + return NULL; + } +} + + diff --git a/erts/emulator/nifs/common/socket_util.h b/erts/emulator/nifs/common/socket_util.h index babfebc17b..f5594c408e 100644 --- a/erts/emulator/nifs/common/socket_util.h +++ b/erts/emulator/nifs/common/socket_util.h @@ -29,17 +29,34 @@ #include #include "socket_int.h" +#define ESOCK_ABORT(E) esock_abort(E, __func__, __FILE__, __LINE__) +#define ESOCK_ASSERT(e) ((void) ((e) ? 1 : (ESOCK_ABORT(#e), 0))) + +/* Two byte integer decoding */ +#define get_int16(s) ((((unsigned char*) (s))[0] << 8) | \ + (((unsigned char*) (s))[1])) + extern char* esock_decode_sockaddr(ErlNifEnv* env, ERL_NIF_TERM eSockAddr, SocketAddress* sockAddrP, unsigned int* addrLen); +extern +char* esock_encode_sockaddr(ErlNifEnv* env, + SocketAddress* sockAddrP, + unsigned int addrLen, + ERL_NIF_TERM* eSockAddr); extern char* esock_decode_sockaddr_in4(ErlNifEnv* env, ERL_NIF_TERM eSockAddr, struct sockaddr_in* sockAddrP, unsigned int* addrLen); +extern +char* esock_encode_sockaddr_in4(ErlNifEnv* env, + struct sockaddr_in* sockAddrP, + unsigned int addrLen, + ERL_NIF_TERM* eSockAddr); #if defined(HAVE_IN6) && defined(AF_INET6) extern @@ -47,6 +64,11 @@ char* esock_decode_sockaddr_in6(ErlNifEnv* env, ERL_NIF_TERM eSockAddr, struct sockaddr_in6* sockAddrP, unsigned int* addrLen); +extern +char* esock_encode_sockaddr_in6(ErlNifEnv* env, + struct sockaddr_in6* sockAddrP, + unsigned int addrLen, + ERL_NIF_TERM* eSockAddr); #endif #ifdef HAVE_SYS_UN_H @@ -55,6 +77,11 @@ char* esock_decode_sockaddr_un(ErlNifEnv* env, ERL_NIF_TERM eSockAddr, struct sockaddr_un* sockAddrP, unsigned int* addrLen); +extern +char* esock_encode_sockaddr_un(ErlNifEnv* env, + struct sockaddr_un* sockAddrP, + unsigned int addrLen, + ERL_NIF_TERM* eSockAddr); #endif extern @@ -62,6 +89,10 @@ char* esock_decode_ip4_address(ErlNifEnv* env, ERL_NIF_TERM eAddr, struct sockaddr_in* sockAddrP, unsigned int* addrLen); +extern +char* esock_encode_ip4_address(ErlNifEnv* env, + struct in_addr* addrP, + ERL_NIF_TERM* eAddr); #if defined(HAVE_IN6) && defined(AF_INET6) extern @@ -69,6 +100,10 @@ char* esock_decode_ip6_address(ErlNifEnv* env, ERL_NIF_TERM eAddr, struct sockaddr_in6* sockAddrP, unsigned int* addrLen); +extern +char* esock_encode_ip6_address(ErlNifEnv* env, + struct in6_addr* addrP, + ERL_NIF_TERM* eAddr); #endif extern @@ -91,6 +126,18 @@ char* esock_encode_type(ErlNifEnv* env, int type, ERL_NIF_TERM* eType); +extern +BOOLEAN_T esock_decode_bool(ERL_NIF_TERM val); +extern +ERL_NIF_TERM esock_encode_bool(BOOLEAN_T val); + +extern +size_t esock_strnlen(const char *s, size_t maxlen); +extern +void esock_abort(const char* expr, + const char* func, + const char* file, + int line); extern ERL_NIF_TERM esock_make_ok2(ErlNifEnv* env, ERL_NIF_TERM any); diff --git a/erts/preloaded/ebin/net.beam b/erts/preloaded/ebin/net.beam index 66391317a2..c3d043edfa 100644 Binary files a/erts/preloaded/ebin/net.beam and b/erts/preloaded/ebin/net.beam differ diff --git a/erts/preloaded/ebin/socket.beam b/erts/preloaded/ebin/socket.beam index c4f0390120..cafd5af945 100644 Binary files a/erts/preloaded/ebin/socket.beam and b/erts/preloaded/ebin/socket.beam differ -- cgit v1.2.3 From b63a16d0958bd748644d22f13f35f8956a903d6c Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Mon, 18 Jun 2018 18:19:39 +0200 Subject: [socket+net-nif] Moved common functions into util files The common stuff, like decode and encode of common types (soch as socket address), has been moved into a util file (socket_util). The debug stuff has also been moved into its own file. Also introduced a common include file for common macros and types. OTP-14831 --- erts/emulator/nifs/common/net_nif.c | 87 +---------- erts/emulator/nifs/common/socket_nif.c | 266 ++++++++++++-------------------- erts/emulator/nifs/common/socket_util.c | 168 +++++++++++++++++++- erts/emulator/nifs/common/socket_util.h | 11 +- erts/preloaded/ebin/socket.beam | Bin 39704 -> 39680 bytes erts/preloaded/src/socket.erl | 56 ++----- 6 files changed, 284 insertions(+), 304 deletions(-) diff --git a/erts/emulator/nifs/common/net_nif.c b/erts/emulator/nifs/common/net_nif.c index 9a96eff654..572813ac62 100644 --- a/erts/emulator/nifs/common/net_nif.c +++ b/erts/emulator/nifs/common/net_nif.c @@ -1390,7 +1390,7 @@ ERL_NIF_TERM encode_address_info(ErlNifEnv* env, } -/* Convert an "native" family to an erlang family +/* Convert an "native" family to an erlang family (=domain). * Note that this is not currently exhaustive, but only supports * inet and inet6. Other values will be returned as is, that is * in the form of an integer. @@ -1401,34 +1401,15 @@ ERL_NIF_TERM encode_address_info_family(ErlNifEnv* env, { ERL_NIF_TERM efam; - switch (family) { - case AF_INET: - efam = esock_atom_inet; - break; - -#if defined(HAVE_IN6) && defined(AF_INET6) - case AF_INET6: - efam = esock_atom_inet6; - break; -#endif - - #ifdef HAVE_SYS_UN_H - case AF_UNIX: - efam = esock_atom_local; - break; -#endif - - default: + if (NULL != esock_encode_type(env, family, &efam)) efam = MKI(env, family); - break; - } return efam; } -/* Convert an "native" socket type to an erlang socket type +/* Convert an "native" socket type to an erlang socket type. * Note that this is not currently exhaustive, but only supports * stream and dgram. Other values will be returned as is, that is * in the form of an integer. @@ -1439,38 +1420,15 @@ ERL_NIF_TERM encode_address_info_type(ErlNifEnv* env, { ERL_NIF_TERM etype; - switch (socktype) { - case SOCK_STREAM: - etype = esock_atom_stream; - break; - - case SOCK_DGRAM: - etype = esock_atom_dgram; - break; - - case SOCK_RAW: - etype = esock_atom_raw; - break; - - case SOCK_RDM: - etype = esock_atom_rdm; - break; - - case SOCK_SEQPACKET: - etype = esock_atom_seqpacket; - break; - - default: + if (NULL != esock_encode_type(env, socktype, &etype)) etype = MKI(env, socktype); - break; - } return etype; } -/* Convert an "native" protocol to an erlang protocol +/* Convert an "native" protocol to an erlang protocol. * Note that this is not currently exhaustive, but only supports * tcp and udp. Other values will be returned as is, that is * in the form of an integer. @@ -1481,39 +1439,8 @@ ERL_NIF_TERM encode_address_info_proto(ErlNifEnv* env, { ERL_NIF_TERM eproto; - switch (proto) { -#if defined(SOL_IP) - case SOL_IP: -#else - case IPPROTO_IP: -#endif - eproto = esock_atom_ip; - break; - -#if defined(SOL_IPV6) - case SOL_IPV6: - eproto = esock_atom_ipv6; - break; -#endif - - case IPPROTO_TCP: - eproto = esock_atom_tcp; - break; - - case IPPROTO_UDP: - eproto = esock_atom_udp; - break; - -#if defined(HAVE_SCTP) - case IPPROTO_SCTP: - eproto = esock_atom_sctp; - break; -#endif - - default: + if (NULL != esock_encode_protocol(env, proto, &eproto)) eproto = MKI(env, proto); - break; - } return eproto; } @@ -1615,7 +1542,7 @@ static int on_load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info) { // We should make it possible to use load_info to get default values - data.debug = FALSE; + data.debug = TRUE; NDBG( ("NET", "on_load -> entry\r\n") ); diff --git a/erts/emulator/nifs/common/socket_nif.c b/erts/emulator/nifs/common/socket_nif.c index d2455a7b3a..848e502ec5 100644 --- a/erts/emulator/nifs/common/socket_nif.c +++ b/erts/emulator/nifs/common/socket_nif.c @@ -757,7 +757,8 @@ static ERL_NIF_TERM nopen(ErlNifEnv* env, char* netns); static ERL_NIF_TERM nbind(ErlNifEnv* env, SocketDescriptor* descP, - ERL_NIF_TERM addr); + SocketAddress* sockAddrP, + unsigned int addrLen); static ERL_NIF_TERM nconnect(ErlNifEnv* env, SocketDescriptor* descP); static ERL_NIF_TERM nlisten(ErlNifEnv* env, @@ -1131,7 +1132,7 @@ static ERL_NIF_TERM nfinalize_connection(ErlNifEnv* env, static ERL_NIF_TERM nfinalize_close(ErlNifEnv* env, SocketDescriptor* descP); - +/* static char* decode_in_sockaddr(ErlNifEnv* env, ERL_NIF_TERM eSockAddr, SocketAddress* sockAddrP, @@ -1162,7 +1163,7 @@ static char* decode_in6_sockaddr_atomaddr(ErlNifEnv* env, unsigned int scopeId, SocketAddress* sockAddrP, unsigned int* addrLenP); -/* Decode an in6_sockaddr where the address field is a tuple */ +/ * Decode an in6_sockaddr where the address field is a tuple * / static char* decode_in6_sockaddr_addr(ErlNifEnv* env, ERL_NIF_TERM eAddr, int port, @@ -1171,6 +1172,8 @@ static char* decode_in6_sockaddr_addr(ErlNifEnv* env, SocketAddress* sockAddrP, unsigned int* addrLenP); #endif +*/ +/* static char* decode_laddress(ErlNifEnv* env, int domain, ERL_NIF_TERM localAddr, @@ -1181,6 +1184,7 @@ static char* decode_laddress_binary(ErlNifEnv* env, ERL_NIF_TERM localAddr, SocketAddress* localP, unsigned int* addrLenP); +*/ /* static char* decode_address_tuple(ErlNifEnv* env, int domain, @@ -1212,11 +1216,13 @@ static char* decode_send_addr_tuple(ErlNifEnv* env, SocketAddress* toAddrP, unsigned int* addrLenP); */ +/* static void encode_address(ErlNifEnv* env, SocketAddress* fromAddrP, unsigned int fromAddrLen, ERL_NIF_TERM* fromDomainT, ERL_NIF_TERM* fromSourceT); +*/ static BOOLEAN_T decode_sock_linger(ErlNifEnv* env, ERL_NIF_TERM eVal, struct linger* valP); @@ -1276,9 +1282,11 @@ static BOOLEAN_T restore_network_namespace(int ns, SOCKET sock, int* err); static BOOLEAN_T cnt_inc(uint32_t* cnt, uint32_t inc); +/* #if defined(HAVE_SYS_UN_H) || defined(SO_BINDTODEVICE) static size_t my_strnlen(const char *s, size_t maxlen); #endif +*/ static void socket_dtor(ErlNifEnv* env, void* obj); static void socket_stop(ErlNifEnv* env, @@ -1875,16 +1883,8 @@ BOOLEAN_T restore_network_namespace(int ns, SOCKET sock, int* err) * Bind a name to a socket. * * Arguments: - * Socket (ref) - Points to the socket descriptor. - * LocalAddr - Local address is either: - * - a binary - when the socket domain = local - * - a tuple of size 2 with - * - first element is the address - * - second element is the port number - * The address can be in the form of either: - * - A tuple of size 4 or 8 (depending on domain) - * - The atom 'any' - * - The atom 'loopback' + * [0] Socket (ref) - Points to the socket descriptor. + * [1] LocalAddr - Local address is a sockaddr map ( socket:sockaddr() ). */ static ERL_NIF_TERM nif_bind(ErlNifEnv* env, @@ -1892,6 +1892,12 @@ ERL_NIF_TERM nif_bind(ErlNifEnv* env, const ERL_NIF_TERM argv[]) { SocketDescriptor* descP; + ERL_NIF_TERM eSockAddr; + SocketAddress sockAddr; + unsigned int addrLen; + char* xres; + + SGDBG( ("SOCKET", "nif_bind -> entry with argc: %d\r\n", argc) ); /* Extract arguments and perform preliminary validation */ @@ -1899,25 +1905,13 @@ ERL_NIF_TERM nif_bind(ErlNifEnv* env, !enif_get_resource(env, argv[0], sockets, (void**) &descP)) { return enif_make_badarg(env); } + eSockAddr = argv[1]; SSDBG( descP, - ("SOCKET", "nif_bind -> " - "\r\n Socket: %T" - "\r\n Addr: %T" - "\r\n", argv[0], argv[1]) ); - - /* Basic arg validation: - * - if binary domain must be local (unix) - * - if tuple domain must be either inet or inet6 - */ - if (IS_BIN(env, argv[1]) && (descP->domain != AF_UNIX)) { - return enif_make_badarg(env); - } else if (IS_TUPLE(env, argv[1]) && - (descP->domain != AF_INET) && - (descP->domain != AF_INET6)) { - return enif_make_badarg(env); - } - + ("SOCKET", "nif_bind -> args:" + "\r\n Socket: %T" + "\r\n SockAddr: %T" + "\r\n", argv[0], eSockAddr) ); /* Make sure we are ready * Not sure how this would even happen, but... @@ -1926,42 +1920,35 @@ ERL_NIF_TERM nif_bind(ErlNifEnv* env, if (descP->state != SOCKET_STATE_OPEN) return esock_make_error(env, atom_exbadstate); - return nbind(env, descP, argv[1]); + if ((xres = esock_decode_sockaddr(env, eSockAddr, &sockAddr, &addrLen)) != NULL) + return esock_make_error_str(env, xres); + + return nbind(env, descP, &sockAddr, addrLen); } static ERL_NIF_TERM nbind(ErlNifEnv* env, SocketDescriptor* descP, - ERL_NIF_TERM addr) + SocketAddress* sockAddrP, + unsigned int addrLen) { - SocketAddress local; - unsigned int addrLen = 0; - char* xerr; - int port; - - SSDBG( descP, - ("SOCKET", "nbind -> entry with" - "\r\n addr: %T" - "\r\n", addr) ); - - if ((xerr = decode_laddress(env, - descP->domain, addr, &local, &addrLen)) != NULL) - return esock_make_error_str(env, xerr); + int port; SSDBG( descP, ("SOCKET", "nbind -> try bind\r\n") ); if (IS_SOCKET_ERROR(sock_bind(descP->sock, - (struct sockaddr*) &local, addrLen))) { + (struct sockaddr*) sockAddrP, addrLen))) { return esock_make_error_errno(env, sock_errno()); } - port = which_address_port(&local); + port = which_address_port(sockAddrP); + SSDBG( descP, ("SOCKET", "nbind -> port: %d\r\n", port) ); if (port == 0) { - SOCKLEN_T len = sizeof(local); - sys_memzero((char *) &local, len); - sock_name(descP->sock, &local.sa, &len); - port = which_address_port(&local); + SOCKLEN_T len = sizeof(SocketAddress); + sys_memzero((char *) sockAddrP, len); + sock_name(descP->sock, &sockAddrP->sa, &len); + port = which_address_port(sockAddrP); } else if (port == -1) { port = 0; } @@ -1973,83 +1960,6 @@ ERL_NIF_TERM nbind(ErlNifEnv* env, } -/* Decode the (local) address. The format of the address should - * either be an binary (domain = local) or an in_sockaddr(), which - * is either a in4_sockaddr record or a int6_sockaddr record - * (if domain is either inet or inet6). - */ -static -char* decode_laddress(ErlNifEnv* env, - int domain, - ERL_NIF_TERM localAddr, - SocketAddress* localP, - unsigned int* addrLenP) -{ - if (IS_BIN(env, localAddr)) { - return decode_laddress_binary(env, domain, localAddr, localP, addrLenP); - } else if (IS_TUPLE(env, localAddr)) { - return decode_in_sockaddr(env, localAddr, localP, addrLenP); - } else { - return ESOCK_STR_EINVAL; - } - -} - - -/* Only for domain = local (unix) - * The erlang interface module (socket) ensures that the size of the - * binary is > 0, so we need not do that here. - */ -static -char* decode_laddress_binary(ErlNifEnv* env, - int domain, - ERL_NIF_TERM localAddr, - SocketAddress* localP, - unsigned int* addrLenP) -{ - unsigned int addrLen; - -#ifdef HAVE_SYS_UN_H - ErlNifBinary bin; - - if (domain != AF_UNIX) - return ESOCK_STR_EINVAL; - - if (!GET_BIN(env, localAddr, &bin)) - return ESOCK_STR_EINVAL; - - if ((bin.size + -#ifdef __linux__ - /* Make sure the address gets zero terminated - * except when the first byte is \0 because then it is - * sort of zero terminated although the zero termination - * comes before the address... - * This fix handles Linux's nonportable - * abstract socket address extension. - */ - (bin.data[0] == '\0' ? 0 : 1) -#else - 1 -#endif - ) > sizeof(localP->un.sun_path)) - return ESOCK_STR_EINVAL; - - sys_memzero((char*)localP, sizeof(struct sockaddr_un)); - localP->un.sun_family = domain; - sys_memcpy(localP->un.sun_path, bin.data, bin.size); - addrLen = offsetof(struct sockaddr_un, sun_path) + bin.size; -#ifndef NO_SA_LEN - localP->un.sun_len = addrLen; -#endif - *addrLenP = addrLen; - return NULL; - -#else // HAVE_SYS_UN_H - return str_eafnosupport; -#endif - -} - /* ---------------------------------------------------------------------- @@ -2061,9 +1971,8 @@ char* decode_laddress_binary(ErlNifEnv* env, * Arguments: * Socket (ref) - Points to the socket descriptor. * SockAddr - Socket Address of "remote" host. - * This is in_sockaddr(), which is either - * in4_sockaddr (#in4_sockaddr{}) or - * in6_sockaddr (#in6_sockaddr{}). + * This is sockaddr(), which is either + * sockaddr_in4 or sockaddr_in6. */ static ERL_NIF_TERM nif_connect(ErlNifEnv* env, @@ -2082,8 +1991,8 @@ ERL_NIF_TERM nif_connect(ErlNifEnv* env, } eSockAddr = argv[1]; - if ((xres = decode_in_sockaddr(env, eSockAddr, - &descP->remote, &descP->addrLen)) != NULL) { + if ((xres = esock_decode_sockaddr(env, eSockAddr, + &descP->remote, &descP->addrLen)) != NULL) { return esock_make_error_str(env, xres); } @@ -2203,6 +2112,7 @@ BOOLEAN_T verify_is_connected(SocketDescriptor* descP, int* err) */ #ifndef SO_ERROR + int sz, code; sz = sizeof(descP->remote); @@ -2689,7 +2599,7 @@ ERL_NIF_TERM nsend(ErlNifEnv* env, * SendRef - A unique id for this (send) request. * Data - The data to send in the form of a IOVec. * Flags - Send flags. - * DestSockAddr - Destination (socket) address. + * Dest - Destination (socket) address. */ static @@ -2725,9 +2635,9 @@ ERL_NIF_TERM nif_sendto(ErlNifEnv* env, if (!esendflags2sendflags(eflags, &flags)) return esock_make_error(env, esock_atom_einval); - if ((xres = decode_in_sockaddr(env, eSockAddr, - &remoteAddr, - &remoteAddrLen)) != NULL) + if ((xres = esock_decode_sockaddr(env, eSockAddr, + &remoteAddr, + &remoteAddrLen)) != NULL) return esock_make_error_str(env, xres); return nsendto(env, descP, sendRef, &data, flags, &remoteAddr, remoteAddrLen); @@ -5404,11 +5314,11 @@ ERL_NIF_TERM recvfrom_check_result(ErlNifEnv* env, /* +++ We sucessfully got a message - time to encode the address +++ */ - ERL_NIF_TERM fromDomainT, fromSourceT; + ERL_NIF_TERM eSockAddr; - encode_address(env, - fromAddrP, fromAddrLen, - &fromDomainT, &fromSourceT); + esock_encode_sockaddr(env, + fromAddrP, fromAddrLen, + &eSockAddr); if (read == bufP->size) { data = MKBIN(env, bufP); @@ -5424,7 +5334,7 @@ ERL_NIF_TERM recvfrom_check_result(ErlNifEnv* env, data = MKSBIN(env, data, 0, read); } - return esock_make_ok2(env, MKT3(env, fromDomainT, fromSourceT, data)); + return esock_make_ok2(env, MKT2(env, eSockAddr, data)); } } @@ -5539,6 +5449,7 @@ char* decode_send_addr_tuple(ErlNifEnv* env, * 5: Scope Id: integer() * */ +/* static char* decode_in_sockaddr(ErlNifEnv* env, ERL_NIF_TERM eSockAddr, @@ -5552,10 +5463,10 @@ char* decode_in_sockaddr(ErlNifEnv* env, if (!GET_TUPLE(env, eSockAddr, &addrtSz, &addrt)) return ESOCK_STR_EINVAL; - /* + / * * We use the tuple size to figure out which * of the records this is. - */ + * / switch (addrtSz) { case 3: result = decode_in4_sockaddr(env, addrt, sockAddrP, addrLenP); @@ -5574,7 +5485,7 @@ char* decode_in_sockaddr(ErlNifEnv* env, return result; } - +*/ /* Decode an in4_sockaddr(). @@ -5582,6 +5493,7 @@ char* decode_in_sockaddr(ErlNifEnv* env, * The second, the port number integer . * The third and final, the ip4_address tuple. */ +/* static char* decode_in4_sockaddr(ErlNifEnv* env, const ERL_NIF_TERM* eIn4SockAddr, @@ -5590,18 +5502,18 @@ char* decode_in4_sockaddr(ErlNifEnv* env, { int port; - /* 1: Ensure that the tuple has the correct tag: in4_sockaddr */ + / * 1: Ensure that the tuple has the correct tag: in4_sockaddr * / if (COMPARE(atom_in4_sockaddr, eIn4SockAddr[0]) != 0) return ESOCK_STR_EINVAL; - /* 2: Get the port number */ + / * 2: Get the port number * / if (!GET_INT(env, eIn4SockAddr[1], &port)) return ESOCK_STR_EINVAL; - /* 3: Get the address. + / * 3: Get the address. * It can either be the atoms: any | loopback, * or the IPv4 address tuple (size 4). - */ + * / if (IS_ATOM(env, eIn4SockAddr[2])) { return decode_in4_sockaddr_atomaddr(env, eIn4SockAddr[2], port, sockAddrP, addrLenP); @@ -5612,9 +5524,10 @@ char* decode_in4_sockaddr(ErlNifEnv* env, return ESOCK_STR_EINVAL; } } +*/ - +/* static char* decode_in4_sockaddr_atomaddr(ErlNifEnv* env, ERL_NIF_TERM eAddr, @@ -5643,11 +5556,13 @@ char* decode_in4_sockaddr_atomaddr(ErlNifEnv* env, return NULL; } +*/ /* Decode an in4_sockaddr where the address field is a tuple. * Its *supposed* to be an ip4_address (tuple). */ +/* static char* decode_in4_sockaddr_addr(ErlNifEnv* env, ERL_NIF_TERM eAddr, @@ -5660,7 +5575,7 @@ char* decode_in4_sockaddr_addr(ErlNifEnv* env, int a, v; char addr[4]; - /* This shall be a 4 tuple */ + / * This shall be a 4 tuple * / if (!GET_TUPLE(env, eAddr, &ip4AddrTSz, &ip4AddrT)) return ESOCK_STR_EINVAL; @@ -5683,6 +5598,7 @@ char* decode_in4_sockaddr_addr(ErlNifEnv* env, return NULL; } +*/ @@ -5693,6 +5609,7 @@ char* decode_in4_sockaddr_addr(ErlNifEnv* env, * The forth, the flowinfo integer. * The fifth and final, the scope_id integer. */ +/* #if defined(HAVE_IN6) && defined(AF_INET6) static char* decode_in6_sockaddr(ErlNifEnv* env, @@ -5703,26 +5620,26 @@ char* decode_in6_sockaddr(ErlNifEnv* env, int port; unsigned int flowInfo, scopeId; - /* 1: Ensure that the tuple has the correct tag: in6_sockaddr */ + / * 1: Ensure that the tuple has the correct tag: in6_sockaddr * / if (COMPARE(atom_in6_sockaddr, eIn6SockAddr[0]) != 0) return ESOCK_STR_EINVAL; - /* 2: Get the port number */ + / * 2: Get the port number * / if (!GET_INT(env, eIn6SockAddr[1], &port)) return ESOCK_STR_EINVAL; - /* 4: Get the flowinfo */ + / * 4: Get the flowinfo * / if (!GET_UINT(env, eIn6SockAddr[3], &flowInfo)) return ESOCK_STR_EINVAL; - /* 5: Get the scope_id */ + / * 5: Get the scope_id * / if (!GET_UINT(env, eIn6SockAddr[4], &scopeId)) return ESOCK_STR_EINVAL; - /* 3: Get the address. + / * 3: Get the address. * It can either be the atoms: any | loopback, * or the IPv6 address tuple (size 8). - */ + * / if (IS_ATOM(env, eIn6SockAddr[2])) { return decode_in6_sockaddr_atomaddr(env, eIn6SockAddr[2], port, flowInfo, scopeId, @@ -5736,8 +5653,10 @@ char* decode_in6_sockaddr(ErlNifEnv* env, } } #endif +*/ +/* #if defined(HAVE_IN6) && defined(AF_INET6) static char* decode_in6_sockaddr_atomaddr(ErlNifEnv* env, @@ -5772,11 +5691,13 @@ char* decode_in6_sockaddr_atomaddr(ErlNifEnv* env, return NULL; } #endif +*/ #if defined(HAVE_IN6) && defined(AF_INET6) /* Decode an in6_sockaddr where the address field is a tuple */ +/* static char* decode_in6_sockaddr_addr(ErlNifEnv* env, ERL_NIF_TERM eAddr, @@ -5791,7 +5712,7 @@ char* decode_in6_sockaddr_addr(ErlNifEnv* env, int a, v; char addr[16]; - /* This shall be a 8 tuple */ + / * This shall be a 8 tuple * / if (!GET_TUPLE(env, eAddr, &ip6AddrTSz, &ip6AddrT)) return ESOCK_STR_EINVAL; @@ -5806,9 +5727,9 @@ char* decode_in6_sockaddr_addr(ErlNifEnv* env, sockAddrP->in6.sin6_port = sock_htons(port); sockAddrP->in6.sin6_flowinfo = flowInfo; sockAddrP->in6.sin6_scope_id = scopeId; - /* The address tuple is of size 8 + / * The address tuple is of size 8 * and each element is a two byte integer - */ + * / for (a = 0; a < 8; a++) { if (!GET_INT(env, ip6AddrT[a], &v)) return ESOCK_STR_EINVAL; @@ -5820,6 +5741,7 @@ char* decode_in6_sockaddr_addr(ErlNifEnv* env, return NULL; } +*/ #endif @@ -5912,6 +5834,7 @@ char* decode_address_tuple(ErlNifEnv* env, * Source: {Address, Port} | string() * */ +/* static void encode_address(ErlNifEnv* env, SocketAddress* sockAddrP, @@ -5923,7 +5846,7 @@ void encode_address(ErlNifEnv* env, switch (sockAddrP->sa.sa_family) { - /* +++ inet (IPv4) +++ */ + / * +++ inet (IPv4) +++ * / case AF_INET: if (addrLen >= sizeof(struct sockaddr_in)) { @@ -5948,7 +5871,7 @@ void encode_address(ErlNifEnv* env, break; - /* +++ inet6 (IPv6) +++ */ + / * +++ inet6 (IPv6) +++ * / #if defined(HAVE_IN6) && defined(AF_INET6) case AF_INET6: @@ -5959,9 +5882,9 @@ void encode_address(ErlNifEnv* env, char* a16 = (char*) &sockAddrP->in6.sin6_addr; port = sock_ntohs(sockAddrP->in6.sin6_port); - /* The address tuple is of size 8 + / * The address tuple is of size 8 * and each element is a two byte integer - */ + * / for (i = 0; i < 8; i++) { // at6[i] = MKI(env, get_int16(a16[i*2])); at6[i] = MKI(env, get_int16(a16 + i*2)); @@ -5980,7 +5903,7 @@ void encode_address(ErlNifEnv* env, break; #endif - /* +++ local (Unix Domain Sockets) +++ */ + / * +++ local (Unix Domain Sockets) +++ * / #ifdef HAVE_SYS_UN_H case AF_UNIX: @@ -5997,12 +5920,12 @@ void encode_address(ErlNifEnv* env, } else { m = my_strnlen(sockAddrP->un.sun_path, n); #ifdef __linux__ - /* Assume that the address is a zero terminated string, + / * Assume that the address is a zero terminated string, * except when the first byte is \0 i.e the string length is 0, * then use the reported length instead. * This fix handles Linux's nonportable * abstract socket address extension. - */ + * / if (m == 0) { m = n; } @@ -6020,9 +5943,10 @@ void encode_address(ErlNifEnv* env, *sourceT = esock_atom_undefined; break; - } /* switch (addrP->sa.sa_family) */ + } / * switch (addrP->sa.sa_family) * / } +*/ /* Decode the address when its an atom. @@ -7100,6 +7024,7 @@ char* decode_ip6_address(ErlNifEnv* env, #if defined(HAVE_SYS_UN_H) || defined(SO_BINDTODEVICE) /* strnlen doesn't exist everywhere */ +/* static size_t my_strnlen(const char *s, size_t maxlen) { @@ -7108,6 +7033,7 @@ size_t my_strnlen(const char *s, size_t maxlen) i++; return i; } +*/ #endif @@ -7439,7 +7365,7 @@ ErlNifFunc socket_funcs[] = // {"nif_debug", 1, nif_debug_, 0}, // The proper "socket" interface - // This is used when we already have a file descriptor + // nif_open/1 is used when we already have a file descriptor // {"nif_open", 1, nif_open, 0}, {"nif_open", 4, nif_open, 0}, {"nif_bind", 2, nif_bind, 0}, @@ -7608,7 +7534,7 @@ int on_load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info) esock_atom_any = MKA(env, "any"); esock_atom_dgram = MKA(env, "dgram"); esock_atom_error = MKA(env, "error"); - esock_atom_false = MKA(env, "famlse"); + esock_atom_false = MKA(env, "false"); esock_atom_family = MKA(env, "family"); esock_atom_flowinfo = MKA(env, "flowinfo"); esock_atom_inet = MKA(env, "inet"); diff --git a/erts/emulator/nifs/common/socket_util.c b/erts/emulator/nifs/common/socket_util.c index 37adee682b..05fb40e286 100644 --- a/erts/emulator/nifs/common/socket_util.c +++ b/erts/emulator/nifs/common/socket_util.c @@ -26,9 +26,23 @@ #include #include "socket_int.h" #include "socket_util.h" +#include "socket_dbg.h" #include "sys.h" +/* We don't have a "debug flag" to check here, so we + * should use the compile debug flag, whatever that is... + */ + +// #define COMPILE_DEBUG_FLAG_WE_NEED_TO_CHECK 1 +#if defined(COMPILE_DEBUG_FLAG_WE_NEED_TO_CHECK) +#define UTIL_DEBUG TRUE +#else +#define UTIL_DEBUG FALSE +#endif + +#define UDBG( proto ) ESOCK_DBG_PRINTF( UTIL_DEBUG , proto ) + extern char* erl_errno_id(int error); /* THIS IS JUST TEMPORARY??? */ @@ -69,15 +83,19 @@ char* esock_decode_sockaddr(ErlNifEnv* env, int fam; char* xres; + UDBG( ("SUTIL", "esock_decode_sockaddr -> entry\r\n") ); + if (!IS_MAP(env, eSockAddr)) return ESOCK_STR_EINVAL; if (!GET_MAP_VAL(env, eSockAddr, esock_atom_family, &efam)) return ESOCK_STR_EINVAL; + UDBG( ("SUTIL", "esock_decode_sockaddr -> try decode domain (%T)\r\n", efam) ); if ((xres = esock_decode_domain(env, efam, &fam)) != NULL) return xres; + UDBG( ("SUTIL", "esock_decode_sockaddr -> fam: %d\r\n", fam) ); switch (fam) { case AF_INET: xres = esock_decode_sockaddr_in4(env, eSockAddr, @@ -147,7 +165,8 @@ char* esock_encode_sockaddr(ErlNifEnv* env, #endif default: - xres = ESOCK_STR_EAFNOSUPPORT; + *eSockAddr = esock_atom_undefined; + xres = ESOCK_STR_EAFNOSUPPORT; break; } @@ -178,6 +197,9 @@ char* esock_decode_sockaddr_in4(ErlNifEnv* env, { ERL_NIF_TERM eport, eaddr; int port; + char* xres; + + UDBG( ("SUTIL", "esock_decode_sockaddr_in4 -> entry\r\n") ); /* Basic init */ sys_memzero((char*) sockAddrP, sizeof(struct sockaddr_in)); @@ -189,22 +211,28 @@ char* esock_decode_sockaddr_in4(ErlNifEnv* env, sockAddrP->sin_family = AF_INET; /* Extract (e) port number from map */ + UDBG( ("SUTIL", "esock_decode_sockaddr_in4 -> try get port number\r\n") ); if (!GET_MAP_VAL(env, eSockAddr, esock_atom_port, &eport)) return ESOCK_STR_EINVAL; /* Decode port number */ + UDBG( ("SUTIL", "esock_decode_sockaddr_in4 -> try decode port number\r\n") ); if (!GET_INT(env, eport, &port)) return ESOCK_STR_EINVAL; sockAddrP->sin_port = htons(port); /* Extract (e) address from map */ + UDBG( ("SUTIL", "esock_decode_sockaddr_in4 -> try get (ip) address\r\n") ); if (!GET_MAP_VAL(env, eSockAddr, esock_atom_addr, &eaddr)) return ESOCK_STR_EINVAL; /* Decode address */ - if (!esock_decode_ip4_address(env, eaddr, sockAddrP, addrLen)) - return ESOCK_STR_EINVAL; + UDBG( ("SUTIL", "esock_decode_sockaddr_in4 -> try decode (ip) address\r\n") ); + if ((xres = esock_decode_ip4_address(env, eaddr, sockAddrP, addrLen)) != NULL) + return xres; + + UDBG( ("SUTIL", "esock_decode_sockaddr_in4 -> done\r\n") ); return NULL; } @@ -232,6 +260,8 @@ char* esock_encode_sockaddr_in4(ErlNifEnv* env, int port; char* xres = NULL; + UDBG( ("SUTIL", "esock_encode_sockaddr_in4 -> entry\r\n") ); + if (addrLen >= sizeof(struct sockaddr_in)) { /* The port */ port = ntohs(sockAddrP->sin_port); @@ -239,15 +269,23 @@ char* esock_encode_sockaddr_in4(ErlNifEnv* env, /* The address */ if ((xres = esock_encode_ip4_address(env, &sockAddrP->sin_addr, - &eAddr)) != NULL) { + &eAddr)) == NULL) { /* And finally construct the in4_sockaddr record */ xres = make_sockaddr_in4(env, ePort, eAddr, eSockAddr); } else { + UDBG( ("SUTIL", "esock_encode_sockaddr_in4 -> " + "failed encoding (ip) address: " + "\r\n xres: %s" + "\r\n", xres) ); *eSockAddr = esock_atom_undefined; xres = ESOCK_STR_EINVAL; } } else { + UDBG( ("SUTIL", "esock_encode_sockaddr_in4 -> wrong size: " + "\r\n addrLen: %d" + "\r\n addr size: %d" + "\r\n", addrLen, sizeof(struct sockaddr_in)) ); *eSockAddr = esock_atom_undefined; xres = ESOCK_STR_EINVAL; } @@ -282,6 +320,7 @@ char* esock_decode_sockaddr_in6(ErlNifEnv* env, ERL_NIF_TERM eport, eaddr, eflowInfo, escopeId; int port; unsigned int flowInfo, scopeId; + char* xres; /* Basic init */ sys_memzero((char*) sockAddrP, sizeof(struct sockaddr_in6)); @@ -326,8 +365,8 @@ char* esock_decode_sockaddr_in6(ErlNifEnv* env, return ESOCK_STR_EINVAL; /* Decode address */ - if (!esock_decode_ip6_address(env, eaddr, sockAddrP, addrLen)) - return ESOCK_STR_EINVAL; + if ((xres = esock_decode_ip6_address(env, eaddr, sockAddrP, addrLen)) != NULL) + return xres; return NULL; } @@ -370,7 +409,7 @@ char* esock_encode_sockaddr_in6(ErlNifEnv* env, /* The address */ if ((xres = esock_encode_ip6_address(env, &sockAddrP->sin6_addr, - &eAddr)) != NULL) { + &eAddr)) == NULL) { /* And finally construct the in6_sockaddr record */ xres = make_sockaddr_in6(env, ePort, eAddr, eFlowInfo, eScopeId, eSockAddr); @@ -532,15 +571,22 @@ char* esock_decode_ip4_address(ErlNifEnv* env, struct sockaddr_in* sockAddrP, unsigned int* addrLen) { + UDBG( ("SUTIL", "esock_decode_ip4_address -> entry with" + "\r\n eAddr: %T" + "\r\n", eAddr) ); + if (IS_ATOM(env, eAddr)) { /* This is either 'any' or 'loopback' */ struct in_addr addr; if (COMPARE(esock_atom_loopback, eAddr) == 0) { + UDBG( ("SUTIL", "esock_decode_ip4_address -> address: lookback\r\n") ); addr.s_addr = htonl(INADDR_LOOPBACK); } else if (COMPARE(esock_atom_any, eAddr) == 0) { + UDBG( ("SUTIL", "esock_decode_ip4_address -> address: any\r\n") ); addr.s_addr = htonl(INADDR_ANY); } else { + UDBG( ("SUTIL", "esock_decode_ip4_address -> address: unknown\r\n") ); return ESOCK_STR_EINVAL; } @@ -596,13 +642,17 @@ char* esock_encode_ip4_address(ErlNifEnv* env, ERL_NIF_TERM at[4]; unsigned int atLen = sizeof(at) / sizeof(ERL_NIF_TERM); unsigned char* a = (unsigned char*) addrP; + ERL_NIF_TERM addr; /* The address */ for (i = 0; i < atLen; i++) { at[i] = MKI(env, a[i]); } - *eAddr = MKTA(env, at, atLen); + addr = MKTA(env, at, atLen); + UDBG( ("SUTIL", "esock_encode_ip4_address -> addr: %T\r\n", addr) ); + // *eAddr = MKTA(env, at, atLen); + *eAddr = addr; return NULL; } @@ -878,6 +928,108 @@ char* esock_encode_type(ErlNifEnv* env, +/* +++ esock_decode_protocol +++ + * + * Encode the native protocol to the Erlang form, that is: + * + * SOL_IP | IPPROTO_IP => ip + * SOL_IPV6 => ipv6 + * SOL_TCP => tcp + * SOL_UDP => udp + * SOL_SCTP => sctp + * + */ +extern +char* esock_encode_protocol(ErlNifEnv* env, + int proto, + ERL_NIF_TERM* eProto) +{ + char* xres = NULL; + + switch (proto) { +#if defined(SOL_IP) + case SOL_IP: +#else + case IPPROTO_IP: +#endif + *eProto = esock_atom_ip; + break; + +#if defined(SOL_IPV6) + case SOL_IPV6: + *eProto = esock_atom_ipv6; + break; +#endif + + case IPPROTO_TCP: + *eProto = esock_atom_tcp; + break; + + case IPPROTO_UDP: + *eProto = esock_atom_udp; + break; + +#if defined(HAVE_SCTP) + case IPPROTO_SCTP: + *eProto = esock_atom_sctp; + break; +#endif + + default: + *eProto = esock_atom_undefined; + xres = ESOCK_STR_EAFNOSUPPORT; + break; + } + + return xres; +} + + + +/* +++ esock_decode_protocol +++ + * + * Decode the Erlang form of the 'protocol' type, that is: + * + * ip => SOL_IP | IPPROTO_IP + * ipv6 => SOL_IPV6 + * tcp => SOL_TCP + * udp => SOL_UDP + * sctp => SOL_SCTP + * + */ +extern +char* esock_decode_protocol(ErlNifEnv* env, + ERL_NIF_TERM eProto, + int* proto) +{ + char* xres = NULL; + + if (COMPARE(esock_atom_ip, eProto) == 0) { +#if defined(SOL_IP) + *proto = SOL_IP; +#else + *proto = IPPROTO_IP; +#endif + } else if (COMPARE(esock_atom_ipv6, eProto) == 0) { + *proto = SOL_IPV6; + } else if (COMPARE(esock_atom_tcp, eProto) == 0) { + *proto = IPPROTO_TCP; + } else if (COMPARE(esock_atom_udp, eProto) == 0) { + *proto = IPPROTO_UDP; +#if defined(HAVE_SCTP) + } else if (COMPARE(esock_atom_sctp, eProto) == 0) { + *proto = IPPROTO_SCTP; +#endif + } else { + *proto = -1; + xres = ESOCK_STR_EAFNOSUPPORT; + } + + return xres; +} + + + /* *** esock_decode_bool *** * * Decode a boolean value. diff --git a/erts/emulator/nifs/common/socket_util.h b/erts/emulator/nifs/common/socket_util.h index f5594c408e..dedeb8dd7d 100644 --- a/erts/emulator/nifs/common/socket_util.h +++ b/erts/emulator/nifs/common/socket_util.h @@ -110,7 +110,6 @@ extern char* esock_decode_domain(ErlNifEnv* env, ERL_NIF_TERM eDomain, int* domain); - extern char* esock_encode_domain(ErlNifEnv* env, int domain, @@ -120,12 +119,20 @@ extern char* esock_decode_type(ErlNifEnv* env, ERL_NIF_TERM eType, int* type); - extern char* esock_encode_type(ErlNifEnv* env, int type, ERL_NIF_TERM* eType); +extern +char* esock_decode_protocol(ErlNifEnv* env, + ERL_NIF_TERM eProtocol, + int* protocol); +extern +char* esock_encode_protocol(ErlNifEnv* env, + int type, + ERL_NIF_TERM* eProtocol); + extern BOOLEAN_T esock_decode_bool(ERL_NIF_TERM val); extern diff --git a/erts/preloaded/ebin/socket.beam b/erts/preloaded/ebin/socket.beam index cafd5af945..f82db6e44e 100644 Binary files a/erts/preloaded/ebin/socket.beam and b/erts/preloaded/ebin/socket.beam differ diff --git a/erts/preloaded/src/socket.erl b/erts/preloaded/src/socket.erl index b89fa06da8..ba3ff6bab9 100644 --- a/erts/preloaded/src/socket.erl +++ b/erts/preloaded/src/socket.erl @@ -657,7 +657,7 @@ open(Domain, Type, Protocol0, Extra) when is_map(Extra) -> end. %% Note that this is just a convenience function for when the protocol was -%% not specified. If its actually specied, then that will be selected. +%% not specified. If its actually specified, then that will be selected. %% Also, this only works for the some of the type's (stream, dgram and %% seqpacket). default_protocol(null, stream) -> tcp; @@ -677,57 +677,22 @@ default_protocol(Protocol, _) -> Protocol. Addr :: any | loopback | sockaddr(), Reason :: term(). -bind(#socket{info = #{domain := inet}} = Socket, Addr) +bind(#socket{ref = SockRef, info = #{domain := inet}} = _Socket, Addr) when ((Addr =:= any) orelse (Addr =:= loopback)) -> - bind(Socket, ?SOCKADDR_IN4_DEFAULT(Addr)); -bind(#socket{info = #{domain := inet6}} = Socket, Addr) + nif_bind(SockRef, ?SOCKADDR_IN4_DEFAULT(Addr)); +bind(#socket{ref = SockRef, info = #{domain := inet6}} = _Socket, Addr) when ((Addr =:= any) orelse (Addr =:= loopback)) -> - bind(Socket, ?SOCKADDR_IN6_DEFAULT(Addr)); -bind(Socket, Addr) -> + nif_bind(SockRef, ?SOCKADDR_IN6_DEFAULT(Addr)); +bind(#socket{ref = SockRef} = _Socket, Addr) when is_map(Addr) -> try begin - nif_bind(Socket, ensure_sockaddr(Addr)) + nif_bind(SockRef, ensure_sockaddr(Addr)) end catch throw:ERROR -> ERROR end. -%% -spec bind(Socket, FileOrAddr) -> ok | {error, Reason} when -%% Socket :: socket(), -%% FileOrAddr :: binary() | string() | in_sockaddr() | any | loopback, -%% Reason :: term(). - -%% bind(Socket, File) when is_binary(File) -> -%% if -%% byte_size(File) =:= 0 -> -%% {error, einval}; -%% byte_size(File) =< 255 -> -%% nif_bind(Socket, File); -%% true -> -%% {error, einval} -%% end; -%% bind(Socket, File) when is_list(File) andalso (File =/= []) -> -%% Bin = unicode:characters_to_binary(File, file:native_name_encoding()), -%% if -%% byte_size(Bin) =< 255 -> -%% nif_bind(Socket, Bin); -%% true -> -%% {error, einval} -%% end; -%% bind(#socket{info = #{domain := inet}} = Socket, Addr) -%% when ((Addr =:= any) orelse (Addr =:= loopback)) -> -%% bind(Socket, #in4_sockaddr{addr = Addr}); -%% bind(#socket{info = #{domain := inet6}} = Socket, Addr) -%% when ((Addr =:= any) orelse (Addr =:= loopback)) -> -%% bind(Socket, #in6_sockaddr{addr = Addr}); -%% bind(#socket{info = #{domain := inet}, ref = SockRef} = _Socket, SockAddr) -%% when is_record(SockAddr, in4_sockaddr) -> -%% nif_bind(SockRef, SockAddr); -%% bind(#socket{info = #{domain := inet6}, ref = SockRef} = _Socket, SockAddr) -%% when is_record(SockAddr, in6_sockaddr) -> -%% nif_bind(SockRef, SockAddr). - %% =========================================================================== @@ -749,6 +714,9 @@ connect(Socket, SockAddr) -> Timeout :: timeout(), Reason :: term(). +%% +%% Is it possible to connect with family = local for the (dest) sockaddr? +%% connect(_Socket, _SockAddr, Timeout) when (is_integer(Timeout) andalso (Timeout =< 0)) -> {error, timeout}; @@ -970,9 +938,9 @@ sendto(Socket, Data, Flags, Dest) -> Timeout :: timeout(), Reason :: term(). -sendto(Socket, Data, Flags, DestSockAddr, Timeout) when is_list(Data) -> +sendto(Socket, Data, Flags, Dest, Timeout) when is_list(Data) -> Bin = erlang:list_to_binary(Data), - sendto(Socket, Bin, Flags, DestSockAddr, Timeout); + sendto(Socket, Bin, Flags, Dest, Timeout); sendto(#socket{ref = SockRef}, Data, Flags, Dest, Timeout) when is_binary(Data) andalso is_list(Flags) andalso -- cgit v1.2.3 From b7b80a767b2938014aa4cfb0c65d4ce5a9019bd5 Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Thu, 28 Jun 2018 13:50:52 +0200 Subject: [socket-nif] Fixed accept The pid compare test was incorrect. OTP-14831 --- erts/emulator/nifs/common/socket_nif.c | 150 +++++++++++++++++---------------- 1 file changed, 76 insertions(+), 74 deletions(-) diff --git a/erts/emulator/nifs/common/socket_nif.c b/erts/emulator/nifs/common/socket_nif.c index 848e502ec5..7f45fb7bcd 100644 --- a/erts/emulator/nifs/common/socket_nif.c +++ b/erts/emulator/nifs/common/socket_nif.c @@ -282,6 +282,7 @@ typedef union { struct { + // 0 = not open, 1 = open unsigned int open:1; // 0 = not conn, 1 = connecting, 2 = connected unsigned int connect:2; @@ -377,63 +378,11 @@ typedef union { * * * =================================================================== */ -/* #define MALLOC(SZ) enif_alloc((SZ)) */ -/* #define FREE(P) enif_free((P)) */ - -/* #define MKA(E,S) enif_make_atom((E), (S)) */ -/* #define MKBIN(E,B) enif_make_binary((E), (B)) */ -/* #define MKI(E,I) enif_make_int((E), (I)) */ -/* #define MKLA(E,A,L) enif_make_list_from_array((E), (A), (L)) */ -/* #define MKMA(E,KA,VA,L,M) enif_make_map_from_arrays((E), (KA), (VA), (L), (M)) */ -/* #define MKREF(E) enif_make_ref((E)) */ -/* #define MKS(E,S) enif_make_string((E), (S), ERL_NIF_LATIN1) */ -/* #define MKSL(E,S,L) enif_make_string_len((E), (S), (L), ERL_NIF_LATIN1) */ -/* #define MKSBIN(E,B,ST,SZ) enif_make_sub_binary((E), (B), (ST), (SZ)) */ -/* #define MKT2(E,E1,E2) enif_make_tuple2((E), (E1), (E2)) */ -/* #define MKT3(E,E1,E2,E3) enif_make_tuple3((E), (E1), (E2), (E3)) */ -/* #define MKT4(E,E1,E2,E3,E4) enif_make_tuple4((E), (E1), (E2), (E3), (E4)) */ -/* #define MKT8(E,E1,E2,E3,E4,E5,E6,E7,E8) \ */ -/* enif_make_tuple8((E), (E1), (E2), (E3), (E4), (E5), (E6), (E7), (E8)) */ - -/* #define MCREATE(N) enif_mutex_create((N)) */ -/* #define MDESTROY(M) enif_mutex_destroy((M)) */ -/* #define MLOCK(M) enif_mutex_lock((M)) */ -/* #define MUNLOCK(M) enif_mutex_unlock((M)) */ - -/* #define MONP(E,D,P,M) enif_monitor_process((E), (D), (P), (M)) */ -/* #define DEMONP(E,D,M) enif_demonitor_process((E), (D), (M)) */ - -/* #define SELECT(E,FD,M,O,P,R) \ */ -/* if (enif_select((E), (FD), (M), (O), (P), (R)) < 0) \ */ -/* return enif_make_badarg((E)); */ - -/* #define COMPARE(A, B) enif_compare((A), (B)) */ - -/* #define IS_ATOM(E, TE) enif_is_atom((E), (TE)) */ -/* #define IS_BIN(E, TE) enif_is_binary((E), (TE)) */ -/* #define IS_MAP(E, TE) enif_is_map((E), (TE)) */ -/* #define IS_NUM(E, TE) enif_is_number((E), (TE)) */ -/* #define IS_TUPLE(E, TE) enif_is_tuple((E), (TE)) */ - -/* #define GET_ATOM_LEN(E, TE, LP) \ */ -/* enif_get_atom_length((E), (TE), (LP), ERL_NIF_LATIN1) */ -/* #define GET_ATOM(E, TE, BP, MAX) \ */ -/* enif_get_atom((E), (TE), (BP), (MAX), ERL_NIF_LATIN1) */ -/* #define GET_BIN(E, TE, BP) enif_inspect_iolist_as_binary((E), (TE), (BP)) */ -/* #define GET_INT(E, TE, IP) enif_get_int((E), (TE), (IP)) */ -/* #define GET_STR(E, L, B, SZ) \ */ -/* enif_get_string((E), (L), (B), (SZ), ERL_NIF_LATIN1) */ -/* #define GET_UINT(E, TE, IP) enif_get_uint((E), (TE), (IP)) */ -/* #define GET_TUPLE(E, TE, TSZ, TA) enif_get_tuple((E), (TE), (TSZ), (TA)) */ - -/* #define ALLOC_BIN(SZ, BP) enif_alloc_binary((SZ), (BP)) */ -/* #define REALLOC_BIN(SZ, BP) enif_realloc_binary((SZ), (BP)) */ - - #define SGDBG( proto ) ESOCK_DBG_PRINTF( data.dbg , proto ) #define SSDBG( __D__ , proto ) ESOCK_DBG_PRINTF( (__D__)->dbg , proto ) + /* =================================================================== * * * * Basic socket operations * @@ -521,22 +470,6 @@ static unsigned long one_value = 1; #define SOCKOPTLEN_T SOCKLEN_T #endif -/* The general purpose sockaddr * / -typedef union { - struct sockaddr in; - struct sockaddr_in in4; - -#ifdef HAVE_IN6 - struct sockaddr_in6 in6; -#endif - -#ifdef HAVE_SYS_UN_H - struct sockaddr_un un; -#endif - -} SocketAddress; -*/ - /* We can use the IPv4 def for this since the beginning * is the same for INET and INET6 */ #define which_address_port(sap) \ @@ -1933,7 +1866,7 @@ ERL_NIF_TERM nbind(ErlNifEnv* env, SocketAddress* sockAddrP, unsigned int addrLen) { - int port; + int port, ntohs_port; SSDBG( descP, ("SOCKET", "nbind -> try bind\r\n") ); @@ -1953,9 +1886,11 @@ ERL_NIF_TERM nbind(ErlNifEnv* env, port = 0; } - SSDBG( descP, ("SOCKET", "nbind -> done with port = %d\r\n", port) ); + ntohs_port = sock_ntohs(port); + + SSDBG( descP, ("SOCKET", "nbind -> done with port = %d\r\n", ntohs_port) ); - return esock_make_ok2(env, MKI(env, port)); + return esock_make_ok2(env, MKI(env, ntohs_port)); } @@ -1983,6 +1918,8 @@ ERL_NIF_TERM nif_connect(ErlNifEnv* env, ERL_NIF_TERM eSockAddr; char* xres; + SGDBG( ("SOCKET", "nif_connect -> entry with argc: %d\r\n", argc) ); + /* Extract arguments and perform preliminary validation */ if ((argc != 2) || @@ -1991,6 +1928,12 @@ ERL_NIF_TERM nif_connect(ErlNifEnv* env, } eSockAddr = argv[1]; + SSDBG( descP, + ("SOCKET", "nif_connect -> args when sock = %d:" + "\r\n Socket: %T" + "\r\n SockAddr: %T" + "\r\n", descP->sock, argv[0], eSockAddr) ); + if ((xres = esock_decode_sockaddr(env, eSockAddr, &descP->remote, &descP->addrLen)) != NULL) { return esock_make_error_str(env, xres); @@ -2166,6 +2109,8 @@ ERL_NIF_TERM nif_listen(ErlNifEnv* env, SocketDescriptor* descP; int backlog; + SGDBG( ("SOCKET", "nif_listen -> entry with argc: %d\r\n", argc) ); + /* Extract arguments and perform preliminary validation */ if ((argc != 2) || @@ -2174,6 +2119,12 @@ ERL_NIF_TERM nif_listen(ErlNifEnv* env, return enif_make_badarg(env); } + SSDBG( descP, + ("SOCKET", "nif_listen -> args when sock = %d:" + "\r\n Socket: %T" + "\r\n backlog: %d" + "\r\n", descP->sock, argv[0], backlog) ); + return nlisten(env, descP, backlog); } @@ -2219,6 +2170,8 @@ ERL_NIF_TERM nif_accept(ErlNifEnv* env, SocketDescriptor* descP; ERL_NIF_TERM ref; + SGDBG( ("SOCKET", "nif_accept -> entry with argc: %d\r\n", argc) ); + /* Extract arguments and perform preliminary validation */ if ((argc != 2) || @@ -2226,6 +2179,12 @@ ERL_NIF_TERM nif_accept(ErlNifEnv* env, return enif_make_badarg(env); } ref = argv[1]; + + SSDBG( descP, + ("SOCKET", "nif_accept -> args when sock = %d:" + "\r\n Socket: %T" + "\r\n ReqRef: %T" + "\r\n", descP->sock, argv[0], ref) ); return naccept(env, descP, ref); } @@ -2275,17 +2234,27 @@ ERL_NIF_TERM naccept_listening(ErlNifEnv* env, int save_errno; ErlNifPid caller; + SSDBG( descP, ("SOCKET", "naccept_listening -> get caller\r\n") ); + if (enif_self(env, &caller) == NULL) return esock_make_error(env, atom_exself); n = sizeof(remote); sys_memzero((char *) &remote, n); + SSDBG( descP, ("SOCKET", "naccept_listening -> try accept\r\n") ); accSock = sock_accept(descP->sock, (struct sockaddr*) &remote, &n); if (accSock == INVALID_SOCKET) { + save_errno = sock_errno(); + + SSDBG( descP, + ("SOCKET", + "naccept_listening -> accept failed (%d)\r\n", save_errno) ); + if (save_errno == ERRNO_BLOCK) { /* *** Try again later *** */ + SSDBG( descP, ("SOCKET", "naccept_listening -> would block\r\n") ); descP->currentAcceptor.pid = caller; if (MONP(env, descP, @@ -2328,6 +2297,9 @@ ERL_NIF_TERM naccept_listening(ErlNifEnv* env, return esock_make_error(env, esock_atom_eagain); } else { + SSDBG( descP, + ("SOCKET", + "naccept_listening -> errno: %d\r\n", save_errno) ); return esock_make_error_errno(env, save_errno); } @@ -2339,6 +2311,8 @@ ERL_NIF_TERM naccept_listening(ErlNifEnv* env, * We got one */ + SSDBG( descP, ("SOCKET", "naccept_listening -> accept success\r\n") ); + if ((accEvent = sock_create_event(accSock)) == INVALID_EVENT) { save_errno = sock_errno(); while ((sock_close(accSock) == INVALID_SOCKET) && @@ -2400,29 +2374,53 @@ ERL_NIF_TERM naccept_accepting(ErlNifEnv* env, ErlNifPid caller; int save_errno; + SSDBG( descP, ("SOCKET", "naccept_accepting -> get caller\r\n") ); + if (enif_self(env, &caller) == NULL) return esock_make_error(env, atom_exself); - if (compare_pids(env, &descP->currentAcceptor.pid, &caller) != 0) { + SSDBG( descP, ("SOCKET", "naccept_accepting -> check: " + "are caller current acceptor:" + "\r\n Caller: %T" + "\r\n Current: %T" + "\r\n", caller, descP->currentAcceptor.pid) ); + + if (!compare_pids(env, &descP->currentAcceptor.pid, &caller)) { /* This will have to do until we implement the queue. * When we have the queue, we should simply push this request, * and instead return with eagain (the caller will then wait * for the select message). */ + + SSDBG( descP, + ("SOCKET", + "naccept_accepting -> not current acceptor: busy\r\n") ); + return esock_make_error(env, atom_exbusy); } n = sizeof(descP->remote); sys_memzero((char *) &remote, n); + SSDBG( descP, ("SOCKET", "naccept_accepting -> try accept\r\n") ); accSock = sock_accept(descP->sock, (struct sockaddr*) &remote, &n); if (accSock == INVALID_SOCKET) { + save_errno = sock_errno(); + + SSDBG( descP, + ("SOCKET", + "naccept_accepting -> accept failed (%d)\r\n", save_errno) ); + if (save_errno == ERRNO_BLOCK) { /* * Just try again, no real error, just a ghost trigger from poll, */ + SSDBG( descP, + ("SOCKET", + "naccept_accepting -> would block: try again\r\n") ); + SELECT(env, descP->sock, (ERL_NIF_SELECT_READ), @@ -2430,6 +2428,9 @@ ERL_NIF_TERM naccept_accepting(ErlNifEnv* env, return esock_make_error(env, esock_atom_eagain); } else { + SSDBG( descP, + ("SOCKET", + "naccept_accepting -> errno: %d\r\n", save_errno) ); return esock_make_error_errno(env, save_errno); } } else { @@ -2440,6 +2441,8 @@ ERL_NIF_TERM naccept_accepting(ErlNifEnv* env, * We got one */ + SSDBG( descP, ("SOCKET", "naccept_accepting -> accept success\r\n") ); + if ((accEvent = sock_create_event(accSock)) == INVALID_EVENT) { save_errno = sock_errno(); while ((sock_close(accSock) == INVALID_SOCKET) && @@ -2480,7 +2483,6 @@ ERL_NIF_TERM naccept_accepting(ErlNifEnv* env, accDescP->state = SOCKET_STATE_CONNECTED; - /* Here we should have the test if we have something in the queue. * And if so, pop it and copy the (waiting) acceptor, and then * make a new select with that info). -- cgit v1.2.3 From 9b45f4dd2cc8862a183941bb4925223bcec35987 Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Wed, 27 Jun 2018 18:37:29 +0200 Subject: [test-socket-nif] Add *very primitive* test progs --- lib/kernel/test/socket_client.erl | 99 +++++++++++++++++++++++++++++++++++ lib/kernel/test/socket_server.erl | 107 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 206 insertions(+) create mode 100644 lib/kernel/test/socket_client.erl create mode 100644 lib/kernel/test/socket_server.erl diff --git a/lib/kernel/test/socket_client.erl b/lib/kernel/test/socket_client.erl new file mode 100644 index 0000000000..6d74d19442 --- /dev/null +++ b/lib/kernel/test/socket_client.erl @@ -0,0 +1,99 @@ +%%%------------------------------------------------------------------- +%%% @author Micael Karlberg +%%% @copyright (C) 2018, Micael Karlberg +%%% @doc +%%% +%%% @end +%%% Created : 27 Jun 2018 by Micael Karlberg +%%%------------------------------------------------------------------- +-module(client). + +-export([start/1]). + +start(Port) -> + start_tcp(Port). + +start_tcp(Port) -> + start(inet, stream, tcp, Port). + +start(Domain, Type, Proto, Port) -> + try do_init(Domain, Type, Proto) of + Sock -> + connect(Sock, Domain, Port) + catch + throw:E:P -> + e("Failed initiate: " + "~n Error: ~p" + "~n Path: ~p", [E, P]) + end. + +do_init(Domain, Type, Proto) -> + i("try (socket) open"), + Sock = case socket:open(Domain, Type, Proto) of + {ok, S} -> + S; + {error, OReason} -> + throw({open, OReason}) + end, + i("try (socket) bind"), + case socket:bind(Sock, any) of + {ok, _P} -> + Sock; + {error, BReason} -> + throw({bind, BReason}) + end. + +which_addr(Domain) -> + Iflist = case inet:getifaddrs() of + {ok, IFL} -> + IFL; + {error, Reason} -> + throw({inet,getifaddrs,Reason}) + end, + which_addr(Domain, Iflist). + + +connect(Sock, Domain, Port) -> + Addr = which_addr(Domain), + SA = #{family => Domain, + addr => Addr, + port => Port}, + i("try (socket) connect to ~p", [SA]), + case socket:connect(Sock, SA) of + ok -> + i("connected"), + ok; + {error, Reason} -> + e("connect failure: " + "~n ~p", [Reason]), + exit({connect, Reason}) + end. + + +which_addr(_Domain, []) -> + throw(no_address); +which_addr(Domain, [{Name, IFO}|_IFL]) when (Name =/= "lo") -> + which_addr2(Domain, IFO); +which_addr(Domain, [_|IFL]) -> + which_addr(Domain, IFL). + +which_addr2(inet = _Domain, [{addr, Addr}|_IFO]) when (size(Addr) =:= 4) -> + Addr; +which_addr2(inet6 = _Domain, [{addr, Addr}|_IFO]) when (size(Addr) =:= 8) -> + Addr; +which_addr2(Domain, [_|IFO]) -> + which_addr2(Domain, IFO). + + + +e(F, A) -> + p(" " ++ F, A). + +i(F) -> + i(F, []). +i(F, A) -> + p("*** " ++ F, A). + +p(F, A) -> + io:format("[client] " ++ F ++ "~n", A). + diff --git a/lib/kernel/test/socket_server.erl b/lib/kernel/test/socket_server.erl new file mode 100644 index 0000000000..039998ba92 --- /dev/null +++ b/lib/kernel/test/socket_server.erl @@ -0,0 +1,107 @@ +%%%------------------------------------------------------------------- +%%% @author Micael Karlberg +%%% @copyright (C) 2018, Micael Karlberg +%%% @doc +%%% +%%% @end +%%% Created : 27 Jun 2018 by Micael Karlberg +%%%------------------------------------------------------------------- +-module(server). + +-export([start/0]). + +start() -> + start_tcp(). + +start_tcp() -> + start(inet, stream, tcp). + +start(Domain, Type, Proto) -> + try do_init(Domain, Type, Proto) of + Sock -> + accept_loop(Sock) + catch + throw:E:P -> + e("Failed initiate: " + "~n Error: ~p" + "~n Path: ~p", [E, P]) + end. + +do_init(Domain, Type, Proto) -> + i("try (socket) open"), + Sock = case socket:open(Domain, Type, Proto) of + {ok, S} -> + S; + {error, OReason} -> + throw({open, OReason}) + end, + i("opened - now try find (local) address"), + Addr = which_addr(Domain), + SA = #{family => Domain, + addr => Addr}, + i("addr ~p - now try (socket) bind", [Addr]), + Port = case socket:bind(Sock, SA) of + {ok, P} -> + P; + {error, BReason} -> + throw({bind, BReason}) + end, + i("bound to ~w - now try (socket) listen", [Port]), + case socket:listen(Sock) of + ok -> + Sock; + {error, LReason} -> + throw({listen, LReason}) + end. + +which_addr(Domain) -> + Iflist = case inet:getifaddrs() of + {ok, IFL} -> + IFL; + {error, Reason} -> + throw({inet,getifaddrs,Reason}) + end, + which_addr(Domain, Iflist). + +which_addr(_Domain, []) -> + throw(no_address); +which_addr(Domain, [{Name, IFO}|_IFL]) when (Name =/= "lo") -> + which_addr2(Domain, IFO); +which_addr(Domain, [_|IFL]) -> + which_addr(Domain, IFL). + +which_addr2(inet = _Domain, [{addr, Addr}|_IFO]) when (size(Addr) =:= 4) -> + Addr; +which_addr2(inet6 = _Domain, [{addr, Addr}|_IFO]) when (size(Addr) =:= 8) -> + Addr; +which_addr2(Domain, [_|IFO]) -> + which_addr2(Domain, IFO). + + +accept_loop(LSock) -> + accept_loop(LSock, []). + +accept_loop(LSock, Socks) -> + i("try accept"), + case socket:accept(LSock, infinity) of + {ok, Sock} -> + i("accepted: ~p", [Sock]), + accept_loop(LSock, [Sock|Socks]); + {error, Reason} -> + e("accept failure: " + "~n ~p", [Reason]), + exit({accept, Reason}) + end. + + +e(F, A) -> + p(" " ++ F, A). + +i(F) -> + i(F, []). +i(F, A) -> + p("*** " ++ F, A). + +p(F, A) -> + io:format("[server] " ++ F ++ "~n", A). + -- cgit v1.2.3 From b09136301525b0717e897ec0864c3d2ea7708758 Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Thu, 28 Jun 2018 21:57:03 +0200 Subject: [socket-nit-test] Added some more test code Added some more features o the simple socket test server and client. OTP-14831 --- lib/kernel/test/Makefile | 13 ++++++- lib/kernel/test/socket_client.erl | 23 +++++++++++- lib/kernel/test/socket_server.erl | 73 ++++++++++++++++++++++++++++++++++++--- 3 files changed, 103 insertions(+), 6 deletions(-) diff --git a/lib/kernel/test/Makefile b/lib/kernel/test/Makefile index 4a86265a4a..07e7922d3d 100644 --- a/lib/kernel/test/Makefile +++ b/lib/kernel/test/Makefile @@ -24,6 +24,10 @@ include $(ERL_TOP)/make/$(TARGET)/otp.mk # Target Specs # ---------------------------------------------------- +SOCKET_MODULES = \ + socket_server \ + socket_client + MODULES= \ rpc_SUITE \ pdict_SUITE \ @@ -90,7 +94,8 @@ MODULES= \ sendfile_SUITE \ standard_error_SUITE \ multi_load_SUITE \ - zzz_SUITE + zzz_SUITE \ + $(SOCKET_MODULES) APP_FILES = \ appinc.app \ @@ -127,6 +132,9 @@ ERL_COMPILE_FLAGS += EBIN = . +SOCKET_TARGETS = $(SOCKET_MODULES:%=$(EBIN)/%.$(EMULATOR)) + + # ---------------------------------------------------- # Targets # ---------------------------------------------------- @@ -147,6 +155,9 @@ clean: docs: +socket: $(SOCKET_TARGETS) + + # ---------------------------------------------------- # Release Target # ---------------------------------------------------- diff --git a/lib/kernel/test/socket_client.erl b/lib/kernel/test/socket_client.erl index 6d74d19442..13e87f4109 100644 --- a/lib/kernel/test/socket_client.erl +++ b/lib/kernel/test/socket_client.erl @@ -6,7 +6,7 @@ %%% @end %%% Created : 27 Jun 2018 by Micael Karlberg %%%------------------------------------------------------------------- --module(client). +-module(socket_client). -export([start/1]). @@ -62,6 +62,7 @@ connect(Sock, Domain, Port) -> case socket:connect(Sock, SA) of ok -> i("connected"), + send_loop(Sock), ok; {error, Reason} -> e("connect failure: " @@ -70,6 +71,26 @@ connect(Sock, Domain, Port) -> end. +send_loop(Sock) -> + send_loop(Sock, 1). + +send_loop(Sock, N) -> + case socket:send(Sock, <<0:32, N:32, "hejsan">>) of + ok -> + case send:recv(Sock, 0) of + {ok, <<1:32, N:32, "hejsan">>} -> + send_loop(Sock, N+1); + {error, RReason} -> + e("Failed recv response for request ~w: " + "~n ~p", [RReason]), + exit({failed_recv, RReason}) + end; + {error, SReason} -> + e("Failed send request ~w: " + "~n ~p", [SReason]), + exit({failed_send, SReason}) + end. + which_addr(_Domain, []) -> throw(no_address); which_addr(Domain, [{Name, IFO}|_IFL]) when (Name =/= "lo") -> diff --git a/lib/kernel/test/socket_server.erl b/lib/kernel/test/socket_server.erl index 039998ba92..0effc7c0ff 100644 --- a/lib/kernel/test/socket_server.erl +++ b/lib/kernel/test/socket_server.erl @@ -6,10 +6,12 @@ %%% @end %%% Created : 27 Jun 2018 by Micael Karlberg %%%------------------------------------------------------------------- --module(server). +-module(socket_server). -export([start/0]). +-record(handler, {socket, parent}). + start() -> start_tcp(). @@ -17,6 +19,7 @@ start_tcp() -> start(inet, stream, tcp). start(Domain, Type, Proto) -> + put(sname, "starter"), try do_init(Domain, Type, Proto) of Sock -> accept_loop(Sock) @@ -79,14 +82,23 @@ which_addr2(Domain, [_|IFO]) -> accept_loop(LSock) -> + put(sname, "accept-loop"), accept_loop(LSock, []). -accept_loop(LSock, Socks) -> +accept_loop(LSock, Handlers) -> i("try accept"), case socket:accept(LSock, infinity) of {ok, Sock} -> i("accepted: ~p", [Sock]), - accept_loop(LSock, [Sock|Socks]); + case handle_accept_success(Sock) of + {ok, Handler} -> + accept_loop(LSock, [Handler|Handlers]); + {error, HReason} -> + e("Failed starting handler: " + "~n ~p", [HReason]), + socket:close(Sock), + exit({failed_starting_handler, HReason}) + end; {error, Reason} -> e("accept failure: " "~n ~p", [Reason]), @@ -94,6 +106,56 @@ accept_loop(LSock, Socks) -> end. +handle_accept_success(Sock) -> + Self = self(), + Handler = spawn_link(fun() -> handler_init(Self, Sock) end), + case socket:setopt(Sock, otp, controlling_process, Handler) of + ok -> + %% Normally we should have a msgs collection here + %% (of messages we receive before the control was + %% handled over to Handler), but since we don't + %% have active implemented yet... + handler_continue(Handler), + {ok, {Handler, Sock}}; + {error, _} = ERROR -> + exit(Handler, kill), + ERROR + end. + + +handler_init(Parent, Socket) -> + put(sname, "handler"), + receive + {handler, Parent, continue} -> + handler_loop(#handler{parent = Parent, + socket = Socket}) + end. + +handler_continue(Handler) -> + Handler ! {handler, self(), continue}. + +handler_loop(#handler{socket = Socket} = H) -> + case socket:read(Socket, 0) of + {ok, <<0:32, N:32, ReqData/binary>>} -> + i("received request ~w: " + "~n ~p", [N, ReqData]), + Reply = <<1:32, N:32, ReqData/binary>>, + case socket:send(Socket, Reply) of + ok -> + i("successfully sent reply ~w", [N]), + handler_loop(H); + {error, SReason} -> + e("failed sending reply ~w:" + "~n ~p", [N, SReason]), + exit({failed_sending_reply, SReason}) + end; + {error, RReason} -> + e("failed reading request: " + "~n ~p", [RReason]), + exit({failed_sending_reply, RReason}) + end. + + e(F, A) -> p(" " ++ F, A). @@ -103,5 +165,8 @@ i(F, A) -> p("*** " ++ F, A). p(F, A) -> - io:format("[server] " ++ F ++ "~n", A). + p(get(sname), F, A). + +p(SName, F, A) -> + io:format("[server,~s] " ++ F ++ "~n", [SName|A]). -- cgit v1.2.3 From 24be0729fe3a1ccfd5f0713b565463d6557d8aa7 Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Fri, 29 Jun 2018 18:23:55 +0200 Subject: [socket-nif] Fixed (stream) recv Fixed handling of closed in the recv function. We still need to properly handle when we get 0 bytes of data for other types ock sockets then stream (its valid for dgram for instance). OTP-14831 --- erts/emulator/nifs/common/socket_int.h | 1 + erts/emulator/nifs/common/socket_nif.c | 695 ++++++++++++++++++++++++-------- erts/emulator/nifs/common/socket_util.c | 89 +++- erts/emulator/nifs/common/socket_util.h | 3 + erts/preloaded/ebin/socket.beam | Bin 39680 -> 42488 bytes erts/preloaded/src/socket.erl | 288 +++++++------ lib/kernel/test/socket_client.erl | 105 ++++- lib/kernel/test/socket_server.erl | 105 ++++- 8 files changed, 973 insertions(+), 313 deletions(-) diff --git a/erts/emulator/nifs/common/socket_int.h b/erts/emulator/nifs/common/socket_int.h index d6a612cab6..a3e54360fe 100644 --- a/erts/emulator/nifs/common/socket_int.h +++ b/erts/emulator/nifs/common/socket_int.h @@ -192,6 +192,7 @@ extern ERL_NIF_TERM esock_atom_einval; #define GET_INT(E, TE, IP) enif_get_int((E), (TE), (IP)) #define GET_LIST_ELEM(E, L, HP, TP) enif_get_list_cell((E), (L), (HP), (TP)) #define GET_LIST_LEN(E, L, LP) enif_get_list_length((E), (L), (LP)) +#define GET_LPID(E, T, P) enif_get_local_pid((E), (T), (P)) #define GET_STR(E, L, B, SZ) \ enif_get_string((E), (L), (B), (SZ), ERL_NIF_LATIN1) #define GET_UINT(E, TE, IP) enif_get_uint((E), (TE), (IP)) diff --git a/erts/emulator/nifs/common/socket_nif.c b/erts/emulator/nifs/common/socket_nif.c index 7f45fb7bcd..027155fc92 100644 --- a/erts/emulator/nifs/common/socket_nif.c +++ b/erts/emulator/nifs/common/socket_nif.c @@ -182,8 +182,8 @@ /* Debug stuff... */ -#define SOCKET_NIF_DEBUG_DEFAULT TRUE -#define SOCKET_DEBUG_DEFAULT TRUE +#define SOCKET_NIF_DEBUG_DEFAULT FALSE +#define SOCKET_DEBUG_DEFAULT FALSE /* Counters and stuff (Don't know where to sent this stuff anyway) */ #define SOCKET_NIF_IOW_DEFAULT FALSE @@ -344,6 +344,7 @@ typedef union { #define SOCKET_OPT_OTP_DEBUG 0 #define SOCKET_OPT_OTP_IOW 1 +#define SOCKET_OPT_OTP_CTRL_PROC 2 #define SOCKET_OPT_SOCK_BROADCAST 4 #define SOCKET_OPT_SOCK_DONTROUTE 7 @@ -594,16 +595,16 @@ typedef struct { ERL_NIF_TERM version; ERL_NIF_TERM buildDate; BOOLEAN_T dbg; - BOOLEAN_T iow; + BOOLEAN_T iow; ErlNifMutex* cntMtx; uint32_t numSockets; - uint32_t numTypeDGrams; uint32_t numTypeStreams; + uint32_t numTypeDGrams; uint32_t numTypeSeqPkgs; - uint32_t numDomainLocal; uint32_t numDomainInet; uint32_t numDomainInet6; + uint32_t numDomainLocal; uint32_t numProtoIP; uint32_t numProtoTCP; uint32_t numProtoUDP; @@ -751,6 +752,9 @@ static ERL_NIF_TERM nsetopt_otp_debug(ErlNifEnv* env, static ERL_NIF_TERM nsetopt_otp_iow(ErlNifEnv* env, SocketDescriptor* descP, ERL_NIF_TERM eVal); +static ERL_NIF_TERM nsetopt_otp_ctrl_proc(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); static ERL_NIF_TERM nsetopt_native(ErlNifEnv* env, SocketDescriptor* descP, int level, @@ -1045,11 +1049,13 @@ static ERL_NIF_TERM send_check_result(ErlNifEnv* env, SocketDescriptor* descP, ssize_t written, ssize_t dataSize, + int saveErrno, ERL_NIF_TERM sendRef); static ERL_NIF_TERM recv_check_result(ErlNifEnv* env, SocketDescriptor* descP, int read, int toRead, + int saveErrno, ErlNifBinary* bufP, ERL_NIF_TERM recvRef); static ERL_NIF_TERM recvfrom_check_result(ErlNifEnv* env, @@ -1214,6 +1220,11 @@ static BOOLEAN_T restore_network_namespace(int ns, SOCKET sock, int* err); #endif static BOOLEAN_T cnt_inc(uint32_t* cnt, uint32_t inc); +static void cnt_dec(uint32_t* cnt, uint32_t dec); + +static void inc_socket(int domain, int type, int protocol); +static void dec_socket(int domain, int type, int protocol); + /* #if defined(HAVE_SYS_UN_H) || defined(SO_BINDTODEVICE) @@ -1698,12 +1709,13 @@ ERL_NIF_TERM nopen(ErlNifEnv* env, * * */ - SELECT(env, - event, - (ERL_NIF_SELECT_READ), - descP, NULL, esock_atom_undefined); + SELECT(env, + event, + (ERL_NIF_SELECT_READ), + descP, NULL, esock_atom_undefined); #endif + inc_socket(domain, type, protocol); return esock_make_ok2(env, res); } @@ -2515,21 +2527,31 @@ ERL_NIF_TERM nif_send(ErlNifEnv* env, { SocketDescriptor* descP; ERL_NIF_TERM sendRef; - ErlNifBinary data; + ErlNifBinary sndData; unsigned int eflags; int flags; ERL_NIF_TERM res; + SGDBG( ("SOCKET", "nif_send -> entry with argc: %d\r\n", argc) ); + /* Extract arguments and perform preliminary validation */ if ((argc != 4) || !enif_get_resource(env, argv[0], sockets, (void**) &descP) || - !GET_BIN(env, argv[2], &data) || + !GET_BIN(env, argv[2], &sndData) || !GET_UINT(env, argv[3], &eflags)) { return enif_make_badarg(env); } sendRef = argv[1]; + SSDBG( descP, + ("SOCKET", "nif_send -> args when sock = %d:" + "\r\n Socket: %T" + "\r\n SendRef: %T" + "\r\n Size of data: %d" + "\r\n eFlags: %d" + "\r\n", descP->sock, argv[0], sendRef, sndData.size, eflags) ); + if (!IS_CONNECTED(descP)) return esock_make_error(env, atom_enotconn); @@ -2551,7 +2573,7 @@ ERL_NIF_TERM nif_send(ErlNifEnv* env, * time we do. */ - res = nsend(env, descP, sendRef, &data, flags); + res = nsend(env, descP, sendRef, &sndData, flags); MUNLOCK(descP->writeMtx); @@ -2570,9 +2592,10 @@ static ERL_NIF_TERM nsend(ErlNifEnv* env, SocketDescriptor* descP, ERL_NIF_TERM sendRef, - ErlNifBinary* dataP, + ErlNifBinary* sndDataP, int flags) { + int save_errno; ssize_t written; if (!descP->isWritable) @@ -2583,9 +2606,15 @@ ERL_NIF_TERM nsend(ErlNifEnv* env, */ cnt_inc(&descP->writeTries, 1); - written = sock_send(descP->sock, dataP->data, dataP->size, flags); + written = sock_send(descP->sock, sndDataP->data, sndDataP->size, flags); + if (IS_SOCKET_ERROR(written)) + save_errno = sock_errno(); + else + save_errno = -1; // The value does not actually matter in this case + - return send_check_result(env, descP, written, dataP->size, sendRef); + return send_check_result(env, descP, + written, sndDataP->size, save_errno, sendRef); } @@ -2655,6 +2684,7 @@ ERL_NIF_TERM nsendto(ErlNifEnv* env, SocketAddress* toAddrP, unsigned int toAddrLen) { + int save_errno; ssize_t written; if (!descP->isWritable) @@ -2674,8 +2704,12 @@ ERL_NIF_TERM nsendto(ErlNifEnv* env, dataP->data, dataP->size, flags, NULL, 0); } + if (IS_SOCKET_ERROR(written)) + save_errno = sock_errno(); + else + save_errno = -1; // The value does not actually matter in this case - return send_check_result(env, descP, written, dataP->size, sendRef); + return send_check_result(env, descP, written, dataP->size, save_errno, sendRef); } @@ -2829,6 +2863,13 @@ ERL_NIF_TERM nrecv(ErlNifEnv* env, { ssize_t read; ErlNifBinary buf; + int save_errno; + int bufSz = (len ? len : descP->rBufSz); + + SSDBG( descP, ("SOCKET", "nrecv -> entry with" + "\r\n len: %d (%d)" + "\r\n flags: %d" + "\r\n", len, bufSz, flags) ); if (!descP->isReadable) return enif_make_badarg(env); @@ -2837,7 +2878,7 @@ ERL_NIF_TERM nrecv(ErlNifEnv* env, * Either as much as we want to read or (if zero (0)) use the "default" * size (what has been configured). */ - if (!ALLOC_BIN((len ? len : descP->rBufSz), &buf)) + if (!ALLOC_BIN(bufSz, &buf)) return esock_make_error(env, atom_exalloc); /* We ignore the wrap for the moment. @@ -2845,10 +2886,19 @@ ERL_NIF_TERM nrecv(ErlNifEnv* env, */ cnt_inc(&descP->readTries, 1); + // If it fails (read = -1), we need errno... + SSDBG( descP, ("SOCKET", "nrecv -> try read (%d)\r\n", buf.size) ); read = sock_recv(descP->sock, buf.data, buf.size, flags); + if (IS_SOCKET_ERROR(read)) + save_errno = sock_errno(); + else + save_errno = -1; // The value does not actually matter in this case + + SSDBG( descP, ("SOCKET", "nrecv -> read: %d (%d)\r\n", read, save_errno) ); return recv_check_result(env, descP, read, len, + save_errno, &buf, recvRef); } @@ -3003,6 +3053,11 @@ ERL_NIF_TERM nclose(ErlNifEnv* env, ERL_NIF_TERM reply, reason; BOOLEAN_T doClose; int selectRes; + int domain = descP->domain; + int type = descP->type; + int protocol = descP->protocol; + + SSDBG( descP, ("SOCKET", "nclose -> [%d] entry\r\n", descP->sock) ); MLOCK(descP->closeMtx); @@ -3049,10 +3104,16 @@ ERL_NIF_TERM nclose(ErlNifEnv* env, descP, NULL, descP->closeRef); if (selectRes & ERL_NIF_SELECT_STOP_CALLED) { /* Prep done - inform the caller it can finalize (close) directly */ + SSDBG( descP, + ("SOCKET", "nclose -> [%d] stop called\r\n", descP->sock) ); + dec_socket(domain, type, protocol); reply = esock_atom_ok; } else if (selectRes & ERL_NIF_SELECT_STOP_SCHEDULED) { /* The stop callback function has been *scheduled* which means that we * have to wait for it to complete. */ + SSDBG( descP, + ("SOCKET", "nclose -> [%d] stop scheduled\r\n", descP->sock) ); + dec_socket(domain, type, protocol); // SHALL WE DO THIS AT finalize? reply = esock_make_ok2(env, descP->closeRef); } else { /* @@ -3071,6 +3132,11 @@ ERL_NIF_TERM nclose(ErlNifEnv* env, reply = esock_make_error(env, reason); } + SSDBG( descP, + ("SOCKET", "nclose -> [%d] done when: " + "\r\n reply: %T" + "\r\n", descP->sock, reply) ); + return reply; } @@ -3081,7 +3147,7 @@ ERL_NIF_TERM nclose(ErlNifEnv* env, * * Description: * Perform the actual socket close! - * Note that this function is executed in a dirfty scheduler. + * Note that this function is executed in a dirty scheduler. * * Arguments: * Socket (ref) - Points to the socket descriptor. @@ -3249,6 +3315,10 @@ ERL_NIF_TERM nif_setopt(ErlNifEnv* env, ERL_NIF_TERM eVal; BOOLEAN_T isEncoded, isOTP; + SGDBG( ("SOCKET", "nif_setopt -> entry with argc: %d\r\n", argc) ); + + /* Extract arguments and perform preliminary validation */ + if ((argc != 5) || !enif_get_resource(env, argv[0], sockets, (void**) &descP) || !GET_INT(env, argv[2], &eLevel) || @@ -3263,6 +3333,19 @@ ERL_NIF_TERM nif_setopt(ErlNifEnv* env, if (!elevel2level(isEncoded, eLevel, &isOTP, &level)) return esock_make_error(env, esock_atom_einval); + SSDBG( descP, + ("SOCKET", "nif_setopt -> args when sock = %d:" + "\r\n Socket: %T" + "\r\n Encoded: %T (%d)" + "\r\n Level: %d (%d)" + "\r\n Opt: %d" + "\r\n Value: %T" + "\r\n", + descP->sock, argv[0], + eIsEncoded, isEncoded, + eLevel, level, + eOpt, eVal) ); + return nsetopt(env, descP, isEncoded, isOTP, level, eOpt, eVal); } @@ -3304,6 +3387,12 @@ ERL_NIF_TERM nsetopt_otp(ErlNifEnv* env, { ERL_NIF_TERM result; + SSDBG( descP, + ("SOCKET", "nsetopt_otp -> entry with" + "\r\n eOpt: %d" + "\r\n eVal: %T" + "\r\n", eOpt, eVal) ); + switch (eOpt) { case SOCKET_OPT_OTP_DEBUG: result = nsetopt_otp_debug(env, descP, eVal); @@ -3313,6 +3402,10 @@ ERL_NIF_TERM nsetopt_otp(ErlNifEnv* env, result = nsetopt_otp_iow(env, descP, eVal); break; + case SOCKET_OPT_OTP_CTRL_PROC: + result = nsetopt_otp_ctrl_proc(env, descP, eVal); + break; + default: result = esock_make_error(env, esock_atom_einval); break; @@ -3349,6 +3442,48 @@ ERL_NIF_TERM nsetopt_otp_iow(ErlNifEnv* env, +/* nsetopt_otp_ctrl_proc - Handle the OTP (level) controlling_process options + */ +static +ERL_NIF_TERM nsetopt_otp_ctrl_proc(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) +{ + ErlNifPid newCtrlPid; + ErlNifMonitor newCtrlMon; + int xres; + + SSDBG( descP, + ("SOCKET", "nsetopt_otp_ctrl_proc -> entry with" + "\r\n eVal: %T" + "\r\n", eVal) ); + + if (!GET_LPID(env, eVal, &newCtrlPid)) { + esock_warning_msg("Failed get pid of new controlling process\r\n"); + return esock_make_error(env, esock_atom_einval); + } + + if ((xres = MONP(env, descP, &newCtrlPid, &newCtrlMon)) != 0) { + esock_warning_msg("Failed monitor %d) (new) controlling process\r\n", xres); + return esock_make_error(env, esock_atom_einval); + } + + if ((xres = DEMONP(env, descP, &descP->ctrlMon)) != 0) { + esock_warning_msg("Failed demonitor (%d) " + "old controlling process %T (%T)\r\n", + xres, descP->ctrlPid, descP->ctrlMon); + } + + descP->ctrlPid = newCtrlPid; + descP->ctrlMon = newCtrlMon; + + SSDBG( descP, ("SOCKET", "nsetopt_otp_ctrl_proc -> done\r\n") ); + + return esock_atom_ok; +} + + + /* The option has *not* been encoded. Instead it has been provided * in "native mode" (option is provided as is and value as a binary). */ @@ -3362,6 +3497,12 @@ ERL_NIF_TERM nsetopt_native(ErlNifEnv* env, ErlNifBinary val; ERL_NIF_TERM result; + SSDBG( descP, + ("SOCKET", "nsetopt_native -> entry with" + "\r\n opt: %d" + "\r\n eVal: %T" + "\r\n", opt, eVal) ); + if (GET_BIN(env, eVal, &val)) { int res = socket_setopt(descP->sock, level, opt, val.data, val.size); @@ -3389,6 +3530,11 @@ ERL_NIF_TERM nsetopt_level(ErlNifEnv* env, { ERL_NIF_TERM result; + SSDBG( descP, + ("SOCKET", "nsetopt_level -> entry with" + "\r\n level: %d" + "\r\n", level) ); + switch (level) { case SOL_SOCKET: result = nsetopt_lvl_socket(env, descP, eOpt, eVal); @@ -5081,30 +5227,44 @@ ERL_NIF_TERM send_check_result(ErlNifEnv* env, SocketDescriptor* descP, ssize_t written, ssize_t dataSize, + int saveErrno, ERL_NIF_TERM sendRef) { + SSDBG( descP, + ("SOCKET", "send_check_result -> entry with" + "\r\n written: %d" + "\r\n dataSize: %d" + "\r\n saveErrno: %d" + "\r\n", written, dataSize, saveErrno) ); + if (written == dataSize) { cnt_inc(&descP->writePkgCnt, 1); cnt_inc(&descP->writeByteCnt, written); + SSDBG( descP, + ("SOCKET", "send_check_result -> everything written - done\r\n") ); + return esock_atom_ok; } else if (written < 0) { /* Ouch, check what kind of failure */ - int save_errno = sock_errno(); - if ((save_errno != EAGAIN) && - (save_errno != EINTR)) { + if ((saveErrno != EAGAIN) && (saveErrno != EINTR)) { cnt_inc(&descP->writeFails, 1); - return esock_make_error_errno(env, save_errno); + SSDBG( descP, + ("SOCKET", "send_check_result -> error: %d\r\n", saveErrno) ); + + return esock_make_error_errno(env, saveErrno); } else { /* Ok, try again later */ + SSDBG( descP, ("SOCKET", "send_check_result -> try again\r\n") ); + /* * SHOULD RESULT IN {error, eagain}!!!! * @@ -5124,6 +5284,9 @@ ERL_NIF_TERM send_check_result(ErlNifEnv* env, SELECT(env, descP->sock, (ERL_NIF_SELECT_WRITE), descP, NULL, sendRef); + SSDBG( descP, + ("SOCKET", "send_check_result -> not entire package written\r\n") ); + return esock_make_ok2(env, enif_make_int(env, written)); } @@ -5134,11 +5297,42 @@ ERL_NIF_TERM recv_check_result(ErlNifEnv* env, SocketDescriptor* descP, int read, int toRead, + int saveErrno, ErlNifBinary* bufP, ERL_NIF_TERM recvRef) { ERL_NIF_TERM data; + SSDBG( descP, + ("SOCKET", "recv_check_result -> entry with" + "\r\n read: %d" + "\r\n toRead: %d" + "\r\n saveErrno: %d" + "\r\n recvRef: %T" + "\r\n", read, toRead, saveErrno, recvRef) ); + + + /* + * + * We need to handle read = 0 for other type(s) (DGRAM) when + * its actually valid to read 0 bytes. + * + * + */ + + if ((read == 0) && (descP->type == SOCK_STREAM)) { + + /* + * When a stream socket peer has performed an orderly shutdown, the return + * value will be 0 (the traditional "end-of-file" return). + * + * *We* do never actually try to read 0 bytes from a stream socket! + */ + + return esock_make_error(env, atom_closed); + + } + /* There is a special case: If the provided 'to read' value is * zero (0). That means that we reads as much as we can, using * the default read buffer size. @@ -5148,6 +5342,10 @@ ERL_NIF_TERM recv_check_result(ErlNifEnv* env, /* +++ We filled the buffer +++ */ + SSDBG( descP, + ("SOCKET", + "recv_check_result -> [%d] filled the buffer\r\n", toRead) ); + if (toRead == 0) { /* +++ Give us everything you have got => needs to continue +++ */ @@ -5168,6 +5366,11 @@ ERL_NIF_TERM recv_check_result(ErlNifEnv* env, data = MKBIN(env, bufP); + SSDBG( descP, + ("SOCKET", + "recv_check_result -> [%d] " + "we are done for now - read more\r\n", toRead) ); + return esock_make_ok3(env, atom_false, data); } else { @@ -5181,6 +5384,11 @@ ERL_NIF_TERM recv_check_result(ErlNifEnv* env, data = MKBIN(env, bufP); + SSDBG( descP, + ("SOCKET", + "recv_check_result -> [%d] " + "we got exactly what we could fit\r\n", toRead) ); + return esock_make_ok3(env, atom_true, data); } @@ -5189,12 +5397,13 @@ ERL_NIF_TERM recv_check_result(ErlNifEnv* env, /* +++ Error handling +++ */ - int save_errno = sock_errno(); - - if (save_errno == ECONNRESET) { + if (saveErrno == ECONNRESET) { /* +++ Oups - closed +++ */ + SSDBG( descP, ("SOCKET", + "recv_check_result -> [%d] closed\r\n", toRead) ); + /* * * IF THE CURRENT PROCESS IS *NOT* THE CONTROLLING @@ -5220,17 +5429,31 @@ ERL_NIF_TERM recv_check_result(ErlNifEnv* env, return esock_make_error(env, atom_closed); - } else if ((save_errno == ERRNO_BLOCK) || - (save_errno == EAGAIN)) { + } else if ((saveErrno == ERRNO_BLOCK) || + (saveErrno == EAGAIN)) { + SSDBG( descP, ("SOCKET", + "recv_check_result -> [%d] eagain\r\n", toRead) ); + + SELECT(env, descP->sock, (ERL_NIF_SELECT_READ), + descP, NULL, recvRef); + return esock_make_error(env, esock_atom_eagain); } else { - return esock_make_error_errno(env, save_errno); + SSDBG( descP, ("SOCKET", "recv_check_result -> [%d] errno: %d\r\n", + toRead, saveErrno) ); + return esock_make_error_errno(env, saveErrno); } } else { /* +++ We did not fill the buffer +++ */ + SSDBG( descP, + ("SOCKET", + "recv_check_result -> [%d] " + "did not fill the buffer (%d of %d)\r\n", + toRead, read, bufP->size) ); + if (toRead == 0) { /* +++ We got a chunk of data but +++ @@ -5239,9 +5462,14 @@ ERL_NIF_TERM recv_check_result(ErlNifEnv* env, * +++ into a sub-binary. +++ */ + SSDBG( descP, ("SOCKET", + "recv_check_result -> [%d] split buffer\r\n", toRead) ); + data = MKBIN(env, bufP); data = MKSBIN(env, data, 0, read); + SSDBG( descP, ("SOCKET", "recv_check_result -> [%d] done\r\n", toRead) ); + return esock_make_ok3(env, atom_true, data); } else { @@ -5249,6 +5477,9 @@ ERL_NIF_TERM recv_check_result(ErlNifEnv* env, /* +++ We got only a part of what was expected +++ * +++ => receive more later. +++ */ + SSDBG( descP, ("SOCKET", "recv_check_result -> [%d] " + "only part of message - expect more\r\n", toRead) ); + return esock_make_ok3(env, atom_false, MKBIN(env, bufP)); } } @@ -6343,6 +6574,90 @@ SocketDescriptor* alloc_descriptor(SOCKET sock, HANDLE event) } + +/* decrement counters for when a socket is closed */ +static +void dec_socket(int domain, int type, int protocol) +{ + MLOCK(data.cntMtx); + + cnt_dec(&data.numSockets, 1); + + if (domain == AF_INET) + cnt_dec(&data.numDomainInet, 1); +#if defined(HAVE_IN6) && defined(AF_INET6) + else if (domain == AF_INET6) + cnt_dec(&data.numDomainInet6, 1); +#endif +#if defined(HAVE_SYS_UN_H) + else if (domain == AF_UNIX) + cnt_dec(&data.numDomainInet6, 1); +#endif + + if (type == SOCK_STREAM) + cnt_dec(&data.numTypeStreams, 1); + else if (type == SOCK_DGRAM) + cnt_dec(&data.numTypeDGrams, 1); + else if (type == SOCK_SEQPACKET) + cnt_dec(&data.numTypeSeqPkgs, 1); + + if (protocol == IPPROTO_IP) + cnt_dec(&data.numProtoIP, 1); + else if (protocol == IPPROTO_TCP) + cnt_dec(&data.numProtoTCP, 1); + else if (protocol == IPPROTO_UDP) + cnt_dec(&data.numProtoUDP, 1); +#if defined(HAVE_SCTP) + else if (protocol == IPPROTO_SCTP) + cnt_dec(&data.numProtoSCTP, 1); +#endif + + MUNLOCK(data.cntMtx); +} + + +/* increment counters for when a socket is opened */ +static +void inc_socket(int domain, int type, int protocol) +{ + MLOCK(data.cntMtx); + + cnt_inc(&data.numSockets, 1); + + if (domain == AF_INET) + cnt_inc(&data.numDomainInet, 1); +#if defined(HAVE_IN6) && defined(AF_INET6) + else if (domain == AF_INET6) + cnt_inc(&data.numDomainInet6, 1); +#endif +#if defined(HAVE_SYS_UN_H) + else if (domain == AF_UNIX) + cnt_inc(&data.numDomainInet6, 1); +#endif + + if (type == SOCK_STREAM) + cnt_inc(&data.numTypeStreams, 1); + else if (type == SOCK_DGRAM) + cnt_inc(&data.numTypeDGrams, 1); + else if (type == SOCK_SEQPACKET) + cnt_inc(&data.numTypeSeqPkgs, 1); + + if (protocol == IPPROTO_IP) + cnt_inc(&data.numProtoIP, 1); + else if (protocol == IPPROTO_TCP) + cnt_inc(&data.numProtoTCP, 1); + else if (protocol == IPPROTO_UDP) + cnt_inc(&data.numProtoUDP, 1); +#if defined(HAVE_SCTP) + else if (protocol == IPPROTO_SCTP) + cnt_inc(&data.numProtoSCTP, 1); +#endif + + MUNLOCK(data.cntMtx); +} + + + /* compare_pids - Test if two pids are equal * */ @@ -6446,9 +6761,11 @@ BOOLEAN_T eproto2proto(int eproto, int* proto) *proto = IPPROTO_UDP; break; +#if defined(HAVE_SCTP) case SOCKET_PROTOCOL_SCTP: *proto = IPPROTO_SCTP; break; +#endif default: return FALSE; @@ -6519,35 +6836,42 @@ BOOLEAN_T emap2netns(ErlNifEnv* env, ERL_NIF_TERM map, char** netns) * send flags. */ static -BOOLEAN_T esendflags2sendflags(unsigned int esendflags, int* sendflags) +BOOLEAN_T esendflags2sendflags(unsigned int eflags, int* flags) { unsigned int ef; int tmp = 0; for (ef = SOCKET_SEND_FLAG_LOW; ef <= SOCKET_SEND_FLAG_HIGH; ef++) { + switch (ef) { case SOCKET_SEND_FLAG_CONFIRM: - tmp |= MSG_CONFIRM; + if ((1 << SOCKET_SEND_FLAG_CONFIRM) & eflags) + tmp |= MSG_CONFIRM; break; case SOCKET_SEND_FLAG_DONTROUTE: - tmp |= MSG_DONTROUTE; + if ((1 << SOCKET_SEND_FLAG_DONTROUTE) & eflags) + tmp |= MSG_DONTROUTE; break; case SOCKET_SEND_FLAG_EOR: - tmp |= MSG_EOR; + if ((1 << SOCKET_SEND_FLAG_EOR) & eflags) + tmp |= MSG_EOR; break; case SOCKET_SEND_FLAG_MORE: - tmp |= MSG_MORE; + if ((1 << SOCKET_SEND_FLAG_MORE) & eflags) + tmp |= MSG_MORE; break; case SOCKET_SEND_FLAG_NOSIGNAL: - tmp |= MSG_NOSIGNAL; + if ((1 << SOCKET_SEND_FLAG_NOSIGNAL) & eflags) + tmp |= MSG_NOSIGNAL; break; case SOCKET_SEND_FLAG_OOB: - tmp |= MSG_OOB; + if ((1 << SOCKET_SEND_FLAG_OOB) & eflags) + tmp |= MSG_OOB; break; default: @@ -6556,7 +6880,7 @@ BOOLEAN_T esendflags2sendflags(unsigned int esendflags, int* sendflags) } - *sendflags = tmp; + *flags = tmp; return TRUE; } @@ -6567,31 +6891,53 @@ BOOLEAN_T esendflags2sendflags(unsigned int esendflags, int* sendflags) * send flags. */ static -BOOLEAN_T erecvflags2recvflags(unsigned int erecvflags, int* recvflags) +BOOLEAN_T erecvflags2recvflags(unsigned int eflags, int* flags) { unsigned int ef; int tmp = 0; + SGDBG( ("SOCKET", "erecvflags2recvflags -> entry with" + "\r\n eflags: %d" + "\r\n", eflags) ); + for (ef = SOCKET_RECV_FLAG_LOW; ef <= SOCKET_RECV_FLAG_HIGH; ef++) { + + SGDBG( ("SOCKET", "erecvflags2recvflags -> iteration" + "\r\n ef: %d" + "\r\n tmp: %d" + "\r\n", ef, tmp) ); + switch (ef) { case SOCKET_RECV_FLAG_CMSG_CLOEXEC: - tmp |= MSG_CMSG_CLOEXEC; + if ((1 << SOCKET_RECV_FLAG_CMSG_CLOEXEC) & eflags) + tmp |= MSG_CMSG_CLOEXEC; break; case SOCKET_RECV_FLAG_ERRQUEUE: - tmp |= MSG_ERRQUEUE; + if ((1 << SOCKET_RECV_FLAG_ERRQUEUE) & eflags) + tmp |= MSG_ERRQUEUE; break; case SOCKET_RECV_FLAG_OOB: - tmp |= MSG_OOB; + if ((1 << SOCKET_RECV_FLAG_OOB) & eflags) + tmp |= MSG_OOB; break; + /* + * + * + * We need to handle this, because it may effect the read algorithm + * + * + */ case SOCKET_RECV_FLAG_PEEK: - tmp |= MSG_PEEK; + if ((1 << SOCKET_RECV_FLAG_PEEK) & eflags) + tmp |= MSG_PEEK; break; case SOCKET_RECV_FLAG_TRUNC: - tmp |= MSG_TRUNC; + if ((1 << SOCKET_RECV_FLAG_TRUNC) & eflags) + tmp |= MSG_TRUNC; break; default: @@ -6600,7 +6946,7 @@ BOOLEAN_T erecvflags2recvflags(unsigned int erecvflags, int* recvflags) } - *recvflags = tmp; + *flags = tmp; return TRUE; } @@ -7117,19 +7463,33 @@ char* send_msg(ErlNifEnv* env, static BOOLEAN_T cnt_inc(uint32_t* cnt, uint32_t inc) { - BOOLEAN_T wrap; - uint32_t max = 0xFFFFFFFF; - uint32_t current = *cnt; + BOOLEAN_T wrap; + uint32_t max = 0xFFFFFFFF; + uint32_t current = *cnt; - if ((max - inc) >= current) { - *cnt += inc; - wrap = FALSE; - } else { - *cnt = inc - (max - current) - 1; - wrap = TRUE; - } + if ((max - inc) >= current) { + *cnt += inc; + wrap = FALSE; + } else { + *cnt = inc - (max - current) - 1; + wrap = TRUE; + } + + return (wrap); +} + + +static +void cnt_dec(uint32_t* cnt, uint32_t dec) +{ + uint32_t current = *cnt; + + if (dec > current) + *cnt = 0; // The counter cannot be < 0 so this is the best we can do... + else + *cnt -= dec; - return (wrap); + return; } @@ -7183,116 +7543,125 @@ void socket_dtor(ErlNifEnv* env, void* obj) static void socket_stop(ErlNifEnv* env, void* obj, int fd, int is_direct_call) { - SocketDescriptor* descP = (SocketDescriptor*) obj; - - MLOCK(descP->writeMtx); - MLOCK(descP->readMtx); - MLOCK(descP->accMtx); - MLOCK(descP->closeMtx); - - - descP->state = SOCKET_STATE_CLOSING; // Just in case...??? - descP->isReadable = FALSE; - descP->isWritable = FALSE; - - - /* We should check that we actually have a monitor. - * This *should* be done with a "NULL" monitor value, - * which there currently is none... - */ - DEMONP(env, descP, &descP->ctrlMon); - - if (descP->currentWriterP != NULL) { - /* We have a (current) writer and *may* therefor also have - * writers waiting. - */ - - ESOCK_ASSERT( (NULL == send_msg_nif_abort(env, - descP->currentWriter.ref, - atom_closed, - &descP->currentWriter.pid)) ); - - /* And also deal with the waiting writers (in the same way) */ - inform_waiting_procs(env, descP, &descP->writersQ, TRUE, atom_closed); - } - - if (descP->currentReaderP != NULL) { - - /* We have a (current) reader and *may* therefor also have - * readers waiting. - */ - - ESOCK_ASSERT( (NULL == send_msg_nif_abort(env, - descP->currentReader.ref, - atom_closed, - &descP->currentReader.pid)) ); - - /* And also deal with the waiting readers (in the same way) */ - inform_waiting_procs(env, descP, &descP->readersQ, TRUE, atom_closed); - } - - if (descP->currentAcceptorP != NULL) { - /* We have a (current) acceptor and *may* therefor also have - * acceptors waiting. - */ - - ESOCK_ASSERT( (NULL == send_msg_nif_abort(env, - descP->currentAcceptor.ref, - atom_closed, - &descP->currentAcceptor.pid)) ); - - /* And also deal with the waiting acceptors (in the same way) */ - inform_waiting_procs(env, descP, &descP->acceptorsQ, TRUE, atom_closed); - } - - - if (descP->sock != INVALID_SOCKET) { - - /* - * - * - * WE NEED TO CHECK IF THIS OPERATION IS TRIGGERED - * LOCALLY (VIA A CALL TO CLOSE) OR REMOTELLY - * (VIA I.E. ECONSRESET). - * - * - */ - - if (descP->closeLocal) { - - /* +++ send close message to the waiting process +++ - * - * {close, CloseRef} - * - * - * - * WHAT HAPPENS IF THE RECEIVER HAS DIED IN THE MEANTIME???? - * - * - */ - - send_msg(env, MKT2(env, atom_close, descP->closeRef), &descP->closerPid); - - DEMONP(env, descP, &descP->closerMon); - - } else { - - /* - * - * - * ABORT? - * - * - */ - } - } - - - MUNLOCK(descP->closeMtx); - MUNLOCK(descP->accMtx); - MUNLOCK(descP->readMtx); - MUNLOCK(descP->writeMtx); + SocketDescriptor* descP = (SocketDescriptor*) obj; + + SSDBG( descP, + ("SOCKET", "socket_stop -> entry when" + "\r\n sock: %d (%d)" + "\r\n is_direct_call: %d" + "\r\n", descP->sock, fd, is_direct_call) ); + + MLOCK(descP->writeMtx); + MLOCK(descP->readMtx); + MLOCK(descP->accMtx); + MLOCK(descP->closeMtx); + + + descP->state = SOCKET_STATE_CLOSING; // Just in case...??? + descP->isReadable = FALSE; + descP->isWritable = FALSE; + + + /* We should check that we actually have a monitor. + * This *should* be done with a "NULL" monitor value, + * which there currently is none... + */ + DEMONP(env, descP, &descP->ctrlMon); + + if (descP->currentWriterP != NULL) { + /* We have a (current) writer and *may* therefor also have + * writers waiting. + */ + + ESOCK_ASSERT( (NULL == send_msg_nif_abort(env, + descP->currentWriter.ref, + atom_closed, + &descP->currentWriter.pid)) ); + + /* And also deal with the waiting writers (in the same way) */ + inform_waiting_procs(env, descP, &descP->writersQ, TRUE, atom_closed); + } + + if (descP->currentReaderP != NULL) { + + /* We have a (current) reader and *may* therefor also have + * readers waiting. + */ + + ESOCK_ASSERT( (NULL == send_msg_nif_abort(env, + descP->currentReader.ref, + atom_closed, + &descP->currentReader.pid)) ); + + /* And also deal with the waiting readers (in the same way) */ + inform_waiting_procs(env, descP, &descP->readersQ, TRUE, atom_closed); + } + + if (descP->currentAcceptorP != NULL) { + /* We have a (current) acceptor and *may* therefor also have + * acceptors waiting. + */ + + ESOCK_ASSERT( (NULL == send_msg_nif_abort(env, + descP->currentAcceptor.ref, + atom_closed, + &descP->currentAcceptor.pid)) ); + + /* And also deal with the waiting acceptors (in the same way) */ + inform_waiting_procs(env, descP, &descP->acceptorsQ, TRUE, atom_closed); + } + + + if (descP->sock != INVALID_SOCKET) { + + /* + * + * + * WE NEED TO CHECK IF THIS OPERATION IS TRIGGERED + * LOCALLY (VIA A CALL TO CLOSE) OR REMOTELLY + * (VIA I.E. ECONSRESET). + * + * + */ + + if (descP->closeLocal) { + + /* +++ send close message to the waiting process +++ + * + * {close, CloseRef} + * + * + * + * WHAT HAPPENS IF THE RECEIVER HAS DIED IN THE MEANTIME???? + * + * + */ + + send_msg(env, MKT2(env, atom_close, descP->closeRef), &descP->closerPid); + + DEMONP(env, descP, &descP->closerMon); + + } else { + + /* + * + * + * ABORT? + * + * + */ + } + } + + + MUNLOCK(descP->closeMtx); + MUNLOCK(descP->accMtx); + MUNLOCK(descP->readMtx); + MUNLOCK(descP->writeMtx); + SSDBG( descP, + ("SOCKET", "socket_stop -> done (%d, %d)\r\n", descP->sock, fd) ); + } diff --git a/erts/emulator/nifs/common/socket_util.c b/erts/emulator/nifs/common/socket_util.c index 05fb40e286..397f69f58d 100644 --- a/erts/emulator/nifs/common/socket_util.c +++ b/erts/emulator/nifs/common/socket_util.c @@ -29,6 +29,11 @@ #include "socket_dbg.h" #include "sys.h" +#include +#include +#include +#include +#include /* We don't have a "debug flag" to check here, so we * should use the compile debug flag, whatever that is... @@ -46,6 +51,9 @@ extern char* erl_errno_id(int error); /* THIS IS JUST TEMPORARY??? */ +static int realtime(struct timespec* tsP); +static int timespec2str(char *buf, unsigned int len, struct timespec *ts); + static char* make_sockaddr_in4(ErlNifEnv* env, ERL_NIF_TERM port, ERL_NIF_TERM addr, @@ -1168,10 +1176,89 @@ void esock_abort(const char* expr, +/* *** esock_warning_msg *** + * + * Temporary function for issuing warning messages. + * + */ +extern +void esock_warning_msg( const char* format, ... ) +{ + va_list args; + char f[512 + sizeof(format)]; // This has to suffice... + char stamp[32]; + struct timespec ts; + int res; + + /* + * We should really include self in the printout, so we can se which process + * are executing the code. But then I must change the API.... + * ....something for later. + */ + + // 2018-06-29 12:13:21.232089 + // 29-Jun-2018::13:47:25.097097 + + if (!realtime(&ts)) { + if (timespec2str(stamp, sizeof(stamp), &ts) != 0) { + res = enif_snprintf(f, sizeof(f), "=WARNING MSG==== %s", format); + } else { + res = enif_snprintf(f, sizeof(f), + "=WARNING MSG==== %s ===\r\n%s" , stamp, format); + } + + if (res > 0) { + va_start (args, format); + enif_vfprintf (stdout, f, args); + va_end (args); + fflush(stdout); + } + } + + return; +} + + +static +int realtime(struct timespec* tsP) +{ + return clock_gettime(CLOCK_REALTIME, tsP); +} + + +/* + * Convert a timespec struct into a readable/printable string. + * + * "%F::%T" => 2018-06-29 12:13:21[.232089] + * "%d-%b-%Y::%T" => 29-Jun-2018::13:47:25.097097 + */ +static +int timespec2str(char *buf, unsigned int len, struct timespec *ts) +{ + int ret, buflen; + struct tm t; + + tzset(); + if (localtime_r(&(ts->tv_sec), &t) == NULL) + return 1; + + ret = strftime(buf, len, "%d-%B-%Y::%T", &t); + if (ret == 0) + return 2; + len -= ret - 1; + buflen = strlen(buf); + + ret = snprintf(&buf[buflen], len, ".%06ld", ts->tv_nsec/1000); + if (ret >= len) + return 3; + + return 0; +} + /* =================================================================== * * * - * Various utility functions * + * Various (internal) utility functions * * * * =================================================================== */ diff --git a/erts/emulator/nifs/common/socket_util.h b/erts/emulator/nifs/common/socket_util.h index dedeb8dd7d..add2c8f4be 100644 --- a/erts/emulator/nifs/common/socket_util.h +++ b/erts/emulator/nifs/common/socket_util.h @@ -158,5 +158,8 @@ ERL_NIF_TERM esock_make_error_str(ErlNifEnv* env, char* reason); extern ERL_NIF_TERM esock_make_error_errno(ErlNifEnv* env, int err); +extern +void esock_warning_msg(const char* format, ... ); + #endif // SOCKET_UTIL_H__ diff --git a/erts/preloaded/ebin/socket.beam b/erts/preloaded/ebin/socket.beam index f82db6e44e..4924c43a5c 100644 Binary files a/erts/preloaded/ebin/socket.beam and b/erts/preloaded/ebin/socket.beam differ diff --git a/erts/preloaded/src/socket.erl b/erts/preloaded/src/socket.erl index ba3ff6bab9..bf94271073 100644 --- a/erts/preloaded/src/socket.erl +++ b/erts/preloaded/src/socket.erl @@ -41,7 +41,7 @@ %% sendmsg/4, %% writev/4, OR SENDV? It will be strange for recv then: recvv (instead of readv) - recv/2, recv/3, recv/4, + recv/1, recv/2, recv/3, recv/4, recvfrom/1, recvfrom/2, recvfrom/3, recvfrom/4, %% recvmsg/4, %% readv/3, @@ -442,16 +442,17 @@ -define(SOCKET_RECV_FLAGS_DEFAULT, []). -define(SOCKET_RECV_TIMEOUT_DEFAULT, infinity). --define(SOCKET_OPT_LEVEL_OTP, 0). --define(SOCKET_OPT_LEVEL_SOCKET, 1). --define(SOCKET_OPT_LEVEL_IP, 2). --define(SOCKET_OPT_LEVEL_IPV6, 3). --define(SOCKET_OPT_LEVEL_TCP, 4). --define(SOCKET_OPT_LEVEL_UDP, 5). --define(SOCKET_OPT_LEVEL_SCTP, 6). +-define(SOCKET_OPT_LEVEL_OTP, 0). +-define(SOCKET_OPT_LEVEL_SOCKET, 1). +-define(SOCKET_OPT_LEVEL_IP, 2). +-define(SOCKET_OPT_LEVEL_IPV6, 3). +-define(SOCKET_OPT_LEVEL_TCP, 4). +-define(SOCKET_OPT_LEVEL_UDP, 5). +-define(SOCKET_OPT_LEVEL_SCTP, 6). --define(SOCKET_OPT_OTP_DEBUG, 0). --define(SOCKET_OPT_OTP_IOW, 1). +-define(SOCKET_OPT_OTP_DEBUG, 0). +-define(SOCKET_OPT_OTP_IOW, 1). +-define(SOCKET_OPT_OTP_CTRL_PROC, 2). -define(SOCKET_OPT_SOCK_BROADCAST, 4). -define(SOCKET_OPT_SOCK_DONTROUTE, 7). @@ -1097,6 +1098,9 @@ do_sendto(SockRef, Data, EFlags, Dest, Timeout) -> %% Flags - A list of "options" for the read. %% Timeout - Time-out in milliseconds. +recv(Socket) -> + recv(Socket, 0). + recv(Socket, Length) -> recv(Socket, Length, ?SOCKET_RECV_FLAGS_DEFAULT, @@ -1131,10 +1135,21 @@ do_recv(SockRef, _OldRef, Length, EFlags, Acc, Timeout) (is_integer(Timeout) andalso (Timeout > 0)) -> TS = timestamp(Timeout), RecvRef = make_ref(), + p("do_recv -> try read with" + "~n SockRef: ~p" + "~n RecvRef: ~p" + "~n Length: ~p" + "~n EFlags: ~p" + "~nwhen" + "~n Timeout: ~p (~p)", [SockRef, RecvRef, Length, EFlags, Timeout, TS]), case nif_recv(SockRef, RecvRef, Length, EFlags) of {ok, true = _Complete, Bin} when (size(Acc) =:= 0) -> + p("do_recv -> ok: complete (size(Acc) =:= 0)" + "~n size(Bin): ~p", [size(Bin)]), {ok, Bin}; {ok, true = _Complete, Bin} -> + p("do_recv -> ok: complete" + "~n size(Bin): ~p", [size(Bin)]), {ok, <>}; %% It depends on the amount of bytes we tried to read: @@ -1143,12 +1158,16 @@ do_recv(SockRef, _OldRef, Length, EFlags, Acc, Timeout) %% > 0 - We got a part of the message and we will be notified %% when there is more to read (a select message) {ok, false = _Complete, Bin} when (Length =:= 0) -> + p("do_recv -> ok: not-complete (Length =:= 0)" + "~n size(Bin): ~p", [size(Bin)]), do_recv(SockRef, RecvRef, Length, EFlags, <>, next_timeout(TS, Timeout)); {ok, false = _Completed, Bin} when (size(Acc) =:= 0) -> + p("do_recv -> ok: not-complete (size(Acc) =:= 0)" + "~n size(Bin): ~p", [size(Bin)]), %% We got the first chunk of it. %% We will be notified (select message) when there %% is more to read. @@ -1171,6 +1190,8 @@ do_recv(SockRef, _OldRef, Length, EFlags, Acc, Timeout) end; {ok, false = _Completed, Bin} -> + p("do_recv -> ok: not-complete" + "~n size(Bin): ~p", [size(Bin)]), %% We got a chunk of it! NewTimeout = next_timeout(TS, Timeout), receive @@ -1190,16 +1211,19 @@ do_recv(SockRef, _OldRef, Length, EFlags, Acc, Timeout) {error, {timeout, Acc}} end; - %% We return with the accumulated binary regardless if its empty... - {error, eagain} when (Length =:= 0) -> + %% We return with the accumulated binary (if its non-empty) + {error, eagain} when (Length =:= 0) andalso (size(Acc) > 0) -> + p("do_recv -> eagain (Length =:= 0)", []), {ok, Acc}; {error, eagain} -> + p("do_recv -> eagain", []), %% There is nothing just now, but we will be notified when there %% is something to read (a select message). NewTimeout = next_timeout(TS, Timeout), receive {select, SockRef, RecvRef, ready_input} -> + p("do_recv -> received select ready-input message", []), do_recv(SockRef, RecvRef, Length, EFlags, Acc, @@ -1214,6 +1238,15 @@ do_recv(SockRef, _OldRef, Length, EFlags, Acc, Timeout) {error, timeout} end; + {error, closed = Reason} -> + do_close(SockRef), + if + (size(Acc) =:= 0) -> + {error, Reason}; + true -> + {error, {Reason, Acc}} + end; + {error, _} = ERROR when (size(Acc) =:= 0) -> ERROR; @@ -1342,6 +1375,9 @@ do_recvfrom(SockRef, BufSz, EFlags, Timeout) -> Reason :: term(). close(#socket{ref = SockRef}) -> + do_close(SockRef). + +do_close(SockRef) -> case nif_close(SockRef) of ok -> nif_finalize_close(SockRef); @@ -1659,6 +1695,8 @@ enc_setopt_value(otp, debug, V, _, _, _) when is_boolean(V) -> V; enc_setopt_value(otp, iow, V, _, _, _) when is_boolean(V) -> V; +enc_setopt_value(otp, controlling_process, V, _, _, _) when is_pid(V) -> + V; enc_setopt_value(otp = L, Opt, V, _D, _T, _P) -> not_supported({L, Opt, V}); @@ -1859,27 +1897,31 @@ enc_sockopt_key(otp, debug, _, _, _, _) -> ?SOCKET_OPT_OTP_DEBUG; enc_sockopt_key(otp, iow, _, _, _, _) -> ?SOCKET_OPT_OTP_IOW; +enc_sockopt_key(otp, controlling_process, _, _, _, _) -> + ?SOCKET_OPT_OTP_CTRL_PROC; +enc_sockopt_key(otp = L, Opt, _, _, _, _) -> + not_supported({L, Opt}); %% +++ SOCKET socket options +++ -enc_sockopt_key(socket, acceptconn = Opt, get = _Dir, _D, _T, _P) -> - not_supported(Opt); +enc_sockopt_key(socket = L, acceptconn = Opt, get = _Dir, _D, _T, _P) -> + not_supported({L, Opt}); enc_sockopt_key(socket, acceptfilter = Opt, _Dir, _D, _T, _P) -> not_supported(Opt); %% Before linux 3.8, this socket option could be set. %% Size of buffer for name: IFNAMSZ %% So, we let the implementation decide. -enc_sockopt_key(socket, bindtodevide = Opt, _Dir, _D, _T, _P) -> - not_supported(Opt); +enc_sockopt_key(socket = L, bindtodevide = Opt, _Dir, _D, _T, _P) -> + not_supported({L, Opt}); enc_sockopt_key(socket, broadcast = _Opt, _Dir, _D, dgram = _T, _P) -> ?SOCKET_OPT_SOCK_BROADCAST; -enc_sockopt_key(socket, busy_poll = Opt, _Dir, _D, _T, _P) -> - not_supported(Opt); -enc_sockopt_key(socket, debug = Opt, _Dir, _D, _T, _P) -> - not_supported(Opt); +enc_sockopt_key(socket = L, busy_poll = Opt, _Dir, _D, _T, _P) -> + not_supported({L, Opt}); +enc_sockopt_key(socket = L, debug = Opt, _Dir, _D, _T, _P) -> + not_supported({L, Opt}); enc_sockopt_key(socket, dontroute = _Opt, _Dir, _D, _T, _P) -> ?SOCKET_OPT_SOCK_DONTROUTE; -enc_sockopt_key(socket, error = Opt, get = _Dir, _D, _T, _P) -> - not_supported(Opt); +enc_sockopt_key(socket = L, error = Opt, get = _Dir, _D, _T, _P) -> + not_supported({L, Opt}); %% This is only for connection-oriented sockets, but who are those? %% Type = stream or Protocol = tcp? %% For now, we just let is pass and it will fail later if not ok... @@ -1887,111 +1929,111 @@ enc_sockopt_key(socket, keepalive = _Opt, _Dir, _D, _T, _P) -> ?SOCKET_OPT_SOCK_KEEPALIVE; enc_sockopt_key(socket, linger = _Opt, _Dir, _D, _T, _P) -> ?SOCKET_OPT_SOCK_LINGER; -enc_sockopt_key(socket, mark = Opt, _Dir, _D, _T, _P) -> - not_supported(Opt); +enc_sockopt_key(socket = L, mark = Opt, _Dir, _D, _T, _P) -> + not_supported({L, Opt}); enc_sockopt_key(socket, oobinline = Opt, _Dir, _D, _T, _P) -> not_supported(Opt); -enc_sockopt_key(socket, passcred = Opt, _Dir, _D, _T, _P) -> - not_supported(Opt); -enc_sockopt_key(socket, peek_off = Opt, _Dir, local = _D, _T, _P) -> - not_supported(Opt); -enc_sockopt_key(socket, peek_cred = Opt, get = _Dir, _D, _T, _P) -> - not_supported(Opt); +enc_sockopt_key(socket = L, passcred = Opt, _Dir, _D, _T, _P) -> + not_supported({L, Opt}); +enc_sockopt_key(socket = L, peek_off = Opt, _Dir, local = _D, _T, _P) -> + not_supported({L, Opt}); +enc_sockopt_key(socket = L, peek_cred = Opt, get = _Dir, _D, _T, _P) -> + not_supported({L, Opt}); enc_sockopt_key(socket, priority = _Opt, _Dir, _D, _T, _P) -> ?SOCKET_OPT_SOCK_PRIORITY; enc_sockopt_key(socket, rcvbuf = _Opt, _Dir, _D, _T, _P) -> ?SOCKET_OPT_SOCK_RCVBUF; -enc_sockopt_key(socket, rcvbufforce = Opt, _Dir, _D, _T, _P) -> - not_supported(Opt); +enc_sockopt_key(socket = L, rcvbufforce = Opt, _Dir, _D, _T, _P) -> + not_supported({L, Opt}); %% May not work on linux. -enc_sockopt_key(socket, rcvlowat = Opt, _Dir, _D, _T, _P) -> - not_supported(Opt); +enc_sockopt_key(socket = L, rcvlowat = Opt, _Dir, _D, _T, _P) -> + not_supported({L, Opt}); enc_sockopt_key(socket, rcvtimeo = Opt, _Dir, _D, _T, _P) -> not_supported(Opt); enc_sockopt_key(socket, reuseaddr = _Opt, _Dir, _D, _T, _P) -> ?SOCKET_OPT_SOCK_REUSEADDR; -enc_sockopt_key(socket, reuseport = Opt, _Dir, D, _T, _P) +enc_sockopt_key(socket = L, reuseport = Opt, _Dir, D, _T, _P) when ((D =:= inet) orelse (D =:= inet6)) -> - not_supported(Opt); -enc_sockopt_key(socket, rxq_ovfl = Opt, _Dir, _D, _T, _P) -> - not_supported(Opt); -enc_sockopt_key(socket, setfib = Opt, set = _Dir, _D, _T, _P) -> - not_supported(Opt); + not_supported({L, Opt}); +enc_sockopt_key(socket = L, rxq_ovfl = Opt, _Dir, _D, _T, _P) -> + not_supported({L, Opt}); +enc_sockopt_key(socket = L, setfib = Opt, set = _Dir, _D, _T, _P) -> + not_supported({L, Opt}); enc_sockopt_key(socket, sndbuf = _Opt, _Dir, _D, _T, _P) -> ?SOCKET_OPT_SOCK_SNDBUF; -enc_sockopt_key(socket, sndbufforce = Opt, _Dir, _D, _T, _P) -> - not_supported(Opt); +enc_sockopt_key(socket = L, sndbufforce = Opt, _Dir, _D, _T, _P) -> + not_supported({L, Opt}); %% Not changeable on linux. -enc_sockopt_key(socket, sndlowat = Opt, _Dir, _D, _T, _P) -> - not_supported(Opt); -enc_sockopt_key(socket, sndtimeo = Opt, _Dir, _D, _T, _P) -> - not_supported(Opt); -enc_sockopt_key(socket, timestamp = Opt, _Dir, _D, _T, _P) -> - not_supported(Opt); -enc_sockopt_key(socket, UnknownOpt, _Dir, _D, _T, _P) -> - unknown(UnknownOpt); +enc_sockopt_key(socket = L, sndlowat = Opt, _Dir, _D, _T, _P) -> + not_supported({L, Opt}); +enc_sockopt_key(socket = L, sndtimeo = Opt, _Dir, _D, _T, _P) -> + not_supported({L, Opt}); +enc_sockopt_key(socket = L, timestamp = Opt, _Dir, _D, _T, _P) -> + not_supported({L, Opt}); +enc_sockopt_key(socket = L, UnknownOpt, _Dir, _D, _T, _P) -> + unknown({L, UnknownOpt}); %% +++ IP socket options +++ -enc_sockopt_key(ip, add_membership = Opt, set = _Dir, _D, _T, _P) -> - not_supported(Opt); -enc_sockopt_key(ip, add_source_membership = Opt, set = _Dir, _D, _T, _P) -> - not_supported(Opt); -enc_sockopt_key(ip, block_source = Opt, set = _Dir, _D, _T, _P) -> - not_supported(Opt); +enc_sockopt_key(ip = L, add_membership = Opt, set = _Dir, _D, _T, _P) -> + not_supported({L, Opt}); +enc_sockopt_key(ip = L, add_source_membership = Opt, set = _Dir, _D, _T, _P) -> + not_supported({L, Opt}); +enc_sockopt_key(ip = L, block_source = Opt, set = _Dir, _D, _T, _P) -> + not_supported({L, Opt}); %% FreeBSD only? %% Only respected on udp and raw ip (unless the hdrincl option has been set). -enc_sockopt_key(ip, dontfrag = Opt, _Dir, _D, _T, _P) -> - not_supported(Opt); -enc_sockopt_key(ip, drop_membership = Opt, set = _Dir, _D, _T, _P) -> - not_supported(Opt); -enc_sockopt_key(ip, drop_source_membership = Opt, set = _Dir, _D, _T, _P) -> - not_supported(Opt); +enc_sockopt_key(ip = L, dontfrag = Opt, _Dir, _D, _T, _P) -> + not_supported({L, Opt}); +enc_sockopt_key(ip = L, drop_membership = Opt, set = _Dir, _D, _T, _P) -> + not_supported({L, Opt}); +enc_sockopt_key(ip = L, drop_source_membership = Opt, set = _Dir, _D, _T, _P) -> + not_supported({L, Opt}); %% Linux only? -enc_sockopt_key(ip, free_bind = Opt, _Dir, _D, _T, _P) -> - not_supported(Opt); -enc_sockopt_key(ip, hdrincl = Opt, _Dir, _D, raw = _T, _P) -> - not_supported(Opt); +enc_sockopt_key(ip = L, free_bind = Opt, _Dir, _D, _T, _P) -> + not_supported({L, Opt}); +enc_sockopt_key(ip = L, hdrincl = Opt, _Dir, _D, raw = _T, _P) -> + not_supported({L, Opt}); %% FreeBSD only? -enc_sockopt_key(ip, minttl = Opt, _Dir, _D, raw = _T, _P) -> - not_supported(Opt); -enc_sockopt_key(ip, msfilter = Opt, _Dir, _D, _T, _P) -> - not_supported(Opt); -enc_sockopt_key(ip, mtu = Opt, get = _Dir, _D, _T, _P) -> - not_supported(Opt); -enc_sockopt_key(ip, mtu_discover = Opt, _Dir, _D, _T, _P) -> - not_supported(Opt); -enc_sockopt_key(ip, multicast_all = Opt, _Dir, _D, _T, _P) -> - not_supported(Opt); -enc_sockopt_key(ip, multicast_if = Opt, _Dir, _D, _T, _P) -> - not_supported(Opt); -enc_sockopt_key(ip, multicast_loop = Opt, _Dir, _D, _T, _P) -> - not_supported(Opt); -enc_sockopt_key(ip, multicast_ttl = Opt, _Dir, _D, _T, _P) -> - not_supported(Opt); -enc_sockopt_key(ip, nodefrag = Opt, _Dir, _D, raw = _T, _P) -> - not_supported(Opt); -enc_sockopt_key(ip, options = Opt, _Dir, _D, _T, _P) -> - not_supported(Opt); -enc_sockopt_key(ip, pktinfo = Opt, _Dir, _D, _T, _P) -> - not_supported(Opt); +enc_sockopt_key(ip = L, minttl = Opt, _Dir, _D, raw = _T, _P) -> + not_supported({L, Opt}); +enc_sockopt_key(ip = L, msfilter = Opt, _Dir, _D, _T, _P) -> + not_supported({L, Opt}); +enc_sockopt_key(ip = L, mtu = Opt, get = _Dir, _D, _T, _P) -> + not_supported({L, Opt}); +enc_sockopt_key(ip = L, mtu_discover = Opt, _Dir, _D, _T, _P) -> + not_supported({L, Opt}); +enc_sockopt_key(ip = L, multicast_all = Opt, _Dir, _D, _T, _P) -> + not_supported({L, Opt}); +enc_sockopt_key(ip = L, multicast_if = Opt, _Dir, _D, _T, _P) -> + not_supported({L, Opt}); +enc_sockopt_key(ip = L, multicast_loop = Opt, _Dir, _D, _T, _P) -> + not_supported({L, Opt}); +enc_sockopt_key(ip = L, multicast_ttl = Opt, _Dir, _D, _T, _P) -> + not_supported({L, Opt}); +enc_sockopt_key(ip = L, nodefrag = Opt, _Dir, _D, raw = _T, _P) -> + not_supported({L, Opt}); +enc_sockopt_key(ip = L, options = Opt, _Dir, _D, _T, _P) -> + not_supported({Opt, L}); +enc_sockopt_key(ip = L, pktinfo = Opt, _Dir, _D, _T, _P) -> + not_supported({L, Opt}); %% This require special code for accessing the errors. %% via calling the recvmsg with the MSG_ERRQUEUE flag set, -enc_sockopt_key(ip, recverr = Opt, _Dir, _D, _T, _P) -> - not_supported(Opt); -enc_sockopt_key(ip, recvif = Opt, _Dir, _D, dgram = _T, _P) -> - not_supported(Opt); -enc_sockopt_key(ip, recvdstaddr = Opt, _Dir, _D, _T, _P) -> - not_supported(Opt); -enc_sockopt_key(ip, recvopts = Opt, _Dir, _D, T, _P) when (T =/= stream) -> - not_supported(Opt); -enc_sockopt_key(ip, recvorigdstaddr = Opt, _Dir, _D, _T, _P) -> - not_supported(Opt); +enc_sockopt_key(ip = L, recverr = Opt, _Dir, _D, _T, _P) -> + not_supported({L, Opt}); +enc_sockopt_key(ip = L, recvif = Opt, _Dir, _D, dgram = _T, _P) -> + not_supported({L, Opt}); +enc_sockopt_key(ip = L, recvdstaddr = Opt, _Dir, _D, _T, _P) -> + not_supported({L, Opt}); +enc_sockopt_key(ip = L, recvopts = Opt, _Dir, _D, T, _P) when (T =/= stream) -> + not_supported({L, Opt}); +enc_sockopt_key(ip = L, recvorigdstaddr = Opt, _Dir, _D, _T, _P) -> + not_supported({L, Opt}); enc_sockopt_key(ip, recvtos = _Opt, _Dir, _D, _T, _P) -> ?SOCKET_OPT_IP_RECVTOS; -enc_sockopt_key(ip, recvttl = Opt, _Dir, _D, T, _P) when (T =/= stream) -> - not_supported(Opt); -enc_sockopt_key(ip, retopts = Opt, _Dir, _D, _T, _P) -> - not_supported(Opt); +enc_sockopt_key(ip = L, recvttl = Opt, _Dir, _D, T, _P) when (T =/= stream) -> + not_supported({L, Opt}); +enc_sockopt_key(ip = L, retopts = Opt, _Dir, _D, _T, _P) -> + not_supported({L, Opt}); enc_sockopt_key(ip, router_alert = _Opt, _Dir, _D, raw = _T, _P) -> ?SOCKET_OPT_IP_ROUTER_ALERT; %% On FreeBSD it specifies that this option is only valid @@ -1999,49 +2041,49 @@ enc_sockopt_key(ip, router_alert = _Opt, _Dir, _D, raw = _T, _P) -> %% No such condition on linux (in the man page)... enc_sockopt_key(ip, tos = _Opt, _Dir, _D, _T, _P) -> ?SOCKET_OPT_IP_TOS; -enc_sockopt_key(ip, transparent = Opt, _Dir, _D, _T, _P) -> - not_supported(Opt); +enc_sockopt_key(ip = L, transparent = Opt, _Dir, _D, _T, _P) -> + not_supported({L, Opt}); enc_sockopt_key(ip, ttl = _Opt, _Dir, _D, _T, _P) -> ?SOCKET_OPT_IP_TTL; -enc_sockopt_key(ip, unblock_source = Opt, set = _Dir, _D, _T, _P) -> - not_supported(Opt); -enc_sockopt_key(ip, UnknownOpt, _Dir, _D, _T, _P) -> - unknown(UnknownOpt); +enc_sockopt_key(ip = L, unblock_source = Opt, set = _Dir, _D, _T, _P) -> + not_supported({L, Opt}); +enc_sockopt_key(ip = L, UnknownOpt, _Dir, _D, _T, _P) -> + unknown({L, UnknownOpt}); %% IPv6 socket options enc_sockopt_key(ipv6, hoplimit = _Opt, _Dir, _D, T, _P) when (T =:= dgram) orelse (T =:= raw) -> ?SOCKET_OPT_IPV6_HOPLIMIT; -enc_sockopt_key(ipv6, UnknownOpt, _Dir, _D, _T, _P) -> - unknown(UnknownOpt); +enc_sockopt_key(ipv6 = L, UnknownOpt, _Dir, _D, _T, _P) -> + unknown({L, UnknownOpt}); %% TCP socket options %% There are other options that would be useful; info, %% but they are difficult to get portable... enc_sockopt_key(tcp, congestion = _Opt, _Dir, _D, _T, _P) -> ?SOCKET_OPT_TCP_CONGESTION; -enc_sockopt_key(tcp, cork = Opt, _Dir, _D, _T, _P) -> - not_supported(Opt); +enc_sockopt_key(tcp = L, cork = Opt, _Dir, _D, _T, _P) -> + not_supported({L, Opt}); enc_sockopt_key(tcp, maxseg = _Opt, _Dir, _D, _T, _P) -> ?SOCKET_OPT_TCP_MAXSEG; enc_sockopt_key(tcp, nodelay = _Opt, _Dir, _D, _T, _P) -> ?SOCKET_OPT_TCP_NODELAY; -enc_sockopt_key(tcp, UnknownOpt, _Dir, _D, _T, _P) -> - unknown(UnknownOpt); +enc_sockopt_key(tcp = L, UnknownOpt, _Dir, _D, _T, _P) -> + unknown({L, UnknownOpt}); %% UDP socket options enc_sockopt_key(udp, cork = _Opt, _Dir, _D, _T, _P) -> ?SOCKET_OPT_UDP_CORK; -enc_sockopt_key(udp, UnknownOpt, _Dir, _D, _T, _P) -> - unknown(UnknownOpt); +enc_sockopt_key(udp = L, UnknownOpt, _Dir, _D, _T, _P) -> + unknown({L, UnknownOpt}); %% SCTP socket options enc_sockopt_key(sctp, autoclose = _Opt, _Dir, _D, _T, _P) -> ?SOCKET_OPT_SCTP_AUTOCLOSE; enc_sockopt_key(sctp, nodelay = _Opt, _Dir, _D, _T, _P) -> ?SOCKET_OPT_SCTP_NODELAY; -enc_sockopt_key(sctp, UnknownOpt, _Dir, _D, _T, _P) -> - unknown(UnknownOpt); +enc_sockopt_key(sctp = L, UnknownOpt, _Dir, _D, _T, _P) -> + unknown({L, UnknownOpt}); %% +++ "Native" socket options +++ enc_sockopt_key(Level, Opt, set = _Dir, _D, _T, _P) @@ -2158,6 +2200,16 @@ tdiff(T1, T2) -> +p(F, A) -> + p(get(sname), F, A). + +p(undefined, F, A) -> + p("***", F, A); +p(SName, F, A) -> + io:format("[~s,~p] " ++ F ++ "~n", [SName, self()|A]). + + + %% =========================================================================== %% %% Error functions diff --git a/lib/kernel/test/socket_client.erl b/lib/kernel/test/socket_client.erl index 13e87f4109..a284777046 100644 --- a/lib/kernel/test/socket_client.erl +++ b/lib/kernel/test/socket_client.erl @@ -10,6 +10,9 @@ -export([start/1]). +-define(REQ, 0). +-define(REP, 1). + start(Port) -> start_tcp(Port). @@ -19,12 +22,16 @@ start_tcp(Port) -> start(Domain, Type, Proto, Port) -> try do_init(Domain, Type, Proto) of Sock -> - connect(Sock, Domain, Port) + connect(Sock, Domain, Port), + %% Give the server some time... + p("wait some", []), + %% sleep(5000), + %% ok = socket:close(Sock), + send_loop(Sock) catch - throw:E:P -> + throw:E -> e("Failed initiate: " - "~n Error: ~p" - "~n Path: ~p", [E, P]) + "~n Error: ~p", [E]) end. do_init(Domain, Type, Proto) -> @@ -58,11 +65,11 @@ connect(Sock, Domain, Port) -> SA = #{family => Domain, addr => Addr, port => Port}, - i("try (socket) connect to ~p", [SA]), + i("try (socket) connect to:" + "~n ~p", [SA]), case socket:connect(Sock, SA) of ok -> i("connected"), - send_loop(Sock), ok; {error, Reason} -> e("connect failure: " @@ -74,22 +81,37 @@ connect(Sock, Domain, Port) -> send_loop(Sock) -> send_loop(Sock, 1). -send_loop(Sock, N) -> - case socket:send(Sock, <<0:32, N:32, "hejsan">>) of +send_loop(Sock, N) when (N =< 10) -> + i("try send request ~w", [N]), + Req = enc_req_msg(N, "hejsan"), + case socket:send(Sock, Req) of ok -> - case send:recv(Sock, 0) of - {ok, <<1:32, N:32, "hejsan">>} -> - send_loop(Sock, N+1); + i("request ~w sent - now try read answer", [N]), + case socket:recv(Sock, 0) of + {ok, Msg} -> + i("received ~w bytes of data", [size(Msg)]), + case dec_msg(Msg) of + {reply, N, Reply} -> + i("received reply ~w: ~p", [N, Reply]), + send_loop(Sock, N+1) + end; {error, RReason} -> e("Failed recv response for request ~w: " - "~n ~p", [RReason]), + "~n ~p", [N, RReason]), exit({failed_recv, RReason}) end; {error, SReason} -> e("Failed send request ~w: " "~n ~p", [SReason]), exit({failed_send, SReason}) - end. + end; +send_loop(Sock, _N) -> + i("we are done - close the socket when: " + "~n ~p", [socket:info()]), + ok = socket:close(Sock), + i("we are done - socket closed when: " + "~n ~p", [socket:info()]). + which_addr(_Domain, []) -> throw(no_address); @@ -106,6 +128,61 @@ which_addr2(Domain, [_|IFO]) -> which_addr2(Domain, IFO). +%% --- + +enc_req_msg(N, Data) -> + enc_msg(?REQ, N, Data). + +enc_rep_msg(N, Data) -> + enc_msg(?REP, N, Data). + +enc_msg(Type, N, Data) when is_list(Data) -> + enc_msg(Type, N, list_to_binary(Data)); +enc_msg(Type, N, Data) + when is_integer(Type) andalso is_integer(N) andalso is_binary(Data) -> + <>. + +dec_msg(<>) -> + {request, N, Data}; +dec_msg(<>) -> + {reply, N, Data}. + + +%% --- + +sleep(T) -> + receive after T -> ok end. + + +%% --- + +formated_timestamp() -> + format_timestamp(os:timestamp()). + +format_timestamp(Now) -> + N2T = fun(N) -> calendar:now_to_local_time(N) end, + format_timestamp(Now, N2T, true). + +format_timestamp({_N1, _N2, N3} = N, N2T, true) -> + FormatExtra = ".~.2.0w", + ArgsExtra = [N3 div 10000], + format_timestamp(N, N2T, FormatExtra, ArgsExtra); +format_timestamp({_N1, _N2, _N3} = N, N2T, false) -> + FormatExtra = "", + ArgsExtra = [], + format_timestamp(N, N2T, FormatExtra, ArgsExtra). + +format_timestamp(N, N2T, FormatExtra, ArgsExtra) -> + {Date, Time} = N2T(N), + {YYYY,MM,DD} = Date, + {Hour,Min,Sec} = Time, + FormatDate = + io_lib:format("~.4w-~.2.0w-~.2.0w ~.2.0w:~.2.0w:~.2.0w" ++ FormatExtra, + [YYYY, MM, DD, Hour, Min, Sec] ++ ArgsExtra), + lists:flatten(FormatDate). + + +%% --- e(F, A) -> p(" " ++ F, A). @@ -116,5 +193,5 @@ i(F, A) -> p("*** " ++ F, A). p(F, A) -> - io:format("[client] " ++ F ++ "~n", A). + io:format("[client,~p][~s] " ++ F ++ "~n", [self(),formated_timestamp()|A]). diff --git a/lib/kernel/test/socket_server.erl b/lib/kernel/test/socket_server.erl index 0effc7c0ff..64bd6396e4 100644 --- a/lib/kernel/test/socket_server.erl +++ b/lib/kernel/test/socket_server.erl @@ -10,6 +10,9 @@ -export([start/0]). +-define(REQ, 0). +-define(REP, 1). + -record(handler, {socket, parent}). start() -> @@ -82,14 +85,17 @@ which_addr2(Domain, [_|IFO]) -> accept_loop(LSock) -> - put(sname, "accept-loop"), + put(sname, "acceptor"), accept_loop(LSock, []). accept_loop(LSock, Handlers) -> i("try accept"), case socket:accept(LSock, infinity) of {ok, Sock} -> - i("accepted: ~p", [Sock]), + i("accepted: " + "~n ~p" + "~nwhen" + "~n ~p", [Sock, socket:info()]), case handle_accept_success(Sock) of {ok, Handler} -> accept_loop(LSock, [Handler|Handlers]); @@ -127,6 +133,7 @@ handler_init(Parent, Socket) -> put(sname, "handler"), receive {handler, Parent, continue} -> + socket:setopt(Socket, otp, debug, true), handler_loop(#handler{parent = Parent, socket = Socket}) end. @@ -135,27 +142,90 @@ handler_continue(Handler) -> Handler ! {handler, self(), continue}. handler_loop(#handler{socket = Socket} = H) -> - case socket:read(Socket, 0) of - {ok, <<0:32, N:32, ReqData/binary>>} -> - i("received request ~w: " - "~n ~p", [N, ReqData]), - Reply = <<1:32, N:32, ReqData/binary>>, - case socket:send(Socket, Reply) of - ok -> - i("successfully sent reply ~w", [N]), - handler_loop(H); - {error, SReason} -> - e("failed sending reply ~w:" - "~n ~p", [N, SReason]), - exit({failed_sending_reply, SReason}) + case socket:recv(Socket, 0) of + {ok, Msg} when (size(Msg) =:= 0) -> + i("received empty msg - hickup? - try again", []), + handler_loop(H); + {ok, Msg} -> + i("received ~w bytes of data", [size(Msg)]), + case dec_msg(Msg) of + {request, N, Req} -> + i("received request ~w: " + "~n ~p", [N, Req]), + Reply = enc_rep_msg(N, "hoppsan"), + case socket:send(Socket, Reply) of + ok -> + i("successfully sent reply ~w", [N]), + handler_loop(H); + {error, SReason} -> + e("failed sending reply ~w:" + "~n ~p", [N, SReason]), + exit({failed_sending_reply, SReason}) + end end; + + {error, closed} -> + i("closed when" + "~n ~p", [socket:info()]), + exit(normal); + {error, RReason} -> e("failed reading request: " "~n ~p", [RReason]), - exit({failed_sending_reply, RReason}) + exit({failed_reading_request, RReason}) end. +%% --- + +enc_req_msg(N, Data) -> + enc_msg(?REQ, N, Data). + +enc_rep_msg(N, Data) -> + enc_msg(?REP, N, Data). + +enc_msg(Type, N, Data) when is_list(Data) -> + enc_msg(Type, N, list_to_binary(Data)); +enc_msg(Type, N, Data) + when is_integer(Type) andalso is_integer(N) andalso is_binary(Data) -> + <>. + +dec_msg(<>) -> + {request, N, Data}; +dec_msg(<>) -> + {reply, N, Data}. + + +%% --- + +formated_timestamp() -> + format_timestamp(os:timestamp()). + +format_timestamp(Now) -> + N2T = fun(N) -> calendar:now_to_local_time(N) end, + format_timestamp(Now, N2T, true). + +format_timestamp({_N1, _N2, N3} = N, N2T, true) -> + FormatExtra = ".~.2.0w", + ArgsExtra = [N3 div 10000], + format_timestamp(N, N2T, FormatExtra, ArgsExtra); +format_timestamp({_N1, _N2, _N3} = N, N2T, false) -> + FormatExtra = "", + ArgsExtra = [], + format_timestamp(N, N2T, FormatExtra, ArgsExtra). + +format_timestamp(N, N2T, FormatExtra, ArgsExtra) -> + {Date, Time} = N2T(N), + {YYYY,MM,DD} = Date, + {Hour,Min,Sec} = Time, + FormatDate = + io_lib:format("~.4w-~.2.0w-~.2.0w ~.2.0w:~.2.0w:~.2.0w" ++ FormatExtra, + [YYYY, MM, DD, Hour, Min, Sec] ++ ArgsExtra), + lists:flatten(FormatDate). + + +%% --- + e(F, A) -> p(" " ++ F, A). @@ -168,5 +238,6 @@ p(F, A) -> p(get(sname), F, A). p(SName, F, A) -> - io:format("[server,~s] " ++ F ++ "~n", [SName|A]). + io:format("[server:~s,~p][~s] " ++ F ++ "~n", + [SName,self(),formated_timestamp()|A]). -- cgit v1.2.3 From dce68cf27f2dd1721bd316594a29ff99a0de7bb9 Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Tue, 3 Jul 2018 15:46:30 +0200 Subject: [socket-nif] Restructure of the socket (test) server --- lib/kernel/test/socket_server.erl | 301 +++++++++++++++++++++++++++++++------- 1 file changed, 252 insertions(+), 49 deletions(-) diff --git a/lib/kernel/test/socket_server.erl b/lib/kernel/test/socket_server.erl index 64bd6396e4..dde605b624 100644 --- a/lib/kernel/test/socket_server.erl +++ b/lib/kernel/test/socket_server.erl @@ -13,7 +13,9 @@ -define(REQ, 0). -define(REP, 1). --record(handler, {socket, parent}). +-record(manager, {acceptor, handler_id, handlers}). +-record(acceptor, {socket, manager}). +-record(handler, {socket, manager}). start() -> start_tcp(). @@ -23,17 +25,169 @@ start_tcp() -> start(Domain, Type, Proto) -> put(sname, "starter"), - try do_init(Domain, Type, Proto) of + i("try start manager"), + {Pid, MRef} = manager_start(Domain, Type, Proto), + i("manager (~p) started", [Pid]), + loop(Pid, MRef). + +loop(Pid, MRef) -> + receive + {'DOWN', MRef, process, Pid, Reason} -> + i("manager process exited: " + "~n ~p", [Reason]), + ok + end. + + +%% ========================================================================= + +manager_start(Domain, Type, Proto) -> + spawn_monitor(fun() -> manager_init(Domain, Type, Proto) end). + +manager_start_handler(Pid, Sock) -> + manager_request(Pid, {start_handler, Sock}). + +manager_stop(Pid, Reason) -> + manager_request(Pid, {stop, Reason}). + +manager_request(Pid, Request) -> + request(manager, Pid, Request). + +manager_reply(Pid, Ref, Reply) -> + reply(manager, Pid, Ref, Reply). + + +manager_init(Domain, Type, Proto) -> + put(sname, "manager"), + i("try start acceptor"), + case acceptor_start(Domain, Type, Proto) of + {ok, {Pid, MRef}} -> + i("acceptor started"), + manager_loop(#manager{acceptor = {Pid, MRef}, + handler_id = 1, + handlers = []}); + {error, Reason} -> + exit({failed_starting_acceptor, Reason}) + end. + +manager_loop(M) -> + receive + {'DOWN', MRef, process, Pid, Reason} -> + M2 = manager_handle_down(M, MRef, Pid, Reason), + manager_loop(M2); + + {manager, Pid, Ref, Request} -> + M2 = manager_handle_request(M, Pid, Ref, Request), + manager_loop(M2) + end. + + +manager_handle_down(#manager{acceptor = {Pid, MRef}}, MRef, Pid, Reason) + when (Reason =/= normal) -> + e("acceptor died: " + "~n ~p", [Reason]), + exit({acceptor_died, Reason}); +manager_handle_down(#manager{acceptor = {Pid, MRef}}, MRef, Pid, Reason) -> + exit(Reason); +manager_handle_down(#manager{handlers = Handlers} = M, _MRef, Pid, Reason) -> + if + (Reason =/= normal) -> + e("handler ~p died: " + "~n ~p", [Pid, Reason]); + true -> + i("handler ~p terminated", [Pid]) + end, + Handlers2 = lists:keydelete(Pid, 1, Handlers), + M#manager{handlers = Handlers2}. + + +manager_handle_request(#manager{handler_id = HID, + handlers = Handlers} = M, Pid, Ref, + {start_handler, Sock}) -> + i("try start handler (~w)", [HID]), + case handler_start(HID, Sock) of + {ok, {HPid, HMRef}} -> + i("handler ~w started", [HID]), + manager_reply(Pid, Ref, {ok, HPid}), + M#manager{handler_id = HID+1, + handlers = [{HPid, HMRef, HID}|Handlers]}; + {error, Reason} = ERROR -> + e("Failed starting new handler: " + "~n Sock: ~p" + "~n Reason: ~p", [Sock, Reason]), + manager_reply(Pid, Ref, ERROR), + M + end; +manager_handle_request(#manager{acceptor = {Pid, MRef}, + handlers = Handlers}, Pid, Ref, + {stop, Reason}) -> + i("stop"), + manager_reply(Pid, Ref, ok), + manager_stop_handlers(Handlers, Reason), + i("try stop acceptor ~p: ~p", [Pid, Reason]), + erlang:demonitor(MRef, [flush]), + acceptor_stop(Pid, Reason), + i("stop", []), + exit(Reason). + + +manager_stop_handlers(Handlers, Reason) -> + lists:foreach(fun({P,M,ID}) -> + manager_stop_handler(P, M, ID, Reason) + end, Handlers). + +manager_stop_handler(Pid, MRef, ID, Reason) -> + i("try stop handler ~w (~p): ~p", [ID, Pid, Reason]), + erlang:demonitor(MRef, [flush]), + handler_stop(Pid, Reason), + ok. + + + +%% ========================================================================= + +acceptor_start(Domain, Type, Proto) -> + Self = self(), + A = {Pid, _} = spawn_monitor(fun() -> + acceptor_init(Self, Domain, Type, Proto) + end), + receive + {acceptor, Pid, ok} -> + {ok, A}; + {acceptor, Pid, {error, _} = Error} -> + exit(Pid, kill), % Just in case + Error; + {'DOWN', _MRef, process, Pid, Reason} -> + {error, {crashed, Reason}} + end. + +acceptor_stop(Pid, _Reason) -> + %% acceptor_request(Pid, {stop, Reason}). + exit(Pid, kill). + +%% acceptor_request(Pid, Request) -> +%% request(acceptor, Pid, Request). + +%% acceptor_reply(Pid, Ref, Reply) -> +%% reply(acceptor, Pid, Ref, Reply). + + +acceptor_init(Manager, Domain, Type, Proto) -> + put(sname, "acceptor"), + try acceptor_do_init(Domain, Type, Proto) of Sock -> - accept_loop(Sock) + Manager ! {acceptor, self(), ok}, + acceptor_loop(#acceptor{manager = Manager, + socket = Sock}) catch throw:E:P -> e("Failed initiate: " "~n Error: ~p" - "~n Path: ~p", [E, P]) + "~n Path: ~p", [E, P]), + Manager ! {acceptor, self(), {error, {catched, E, P}}} end. -do_init(Domain, Type, Proto) -> +acceptor_do_init(Domain, Type, Proto) -> i("try (socket) open"), Sock = case socket:open(Domain, Type, Proto) of {ok, S} -> @@ -41,18 +195,18 @@ do_init(Domain, Type, Proto) -> {error, OReason} -> throw({open, OReason}) end, - i("opened - now try find (local) address"), + i("(socket) open - try find (local) address"), Addr = which_addr(Domain), SA = #{family => Domain, addr => Addr}, - i("addr ~p - now try (socket) bind", [Addr]), + i("found (~p) - try (socket) bind", [Addr]), Port = case socket:bind(Sock, SA) of {ok, P} -> P; {error, BReason} -> throw({bind, BReason}) end, - i("bound to ~w - now try (socket) listen", [Port]), + i("bound (~w) - try (socket) listen", [Port]), case socket:listen(Sock) of ok -> Sock; @@ -84,11 +238,7 @@ which_addr2(Domain, [_|IFO]) -> which_addr2(Domain, IFO). -accept_loop(LSock) -> - put(sname, "acceptor"), - accept_loop(LSock, []). - -accept_loop(LSock, Handlers) -> +acceptor_loop(#acceptor{socket = LSock} = A) -> i("try accept"), case socket:accept(LSock, infinity) of {ok, Sock} -> @@ -96,14 +246,14 @@ accept_loop(LSock, Handlers) -> "~n ~p" "~nwhen" "~n ~p", [Sock, socket:info()]), - case handle_accept_success(Sock) of - {ok, Handler} -> - accept_loop(LSock, [Handler|Handlers]); - {error, HReason} -> + case acceptor_handle_accept_success(A, Sock) of + ok -> + acceptor_loop(A); + {error, Reason} -> e("Failed starting handler: " - "~n ~p", [HReason]), + "~n ~p", [Reason]), socket:close(Sock), - exit({failed_starting_handler, HReason}) + exit({failed_starting_handler, Reason}) end; {error, Reason} -> e("accept failure: " @@ -111,41 +261,75 @@ accept_loop(LSock, Handlers) -> exit({accept, Reason}) end. +acceptor_handle_accept_success(#acceptor{manager = Manager}, Sock) -> + i("try start handler"), + case manager_start_handler(Manager, Sock) of + {ok, Pid} -> + i("handler (~p) started - now change 'ownership'", [Pid]), + case socket:setopt(Sock, otp, controlling_process, Pid) of + ok -> + %% Normally we should have a msgs collection here + %% (of messages we receive before the control was + %% handled over to Handler), but since we don't + %% have active implemented yet... + i("new handler (~p) now controlling process", [Pid]), + handler_continue(Pid), + ok; + {error, _} = ERROR -> + exit(Pid, kill), + ERROR + end; + {error, Reason2} -> + e("failed starting handler: " + "~n (new) Socket: ~p" + "~n Reason: ~p", [Sock, Reason2]), + exit({failed_starting_handler, Reason2}) + end. -handle_accept_success(Sock) -> - Self = self(), - Handler = spawn_link(fun() -> handler_init(Self, Sock) end), - case socket:setopt(Sock, otp, controlling_process, Handler) of - ok -> - %% Normally we should have a msgs collection here - %% (of messages we receive before the control was - %% handled over to Handler), but since we don't - %% have active implemented yet... - handler_continue(Handler), - {ok, {Handler, Sock}}; - {error, _} = ERROR -> - exit(Handler, kill), + + +%% ========================================================================= + +handler_start(ID, Sock) -> + Self = self(), + H = {Pid, _} = spawn_monitor(fun() -> handler_init(Self, ID, Sock) end), + receive + {handler, Pid, ok} -> + {ok, H}; + {handler, Pid, {error, _} = ERROR} -> + exit(Pid, kill), % Just in case ERROR end. +handler_stop(Pid, _Reason) -> + %% handler_request(Pid, {stop, Reason}). + exit(Pid, kill). + +handler_continue(Pid) -> + handler_request(Pid, continue). + +handler_request(Pid, Request) -> + request(handler, Pid, Request). + +handler_reply(Pid, Ref, Reply) -> + reply(handler, Pid, Ref, Reply). + -handler_init(Parent, Socket) -> - put(sname, "handler"), +handler_init(Manager, ID, Sock) -> + put(sname, f("handler:~w", [ID])), + i("starting"), + Manager ! {handler, self(), ok}, receive - {handler, Parent, continue} -> - socket:setopt(Socket, otp, debug, true), - handler_loop(#handler{parent = Parent, - socket = Socket}) + {handler, Pid, Ref, continue} -> + i("continue"), + handler_reply(Pid, Ref, ok), + %% socket:setopt(Socket, otp, debug, true), + handler_loop(#handler{manager = Manager, + socket = Sock}) end. -handler_continue(Handler) -> - Handler ! {handler, self(), continue}. - handler_loop(#handler{socket = Socket} = H) -> - case socket:recv(Socket, 0) of - {ok, Msg} when (size(Msg) =:= 0) -> - i("received empty msg - hickup? - try again", []), - handler_loop(H); + case socket:recv(Socket) of {ok, Msg} -> i("received ~w bytes of data", [size(Msg)]), case dec_msg(Msg) of @@ -174,9 +358,10 @@ handler_loop(#handler{socket = Socket} = H) -> "~n ~p", [RReason]), exit({failed_reading_request, RReason}) end. - - -%% --- + + + +%% ========================================================================= enc_req_msg(N, Data) -> enc_msg(?REQ, N, Data). @@ -196,6 +381,21 @@ dec_msg(<>) -> {reply, N, Data}. + +%% --- + +request(Tag, Pid, Request) -> + Ref = make_ref(), + Pid ! {Tag, self(), Ref, Request}, + receive + {Tag, Pid, Ref, Reply} -> + Reply + end. + +reply(Tag, Pid, Ref, Reply) -> + Pid ! {Tag, self(), Ref, Reply}. + + %% --- formated_timestamp() -> @@ -226,6 +426,9 @@ format_timestamp(N, N2T, FormatExtra, ArgsExtra) -> %% --- +f(F, A) -> + lists:flatten(io_lib:format(F, A)). + e(F, A) -> p(" " ++ F, A). @@ -238,6 +441,6 @@ p(F, A) -> p(get(sname), F, A). p(SName, F, A) -> - io:format("[server:~s,~p][~s] " ++ F ++ "~n", + io:format("[~s,~p][~s] " ++ F ++ "~n", [SName,self(),formated_timestamp()|A]). -- cgit v1.2.3 From e39e25d84405e13ca0ce476e3ba473510e5548de Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Wed, 4 Jul 2018 15:18:18 +0200 Subject: [socket-nif] Fixed (dgram) recv Fixed handling of recvfrom (used by dgram sockets). Had forgot to do select(read) when we got block from the call to recvfrom. Argh! Also updated the (simple) test server and client to to be able to use udp (dgram+udp). OTP-14831 --- erts/emulator/nifs/common/socket_nif.c | 323 +++++++++++++++++++++++++++++++-- erts/preloaded/ebin/socket.beam | Bin 42488 -> 42660 bytes erts/preloaded/src/socket.erl | 122 ++++++++----- lib/kernel/test/Makefile | 1 + lib/kernel/test/socket_client.erl | 252 +++++++++++++++++-------- lib/kernel/test/socket_lib.erl | 129 +++++++++++++ lib/kernel/test/socket_server.erl | 237 +++++++++++++++--------- 7 files changed, 848 insertions(+), 216 deletions(-) create mode 100644 lib/kernel/test/socket_lib.erl diff --git a/erts/emulator/nifs/common/socket_nif.c b/erts/emulator/nifs/common/socket_nif.c index 027155fc92..4efca1c72d 100644 --- a/erts/emulator/nifs/common/socket_nif.c +++ b/erts/emulator/nifs/common/socket_nif.c @@ -354,6 +354,7 @@ typedef union { #define SOCKET_OPT_SOCK_RCVBUF 17 #define SOCKET_OPT_SOCK_REUSEADDR 21 #define SOCKET_OPT_SOCK_SNDBUF 27 +#define SOCKET_OPT_SOCK_TYPE 32 #define SOCKET_OPT_IP_RECVTOS 25 #define SOCKET_OPT_IP_ROUTER_ALERT 28 @@ -410,6 +411,7 @@ typedef union { #define sock_ntohs(x) ntohs((x)) #define sock_open(domain, type, proto) \ make_noninheritable_handle(socket((domain), (type), (proto))) +#define sock_peer(s, addr, len) getpeername((s), (addr), (len)) #define sock_recv(s,buf,len,flag) recv((s),(buf),(len),(flag)) #define sock_recvfrom(s,buf,blen,flag,addr,alen) \ recvfrom((s),(buf),(blen),(flag),(addr),(alen)) @@ -448,6 +450,7 @@ static unsigned long one_value = 1; #define sock_name(s, addr, len) getsockname((s), (addr), (len)) #define sock_ntohs(x) ntohs((x)) #define sock_open(domain, type, proto) socket((domain), (type), (proto)) +#define sock_peer(s, addr, len) getpeername((s), (addr), (len)) #define sock_recv(s,buf,len,flag) recv((s),(buf),(len),(flag)) #define sock_recvfrom(s,buf,blen,flag,addr,alen) \ recvfrom((s),(buf),(blen),(flag),(addr),(alen)) @@ -671,6 +674,12 @@ static ERL_NIF_TERM nif_setopt(ErlNifEnv* env, static ERL_NIF_TERM nif_getopt(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); +static ERL_NIF_TERM nif_sockname(ErlNifEnv* env, + int argc, + const ERL_NIF_TERM argv[]); +static ERL_NIF_TERM nif_peername(ErlNifEnv* env, + int argc, + const ERL_NIF_TERM argv[]); static ERL_NIF_TERM nif_finalize_connection(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); @@ -950,6 +959,10 @@ static ERL_NIF_TERM ngetopt_lvl_sock_reuseaddr(ErlNifEnv* env, static ERL_NIF_TERM ngetopt_lvl_sock_sndbuf(ErlNifEnv* env, SocketDescriptor* descP); #endif +#if defined(SO_TYPE) +static ERL_NIF_TERM ngetopt_lvl_sock_type(ErlNifEnv* env, + SocketDescriptor* descP); +#endif static ERL_NIF_TERM ngetopt_lvl_ip(ErlNifEnv* env, SocketDescriptor* descP, int eOpt); @@ -1013,6 +1026,10 @@ static ERL_NIF_TERM ngetopt_lvl_sctp_nodelay(ErlNifEnv* env, SocketDescriptor* descP); #endif #endif // defined(HAVE_SCTP) +static ERL_NIF_TERM nsockname(ErlNifEnv* env, + SocketDescriptor* descP); +static ERL_NIF_TERM npeername(ErlNifEnv* env, + SocketDescriptor* descP); static ERL_NIF_TERM nsetopt_str_opt(ErlNifEnv* env, SocketDescriptor* descP, @@ -1061,6 +1078,7 @@ static ERL_NIF_TERM recv_check_result(ErlNifEnv* env, static ERL_NIF_TERM recvfrom_check_result(ErlNifEnv* env, SocketDescriptor* descP, int read, + int saveErrno, ErlNifBinary* bufP, SocketAddress* fromAddrP, unsigned int fromAddrLen, @@ -1369,6 +1387,7 @@ ERL_NIF_TERM esock_atom_tcp; ERL_NIF_TERM esock_atom_true; ERL_NIF_TERM esock_atom_udp; ERL_NIF_TERM esock_atom_undefined; +ERL_NIF_TERM esock_atom_unknown; /* *** "Global" error (=reason) atoms *** */ ERL_NIF_TERM esock_atom_eagain; @@ -1447,7 +1466,7 @@ static SocketData data; * nif_listen(Sock, Backlog) * nif_accept(LSock, Ref) * nif_send(Sock, SendRef, Data, Flags) - * nif_sendto(Sock, SendRef, Data, Flags, DstSockAddr) + * nif_sendto(Sock, SendRef, Data, Dest, Flags) * nif_recv(Sock, RecvRef, Length, Flags) * nif_recvfrom(Sock, Flags) * nif_close(Sock) @@ -2629,8 +2648,8 @@ ERL_NIF_TERM nsend(ErlNifEnv* env, * Socket (ref) - Points to the socket descriptor. * SendRef - A unique id for this (send) request. * Data - The data to send in the form of a IOVec. - * Flags - Send flags. * Dest - Destination (socket) address. + * Flags - Send flags. */ static @@ -2640,24 +2659,37 @@ ERL_NIF_TERM nif_sendto(ErlNifEnv* env, { SocketDescriptor* descP; ERL_NIF_TERM sendRef; - ErlNifBinary data; + ErlNifBinary sndData; unsigned int eflags; int flags; ERL_NIF_TERM eSockAddr; SocketAddress remoteAddr; unsigned int remoteAddrLen; char* xres; + ERL_NIF_TERM res; + + SGDBG( ("SOCKET", "nif_sendto -> entry with argc: %d\r\n", argc) ); /* Extract arguments and perform preliminary validation */ - if ((argc != 6) || + if ((argc != 5) || !enif_get_resource(env, argv[0], sockets, (void**) &descP) || - !GET_BIN(env, argv[2], &data) || - !GET_UINT(env, argv[3], &eflags)) { + !GET_BIN(env, argv[2], &sndData) || + !GET_UINT(env, argv[4], &eflags)) { return enif_make_badarg(env); } sendRef = argv[1]; - eSockAddr = argv[4]; + eSockAddr = argv[3]; + + SSDBG( descP, + ("SOCKET", "nif_sendto -> args when sock = %d:" + "\r\n Socket: %T" + "\r\n sendRef: %T" + "\r\n size of data: %d" + "\r\n eSockAddr: %T" + "\r\n eflags: %d" + "\r\n", + descP->sock, argv[0], sendRef, sndData.size, eSockAddr, eflags) ); /* THIS TEST IS NOT CORRECT!!! */ if (!IS_OPEN(descP)) @@ -2671,7 +2703,14 @@ ERL_NIF_TERM nif_sendto(ErlNifEnv* env, &remoteAddrLen)) != NULL) return esock_make_error_str(env, xres); - return nsendto(env, descP, sendRef, &data, flags, &remoteAddr, remoteAddrLen); + res = nsendto(env, descP, sendRef, &sndData, flags, + &remoteAddr, remoteAddrLen); + + SGDBG( ("SOCKET", "nif_sendto -> done with result: " + "\r\n %T" + "\r\n", res) ); + + return res; } @@ -2919,6 +2958,14 @@ ERL_NIF_TERM nrecv(ErlNifEnv* env, * RecvRef - A unique id for this (send) request. * BufSz - Size of the buffer into which we put the received message. * Flags - Receive flags. + * + * + * + * How do we handle if the peek flag is set? We need to basically keep + * track of if we expect any data from the read. Regardless of the + * number of bytes we try to read. + * + * */ static @@ -2933,6 +2980,10 @@ ERL_NIF_TERM nif_recvfrom(ErlNifEnv* env, int flags; ERL_NIF_TERM res; + SGDBG( ("SOCKET", "nif_recvfrom -> entry with argc: %d\r\n", argc) ); + + /* Extract arguments and perform preliminary validation */ + if ((argc != 4) || !enif_get_resource(env, argv[0], sockets, (void**) &descP) || !GET_UINT(env, argv[2], &bufSz) || @@ -2941,6 +2992,14 @@ ERL_NIF_TERM nif_recvfrom(ErlNifEnv* env, } recvRef = argv[1]; + SSDBG( descP, + ("SOCKET", "nif_recvfrom -> args when sock = %d:" + "\r\n Socket: %T" + "\r\n recvRef: %T" + "\r\n bufSz: %d" + "\r\n eflags: %d" + "\r\n", descP->sock, argv[0], recvRef, bufSz, eflags) ); + /* if (IS_OPEN(descP)) */ /* return esock_make_error(env, atom_enotconn); */ @@ -2982,13 +3041,20 @@ static ERL_NIF_TERM nrecvfrom(ErlNifEnv* env, SocketDescriptor* descP, ERL_NIF_TERM recvRef, - uint16_t bufSz, + uint16_t len, int flags) { SocketAddress fromAddr; unsigned int addrLen; ssize_t read; + int save_errno; ErlNifBinary buf; + int bufSz = (len ? len : descP->rBufSz); + + SSDBG( descP, ("SOCKET", "nrecvfrom -> entry with" + "\r\n len: %d (%d)" + "\r\n flags: %d" + "\r\n", len, bufSz, flags) ); if (!descP->isReadable) return enif_make_badarg(env); @@ -2997,7 +3063,7 @@ ERL_NIF_TERM nrecvfrom(ErlNifEnv* env, * Either as much as we want to read or (if zero (0)) use the "default" * size (what has been configured). */ - if (!ALLOC_BIN((bufSz ? bufSz : descP->rBufSz), &buf)) + if (!ALLOC_BIN(bufSz, &buf)) return esock_make_error(env, atom_exalloc); /* We ignore the wrap for the moment. @@ -3010,9 +3076,14 @@ ERL_NIF_TERM nrecvfrom(ErlNifEnv* env, read = sock_recvfrom(descP->sock, buf.data, buf.size, flags, &fromAddr.sa, &addrLen); + if (IS_SOCKET_ERROR(read)) + save_errno = sock_errno(); + else + save_errno = -1; // The value does not actually matter in this case return recvfrom_check_result(env, descP, read, + save_errno, &buf, &fromAddr, addrLen, recvRef); @@ -4683,6 +4754,12 @@ ERL_NIF_TERM ngetopt_lvl_socket(ErlNifEnv* env, break; #endif +#if defined(SO_TYPE) + case SOCKET_OPT_SOCK_TYPE: + result = ngetopt_lvl_sock_type(env, descP); + break; +#endif + default: result = esock_make_error(env, esock_atom_einval); break; @@ -4792,6 +4869,51 @@ ERL_NIF_TERM ngetopt_lvl_sock_sndbuf(ErlNifEnv* env, #endif +#if defined(SO_TYPE) +static +ERL_NIF_TERM ngetopt_lvl_sock_type(ErlNifEnv* env, + SocketDescriptor* descP) +{ + ERL_NIF_TERM result; + int val; + SOCKOPTLEN_T valSz = sizeof(val); + int res; + + res = sock_getopt(descP->sock, SOL_SOCKET, SO_TYPE, &val, &valSz); + + if (res != 0) { + result = esock_make_error_errno(env, res); + } else { + switch (val) { + case SOCK_STREAM: + result = esock_make_ok2(env, esock_atom_stream); + break; + case SOCK_DGRAM: + result = esock_make_ok2(env, esock_atom_dgram); + break; +#ifdef HAVE_SCTP + case SOCK_SEQPACKET: + result = esock_make_ok2(env, esock_atom_seqpacket); + break; +#endif + case SOCK_RAW: + result = esock_make_ok2(env, esock_atom_raw); + break; + case SOCK_RDM: + result = esock_make_ok2(env, esock_atom_rdm); + break; + default: + result = esock_make_error(env, + MKT2(env, esock_atom_unknown, MKI(env, val))); + break; + } + } + + return result; +} +#endif + + /* ngetopt_lvl_ip - Level *IP* option(s) */ static @@ -5217,6 +5339,137 @@ ERL_NIF_TERM ngetopt_int_opt(ErlNifEnv* env, + +/* ---------------------------------------------------------------------- + * nif_sockname - get socket name + * + * Description: + * Returns the current address to which the socket is bound. + * + * Arguments: + * Socket (ref) - Points to the socket descriptor. + */ + +static +ERL_NIF_TERM nif_sockname(ErlNifEnv* env, + int argc, + const ERL_NIF_TERM argv[]) +{ + SocketDescriptor* descP; + ERL_NIF_TERM res; + + SGDBG( ("SOCKET", "nif_sockname -> entry with argc: %d\r\n", argc) ); + + /* Extract arguments and perform preliminary validation */ + + if ((argc != 1) || + !enif_get_resource(env, argv[0], sockets, (void**) &descP)) { + return enif_make_badarg(env); + } + + SSDBG( descP, + ("SOCKET", "nif_sockname -> args when sock = %d:" + "\r\n Socket: %T" + "\r\n", descP->sock, argv[0]) ); + + res = nsockname(env, descP); + + SSDBG( descP, ("SOCKET", "nif_sockname -> done with res = %T\r\n", res) ); + + return res; +} + + + +static +ERL_NIF_TERM nsockname(ErlNifEnv* env, + SocketDescriptor* descP) +{ + SocketAddress sa; + SocketAddress* saP = &sa; + unsigned int sz = sizeof(SocketAddress); + + sys_memzero((char*) saP, sz); + if (IS_SOCKET_ERROR(sock_name(descP->sock, (struct sockaddr*) saP, &sz))) { + return esock_make_error_errno(env, sock_errno()); + } else { + ERL_NIF_TERM esa; + char* xres; + + if ((xres = esock_encode_sockaddr(env, saP, sz, &esa)) != NULL) + return esock_make_error_str(env, xres); + else + return esock_make_ok2(env, esa); + } +} + + + +/* ---------------------------------------------------------------------- + * nif_peername - get name of the connected peer socket + * + * Description: + * Returns the address of the peer connected to the socket. + * + * Arguments: + * Socket (ref) - Points to the socket descriptor. + */ + +static +ERL_NIF_TERM nif_peername(ErlNifEnv* env, + int argc, + const ERL_NIF_TERM argv[]) +{ + SocketDescriptor* descP; + ERL_NIF_TERM res; + + SGDBG( ("SOCKET", "nif_peername -> entry with argc: %d\r\n", argc) ); + + /* Extract arguments and perform preliminary validation */ + + if ((argc != 1) || + !enif_get_resource(env, argv[0], sockets, (void**) &descP)) { + return enif_make_badarg(env); + } + + SSDBG( descP, + ("SOCKET", "nif_peername -> args when sock = %d:" + "\r\n Socket: %T" + "\r\n", descP->sock, argv[0]) ); + + res = npeername(env, descP); + + SSDBG( descP, ("SOCKET", "nif_peername -> done with res = %T\r\n", res) ); + + return res; +} + + + +static +ERL_NIF_TERM npeername(ErlNifEnv* env, + SocketDescriptor* descP) +{ + SocketAddress sa; + SocketAddress* saP = &sa; + unsigned int sz = sizeof(SocketAddress); + + sys_memzero((char*) saP, sz); + if (IS_SOCKET_ERROR(sock_peer(descP->sock, (struct sockaddr*) saP, &sz))) { + return esock_make_error_errno(env, sock_errno()); + } else { + ERL_NIF_TERM esa; + char* xres; + + if ((xres = esock_encode_sockaddr(env, saP, sz, &esa)) != NULL) + return esock_make_error_str(env, xres); + else + return esock_make_ok2(env, esa); + } +} + + + /* ---------------------------------------------------------------------- * U t i l i t y F u n c t i o n s * ---------------------------------------------------------------------- @@ -5495,6 +5748,7 @@ static ERL_NIF_TERM recvfrom_check_result(ErlNifEnv* env, SocketDescriptor* descP, int read, + int saveErrno, ErlNifBinary* bufP, SocketAddress* fromAddrP, unsigned int fromAddrLen, @@ -5502,6 +5756,14 @@ ERL_NIF_TERM recvfrom_check_result(ErlNifEnv* env, { ERL_NIF_TERM data; + SSDBG( descP, + ("SOCKET", "recvfrom_check_result -> entry with" + "\r\n read: %d" + "\r\n saveErrno: %d" + "\r\n recvRef: %T" + "\r\n", read, saveErrno, recvRef) ); + + /* There is a special case: If the provided 'to read' value is * zero (0). That means that we reads as much as we can, using * the default read buffer size. @@ -5511,12 +5773,12 @@ ERL_NIF_TERM recvfrom_check_result(ErlNifEnv* env, /* +++ Error handling +++ */ - int save_errno = sock_errno(); - - if (save_errno == ECONNRESET) { + if (saveErrno == ECONNRESET) { /* +++ Oups - closed +++ */ + SSDBG( descP, ("SOCKET", "recvfrom_check_result -> closed\r\n") ); + /* * IF THE CURRENT PROCESS IS *NOT* THE CONTROLLING * PROCESS, WE NEED TO INFORM IT!!! @@ -5536,11 +5798,22 @@ ERL_NIF_TERM recvfrom_check_result(ErlNifEnv* env, return esock_make_error(env, atom_closed); - } else if ((save_errno == ERRNO_BLOCK) || - (save_errno == EAGAIN)) { + } else if ((saveErrno == ERRNO_BLOCK) || + (saveErrno == EAGAIN)) { + + SSDBG( descP, ("SOCKET", "recvfrom_check_result -> eagain\r\n") ); + + SELECT(env, descP->sock, (ERL_NIF_SELECT_READ), + descP, NULL, recvRef); + return esock_make_error(env, esock_atom_eagain); } else { - return esock_make_error_errno(env, save_errno); + + SSDBG( descP, + ("SOCKET", + "recvfrom_check_result -> errno: %d\r\n", saveErrno) ); + + return esock_make_error_errno(env, saveErrno); } } else { @@ -6598,8 +6871,10 @@ void dec_socket(int domain, int type, int protocol) cnt_dec(&data.numTypeStreams, 1); else if (type == SOCK_DGRAM) cnt_dec(&data.numTypeDGrams, 1); +#ifdef HAVE_SCTP else if (type == SOCK_SEQPACKET) cnt_dec(&data.numTypeSeqPkgs, 1); +#endif if (protocol == IPPROTO_IP) cnt_dec(&data.numProtoIP, 1); @@ -6639,8 +6914,10 @@ void inc_socket(int domain, int type, int protocol) cnt_inc(&data.numTypeStreams, 1); else if (type == SOCK_DGRAM) cnt_inc(&data.numTypeDGrams, 1); +#ifdef HAVE_SCTP else if (type == SOCK_SEQPACKET) cnt_inc(&data.numTypeSeqPkgs, 1); +#endif if (protocol == IPPROTO_IP) cnt_inc(&data.numProtoIP, 1); @@ -6729,9 +7006,11 @@ BOOLEAN_T etype2type(int etype, int* type) *type = SOCK_RAW; break; +#ifdef HAVE_SCTP case SOCKET_TYPE_SEQPACKET: *type = SOCK_SEQPACKET; break; +#endif default: return FALSE; @@ -7718,6 +7997,15 @@ void socket_down(ErlNifEnv* env, const ErlNifPid* pid, const ErlNifMonitor* mon) { + SocketDescriptor* descP = (SocketDescriptor*) obj; + + SSDBG( descP, + ("SOCKET", "socket_down -> entry when" + "\r\n sock: %d" + "\r\n pid: %T" + "\r\n mon: %T" + "\r\n", descP->sock, *pid, *mon) ); + } @@ -7751,6 +8039,8 @@ ErlNifFunc socket_funcs[] = {"nif_shutdown", 2, nif_shutdown, 0}, {"nif_setopt", 5, nif_setopt, 0}, {"nif_getopt", 4, nif_getopt, 0}, + {"nif_sockname", 1, nif_sockname, 0}, + {"nif_peername", 1, nif_peername, 0}, /* Misc utility functions */ @@ -7927,6 +8217,7 @@ int on_load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info) esock_atom_true = MKA(env, "true"); esock_atom_udp = MKA(env, "udp"); esock_atom_undefined = MKA(env, "undefined"); + esock_atom_unknown = MKA(env, "unknown"); /* Global error codes */ esock_atom_eafnosupport = MKA(env, ESOCK_STR_EAFNOSUPPORT); diff --git a/erts/preloaded/ebin/socket.beam b/erts/preloaded/ebin/socket.beam index 4924c43a5c..99a9f0f749 100644 Binary files a/erts/preloaded/ebin/socket.beam and b/erts/preloaded/ebin/socket.beam differ diff --git a/erts/preloaded/src/socket.erl b/erts/preloaded/src/socket.erl index bf94271073..79b8471e08 100644 --- a/erts/preloaded/src/socket.erl +++ b/erts/preloaded/src/socket.erl @@ -37,7 +37,7 @@ accept/1, accept/2, send/2, send/3, send/4, - sendto/4, sendto/5, + sendto/3, sendto/4, sendto/5, %% sendmsg/4, %% writev/4, OR SENDV? It will be strange for recv then: recvv (instead of readv) @@ -50,7 +50,10 @@ shutdown/2, setopt/4, - getopt/3 + getopt/3, + + sockname/1, + peername/1 ]). -export_type([ @@ -204,11 +207,11 @@ reuseaddr | reuseport | rxq_ovfl | - sndlowat | - sndtimeo | setfib | sndbuf | sndbufforce | + sndlowat | + sndtimeo | timestamp | type. @@ -431,6 +434,7 @@ -define(SOCKET_SEND_FLAGS_DEFAULT, []). -define(SOCKET_SEND_TIMEOUT_DEFAULT, infinity). +-define(SOCKET_SENDTO_FLAGS_DEFAULT, []). -define(SOCKET_SENDTO_TIMEOUT_DEFAULT, ?SOCKET_SEND_TIMEOUT_DEFAULT). -define(SOCKET_RECV_FLAG_CMSG_CLOEXEC, 0). @@ -462,6 +466,7 @@ -define(SOCKET_OPT_SOCK_RCVBUF, 17). -define(SOCKET_OPT_SOCK_REUSEADDR, 21). -define(SOCKET_OPT_SOCK_SNDBUF, 27). +-define(SOCKET_OPT_SOCK_TYPE, 32). -define(SOCKET_OPT_IP_RECVTOS, 25). -define(SOCKET_OPT_IP_ROUTER_ALERT, 28). @@ -927,40 +932,43 @@ do_send(SockRef, Data, EFlags, Timeout) -> %% --------------------------------------------------------------------------- %% -sendto(Socket, Data, Flags, Dest) -> - sendto(Socket, Data, Flags, Dest, ?SOCKET_SENDTO_TIMEOUT_DEFAULT). +sendto(Socket, Data, Dest) -> + sendto(Socket, Data, Dest, ?SOCKET_SENDTO_FLAGS_DEFAULT). + +sendto(Socket, Data, Dest, Flags) -> + sendto(Socket, Data, Dest, Flags, ?SOCKET_SENDTO_TIMEOUT_DEFAULT). --spec sendto(Socket, Data, Flags, Dest, Timeout) -> +-spec sendto(Socket, Data, Dest, Flags, Timeout) -> ok | {error, Reason} when Socket :: socket(), Data :: binary(), - Flags :: send_flags(), Dest :: null | sockaddr(), + Flags :: send_flags(), Timeout :: timeout(), Reason :: term(). -sendto(Socket, Data, Flags, Dest, Timeout) when is_list(Data) -> +sendto(Socket, Data, Dest, Flags, Timeout) when is_list(Data) -> Bin = erlang:list_to_binary(Data), - sendto(Socket, Bin, Flags, Dest, Timeout); -sendto(#socket{ref = SockRef}, Data, Flags, Dest, Timeout) + sendto(Socket, Bin, Dest, Flags, Timeout); +sendto(#socket{ref = SockRef}, Data, Dest, Flags, Timeout) when is_binary(Data) andalso - is_list(Flags) andalso (Dest =:= null) andalso + is_list(Flags) andalso (is_integer(Timeout) orelse (Timeout =:= infinity)) -> EFlags = enc_send_flags(Flags), - do_sendto(SockRef, Data, EFlags, Dest, Timeout); -sendto(#socket{ref = SockRef}, Data, Flags, #{family := Fam} = Dest, Timeout) + do_sendto(SockRef, Data, Dest, EFlags, Timeout); +sendto(#socket{ref = SockRef}, Data, #{family := Fam} = Dest, Flags, Timeout) when is_binary(Data) andalso - is_list(Flags) andalso ((Fam =:= inet) orelse (Fam =:= inet6) orelse (Fam =:= local)) andalso + is_list(Flags) andalso (is_integer(Timeout) orelse (Timeout =:= infinity)) -> EFlags = enc_send_flags(Flags), - do_sendto(SockRef, Data, EFlags, Dest, Timeout). + do_sendto(SockRef, Data, Dest, EFlags, Timeout). -do_sendto(SockRef, Data, EFlags, Dest, Timeout) -> +do_sendto(SockRef, Data, Dest, EFlags, Timeout) -> TS = timestamp(Timeout), SendRef = make_ref(), - case nif_sendto(SockRef, SendRef, Data, EFlags, Dest) of + case nif_sendto(SockRef, SendRef, Data, Dest, EFlags) of ok -> %% We are done ok; @@ -970,10 +978,10 @@ do_sendto(SockRef, Data, EFlags, Dest, Timeout) -> receive {select, SockRef, SendRef, ready_output} when (Written > 0) -> <<_:Written/binary, Rest/binary>> = Data, - do_sendto(SockRef, Rest, EFlags, Dest, + do_sendto(SockRef, Rest, Dest, EFlags, next_timeout(TS, Timeout)); {select, SockRef, SendRef, ready_output} -> - do_sendto(SockRef, Data, EFlags, Dest, + do_sendto(SockRef, Data, Dest, EFlags, next_timeout(TS, Timeout)); {nif_abort, SendRef, Reason} -> @@ -988,7 +996,7 @@ do_sendto(SockRef, Data, EFlags, Dest, Timeout) -> {error, eagain} -> receive {select, SockRef, SendRef, ready_output} -> - do_sendto(SockRef, Data, EFlags, Dest, + do_sendto(SockRef, Data, Dest, EFlags, next_timeout(TS, Timeout)) after Timeout -> nif_cancel(SockRef, sendto, SendRef), @@ -1135,21 +1143,10 @@ do_recv(SockRef, _OldRef, Length, EFlags, Acc, Timeout) (is_integer(Timeout) andalso (Timeout > 0)) -> TS = timestamp(Timeout), RecvRef = make_ref(), - p("do_recv -> try read with" - "~n SockRef: ~p" - "~n RecvRef: ~p" - "~n Length: ~p" - "~n EFlags: ~p" - "~nwhen" - "~n Timeout: ~p (~p)", [SockRef, RecvRef, Length, EFlags, Timeout, TS]), case nif_recv(SockRef, RecvRef, Length, EFlags) of {ok, true = _Complete, Bin} when (size(Acc) =:= 0) -> - p("do_recv -> ok: complete (size(Acc) =:= 0)" - "~n size(Bin): ~p", [size(Bin)]), {ok, Bin}; {ok, true = _Complete, Bin} -> - p("do_recv -> ok: complete" - "~n size(Bin): ~p", [size(Bin)]), {ok, <>}; %% It depends on the amount of bytes we tried to read: @@ -1158,16 +1155,12 @@ do_recv(SockRef, _OldRef, Length, EFlags, Acc, Timeout) %% > 0 - We got a part of the message and we will be notified %% when there is more to read (a select message) {ok, false = _Complete, Bin} when (Length =:= 0) -> - p("do_recv -> ok: not-complete (Length =:= 0)" - "~n size(Bin): ~p", [size(Bin)]), do_recv(SockRef, RecvRef, Length, EFlags, <>, next_timeout(TS, Timeout)); {ok, false = _Completed, Bin} when (size(Acc) =:= 0) -> - p("do_recv -> ok: not-complete (size(Acc) =:= 0)" - "~n size(Bin): ~p", [size(Bin)]), %% We got the first chunk of it. %% We will be notified (select message) when there %% is more to read. @@ -1190,8 +1183,6 @@ do_recv(SockRef, _OldRef, Length, EFlags, Acc, Timeout) end; {ok, false = _Completed, Bin} -> - p("do_recv -> ok: not-complete" - "~n size(Bin): ~p", [size(Bin)]), %% We got a chunk of it! NewTimeout = next_timeout(TS, Timeout), receive @@ -1213,17 +1204,14 @@ do_recv(SockRef, _OldRef, Length, EFlags, Acc, Timeout) %% We return with the accumulated binary (if its non-empty) {error, eagain} when (Length =:= 0) andalso (size(Acc) > 0) -> - p("do_recv -> eagain (Length =:= 0)", []), {ok, Acc}; {error, eagain} -> - p("do_recv -> eagain", []), %% There is nothing just now, but we will be notified when there %% is something to read (a select message). NewTimeout = next_timeout(TS, Timeout), receive {select, SockRef, RecvRef, ready_input} -> - p("do_recv -> received select ready-input message", []), do_recv(SockRef, RecvRef, Length, EFlags, Acc, @@ -1317,16 +1305,21 @@ recvfrom(#socket{ref = SockRef}, BufSz, Flags, Timeout) do_recvfrom(SockRef, BufSz, EFlags, Timeout) -> TS = timestamp(Timeout), RecvRef = make_ref(), + p("recvfrom -> try recvfrom"), case nif_recvfrom(SockRef, RecvRef, BufSz, EFlags) of {ok, {_Source, _NewData}} = OK -> + p("recvfrom -> ok: " + "~n Source: ~p", [_Source]), OK; {error, eagain} -> + p("recvfrom -> eagain - wait for select ready-input"), %% There is nothing just now, but we will be notified when there %% is something to read (a select message). NewTimeout = next_timeout(TS, Timeout), receive {select, SockRef, RecvRef, ready_input} -> + p("recvfrom -> eagain - got select ready-input"), do_recvfrom(SockRef, BufSz, EFlags, next_timeout(TS, Timeout)); @@ -1339,7 +1332,8 @@ do_recvfrom(SockRef, BufSz, EFlags, Timeout) -> {error, timeout} end; - {error, _} = ERROR -> + {error, _Reason} = ERROR -> + p("recvfrom -> error: ~p", [_Reason]), ERROR end. @@ -1572,6 +1566,37 @@ getopt(#socket{info = Info, ref = SockRef}, Level, Key) -> +%% =========================================================================== +%% +%% sockname - return the current address of the socket. +%% +%% + +-spec sockname(Socket) -> {ok, SockAddr} | {error, Reason} when + Socket :: socket(), + SockAddr :: sockaddr(), + Reason :: term(). + +sockname(#socket{ref = SockRef}) -> + nif_sockname(SockRef). + + + +%% =========================================================================== +%% +%% peername - return the address of the peer *connected* to the socket. +%% +%% + +-spec peername(Socket) -> {ok, SockAddr} | {error, Reason} when + Socket :: socket(), + SockAddr :: sockaddr(), + Reason :: term(). + +peername(#socket{ref = SockRef}) -> + nif_peername(SockRef). + + %% =========================================================================== %% @@ -1970,6 +1995,8 @@ enc_sockopt_key(socket = L, sndtimeo = Opt, _Dir, _D, _T, _P) -> not_supported({L, Opt}); enc_sockopt_key(socket = L, timestamp = Opt, _Dir, _D, _T, _P) -> not_supported({L, Opt}); +enc_sockopt_key(socket = _L, type = _Opt, _Dir, _D, _T, _P) -> + ?SOCKET_OPT_SOCK_TYPE; enc_sockopt_key(socket = L, UnknownOpt, _Dir, _D, _T, _P) -> unknown({L, UnknownOpt}); @@ -2200,6 +2227,9 @@ tdiff(T1, T2) -> +p(F) -> + p(F, []). + p(F, A) -> p(get(sname), F, A). @@ -2261,7 +2291,7 @@ nif_accept(_SRef, _Ref) -> nif_send(_SockRef, _SendRef, _Data, _Flags) -> erlang:error(badarg). -nif_sendto(_SRef, _SendRef, _Data, _Flags, _DestSockAddr) -> +nif_sendto(_SRef, _SendRef, _Data, _Dest, _Flags) -> erlang:error(badarg). nif_recv(_SRef, _RecvRef, _Length, _Flags) -> @@ -2288,3 +2318,9 @@ nif_setopt(_Ref, _IsEnc, _Lev, _Key, _Val) -> nif_getopt(_Ref, _IsEnc, _Lev, _Key) -> erlang:error(badarg). +nif_sockname(_Ref) -> + erlang:error(badarg). + +nif_peername(_Ref) -> + erlang:error(badarg). + diff --git a/lib/kernel/test/Makefile b/lib/kernel/test/Makefile index 07e7922d3d..051fac25af 100644 --- a/lib/kernel/test/Makefile +++ b/lib/kernel/test/Makefile @@ -25,6 +25,7 @@ include $(ERL_TOP)/make/$(TARGET)/otp.mk # ---------------------------------------------------- SOCKET_MODULES = \ + socket_lib \ socket_server \ socket_client diff --git a/lib/kernel/test/socket_client.erl b/lib/kernel/test/socket_client.erl index a284777046..0b570e1f71 100644 --- a/lib/kernel/test/socket_client.erl +++ b/lib/kernel/test/socket_client.erl @@ -1,17 +1,34 @@ -%%%------------------------------------------------------------------- -%%% @author Micael Karlberg -%%% @copyright (C) 2018, Micael Karlberg -%%% @doc -%%% -%%% @end -%%% Created : 27 Jun 2018 by Micael Karlberg -%%%------------------------------------------------------------------- +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2018-2018. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% + -module(socket_client). --export([start/1]). +-export([ + start/1, + start_tcp/1, start_tcp/2, + start_udp/1, start_udp/2 + ]). + +-define(LIB, socket_lib). --define(REQ, 0). --define(REP, 1). +-record(client, {socket, type, dest, msg_id = 1}). start(Port) -> start_tcp(Port). @@ -19,22 +36,77 @@ start(Port) -> start_tcp(Port) -> start(inet, stream, tcp, Port). +start_tcp(Addr, Port) when (size(Addr) =:= 4) -> + start(inet, stream, tcp, Addr, Port); +start_tcp(Addr, Port) when (size(Addr) =:= 8) -> + start(inet6, stream, tcp, Addr, Port). + + +start_udp(Port) -> + start(inet, dgram, udp, Port). + +start_udp(Addr, Port) when (size(Addr) =:= 4) -> + start(inet, dgram, udp, Addr, Port); +start_udp(Addr, Port) when (size(Addr) =:= 8) -> + start(inet6, dgram, udp, Addr, Port). + + start(Domain, Type, Proto, Port) -> + start(Domain, Type, Proto, which_addr(Domain), Port). + +start(Domain, Type, Proto, Addr, Port) -> + put(sname, "starter"), + SA = #{family => Domain, + addr => Addr, + port => Port}, + do_start(Domain, Type, Proto, SA). + +do_start(Domain, stream = Type, Proto, SA) -> try do_init(Domain, Type, Proto) of Sock -> - connect(Sock, Domain, Port), + connect(Sock, SA), + i("connected: " + "~n From: ~p" + "~n To: ~p", + [ + case socket:sockname(Sock) of + {ok, Name} -> Name; + {error, _} = NE -> NE + end, + case socket:peername(Sock) of + {ok, Name} -> Name; + {error, _} = PE -> PE + end + ]), %% Give the server some time... - p("wait some", []), - %% sleep(5000), + i("wait some", []), + ?LIB:sleep(5000), %% ok = socket:close(Sock), - send_loop(Sock) + send_loop(#client{socket = Sock, + type = Type}) + catch + throw:E -> + e("Failed initiate: " + "~n Error: ~p", [E]) + end; +do_start(Domain, dgram = Type, Proto, SA) -> + try do_init(Domain, Type, Proto) of + Sock -> + %% Give the server some time... + i("wait some", []), + ?LIB:sleep(5000), + %% ok = socket:close(Sock), + send_loop(#client{socket = Sock, + type = Type, + dest = SA}) catch throw:E -> e("Failed initiate: " "~n Error: ~p", [E]) end. -do_init(Domain, Type, Proto) -> + +do_init(Domain, stream = Type, Proto) -> i("try (socket) open"), Sock = case socket:open(Domain, Type, Proto) of {ok, S} -> @@ -48,8 +120,23 @@ do_init(Domain, Type, Proto) -> Sock; {error, BReason} -> throw({bind, BReason}) + end; +do_init(Domain, dgram = Type, Proto) -> + i("try (socket) open"), + Sock = case socket:open(Domain, Type, Proto) of + {ok, S} -> + S; + {error, OReason} -> + throw({open, OReason}) + end, + case socket:bind(Sock, any) of + {ok, _} -> + Sock; + {error, BReason} -> + throw({bind, BReason}) end. + which_addr(Domain) -> Iflist = case inet:getifaddrs() of {ok, IFL} -> @@ -60,16 +147,11 @@ which_addr(Domain) -> which_addr(Domain, Iflist). -connect(Sock, Domain, Port) -> - Addr = which_addr(Domain), - SA = #{family => Domain, - addr => Addr, - port => Port}, +connect(Sock, SA) -> i("try (socket) connect to:" "~n ~p", [SA]), case socket:connect(Sock, SA) of ok -> - i("connected"), ok; {error, Reason} -> e("connect failure: " @@ -78,22 +160,23 @@ connect(Sock, Domain, Port) -> end. -send_loop(Sock) -> - send_loop(Sock, 1). - -send_loop(Sock, N) when (N =< 10) -> +send_loop(#client{msg_id = N} = C) when (N =< 10) -> i("try send request ~w", [N]), - Req = enc_req_msg(N, "hejsan"), - case socket:send(Sock, Req) of + Req = ?LIB:enc_req_msg(N, "hejsan"), + case send(C, Req) of ok -> i("request ~w sent - now try read answer", [N]), - case socket:recv(Sock, 0) of - {ok, Msg} -> - i("received ~w bytes of data", [size(Msg)]), - case dec_msg(Msg) of + case recv(C) of + {ok, {Source, Msg}} -> + i("received ~w bytes of data~s", + [size(Msg), case Source of + undefined -> ""; + _ -> ?LIB:f(" from:~n ~p", [Source]) + end]), + case ?LIB:dec_msg(Msg) of {reply, N, Reply} -> i("received reply ~w: ~p", [N, Reply]), - send_loop(Sock, N+1) + send_loop(C#client{msg_id = N+1}) end; {error, RReason} -> e("Failed recv response for request ~w: " @@ -105,7 +188,7 @@ send_loop(Sock, N) when (N =< 10) -> "~n ~p", [SReason]), exit({failed_send, SReason}) end; -send_loop(Sock, _N) -> +send_loop(#client{socket = Sock}) -> i("we are done - close the socket when: " "~n ~p", [socket:info()]), ok = socket:close(Sock), @@ -113,6 +196,25 @@ send_loop(Sock, _N) -> "~n ~p", [socket:info()]). +send(#client{socket = Sock, type = stream}, Msg) -> + socket:send(Sock, Msg); +send(#client{socket = Sock, type = dgram, dest = Dest}, Msg) -> + %% i("try send to: " + %% "~n ~p", [Dest]), + %% ok = socket:setopt(Sock, otp, debug, true), + socket:sendto(Sock, Msg, Dest). + +recv(#client{socket = Sock, type = stream}) -> + case socket:recv(Sock) of + {ok, Msg} -> + {ok, {undefined, Msg}}; + {error, _} = ERROR -> + ERROR + end; +recv(#client{socket = Sock, type = dgram}) -> + socket:recvfrom(Sock). + + which_addr(_Domain, []) -> throw(no_address); which_addr(Domain, [{Name, IFO}|_IFL]) when (Name =/= "lo") -> @@ -130,68 +232,66 @@ which_addr2(Domain, [_|IFO]) -> %% --- -enc_req_msg(N, Data) -> - enc_msg(?REQ, N, Data). +%% enc_req_msg(N, Data) -> +%% enc_msg(?REQ, N, Data). -enc_rep_msg(N, Data) -> - enc_msg(?REP, N, Data). +%% enc_rep_msg(N, Data) -> +%% enc_msg(?REP, N, Data). -enc_msg(Type, N, Data) when is_list(Data) -> - enc_msg(Type, N, list_to_binary(Data)); -enc_msg(Type, N, Data) - when is_integer(Type) andalso is_integer(N) andalso is_binary(Data) -> - <>. +%% enc_msg(Type, N, Data) when is_list(Data) -> +%% enc_msg(Type, N, list_to_binary(Data)); +%% enc_msg(Type, N, Data) +%% when is_integer(Type) andalso is_integer(N) andalso is_binary(Data) -> +%% <>. -dec_msg(<>) -> - {request, N, Data}; -dec_msg(<>) -> - {reply, N, Data}. +%% dec_msg(<>) -> +%% {request, N, Data}; +%% dec_msg(<>) -> +%% {reply, N, Data}. %% --- -sleep(T) -> - receive after T -> ok end. +%% sleep(T) -> +%% receive after T -> ok end. %% --- -formated_timestamp() -> - format_timestamp(os:timestamp()). +%% formated_timestamp() -> +%% format_timestamp(os:timestamp()). -format_timestamp(Now) -> - N2T = fun(N) -> calendar:now_to_local_time(N) end, - format_timestamp(Now, N2T, true). +%% format_timestamp(Now) -> +%% N2T = fun(N) -> calendar:now_to_local_time(N) end, +%% format_timestamp(Now, N2T, true). -format_timestamp({_N1, _N2, N3} = N, N2T, true) -> - FormatExtra = ".~.2.0w", - ArgsExtra = [N3 div 10000], - format_timestamp(N, N2T, FormatExtra, ArgsExtra); -format_timestamp({_N1, _N2, _N3} = N, N2T, false) -> - FormatExtra = "", - ArgsExtra = [], - format_timestamp(N, N2T, FormatExtra, ArgsExtra). +%% format_timestamp({_N1, _N2, N3} = N, N2T, true) -> +%% FormatExtra = ".~.2.0w", +%% ArgsExtra = [N3 div 10000], +%% format_timestamp(N, N2T, FormatExtra, ArgsExtra); +%% format_timestamp({_N1, _N2, _N3} = N, N2T, false) -> +%% FormatExtra = "", +%% ArgsExtra = [], +%% format_timestamp(N, N2T, FormatExtra, ArgsExtra). -format_timestamp(N, N2T, FormatExtra, ArgsExtra) -> - {Date, Time} = N2T(N), - {YYYY,MM,DD} = Date, - {Hour,Min,Sec} = Time, - FormatDate = - io_lib:format("~.4w-~.2.0w-~.2.0w ~.2.0w:~.2.0w:~.2.0w" ++ FormatExtra, - [YYYY, MM, DD, Hour, Min, Sec] ++ ArgsExtra), - lists:flatten(FormatDate). +%% format_timestamp(N, N2T, FormatExtra, ArgsExtra) -> +%% {Date, Time} = N2T(N), +%% {YYYY,MM,DD} = Date, +%% {Hour,Min,Sec} = Time, +%% FormatDate = +%% io_lib:format("~.4w-~.2.0w-~.2.0w ~.2.0w:~.2.0w:~.2.0w" ++ FormatExtra, +%% [YYYY, MM, DD, Hour, Min, Sec] ++ ArgsExtra), +%% lists:flatten(FormatDate). %% --- e(F, A) -> - p(" " ++ F, A). + ?LIB:e(F, A). i(F) -> - i(F, []). + ?LIB:i(F). + i(F, A) -> - p("*** " ++ F, A). - -p(F, A) -> - io:format("[client,~p][~s] " ++ F ++ "~n", [self(),formated_timestamp()|A]). + ?LIB:i(F, A). diff --git a/lib/kernel/test/socket_lib.erl b/lib/kernel/test/socket_lib.erl new file mode 100644 index 0000000000..0eed81d61a --- /dev/null +++ b/lib/kernel/test/socket_lib.erl @@ -0,0 +1,129 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2018-2018. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% + +-module(socket_lib). + +-export([ + sleep/1, + enc_req_msg/2, enc_rep_msg/2, + enc_msg/3, dec_msg/1, + request/3, reply/4, + f/2, + i/1, i/2, + e/2 + ]). + + +-define(REQ, 0). +-define(REP, 1). + + +%% --- + +sleep(T) -> + receive after T -> ok end. + + +%% --- + +enc_req_msg(N, Data) -> + enc_msg(?REQ, N, Data). + +enc_rep_msg(N, Data) -> + enc_msg(?REP, N, Data). + +enc_msg(Type, N, Data) when is_list(Data) -> + enc_msg(Type, N, list_to_binary(Data)); +enc_msg(Type, N, Data) + when is_integer(Type) andalso is_integer(N) andalso is_binary(Data) -> + <>. + +dec_msg(<>) -> + {request, N, Data}; +dec_msg(<>) -> + {reply, N, Data}. + + +%% --- + +request(Tag, Pid, Request) -> + Ref = make_ref(), + Pid ! {Tag, self(), Ref, Request}, + receive + {Tag, Pid, Ref, Reply} -> + Reply + end. + +reply(Tag, Pid, Ref, Reply) -> + Pid ! {Tag, self(), Ref, Reply}. + + +%% --- + +f(F, A) -> + lists:flatten(io_lib:format(F, A)). + + +%% --- + +e(F, A) -> + p(" " ++ F, A). + +i(F) -> + i(F, []). +i(F, A) -> + p("*** " ++ F, A). + +p(F, A) -> + p(get(sname), F, A). + +p(SName, F, A) -> + io:format("[~s,~p][~s] " ++ F ++ "~n", + [SName,self(),formated_timestamp()|A]). + + +%% --- + +formated_timestamp() -> + format_timestamp(os:timestamp()). + +format_timestamp(Now) -> + N2T = fun(N) -> calendar:now_to_local_time(N) end, + format_timestamp(Now, N2T, true). + +format_timestamp({_N1, _N2, N3} = N, N2T, true) -> + FormatExtra = ".~.2.0w", + ArgsExtra = [N3 div 10000], + format_timestamp(N, N2T, FormatExtra, ArgsExtra); +format_timestamp({_N1, _N2, _N3} = N, N2T, false) -> + FormatExtra = "", + ArgsExtra = [], + format_timestamp(N, N2T, FormatExtra, ArgsExtra). + +format_timestamp(N, N2T, FormatExtra, ArgsExtra) -> + {Date, Time} = N2T(N), + {YYYY,MM,DD} = Date, + {Hour,Min,Sec} = Time, + FormatDate = + io_lib:format("~.4w-~.2.0w-~.2.0w ~.2.0w:~.2.0w:~.2.0w" ++ FormatExtra, + [YYYY, MM, DD, Hour, Min, Sec] ++ ArgsExtra), + lists:flatten(FormatDate). + + diff --git a/lib/kernel/test/socket_server.erl b/lib/kernel/test/socket_server.erl index dde605b624..702f040434 100644 --- a/lib/kernel/test/socket_server.erl +++ b/lib/kernel/test/socket_server.erl @@ -1,21 +1,34 @@ -%%%------------------------------------------------------------------- -%%% @author Micael Karlberg -%%% @copyright (C) 2018, Micael Karlberg -%%% @doc -%%% -%%% @end -%%% Created : 27 Jun 2018 by Micael Karlberg -%%%------------------------------------------------------------------- +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2018-2018. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% + -module(socket_server). --export([start/0]). +-export([start/0, + start_tcp/0, + start_udp/0]). --define(REQ, 0). --define(REP, 1). +-define(LIB, socket_lib). --record(manager, {acceptor, handler_id, handlers}). +-record(manager, {acceptor, handler_id, handlers}). -record(acceptor, {socket, manager}). --record(handler, {socket, manager}). +-record(handler, {socket, type, manager}). start() -> start_tcp(). @@ -23,6 +36,9 @@ start() -> start_tcp() -> start(inet, stream, tcp). +start_udp() -> + start(inet, dgram, udp). + start(Domain, Type, Proto) -> put(sname, "starter"), i("try start manager"), @@ -51,13 +67,13 @@ manager_stop(Pid, Reason) -> manager_request(Pid, {stop, Reason}). manager_request(Pid, Request) -> - request(manager, Pid, Request). + ?LIB:request(manager, Pid, Request). manager_reply(Pid, Ref, Reply) -> - reply(manager, Pid, Ref, Reply). + ?LIB:reply(manager, Pid, Ref, Reply). -manager_init(Domain, Type, Proto) -> +manager_init(Domain, stream = Type, Proto) -> put(sname, "manager"), i("try start acceptor"), case acceptor_start(Domain, Type, Proto) of @@ -68,8 +84,44 @@ manager_init(Domain, Type, Proto) -> handlers = []}); {error, Reason} -> exit({failed_starting_acceptor, Reason}) + end; +manager_init(Domain, dgram = Type, Proto) -> + put(sname, "manager"), + i("try open socket"), + case socket:open(Domain, Type, Proto) of + {ok, Sock} -> + Addr = which_addr(Domain), + SA = #{family => Domain, + addr => Addr}, + case socket:bind(Sock, SA) of + {ok, _P} -> + ok; + {error, BReason} -> + throw({bind, BReason}) + end, + i("try start handler for" + "~n ~p", [case socket:sockname(Sock) of + {ok, Name} -> Name; + {error, _} = E -> E + end]), + case handler_start(1, Sock) of + {ok, {Pid, MRef}} -> + i("handler (~p) started", [Pid]), + handler_continue(Pid), + manager_loop(#manager{handler_id = 2, % Just in case + handlers = [{Pid, MRef, 1}]}); + {error, SReason} -> + e("Failed starting handler: " + "~n ~p", [SReason]), + exit({failed_start_handler, SReason}) + end; + {error, OReason} -> + e("Failed open socket: " + "~n ~p", [OReason]), + exit({failed_open_socket, OReason}) end. + manager_loop(M) -> receive {'DOWN', MRef, process, Pid, Reason} -> @@ -262,7 +314,11 @@ acceptor_loop(#acceptor{socket = LSock} = A) -> end. acceptor_handle_accept_success(#acceptor{manager = Manager}, Sock) -> - i("try start handler"), + i("try start handler for peer" + "~n ~p", [case socket:peername(Sock) of + {ok, Peer} -> Peer; + {error, _} = E -> E + end]), case manager_start_handler(Manager, Sock) of {ok, Pid} -> i("handler (~p) started - now change 'ownership'", [Pid]), @@ -309,10 +365,10 @@ handler_continue(Pid) -> handler_request(Pid, continue). handler_request(Pid, Request) -> - request(handler, Pid, Request). + ?LIB:request(handler, Pid, Request). handler_reply(Pid, Ref, Reply) -> - reply(handler, Pid, Ref, Reply). + ?LIB:reply(handler, Pid, Ref, Reply). handler_init(Manager, ID, Sock) -> @@ -321,23 +377,30 @@ handler_init(Manager, ID, Sock) -> Manager ! {handler, self(), ok}, receive {handler, Pid, Ref, continue} -> - i("continue"), + i("got continue"), handler_reply(Pid, Ref, ok), + {ok, Type} = socket:getopt(Sock, socket, type), %% socket:setopt(Socket, otp, debug, true), handler_loop(#handler{manager = Manager, + type = Type, socket = Sock}) end. -handler_loop(#handler{socket = Socket} = H) -> - case socket:recv(Socket) of - {ok, Msg} -> - i("received ~w bytes of data", [size(Msg)]), - case dec_msg(Msg) of +handler_loop(H) -> + i("try read message"), + case recv(H) of + {ok, {Source, Msg}} -> + i("received ~w bytes of data~s", + [size(Msg), case Source of + undefined -> ""; + _ -> f(" from:~n ~p", [Source]) + end]), + case ?LIB:dec_msg(Msg) of {request, N, Req} -> i("received request ~w: " "~n ~p", [N, Req]), - Reply = enc_rep_msg(N, "hoppsan"), - case socket:send(Socket, Reply) of + Reply = ?LIB:enc_rep_msg(N, "hoppsan"), + case send(H, Reply, Source) of ok -> i("successfully sent reply ~w", [N]), handler_loop(H); @@ -360,87 +423,99 @@ handler_loop(#handler{socket = Socket} = H) -> end. +recv(#handler{socket = Sock, type = stream}) -> + case socket:recv(Sock) of + {ok, Msg} -> + {ok, {undefined, Msg}}; + {error, _} = ERROR -> + ERROR + end; +recv(#handler{socket = Sock, type = dgram}) -> + %% ok = socket:setopt(Sock, otp, debug, true), + socket:recvfrom(Sock). + + +send(#handler{socket = Sock, type = stream}, Msg, _) -> + socket:send(Sock, Msg); +send(#handler{socket = Sock, type = dgram}, Msg, Dest) -> + socket:sendto(Sock, Msg, Dest). + + %% ========================================================================= -enc_req_msg(N, Data) -> - enc_msg(?REQ, N, Data). +%% enc_req_msg(N, Data) -> +%% enc_msg(?REQ, N, Data). -enc_rep_msg(N, Data) -> - enc_msg(?REP, N, Data). +%% enc_rep_msg(N, Data) -> +%% enc_msg(?REP, N, Data). -enc_msg(Type, N, Data) when is_list(Data) -> - enc_msg(Type, N, list_to_binary(Data)); -enc_msg(Type, N, Data) - when is_integer(Type) andalso is_integer(N) andalso is_binary(Data) -> - <>. +%% enc_msg(Type, N, Data) when is_list(Data) -> +%% enc_msg(Type, N, list_to_binary(Data)); +%% enc_msg(Type, N, Data) +%% when is_integer(Type) andalso is_integer(N) andalso is_binary(Data) -> +%% <>. -dec_msg(<>) -> - {request, N, Data}; -dec_msg(<>) -> - {reply, N, Data}. +%% dec_msg(<>) -> +%% {request, N, Data}; +%% dec_msg(<>) -> +%% {reply, N, Data}. %% --- -request(Tag, Pid, Request) -> - Ref = make_ref(), - Pid ! {Tag, self(), Ref, Request}, - receive - {Tag, Pid, Ref, Reply} -> - Reply - end. +%% request(Tag, Pid, Request) -> +%% Ref = make_ref(), +%% Pid ! {Tag, self(), Ref, Request}, +%% receive +%% {Tag, Pid, Ref, Reply} -> +%% Reply +%% end. -reply(Tag, Pid, Ref, Reply) -> - Pid ! {Tag, self(), Ref, Reply}. +%% reply(Tag, Pid, Ref, Reply) -> +%% Pid ! {Tag, self(), Ref, Reply}. %% --- -formated_timestamp() -> - format_timestamp(os:timestamp()). +%% formated_timestamp() -> +%% format_timestamp(os:timestamp()). -format_timestamp(Now) -> - N2T = fun(N) -> calendar:now_to_local_time(N) end, - format_timestamp(Now, N2T, true). +%% format_timestamp(Now) -> +%% N2T = fun(N) -> calendar:now_to_local_time(N) end, +%% format_timestamp(Now, N2T, true). -format_timestamp({_N1, _N2, N3} = N, N2T, true) -> - FormatExtra = ".~.2.0w", - ArgsExtra = [N3 div 10000], - format_timestamp(N, N2T, FormatExtra, ArgsExtra); -format_timestamp({_N1, _N2, _N3} = N, N2T, false) -> - FormatExtra = "", - ArgsExtra = [], - format_timestamp(N, N2T, FormatExtra, ArgsExtra). +%% format_timestamp({_N1, _N2, N3} = N, N2T, true) -> +%% FormatExtra = ".~.2.0w", +%% ArgsExtra = [N3 div 10000], +%% format_timestamp(N, N2T, FormatExtra, ArgsExtra); +%% format_timestamp({_N1, _N2, _N3} = N, N2T, false) -> +%% FormatExtra = "", +%% ArgsExtra = [], +%% format_timestamp(N, N2T, FormatExtra, ArgsExtra). -format_timestamp(N, N2T, FormatExtra, ArgsExtra) -> - {Date, Time} = N2T(N), - {YYYY,MM,DD} = Date, - {Hour,Min,Sec} = Time, - FormatDate = - io_lib:format("~.4w-~.2.0w-~.2.0w ~.2.0w:~.2.0w:~.2.0w" ++ FormatExtra, - [YYYY, MM, DD, Hour, Min, Sec] ++ ArgsExtra), - lists:flatten(FormatDate). +%% format_timestamp(N, N2T, FormatExtra, ArgsExtra) -> +%% {Date, Time} = N2T(N), +%% {YYYY,MM,DD} = Date, +%% {Hour,Min,Sec} = Time, +%% FormatDate = +%% io_lib:format("~.4w-~.2.0w-~.2.0w ~.2.0w:~.2.0w:~.2.0w" ++ FormatExtra, +%% [YYYY, MM, DD, Hour, Min, Sec] ++ ArgsExtra), +%% lists:flatten(FormatDate). %% --- f(F, A) -> - lists:flatten(io_lib:format(F, A)). + ?LIB:f(F, A). e(F, A) -> - p(" " ++ F, A). + ?LIB:e(F, A). i(F) -> - i(F, []). -i(F, A) -> - p("*** " ++ F, A). + ?LIB:i(F). -p(F, A) -> - p(get(sname), F, A). +i(F, A) -> + ?LIB:i(F, A). -p(SName, F, A) -> - io:format("[~s,~p][~s] " ++ F ++ "~n", - [SName,self(),formated_timestamp()|A]). - -- cgit v1.2.3 From eacb95d88db1e1123d17288e71835b457bcf4016 Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Wed, 4 Jul 2018 18:37:32 +0200 Subject: [socket-doc-nif] Updated documentation Updated the documentation of recv, recvfrom, send and sendto. Also added doc for functions peername and sockname. OTP-14831 --- erts/doc/src/socket.xml | 37 ++++++++-- erts/preloaded/ebin/socket.beam | Bin 42660 -> 43472 bytes erts/preloaded/src/socket.erl | 157 +++++++++++++++++++++++++++++++--------- 3 files changed, 154 insertions(+), 40 deletions(-) diff --git a/erts/doc/src/socket.xml b/erts/doc/src/socket.xml index 3efa412b8a..6f116abca9 100644 --- a/erts/doc/src/socket.xml +++ b/erts/doc/src/socket.xml @@ -63,13 +63,16 @@
- + - + - + + + + @@ -244,8 +247,18 @@ + + Get name of connected socket peer. + +

Returns the address of the peer connected to the socket.

+
+
+ + + - + + Receive a message from a socket. @@ -259,7 +272,9 @@ - + + + Receive a message from a socket. @@ -279,7 +294,8 @@ - + + Send a message on a socket. @@ -288,6 +304,7 @@ + Send a message on a socket. @@ -329,6 +346,14 @@ + + + Get socket name. + +

Returns the current address to which the socket is bound.

+
+
+
Examples diff --git a/erts/preloaded/ebin/socket.beam b/erts/preloaded/ebin/socket.beam index 99a9f0f749..8a3dab1313 100644 Binary files a/erts/preloaded/ebin/socket.beam and b/erts/preloaded/ebin/socket.beam differ diff --git a/erts/preloaded/src/socket.erl b/erts/preloaded/src/socket.erl index 79b8471e08..a1db295a79 100644 --- a/erts/preloaded/src/socket.erl +++ b/erts/preloaded/src/socket.erl @@ -88,7 +88,10 @@ udp_socket_option/0, sctp_socket_option/0, - ip_tos_flag/0 + ip_tos_flag/0, + + + msg_hdr/0 ]). %% We support only a subset of all domains. @@ -385,22 +388,23 @@ -type shutdown_how() :: read | write | read_write. +%% This is just a place-holder -record(msg_hdr, { - %% Optional address - %% On an unconnected socket this is used to specify the target - %% address for a datagram. - %% For a connected socket, this field should be specifiedset to []. - name :: list(), - - %% Scatter/gather array - iov :: [binary()], % iovec(), - - %% Ancillary (control) data - ctrl :: binary(), - - %% Unused - flags = [] :: list() + %% Optional address + %% On an unconnected socket this is used to specify the target + %% address for a datagram. + %% For a connected socket, this field should be specifiedset to []. + name :: list(), + + %% Scatter/gather array + iov :: [binary()], % iovec(), + + %% Ancillary (control) data + ctrl :: binary(), + + %% Unused + flags = [] :: list() }). -type msg_hdr() :: #msg_hdr{}. @@ -858,9 +862,25 @@ do_accept(LSockRef, SI, Timeout) -> %% send, sendto, sendmsg - send a message on a socket %% +-spec send(Socket, Data) -> ok | {error, Reason} when + Socket :: socket(), + Data :: iodata(), + Reason :: term(). + send(Socket, Data) -> send(Socket, Data, ?SOCKET_SEND_FLAGS_DEFAULT, ?SOCKET_SEND_TIMEOUT_DEFAULT). +-spec send(Socket, Data, Flags) -> ok | {error, Reason} when + Socket :: socket(), + Data :: iodata(), + Flags :: send_flags(), + Reason :: term() + ; (Socket, Data, Timeout) -> ok | {error, Reason} when + Socket :: socket(), + Data :: iodata(), + Timeout :: timeout(), + Reason :: term(). + send(Socket, Data, Flags) when is_list(Flags) -> send(Socket, Data, Flags, ?SOCKET_SEND_TIMEOUT_DEFAULT); send(Socket, Data, Timeout) -> @@ -932,14 +952,27 @@ do_send(SockRef, Data, EFlags, Timeout) -> %% --------------------------------------------------------------------------- %% +-spec sendto(Socket, Data, Dest) -> + ok | {error, Reason} when + Socket :: socket(), + Data :: binary(), + Dest :: null | sockaddr(), + Reason :: term(). + sendto(Socket, Data, Dest) -> sendto(Socket, Data, Dest, ?SOCKET_SENDTO_FLAGS_DEFAULT). +-spec sendto(Socket, Data, Dest, Flags) -> ok | {error, Reason} when + Socket :: socket(), + Data :: binary(), + Dest :: null | sockaddr(), + Flags :: send_flags(), + Reason :: term(). + sendto(Socket, Data, Dest, Flags) -> sendto(Socket, Data, Dest, Flags, ?SOCKET_SENDTO_TIMEOUT_DEFAULT). --spec sendto(Socket, Data, Dest, Flags, Timeout) -> - ok | {error, Reason} when +-spec sendto(Socket, Data, Dest, Flags, Timeout) -> ok | {error, Reason} when Socket :: socket(), Data :: binary(), Dest :: null | sockaddr(), @@ -1014,7 +1047,7 @@ do_sendto(SockRef, Data, Dest, EFlags, Timeout) -> %% -spec sendmsg(Socket, MsgHdr, Flags) -> ok | {error, Reason} when %% Socket :: socket(), -%% MsgHdr :: msg_header(), +%% MsgHdr :: msg_hdr(), %% Flags :: send_flags(), %% Reason :: term(). @@ -1106,14 +1139,38 @@ do_sendto(SockRef, Data, Dest, EFlags, Timeout) -> %% Flags - A list of "options" for the read. %% Timeout - Time-out in milliseconds. +-spec recv(Socket) -> {ok, Data} | {error, Reason} when + Socket :: socket(), + Data :: binary(), + Reason :: term(). + recv(Socket) -> recv(Socket, 0). +-spec recv(Socket, Length) -> {ok, Data} | {error, Reason} when + Socket :: socket(), + Length :: non_neg_integer(), + Data :: binary(), + Reason :: term(). + recv(Socket, Length) -> recv(Socket, Length, ?SOCKET_RECV_FLAGS_DEFAULT, ?SOCKET_RECV_TIMEOUT_DEFAULT). +-spec recv(Socket, Length, Flags) -> {ok, Data} | {error, Reason} when + Socket :: socket(), + Length :: non_neg_integer(), + Flags :: recv_flags(), + Data :: binary(), + Reason :: term() + ; (Socket, Length, Timeout) -> {ok, Data} | {error, Reason} when + Socket :: socket(), + Length :: non_neg_integer(), + Timeout :: timeout(), + Data :: binary(), + Reason :: term(). + recv(Socket, Length, Flags) when is_list(Flags) -> recv(Socket, Length, Flags, ?SOCKET_RECV_TIMEOUT_DEFAULT); recv(Socket, Length, Timeout) -> @@ -1270,14 +1327,51 @@ do_recv(_SockRef, _RecvRef, _Length, _EFlags, _Acc, _Timeout) -> %% is needed, possibly with a then adjusted buffer size. %% +-spec recvfrom(Socket) -> {ok, {Source, Data}} | {error, Reason} when + Socket :: socket(), + Source :: sockaddr() | undefined, + Data :: binary(), + Reason :: term(). + recvfrom(Socket) -> recvfrom(Socket, 0). +-spec recvfrom(Socket, BufSz) -> {ok, {Source, Data}} | {error, Reason} when + Socket :: socket(), + BufSz :: non_neg_integer(), + Source :: sockaddr() | undefined, + Data :: binary(), + Reason :: term(). + recvfrom(Socket, BufSz) -> recvfrom(Socket, BufSz, ?SOCKET_RECV_FLAGS_DEFAULT, ?SOCKET_RECV_TIMEOUT_DEFAULT). +-spec recvfrom(Socket, Flags, Timeout) -> + {ok, {Source, Data}} | {error, Reason} when + Socket :: socket(), + Flags :: recv_flags(), + Timeout :: timeout(), + Source :: sockaddr() | undefined, + Data :: binary(), + Reason :: term() + ; (Socket, BufSz, Flags) -> + {ok, {Source, Data}} | {error, Reason} when + Socket :: socket(), + BufSz :: non_neg_integer(), + Flags :: recv_flags(), + Source :: sockaddr() | undefined, + Data :: binary(), + Reason :: term() + ; (Socket, BufSz, Timeout) -> + {ok, {Source, Data}} | {error, Reason} when + Socket :: socket(), + BufSz :: non_neg_integer(), + Timeout :: timeout(), + Source :: sockaddr() | undefined, + Data :: binary(), + Reason :: term(). recvfrom(Socket, Flags, Timeout) when is_list(Flags) -> recvfrom(Socket, 0, Flags, Timeout); @@ -1286,7 +1380,8 @@ recvfrom(Socket, BufSz, Flags) when is_list(Flags) -> recvfrom(Socket, BufSz, Timeout) -> recvfrom(Socket, BufSz, ?SOCKET_RECV_FLAGS_DEFAULT, Timeout). --spec recvfrom(Socket, BufSz, Flags, Timeout) -> {ok, {Source, Data}} | {error, Reason} when +-spec recvfrom(Socket, BufSz, Flags, Timeout) -> + {ok, {Source, Data}} | {error, Reason} when Socket :: socket(), BufSz :: non_neg_integer(), Flags :: recv_flags(), @@ -1305,21 +1400,16 @@ recvfrom(#socket{ref = SockRef}, BufSz, Flags, Timeout) do_recvfrom(SockRef, BufSz, EFlags, Timeout) -> TS = timestamp(Timeout), RecvRef = make_ref(), - p("recvfrom -> try recvfrom"), case nif_recvfrom(SockRef, RecvRef, BufSz, EFlags) of {ok, {_Source, _NewData}} = OK -> - p("recvfrom -> ok: " - "~n Source: ~p", [_Source]), OK; {error, eagain} -> - p("recvfrom -> eagain - wait for select ready-input"), %% There is nothing just now, but we will be notified when there %% is something to read (a select message). NewTimeout = next_timeout(TS, Timeout), receive {select, SockRef, RecvRef, ready_input} -> - p("recvfrom -> eagain - got select ready-input"), do_recvfrom(SockRef, BufSz, EFlags, next_timeout(TS, Timeout)); @@ -1333,7 +1423,6 @@ do_recvfrom(SockRef, BufSz, EFlags, Timeout) -> end; {error, _Reason} = ERROR -> - p("recvfrom -> error: ~p", [_Reason]), ERROR end. @@ -1345,7 +1434,7 @@ do_recvfrom(SockRef, BufSz, EFlags, Timeout) -> %% -spec recvmsg(Socket, [out] MsgHdr, Flags) -> {ok, Data} | {error, Reason} when %% Socket :: socket(), -%% MsgHdr :: msg_header(), +%% MsgHdr :: msg_hdr(), %% Flags :: recv_flags(), %% Data :: binary(), %% Reason :: term(). @@ -2227,16 +2316,16 @@ tdiff(T1, T2) -> -p(F) -> - p(F, []). +%% p(F) -> +%% p(F, []). -p(F, A) -> - p(get(sname), F, A). +%% p(F, A) -> +%% p(get(sname), F, A). -p(undefined, F, A) -> - p("***", F, A); -p(SName, F, A) -> - io:format("[~s,~p] " ++ F ++ "~n", [SName, self()|A]). +%% p(undefined, F, A) -> +%% p("***", F, A); +%% p(SName, F, A) -> +%% io:format("[~s,~p] " ++ F ++ "~n", [SName, self()|A]). -- cgit v1.2.3 From 1952754950f371f43ed4dd675a42c60b3d166bd7 Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Thu, 5 Jul 2018 11:27:26 +0200 Subject: [socket-test-nif] Testing with the (recv/recvfrom) peek flag --- lib/kernel/test/socket_lib.erl | 4 + lib/kernel/test/socket_server.erl | 170 ++++++++++++++++++-------------------- 2 files changed, 84 insertions(+), 90 deletions(-) diff --git a/lib/kernel/test/socket_lib.erl b/lib/kernel/test/socket_lib.erl index 0eed81d61a..9d6524d467 100644 --- a/lib/kernel/test/socket_lib.erl +++ b/lib/kernel/test/socket_lib.erl @@ -22,6 +22,7 @@ -export([ sleep/1, + req/0, rep/0, enc_req_msg/2, enc_rep_msg/2, enc_msg/3, dec_msg/1, request/3, reply/4, @@ -43,6 +44,9 @@ sleep(T) -> %% --- +req() -> ?REQ. +rep() -> ?REP. + enc_req_msg(N, Data) -> enc_msg(?REQ, N, Data). diff --git a/lib/kernel/test/socket_server.erl b/lib/kernel/test/socket_server.erl index 702f040434..02fc6fc26d 100644 --- a/lib/kernel/test/socket_server.erl +++ b/lib/kernel/test/socket_server.erl @@ -20,29 +20,37 @@ -module(socket_server). --export([start/0, - start_tcp/0, - start_udp/0]). +-export([ + start/0, + start_tcp/0, start_tcp/1, + start_udp/0, start_udp/1 + ]). -define(LIB, socket_lib). --record(manager, {acceptor, handler_id, handlers}). +-record(manager, {peek, acceptor, handler_id, handlers}). -record(acceptor, {socket, manager}). --record(handler, {socket, type, manager}). +-record(handler, {socket, peek, type, manager}). start() -> start_tcp(). start_tcp() -> - start(inet, stream, tcp). + start_tcp(false). + +start_tcp(Peek) -> + start(inet, stream, tcp, Peek). start_udp() -> - start(inet, dgram, udp). + start_udp(false). + +start_udp(Peek) when is_boolean(Peek) -> + start(inet, dgram, udp, Peek). -start(Domain, Type, Proto) -> +start(Domain, Type, Proto, Peek) -> put(sname, "starter"), i("try start manager"), - {Pid, MRef} = manager_start(Domain, Type, Proto), + {Pid, MRef} = manager_start(Domain, Type, Proto, Peek), i("manager (~p) started", [Pid]), loop(Pid, MRef). @@ -57,8 +65,8 @@ loop(Pid, MRef) -> %% ========================================================================= -manager_start(Domain, Type, Proto) -> - spawn_monitor(fun() -> manager_init(Domain, Type, Proto) end). +manager_start(Domain, Type, Proto, Peek) -> + spawn_monitor(fun() -> manager_init(Domain, Type, Proto, Peek) end). manager_start_handler(Pid, Sock) -> manager_request(Pid, {start_handler, Sock}). @@ -73,19 +81,20 @@ manager_reply(Pid, Ref, Reply) -> ?LIB:reply(manager, Pid, Ref, Reply). -manager_init(Domain, stream = Type, Proto) -> +manager_init(Domain, stream = Type, Proto, Peek) -> put(sname, "manager"), i("try start acceptor"), case acceptor_start(Domain, Type, Proto) of {ok, {Pid, MRef}} -> i("acceptor started"), - manager_loop(#manager{acceptor = {Pid, MRef}, + manager_loop(#manager{peek = Peek, + acceptor = {Pid, MRef}, handler_id = 1, handlers = []}); {error, Reason} -> exit({failed_starting_acceptor, Reason}) end; -manager_init(Domain, dgram = Type, Proto) -> +manager_init(Domain, dgram = Type, Proto, Peek) -> put(sname, "manager"), i("try open socket"), case socket:open(Domain, Type, Proto) of @@ -104,11 +113,12 @@ manager_init(Domain, dgram = Type, Proto) -> {ok, Name} -> Name; {error, _} = E -> E end]), - case handler_start(1, Sock) of + case handler_start(1, Sock, Peek) of {ok, {Pid, MRef}} -> i("handler (~p) started", [Pid]), handler_continue(Pid), - manager_loop(#manager{handler_id = 2, % Just in case + manager_loop(#manager{peek = Peek, + handler_id = 2, % Just in case handlers = [{Pid, MRef, 1}]}); {error, SReason} -> e("Failed starting handler: " @@ -153,11 +163,12 @@ manager_handle_down(#manager{handlers = Handlers} = M, _MRef, Pid, Reason) -> M#manager{handlers = Handlers2}. -manager_handle_request(#manager{handler_id = HID, +manager_handle_request(#manager{peek = Peek, + handler_id = HID, handlers = Handlers} = M, Pid, Ref, {start_handler, Sock}) -> i("try start handler (~w)", [HID]), - case handler_start(HID, Sock) of + case handler_start(HID, Sock, Peek) of {ok, {HPid, HMRef}} -> i("handler ~w started", [HID]), manager_reply(Pid, Ref, {ok, HPid}), @@ -346,9 +357,11 @@ acceptor_handle_accept_success(#acceptor{manager = Manager}, Sock) -> %% ========================================================================= -handler_start(ID, Sock) -> +handler_start(ID, Sock, Peek) -> Self = self(), - H = {Pid, _} = spawn_monitor(fun() -> handler_init(Self, ID, Sock) end), + H = {Pid, _} = spawn_monitor(fun() -> + handler_init(Self, ID, Peek, Sock) + end), receive {handler, Pid, ok} -> {ok, H}; @@ -371,7 +384,7 @@ handler_reply(Pid, Ref, Reply) -> ?LIB:reply(handler, Pid, Ref, Reply). -handler_init(Manager, ID, Sock) -> +handler_init(Manager, ID, Peek, Sock) -> put(sname, f("handler:~w", [ID])), i("starting"), Manager ! {handler, self(), ok}, @@ -381,7 +394,8 @@ handler_init(Manager, ID, Sock) -> handler_reply(Pid, Ref, ok), {ok, Type} = socket:getopt(Sock, socket, type), %% socket:setopt(Socket, otp, debug, true), - handler_loop(#handler{manager = Manager, + handler_loop(#handler{peek = Peek, + manager = Manager, type = Type, socket = Sock}) end. @@ -423,16 +437,55 @@ handler_loop(H) -> end. -recv(#handler{socket = Sock, type = stream}) -> +recv(#handler{peek = true, socket = Sock, type = stream}) -> + peek_recv(Sock); +recv(#handler{peek = false, socket = Sock, type = stream}) -> + do_recv(Sock); +recv(#handler{peek = Peek, socket = Sock, type = dgram}) + when (Peek =:= true) -> + %% ok = socket:setopt(Sock, otp, debug, true), + RES = peek_recvfrom(Sock, 5), + %% ok = socket:setopt(Sock, otp, debug, false), + RES; +recv(#handler{peek = Peek, socket = Sock, type = dgram}) + when (Peek =:= false) -> + %% ok = socket:setopt(Sock, otp, debug, true), + socket:recvfrom(Sock). + +do_recv(Sock) -> case socket:recv(Sock) of {ok, Msg} -> {ok, {undefined, Msg}}; {error, _} = ERROR -> ERROR - end; -recv(#handler{socket = Sock, type = dgram}) -> - %% ok = socket:setopt(Sock, otp, debug, true), - socket:recvfrom(Sock). + end. + +peek_recv(Sock) -> + i("try peek on the message type (expect request)"), + Type = ?LIB:req(), + case socket:recv(Sock, 4, [peek]) of + {ok, <>} -> + i("was request - do proper recv"), + do_recv(Sock); + {error, _} = ERROR -> + ERROR + end. + +peek_recvfrom(Sock, BufSz) -> + i("try peek recvfrom with buffer size ~w", [BufSz]), + case socket:recvfrom(Sock, BufSz, [peek]) of + {ok, {_Source, Msg}} when (BufSz =:= size(Msg)) -> + %% i("we filled the buffer: " + %% "~n ~p", [Msg]), + %% It *may not* fit => try again with double size + peek_recvfrom(Sock, BufSz*2); + {ok, _} -> + %% It fits => read for real + i("we did *not* fill the buffer - do the 'real' read"), + socket:recvfrom(Sock); + {error, _} = ERROR -> + ERROR + end. send(#handler{socket = Sock, type = stream}, Msg, _) -> @@ -444,69 +497,6 @@ send(#handler{socket = Sock, type = dgram}, Msg, Dest) -> %% ========================================================================= -%% enc_req_msg(N, Data) -> -%% enc_msg(?REQ, N, Data). - -%% enc_rep_msg(N, Data) -> -%% enc_msg(?REP, N, Data). - -%% enc_msg(Type, N, Data) when is_list(Data) -> -%% enc_msg(Type, N, list_to_binary(Data)); -%% enc_msg(Type, N, Data) -%% when is_integer(Type) andalso is_integer(N) andalso is_binary(Data) -> -%% <>. - -%% dec_msg(<>) -> -%% {request, N, Data}; -%% dec_msg(<>) -> -%% {reply, N, Data}. - - - -%% --- - -%% request(Tag, Pid, Request) -> -%% Ref = make_ref(), -%% Pid ! {Tag, self(), Ref, Request}, -%% receive -%% {Tag, Pid, Ref, Reply} -> -%% Reply -%% end. - -%% reply(Tag, Pid, Ref, Reply) -> -%% Pid ! {Tag, self(), Ref, Reply}. - - -%% --- - -%% formated_timestamp() -> -%% format_timestamp(os:timestamp()). - -%% format_timestamp(Now) -> -%% N2T = fun(N) -> calendar:now_to_local_time(N) end, -%% format_timestamp(Now, N2T, true). - -%% format_timestamp({_N1, _N2, N3} = N, N2T, true) -> -%% FormatExtra = ".~.2.0w", -%% ArgsExtra = [N3 div 10000], -%% format_timestamp(N, N2T, FormatExtra, ArgsExtra); -%% format_timestamp({_N1, _N2, _N3} = N, N2T, false) -> -%% FormatExtra = "", -%% ArgsExtra = [], -%% format_timestamp(N, N2T, FormatExtra, ArgsExtra). - -%% format_timestamp(N, N2T, FormatExtra, ArgsExtra) -> -%% {Date, Time} = N2T(N), -%% {YYYY,MM,DD} = Date, -%% {Hour,Min,Sec} = Time, -%% FormatDate = -%% io_lib:format("~.4w-~.2.0w-~.2.0w ~.2.0w:~.2.0w:~.2.0w" ++ FormatExtra, -%% [YYYY, MM, DD, Hour, Min, Sec] ++ ArgsExtra), -%% lists:flatten(FormatDate). - - -%% --- - f(F, A) -> ?LIB:f(F, A). -- cgit v1.2.3 From 44cfb3d222ba4d20607af7cc654746f84ece3989 Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Tue, 19 Jun 2018 12:20:21 +0200 Subject: [socket-nif] Add doc for net module and some cleanup Added doc for the net module. Also some socket-nif cleanup. OTP-14831 --- erts/doc/src/Makefile | 6 +- erts/doc/src/net.xml | 126 +++++ erts/doc/src/ref_man.xml | 1 + erts/doc/src/specs.xml | 1 + erts/emulator/nifs/common/socket_int.h | 1 + erts/emulator/nifs/common/socket_nif.c | 870 ++------------------------------- erts/preloaded/ebin/net.beam | Bin 5928 -> 6036 bytes erts/preloaded/src/net.erl | 26 +- 8 files changed, 198 insertions(+), 833 deletions(-) create mode 100644 erts/doc/src/net.xml diff --git a/erts/doc/src/Makefile b/erts/doc/src/Makefile index 1540344fde..9a2750b751 100644 --- a/erts/doc/src/Makefile +++ b/erts/doc/src/Makefile @@ -53,7 +53,8 @@ XML_REF3_EFILES = \ erl_tracer.xml \ init.xml \ zlib.xml \ - socket.xml + socket.xml \ + net.xml XML_REF3_FILES = \ driver_entry.xml \ @@ -65,7 +66,8 @@ XML_REF3_FILES = \ erts_alloc.xml \ init.xml \ zlib.xml \ - socket.xml + socket.xml \ + net.xml XML_PART_FILES = \ part.xml diff --git a/erts/doc/src/net.xml b/erts/doc/src/net.xml new file mode 100644 index 0000000000..c022fee4f7 --- /dev/null +++ b/erts/doc/src/net.xml @@ -0,0 +1,126 @@ + + + + +
+ + 20182018 + Ericsson AB. All Rights Reserved. + + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + + + net + + + + + net.xml +
+ net + Network interface. + +

This module provides an API for the network interface.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + Get hostname. + +

Returns the name of the current host.

+
+
+ + + + + Address-to-name transaltion. + +

Address-to-name translation in a protocol-independant manner.

+

This function is the inverse of + getaddrinfo. + It converts a socket address to a corresponding host and service.

+
+
+ + + + + + + Network address and service transation. + +

Network address and service translation.

+

This function is the inverse of + getnameinfo. + It converts host and service to a corresponding socket address.

+

One of the Host and Service may be undefined + but not both.

+
+
+ + + + Mappings between network interface names and indexes. + +

Mappings between network interface names and indexes.

+
+
+ + + + Mappings between network interface index and names. + +

Mappings between network interface index and names.

+
+
+ + + + Get network interface names and indexes. + +

Get network interface names and indexes.

+
+
+ +
+ +
+ diff --git a/erts/doc/src/ref_man.xml b/erts/doc/src/ref_man.xml index da099dd5bb..ccbafb1530 100644 --- a/erts/doc/src/ref_man.xml +++ b/erts/doc/src/ref_man.xml @@ -36,6 +36,7 @@ + diff --git a/erts/doc/src/specs.xml b/erts/doc/src/specs.xml index 4f6951a44b..6c12f619b1 100644 --- a/erts/doc/src/specs.xml +++ b/erts/doc/src/specs.xml @@ -5,5 +5,6 @@ + diff --git a/erts/emulator/nifs/common/socket_int.h b/erts/emulator/nifs/common/socket_int.h index a3e54360fe..878ed513c8 100644 --- a/erts/emulator/nifs/common/socket_int.h +++ b/erts/emulator/nifs/common/socket_int.h @@ -104,6 +104,7 @@ typedef unsigned int BOOLEAN_T; */ extern ERL_NIF_TERM esock_atom_addr; extern ERL_NIF_TERM esock_atom_any; +extern ERL_NIF_TERM esock_atom_debug; extern ERL_NIF_TERM esock_atom_dgram; extern ERL_NIF_TERM esock_atom_error; extern ERL_NIF_TERM esock_atom_false; diff --git a/erts/emulator/nifs/common/socket_nif.c b/erts/emulator/nifs/common/socket_nif.c index 4efca1c72d..1534bd0766 100644 --- a/erts/emulator/nifs/common/socket_nif.c +++ b/erts/emulator/nifs/common/socket_nif.c @@ -397,29 +397,29 @@ typedef union { #define sock_accept(s, addr, len) \ make_noninheritable_handle(accept((s), (addr), (len))) -#define sock_bind(s, addr, len) bind((s), (addr), (len)) -#define sock_close(s) closesocket((s)) -#define sock_close_event(e) WSACloseEvent(e) -#define sock_connect(s, addr, len) connect((s), (addr), (len)) -#define sock_create_event(s) WSACreateEvent() -#define sock_errno() WSAGetLastError() -#define sock_getopt(s,l,o,v,ln) getsockopt((s),(l),(o),(v),(ln)) -#define sock_htons(x) htons((x)) -#define sock_htonl(x) htonl((x)) -#define sock_listen(s, b) listen((s), (b)) -#define sock_name(s, addr, len) getsockname((s), (addr), (len)) -#define sock_ntohs(x) ntohs((x)) +#define sock_bind(s, addr, len) bind((s), (addr), (len)) +#define sock_close(s) closesocket((s)) +#define sock_close_event(e) WSACloseEvent(e) +#define sock_connect(s, addr, len) connect((s), (addr), (len)) +#define sock_create_event(s) WSACreateEvent() +#define sock_errno() WSAGetLastError() +#define sock_getopt(s,l,o,v,ln) getsockopt((s),(l),(o),(v),(ln)) +#define sock_htons(x) htons((x)) +#define sock_htonl(x) htonl((x)) +#define sock_listen(s, b) listen((s), (b)) +#define sock_name(s, addr, len) getsockname((s), (addr), (len)) +#define sock_ntohs(x) ntohs((x)) #define sock_open(domain, type, proto) \ make_noninheritable_handle(socket((domain), (type), (proto))) #define sock_peer(s, addr, len) getpeername((s), (addr), (len)) #define sock_recv(s,buf,len,flag) recv((s),(buf),(len),(flag)) #define sock_recvfrom(s,buf,blen,flag,addr,alen) \ recvfrom((s),(buf),(blen),(flag),(addr),(alen)) -#define sock_send(s,buf,len,flag) send((s),(buf),(len),(flag)) +#define sock_send(s,buf,len,flag) send((s),(buf),(len),(flag)) #define sock_sendto(s,buf,blen,flag,addr,alen) \ sendto((s),(buf),(blen),(flag),(addr),(alen)) -#define sock_setopt(s,l,o,v,ln) setsockopt((s),(l),(o),(v),(ln)) -#define sock_shutdown(s, how) shutdown((s), (how)) +#define sock_setopt(s,l,o,v,ln) setsockopt((s),(l),(o),(v),(ln)) +#define sock_shutdown(s, how) shutdown((s), (how)) #define SET_BLOCKING(s) ioctlsocket(s, FIONBIO, &zero_value) @@ -1277,17 +1277,14 @@ static char* send_msg(ErlNifEnv* env, ERL_NIF_TERM msg, ErlNifPid* pid); -static BOOLEAN_T extract_item_on_load(ErlNifEnv* env, - ERL_NIF_TERM map, - ERL_NIF_TERM key, - ERL_NIF_TERM* val); - static BOOLEAN_T extract_debug_on_load(ErlNifEnv* env, - ERL_NIF_TERM map, - BOOLEAN_T def); + ERL_NIF_TERM map); static BOOLEAN_T extract_iow_on_load(ErlNifEnv* env, - ERL_NIF_TERM map, - BOOLEAN_T def); + ERL_NIF_TERM map); +static BOOLEAN_T extract_bool(ErlNifEnv* env, + ERL_NIF_TERM map, + ERL_NIF_TERM ekey, + BOOLEAN_T def); static int on_load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info); @@ -1320,7 +1317,6 @@ static const struct in6_addr in6addr_loopback = static char str_close[] = "close"; static char str_closed[] = "closed"; static char str_closing[] = "closing"; -static char str_debug[] = "debug"; static char str_false[] = "false"; static char str_global_counters[] = "global_counters"; static char str_in4_sockaddr[] = "in4_sockaddr"; @@ -1364,6 +1360,7 @@ static char str_exsend[] = "exsend"; // failed send ERL_NIF_TERM esock_atom_addr; ERL_NIF_TERM esock_atom_any; ERL_NIF_TERM esock_atom_dgram; +ERL_NIF_TERM esock_atom_debug; ERL_NIF_TERM esock_atom_error; ERL_NIF_TERM esock_atom_false; ERL_NIF_TERM esock_atom_family; @@ -1398,7 +1395,6 @@ ERL_NIF_TERM esock_atom_einval; static ERL_NIF_TERM atom_close; static ERL_NIF_TERM atom_closed; static ERL_NIF_TERM atom_closing; -static ERL_NIF_TERM atom_debug; static ERL_NIF_TERM atom_false; static ERL_NIF_TERM atom_global_counters; static ERL_NIF_TERM atom_in4_sockaddr; @@ -1471,6 +1467,8 @@ static SocketData data; * nif_recvfrom(Sock, Flags) * nif_close(Sock) * nif_shutdown(Sock, How) + * nif_sockname(Sock) + * nif_peername(Sock) * * And some functions to manipulate and retrieve socket options: * ------------------------------------------------------------- @@ -1540,7 +1538,7 @@ ERL_NIF_TERM nif_info(ErlNifEnv* env, numProtoIP, numProtoTCP, numProtoUDP, numProtoSCTP}; unsigned int lenGCnt = sizeof(gcnt) / sizeof(ERL_NIF_TERM); ERL_NIF_TERM lgcnt = MKLA(env, gcnt, lenGCnt); - ERL_NIF_TERM keys[] = {atom_debug, atom_iow, atom_global_counters}; + ERL_NIF_TERM keys[] = {esock_atom_debug, atom_iow, atom_global_counters}; ERL_NIF_TERM vals[] = {BOOL2ATOM(data.dbg), BOOL2ATOM(data.iow), lgcnt}; ERL_NIF_TERM info; unsigned int numKeys = sizeof(keys) / sizeof(ERL_NIF_TERM); @@ -1647,7 +1645,7 @@ ERL_NIF_TERM nopen(ErlNifEnv* env, SOCKET sock; HANDLE event; #ifdef HAVE_SETNS - int current_ns; + int current_ns = 0; #endif SGDBG( ("SOCKET", "nopen -> entry with" @@ -1666,6 +1664,8 @@ ERL_NIF_TERM nopen(ErlNifEnv* env, if ((sock = sock_open(domain, type, protocol)) == INVALID_SOCKET) return esock_make_error_errno(env, sock_errno()); + SGDBG( ("SOCKET", "nopen -> open success: %d\r\n", sock) ); + #ifdef HAVE_SETNS if ((netns != NULL) && !restore_network_namespace(current_ns, sock, &save_errno)) @@ -1682,6 +1682,7 @@ ERL_NIF_TERM nopen(ErlNifEnv* env, return esock_make_error_errno(env, save_errno); } + SGDBG( ("SOCKET", "nopen -> event success: %d\r\n", event) ); SET_NONBLOCKING(sock); @@ -1872,10 +1873,10 @@ ERL_NIF_TERM nif_bind(ErlNifEnv* env, eSockAddr = argv[1]; SSDBG( descP, - ("SOCKET", "nif_bind -> args:" + ("SOCKET", "nif_bind -> args when sock = %d:" "\r\n Socket: %T" "\r\n SockAddr: %T" - "\r\n", argv[0], eSockAddr) ); + "\r\n", descP->sock, argv[0], eSockAddr) ); /* Make sure we are ready * Not sure how this would even happen, but... @@ -3298,8 +3299,8 @@ ERL_NIF_TERM nfinalize_close(ErlNifEnv* env, * Disable sends and/or receives on a socket. * * Arguments: - * Socket (ref) - Points to the socket descriptor. - * How - What will be shutdown. + * [0] Socket (ref) - Points to the socket descriptor. + * [1] How - What will be shutdown. */ static @@ -5846,721 +5847,6 @@ ERL_NIF_TERM recvfrom_check_result(ErlNifEnv* env, } -/* *** decode_send_addr *** - * - * The rather odd thing about the 'toAddrP' (the **) is - * because we need to be able to return a NULL pointer, - * in the case of the dest address is the atom 'null'. - * Its possible to call the sendto function with the - * args NULL (address) and 0 (port number). - * - * This function whouls really have a char* return value - * type!! - */ -/* -static -char* decode_send_addr(ErlNifEnv* env, - int domain, - ERL_NIF_TERM addr, - int port, - SocketAddress** toAddrP, - unsigned int* toAddrLenP) -{ - if (IS_ATOM(env, addr)) { - unsigned int len; - char a[16]; // Just in case... - - / * The only acceptable value is the atom 'null' * / - - if (!(GET_ATOM_LEN(env, addr, &len) && - (len > 0) && - (len <= (sizeof("null"))))) - return ESOCK_STR_EINVAL; - - if (!GET_ATOM(env, addr, a, sizeof(a))) - return ESOCK_STR_EINVAL; - - *toAddrP = NULL; - if (strncmp(a, "null", len) == 0) - return NULL; - else - return ESOCK_STR_EINVAL; - - } else if (IS_TUPLE(env, addr)) { - / * We now know that the we have a proper address. * / - return decode_send_addr_tuple(env, domain, addr, port, - *toAddrP, toAddrLenP); - } else { - return ESOCK_STR_EINVAL; - } -} -*/ - -/* -static -char* decode_send_addr_tuple(ErlNifEnv* env, - int domain, - ERL_NIF_TERM addr, - int port, - SocketAddress* toAddrP, - unsigned int* toAddrLenP) -{ - / * We handle two different tuples: - * - size 4 (INET) - * - size 8 (INET6) - * / - - const ERL_NIF_TERM* addrt; - int addrtSz; - - if (!GET_TUPLE(env, addr, &addrtSz, &addrt)) - return ESOCK_STR_EINVAL; // PLACEHOLDER - - switch (domain) { - case AF_INET: - if (addrtSz != 4) - return ESOCK_STR_EINVAL; - break; - -#if defined(HAVE_IN6) && defined(AF_INET6) - case AF_INET6: - if (addrtSz != 8) - return ESOCK_STR_EINVAL; - break; -#endif - - default: - return str_eafnosupport; - break; - } - - return decode_address_tuple(env, domain, - addrt, port, - toAddrP, toAddrLenP); - -} -*/ - - -/* Decode an in_sockaddr(). This is either a: - * in4_sockaddr: #in4_sockaddr{} = 3 tuple => - * 1: The tag in4_sockaddr - * 2: Port number (an integer()) - * 3: The address: any | loopback | ip4_address() - * in6_sockaddr: #in6_sockaddr{} = 5 tuple => - * 1: The tag in6_sockaddr - * 2: Port number: integer() - * 3: The address: any | loopback | ip6_address() - * 4: Flow info: integer() - * 5: Scope Id: integer() - * - */ -/* -static -char* decode_in_sockaddr(ErlNifEnv* env, - ERL_NIF_TERM eSockAddr, - SocketAddress* sockAddrP, - unsigned int* addrLenP) -{ - const ERL_NIF_TERM* addrt; - int addrtSz; - char* result = NULL; - - if (!GET_TUPLE(env, eSockAddr, &addrtSz, &addrt)) - return ESOCK_STR_EINVAL; - - / * - * We use the tuple size to figure out which - * of the records this is. - * / - switch (addrtSz) { - case 3: - result = decode_in4_sockaddr(env, addrt, sockAddrP, addrLenP); - break; - -#if defined(HAVE_IN6) && defined(AF_INET6) - case 5: - result = decode_in6_sockaddr(env, addrt, sockAddrP, addrLenP); - break; -#endif - - default: - result = ESOCK_STR_EAFNOSUPPORT; - break; - } - - return result; -} -*/ - - -/* Decode an in4_sockaddr(). - * The first element should be the atom in4_sockaddr - * The second, the port number integer . - * The third and final, the ip4_address tuple. - */ -/* -static -char* decode_in4_sockaddr(ErlNifEnv* env, - const ERL_NIF_TERM* eIn4SockAddr, - SocketAddress* sockAddrP, - unsigned int* addrLenP) -{ - int port; - - / * 1: Ensure that the tuple has the correct tag: in4_sockaddr * / - if (COMPARE(atom_in4_sockaddr, eIn4SockAddr[0]) != 0) - return ESOCK_STR_EINVAL; - - / * 2: Get the port number * / - if (!GET_INT(env, eIn4SockAddr[1], &port)) - return ESOCK_STR_EINVAL; - - / * 3: Get the address. - * It can either be the atoms: any | loopback, - * or the IPv4 address tuple (size 4). - * / - if (IS_ATOM(env, eIn4SockAddr[2])) { - return decode_in4_sockaddr_atomaddr(env, eIn4SockAddr[2], port, - sockAddrP, addrLenP); - } else if (IS_TUPLE(env, eIn4SockAddr[2])) { - return decode_in4_sockaddr_addr(env, eIn4SockAddr[2], port, - sockAddrP, addrLenP); - } else { - return ESOCK_STR_EINVAL; - } -} -*/ - - -/* -static -char* decode_in4_sockaddr_atomaddr(ErlNifEnv* env, - ERL_NIF_TERM eAddr, - int port, - SocketAddress* sockAddrP, - unsigned int* addrLenP) -{ - struct in_addr addr; - - if (COMPARE(esock_atom_loopback, eAddr) == 0) { - addr.s_addr = sock_htonl(INADDR_LOOPBACK); - } else if (COMPARE(esock_atom_any, eAddr) == 0) { - addr.s_addr = sock_htonl(INADDR_ANY); - } else { - return ESOCK_STR_EINVAL; - } - - sys_memzero((char*) sockAddrP, sizeof(struct sockaddr_in)); -#ifndef NO_SA_LEN - sockAddrP->sai.sin_len = sizeof(struct sockaddr_in); -#endif - sockAddrP->in4.sin_family = AF_INET; - sockAddrP->in4.sin_port = sock_htons(port); - sockAddrP->in4.sin_addr.s_addr = addr.s_addr; - *addrLenP = sizeof(struct sockaddr_in); - - return NULL; -} -*/ - - -/* Decode an in4_sockaddr where the address field is a tuple. - * Its *supposed* to be an ip4_address (tuple). - */ -/* -static -char* decode_in4_sockaddr_addr(ErlNifEnv* env, - ERL_NIF_TERM eAddr, - int port, - SocketAddress* sockAddrP, - unsigned int* addrLenP) -{ - const ERL_NIF_TERM* ip4AddrT; - int ip4AddrTSz; - int a, v; - char addr[4]; - - / * This shall be a 4 tuple * / - if (!GET_TUPLE(env, eAddr, &ip4AddrTSz, &ip4AddrT)) - return ESOCK_STR_EINVAL; - - if (ip4AddrTSz != 4) - return ESOCK_STR_EINVAL; - - sys_memzero((char*)sockAddrP, sizeof(struct sockaddr_in)); -#ifndef NO_SA_LEN - sockAddrP->in4.sin_len = sizeof(struct sockaddr_in); -#endif - sockAddrP->in4.sin_family = AF_INET; - sockAddrP->in4.sin_port = sock_htons(port); - for (a = 0; a < 4; a++) { - if (!GET_INT(env, ip4AddrT[a], &v)) - return ESOCK_STR_EINVAL; - addr[a] = v; - } - sys_memcpy(&sockAddrP->in4.sin_addr, &addr, sizeof(addr)); - *addrLenP = sizeof(struct sockaddr_in); - - return NULL; -} -*/ - - - -/* Decode an in6_sockaddr(). - * The first element should be the atom in4_sockaddr - * The second, the port number integer . - * The third, the ip4_address tuple. - * The forth, the flowinfo integer. - * The fifth and final, the scope_id integer. - */ -/* -#if defined(HAVE_IN6) && defined(AF_INET6) -static -char* decode_in6_sockaddr(ErlNifEnv* env, - const ERL_NIF_TERM* eIn6SockAddr, - SocketAddress* sockAddrP, - unsigned int* addrLenP) -{ - int port; - unsigned int flowInfo, scopeId; - - / * 1: Ensure that the tuple has the correct tag: in6_sockaddr * / - if (COMPARE(atom_in6_sockaddr, eIn6SockAddr[0]) != 0) - return ESOCK_STR_EINVAL; - - / * 2: Get the port number * / - if (!GET_INT(env, eIn6SockAddr[1], &port)) - return ESOCK_STR_EINVAL; - - / * 4: Get the flowinfo * / - if (!GET_UINT(env, eIn6SockAddr[3], &flowInfo)) - return ESOCK_STR_EINVAL; - - / * 5: Get the scope_id * / - if (!GET_UINT(env, eIn6SockAddr[4], &scopeId)) - return ESOCK_STR_EINVAL; - - / * 3: Get the address. - * It can either be the atoms: any | loopback, - * or the IPv6 address tuple (size 8). - * / - if (IS_ATOM(env, eIn6SockAddr[2])) { - return decode_in6_sockaddr_atomaddr(env, eIn6SockAddr[2], port, - flowInfo, scopeId, - sockAddrP, addrLenP); - } else if (IS_TUPLE(env, eIn6SockAddr[2])) { - return decode_in6_sockaddr_addr(env, eIn6SockAddr[2], port, - flowInfo, scopeId, - sockAddrP, addrLenP); - } else { - return ESOCK_STR_EINVAL; - } -} -#endif -*/ - - -/* -#if defined(HAVE_IN6) && defined(AF_INET6) -static -char* decode_in6_sockaddr_atomaddr(ErlNifEnv* env, - ERL_NIF_TERM eAddr, - int port, - unsigned int flowInfo, - unsigned int scopeId, - SocketAddress* sockAddrP, - unsigned int* addrLenP) -{ - const struct in6_addr* addr; - - if (COMPARE(esock_atom_loopback, eAddr) == 0) { - addr = &in6addr_loopback; - } else if (COMPARE(esock_atom_any, eAddr) == 0) { - addr = &in6addr_any; - } else { - return ESOCK_STR_EINVAL; - } - - sys_memzero((char*)sockAddrP, sizeof(struct sockaddr_in6)); -#ifndef NO_SA_LEN - sockAddrP->in6.sin6_len = sizeof(struct sockaddr_in6); -#endif - sockAddrP->in6.sin6_family = AF_INET6; - sockAddrP->in6.sin6_port = sock_htons(port); - sockAddrP->in6.sin6_flowinfo = flowInfo; - sockAddrP->in6.sin6_scope_id = scopeId; - sockAddrP->in6.sin6_addr = *addr; - *addrLenP = sizeof(struct sockaddr_in6); - - return NULL; -} -#endif -*/ - - - -#if defined(HAVE_IN6) && defined(AF_INET6) -/* Decode an in6_sockaddr where the address field is a tuple */ -/* -static -char* decode_in6_sockaddr_addr(ErlNifEnv* env, - ERL_NIF_TERM eAddr, - int port, - unsigned int flowInfo, - unsigned int scopeId, - SocketAddress* sockAddrP, - unsigned int* addrLenP) -{ - const ERL_NIF_TERM* ip6AddrT; - int ip6AddrTSz; - int a, v; - char addr[16]; - - / * This shall be a 8 tuple * / - if (!GET_TUPLE(env, eAddr, &ip6AddrTSz, &ip6AddrT)) - return ESOCK_STR_EINVAL; - - if (ip6AddrTSz != 8) - return ESOCK_STR_EINVAL; - - sys_memzero((char*)sockAddrP, sizeof(struct sockaddr_in6)); -#ifndef NO_SA_LEN - sockAddrP->in6.sin6_len = sizeof(struct sockaddr_in6); -#endif - sockAddrP->in6.sin6_family = AF_INET6; - sockAddrP->in6.sin6_port = sock_htons(port); - sockAddrP->in6.sin6_flowinfo = flowInfo; - sockAddrP->in6.sin6_scope_id = scopeId; - / * The address tuple is of size 8 - * and each element is a two byte integer - * / - for (a = 0; a < 8; a++) { - if (!GET_INT(env, ip6AddrT[a], &v)) - return ESOCK_STR_EINVAL; - addr[a*2 ] = ((v >> 8) & 0xFF); - addr[a*2+1] = (v & 0xFF); - } - sys_memcpy(&sockAddrP->in6.sin6_addr, &addr, sizeof(addr)); - *addrLenP = sizeof(struct sockaddr_in6); - - return NULL; -} -*/ -#endif - - -/* Decode the 4- or 8-element address tuple - * and initiate the socket address structure. - */ -/* -static -char* decode_address_tuple(ErlNifEnv* env, - int domain, - const ERL_NIF_TERM* addrt, - int port, - SocketAddress* addrP, - unsigned int* addrLenP) -{ - - / * We now *know* that the size of the tuple is correct, - * so we don't need to check anything here, just unpack. - * / - - switch (domain) { - case AF_INET: - { - int a, v; - char laddr[4]; - - sys_memzero((char*)addrP, sizeof(struct sockaddr_in)); -#ifndef NO_SA_LEN - addrP->sai.sin_len = sizeof(struct sockaddr_in); -#endif - addrP->sai.sin_family = domain; - addrP->sai.sin_port = sock_htons(port); - for (a = 0; a < 4; a++) { - if (!GET_INT(env, addrt[a], &v)) - return ESOCK_STR_EINVAL; - laddr[a] = v; - } - sys_memcpy(&addrP->sai.sin_addr, &laddr, sizeof(laddr)); - *addrLenP = sizeof(struct sockaddr_in); - return NULL; - } - break; - -#if defined(HAVE_IN6) && defined(AF_INET6) - case AF_INET6: - { - int a, v; - char laddr[16]; - - sys_memzero((char*)addrP, sizeof(struct sockaddr_in6)); -#ifndef NO_SA_LEN - addrP->sai6.sin6_len = sizeof(struct sockaddr_in6); -#endif - addrP->sai6.sin6_family = domain; - addrP->sai6.sin6_port = sock_htons(port); - addrP->sai6.sin6_flowinfo = 0; - / * The address tuple is of size 8 - * and each element is a two byte integer - * / - for (a = 0; a < 8; a++) { - if (!GET_INT(env, addrt[a], &v)) - return ESOCK_STR_EINVAL; - laddr[a*2 ] = ((v >> 8) & 0xFF); - laddr[a*2+1] = (v & 0xFF); - } - sys_memcpy(&addrP->sai6.sin6_addr, &laddr, sizeof(laddr)); - *addrLenP = sizeof(struct sockaddr_in6); - return NULL; - } - break; -#endif - - } / * switch (domain) * / - - return ESOCK_STR_EAFNOSUPPORT; - -} -*/ - -/* Encode the 4- or 8-element address tuple from the socket address structure. - * - * This function is called when we have received a message. So, if we for some - * reason fail to decode the address or parts of it, it makes more sense to - * return with "undefined" for the values rather then fail completely (and not - * deliver the received message). - * - * Returns two things (assuming the encode works): - * - * Domain: inet | inet6 | local - * Source: {Address, Port} | string() - * - */ -/* -static -void encode_address(ErlNifEnv* env, - SocketAddress* sockAddrP, - unsigned int addrLen, - ERL_NIF_TERM* domainT, - ERL_NIF_TERM* sourceT) -{ - short port; - - switch (sockAddrP->sa.sa_family) { - - / * +++ inet (IPv4) +++ * / - - case AF_INET: - if (addrLen >= sizeof(struct sockaddr_in)) { - ERL_NIF_TERM addrT, portT; - unsigned int i; - ERL_NIF_TERM at4[4]; - char* a4 = (char*) &sockAddrP->in4.sin_addr; - - port = sock_ntohs(sockAddrP->in4.sin_port); - for (i = 0; i < 4; i++) { - at4[i] = MKI(env, a4[i]); - } - - *domainT = MKA(env, "inet"); // Shall we encode these? See decode - addrT = MKT4(env, at4[0], at4[1], at4[2], at4[3]); - portT = MKI(env, port); - *sourceT = MKT2(env, addrT, portT); - } else { - *domainT = esock_atom_undefined; - *sourceT = esock_atom_undefined; - } - break; - - - / * +++ inet6 (IPv6) +++ * / - -#if defined(HAVE_IN6) && defined(AF_INET6) - case AF_INET6: - if (addrLen >= sizeof(struct sockaddr_in6)) { - ERL_NIF_TERM addrT, portT; - unsigned int i; - ERL_NIF_TERM at6[8]; - char* a16 = (char*) &sockAddrP->in6.sin6_addr; - - port = sock_ntohs(sockAddrP->in6.sin6_port); - / * The address tuple is of size 8 - * and each element is a two byte integer - * / - for (i = 0; i < 8; i++) { - // at6[i] = MKI(env, get_int16(a16[i*2])); - at6[i] = MKI(env, get_int16(a16 + i*2)); - } - - *domainT = MKA(env, "inet6"); // Shall we encode these? See decode - addrT = MKT8(env, - at6[0], at6[1], at6[2], at6[3], - at6[4], at6[5], at6[6], at6[7]); - portT = MKI(env, port); - *sourceT = MKT2(env, addrT, portT); - } else { - *domainT = esock_atom_undefined; - *sourceT = esock_atom_undefined; - } - break; -#endif - - / * +++ local (Unix Domain Sockets) +++ * / - -#ifdef HAVE_SYS_UN_H - case AF_UNIX: - { - size_t n, m; - - *domainT = MKA(env, "local"); - if (addrLen < offsetof(struct sockaddr_un, sun_path)) { - *sourceT = esock_atom_undefined; - } else { - n = addrLen - offsetof(struct sockaddr_un, sun_path); - if (255 < n) { - *sourceT = esock_atom_undefined; - } else { - m = my_strnlen(sockAddrP->un.sun_path, n); -#ifdef __linux__ - / * Assume that the address is a zero terminated string, - * except when the first byte is \0 i.e the string length is 0, - * then use the reported length instead. - * This fix handles Linux's nonportable - * abstract socket address extension. - * / - if (m == 0) { - m = n; - } -#endif - - *sourceT = MKSL(env, sockAddrP->un.sun_path, m); - } - } - } - break; -#endif - - default: - *domainT = esock_atom_undefined; - *sourceT = esock_atom_undefined; - break; - - } / * switch (addrP->sa.sa_family) * / - -} -*/ - - -/* Decode the address when its an atom. - * Currently we only accept two atoms: 'any' and 'loopback' - */ -/* -static -char* decode_address_atom(ErlNifEnv* env, - int domain, - char* addr, - int addrLen, - int port, - SocketAddress* addrP, - unsigned int* addrLenP) -{ - BOOLEAN_T any; - - if (strncmp(addr, "any", addrLen) == 0) { - any = TRUE; - } if (strncmp(addr, "loopback", addrLen) == 0) { - any = FALSE; - } else { - return ESOCK_STR_EINVAL; - } - - / * If we get this far, we *know* its either 'any' or 'loopback' * / - - switch (domain) { - case AF_INET: - { - struct in_addr addr; - if (any) { - addr.s_addr = sock_htonl(INADDR_ANY); - } else { - addr.s_addr = sock_htonl(INADDR_LOOPBACK); - } - sys_memzero((char*) addrP, sizeof(struct sockaddr_in)); -#ifndef NO_SA_LEN - addrP->sai.sin_len = sizeof(struct sockaddr_in6); -#endif - addrP->sai.sin_family = domain; - addrP->sai.sin_port = sock_htons(port); - addrP->sai.sin_addr.s_addr = addr.s_addr; - *addrLenP = sizeof(struct sockaddr_in); - } - break; - -#if defined(HAVE_IN6) && defined(AF_INET6) - case AF_INET6: - { - const struct in6_addr* paddr; - if (any) { - paddr = &in6addr_any; - } else { - paddr = &in6addr_loopback; - } - sys_memzero((char*)addrP, sizeof(struct sockaddr_in6)); -#ifndef NO_SA_LEN - addrP->sai6.sin6_len = sizeof(struct sockaddr_in6); -#endif - addrP->sai6.sin6_family = domain; - addrP->sai6.sin6_port = sock_htons(port); - addrP->sai6.sin6_flowinfo = 0; - addrP->sai6.sin6_addr = *paddr; - *addrLenP = sizeof(struct sockaddr_in6); - } - break; -#endif - - default: - return ESOCK_STR_EINVAL; - break; - } - - return NULL; -} -*/ - -/* -static -BOOLEAN_T decode_bool(ErlNifEnv* env, ERL_NIF_TERM eVal, BOOLEAN_T* val) -{ - unsigned int len; - char b[16]; // Just in case... - - / * Verify that the value is actually an atom * / - if (!IS_ATOM(env, eVal)) - return FALSE; - - / * Verify that the value is of acceptable length * / - if (!(GET_ATOM_LEN(env, eVal, &len) && - (len > 0) && - (len <= sizeof("false")))) - return FALSE; - - / * And finally try to extract the value * / - if (!GET_ATOM(env, eVal, b, sizeof(b))) - return FALSE; - - if (strncmp(b, "true", len) == 0) - *val = TRUE; - else - *val = FALSE; - - return TRUE; -} -*/ - /* +++ decode the linger value +++ * The (socket) linger option is provided as a two tuple: @@ -8055,88 +7341,40 @@ ErlNifFunc socket_funcs[] = static -BOOLEAN_T extract_item_on_load(ErlNifEnv* env, - ERL_NIF_TERM map, - ERL_NIF_TERM key, - ERL_NIF_TERM* val) +BOOLEAN_T extract_debug_on_load(ErlNifEnv* env, + ERL_NIF_TERM map) { - if (!enif_is_map(env, map)) - return FALSE; - - if (!GET_MAP_VAL(env, map, key, val)) - return FALSE; - - return TRUE; + return extract_bool(env, map, esock_atom_debug, SOCKET_NIF_DEBUG_DEFAULT); } static -BOOLEAN_T extract_debug_on_load(ErlNifEnv* env, ERL_NIF_TERM map, BOOLEAN_T def) +BOOLEAN_T extract_iow_on_load(ErlNifEnv* env, + ERL_NIF_TERM map) { - ERL_NIF_TERM dbgKey = enif_make_atom(env, "debug"); - ERL_NIF_TERM dbgVal; - unsigned int len; - char d[16]; // Just in case... - - /* Extra the value of the debug property */ - if (!extract_item_on_load(env, map, dbgKey, &dbgVal)) - return def; + return extract_bool(env, map, atom_iow, SOCKET_NIF_IOW_DEFAULT); +} - /* Verify that the value is actually an atom */ - if (!enif_is_atom(env, dbgVal)) - return def; +static +BOOLEAN_T extract_bool(ErlNifEnv* env, + ERL_NIF_TERM map, + ERL_NIF_TERM ekey, + BOOLEAN_T def) +{ + ERL_NIF_TERM eval; - /* Verify that the value is of acceptable length */ - if (!(GET_ATOM_LEN(env, dbgVal, &len) && - (len > 0) && - (len <= sizeof("false")))) + if (!GET_MAP_VAL(env, map, ekey, &eval)) return def; - /* And finally try to extract the value */ - if (!GET_ATOM(env, dbgVal, d, sizeof(d))) + if (!IS_ATOM(env, eval)) return def; - if (strncmp(d, "true", len) == 0) + if (COMPARE(eval, esock_atom_true) == 0) return TRUE; else return FALSE; - } -static -BOOLEAN_T extract_iow_on_load(ErlNifEnv* env, ERL_NIF_TERM map, BOOLEAN_T def) -{ - ERL_NIF_TERM iowKey = enif_make_atom(env, "iow"); - ERL_NIF_TERM iowVal; - unsigned int len; - char b[16]; // Just in case... - - /* Extra the value of the debug property */ - if (!extract_item_on_load(env, map, iowKey, &iowVal)) - return def; - - /* Verify that the value is actually an atom */ - if (!enif_is_atom(env, iowVal)) - return def; - - /* Verify that the value is of acceptable length */ - if (!(GET_ATOM_LEN(env, iowVal, &len) && - (len > 0) && - (len <= sizeof("false")))) - return def; - - /* And finally try to extract the value */ - if (!GET_ATOM(env, iowVal, b, sizeof(b))) - return def; - - if (strncmp(b, "true", len) == 0) - return TRUE; - else - return FALSE; - - } - - /* ======================================================================= * load_info - A map of misc info (e.g global debug) @@ -8145,10 +7383,8 @@ BOOLEAN_T extract_iow_on_load(ErlNifEnv* env, ERL_NIF_TERM map, BOOLEAN_T def) static int on_load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info) { - data.dbg = extract_debug_on_load(env, load_info, - SOCKET_NIF_DEBUG_DEFAULT); - data.iow = extract_iow_on_load(env, load_info, - SOCKET_NIF_IOW_DEFAULT); + data.dbg = extract_debug_on_load(env, load_info); + data.iow = extract_iow_on_load(env, load_info); /* +++ Global Counters +++ */ data.cntMtx = MCREATE("socket[gcnt]"); @@ -8168,7 +7404,6 @@ int on_load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info) atom_close = MKA(env, str_close); atom_closed = MKA(env, str_closed); atom_closing = MKA(env, str_closing); - atom_debug = MKA(env, str_debug); atom_false = MKA(env, str_false); atom_global_counters = MKA(env, str_global_counters); atom_in4_sockaddr = MKA(env, str_in4_sockaddr); @@ -8193,6 +7428,7 @@ int on_load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info) /* Global atom(s) */ esock_atom_addr = MKA(env, "addr"); esock_atom_any = MKA(env, "any"); + esock_atom_debug = MKA(env, "debug"); esock_atom_dgram = MKA(env, "dgram"); esock_atom_error = MKA(env, "error"); esock_atom_false = MKA(env, "false"); diff --git a/erts/preloaded/ebin/net.beam b/erts/preloaded/ebin/net.beam index c3d043edfa..b52fa01b07 100644 Binary files a/erts/preloaded/ebin/net.beam and b/erts/preloaded/ebin/net.beam differ diff --git a/erts/preloaded/src/net.erl b/erts/preloaded/src/net.erl index 6abfc22d3f..01b12696e9 100644 --- a/erts/preloaded/src/net.erl +++ b/erts/preloaded/src/net.erl @@ -69,17 +69,10 @@ idna_use_std3_ascii_rules. -type name_info() :: #{host := string(), service := string()}. -%% -record(name_info, {host, service}). -%% -type name_info() :: #name_info{}. -type address_info() :: #{family := socket:domain(), socktype := socket:type(), protocol := socket:protocol(), address := socket:sockaddr()}. -%% -record(address_info, {family :: socket:domain(), -%% socktype :: socket:type(), -%% protocol :: socket:protocol(), -%% addr :: undefined | socket:in_sockaddr() | string()}). -%% -type address_info() :: #address_info{}. -type network_interface_name() :: string(). -type network_interface_index() :: non_neg_integer(). @@ -218,6 +211,11 @@ getaddrinfo(Host) when is_list(Host) -> ; (undefined, Service) -> {ok, Info} | {error, Reason} when Service :: string(), Info :: [address_info()], + Reason :: term() + ; (Host, Service) -> {ok, Info} | {error, Reason} when + Host :: string(), + Service :: string(), + Info :: [address_info()], Reason :: term(). getaddrinfo(Host, Service) @@ -236,8 +234,8 @@ getaddrinfo(Host, Service) %% -spec if_name2index(Name) -> {ok, Idx} | {error, Reason} when - Name :: string(), - Idx :: non_neg_integer(), + Name :: network_interface_name(), + Idx :: network_interface_index(), Reason :: term(). if_name2index(If) when is_list(If) -> @@ -247,14 +245,14 @@ if_name2index(If) when is_list(If) -> %% =========================================================================== %% -%% if_index2name - Mappings between network interface names and indexes: +%% if_index2name - Mappings between network interface index and names: %% idx -> name %% %% -spec if_index2name(Idx) -> {ok, Name} | {error, Reason} when - Idx :: non_neg_integer(), - Name :: string(), + Idx :: network_interface_index(), + Name :: network_interface_name(), Reason :: term(). if_index2name(Idx) when is_integer(Idx) -> @@ -270,8 +268,8 @@ if_index2name(Idx) when is_integer(Idx) -> -spec if_names() -> Names | {error, Reason} when Names :: [{Idx, If}], - Idx :: non_neg_integer(), - If :: string(), + Idx :: network_interface_index(), + If :: network_interface_name(), Reason :: term(). if_names() -> -- cgit v1.2.3 From 6632cb103336786f3ca12f9d1f1d3338fc76c237 Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Wed, 20 Jun 2018 12:33:18 +0200 Subject: [net-nif] Changed return type of getaddrinfo The returned address info was supposed to be a map, by was instead (still) a record. OTP-14831 --- erts/emulator/nifs/common/net_nif.c | 72 +++++++++++++++++---------------- erts/emulator/nifs/common/socket_int.h | 2 + erts/emulator/nifs/common/socket_nif.c | 4 ++ erts/emulator/nifs/common/socket_util.c | 43 +++++++++++++++++++- erts/emulator/nifs/common/socket_util.h | 5 +++ 5 files changed, 90 insertions(+), 36 deletions(-) diff --git a/erts/emulator/nifs/common/net_nif.c b/erts/emulator/nifs/common/net_nif.c index 572813ac62..6829de509b 100644 --- a/erts/emulator/nifs/common/net_nif.c +++ b/erts/emulator/nifs/common/net_nif.c @@ -324,11 +324,12 @@ static ERL_NIF_TERM encode_address_info_type(ErlNifEnv* env, static ERL_NIF_TERM encode_address_info_proto(ErlNifEnv* env, int proto); -static ERL_NIF_TERM make_address_info(ErlNifEnv* env, - ERL_NIF_TERM fam, - ERL_NIF_TERM sockType, - ERL_NIF_TERM proto, - ERL_NIF_TERM addr); +static char* make_address_info(ErlNifEnv* env, + ERL_NIF_TERM fam, + ERL_NIF_TERM sockType, + ERL_NIF_TERM proto, + ERL_NIF_TERM addr, + ERL_NIF_TERM* ai); static int on_load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info); @@ -1245,6 +1246,7 @@ BOOLEAN_T decode_addrinfo_string(ErlNifEnv* env, BOOLEAN_T result; if (IS_ATOM(env, eString)) { + if (COMPARE(eString, esock_atom_undefined) == 0) { *stringP = NULL; result = TRUE; @@ -1252,28 +1254,11 @@ BOOLEAN_T decode_addrinfo_string(ErlNifEnv* env, *stringP = NULL; result = FALSE; } - } else { - unsigned int len; - char* bufP; - if (!GET_LIST_LEN(env, eString, &len) && (len != 0)) { - *stringP = NULL; - result = FALSE; - } + } else { - NDBG( ("NET", "decode_addrinfo_string -> len: %d\r\n", len) ); - - bufP = MALLOC(len + 1); // We shall NULL-terminate + result = esock_decode_string(env, eString, stringP); - if (GET_STR(env, eString, bufP, len+1)) { - NDBG( ("NET", "decode_addrinfo_string -> buf: %s\r\n", bufP) ); - // bufP[len] = '\0'; - *stringP = bufP; - result = TRUE; - } else { - *stringP = NULL; - result = FALSE; - } } return result; @@ -1373,7 +1358,7 @@ static ERL_NIF_TERM encode_address_info(ErlNifEnv* env, struct addrinfo* addrInfoP) { - ERL_NIF_TERM result, fam, type, proto, addr; + ERL_NIF_TERM fam, type, proto, addr, addrInfo; fam = encode_address_info_family(env, addrInfoP->ai_family); type = encode_address_info_type(env, addrInfoP->ai_socktype); @@ -1383,9 +1368,10 @@ ERL_NIF_TERM encode_address_info(ErlNifEnv* env, addrInfoP->ai_addrlen, &addr); - result = make_address_info(env, fam, type, proto, addr); - - return result; + if (make_address_info(env, fam, type, proto, addr, &addrInfo) == NULL) + return addrInfo; + else + return esock_atom_undefined; // We should to better... } @@ -1401,7 +1387,7 @@ ERL_NIF_TERM encode_address_info_family(ErlNifEnv* env, { ERL_NIF_TERM efam; - if (NULL != esock_encode_type(env, family, &efam)) + if (NULL != esock_encode_domain(env, family, &efam)) efam = MKI(env, family); return efam; @@ -1448,13 +1434,29 @@ ERL_NIF_TERM encode_address_info_proto(ErlNifEnv* env, static -ERL_NIF_TERM make_address_info(ErlNifEnv* env, - ERL_NIF_TERM fam, - ERL_NIF_TERM sockType, - ERL_NIF_TERM proto, - ERL_NIF_TERM addr) +char* make_address_info(ErlNifEnv* env, + ERL_NIF_TERM fam, + ERL_NIF_TERM sockType, + ERL_NIF_TERM proto, + ERL_NIF_TERM addr, + ERL_NIF_TERM* ai) { - return MKT5(env, atom_address_info, fam, sockType, proto, addr); + ERL_NIF_TERM keys[] = {esock_atom_family, + esock_atom_type, + esock_atom_protocol, + esock_atom_addr}; + ERL_NIF_TERM vals[] = {fam, sockType, proto, addr}; + unsigned int numKeys = sizeof(keys) / sizeof(ERL_NIF_TERM); + unsigned int numVals = sizeof(vals) / sizeof(ERL_NIF_TERM); + + ESOCK_ASSERT( (numKeys == numVals) ); + + if (!MKMA(env, keys, vals, numKeys, ai)) { + *ai = esock_atom_undefined; + return ESOCK_STR_EINVAL; + } else { + return NULL; + } } diff --git a/erts/emulator/nifs/common/socket_int.h b/erts/emulator/nifs/common/socket_int.h index 878ed513c8..aa10260134 100644 --- a/erts/emulator/nifs/common/socket_int.h +++ b/erts/emulator/nifs/common/socket_int.h @@ -119,6 +119,7 @@ extern ERL_NIF_TERM esock_atom_loopback; extern ERL_NIF_TERM esock_atom_ok; extern ERL_NIF_TERM esock_atom_path; extern ERL_NIF_TERM esock_atom_port; +extern ERL_NIF_TERM esock_atom_protocol; extern ERL_NIF_TERM esock_atom_raw; extern ERL_NIF_TERM esock_atom_rdm; extern ERL_NIF_TERM esock_atom_scope_id; @@ -127,6 +128,7 @@ extern ERL_NIF_TERM esock_atom_seqpacket; extern ERL_NIF_TERM esock_atom_stream; extern ERL_NIF_TERM esock_atom_tcp; extern ERL_NIF_TERM esock_atom_true; +extern ERL_NIF_TERM esock_atom_type; extern ERL_NIF_TERM esock_atom_udp; extern ERL_NIF_TERM esock_atom_undefined; diff --git a/erts/emulator/nifs/common/socket_nif.c b/erts/emulator/nifs/common/socket_nif.c index 1534bd0766..c23f0b021d 100644 --- a/erts/emulator/nifs/common/socket_nif.c +++ b/erts/emulator/nifs/common/socket_nif.c @@ -1373,6 +1373,7 @@ ERL_NIF_TERM esock_atom_local; ERL_NIF_TERM esock_atom_loopback; ERL_NIF_TERM esock_atom_ok; ERL_NIF_TERM esock_atom_path; +ERL_NIF_TERM esock_atom_protocol; ERL_NIF_TERM esock_atom_port; ERL_NIF_TERM esock_atom_raw; ERL_NIF_TERM esock_atom_rdm; @@ -1382,6 +1383,7 @@ ERL_NIF_TERM esock_atom_seqpacket; ERL_NIF_TERM esock_atom_stream; ERL_NIF_TERM esock_atom_tcp; ERL_NIF_TERM esock_atom_true; +ERL_NIF_TERM esock_atom_type; ERL_NIF_TERM esock_atom_udp; ERL_NIF_TERM esock_atom_undefined; ERL_NIF_TERM esock_atom_unknown; @@ -7443,6 +7445,7 @@ int on_load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info) esock_atom_ok = MKA(env, "ok"); esock_atom_path = MKA(env, "path"); esock_atom_port = MKA(env, "port"); + esock_atom_protocol = MKA(env, "protocol"); esock_atom_raw = MKA(env, "raw"); esock_atom_rdm = MKA(env, "rdm"); esock_atom_scope_id = MKA(env, "scope_id"); @@ -7451,6 +7454,7 @@ int on_load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info) esock_atom_stream = MKA(env, "stream"); esock_atom_tcp = MKA(env, "tcp"); esock_atom_true = MKA(env, "true"); + esock_atom_type = MKA(env, "type"); esock_atom_udp = MKA(env, "udp"); esock_atom_undefined = MKA(env, "undefined"); esock_atom_unknown = MKA(env, "unknown"); diff --git a/erts/emulator/nifs/common/socket_util.c b/erts/emulator/nifs/common/socket_util.c index 397f69f58d..5014688852 100644 --- a/erts/emulator/nifs/common/socket_util.c +++ b/erts/emulator/nifs/common/socket_util.c @@ -890,7 +890,7 @@ char* esock_decode_type(ErlNifEnv* env, -/* +++ esock_decode_domain +++ +/* +++ esock_decode_type +++ * * Encode the native type to the Erlang form, that is: * @@ -1038,6 +1038,47 @@ char* esock_decode_protocol(ErlNifEnv* env, +/* *** esock_decode_string *** + * + * Decode a string value. A successful decode results in an + * allocation of the string, which the caller has to free + * once the string has been used. + */ +extern +BOOLEAN_T esock_decode_string(ErlNifEnv* env, + const ERL_NIF_TERM eString, + char** stringP) +{ + BOOLEAN_T result; + unsigned int len; + char* bufP; + + if (!GET_LIST_LEN(env, eString, &len) && (len != 0)) { + *stringP = NULL; + result = FALSE; + } else { + + UDBG( ("SUTIL", "esock_decode_string -> len: %d\r\n", len) ); + + bufP = MALLOC(len + 1); // We shall NULL-terminate + + if (GET_STR(env, eString, bufP, len+1)) { + UDBG( ("SUTIL", "esock_decode_string -> buf: %s\r\n", bufP) ); + // bufP[len] = '\0'; + *stringP = bufP; + result = TRUE; + } else { + *stringP = NULL; + result = FALSE; + FREE(bufP); + } + } + + return result; +} + + + /* *** esock_decode_bool *** * * Decode a boolean value. diff --git a/erts/emulator/nifs/common/socket_util.h b/erts/emulator/nifs/common/socket_util.h index add2c8f4be..cd8cc7e1fb 100644 --- a/erts/emulator/nifs/common/socket_util.h +++ b/erts/emulator/nifs/common/socket_util.h @@ -133,6 +133,11 @@ char* esock_encode_protocol(ErlNifEnv* env, int type, ERL_NIF_TERM* eProtocol); +extern +BOOLEAN_T esock_decode_string(ErlNifEnv* env, + const ERL_NIF_TERM eString, + char** stringP); + extern BOOLEAN_T esock_decode_bool(ERL_NIF_TERM val); extern -- cgit v1.2.3 From d45e3bf3dfeda0e849b07a9a7a19e50a52b04c35 Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Mon, 9 Jul 2018 14:10:35 +0200 Subject: [socket-nif] Add support for socket (level socket) options domain and protocol Make it possible to *get* the socket options domain and protocol (in addition to type). OTP-14831 --- erts/emulator/nifs/common/socket_nif.c | 130 +++++++++++++++++++++++++++++++-- erts/preloaded/ebin/socket.beam | Bin 43472 -> 43604 bytes erts/preloaded/src/socket.erl | 24 ++++-- lib/kernel/test/socket_server.erl | 16 +++- 4 files changed, 152 insertions(+), 18 deletions(-) diff --git a/erts/emulator/nifs/common/socket_nif.c b/erts/emulator/nifs/common/socket_nif.c index c23f0b021d..70a5de912f 100644 --- a/erts/emulator/nifs/common/socket_nif.c +++ b/erts/emulator/nifs/common/socket_nif.c @@ -347,14 +347,16 @@ typedef union { #define SOCKET_OPT_OTP_CTRL_PROC 2 #define SOCKET_OPT_SOCK_BROADCAST 4 -#define SOCKET_OPT_SOCK_DONTROUTE 7 -#define SOCKET_OPT_SOCK_KEEPALIVE 9 -#define SOCKET_OPT_SOCK_LINGER 10 -#define SOCKET_OPT_SOCK_PRIORITY 16 -#define SOCKET_OPT_SOCK_RCVBUF 17 -#define SOCKET_OPT_SOCK_REUSEADDR 21 -#define SOCKET_OPT_SOCK_SNDBUF 27 -#define SOCKET_OPT_SOCK_TYPE 32 +#define SOCKET_OPT_SOCK_DOMAIN 7 +#define SOCKET_OPT_SOCK_DONTROUTE 8 +#define SOCKET_OPT_SOCK_KEEPALIVE 10 +#define SOCKET_OPT_SOCK_LINGER 11 +#define SOCKET_OPT_SOCK_PRIORITY 17 +#define SOCKET_OPT_SOCK_PROTOCOL 18 +#define SOCKET_OPT_SOCK_RCVBUF 19 +#define SOCKET_OPT_SOCK_REUSEADDR 23 +#define SOCKET_OPT_SOCK_SNDBUF 29 +#define SOCKET_OPT_SOCK_TYPE 34 #define SOCKET_OPT_IP_RECVTOS 25 #define SOCKET_OPT_IP_ROUTER_ALERT 28 @@ -931,6 +933,10 @@ static ERL_NIF_TERM ngetopt_lvl_socket(ErlNifEnv* env, static ERL_NIF_TERM ngetopt_lvl_sock_broadcast(ErlNifEnv* env, SocketDescriptor* descP); #endif +#if defined(SO_DOMAIN) +static ERL_NIF_TERM ngetopt_lvl_sock_domain(ErlNifEnv* env, + SocketDescriptor* descP); +#endif #if defined(SO_DONTROUTE) static ERL_NIF_TERM ngetopt_lvl_sock_dontroute(ErlNifEnv* env, SocketDescriptor* descP); @@ -947,6 +953,10 @@ static ERL_NIF_TERM ngetopt_lvl_sock_linger(ErlNifEnv* env, static ERL_NIF_TERM ngetopt_lvl_sock_priority(ErlNifEnv* env, SocketDescriptor* descP); #endif +#if defined(SO_PROTOCOL) +static ERL_NIF_TERM ngetopt_lvl_sock_protocol(ErlNifEnv* env, + SocketDescriptor* descP); +#endif #if defined(SO_RCVBUF) static ERL_NIF_TERM ngetopt_lvl_sock_rcvbuf(ErlNifEnv* env, SocketDescriptor* descP); @@ -4715,6 +4725,12 @@ ERL_NIF_TERM ngetopt_lvl_socket(ErlNifEnv* env, break; #endif +#if defined(SO_DOMAIN) + case SOCKET_OPT_SOCK_DOMAIN: + result = ngetopt_lvl_sock_domain(env, descP); + break; +#endif + #if defined(SO_DONTROUTE) case SOCKET_OPT_SOCK_DONTROUTE: result = ngetopt_lvl_sock_dontroute(env, descP); @@ -4739,6 +4755,12 @@ ERL_NIF_TERM ngetopt_lvl_socket(ErlNifEnv* env, break; #endif +#if defined(SO_PROTOCOL) + case SOCKET_OPT_SOCK_PROTOCOL: + result = ngetopt_lvl_sock_protocol(env, descP); + break; +#endif + #if defined(SO_RCVBUF) case SOCKET_OPT_SOCK_RCVBUF: result = ngetopt_lvl_sock_rcvbuf(env, descP); @@ -4782,6 +4804,51 @@ ERL_NIF_TERM ngetopt_lvl_sock_broadcast(ErlNifEnv* env, #endif +#if defined(SO_DOMAIN) +static +ERL_NIF_TERM ngetopt_lvl_sock_domain(ErlNifEnv* env, + SocketDescriptor* descP) +{ + ERL_NIF_TERM result; + int val; + SOCKOPTLEN_T valSz = sizeof(val); + int res; + + res = sock_getopt(descP->sock, SOL_SOCKET, SO_DOMAIN, + &val, &valSz); + + if (res != 0) { + result = esock_make_error_errno(env, res); + } else { + switch (val) { + case AF_INET: + result = esock_make_ok2(env, esock_atom_inet); + break; + +#if defined(HAVE_IN6) && defined(AF_INET6) + case AF_INET6: + result = esock_make_ok2(env, esock_atom_inet6); + break; +#endif + +#ifdef HAVE_SYS_UN_H + case AF_UNIX: + result = esock_make_ok2(env, esock_atom_local); + break; +#endif + + default: + result = esock_make_error(env, + MKT2(env, esock_atom_unknown, MKI(env, val))); + break; + } + } + + return result; +} +#endif + + #if defined(SO_DONTROUTE) static ERL_NIF_TERM ngetopt_lvl_sock_dontroute(ErlNifEnv* env, @@ -4842,6 +4909,53 @@ ERL_NIF_TERM ngetopt_lvl_sock_priority(ErlNifEnv* env, #endif +#if defined(SO_PROTOCOL) +static +ERL_NIF_TERM ngetopt_lvl_sock_protocol(ErlNifEnv* env, + SocketDescriptor* descP) +{ + ERL_NIF_TERM result; + int val; + SOCKOPTLEN_T valSz = sizeof(val); + int res; + + res = sock_getopt(descP->sock, SOL_SOCKET, SO_PROTOCOL, + &val, &valSz); + + if (res != 0) { + result = esock_make_error_errno(env, res); + } else { + switch (val) { + case IPPROTO_IP: + result = esock_make_ok2(env, esock_atom_ip); + break; + + case IPPROTO_TCP: + result = esock_make_ok2(env, esock_atom_tcp); + break; + + case IPPROTO_UDP: + result = esock_make_ok2(env, esock_atom_udp); + break; + +#if defined(HAVE_SCTP) + case IPPROTO_SCTP: + result = esock_make_ok2(env, esock_atom_sctp); + break; +#endif + + default: + result = esock_make_error(env, + MKT2(env, esock_atom_unknown, MKI(env, val))); + break; + } + } + + return result; +} +#endif + + #if defined(SO_RCVBUF) static ERL_NIF_TERM ngetopt_lvl_sock_rcvbuf(ErlNifEnv* env, diff --git a/erts/preloaded/ebin/socket.beam b/erts/preloaded/ebin/socket.beam index 8a3dab1313..952f869d54 100644 Binary files a/erts/preloaded/ebin/socket.beam and b/erts/preloaded/ebin/socket.beam differ diff --git a/erts/preloaded/src/socket.erl b/erts/preloaded/src/socket.erl index a1db295a79..4932e301fb 100644 --- a/erts/preloaded/src/socket.erl +++ b/erts/preloaded/src/socket.erl @@ -193,6 +193,7 @@ broadcast | busy_poll | debug | + domain | dontroute | error | keepalive | @@ -203,6 +204,7 @@ peek_off | peek_cred | priority | + protocol | rcvbuf | rcvbufforce | rcvlowat | @@ -463,14 +465,16 @@ -define(SOCKET_OPT_OTP_CTRL_PROC, 2). -define(SOCKET_OPT_SOCK_BROADCAST, 4). --define(SOCKET_OPT_SOCK_DONTROUTE, 7). --define(SOCKET_OPT_SOCK_KEEPALIVE, 9). --define(SOCKET_OPT_SOCK_LINGER, 10). --define(SOCKET_OPT_SOCK_PRIORITY, 16). --define(SOCKET_OPT_SOCK_RCVBUF, 17). --define(SOCKET_OPT_SOCK_REUSEADDR, 21). --define(SOCKET_OPT_SOCK_SNDBUF, 27). --define(SOCKET_OPT_SOCK_TYPE, 32). +-define(SOCKET_OPT_SOCK_DOMAIN, 7). +-define(SOCKET_OPT_SOCK_DONTROUTE, 8). +-define(SOCKET_OPT_SOCK_KEEPALIVE, 10). +-define(SOCKET_OPT_SOCK_LINGER, 11). +-define(SOCKET_OPT_SOCK_PRIORITY, 17). +-define(SOCKET_OPT_SOCK_PROTOCOL, 18). +-define(SOCKET_OPT_SOCK_RCVBUF, 19). +-define(SOCKET_OPT_SOCK_REUSEADDR, 23). +-define(SOCKET_OPT_SOCK_SNDBUF, 29). +-define(SOCKET_OPT_SOCK_TYPE, 34). -define(SOCKET_OPT_IP_RECVTOS, 25). -define(SOCKET_OPT_IP_ROUTER_ALERT, 28). @@ -2032,6 +2036,8 @@ enc_sockopt_key(socket = L, busy_poll = Opt, _Dir, _D, _T, _P) -> not_supported({L, Opt}); enc_sockopt_key(socket = L, debug = Opt, _Dir, _D, _T, _P) -> not_supported({L, Opt}); +enc_sockopt_key(socket, domain = _Opt, get = _Dir, _D, _T, _P) -> + ?SOCKET_OPT_SOCK_DOMAIN; enc_sockopt_key(socket, dontroute = _Opt, _Dir, _D, _T, _P) -> ?SOCKET_OPT_SOCK_DONTROUTE; enc_sockopt_key(socket = L, error = Opt, get = _Dir, _D, _T, _P) -> @@ -2055,6 +2061,8 @@ enc_sockopt_key(socket = L, peek_cred = Opt, get = _Dir, _D, _T, _P) -> not_supported({L, Opt}); enc_sockopt_key(socket, priority = _Opt, _Dir, _D, _T, _P) -> ?SOCKET_OPT_SOCK_PRIORITY; +enc_sockopt_key(socket, protocol = _Opt, get = _Dir, _D, _T, _P) -> + ?SOCKET_OPT_SOCK_PROTOCOL; enc_sockopt_key(socket, rcvbuf = _Opt, _Dir, _D, _T, _P) -> ?SOCKET_OPT_SOCK_RCVBUF; enc_sockopt_key(socket = L, rcvbufforce = Opt, _Dir, _D, _T, _P) -> diff --git a/lib/kernel/test/socket_server.erl b/lib/kernel/test/socket_server.erl index 02fc6fc26d..cf7e349a3d 100644 --- a/lib/kernel/test/socket_server.erl +++ b/lib/kernel/test/socket_server.erl @@ -258,7 +258,13 @@ acceptor_do_init(Domain, Type, Proto) -> {error, OReason} -> throw({open, OReason}) end, - i("(socket) open - try find (local) address"), + F = fun(X) -> case socket:getopt(Sock, socket, X) of + {ok, V} -> f("~p", [V]); + {error, _} -> f("~w", [X]) + end + end, + i("(socket) open (~s,~s,~s) - try find (local) address", + [F(domain), F(type), F(protocol)]), Addr = which_addr(Domain), SA = #{family => Domain, addr => Addr}, @@ -392,7 +398,13 @@ handler_init(Manager, ID, Peek, Sock) -> {handler, Pid, Ref, continue} -> i("got continue"), handler_reply(Pid, Ref, ok), - {ok, Type} = socket:getopt(Sock, socket, type), + {ok, Domain} = socket:getopt(Sock, socket, domain), + {ok, Type} = socket:getopt(Sock, socket, type), + {ok, Proto} = socket:getopt(Sock, socket, protocol), + i("got continue when: " + "~n Domain: ~p" + "~n Type: ~p" + "~n Protocol: ~p", [Domain, Type, Proto]), %% socket:setopt(Socket, otp, debug, true), handler_loop(#handler{peek = Peek, manager = Manager, -- cgit v1.2.3 From 7ed2af8dcfd62a051686ac1e1326fbeca0b18334 Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Mon, 9 Jul 2018 14:54:57 +0200 Subject: [socket-nif] Add support for socket (level socket) option acceptconn The socket (level socket) option acceptconn is now supported (for getopt). OTP-14831 --- erts/emulator/nifs/common/socket_nif.c | 21 +++++++++++++++++++++ erts/preloaded/ebin/socket.beam | Bin 43604 -> 43616 bytes erts/preloaded/src/socket.erl | 33 ++++++++++++++++++++++----------- lib/kernel/test/socket_server.erl | 5 ++++- 4 files changed, 47 insertions(+), 12 deletions(-) diff --git a/erts/emulator/nifs/common/socket_nif.c b/erts/emulator/nifs/common/socket_nif.c index 70a5de912f..73da6455c3 100644 --- a/erts/emulator/nifs/common/socket_nif.c +++ b/erts/emulator/nifs/common/socket_nif.c @@ -346,6 +346,7 @@ typedef union { #define SOCKET_OPT_OTP_IOW 1 #define SOCKET_OPT_OTP_CTRL_PROC 2 +#define SOCKET_OPT_SOCK_ACCEPTCONN 1 #define SOCKET_OPT_SOCK_BROADCAST 4 #define SOCKET_OPT_SOCK_DOMAIN 7 #define SOCKET_OPT_SOCK_DONTROUTE 8 @@ -929,6 +930,10 @@ static ERL_NIF_TERM ngetopt_level(ErlNifEnv* env, static ERL_NIF_TERM ngetopt_lvl_socket(ErlNifEnv* env, SocketDescriptor* descP, int eOpt); +#if defined(SO_ACCEPTCONN) +static ERL_NIF_TERM ngetopt_lvl_sock_acceptconn(ErlNifEnv* env, + SocketDescriptor* descP); +#endif #if defined(SO_BROADCAST) static ERL_NIF_TERM ngetopt_lvl_sock_broadcast(ErlNifEnv* env, SocketDescriptor* descP); @@ -4719,6 +4724,12 @@ ERL_NIF_TERM ngetopt_lvl_socket(ErlNifEnv* env, ERL_NIF_TERM result; switch (eOpt) { +#if defined(SO_ACCEPTCONN) + case SOCKET_OPT_SOCK_ACCEPTCONN: + result = ngetopt_lvl_sock_acceptconn(env, descP); + break; +#endif + #if defined(SO_BROADCAST) case SOCKET_OPT_SOCK_BROADCAST: result = ngetopt_lvl_sock_broadcast(env, descP); @@ -4794,6 +4805,16 @@ ERL_NIF_TERM ngetopt_lvl_socket(ErlNifEnv* env, } +#if defined(SO_ACCEPTCONN) +static +ERL_NIF_TERM ngetopt_lvl_sock_acceptconn(ErlNifEnv* env, + SocketDescriptor* descP) +{ + return ngetopt_bool_opt(env, descP, SOL_SOCKET, SO_ACCEPTCONN); +} +#endif + + #if defined(SO_BROADCAST) static ERL_NIF_TERM ngetopt_lvl_sock_broadcast(ErlNifEnv* env, diff --git a/erts/preloaded/ebin/socket.beam b/erts/preloaded/ebin/socket.beam index 952f869d54..f5248d8622 100644 Binary files a/erts/preloaded/ebin/socket.beam and b/erts/preloaded/ebin/socket.beam differ diff --git a/erts/preloaded/src/socket.erl b/erts/preloaded/src/socket.erl index 4932e301fb..f22f9e5072 100644 --- a/erts/preloaded/src/socket.erl +++ b/erts/preloaded/src/socket.erl @@ -202,7 +202,7 @@ oobinline | passcred | peek_off | - peek_cred | + peekcred | priority | protocol | rcvbuf | @@ -464,17 +464,28 @@ -define(SOCKET_OPT_OTP_IOW, 1). -define(SOCKET_OPT_OTP_CTRL_PROC, 2). +-define(SOCKET_OPT_SOCK_ACCEPTCONN, 1). +%% -define(SOCKET_OPT_SOCK_ACCEPTFILTER, 2). +%% -define(SOCKET_OPT_SOCK_BINDTODEVICE, 3). -define(SOCKET_OPT_SOCK_BROADCAST, 4). +%% -define(SOCKET_OPT_SOCK_BUSY_POLL, 5). +%% -define(SOCKET_OPT_SOCK_DEBUG, 6). -define(SOCKET_OPT_SOCK_DOMAIN, 7). -define(SOCKET_OPT_SOCK_DONTROUTE, 8). +%% -define(SOCKET_OPT_SOCK_ERROR, 9). -define(SOCKET_OPT_SOCK_KEEPALIVE, 10). -define(SOCKET_OPT_SOCK_LINGER, 11). --define(SOCKET_OPT_SOCK_PRIORITY, 17). --define(SOCKET_OPT_SOCK_PROTOCOL, 18). --define(SOCKET_OPT_SOCK_RCVBUF, 19). --define(SOCKET_OPT_SOCK_REUSEADDR, 23). --define(SOCKET_OPT_SOCK_SNDBUF, 29). --define(SOCKET_OPT_SOCK_TYPE, 34). +%% -define(SOCKET_OPT_SOCK_MARK, 12). +%% -define(SOCKET_OPT_SOCK_OOBINLLINE, 13). +%% -define(SOCKET_OPT_SOCK_PASSCRED, 14). +%% -define(SOCKET_OPT_SOCK_PEEK_OFF, 16). +%% -define(SOCKET_OPT_SOCK_PEEKCRED, 17). +-define(SOCKET_OPT_SOCK_PRIORITY, 18). +-define(SOCKET_OPT_SOCK_PROTOCOL, 19). +-define(SOCKET_OPT_SOCK_RCVBUF, 20). +-define(SOCKET_OPT_SOCK_REUSEADDR, 24). +-define(SOCKET_OPT_SOCK_SNDBUF, 30). +-define(SOCKET_OPT_SOCK_TYPE, 35). -define(SOCKET_OPT_IP_RECVTOS, 25). -define(SOCKET_OPT_IP_ROUTER_ALERT, 28). @@ -2021,10 +2032,10 @@ enc_sockopt_key(otp = L, Opt, _, _, _, _) -> not_supported({L, Opt}); %% +++ SOCKET socket options +++ -enc_sockopt_key(socket = L, acceptconn = Opt, get = _Dir, _D, _T, _P) -> +enc_sockopt_key(socket = _L, acceptconn = _Opt, get = _Dir, _D, _T, _P) -> + ?SOCKET_OPT_SOCK_ACCEPTCONN; +enc_sockopt_key(socket = L, acceptfilter = Opt, _Dir, _D, _T, _P) -> not_supported({L, Opt}); -enc_sockopt_key(socket, acceptfilter = Opt, _Dir, _D, _T, _P) -> - not_supported(Opt); %% Before linux 3.8, this socket option could be set. %% Size of buffer for name: IFNAMSZ %% So, we let the implementation decide. @@ -2057,7 +2068,7 @@ enc_sockopt_key(socket = L, passcred = Opt, _Dir, _D, _T, _P) -> not_supported({L, Opt}); enc_sockopt_key(socket = L, peek_off = Opt, _Dir, local = _D, _T, _P) -> not_supported({L, Opt}); -enc_sockopt_key(socket = L, peek_cred = Opt, get = _Dir, _D, _T, _P) -> +enc_sockopt_key(socket = L, peekcred = Opt, get = _Dir, _D, _T, _P) -> not_supported({L, Opt}); enc_sockopt_key(socket, priority = _Opt, _Dir, _D, _T, _P) -> ?SOCKET_OPT_SOCK_PRIORITY; diff --git a/lib/kernel/test/socket_server.erl b/lib/kernel/test/socket_server.erl index cf7e349a3d..52b0d5b558 100644 --- a/lib/kernel/test/socket_server.erl +++ b/lib/kernel/test/socket_server.erl @@ -275,9 +275,12 @@ acceptor_do_init(Domain, Type, Proto) -> {error, BReason} -> throw({bind, BReason}) end, - i("bound (~w) - try (socket) listen", [Port]), + i("bound (~w) - try (socket) listen (acceptconn: ~s)", + [Port, F(acceptconn)]), case socket:listen(Sock) of ok -> + i("listening (acceptconn: ~s)", + [F(acceptconn)]), Sock; {error, LReason} -> throw({listen, LReason}) -- cgit v1.2.3 From 6031321a78e3df5304f555ba356a4482469e7d56 Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Mon, 9 Jul 2018 15:28:09 +0200 Subject: [socket-nif] Add support for socket (level socket) option debug The socket option (level socket) debug is now supported. To actually set this option, the user must have the proper "authority" (CAP_NET_ADMIN capability or an effective user ID of 0). OTP-14831 --- erts/emulator/nifs/common/socket_nif.c | 62 +++++++++++++++++++++++++++++---- erts/preloaded/ebin/socket.beam | Bin 43616 -> 43652 bytes erts/preloaded/src/socket.erl | 8 +++-- lib/kernel/test/socket_server.erl | 13 +++++-- 4 files changed, 70 insertions(+), 13 deletions(-) diff --git a/erts/emulator/nifs/common/socket_nif.c b/erts/emulator/nifs/common/socket_nif.c index 73da6455c3..c2a8170227 100644 --- a/erts/emulator/nifs/common/socket_nif.c +++ b/erts/emulator/nifs/common/socket_nif.c @@ -348,16 +348,17 @@ typedef union { #define SOCKET_OPT_SOCK_ACCEPTCONN 1 #define SOCKET_OPT_SOCK_BROADCAST 4 +#define SOCKET_OPT_SOCK_DEBUG 6 #define SOCKET_OPT_SOCK_DOMAIN 7 #define SOCKET_OPT_SOCK_DONTROUTE 8 #define SOCKET_OPT_SOCK_KEEPALIVE 10 #define SOCKET_OPT_SOCK_LINGER 11 -#define SOCKET_OPT_SOCK_PRIORITY 17 -#define SOCKET_OPT_SOCK_PROTOCOL 18 -#define SOCKET_OPT_SOCK_RCVBUF 19 -#define SOCKET_OPT_SOCK_REUSEADDR 23 -#define SOCKET_OPT_SOCK_SNDBUF 29 -#define SOCKET_OPT_SOCK_TYPE 34 +#define SOCKET_OPT_SOCK_PRIORITY 18 +#define SOCKET_OPT_SOCK_PROTOCOL 19 +#define SOCKET_OPT_SOCK_RCVBUF 20 +#define SOCKET_OPT_SOCK_REUSEADDR 24 +#define SOCKET_OPT_SOCK_SNDBUF 30 +#define SOCKET_OPT_SOCK_TYPE 35 #define SOCKET_OPT_IP_RECVTOS 25 #define SOCKET_OPT_IP_ROUTER_ALERT 28 @@ -786,6 +787,11 @@ static ERL_NIF_TERM nsetopt_lvl_sock_broadcast(ErlNifEnv* env, SocketDescriptor* descP, ERL_NIF_TERM eVal); #endif +#if defined(SO_DEBUG) +static ERL_NIF_TERM nsetopt_lvl_sock_debug(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); +#endif #if defined(SO_DONTROUTE) static ERL_NIF_TERM nsetopt_lvl_sock_dontroute(ErlNifEnv* env, SocketDescriptor* descP, @@ -938,6 +944,10 @@ static ERL_NIF_TERM ngetopt_lvl_sock_acceptconn(ErlNifEnv* env, static ERL_NIF_TERM ngetopt_lvl_sock_broadcast(ErlNifEnv* env, SocketDescriptor* descP); #endif +#if defined(SO_DEBUG) +static ERL_NIF_TERM ngetopt_lvl_sock_debug(ErlNifEnv* env, + SocketDescriptor* descP); +#endif #if defined(SO_DOMAIN) static ERL_NIF_TERM ngetopt_lvl_sock_domain(ErlNifEnv* env, SocketDescriptor* descP); @@ -3677,6 +3687,11 @@ ERL_NIF_TERM nsetopt_lvl_socket(ErlNifEnv* env, { ERL_NIF_TERM result; + SSDBG( descP, + ("SOCKET", "nsetopt_lvl_socket -> entry with" + "\r\n opt: %d" + "\r\n", eOpt) ); + switch (eOpt) { #if defined(SO_BROADCAST) case SOCKET_OPT_SOCK_BROADCAST: @@ -3684,6 +3699,12 @@ ERL_NIF_TERM nsetopt_lvl_socket(ErlNifEnv* env, break; #endif +#if defined(SO_DEBUG) + case SOCKET_OPT_SOCK_DEBUG: + result = nsetopt_lvl_sock_debug(env, descP, eVal); + break; +#endif + #if defined(SO_DONTROUTE) case SOCKET_OPT_SOCK_DONTROUTE: result = nsetopt_lvl_sock_dontroute(env, descP, eVal); @@ -3746,6 +3767,17 @@ ERL_NIF_TERM nsetopt_lvl_sock_broadcast(ErlNifEnv* env, #endif +#if defined(SO_DEBUG) +static +ERL_NIF_TERM nsetopt_lvl_sock_debug(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) +{ + return nsetopt_int_opt(env, descP, SOL_SOCKET, SO_DEBUG, eVal); +} +#endif + + #if defined(SO_DONTROUTE) static ERL_NIF_TERM nsetopt_lvl_sock_dontroute(ErlNifEnv* env, @@ -4277,7 +4309,7 @@ ERL_NIF_TERM nsetopt_int_opt(ErlNifEnv* env, int res = socket_setopt(descP->sock, level, opt, &val, sizeof(val)); if (res != 0) - result = esock_make_error_errno(env, res); + result = esock_make_error_errno(env, sock_errno()); else result = esock_atom_ok; @@ -4736,6 +4768,12 @@ ERL_NIF_TERM ngetopt_lvl_socket(ErlNifEnv* env, break; #endif +#if defined(SO_DEBUG) + case SOCKET_OPT_SOCK_DEBUG: + result = ngetopt_lvl_sock_debug(env, descP); + break; +#endif + #if defined(SO_DOMAIN) case SOCKET_OPT_SOCK_DOMAIN: result = ngetopt_lvl_sock_domain(env, descP); @@ -4825,6 +4863,16 @@ ERL_NIF_TERM ngetopt_lvl_sock_broadcast(ErlNifEnv* env, #endif +#if defined(SO_DEBUG) +static +ERL_NIF_TERM ngetopt_lvl_sock_debug(ErlNifEnv* env, + SocketDescriptor* descP) +{ + return ngetopt_int_opt(env, descP, SOL_SOCKET, SO_DEBUG); +} +#endif + + #if defined(SO_DOMAIN) static ERL_NIF_TERM ngetopt_lvl_sock_domain(ErlNifEnv* env, diff --git a/erts/preloaded/ebin/socket.beam b/erts/preloaded/ebin/socket.beam index f5248d8622..508c669895 100644 Binary files a/erts/preloaded/ebin/socket.beam and b/erts/preloaded/ebin/socket.beam differ diff --git a/erts/preloaded/src/socket.erl b/erts/preloaded/src/socket.erl index f22f9e5072..66ae9c7fd8 100644 --- a/erts/preloaded/src/socket.erl +++ b/erts/preloaded/src/socket.erl @@ -469,7 +469,7 @@ %% -define(SOCKET_OPT_SOCK_BINDTODEVICE, 3). -define(SOCKET_OPT_SOCK_BROADCAST, 4). %% -define(SOCKET_OPT_SOCK_BUSY_POLL, 5). -%% -define(SOCKET_OPT_SOCK_DEBUG, 6). +-define(SOCKET_OPT_SOCK_DEBUG, 6). -define(SOCKET_OPT_SOCK_DOMAIN, 7). -define(SOCKET_OPT_SOCK_DONTROUTE, 8). %% -define(SOCKET_OPT_SOCK_ERROR, 9). @@ -1831,6 +1831,8 @@ enc_setopt_value(otp = L, Opt, V, _D, _T, _P) -> enc_setopt_value(socket, broadcast, V, _D, _T, _P) when is_boolean(V) -> V; +enc_setopt_value(socket, debug, V, _D, _T, _P) when is_integer(V) -> + V; enc_setopt_value(socket, keepalive, V, _D, _T, _P) when is_boolean(V) -> V; enc_setopt_value(socket, linger, abort, D, T, P) -> @@ -2045,8 +2047,8 @@ enc_sockopt_key(socket, broadcast = _Opt, _Dir, _D, dgram = _T, _P) -> ?SOCKET_OPT_SOCK_BROADCAST; enc_sockopt_key(socket = L, busy_poll = Opt, _Dir, _D, _T, _P) -> not_supported({L, Opt}); -enc_sockopt_key(socket = L, debug = Opt, _Dir, _D, _T, _P) -> - not_supported({L, Opt}); +enc_sockopt_key(socket = _L, debug = _Opt, _Dir, _D, _T, _P) -> + ?SOCKET_OPT_SOCK_DEBUG; enc_sockopt_key(socket, domain = _Opt, get = _Dir, _D, _T, _P) -> ?SOCKET_OPT_SOCK_DOMAIN; enc_sockopt_key(socket, dontroute = _Opt, _Dir, _D, _T, _P) -> diff --git a/lib/kernel/test/socket_server.erl b/lib/kernel/test/socket_server.erl index 52b0d5b558..54e13cc92d 100644 --- a/lib/kernel/test/socket_server.erl +++ b/lib/kernel/test/socket_server.erl @@ -260,17 +260,24 @@ acceptor_do_init(Domain, Type, Proto) -> end, F = fun(X) -> case socket:getopt(Sock, socket, X) of {ok, V} -> f("~p", [V]); - {error, _} -> f("~w", [X]) + {error, _} -> "-" end end, - i("(socket) open (~s,~s,~s) - try find (local) address", - [F(domain), F(type), F(protocol)]), + i("(socket) open (~s,~s,~s): " + "~n debug: ~s" + "~n prio: ~s" + "~n try find (local) address", + [F(domain), F(type), F(protocol), F(debug), F(priority)]), Addr = which_addr(Domain), SA = #{family => Domain, addr => Addr}, i("found (~p) - try (socket) bind", [Addr]), + %% ok = socket:setopt(Sock, otp, debug, true), + %% ok = socket:setopt(Sock, socket, debug, 1), %% must have rights!! Port = case socket:bind(Sock, SA) of {ok, P} -> + %% ok = socket:setopt(Sock, socket, debug, 0), %% must have rights!! + %% ok = socket:setopt(Sock, otp, debug, false), P; {error, BReason} -> throw({bind, BReason}) -- cgit v1.2.3 From 5329726352f8b983e419b6206a85c15cc8836676 Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Mon, 9 Jul 2018 18:41:07 +0200 Subject: [socket-nif] Add support for socket (level socket) option peek_off The socket option (level socket) peek_off is now supported. OTP-14831 --- erts/emulator/nifs/common/socket_nif.c | 43 +++++++++++++++++++++++++++++++++ erts/preloaded/ebin/socket.beam | Bin 43652 -> 43668 bytes erts/preloaded/src/socket.erl | 8 +++--- lib/kernel/test/socket_server.erl | 37 ++++++++++++++++++++++++---- 4 files changed, 80 insertions(+), 8 deletions(-) diff --git a/erts/emulator/nifs/common/socket_nif.c b/erts/emulator/nifs/common/socket_nif.c index c2a8170227..4cc953df62 100644 --- a/erts/emulator/nifs/common/socket_nif.c +++ b/erts/emulator/nifs/common/socket_nif.c @@ -353,6 +353,7 @@ typedef union { #define SOCKET_OPT_SOCK_DONTROUTE 8 #define SOCKET_OPT_SOCK_KEEPALIVE 10 #define SOCKET_OPT_SOCK_LINGER 11 +#define SOCKET_OPT_SOCK_PEEK_OFF 16 #define SOCKET_OPT_SOCK_PRIORITY 18 #define SOCKET_OPT_SOCK_PROTOCOL 19 #define SOCKET_OPT_SOCK_RCVBUF 20 @@ -807,6 +808,11 @@ static ERL_NIF_TERM nsetopt_lvl_sock_linger(ErlNifEnv* env, SocketDescriptor* descP, ERL_NIF_TERM eVal); #endif +#if defined(SO_PEEK_OFF) +static ERL_NIF_TERM nsetopt_lvl_sock_peek_off(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); +#endif #if defined(SO_PRIORITY) static ERL_NIF_TERM nsetopt_lvl_sock_priority(ErlNifEnv* env, SocketDescriptor* descP, @@ -964,6 +970,10 @@ static ERL_NIF_TERM ngetopt_lvl_sock_keepalive(ErlNifEnv* env, static ERL_NIF_TERM ngetopt_lvl_sock_linger(ErlNifEnv* env, SocketDescriptor* descP); #endif +#if defined(SO_PEEK_OFF) +static ERL_NIF_TERM ngetopt_lvl_sock_peek_off(ErlNifEnv* env, + SocketDescriptor* descP); +#endif #if defined(SO_PRIORITY) static ERL_NIF_TERM ngetopt_lvl_sock_priority(ErlNifEnv* env, SocketDescriptor* descP); @@ -3723,6 +3733,12 @@ ERL_NIF_TERM nsetopt_lvl_socket(ErlNifEnv* env, break; #endif +#if defined(SO_PEEK_OFF) + case SOCKET_OPT_SOCK_PEEK_OFF: + result = nsetopt_lvl_sock_peek_off(env, descP, eVal); + break; +#endif + #if defined(SO_PRIORITY) case SOCKET_OPT_SOCK_PRIORITY: result = nsetopt_lvl_sock_priority(env, descP, eVal); @@ -3837,6 +3853,17 @@ ERL_NIF_TERM nsetopt_lvl_sock_priority(ErlNifEnv* env, #endif +#if defined(SO_PEEK_OFF) +static +ERL_NIF_TERM nsetopt_lvl_sock_peek_off(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) +{ + return nsetopt_int_opt(env, descP, SOL_SOCKET, SO_PEEK_OFF, eVal); +} +#endif + + #if defined(SO_RCVBUF) static ERL_NIF_TERM nsetopt_lvl_sock_rcvbuf(ErlNifEnv* env, @@ -4798,6 +4825,12 @@ ERL_NIF_TERM ngetopt_lvl_socket(ErlNifEnv* env, break; #endif +#if defined(SO_PEEK_OFF) + case SOCKET_OPT_SOCK_PEEK_OFF: + result = ngetopt_lvl_sock_peek_off(env, descP); + break; +#endif + #if defined(SO_PRIORITY) case SOCKET_OPT_SOCK_PRIORITY: result = ngetopt_lvl_sock_priority(env, descP); @@ -4968,6 +5001,16 @@ ERL_NIF_TERM ngetopt_lvl_sock_linger(ErlNifEnv* env, #endif +#if defined(SO_PEEK_OFF) +static +ERL_NIF_TERM ngetopt_lvl_sock_peek_off(ErlNifEnv* env, + SocketDescriptor* descP) +{ + return ngetopt_int_opt(env, descP, SOL_SOCKET, SO_PEEK_OFF); +} +#endif + + #if defined(SO_PRIORITY) static ERL_NIF_TERM ngetopt_lvl_sock_priority(ErlNifEnv* env, diff --git a/erts/preloaded/ebin/socket.beam b/erts/preloaded/ebin/socket.beam index 508c669895..9c10182bb4 100644 Binary files a/erts/preloaded/ebin/socket.beam and b/erts/preloaded/ebin/socket.beam differ diff --git a/erts/preloaded/src/socket.erl b/erts/preloaded/src/socket.erl index 66ae9c7fd8..8f489bd681 100644 --- a/erts/preloaded/src/socket.erl +++ b/erts/preloaded/src/socket.erl @@ -478,7 +478,7 @@ %% -define(SOCKET_OPT_SOCK_MARK, 12). %% -define(SOCKET_OPT_SOCK_OOBINLLINE, 13). %% -define(SOCKET_OPT_SOCK_PASSCRED, 14). -%% -define(SOCKET_OPT_SOCK_PEEK_OFF, 16). +-define(SOCKET_OPT_SOCK_PEEK_OFF, 16). %% -define(SOCKET_OPT_SOCK_PEEKCRED, 17). -define(SOCKET_OPT_SOCK_PRIORITY, 18). -define(SOCKET_OPT_SOCK_PROTOCOL, 19). @@ -1840,6 +1840,8 @@ enc_setopt_value(socket, linger, abort, D, T, P) -> enc_setopt_value(socket, linger, {OnOff, Secs} = V, _D, _T, _P) when is_boolean(OnOff) andalso is_integer(Secs) andalso (Secs >= 0) -> V; +enc_setopt_value(socket, peek_off, V, _D, _T, _P) when is_integer(V) -> + V; enc_setopt_value(socket, priority, V, _D, _T, _P) when is_integer(V) -> V; enc_setopt_value(socket, rcvbuf, V, _D, _T, _P) when is_integer(V) -> @@ -2068,8 +2070,8 @@ enc_sockopt_key(socket, oobinline = Opt, _Dir, _D, _T, _P) -> not_supported(Opt); enc_sockopt_key(socket = L, passcred = Opt, _Dir, _D, _T, _P) -> not_supported({L, Opt}); -enc_sockopt_key(socket = L, peek_off = Opt, _Dir, local = _D, _T, _P) -> - not_supported({L, Opt}); +enc_sockopt_key(socket = _L, peek_off = _Opt, _Dir, local = _D, _T, _P) -> + ?SOCKET_OPT_SOCK_PEEK_OFF; enc_sockopt_key(socket = L, peekcred = Opt, get = _Dir, _D, _T, _P) -> not_supported({L, Opt}); enc_sockopt_key(socket, priority = _Opt, _Dir, _D, _T, _P) -> diff --git a/lib/kernel/test/socket_server.erl b/lib/kernel/test/socket_server.erl index 54e13cc92d..e5338644b1 100644 --- a/lib/kernel/test/socket_server.erl +++ b/lib/kernel/test/socket_server.erl @@ -99,6 +99,25 @@ manager_init(Domain, dgram = Type, Proto, Peek) -> i("try open socket"), case socket:open(Domain, Type, Proto) of {ok, Sock} -> + F = fun(X) -> case socket:getopt(Sock, socket, X) of + {ok, V} -> f("~p", [V]); + {error, _} -> "-" + end + end, + i("(socket) open (~s,~s,~s): " + "~n broadcast: ~s" + "~n dontroute: ~s" + "~n keepalive: ~s" + "~n reuseaddr: ~s" + "~n linger: ~s" + "~n debug: ~s" + "~n prio: ~s" + "~n rcvbuf: ~s" + "~n sndbuf: ~s" + "~n try find (local) address", + [F(domain), F(type), F(protocol), + F(broadcast), F(dontroute), F(keepalive), F(reuseaddr), F(linger), + F(debug), F(priority), F(rcvbuf), F(sndbuf)]), Addr = which_addr(Domain), SA = #{family => Domain, addr => Addr}, @@ -260,14 +279,22 @@ acceptor_do_init(Domain, Type, Proto) -> end, F = fun(X) -> case socket:getopt(Sock, socket, X) of {ok, V} -> f("~p", [V]); - {error, _} -> "-" + {error, R} -> f("error: ~p", [R]) end end, i("(socket) open (~s,~s,~s): " - "~n debug: ~s" - "~n prio: ~s" - "~n try find (local) address", - [F(domain), F(type), F(protocol), F(debug), F(priority)]), + "~n dontroute: ~s" + "~n keepalive: ~s" + "~n reuseaddr: ~s" + "~n linger: ~s" + "~n debug: ~s" + "~n prio: ~s" + "~n rcvbuf: ~s" + "~n sndbuf: ~s" + "~n => try find (local) address", + [F(domain), F(type), F(protocol), + F(dontroute), F(keepalive), F(reuseaddr), F(linger), + F(debug), F(priority), F(rcvbuf), F(sndbuf)]), Addr = which_addr(Domain), SA = #{family => Domain, addr => Addr}, -- cgit v1.2.3 From 9110ba256099e6fa55461fc4ca90da5ec4b2966b Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Tue, 10 Jul 2018 11:28:11 +0200 Subject: [socket-nif] Add support for socket (level socket) option oobinline Added support for socket level socket option oobinline (both get and set). OTP-14831 --- erts/emulator/nifs/common/socket_nif.c | 67 +++++++++++++++++++++++++++------ erts/preloaded/ebin/socket.beam | Bin 43668 -> 43748 bytes erts/preloaded/src/socket.erl | 34 +++++++++++------ lib/kernel/test/socket_server.erl | 10 +++-- 4 files changed, 84 insertions(+), 27 deletions(-) diff --git a/erts/emulator/nifs/common/socket_nif.c b/erts/emulator/nifs/common/socket_nif.c index 4cc953df62..82468c6e8f 100644 --- a/erts/emulator/nifs/common/socket_nif.c +++ b/erts/emulator/nifs/common/socket_nif.c @@ -353,13 +353,14 @@ typedef union { #define SOCKET_OPT_SOCK_DONTROUTE 8 #define SOCKET_OPT_SOCK_KEEPALIVE 10 #define SOCKET_OPT_SOCK_LINGER 11 -#define SOCKET_OPT_SOCK_PEEK_OFF 16 -#define SOCKET_OPT_SOCK_PRIORITY 18 -#define SOCKET_OPT_SOCK_PROTOCOL 19 -#define SOCKET_OPT_SOCK_RCVBUF 20 -#define SOCKET_OPT_SOCK_REUSEADDR 24 -#define SOCKET_OPT_SOCK_SNDBUF 30 -#define SOCKET_OPT_SOCK_TYPE 35 +#define SOCKET_OPT_SOCK_OOBINLINE 13 +#define SOCKET_OPT_SOCK_PEEK_OFF 15 +#define SOCKET_OPT_SOCK_PRIORITY 17 +#define SOCKET_OPT_SOCK_PROTOCOL 18 +#define SOCKET_OPT_SOCK_RCVBUF 19 +#define SOCKET_OPT_SOCK_REUSEADDR 23 +#define SOCKET_OPT_SOCK_SNDBUF 27 +#define SOCKET_OPT_SOCK_TYPE 32 #define SOCKET_OPT_IP_RECVTOS 25 #define SOCKET_OPT_IP_ROUTER_ALERT 28 @@ -808,6 +809,11 @@ static ERL_NIF_TERM nsetopt_lvl_sock_linger(ErlNifEnv* env, SocketDescriptor* descP, ERL_NIF_TERM eVal); #endif +#if defined(SO_OOBINLINE) +static ERL_NIF_TERM nsetopt_lvl_sock_oobinline(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); +#endif #if defined(SO_PEEK_OFF) static ERL_NIF_TERM nsetopt_lvl_sock_peek_off(ErlNifEnv* env, SocketDescriptor* descP, @@ -970,6 +976,10 @@ static ERL_NIF_TERM ngetopt_lvl_sock_keepalive(ErlNifEnv* env, static ERL_NIF_TERM ngetopt_lvl_sock_linger(ErlNifEnv* env, SocketDescriptor* descP); #endif +#if defined(SO_OOBINLINE) +static ERL_NIF_TERM ngetopt_lvl_sock_oobinline(ErlNifEnv* env, + SocketDescriptor* descP); +#endif #if defined(SO_PEEK_OFF) static ERL_NIF_TERM ngetopt_lvl_sock_peek_off(ErlNifEnv* env, SocketDescriptor* descP); @@ -3739,6 +3749,12 @@ ERL_NIF_TERM nsetopt_lvl_socket(ErlNifEnv* env, break; #endif +#if defined(SO_OOBINLINE) + case SOCKET_OPT_SOCK_OOBINLINE: + result = nsetopt_lvl_sock_oobinline(env, descP, eVal); + break; +#endif + #if defined(SO_PRIORITY) case SOCKET_OPT_SOCK_PRIORITY: result = nsetopt_lvl_sock_priority(env, descP, eVal); @@ -3842,13 +3858,13 @@ ERL_NIF_TERM nsetopt_lvl_sock_linger(ErlNifEnv* env, #endif -#if defined(SO_PRIORITY) +#if defined(SO_OOBINLINE) static -ERL_NIF_TERM nsetopt_lvl_sock_priority(ErlNifEnv* env, - SocketDescriptor* descP, - ERL_NIF_TERM eVal) +ERL_NIF_TERM nsetopt_lvl_sock_oobinline(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) { - return nsetopt_int_opt(env, descP, SOL_SOCKET, SO_PRIORITY, eVal); + return nsetopt_bool_opt(env, descP, SOL_SOCKET, SO_OOBINLINE, eVal); } #endif @@ -3864,6 +3880,17 @@ ERL_NIF_TERM nsetopt_lvl_sock_peek_off(ErlNifEnv* env, #endif +#if defined(SO_PRIORITY) +static +ERL_NIF_TERM nsetopt_lvl_sock_priority(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) +{ + return nsetopt_int_opt(env, descP, SOL_SOCKET, SO_PRIORITY, eVal); +} +#endif + + #if defined(SO_RCVBUF) static ERL_NIF_TERM nsetopt_lvl_sock_rcvbuf(ErlNifEnv* env, @@ -4825,6 +4852,12 @@ ERL_NIF_TERM ngetopt_lvl_socket(ErlNifEnv* env, break; #endif +#if defined(SO_OOBINLINE) + case SOCKET_OPT_SOCK_OOBINLINE: + result = ngetopt_lvl_sock_oobinline(env, descP); + break; +#endif + #if defined(SO_PEEK_OFF) case SOCKET_OPT_SOCK_PEEK_OFF: result = ngetopt_lvl_sock_peek_off(env, descP); @@ -5001,6 +5034,16 @@ ERL_NIF_TERM ngetopt_lvl_sock_linger(ErlNifEnv* env, #endif +#if defined(SO_OOBINLINE) +static +ERL_NIF_TERM ngetopt_lvl_sock_oobinline(ErlNifEnv* env, + SocketDescriptor* descP) +{ + return ngetopt_bool_opt(env, descP, SOL_SOCKET, SO_OOBINLINE); +} +#endif + + #if defined(SO_PEEK_OFF) static ERL_NIF_TERM ngetopt_lvl_sock_peek_off(ErlNifEnv* env, diff --git a/erts/preloaded/ebin/socket.beam b/erts/preloaded/ebin/socket.beam index 9c10182bb4..4e8c848539 100644 Binary files a/erts/preloaded/ebin/socket.beam and b/erts/preloaded/ebin/socket.beam differ diff --git a/erts/preloaded/src/socket.erl b/erts/preloaded/src/socket.erl index 8f489bd681..278a3f17ad 100644 --- a/erts/preloaded/src/socket.erl +++ b/erts/preloaded/src/socket.erl @@ -476,16 +476,26 @@ -define(SOCKET_OPT_SOCK_KEEPALIVE, 10). -define(SOCKET_OPT_SOCK_LINGER, 11). %% -define(SOCKET_OPT_SOCK_MARK, 12). -%% -define(SOCKET_OPT_SOCK_OOBINLLINE, 13). +-define(SOCKET_OPT_SOCK_OOBINLINE, 13). %% -define(SOCKET_OPT_SOCK_PASSCRED, 14). --define(SOCKET_OPT_SOCK_PEEK_OFF, 16). -%% -define(SOCKET_OPT_SOCK_PEEKCRED, 17). --define(SOCKET_OPT_SOCK_PRIORITY, 18). --define(SOCKET_OPT_SOCK_PROTOCOL, 19). --define(SOCKET_OPT_SOCK_RCVBUF, 20). --define(SOCKET_OPT_SOCK_REUSEADDR, 24). --define(SOCKET_OPT_SOCK_SNDBUF, 30). --define(SOCKET_OPT_SOCK_TYPE, 35). +-define(SOCKET_OPT_SOCK_PEEK_OFF, 15). +%% -define(SOCKET_OPT_SOCK_PEEKCRED, 16). +-define(SOCKET_OPT_SOCK_PRIORITY, 17). +-define(SOCKET_OPT_SOCK_PROTOCOL, 18). +-define(SOCKET_OPT_SOCK_RCVBUF, 19). +%% -define(SOCKET_OPT_SOCK_RCVBUFFORCE, 20). +%% -define(SOCKET_OPT_SOCK_RCVLOWAT, 21). +%% -define(SOCKET_OPT_SOCK_RCVTIMEO, 22). +-define(SOCKET_OPT_SOCK_REUSEADDR, 23). +%% -define(SOCKET_OPT_SOCK_REUSEPORT, 24). +%% -define(SOCKET_OPT_SOCK_RXQ_OVFL, 25). +%% -define(SOCKET_OPT_SOCK_SETFIB, 26). +-define(SOCKET_OPT_SOCK_SNDBUF, 27). +%% -define(SOCKET_OPT_SOCK_SNDBUFFORCE, 28). +%% -define(SOCKET_OPT_SOCK_SNDLOWAT, 29). +%% -define(SOCKET_OPT_SOCK_SNDTIMEO, 30). +%% -define(SOCKET_OPT_SOCK_TIMESTAMP, 31). +-define(SOCKET_OPT_SOCK_TYPE, 32). -define(SOCKET_OPT_IP_RECVTOS, 25). -define(SOCKET_OPT_IP_ROUTER_ALERT, 28). @@ -1840,6 +1850,8 @@ enc_setopt_value(socket, linger, abort, D, T, P) -> enc_setopt_value(socket, linger, {OnOff, Secs} = V, _D, _T, _P) when is_boolean(OnOff) andalso is_integer(Secs) andalso (Secs >= 0) -> V; +enc_setopt_value(socket, oobinline, V, _D, _T, _P) when is_boolean(V) -> + V; enc_setopt_value(socket, peek_off, V, _D, _T, _P) when is_integer(V) -> V; enc_setopt_value(socket, priority, V, _D, _T, _P) when is_integer(V) -> @@ -2066,8 +2078,8 @@ enc_sockopt_key(socket, linger = _Opt, _Dir, _D, _T, _P) -> ?SOCKET_OPT_SOCK_LINGER; enc_sockopt_key(socket = L, mark = Opt, _Dir, _D, _T, _P) -> not_supported({L, Opt}); -enc_sockopt_key(socket, oobinline = Opt, _Dir, _D, _T, _P) -> - not_supported(Opt); +enc_sockopt_key(socket = _L, oobinline = _Opt, _Dir, _D, _T, _P) -> + ?SOCKET_OPT_SOCK_OOBINLINE; enc_sockopt_key(socket = L, passcred = Opt, _Dir, _D, _T, _P) -> not_supported({L, Opt}); enc_sockopt_key(socket = _L, peek_off = _Opt, _Dir, local = _D, _T, _P) -> diff --git a/lib/kernel/test/socket_server.erl b/lib/kernel/test/socket_server.erl index e5338644b1..da5a2ca905 100644 --- a/lib/kernel/test/socket_server.erl +++ b/lib/kernel/test/socket_server.erl @@ -438,11 +438,13 @@ handler_init(Manager, ID, Peek, Sock) -> {ok, Domain} = socket:getopt(Sock, socket, domain), {ok, Type} = socket:getopt(Sock, socket, type), {ok, Proto} = socket:getopt(Sock, socket, protocol), + {ok, OOBI} = socket:getopt(Sock, socket, oobinline), i("got continue when: " - "~n Domain: ~p" - "~n Type: ~p" - "~n Protocol: ~p", [Domain, Type, Proto]), - %% socket:setopt(Socket, otp, debug, true), + "~n Domain: ~p" + "~n Type: ~p" + "~n Protocol: ~p" + "~n OOBInline: ~p", [Domain, Type, Proto, OOBI]), + %% socket:setopt(Sock, otp, debug, true), handler_loop(#handler{peek = Peek, manager = Manager, type = Type, -- cgit v1.2.3 From 8936b71f13b37559600afb5536ff7d7878b9ab0e Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Tue, 10 Jul 2018 13:42:28 +0200 Subject: [socket-nif] Add support for socket (level ip) option multicast_loop Added support for the IP option MULTICAST_LOOP. OTP-14831 --- erts/emulator/nifs/common/socket_nif.c | 89 +++++++++++++++++++---- erts/preloaded/ebin/socket.beam | Bin 43748 -> 43784 bytes erts/preloaded/src/socket.erl | 127 ++++++++++++++++++++++++++++----- lib/kernel/test/socket_server.erl | 18 +++-- 4 files changed, 198 insertions(+), 36 deletions(-) diff --git a/erts/emulator/nifs/common/socket_nif.c b/erts/emulator/nifs/common/socket_nif.c index 82468c6e8f..95f29f3dd8 100644 --- a/erts/emulator/nifs/common/socket_nif.c +++ b/erts/emulator/nifs/common/socket_nif.c @@ -342,9 +342,9 @@ typedef union { #define SOCKET_OPT_LEVEL_UDP 5 #define SOCKET_OPT_LEVEL_SCTP 6 -#define SOCKET_OPT_OTP_DEBUG 0 -#define SOCKET_OPT_OTP_IOW 1 -#define SOCKET_OPT_OTP_CTRL_PROC 2 +#define SOCKET_OPT_OTP_DEBUG 1 +#define SOCKET_OPT_OTP_IOW 2 +#define SOCKET_OPT_OTP_CTRL_PROC 3 #define SOCKET_OPT_SOCK_ACCEPTCONN 1 #define SOCKET_OPT_SOCK_BROADCAST 4 @@ -362,22 +362,23 @@ typedef union { #define SOCKET_OPT_SOCK_SNDBUF 27 #define SOCKET_OPT_SOCK_TYPE 32 -#define SOCKET_OPT_IP_RECVTOS 25 -#define SOCKET_OPT_IP_ROUTER_ALERT 28 -#define SOCKET_OPT_IP_TOS 30 -#define SOCKET_OPT_IP_TTL 32 +#define SOCKET_OPT_IP_MULTICAST_LOOP 15 +#define SOCKET_OPT_IP_RECVTOS 25 +#define SOCKET_OPT_IP_ROUTER_ALERT 28 +#define SOCKET_OPT_IP_TOS 30 +#define SOCKET_OPT_IP_TTL 32 #define SOCKET_OPT_IPV6_HOPLIMIT 12 -#define SOCKET_OPT_TCP_CONGESTION 0 -#define SOCKET_OPT_TCP_CORK 1 -#define SOCKET_OPT_TCP_MAXSEG 2 -#define SOCKET_OPT_TCP_NODELAY 3 +#define SOCKET_OPT_TCP_CONGESTION 1 +#define SOCKET_OPT_TCP_CORK 2 +#define SOCKET_OPT_TCP_MAXSEG 3 +#define SOCKET_OPT_TCP_NODELAY 4 -#define SOCKET_OPT_UDP_CORK 0 +#define SOCKET_OPT_UDP_CORK 1 -#define SOCKET_OPT_SCTP_AUTOCLOSE 7 -#define SOCKET_OPT_SCTP_NODELAY 22 +#define SOCKET_OPT_SCTP_AUTOCLOSE 8 +#define SOCKET_OPT_SCTP_NODELAY 23 /* =================================================================== * @@ -843,6 +844,11 @@ static ERL_NIF_TERM nsetopt_lvl_ip(ErlNifEnv* env, SocketDescriptor* descP, int eOpt, ERL_NIF_TERM eVal); +#if defined(IP_MULTICAST_LOOP) +static ERL_NIF_TERM nsetopt_lvl_ip_multicast_loop(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); +#endif #if defined(IP_RECVTOS) static ERL_NIF_TERM nsetopt_lvl_ip_recvtos(ErlNifEnv* env, SocketDescriptor* descP, @@ -1011,6 +1017,10 @@ static ERL_NIF_TERM ngetopt_lvl_sock_type(ErlNifEnv* env, static ERL_NIF_TERM ngetopt_lvl_ip(ErlNifEnv* env, SocketDescriptor* descP, int eOpt); +#if defined(IP_MULTICAST_LOOP) +static ERL_NIF_TERM ngetopt_lvl_ip_multicast_loop(ErlNifEnv* env, + SocketDescriptor* descP); +#endif #if defined(IP_RECVTOS) static ERL_NIF_TERM ngetopt_lvl_ip_recvtos(ErlNifEnv* env, SocketDescriptor* descP); @@ -3936,6 +3946,12 @@ ERL_NIF_TERM nsetopt_lvl_ip(ErlNifEnv* env, ERL_NIF_TERM result; switch (eOpt) { +#if defined(IP_MULTICAST_LOOP) + case SOCKET_OPT_IP_MULTICAST_LOOP: + result = nsetopt_lvl_ip_multicast_loop(env, descP, eVal); + break; +#endif + #if defined(IP_RECVTOS) case SOCKET_OPT_IP_RECVTOS: result = nsetopt_lvl_ip_recvtos(env, descP, eVal); @@ -3969,6 +3985,25 @@ ERL_NIF_TERM nsetopt_lvl_ip(ErlNifEnv* env, } +/* nsetopt_lvl_ip_multicast_loop - Level IP MULTICAST_LOOP option + */ +#if defined(IP_MULTICAST_LOOP) +static +ERL_NIF_TERM nsetopt_lvl_ip_multicast_loop(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) +{ +#if defined(SOL_IP) + int level = SOL_IP; +#else + int level = IPPROTO_IP; +#endif + + return nsetopt_bool_opt(env, descP, level, IP_MULTICAST_LOOP, eVal); +} +#endif + + /* nsetopt_lvl_ip_recvtos - Level IP RECVTOS option */ #if defined(IP_RECVTOS) @@ -5022,7 +5057,7 @@ ERL_NIF_TERM ngetopt_lvl_sock_linger(ErlNifEnv* env, if (res != 0) { result = esock_make_error_errno(env, res); } else { - ERL_NIF_TERM lOnOff = MKI(env, val.l_onoff); + ERL_NIF_TERM lOnOff = ((val.l_onoff) ? atom_true : atom_false); ERL_NIF_TERM lSecs = MKI(env, val.l_linger); ERL_NIF_TERM linger = MKT2(env, lOnOff, lSecs); @@ -5196,6 +5231,12 @@ ERL_NIF_TERM ngetopt_lvl_ip(ErlNifEnv* env, ERL_NIF_TERM result; switch (eOpt) { +#if defined(IP_MULTICAST_LOOP) + case SOCKET_OPT_IP_MULTICAST_LOOP: + result = ngetopt_lvl_ip_multicast_loop(env, descP); + break; +#endif + #if defined(IP_RECVTOS) case SOCKET_OPT_IP_RECVTOS: result = ngetopt_lvl_ip_recvtos(env, descP); @@ -5229,6 +5270,24 @@ ERL_NIF_TERM ngetopt_lvl_ip(ErlNifEnv* env, } +/* ngetopt_lvl_ip_multicast_loop - Level IP MULTICAST_LOOP option + */ +#if defined(IP_RECVTOS) +static +ERL_NIF_TERM ngetopt_lvl_ip_multicast_loop(ErlNifEnv* env, + SocketDescriptor* descP) +{ +#if defined(SOL_IP) + int level = SOL_IP; +#else + int level = IPPROTO_IP; +#endif + + return ngetopt_bool_opt(env, descP, level, IP_MULTICAST_LOOP); +} +#endif + + /* ngetopt_lvl_ip_recvtos - Level IP RECVTOS option */ #if defined(IP_RECVTOS) diff --git a/erts/preloaded/ebin/socket.beam b/erts/preloaded/ebin/socket.beam index 4e8c848539..0f49c421fa 100644 Binary files a/erts/preloaded/ebin/socket.beam and b/erts/preloaded/ebin/socket.beam differ diff --git a/erts/preloaded/src/socket.erl b/erts/preloaded/src/socket.erl index 278a3f17ad..d2ed733b3c 100644 --- a/erts/preloaded/src/socket.erl +++ b/erts/preloaded/src/socket.erl @@ -460,9 +460,9 @@ -define(SOCKET_OPT_LEVEL_UDP, 5). -define(SOCKET_OPT_LEVEL_SCTP, 6). --define(SOCKET_OPT_OTP_DEBUG, 0). --define(SOCKET_OPT_OTP_IOW, 1). --define(SOCKET_OPT_OTP_CTRL_PROC, 2). +-define(SOCKET_OPT_OTP_DEBUG, 1). +-define(SOCKET_OPT_OTP_IOW, 2). +-define(SOCKET_OPT_OTP_CTRL_PROC, 3). -define(SOCKET_OPT_SOCK_ACCEPTCONN, 1). %% -define(SOCKET_OPT_SOCK_ACCEPTFILTER, 2). @@ -497,22 +497,112 @@ %% -define(SOCKET_OPT_SOCK_TIMESTAMP, 31). -define(SOCKET_OPT_SOCK_TYPE, 32). --define(SOCKET_OPT_IP_RECVTOS, 25). --define(SOCKET_OPT_IP_ROUTER_ALERT, 28). --define(SOCKET_OPT_IP_TOS, 30). --define(SOCKET_OPT_IP_TTL, 32). - --define(SOCKET_OPT_IPV6_HOPLIMIT, 12). - --define(SOCKET_OPT_TCP_CONGESTION, 0). +%% -define(SOCKET_OPT_IP_ADD_MEMBERSHIP, 1). +%% -define(SOCKET_OPT_IP_ADD_SOURCE_MEMBERSHIP, 2). +%% -define(SOCKET_OPT_IP_BLOCK_SOURCE, 3). +%% -define(SOCKET_OPT_IP_DONT_FRAG, 4). +%% -define(SOCKET_OPT_IP_DROP_MEMBERSHIP, 5). +%% -define(SOCKET_OPT_IP_DROP_SOURCE_MEMBERSHIP, 6). +%% -define(SOCKET_OPT_IP_FREEBIND, 7). +%% -define(SOCKET_OPT_IP_HDRINCL, 8). +%% -define(SOCKET_OPT_IP_MINTTL, 9). +%% -define(SOCKET_OPT_IP_MSFILTER, 10). +%% -define(SOCKET_OPT_IP_MTU, 11). +%% -define(SOCKET_OPT_IP_MTU_DISCOVER, 12). +%% -define(SOCKET_OPT_IP_MULTICAST_ALL, 13). +%% -define(SOCKET_OPT_IP_MULTICAST_IF, 14). +-define(SOCKET_OPT_IP_MULTICAST_LOOP, 15). +%% -define(SOCKET_OPT_IP_MULTICAST_TTL, 16). +%% -define(SOCKET_OPT_IP_NODEFRAG, 17). +%% -define(SOCKET_OPT_IP_OPTIONS, 18). +%% -define(SOCKET_OPT_IP_PKTINFO, 19). +%% -define(SOCKET_OPT_IP_RECVERR, 20). +%% -define(SOCKET_OPT_IP_RECVIF, 21). +%% -define(SOCKET_OPT_IP_RECVDSTADDR, 22). +%% -define(SOCKET_OPT_IP_RECVOPTS, 23). +%% -define(SOCKET_OPT_IP_RECVORIGDSTADDR, 24). +-define(SOCKET_OPT_IP_RECVTOS, 25). +%% -define(SOCKET_OPT_IP_RECVTTL, 26). +%% -define(SOCKET_OPT_IP_RETOPTS, 27). +-define(SOCKET_OPT_IP_ROUTER_ALERT, 28). +%% -define(SOCKET_OPT_IP_SNDSRCADDR, 29). +-define(SOCKET_OPT_IP_TOS, 30). +%% -define(SOCKET_OPT_IP_TRANSPARENT, 31). +-define(SOCKET_OPT_IP_TTL, 32). +-define(SOCKET_OPT_IP_UNBLOCK_SOURCE, 33). + +%% -define(SOCKET_OPT_IPV6_ADDFORM, 1). +%% -define(SOCKET_OPT_IPV6_ADD_MEMBERSHIP, 2). +%% -define(SOCKET_OPT_IPV6_AUTHHDR, 3). +%% -define(SOCKET_OPT_IPV6_AUTH_LEVEL, 4). +%% -define(SOCKET_OPT_IPV6_CHECKSUM, 5). +%% -define(SOCKET_OPT_IPV6_DROP_MEMBERSHIP, 6). +%% -define(SOCKET_OPT_IPV6_DSTOPTS, 7). +%% -define(SOCKET_OPT_IPV6_ESP_TRANS_LEVEL, 8). +%% -define(SOCKET_OPT_IPV6_ESP_NETWORK_LEVEL, 9). +%% -define(SOCKET_OPT_IPV6_FAITH, 10). +%% -define(SOCKET_OPT_IPV6_FLOWINFO, 11). +-define(SOCKET_OPT_IPV6_HOPLIMIT, 12). +%% -define(SOCKET_OPT_IPV6_HOPOPTS, 13). +%% -define(SOCKET_OPT_IPV6_IPCOMP_LEVEL, 14). +%% -define(SOCKET_OPT_IPV6_JOIN_GROUP, 15). +%% -define(SOCKET_OPT_IPV6_LEAVE_GROUP, 16). +%% -define(SOCKET_OPT_IPV6_MTU, 17). +%% -define(SOCKET_OPT_IPV6_MTU_DISCOVER, 18). +%% -define(SOCKET_OPT_IPV6_MULTICAST_HOPS, 19). +%% -define(SOCKET_OPT_IPV6_MULTICAST_IF, 20). +%% -define(SOCKET_OPT_IPV6_MULTICAST_LOOP, 21). +%% -define(SOCKET_OPT_IPV6_PORTRANGE, 22). +%% -define(SOCKET_OPT_IPV6_PKTINFO, 23). +%% -define(SOCKET_OPT_IPV6_PKTOPTIONS, 24). +%% -define(SOCKET_OPT_IPV6_RECVERR, 25). +%% -define(SOCKET_OPT_IPV6_RECVPKTINFO, 26). +%% -define(SOCKET_OPT_IPV6_RECVTCLASS, 27). +%% -define(SOCKET_OPT_IPV6_ROUTER_ALERT, 28). +%% -define(SOCKET_OPT_IPV6_RTHDR, 29). +%% -define(SOCKET_OPT_IPV6_TCLASS, 30). +%% -define(SOCKET_OPT_IPV6_UNICAST_HOPS, 31). +%% -define(SOCKET_OPT_IPV6_USE_MIN_MTU, 32). +%% -define(SOCKET_OPT_IPV6_V6ONLY, 33). + +-define(SOCKET_OPT_TCP_CONGESTION, 1). -define(SOCKET_OPT_TCP_CORK, 2). -define(SOCKET_OPT_TCP_MAXSEG, 3). -define(SOCKET_OPT_TCP_NODELAY, 4). --define(SOCKET_OPT_UDP_CORK, 0). - --define(SOCKET_OPT_SCTP_AUTOCLOSE, 7). --define(SOCKET_OPT_SCTP_NODELAY, 22). +-define(SOCKET_OPT_UDP_CORK, 1). + +%% -define(SOCKET_OPT_SCTP_ADAPTION_LAYER, 1). +%% -define(SOCKET_OPT_SCTP_ASSOCINFO, 2). +%% -define(SOCKET_OPT_SCTP_AUTH_ACTIVE_KEY, 3). +%% -define(SOCKET_OPT_SCTP_AUTH_ASCONF, 4). +%% -define(SOCKET_OPT_SCTP_AUTH_CHUNK, 5). +%% -define(SOCKET_OPT_SCTP_AUTH_KEY, 6). +%% -define(SOCKET_OPT_SCTP_AUTH_DELETE_KEY, 7). +-define(SOCKET_OPT_SCTP_AUTOCLOSE, 8). +%% -define(SOCKET_OPT_SCTP_CONTEXT, 9). +%% -define(SOCKET_OPT_SCTP_DEFAULT_SEND_PARAMS, 10). +%% -define(SOCKET_OPT_SCTP_DELAYED_ACK_TIME, 11). +%% -define(SOCKET_OPT_SCTP_DISABLE_FRAGMENTS, 12). +%% -define(SOCKET_OPT_SCTP_HMAC_IDENT, 13). +%% -define(SOCKET_OPT_SCTP_EXPLICIT_EOR, 14). +%% -define(SOCKET_OPT_SCTP_FRAGMENT_INTERLEAVE, 15). +%% -define(SOCKET_OPT_SCTP_GET_PEER_ADDR_INFO, 16). +%% -define(SOCKET_OPT_SCTP_INITMSG, 17). +%% -define(SOCKET_OPT_SCTP_I_WANT_MAPPED_V4_ADDR, 18). +%% -define(SOCKET_OPT_SCTP_LOCAL_AUTH_CHUNKS, 19). +%% -define(SOCKET_OPT_SCTP_MAXSEG, 20). +%% -define(SOCKET_OPT_SCTP_MAXBURST, 21). +-define(SOCKET_OPT_SCTP_NODELAY, 22). +%% -define(SOCKET_OPT_SCTP_PARTIAL_DELIVERY_POINT, 23). +%% -define(SOCKET_OPT_SCTP_PEER_ADDR_PARAMS, 24). +%% -define(SOCKET_OPT_SCTP_PEER_AUTH_CHUNKS, 25). +%% -define(SOCKET_OPT_SCTP_PRIMARY_ADDR, 26). +%% -define(SOCKET_OPT_SCTP_RESET_STREAMS, 27). +%% -define(SOCKET_OPT_SCTP_RTOINFO, 28). +%% -define(SOCKET_OPT_SCTP_SET_PEER_PRIMARY_ADDR, 29). +%% -define(SOCKET_OPT_SCTP_STATUS, 30). +%% -define(SOCKET_OPT_SCTP_USE_EXT_RECVINFO, 31). -define(SOCKET_SHUTDOWN_HOW_READ, 0). -define(SOCKET_SHUTDOWN_HOW_WRITE, 1). @@ -1865,6 +1955,9 @@ enc_setopt_value(socket, sndbuf, V, _D, _T, _P) when is_integer(V) -> enc_setopt_value(socket = L, Opt, V, _D, _T, _P) -> not_supported({L, Opt, V}); +enc_setopt_value(ip, multicast_loop, V, _D, _T, _P) + when is_boolean(V) -> + V; enc_setopt_value(ip, recvtos, V, _D, _T, _P) when is_boolean(V) -> V; @@ -2157,8 +2250,8 @@ enc_sockopt_key(ip = L, multicast_all = Opt, _Dir, _D, _T, _P) -> not_supported({L, Opt}); enc_sockopt_key(ip = L, multicast_if = Opt, _Dir, _D, _T, _P) -> not_supported({L, Opt}); -enc_sockopt_key(ip = L, multicast_loop = Opt, _Dir, _D, _T, _P) -> - not_supported({L, Opt}); +enc_sockopt_key(ip = _L, multicast_loop = _Opt, _Dir, _D, _T, _P) -> + ?SOCKET_OPT_IP_MULTICAST_LOOP; enc_sockopt_key(ip = L, multicast_ttl = Opt, _Dir, _D, _T, _P) -> not_supported({L, Opt}); enc_sockopt_key(ip = L, nodefrag = Opt, _Dir, _D, raw = _T, _P) -> diff --git a/lib/kernel/test/socket_server.erl b/lib/kernel/test/socket_server.erl index da5a2ca905..9c6ef47fbc 100644 --- a/lib/kernel/test/socket_server.erl +++ b/lib/kernel/test/socket_server.erl @@ -439,11 +439,21 @@ handler_init(Manager, ID, Peek, Sock) -> {ok, Type} = socket:getopt(Sock, socket, type), {ok, Proto} = socket:getopt(Sock, socket, protocol), {ok, OOBI} = socket:getopt(Sock, socket, oobinline), + {ok, SndBuf} = socket:getopt(Sock, socket, sndbuf), + {ok, RcvBuf} = socket:getopt(Sock, socket, rcvbuf), + {ok, Linger} = socket:getopt(Sock, socket, linger), + {ok, ML} = socket:getopt(Sock, ip, multicast_loop), i("got continue when: " - "~n Domain: ~p" - "~n Type: ~p" - "~n Protocol: ~p" - "~n OOBInline: ~p", [Domain, Type, Proto, OOBI]), + "~n (socket) Domain: ~p" + "~n (socket) Type: ~p" + "~n (socket) Protocol: ~p" + "~n (socket) OOBInline: ~p" + "~n (socket) SndBuf: ~p" + "~n (socket) RcvBuf: ~p" + "~n (socket) Linger: ~p" + "~n (ip) Multicast Loop: ~p", + [Domain, Type, Proto, + OOBI, SndBuf, RcvBuf, Linger, ML]), %% socket:setopt(Sock, otp, debug, true), handler_loop(#handler{peek = Peek, manager = Manager, -- cgit v1.2.3 From 59586c412340d24188f78730c5b2c45db772c8ca Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Tue, 10 Jul 2018 14:01:00 +0200 Subject: [socket-nif] Add support for socket (level ip) option multicast_ttl Added support for the IP option MULTICAST_TTL. OTP-14831 --- erts/emulator/nifs/common/socket_nif.c | 61 ++++++++++++++++++++++++++++++++- erts/preloaded/ebin/socket.beam | Bin 43784 -> 43884 bytes erts/preloaded/src/socket.erl | 9 +++-- lib/kernel/test/socket_server.erl | 18 ++++------ 4 files changed, 72 insertions(+), 16 deletions(-) diff --git a/erts/emulator/nifs/common/socket_nif.c b/erts/emulator/nifs/common/socket_nif.c index 95f29f3dd8..f73364f28f 100644 --- a/erts/emulator/nifs/common/socket_nif.c +++ b/erts/emulator/nifs/common/socket_nif.c @@ -363,6 +363,7 @@ typedef union { #define SOCKET_OPT_SOCK_TYPE 32 #define SOCKET_OPT_IP_MULTICAST_LOOP 15 +#define SOCKET_OPT_IP_MULTICAST_TTL 16 #define SOCKET_OPT_IP_RECVTOS 25 #define SOCKET_OPT_IP_ROUTER_ALERT 28 #define SOCKET_OPT_IP_TOS 30 @@ -849,6 +850,11 @@ static ERL_NIF_TERM nsetopt_lvl_ip_multicast_loop(ErlNifEnv* env, SocketDescriptor* descP, ERL_NIF_TERM eVal); #endif +#if defined(IP_MULTICAST_TTL) +static ERL_NIF_TERM nsetopt_lvl_ip_multicast_ttl(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); +#endif #if defined(IP_RECVTOS) static ERL_NIF_TERM nsetopt_lvl_ip_recvtos(ErlNifEnv* env, SocketDescriptor* descP, @@ -1021,6 +1027,10 @@ static ERL_NIF_TERM ngetopt_lvl_ip(ErlNifEnv* env, static ERL_NIF_TERM ngetopt_lvl_ip_multicast_loop(ErlNifEnv* env, SocketDescriptor* descP); #endif +#if defined(IP_MULTICAST_TTL) +static ERL_NIF_TERM ngetopt_lvl_ip_multicast_ttl(ErlNifEnv* env, + SocketDescriptor* descP); +#endif #if defined(IP_RECVTOS) static ERL_NIF_TERM ngetopt_lvl_ip_recvtos(ErlNifEnv* env, SocketDescriptor* descP); @@ -3952,6 +3962,12 @@ ERL_NIF_TERM nsetopt_lvl_ip(ErlNifEnv* env, break; #endif +#if defined(IP_MULTICAST_TTL) + case SOCKET_OPT_IP_MULTICAST_TTL: + result = nsetopt_lvl_ip_multicast_ttl(env, descP, eVal); + break; +#endif + #if defined(IP_RECVTOS) case SOCKET_OPT_IP_RECVTOS: result = nsetopt_lvl_ip_recvtos(env, descP, eVal); @@ -4004,6 +4020,25 @@ ERL_NIF_TERM nsetopt_lvl_ip_multicast_loop(ErlNifEnv* env, #endif +/* nsetopt_lvl_ip_multicast_ttl - Level IP MULTICAST_TTL option + */ +#if defined(IP_MULTICAST_TTL) +static +ERL_NIF_TERM nsetopt_lvl_ip_multicast_ttl(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) +{ +#if defined(SOL_IP) + int level = SOL_IP; +#else + int level = IPPROTO_IP; +#endif + + return nsetopt_int_opt(env, descP, level, IP_MULTICAST_TTL, eVal); +} +#endif + + /* nsetopt_lvl_ip_recvtos - Level IP RECVTOS option */ #if defined(IP_RECVTOS) @@ -5237,6 +5272,12 @@ ERL_NIF_TERM ngetopt_lvl_ip(ErlNifEnv* env, break; #endif +#if defined(IP_MULTICAST_TTL) + case SOCKET_OPT_IP_MULTICAST_TTL: + result = ngetopt_lvl_ip_multicast_ttl(env, descP); + break; +#endif + #if defined(IP_RECVTOS) case SOCKET_OPT_IP_RECVTOS: result = ngetopt_lvl_ip_recvtos(env, descP); @@ -5272,7 +5313,7 @@ ERL_NIF_TERM ngetopt_lvl_ip(ErlNifEnv* env, /* ngetopt_lvl_ip_multicast_loop - Level IP MULTICAST_LOOP option */ -#if defined(IP_RECVTOS) +#if defined(IP_MULTICAST_LOOP) static ERL_NIF_TERM ngetopt_lvl_ip_multicast_loop(ErlNifEnv* env, SocketDescriptor* descP) @@ -5288,6 +5329,24 @@ ERL_NIF_TERM ngetopt_lvl_ip_multicast_loop(ErlNifEnv* env, #endif +/* ngetopt_lvl_ip_multicast_ttl - Level IP MULTICAST_TTL option + */ +#if defined(IP_MULTICAST_TTL) +static +ERL_NIF_TERM ngetopt_lvl_ip_multicast_ttl(ErlNifEnv* env, + SocketDescriptor* descP) +{ +#if defined(SOL_IP) + int level = SOL_IP; +#else + int level = IPPROTO_IP; +#endif + + return ngetopt_int_opt(env, descP, level, IP_MULTICAST_TTL); +} +#endif + + /* ngetopt_lvl_ip_recvtos - Level IP RECVTOS option */ #if defined(IP_RECVTOS) diff --git a/erts/preloaded/ebin/socket.beam b/erts/preloaded/ebin/socket.beam index 0f49c421fa..4ffa22f70d 100644 Binary files a/erts/preloaded/ebin/socket.beam and b/erts/preloaded/ebin/socket.beam differ diff --git a/erts/preloaded/src/socket.erl b/erts/preloaded/src/socket.erl index d2ed733b3c..f243559ceb 100644 --- a/erts/preloaded/src/socket.erl +++ b/erts/preloaded/src/socket.erl @@ -512,7 +512,7 @@ %% -define(SOCKET_OPT_IP_MULTICAST_ALL, 13). %% -define(SOCKET_OPT_IP_MULTICAST_IF, 14). -define(SOCKET_OPT_IP_MULTICAST_LOOP, 15). -%% -define(SOCKET_OPT_IP_MULTICAST_TTL, 16). +-define(SOCKET_OPT_IP_MULTICAST_TTL, 16). %% -define(SOCKET_OPT_IP_NODEFRAG, 17). %% -define(SOCKET_OPT_IP_OPTIONS, 18). %% -define(SOCKET_OPT_IP_PKTINFO, 19). @@ -1958,6 +1958,9 @@ enc_setopt_value(socket = L, Opt, V, _D, _T, _P) -> enc_setopt_value(ip, multicast_loop, V, _D, _T, _P) when is_boolean(V) -> V; +enc_setopt_value(ip, multicast_ttl, V, _D, _T, _P) + when is_integer(V) andalso (0 =< V) andalso (V =< 255) -> + V; enc_setopt_value(ip, recvtos, V, _D, _T, _P) when is_boolean(V) -> V; @@ -2252,8 +2255,8 @@ enc_sockopt_key(ip = L, multicast_if = Opt, _Dir, _D, _T, _P) -> not_supported({L, Opt}); enc_sockopt_key(ip = _L, multicast_loop = _Opt, _Dir, _D, _T, _P) -> ?SOCKET_OPT_IP_MULTICAST_LOOP; -enc_sockopt_key(ip = L, multicast_ttl = Opt, _Dir, _D, _T, _P) -> - not_supported({L, Opt}); +enc_sockopt_key(ip = _L, multicast_ttl = _Opt, _Dir, _D, _T, _P) -> + ?SOCKET_OPT_IP_MULTICAST_TTL; enc_sockopt_key(ip = L, nodefrag = Opt, _Dir, _D, raw = _T, _P) -> not_supported({L, Opt}); enc_sockopt_key(ip = L, options = Opt, _Dir, _D, _T, _P) -> diff --git a/lib/kernel/test/socket_server.erl b/lib/kernel/test/socket_server.erl index 9c6ef47fbc..9e4e355179 100644 --- a/lib/kernel/test/socket_server.erl +++ b/lib/kernel/test/socket_server.erl @@ -283,18 +283,10 @@ acceptor_do_init(Domain, Type, Proto) -> end end, i("(socket) open (~s,~s,~s): " - "~n dontroute: ~s" - "~n keepalive: ~s" - "~n reuseaddr: ~s" - "~n linger: ~s" "~n debug: ~s" "~n prio: ~s" - "~n rcvbuf: ~s" - "~n sndbuf: ~s" "~n => try find (local) address", - [F(domain), F(type), F(protocol), - F(dontroute), F(keepalive), F(reuseaddr), F(linger), - F(debug), F(priority), F(rcvbuf), F(sndbuf)]), + [F(domain), F(type), F(protocol), F(debug), F(priority)]), Addr = which_addr(Domain), SA = #{family => Domain, addr => Addr}, @@ -442,7 +434,8 @@ handler_init(Manager, ID, Peek, Sock) -> {ok, SndBuf} = socket:getopt(Sock, socket, sndbuf), {ok, RcvBuf} = socket:getopt(Sock, socket, rcvbuf), {ok, Linger} = socket:getopt(Sock, socket, linger), - {ok, ML} = socket:getopt(Sock, ip, multicast_loop), + {ok, MLoop} = socket:getopt(Sock, ip, multicast_loop), + {ok, MTTL} = socket:getopt(Sock, ip, multicast_ttl), i("got continue when: " "~n (socket) Domain: ~p" "~n (socket) Type: ~p" @@ -451,9 +444,10 @@ handler_init(Manager, ID, Peek, Sock) -> "~n (socket) SndBuf: ~p" "~n (socket) RcvBuf: ~p" "~n (socket) Linger: ~p" - "~n (ip) Multicast Loop: ~p", + "~n (ip) Multicast Loop: ~p" + "~n (ip) Multicast TTL: ~p", [Domain, Type, Proto, - OOBI, SndBuf, RcvBuf, Linger, ML]), + OOBI, SndBuf, RcvBuf, Linger, MLoop, MTTL]), %% socket:setopt(Sock, otp, debug, true), handler_loop(#handler{peek = Peek, manager = Manager, -- cgit v1.2.3 From 8118227d80fc41efac23d30a5601fdfadb75931f Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Tue, 10 Jul 2018 17:41:40 +0200 Subject: [socket-nif] Add support for socket (level ip) option multicast_if Added support for the IP option MULTICAST_IF. OTP-14831 --- erts/emulator/nifs/common/socket_nif.c | 518 +++++++------------------------- erts/emulator/nifs/common/socket_util.c | 83 +++-- erts/emulator/nifs/common/socket_util.h | 18 +- erts/preloaded/ebin/socket.beam | Bin 43884 -> 44012 bytes erts/preloaded/src/socket.erl | 9 +- lib/kernel/test/socket_client.erl | 77 ++++- lib/kernel/test/socket_server.erl | 22 +- 7 files changed, 255 insertions(+), 472 deletions(-) diff --git a/erts/emulator/nifs/common/socket_nif.c b/erts/emulator/nifs/common/socket_nif.c index f73364f28f..05ba9e55f1 100644 --- a/erts/emulator/nifs/common/socket_nif.c +++ b/erts/emulator/nifs/common/socket_nif.c @@ -362,6 +362,7 @@ typedef union { #define SOCKET_OPT_SOCK_SNDBUF 27 #define SOCKET_OPT_SOCK_TYPE 32 +#define SOCKET_OPT_IP_MULTICAST_IF 14 #define SOCKET_OPT_IP_MULTICAST_LOOP 15 #define SOCKET_OPT_IP_MULTICAST_TTL 16 #define SOCKET_OPT_IP_RECVTOS 25 @@ -845,6 +846,11 @@ static ERL_NIF_TERM nsetopt_lvl_ip(ErlNifEnv* env, SocketDescriptor* descP, int eOpt, ERL_NIF_TERM eVal); +#if defined(IP_MULTICAST_IF) +static ERL_NIF_TERM nsetopt_lvl_ip_multicast_if(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); +#endif #if defined(IP_MULTICAST_LOOP) static ERL_NIF_TERM nsetopt_lvl_ip_multicast_loop(ErlNifEnv* env, SocketDescriptor* descP, @@ -1023,6 +1029,10 @@ static ERL_NIF_TERM ngetopt_lvl_sock_type(ErlNifEnv* env, static ERL_NIF_TERM ngetopt_lvl_ip(ErlNifEnv* env, SocketDescriptor* descP, int eOpt); +#if defined(IP_MULTICAST_IF) +static ERL_NIF_TERM ngetopt_lvl_ip_multicast_if(ErlNifEnv* env, + SocketDescriptor* descP); +#endif #if defined(IP_MULTICAST_LOOP) static ERL_NIF_TERM ngetopt_lvl_ip_multicast_loop(ErlNifEnv* env, SocketDescriptor* descP); @@ -1974,6 +1984,8 @@ ERL_NIF_TERM nbind(ErlNifEnv* env, return esock_make_error_errno(env, sock_errno()); } + SSDBG( descP, ("SOCKET", "nbind -> bound - get port\r\n") ); + port = which_address_port(sockAddrP); SSDBG( descP, ("SOCKET", "nbind -> port: %d\r\n", port) ); if (port == 0) { @@ -3646,7 +3658,7 @@ ERL_NIF_TERM nsetopt_native(ErlNifEnv* env, int res = socket_setopt(descP->sock, level, opt, val.data, val.size); if (res != 0) - result = esock_make_error_errno(env, res); + result = esock_make_error_errno(env, sock_errno()); else result = esock_atom_ok; } else { @@ -3866,7 +3878,7 @@ ERL_NIF_TERM nsetopt_lvl_sock_linger(ErlNifEnv* env, int res = socket_setopt(descP->sock, SOL_SOCKET, SO_LINGER, (void*) &val, optLen); if (res != 0) - result = esock_make_error_errno(env, res); + result = esock_make_error_errno(env, sock_errno()); else result = esock_atom_ok; } else { @@ -3956,6 +3968,12 @@ ERL_NIF_TERM nsetopt_lvl_ip(ErlNifEnv* env, ERL_NIF_TERM result; switch (eOpt) { +#if defined(IP_MULTICAST_IF) + case SOCKET_OPT_IP_MULTICAST_IF: + result = nsetopt_lvl_ip_multicast_if(env, descP, eVal); + break; +#endif + #if defined(IP_MULTICAST_LOOP) case SOCKET_OPT_IP_MULTICAST_LOOP: result = nsetopt_lvl_ip_multicast_loop(env, descP, eVal); @@ -4001,6 +4019,45 @@ ERL_NIF_TERM nsetopt_lvl_ip(ErlNifEnv* env, } +/* nsetopt_lvl_ip_multicast_if - Level IP MULTICAST_IF option + * + * The value is either the atom 'any' or a 4-tuple. + */ +#if defined(IP_MULTICAST_IF) +static +ERL_NIF_TERM nsetopt_lvl_ip_multicast_if(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) +{ + ERL_NIF_TERM result; + struct in_addr ifAddr; + char* xres; + int res; +#if defined(SOL_IP) + int level = SOL_IP; +#else + int level = IPPROTO_IP; +#endif + + if ((xres = esock_decode_ip4_address(env, eVal, &ifAddr)) != NULL) { + result = esock_make_error_str(env, xres); + } else { + + res = socket_setopt(descP->sock, level, IP_MULTICAST_LOOP, + &ifAddr, sizeof(ifAddr)); + + if (res != 0) + result = esock_make_error_errno(env, sock_errno()); + else + result = esock_atom_ok; + + } + + return result; +} +#endif + + /* nsetopt_lvl_ip_multicast_loop - Level IP MULTICAST_LOOP option */ #if defined(IP_MULTICAST_LOOP) @@ -4097,7 +4154,7 @@ ERL_NIF_TERM nsetopt_lvl_ip_tos(ErlNifEnv* env, int res = socket_setopt(descP->sock, level, IP_TOS, &val, sizeof(val)); if (res != 0) - result = esock_make_error_errno(env, res); + result = esock_make_error_errno(env, sock_errno()); else result = esock_atom_ok; @@ -4376,7 +4433,7 @@ ERL_NIF_TERM nsetopt_str_opt(ErlNifEnv* env, int res = socket_setopt(descP->sock, level, opt, &val, optLen); if (res != 0) - result = esock_make_error_errno(env, res); + result = esock_make_error_errno(env, sock_errno()); else result = esock_atom_ok; @@ -4409,7 +4466,7 @@ ERL_NIF_TERM nsetopt_bool_opt(ErlNifEnv* env, res = socket_setopt(descP->sock, level, opt, &ival, sizeof(ival)); if (res != 0) - result = esock_make_error_errno(env, res); + result = esock_make_error_errno(env, sock_errno()); else result = esock_atom_ok; @@ -4789,7 +4846,7 @@ ERL_NIF_TERM ngetopt_native_unspec(ErlNifEnv* env, if (valueSz == 0) { res = sock_getopt(descP->sock, level, opt, NULL, NULL); if (res != 0) - result = esock_make_error_errno(env, res); + result = esock_make_error_errno(env, sock_errno()); else result = esock_atom_ok; } else { @@ -4798,7 +4855,7 @@ ERL_NIF_TERM ngetopt_native_unspec(ErlNifEnv* env, if (ALLOC_BIN(valueSz, &val)) { res = sock_getopt(descP->sock, level, opt, val.data, &valueSz); if (res != 0) { - result = esock_make_error_errno(env, res); + result = esock_make_error_errno(env, sock_errno()); } else { if (valueSz < val.size) { if (REALLOC_BIN(&val, valueSz)) { @@ -5023,7 +5080,7 @@ ERL_NIF_TERM ngetopt_lvl_sock_domain(ErlNifEnv* env, &val, &valSz); if (res != 0) { - result = esock_make_error_errno(env, res); + result = esock_make_error_errno(env, sock_errno()); } else { switch (val) { case AF_INET: @@ -5090,7 +5147,7 @@ ERL_NIF_TERM ngetopt_lvl_sock_linger(ErlNifEnv* env, &val, &valSz); if (res != 0) { - result = esock_make_error_errno(env, res); + result = esock_make_error_errno(env, sock_errno()); } else { ERL_NIF_TERM lOnOff = ((val.l_onoff) ? atom_true : atom_false); ERL_NIF_TERM lSecs = MKI(env, val.l_linger); @@ -5148,7 +5205,7 @@ ERL_NIF_TERM ngetopt_lvl_sock_protocol(ErlNifEnv* env, &val, &valSz); if (res != 0) { - result = esock_make_error_errno(env, res); + result = esock_make_error_errno(env, sock_errno()); } else { switch (val) { case IPPROTO_IP: @@ -5224,7 +5281,7 @@ ERL_NIF_TERM ngetopt_lvl_sock_type(ErlNifEnv* env, res = sock_getopt(descP->sock, SOL_SOCKET, SO_TYPE, &val, &valSz); if (res != 0) { - result = esock_make_error_errno(env, res); + result = esock_make_error_errno(env, sock_errno()); } else { switch (val) { case SOCK_STREAM: @@ -5266,6 +5323,12 @@ ERL_NIF_TERM ngetopt_lvl_ip(ErlNifEnv* env, ERL_NIF_TERM result; switch (eOpt) { +#if defined(IP_MULTICAST_IF) + case SOCKET_OPT_IP_MULTICAST_IF: + result = ngetopt_lvl_ip_multicast_if(env, descP); + break; +#endif + #if defined(IP_MULTICAST_LOOP) case SOCKET_OPT_IP_MULTICAST_LOOP: result = ngetopt_lvl_ip_multicast_loop(env, descP); @@ -5311,6 +5374,43 @@ ERL_NIF_TERM ngetopt_lvl_ip(ErlNifEnv* env, } +/* ngetopt_lvl_ip_multicast_if - Level IP MULTICAST_IF option + */ +#if defined(IP_MULTICAST_IF) +static +ERL_NIF_TERM ngetopt_lvl_ip_multicast_if(ErlNifEnv* env, + SocketDescriptor* descP) +{ + ERL_NIF_TERM result; + ERL_NIF_TERM eAddr; + struct in_addr ifAddr; + SOCKOPTLEN_T ifAddrSz = sizeof(ifAddr); + char* xres; + int res; +#if defined(SOL_IP) + int level = SOL_IP; +#else + int level = IPPROTO_IP; +#endif + + res = sock_getopt(descP->sock, level, IP_MULTICAST_IF, &ifAddr, &ifAddrSz); + + if (res != 0) { + result = esock_make_error_errno(env, sock_errno()); + } else { + if ((xres = esock_encode_ip4_address(env, &ifAddr, &eAddr)) != NULL) { + result = esock_make_error_str(env, xres); + } else { + result = esock_make_ok2(env, eAddr); + } + } + + return result; + +} +#endif + + /* ngetopt_lvl_ip_multicast_loop - Level IP MULTICAST_LOOP option */ #if defined(IP_MULTICAST_LOOP) @@ -5403,7 +5503,7 @@ ERL_NIF_TERM ngetopt_lvl_ip_tos(ErlNifEnv* env, res = sock_getopt(descP->sock, level, IP_TOS, &val, &valSz); if (res != 0) { - result = esock_make_error_errno(env, res); + result = esock_make_error_errno(env, sock_errno()); } else { result = encode_ip_tos(env, val); } @@ -5663,7 +5763,7 @@ ERL_NIF_TERM ngetopt_str_opt(ErlNifEnv* env, res = sock_getopt(descP->sock, level, opt, &val, &valSz); if (res != 0) { - result = esock_make_error_errno(env, res); + result = esock_make_error_errno(env, sock_errno()); } else { ERL_NIF_TERM sval = MKSL(env, val, valSz); @@ -5692,7 +5792,7 @@ ERL_NIF_TERM ngetopt_bool_opt(ErlNifEnv* env, res = sock_getopt(descP->sock, level, opt, &val, &valSz); if (res != 0) { - result = esock_make_error_errno(env, res); + result = esock_make_error_errno(env, sock_errno()); } else { ERL_NIF_TERM bval = ((val) ? atom_true : atom_false); @@ -5719,7 +5819,7 @@ ERL_NIF_TERM ngetopt_int_opt(ErlNifEnv* env, res = sock_getopt(descP->sock, level, opt, &val, &valSz); if (res != 0) { - result = esock_make_error_errno(env, res); + result = esock_make_error_errno(env, sock_errno()); } else { result = esock_make_ok2(env, MKI(env, val)); } @@ -6936,394 +7036,6 @@ BOOLEAN_T ehow2how(unsigned int ehow, int* how) -/* +++ decode_sockaddr +++ - * - * Decode a socket address - sockaddr. In erlang its represented by - * a map, which has a specific set of attributes, depending on one - * mandatory attribute; family. So depending on the value of the family - * attribute: - * - * local - sockaddr_un: path - * inet - sockaddr_in4: port, addr - * inet6 - sockaddr_in6: port, addr, flowinfo, scope_id - */ -/* -static -char* decode_sockaddr(ErlNifEnv* env, - ERL_NIF_TERM eSockAddr, - SocketAddress* sockAddrP, - unsigned int* addrLen) -{ - ERL_NIF_TERM efam; - int fam; - char* res; - - if (!IS_MAP(env, eSockAddr)) - return ESOCK_STR_EINVAL; - - if (!GET_MAP_VAL(env, eSockAddr, esock_atom_family, &efam)) - return ESOCK_STR_EINVAL; - - if (!decode_domain(env, efam, &fam)) - return ESOCK_STR_EINVAL; - - switch (fam) { - case AF_INET: - res = decode_sockaddr_in4(env, eSockAddr, &sockAddrP->in6, addrLen); - break; - -#if defined(HAVE_IN6) && defined(AF_INET6) - case AF_INET6: - res = decode_sockaddr_in6(env, eSockAddr, &sockAddrP->in6, addrLen); - break; -#endif - -#ifdef HAVE_SYS_UN_H - case AF_UNIX: - res = decode_sockaddr_un(env, eSockAddr, &sockAddrP->un, addrLen); - break; -#endif - - default: - result = ESOCK_STR_EAFNOSUPPORT; - break; - - } - - return res; -} -*/ - - - -/* +++ decode_sockaddr_in4 +++ - * - * Decode a IPv4 socket address - sockaddr_in4. In erlang its represented by - * a map, which has a specific set of attributes: - * - * port :: port_numbber() - * addr :: ip4_address() - * - * The erlang module ensures that both of these has values exist, so there - * is no need for any elaborate error handling. - */ -/* -static -char* decode_sockaddr_in4(ErlNifEnv* env, - ERL_NIF_TERM eSockAddr, - struct sockaddr_in* sockAddrP, - unsigned int* addrLen) -{ - ERL_NIF_TERM eport, eaddr; - short port; - - / * Basic init * / - sys_memzero((char*) sockAddrP, sizeof(struct sockaddr_in)); - -#ifndef NO_SA_LEN - sockAddrP->sin_len = sizeof(struct sockaddr_in); -#endif - - sockAddrP->sin_family = AF_INET; - - / * Extract (e) port number from map * / - if (!GET_MAP_VAL(env, eSockAddr, atom_port, &eport)) - return ESOCK_STR_EINVAL; - - / * Decode port number * / - if (!GET_INT(env, eport, &port)) - return ESOCK_STR_EINVAL; - sockAddrP->sin_port = sock_htons(port); - - / * Extract (e) address from map * / - if (!GET_MAP_VAL(env, eSockAddr, atom_addr, &eaddr)) - return ESOCK_STR_EINVAL; - - / * Decode address * / - if (!decode_ip4_address(env, eaddr, sockAddrP, addrLen)) - return ESOCK_STR_EINVAL; - - return NULL; -} -*/ - - - -/* +++ decode_sockaddr_in6 +++ - * - * Decode a IPv6 socket address - sockaddr_in6. In erlang its represented by - * a map, which has a specific set of attributes: - * - * port :: port_numbber() (integer) - * addr :: ip6_address() (tuple) - * flowinfo :: in6_flow_info() (integer) - * scope_id :: in6_scope_id() (integer) - * - * The erlang module ensures that all of these has values exist, so there - * is no need for any elaborate error handling. - */ -/* -#if defined(HAVE_IN6) && defined(AF_INET6) -static -char* decode_sockaddr_in6(ErlNifEnv* env, - ERL_NIF_TERM eSockAddr, - struct sockaddr_in6* sockAddrP, - unsigned int* addrLen) -{ - ERL_NIF_TERM eport, eaddr; - short port; - - / * Basic init * / - sys_memzero((char*) sockAddrP, sizeof(struct sockaddr_in6)); -#ifndef NO_SA_LEN - sockAddrP->sin6_len = sizeof(struct sockaddr_in); -#endif - - sockAddrP->sin6_family = AF_INET6; - - / * *** Extract (e) port number from map *** * / - if (!GET_MAP_VAL(env, eSockAddr, atom_port, &eport)) - return ESOCK_STR_EINVAL; - - / * Decode port number * / - if (!GET_INT(env, eport, &port)) - return ESOCK_STR_EINVAL; - - sockAddrP->sin6_port = sock_htons(port); - - / * *** Extract (e) flowinfo from map *** * / - if (!GET_MAP_VAL(env, eSockAddr, atom_flowinfo, &eflowInfo)) - return ESOCK_STR_EINVAL; - - / * 4: Get the flowinfo * / - if (!GET_UINT(env, eflowInfo, &flowInfo)) - return ESOCK_STR_EINVAL; - - sockAddrP->sin6_flowinfo = flowInfo; - - / * *** Extract (e) scope_id from map *** * / - if (!GET_MAP_VAL(env, eSockAddr, atom_scope_id, &escopeId)) - return ESOCK_STR_EINVAL; - - / * *** Get the scope_id *** * / - if (!GET_UINT(env, escopeId, &scopeId)) - return ESOCK_STR_EINVAL; - - sockAddrP->sin6_scope_id = scopeId; - - / * *** Extract (e) address from map *** * / - if (!GET_MAP_VAL(env, eSockAddr, atom_addr, &eaddr)) - return ESOCK_STR_EINVAL; - - / * Decode address * / - if (!decode_ip6_address(env, eaddr, sockAddrP, addrLen)) - return ESOCK_STR_EINVAL; - - return NULL; -} -#endif -*/ - - - - -/* +++ decode_sockaddr_un +++ - * - * Decode a Unix Domain socket address - sockaddr_un. In erlang its represented by - * a map, which has a specific set of attributes: - * - * path :: binary() - * - * The erlang module ensures that this value exist, so there - * is no need for any elaborate error handling. - */ -/* -#ifdef HAVE_SYS_UN_H -static -char* decode_sockaddr_un(ErlNifEnv* env, - ERL_NIF_TERM eSockAddr, - struct sockaddr_un* sockAddrP, - unsigned int* addrLen) -{ - ErlNifBinary bin; - ERL_NIF_TERM epath; - unsigned int len; - - / * *** Extract (e) path (a binary) from map *** * / - if (!GET_MAP_VAL(env, eSockAddr, atom_port, &epath)) - return ESOCK_STR_EINVAL; - - / * Get the path * / - if (!GET_BIN(env, epath, &bin)) - return ESOCK_STR_EINVAL; - - if ((bin.size + -#ifdef __linux__ - / * Make sure the address gets zero terminated - * except when the first byte is \0 because then it is - * sort of zero terminated although the zero termination - * comes before the address... - * This fix handles Linux's nonportable - * abstract socket address extension. - * / - (bin.data[0] == '\0' ? 0 : 1) -#else - 1 -#endif - ) > sizeof(sockaAddrP->sun_path)) - return ESOCK_STR_EINVAL; - - - sys_memzero((char*) sockAddrP, sizeof(struct sockaddr_un)); - sockAddrP->sun_family = AF_UNIX; - - sys_memcpy(sockAddrP->sun_path, bin.data, bin.size); - len = offsetof(struct sockaddr_un, sun_path) + bin.size; - -#ifndef NO_SA_LEN - sockAddrP->sun_len = len; -#endif - *addrLen = len: - - return NULL; -} -#endif -*/ - - - -/* +++ decode_ip4_address +++ - * - * Decode a IPv4 address. This can be three things: - * - * + Then atom 'any' - * + Then atom 'loopback' - * + An ip4_address() (4 tuple) - * - * Note that this *only* decodes the "address" part of a - * (IPv4) socket address. There are several other things (port). - */ -/* -static -char* decode_ip4_address(ErlNifEnv* env, - ERL_NIF_TERM eAddr, - struct sockaddr_in* sockAddrP, - unsigned int* addrLen) -{ - if (IS_ATOM(env, eAddr)) { - / * This is either 'any' or 'loopback' * / - struct in_addr addr; - - if (COMPARE(esock_atom_loopback, eAddr) == 0) { - addr.s_addr = sock_htonl(INADDR_LOOPBACK); - } else if (COMPARE(esock_atom_any, eAddr) == 0) { - addr.s_addr = sock_htonl(INADDR_ANY); - } else { - return ESOCK_STR_EINVAL; - } - - sockAddrP->sin_addr.s_addr = addr.s_addr; - *addrLen = sizeof(struct sockaddr_in); - - } else { - / * This is a 4-tuple * / - - const ERL_NIF_TERM* addrt; - int addrtSz; - int a, v; - char addr[4]; - - if (!GET_TUPLE(env, eAddr, &addrtSz, &addrt)) - return ESOCK_STR_EINVAL; - - if (addrtSz != 4) - return ESOCK_STR_EINVAL; - - for (a = 0; a < 4; a++) { - if (!GET_INT(env, addrt[a], &v)) - return ESOCK_STR_EINVAL; - addr[a] = v; - } - - sys_memcpy(&sockAddrP->sin_addr, &addr, sizeof(addr)); - *addrLenP = sizeof(struct sockaddr_in); - - } - - return NULL; -} -*/ - - - -/* +++ decode_ip6_address +++ - * - * Decode a IPv6 address. This can be three things: - * - * + Then atom 'any' - * + Then atom 'loopback' - * + An ip6_address() (8 tuple) - * - * Note that this *only* decodes the "address" part of a - * (IPv6) socket address. There are several other things - * (port, flowinfo and scope_id) that are handled elsewhere). - */ -/* -#if defined(HAVE_IN6) && defined(AF_INET6) -static -char* decode_ip6_address(ErlNifEnv* env, - ERL_NIF_TERM eAddr, - struct sockaddr_in6* sockAddrP, - unsigned int* addrLen) -{ - if (IS_ATOM(env, eAddr)) { - / * This is either 'any' or 'loopback' * / - const struct in6_addr* addr; - - if (COMPARE(esock_atom_loopback, eAddr) == 0) { - addr = &in6addr_loopback; - } else if (COMPARE(esock_atom_any, eAddr) == 0) { - addr = &in6addr_any; - } else { - return ESOCK_STR_EINVAL; - } - - sockAddrP->sin6_addr = *addr; - *addrLen = sizeof(struct sockaddr_in6); - - } else { - / * This is a 8-tuple * / - - const ERL_NIF_TERM* addrt; - int addrtSz; - int a, v; - char addr[16]; - - if (!GET_TUPLE(env, eAddr, &addrtSz, &addrt)) - return ESOCK_STR_EINVAL; - - if (addrtSz != 8) - return ESOCK_STR_EINVAL; - - for (a = 0; a < 8; a++) { - if (!GET_INT(env, addrt[a], &v)) - return ESOCK_STR_EINVAL; - addr[a*2 ] = ((v >> 8) & 0xFF); - addr[a*2+1] = (v & 0xFF); - } - - sys_memcpy(&sockAddrP->sin6_addr, &addr, sizeof(addr)); - *addrLen = sizeof(struct sockaddr_in6); - - } - - return NULL; -} -#endif -*/ - - - #if defined(HAVE_SYS_UN_H) || defined(SO_BINDTODEVICE) /* strnlen doesn't exist everywhere */ /* diff --git a/erts/emulator/nifs/common/socket_util.c b/erts/emulator/nifs/common/socket_util.c index 5014688852..512c1d38e0 100644 --- a/erts/emulator/nifs/common/socket_util.c +++ b/erts/emulator/nifs/common/socket_util.c @@ -237,9 +237,13 @@ char* esock_decode_sockaddr_in4(ErlNifEnv* env, /* Decode address */ UDBG( ("SUTIL", "esock_decode_sockaddr_in4 -> try decode (ip) address\r\n") ); - if ((xres = esock_decode_ip4_address(env, eaddr, sockAddrP, addrLen)) != NULL) + if ((xres = esock_decode_ip4_address(env, + eaddr, + &sockAddrP->sin_addr)) != NULL) return xres; + *addrLen = sizeof(struct sockaddr_in); + UDBG( ("SUTIL", "esock_decode_sockaddr_in4 -> done\r\n") ); return NULL; @@ -330,6 +334,8 @@ char* esock_decode_sockaddr_in6(ErlNifEnv* env, unsigned int flowInfo, scopeId; char* xres; + UDBG( ("SUTIL", "esock_decode_sockaddr_in6 -> entry\r\n") ); + /* Basic init */ sys_memzero((char*) sockAddrP, sizeof(struct sockaddr_in6)); #ifndef NO_SA_LEN @@ -346,6 +352,8 @@ char* esock_decode_sockaddr_in6(ErlNifEnv* env, if (!GET_INT(env, eport, &port)) return ESOCK_STR_EINVAL; + UDBG( ("SUTIL", "esock_decode_sockaddr_in6 -> port: %d\r\n", port) ); + sockAddrP->sin6_port = htons(port); /* *** Extract (e) flowinfo from map *** */ @@ -356,6 +364,8 @@ char* esock_decode_sockaddr_in6(ErlNifEnv* env, if (!GET_UINT(env, eflowInfo, &flowInfo)) return ESOCK_STR_EINVAL; + UDBG( ("SUTIL", "esock_decode_sockaddr_in6 -> flowinfo: %d\r\n", flowInfo) ); + sockAddrP->sin6_flowinfo = flowInfo; /* *** Extract (e) scope_id from map *** */ @@ -366,6 +376,8 @@ char* esock_decode_sockaddr_in6(ErlNifEnv* env, if (!GET_UINT(env, escopeId, &scopeId)) return ESOCK_STR_EINVAL; + UDBG( ("SUTIL", "esock_decode_sockaddr_in6 -> scopeId: %d\r\n", scopeId) ); + sockAddrP->sin6_scope_id = scopeId; /* *** Extract (e) address from map *** */ @@ -373,9 +385,15 @@ char* esock_decode_sockaddr_in6(ErlNifEnv* env, return ESOCK_STR_EINVAL; /* Decode address */ - if ((xres = esock_decode_ip6_address(env, eaddr, sockAddrP, addrLen)) != NULL) + if ((xres = esock_decode_ip6_address(env, + eaddr, + &sockAddrP->sin6_addr)) != NULL) return xres; + *addrLen = sizeof(struct sockaddr_in6); + + UDBG( ("SUTIL", "esock_decode_sockaddr_in6 -> done\r\n") ); + return NULL; } #endif @@ -570,22 +588,22 @@ char* esock_encode_sockaddr_un(ErlNifEnv* env, * + An ip4_address() (4 tuple) * * Note that this *only* decodes the "address" part of a - * (IPv4) socket address. There are several other things (port). + * (IPv4) socket address. */ extern -char* esock_decode_ip4_address(ErlNifEnv* env, - ERL_NIF_TERM eAddr, - struct sockaddr_in* sockAddrP, - unsigned int* addrLen) +char* esock_decode_ip4_address(ErlNifEnv* env, + ERL_NIF_TERM eAddr, + struct in_addr* inAddrP) { + struct in_addr addr; + UDBG( ("SUTIL", "esock_decode_ip4_address -> entry with" "\r\n eAddr: %T" "\r\n", eAddr) ); if (IS_ATOM(env, eAddr)) { /* This is either 'any' or 'loopback' */ - struct in_addr addr; if (COMPARE(esock_atom_loopback, eAddr) == 0) { UDBG( ("SUTIL", "esock_decode_ip4_address -> address: lookback\r\n") ); @@ -598,8 +616,7 @@ char* esock_decode_ip4_address(ErlNifEnv* env, return ESOCK_STR_EINVAL; } - sockAddrP->sin_addr.s_addr = addr.s_addr; - *addrLen = sizeof(struct sockaddr_in); + inAddrP->s_addr = addr.s_addr; } else { /* This is a 4-tuple */ @@ -621,8 +638,7 @@ char* esock_decode_ip4_address(ErlNifEnv* env, addr[a] = v; } - sys_memcpy(&sockAddrP->sin_addr, &addr, sizeof(addr)); - *addrLen = sizeof(struct sockaddr_in); + sys_memcpy(inAddrP, &addr, sizeof(addr)); } @@ -682,11 +698,14 @@ char* esock_encode_ip4_address(ErlNifEnv* env, #if defined(HAVE_IN6) && defined(AF_INET6) extern -char* esock_decode_ip6_address(ErlNifEnv* env, - ERL_NIF_TERM eAddr, - struct sockaddr_in6* sockAddrP, - unsigned int* addrLen) +char* esock_decode_ip6_address(ErlNifEnv* env, + ERL_NIF_TERM eAddr, + struct in6_addr* inAddrP) { + UDBG( ("SUTIL", "esock_decode_ip6_address -> entry with" + "\r\n eAddr: %T" + "\r\n", eAddr) ); + if (IS_ATOM(env, eAddr)) { /* This is either 'any' or 'loopback' */ const struct in6_addr* addr; @@ -698,34 +717,34 @@ char* esock_decode_ip6_address(ErlNifEnv* env, } else { return ESOCK_STR_EINVAL; } - - sockAddrP->sin6_addr = *addr; - *addrLen = sizeof(struct sockaddr_in6); - + + *inAddrP = *addr; + } else { /* This is a 8-tuple */ - + const ERL_NIF_TERM* addrt; int addrtSz; - int a, v; - char addr[16]; + int ai, v; + unsigned char addr[16]; + unsigned char* a = addr; + unsigned int addrLen = sizeof(addr) / sizeof(unsigned char); if (!GET_TUPLE(env, eAddr, &addrtSz, &addrt)) return ESOCK_STR_EINVAL; if (addrtSz != 8) return ESOCK_STR_EINVAL; - - for (a = 0; a < 8; a++) { - if (!GET_INT(env, addrt[a], &v)) + + for (ai = 0; ai < 8; ai++) { + if (!GET_INT(env, addrt[ai], &v)) return ESOCK_STR_EINVAL; - addr[a*2 ] = ((v >> 8) & 0xFF); - addr[a*2+1] = (v & 0xFF); + put_int16(v, a); + a += 2; } - - sys_memcpy(&sockAddrP->sin6_addr, &addr, sizeof(addr)); - *addrLen = sizeof(struct sockaddr_in6); + sys_memcpy(inAddrP, &addr, addrLen); + } return NULL; @@ -754,7 +773,7 @@ char* esock_encode_ip6_address(ErlNifEnv* env, unsigned int i; ERL_NIF_TERM at[8]; unsigned int atLen = sizeof(at) / sizeof(ERL_NIF_TERM); - unsigned char* a = (unsigned char*) &addrP; + unsigned char* a = (unsigned char*) addrP; /* The address */ for (i = 0; i < atLen; i++) { diff --git a/erts/emulator/nifs/common/socket_util.h b/erts/emulator/nifs/common/socket_util.h index cd8cc7e1fb..f1c122e281 100644 --- a/erts/emulator/nifs/common/socket_util.h +++ b/erts/emulator/nifs/common/socket_util.h @@ -32,10 +32,6 @@ #define ESOCK_ABORT(E) esock_abort(E, __func__, __FILE__, __LINE__) #define ESOCK_ASSERT(e) ((void) ((e) ? 1 : (ESOCK_ABORT(#e), 0))) -/* Two byte integer decoding */ -#define get_int16(s) ((((unsigned char*) (s))[0] << 8) | \ - (((unsigned char*) (s))[1])) - extern char* esock_decode_sockaddr(ErlNifEnv* env, ERL_NIF_TERM eSockAddr, @@ -85,10 +81,9 @@ char* esock_encode_sockaddr_un(ErlNifEnv* env, #endif extern -char* esock_decode_ip4_address(ErlNifEnv* env, - ERL_NIF_TERM eAddr, - struct sockaddr_in* sockAddrP, - unsigned int* addrLen); +char* esock_decode_ip4_address(ErlNifEnv* env, + ERL_NIF_TERM eAddr, + struct in_addr* inAddrP); extern char* esock_encode_ip4_address(ErlNifEnv* env, struct in_addr* addrP, @@ -96,10 +91,9 @@ char* esock_encode_ip4_address(ErlNifEnv* env, #if defined(HAVE_IN6) && defined(AF_INET6) extern -char* esock_decode_ip6_address(ErlNifEnv* env, - ERL_NIF_TERM eAddr, - struct sockaddr_in6* sockAddrP, - unsigned int* addrLen); +char* esock_decode_ip6_address(ErlNifEnv* env, + ERL_NIF_TERM eAddr, + struct in6_addr* inAddrP); extern char* esock_encode_ip6_address(ErlNifEnv* env, struct in6_addr* addrP, diff --git a/erts/preloaded/ebin/socket.beam b/erts/preloaded/ebin/socket.beam index 4ffa22f70d..01db75c170 100644 Binary files a/erts/preloaded/ebin/socket.beam and b/erts/preloaded/ebin/socket.beam differ diff --git a/erts/preloaded/src/socket.erl b/erts/preloaded/src/socket.erl index f243559ceb..997a4ac225 100644 --- a/erts/preloaded/src/socket.erl +++ b/erts/preloaded/src/socket.erl @@ -510,7 +510,7 @@ %% -define(SOCKET_OPT_IP_MTU, 11). %% -define(SOCKET_OPT_IP_MTU_DISCOVER, 12). %% -define(SOCKET_OPT_IP_MULTICAST_ALL, 13). -%% -define(SOCKET_OPT_IP_MULTICAST_IF, 14). +-define(SOCKET_OPT_IP_MULTICAST_IF, 14). -define(SOCKET_OPT_IP_MULTICAST_LOOP, 15). -define(SOCKET_OPT_IP_MULTICAST_TTL, 16). %% -define(SOCKET_OPT_IP_NODEFRAG, 17). @@ -1955,6 +1955,9 @@ enc_setopt_value(socket, sndbuf, V, _D, _T, _P) when is_integer(V) -> enc_setopt_value(socket = L, Opt, V, _D, _T, _P) -> not_supported({L, Opt, V}); +enc_setopt_value(ip, multicast_if, V, _D, _T, _P) + when (V =:= any) orelse (is_tuple(V) andalso (size(V) =:= 4)) -> + V; enc_setopt_value(ip, multicast_loop, V, _D, _T, _P) when is_boolean(V) -> V; @@ -2251,8 +2254,8 @@ enc_sockopt_key(ip = L, mtu_discover = Opt, _Dir, _D, _T, _P) -> not_supported({L, Opt}); enc_sockopt_key(ip = L, multicast_all = Opt, _Dir, _D, _T, _P) -> not_supported({L, Opt}); -enc_sockopt_key(ip = L, multicast_if = Opt, _Dir, _D, _T, _P) -> - not_supported({L, Opt}); +enc_sockopt_key(ip = _L, multicast_if = _Opt, _Dir, _D, _T, _P) -> + ?SOCKET_OPT_IP_MULTICAST_IF; enc_sockopt_key(ip = _L, multicast_loop = _Opt, _Dir, _D, _T, _P) -> ?SOCKET_OPT_IP_MULTICAST_LOOP; enc_sockopt_key(ip = _L, multicast_ttl = _Opt, _Dir, _D, _T, _P) -> diff --git a/lib/kernel/test/socket_client.erl b/lib/kernel/test/socket_client.erl index 0b570e1f71..23d3c9956e 100644 --- a/lib/kernel/test/socket_client.erl +++ b/lib/kernel/test/socket_client.erl @@ -22,8 +22,8 @@ -export([ start/1, - start_tcp/1, start_tcp/2, - start_udp/1, start_udp/2 + start_tcp/1, start_tcp/2, start_tcp6/1, + start_udp/1, start_udp/2, start_udp6/1 ]). -define(LIB, socket_lib). @@ -36,6 +36,9 @@ start(Port) -> start_tcp(Port) -> start(inet, stream, tcp, Port). +start_tcp6(Port) -> + start(inet6, stream, tcp, Port). + start_tcp(Addr, Port) when (size(Addr) =:= 4) -> start(inet, stream, tcp, Addr, Port); start_tcp(Addr, Port) when (size(Addr) =:= 8) -> @@ -45,6 +48,9 @@ start_tcp(Addr, Port) when (size(Addr) =:= 8) -> start_udp(Port) -> start(inet, dgram, udp, Port). +start_udp6(Port) -> + start(inet6, dgram, udp, Port). + start_udp(Addr, Port) when (size(Addr) =:= 4) -> start(inet, dgram, udp, Addr, Port); start_udp(Addr, Port) when (size(Addr) =:= 8) -> @@ -65,21 +71,37 @@ do_start(Domain, stream = Type, Proto, SA) -> try do_init(Domain, Type, Proto) of Sock -> connect(Sock, SA), + {ok, Name} = socket:sockname(Sock), + {ok, Peer} = socket:peername(Sock), + {ok, Domain} = socket:getopt(Sock, socket, domain), + {ok, Type} = socket:getopt(Sock, socket, type), + {ok, Proto} = socket:getopt(Sock, socket, protocol), + {ok, OOBI} = socket:getopt(Sock, socket, oobinline), + {ok, SndBuf} = socket:getopt(Sock, socket, sndbuf), + {ok, RcvBuf} = socket:getopt(Sock, socket, rcvbuf), + {ok, Linger} = socket:getopt(Sock, socket, linger), + {ok, MIF} = socket:getopt(Sock, ip, multicast_if), + {ok, MLoop} = socket:getopt(Sock, ip, multicast_loop), + {ok, MTTL} = socket:getopt(Sock, ip, multicast_ttl), i("connected: " "~n From: ~p" - "~n To: ~p", - [ - case socket:sockname(Sock) of - {ok, Name} -> Name; - {error, _} = NE -> NE - end, - case socket:peername(Sock) of - {ok, Name} -> Name; - {error, _} = PE -> PE - end - ]), + "~n To: ~p" + "~nwhen" + "~n (socket) Domain: ~p" + "~n (socket) Type: ~p" + "~n (socket) Protocol: ~p" + "~n (socket) OOBInline: ~p" + "~n (socket) SndBuf: ~p" + "~n (socket) RcvBuf: ~p" + "~n (socket) Linger: ~p" + "~n (ip) Multicast IF: ~p" + "~n (ip) Multicast Loop: ~p" + "~n (ip) Multicast TTL: ~p" + "~n => wait some", + [Name, Peer, + Domain, Type, Proto, + OOBI, SndBuf, RcvBuf, Linger, MIF, MLoop, MTTL]), %% Give the server some time... - i("wait some", []), ?LIB:sleep(5000), %% ok = socket:close(Sock), send_loop(#client{socket = Sock, @@ -93,7 +115,30 @@ do_start(Domain, dgram = Type, Proto, SA) -> try do_init(Domain, Type, Proto) of Sock -> %% Give the server some time... - i("wait some", []), + {ok, Domain} = socket:getopt(Sock, socket, domain), + {ok, Type} = socket:getopt(Sock, socket, type), + {ok, Proto} = socket:getopt(Sock, socket, protocol), + {ok, OOBI} = socket:getopt(Sock, socket, oobinline), + {ok, SndBuf} = socket:getopt(Sock, socket, sndbuf), + {ok, RcvBuf} = socket:getopt(Sock, socket, rcvbuf), + {ok, Linger} = socket:getopt(Sock, socket, linger), + {ok, MIF} = socket:getopt(Sock, ip, multicast_if), + {ok, MLoop} = socket:getopt(Sock, ip, multicast_loop), + {ok, MTTL} = socket:getopt(Sock, ip, multicast_ttl), + i("initiated when: " + "~n (socket) Domain: ~p" + "~n (socket) Type: ~p" + "~n (socket) Protocol: ~p" + "~n (socket) OOBInline: ~p" + "~n (socket) SndBuf: ~p" + "~n (socket) RcvBuf: ~p" + "~n (socket) Linger: ~p" + "~n (ip) Multicast IF: ~p" + "~n (ip) Multicast Loop: ~p" + "~n (ip) Multicast TTL: ~p" + "~n => wait some", + [Domain, Type, Proto, + OOBI, SndBuf, RcvBuf, Linger, MIF, MLoop, MTTL]), ?LIB:sleep(5000), %% ok = socket:close(Sock), send_loop(#client{socket = Sock, @@ -185,7 +230,7 @@ send_loop(#client{msg_id = N} = C) when (N =< 10) -> end; {error, SReason} -> e("Failed send request ~w: " - "~n ~p", [SReason]), + "~n ~p", [N, SReason]), exit({failed_send, SReason}) end; send_loop(#client{socket = Sock}) -> diff --git a/lib/kernel/test/socket_server.erl b/lib/kernel/test/socket_server.erl index 9e4e355179..5fd32e040c 100644 --- a/lib/kernel/test/socket_server.erl +++ b/lib/kernel/test/socket_server.erl @@ -21,7 +21,7 @@ -module(socket_server). -export([ - start/0, + start/0, start/4, start_tcp/0, start_tcp/1, start_udp/0, start_udp/1 ]). @@ -104,7 +104,7 @@ manager_init(Domain, dgram = Type, Proto, Peek) -> {error, _} -> "-" end end, - i("(socket) open (~s,~s,~s): " + i("socket opened (~s,~s,~s): " "~n broadcast: ~s" "~n dontroute: ~s" "~n keepalive: ~s" @@ -114,13 +114,15 @@ manager_init(Domain, dgram = Type, Proto, Peek) -> "~n prio: ~s" "~n rcvbuf: ~s" "~n sndbuf: ~s" - "~n try find (local) address", + "~n => try find (local) address", [F(domain), F(type), F(protocol), F(broadcast), F(dontroute), F(keepalive), F(reuseaddr), F(linger), F(debug), F(priority), F(rcvbuf), F(sndbuf)]), Addr = which_addr(Domain), SA = #{family => Domain, addr => Addr}, + i("try bind to: " + "~n ~p", [Addr]), case socket:bind(Sock, SA) of {ok, _P} -> ok; @@ -290,7 +292,9 @@ acceptor_do_init(Domain, Type, Proto) -> Addr = which_addr(Domain), SA = #{family => Domain, addr => Addr}, - i("found (~p) - try (socket) bind", [Addr]), + i("found: " + "~n ~p" + "~n => try (socket) bind", [Addr]), %% ok = socket:setopt(Sock, otp, debug, true), %% ok = socket:setopt(Sock, socket, debug, 1), %% must have rights!! Port = case socket:bind(Sock, SA) of @@ -301,7 +305,9 @@ acceptor_do_init(Domain, Type, Proto) -> {error, BReason} -> throw({bind, BReason}) end, - i("bound (~w) - try (socket) listen (acceptconn: ~s)", + i("bound to: " + "~n ~p" + "~n => try (socket) listen (acceptconn: ~s)", [Port, F(acceptconn)]), case socket:listen(Sock) of ok -> @@ -328,6 +334,8 @@ which_addr(Domain, [{Name, IFO}|_IFL]) when (Name =/= "lo") -> which_addr(Domain, [_|IFL]) -> which_addr(Domain, IFL). +which_addr2(_, []) -> + throw(no_address); which_addr2(inet = _Domain, [{addr, Addr}|_IFO]) when (size(Addr) =:= 4) -> Addr; which_addr2(inet6 = _Domain, [{addr, Addr}|_IFO]) when (size(Addr) =:= 8) -> @@ -434,6 +442,7 @@ handler_init(Manager, ID, Peek, Sock) -> {ok, SndBuf} = socket:getopt(Sock, socket, sndbuf), {ok, RcvBuf} = socket:getopt(Sock, socket, rcvbuf), {ok, Linger} = socket:getopt(Sock, socket, linger), + {ok, MIF} = socket:getopt(Sock, ip, multicast_if), {ok, MLoop} = socket:getopt(Sock, ip, multicast_loop), {ok, MTTL} = socket:getopt(Sock, ip, multicast_ttl), i("got continue when: " @@ -444,10 +453,11 @@ handler_init(Manager, ID, Peek, Sock) -> "~n (socket) SndBuf: ~p" "~n (socket) RcvBuf: ~p" "~n (socket) Linger: ~p" + "~n (ip) Multicast IF: ~p" "~n (ip) Multicast Loop: ~p" "~n (ip) Multicast TTL: ~p", [Domain, Type, Proto, - OOBI, SndBuf, RcvBuf, Linger, MLoop, MTTL]), + OOBI, SndBuf, RcvBuf, Linger, MIF, MLoop, MTTL]), %% socket:setopt(Sock, otp, debug, true), handler_loop(#handler{peek = Peek, manager = Manager, -- cgit v1.2.3 From 04524794d5e1f80a33f48201e5c14de8c396c30e Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Wed, 11 Jul 2018 11:20:46 +0200 Subject: [socket-nif] Add support for socket (level ip) options [add|drop]_membership Added support for the socket options (level ip) add_membership and drop_membership. OTP-14831 --- erts/emulator/nifs/common/socket_nif.c | 146 +++++++++++++++++++++++++++++++-- erts/preloaded/ebin/socket.beam | Bin 44012 -> 44536 bytes erts/preloaded/src/socket.erl | 37 +++++++-- 3 files changed, 170 insertions(+), 13 deletions(-) diff --git a/erts/emulator/nifs/common/socket_nif.c b/erts/emulator/nifs/common/socket_nif.c index 05ba9e55f1..267151083f 100644 --- a/erts/emulator/nifs/common/socket_nif.c +++ b/erts/emulator/nifs/common/socket_nif.c @@ -362,13 +362,15 @@ typedef union { #define SOCKET_OPT_SOCK_SNDBUF 27 #define SOCKET_OPT_SOCK_TYPE 32 -#define SOCKET_OPT_IP_MULTICAST_IF 14 -#define SOCKET_OPT_IP_MULTICAST_LOOP 15 -#define SOCKET_OPT_IP_MULTICAST_TTL 16 -#define SOCKET_OPT_IP_RECVTOS 25 -#define SOCKET_OPT_IP_ROUTER_ALERT 28 -#define SOCKET_OPT_IP_TOS 30 -#define SOCKET_OPT_IP_TTL 32 +#define SOCKET_OPT_IP_ADD_MEMBERSHIP 1 +#define SOCKET_OPT_IP_DROP_MEMBERSHIP 5 +#define SOCKET_OPT_IP_MULTICAST_IF 14 +#define SOCKET_OPT_IP_MULTICAST_LOOP 15 +#define SOCKET_OPT_IP_MULTICAST_TTL 16 +#define SOCKET_OPT_IP_RECVTOS 25 +#define SOCKET_OPT_IP_ROUTER_ALERT 28 +#define SOCKET_OPT_IP_TOS 30 +#define SOCKET_OPT_IP_TTL 32 #define SOCKET_OPT_IPV6_HOPLIMIT 12 @@ -846,6 +848,23 @@ static ERL_NIF_TERM nsetopt_lvl_ip(ErlNifEnv* env, SocketDescriptor* descP, int eOpt, ERL_NIF_TERM eVal); +#if defined(IP_ADD_MEMBERSHIP) +static ERL_NIF_TERM nsetopt_lvl_ip_add_membership(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); +#endif +#if defined(IP_DROP_MEMBERSHIP) +static ERL_NIF_TERM nsetopt_lvl_ip_drop_membership(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); +#endif +#if defined(IP_DROP_MEMBERSHIP) || defined(IP_ADD_MEMBERSHIP) +static +ERL_NIF_TERM nsetopt_lvl_ip_update_membership(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal, + int opt); +#endif #if defined(IP_MULTICAST_IF) static ERL_NIF_TERM nsetopt_lvl_ip_multicast_if(ErlNifEnv* env, SocketDescriptor* descP, @@ -1397,7 +1416,9 @@ static char str_global_counters[] = "global_counters"; static char str_in4_sockaddr[] = "in4_sockaddr"; static char str_in6_sockaddr[] = "in6_sockaddr"; static char str_iow[] = "iow"; +static char str_interface[] = "interface"; // static char str_loopback[] = "loopback"; +static char str_multiaddr[] = "multiaddr"; static char str_nif_abort[] = "nif_abort"; static char str_select[] = "select"; static char str_num_dlocal[] = "num_domain_local"; @@ -1477,6 +1498,8 @@ static ERL_NIF_TERM atom_global_counters; static ERL_NIF_TERM atom_in4_sockaddr; static ERL_NIF_TERM atom_in6_sockaddr; static ERL_NIF_TERM atom_iow; +static ERL_NIF_TERM atom_interface; +static ERL_NIF_TERM atom_multiaddr; static ERL_NIF_TERM atom_nif_abort; static ERL_NIF_TERM atom_num_dinet; static ERL_NIF_TERM atom_num_dinet6; @@ -3968,6 +3991,18 @@ ERL_NIF_TERM nsetopt_lvl_ip(ErlNifEnv* env, ERL_NIF_TERM result; switch (eOpt) { +#if defined(IP_ADD_MEMBERSHIP) + case SOCKET_OPT_IP_ADD_MEMBERSHIP: + result = nsetopt_lvl_ip_add_membership(env, descP, eVal); + break; +#endif + +#if defined(IP_DROP_MEMBERSHIP) + case SOCKET_OPT_IP_DROP_MEMBERSHIP: + result = nsetopt_lvl_ip_drop_membership(env, descP, eVal); + break; +#endif + #if defined(IP_MULTICAST_IF) case SOCKET_OPT_IP_MULTICAST_IF: result = nsetopt_lvl_ip_multicast_if(env, descP, eVal); @@ -4019,6 +4054,101 @@ ERL_NIF_TERM nsetopt_lvl_ip(ErlNifEnv* env, } +/* nsetopt_lvl_ip_add_membership - Level IP ADD_MEMBERSHIP option + * + * The value is a map with two attributes: multiaddr and interface. + * The attribute 'multiaddr' is always a 4-tuple (IPv4 address). + * The attribute 'interface' is either the atom 'any' or a 4-tuple + * (IPv4 address). + */ +#if defined(IP_ADD_MEMBERSHIP) +static +ERL_NIF_TERM nsetopt_lvl_ip_add_membership(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) +{ + return nsetopt_lvl_ip_update_membership(env, descP, eVal, IP_ADD_MEMBERSHIP); +} +#endif + + +/* nsetopt_lvl_ip_drop_membership - Level IP DROP_MEMBERSHIP option + * + * The value is a map with two attributes: multiaddr and interface. + * The attribute 'multiaddr' is always a 4-tuple (IPv4 address). + * The attribute 'interface' is either the atom 'any' or a 4-tuple + * (IPv4 address). + * + * We should really have a common function with add_membership, + * since the code is virtually identical (except for the option + * value). + */ +#if defined(IP_DROP_MEMBERSHIP) +static +ERL_NIF_TERM nsetopt_lvl_ip_drop_membership(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) +{ + return nsetopt_lvl_ip_update_membership(env, descP, eVal, IP_DROP_MEMBERSHIP); +} +#endif + + + +#if defined(IP_DROP_MEMBERSHIP) || defined(IP_ADD_MEMBERSHIP) +static +ERL_NIF_TERM nsetopt_lvl_ip_update_membership(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal, + int opt) +{ + ERL_NIF_TERM result, eMultiAddr, eInterface; + struct ip_mreq mreq; + char* xres; + int res; + size_t sz; +#if defined(SOL_IP) + int level = SOL_IP; +#else + int level = IPPROTO_IP; +#endif + + // It must be a map + if (!IS_MAP(env, eVal)) + return enif_make_badarg(env); + + // It must have exactly two attributes + if (!enif_get_map_size(env, eVal, &sz) || (sz >= 2)) + return enif_make_badarg(env); + + if (!GET_MAP_VAL(env, eVal, atom_multiaddr, &eMultiAddr)) + return enif_make_badarg(env); + + if (!GET_MAP_VAL(env, eVal, atom_interface, &eInterface)) + return enif_make_badarg(env); + + if ((xres = esock_decode_ip4_address(env, + eMultiAddr, + &mreq.imr_multiaddr)) != NULL) + return esock_make_error_str(env, xres); + + if ((xres = esock_decode_ip4_address(env, + eInterface, + &mreq.imr_interface)) != NULL) + return esock_make_error_str(env, xres); + + res = socket_setopt(descP->sock, level, opt, &mreq, sizeof(mreq)); + + if (res != 0) + result = esock_make_error_errno(env, sock_errno()); + else + result = esock_atom_ok; + + return result; +} +#endif + + /* nsetopt_lvl_ip_multicast_if - Level IP MULTICAST_IF option * * The value is either the atom 'any' or a 4-tuple. @@ -7510,6 +7640,8 @@ int on_load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info) atom_in4_sockaddr = MKA(env, str_in4_sockaddr); atom_in6_sockaddr = MKA(env, str_in6_sockaddr); atom_iow = MKA(env, str_iow); + atom_interface = MKA(env, str_interface); + atom_multiaddr = MKA(env, str_multiaddr); atom_nif_abort = MKA(env, str_nif_abort); atom_num_dinet = MKA(env, str_num_dinet); atom_num_dinet6 = MKA(env, str_num_dinet6); diff --git a/erts/preloaded/ebin/socket.beam b/erts/preloaded/ebin/socket.beam index 01db75c170..f64ea4103b 100644 Binary files a/erts/preloaded/ebin/socket.beam and b/erts/preloaded/ebin/socket.beam differ diff --git a/erts/preloaded/src/socket.erl b/erts/preloaded/src/socket.erl index 997a4ac225..3e7679daa1 100644 --- a/erts/preloaded/src/socket.erl +++ b/erts/preloaded/src/socket.erl @@ -89,6 +89,7 @@ sctp_socket_option/0, ip_tos_flag/0, + ip_mreq/0, msg_hdr/0 @@ -134,6 +135,20 @@ %% %% +%% This type is used when requesting to become member of a multicast +%% group with a call to setopt. Example: +%% +%% socket:setopt(Socket, ip, add_membership, #{multiaddr => Addr, +%% interface => any}). +%% +%% Its also used when removing from a multicast group. Example: +%% +%% socket:setopt(Socket, ip, drop_membership, #{multiaddr => Addr, +%% interface => any}). +%% +-type ip_mreq() :: #{multiaddr := ip4_address(), + interface := any | ip4_address()}. + -type sockaddr_un() :: #{family := local, path := binary() | string()}. -type sockaddr_in4() :: #{family := inet, @@ -497,11 +512,11 @@ %% -define(SOCKET_OPT_SOCK_TIMESTAMP, 31). -define(SOCKET_OPT_SOCK_TYPE, 32). -%% -define(SOCKET_OPT_IP_ADD_MEMBERSHIP, 1). +-define(SOCKET_OPT_IP_ADD_MEMBERSHIP, 1). %% -define(SOCKET_OPT_IP_ADD_SOURCE_MEMBERSHIP, 2). %% -define(SOCKET_OPT_IP_BLOCK_SOURCE, 3). %% -define(SOCKET_OPT_IP_DONT_FRAG, 4). -%% -define(SOCKET_OPT_IP_DROP_MEMBERSHIP, 5). +-define(SOCKET_OPT_IP_DROP_MEMBERSHIP, 5). %% -define(SOCKET_OPT_IP_DROP_SOURCE_MEMBERSHIP, 6). %% -define(SOCKET_OPT_IP_FREEBIND, 7). %% -define(SOCKET_OPT_IP_HDRINCL, 8). @@ -1955,6 +1970,16 @@ enc_setopt_value(socket, sndbuf, V, _D, _T, _P) when is_integer(V) -> enc_setopt_value(socket = L, Opt, V, _D, _T, _P) -> not_supported({L, Opt, V}); +enc_setopt_value(ip, add_membership, #{multiaddr := M, + interface := IF} = V, _D, _T, _P) + when (is_tuple(M) andalso (size(M) =:= 4)) andalso + ((IF =:= any) orelse (is_tuple(IF) andalso (size(IF) =:= 4))) -> + V; +enc_setopt_value(ip, drop_membership, #{multiaddr := M, + interface := IF} = V, _D, _T, _P) + when (is_tuple(M) andalso (size(M) =:= 4)) andalso + ((IF =:= any) orelse (is_tuple(IF) andalso (size(IF) =:= 4))) -> + V; enc_setopt_value(ip, multicast_if, V, _D, _T, _P) when (V =:= any) orelse (is_tuple(V) andalso (size(V) =:= 4)) -> V; @@ -2224,8 +2249,8 @@ enc_sockopt_key(socket = L, UnknownOpt, _Dir, _D, _T, _P) -> unknown({L, UnknownOpt}); %% +++ IP socket options +++ -enc_sockopt_key(ip = L, add_membership = Opt, set = _Dir, _D, _T, _P) -> - not_supported({L, Opt}); +enc_sockopt_key(ip = _L, add_membership = _Opt, set = _Dir, _D, _T, _P) -> + ?SOCKET_OPT_IP_ADD_MEMBERSHIP; enc_sockopt_key(ip = L, add_source_membership = Opt, set = _Dir, _D, _T, _P) -> not_supported({L, Opt}); enc_sockopt_key(ip = L, block_source = Opt, set = _Dir, _D, _T, _P) -> @@ -2234,8 +2259,8 @@ enc_sockopt_key(ip = L, block_source = Opt, set = _Dir, _D, _T, _P) -> %% Only respected on udp and raw ip (unless the hdrincl option has been set). enc_sockopt_key(ip = L, dontfrag = Opt, _Dir, _D, _T, _P) -> not_supported({L, Opt}); -enc_sockopt_key(ip = L, drop_membership = Opt, set = _Dir, _D, _T, _P) -> - not_supported({L, Opt}); +enc_sockopt_key(ip = _L, drop_membership = _Opt, set = _Dir, _D, _T, _P) -> + ?SOCKET_OPT_IP_DROP_MEMBERSHIP; enc_sockopt_key(ip = L, drop_source_membership = Opt, set = _Dir, _D, _T, _P) -> not_supported({L, Opt}); %% Linux only? -- cgit v1.2.3 From 9b5560dbe50c377c8fc9b6a0c0c7b6c2dcf9f0de Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Wed, 11 Jul 2018 11:49:58 +0200 Subject: [socket-doc-nif] Preliminary socket option table Added a commented out (preliminary) table in the getopt function for the socket level. The table is not included (atleast) in the man page, so there is no point in including it yet. But since its table(s) of all socket options (one for each level) we need comprehensive tables "somewhere", but where... OTP-14831 --- erts/doc/src/socket.xml | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/erts/doc/src/socket.xml b/erts/doc/src/socket.xml index 6f116abca9..2681910a72 100644 --- a/erts/doc/src/socket.xml +++ b/erts/doc/src/socket.xml @@ -119,6 +119,9 @@ + + + @@ -194,6 +197,50 @@

Not all options are valid on all platforms. That is, even if "we" support an option, that does not mean that the underlying OS does.

+ + -- cgit v1.2.3 From 4d29dde851fb30d86c194961f93e148198ae456d Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Wed, 11 Jul 2018 11:53:31 +0200 Subject: [socket-nif] Add value type check for the dontroute (socket) option --- erts/preloaded/src/socket.erl | 2 ++ 1 file changed, 2 insertions(+) diff --git a/erts/preloaded/src/socket.erl b/erts/preloaded/src/socket.erl index 3e7679daa1..a4efc2e38c 100644 --- a/erts/preloaded/src/socket.erl +++ b/erts/preloaded/src/socket.erl @@ -1948,6 +1948,8 @@ enc_setopt_value(socket, broadcast, V, _D, _T, _P) when is_boolean(V) -> V; enc_setopt_value(socket, debug, V, _D, _T, _P) when is_integer(V) -> V; +enc_setopt_value(socket, dontroute, V, _D, _T, _P) when is_boolean(V) -> + V; enc_setopt_value(socket, keepalive, V, _D, _T, _P) when is_boolean(V) -> V; enc_setopt_value(socket, linger, abort, D, T, P) -> -- cgit v1.2.3 From e5a5cb1025270c265baeda89dd4cd13a1417a262 Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Wed, 11 Jul 2018 18:20:06 +0200 Subject: [socket-nif] Add support for socket (level ip) option mtu Added support for the IP option MTU. OTP-14831 --- erts/emulator/nifs/common/socket_nif.c | 29 +++++++++++++++++++++++++++++ erts/preloaded/ebin/socket.beam | Bin 44536 -> 44548 bytes erts/preloaded/src/socket.erl | 8 ++++---- lib/kernel/test/socket_client.erl | 4 +++- lib/kernel/test/socket_server.erl | 9 ++++++++- 5 files changed, 44 insertions(+), 6 deletions(-) diff --git a/erts/emulator/nifs/common/socket_nif.c b/erts/emulator/nifs/common/socket_nif.c index 267151083f..6b9f27aa3f 100644 --- a/erts/emulator/nifs/common/socket_nif.c +++ b/erts/emulator/nifs/common/socket_nif.c @@ -364,6 +364,7 @@ typedef union { #define SOCKET_OPT_IP_ADD_MEMBERSHIP 1 #define SOCKET_OPT_IP_DROP_MEMBERSHIP 5 +#define SOCKET_OPT_IP_MTU 11 #define SOCKET_OPT_IP_MULTICAST_IF 14 #define SOCKET_OPT_IP_MULTICAST_LOOP 15 #define SOCKET_OPT_IP_MULTICAST_TTL 16 @@ -1048,6 +1049,10 @@ static ERL_NIF_TERM ngetopt_lvl_sock_type(ErlNifEnv* env, static ERL_NIF_TERM ngetopt_lvl_ip(ErlNifEnv* env, SocketDescriptor* descP, int eOpt); +#if defined(IP_MTU) +static ERL_NIF_TERM ngetopt_lvl_ip_mtu(ErlNifEnv* env, + SocketDescriptor* descP); +#endif #if defined(IP_MULTICAST_IF) static ERL_NIF_TERM ngetopt_lvl_ip_multicast_if(ErlNifEnv* env, SocketDescriptor* descP); @@ -5453,6 +5458,12 @@ ERL_NIF_TERM ngetopt_lvl_ip(ErlNifEnv* env, ERL_NIF_TERM result; switch (eOpt) { +#if defined(IP_MTU) + case SOCKET_OPT_IP_MTU: + result = ngetopt_lvl_ip_mtu(env, descP); + break; +#endif + #if defined(IP_MULTICAST_IF) case SOCKET_OPT_IP_MULTICAST_IF: result = ngetopt_lvl_ip_multicast_if(env, descP); @@ -5504,6 +5515,24 @@ ERL_NIF_TERM ngetopt_lvl_ip(ErlNifEnv* env, } +/* ngetopt_lvl_ip_mtu - Level IP MTU option + */ +#if defined(IP_MTU) +static +ERL_NIF_TERM ngetopt_lvl_ip_mtu(ErlNifEnv* env, + SocketDescriptor* descP) +{ +#if defined(SOL_IP) + int level = SOL_IP; +#else + int level = IPPROTO_IP; +#endif + + return ngetopt_int_opt(env, descP, level, IP_MTU); +} +#endif + + /* ngetopt_lvl_ip_multicast_if - Level IP MULTICAST_IF option */ #if defined(IP_MULTICAST_IF) diff --git a/erts/preloaded/ebin/socket.beam b/erts/preloaded/ebin/socket.beam index f64ea4103b..a172b48cdd 100644 Binary files a/erts/preloaded/ebin/socket.beam and b/erts/preloaded/ebin/socket.beam differ diff --git a/erts/preloaded/src/socket.erl b/erts/preloaded/src/socket.erl index a4efc2e38c..d6b2289b69 100644 --- a/erts/preloaded/src/socket.erl +++ b/erts/preloaded/src/socket.erl @@ -515,14 +515,14 @@ -define(SOCKET_OPT_IP_ADD_MEMBERSHIP, 1). %% -define(SOCKET_OPT_IP_ADD_SOURCE_MEMBERSHIP, 2). %% -define(SOCKET_OPT_IP_BLOCK_SOURCE, 3). -%% -define(SOCKET_OPT_IP_DONT_FRAG, 4). +%% -define(SOCKET_OPT_IP_DONTFRAG, 4). % Windows? MTU_DISCOVER... -define(SOCKET_OPT_IP_DROP_MEMBERSHIP, 5). %% -define(SOCKET_OPT_IP_DROP_SOURCE_MEMBERSHIP, 6). %% -define(SOCKET_OPT_IP_FREEBIND, 7). %% -define(SOCKET_OPT_IP_HDRINCL, 8). %% -define(SOCKET_OPT_IP_MINTTL, 9). %% -define(SOCKET_OPT_IP_MSFILTER, 10). -%% -define(SOCKET_OPT_IP_MTU, 11). +-define(SOCKET_OPT_IP_MTU, 11). %% -define(SOCKET_OPT_IP_MTU_DISCOVER, 12). %% -define(SOCKET_OPT_IP_MULTICAST_ALL, 13). -define(SOCKET_OPT_IP_MULTICAST_IF, 14). @@ -2275,8 +2275,8 @@ enc_sockopt_key(ip = L, minttl = Opt, _Dir, _D, raw = _T, _P) -> not_supported({L, Opt}); enc_sockopt_key(ip = L, msfilter = Opt, _Dir, _D, _T, _P) -> not_supported({L, Opt}); -enc_sockopt_key(ip = L, mtu = Opt, get = _Dir, _D, _T, _P) -> - not_supported({L, Opt}); +enc_sockopt_key(ip = _L, mtu = _Opt, get = _Dir, _D, _T, _P) -> + ?SOCKET_OPT_IP_MTU; enc_sockopt_key(ip = L, mtu_discover = Opt, _Dir, _D, _T, _P) -> not_supported({L, Opt}); enc_sockopt_key(ip = L, multicast_all = Opt, _Dir, _D, _T, _P) -> diff --git a/lib/kernel/test/socket_client.erl b/lib/kernel/test/socket_client.erl index 23d3c9956e..dce50682ab 100644 --- a/lib/kernel/test/socket_client.erl +++ b/lib/kernel/test/socket_client.erl @@ -80,6 +80,7 @@ do_start(Domain, stream = Type, Proto, SA) -> {ok, SndBuf} = socket:getopt(Sock, socket, sndbuf), {ok, RcvBuf} = socket:getopt(Sock, socket, rcvbuf), {ok, Linger} = socket:getopt(Sock, socket, linger), + {ok, MTU} = socket:getopt(Sock, ip, mtu), {ok, MIF} = socket:getopt(Sock, ip, multicast_if), {ok, MLoop} = socket:getopt(Sock, ip, multicast_loop), {ok, MTTL} = socket:getopt(Sock, ip, multicast_ttl), @@ -94,13 +95,14 @@ do_start(Domain, stream = Type, Proto, SA) -> "~n (socket) SndBuf: ~p" "~n (socket) RcvBuf: ~p" "~n (socket) Linger: ~p" + "~n (ip) MTU: ~p" "~n (ip) Multicast IF: ~p" "~n (ip) Multicast Loop: ~p" "~n (ip) Multicast TTL: ~p" "~n => wait some", [Name, Peer, Domain, Type, Proto, - OOBI, SndBuf, RcvBuf, Linger, MIF, MLoop, MTTL]), + OOBI, SndBuf, RcvBuf, Linger, MTU, MIF, MLoop, MTTL]), %% Give the server some time... ?LIB:sleep(5000), %% ok = socket:close(Sock), diff --git a/lib/kernel/test/socket_server.erl b/lib/kernel/test/socket_server.erl index 5fd32e040c..f77c91996f 100644 --- a/lib/kernel/test/socket_server.erl +++ b/lib/kernel/test/socket_server.erl @@ -442,6 +442,12 @@ handler_init(Manager, ID, Peek, Sock) -> {ok, SndBuf} = socket:getopt(Sock, socket, sndbuf), {ok, RcvBuf} = socket:getopt(Sock, socket, rcvbuf), {ok, Linger} = socket:getopt(Sock, socket, linger), + MTU = case socket:getopt(Sock, ip, mtu) of + {ok, Val} -> + f("~w", [Val]); + {error, _} -> + "-" % We don't connect UDP (it can be done but we don't) + end, {ok, MIF} = socket:getopt(Sock, ip, multicast_if), {ok, MLoop} = socket:getopt(Sock, ip, multicast_loop), {ok, MTTL} = socket:getopt(Sock, ip, multicast_ttl), @@ -453,11 +459,12 @@ handler_init(Manager, ID, Peek, Sock) -> "~n (socket) SndBuf: ~p" "~n (socket) RcvBuf: ~p" "~n (socket) Linger: ~p" + "~n (ip) MTU: ~s" "~n (ip) Multicast IF: ~p" "~n (ip) Multicast Loop: ~p" "~n (ip) Multicast TTL: ~p", [Domain, Type, Proto, - OOBI, SndBuf, RcvBuf, Linger, MIF, MLoop, MTTL]), + OOBI, SndBuf, RcvBuf, Linger, MTU, MIF, MLoop, MTTL]), %% socket:setopt(Sock, otp, debug, true), handler_loop(#handler{peek = Peek, manager = Manager, -- cgit v1.2.3 From e0f27afac1cb3e3a5567a05081d0cf307c154b0d Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Thu, 12 Jul 2018 10:24:44 +0200 Subject: [socket-nif] Add support for socket (level ip) option mtu_discover Added support for the IP option MTU_DISCOVER. OTP-14831 --- erts/emulator/nifs/common/socket_nif.c | 225 ++++++++++++++++++++++++++++++++- erts/preloaded/ebin/socket.beam | Bin 44548 -> 44676 bytes erts/preloaded/src/socket.erl | 16 ++- lib/kernel/test/socket_client.erl | 33 ++--- lib/kernel/test/socket_server.erl | 21 +-- 5 files changed, 266 insertions(+), 29 deletions(-) diff --git a/erts/emulator/nifs/common/socket_nif.c b/erts/emulator/nifs/common/socket_nif.c index 6b9f27aa3f..65e530adee 100644 --- a/erts/emulator/nifs/common/socket_nif.c +++ b/erts/emulator/nifs/common/socket_nif.c @@ -365,6 +365,7 @@ typedef union { #define SOCKET_OPT_IP_ADD_MEMBERSHIP 1 #define SOCKET_OPT_IP_DROP_MEMBERSHIP 5 #define SOCKET_OPT_IP_MTU 11 +#define SOCKET_OPT_IP_MTU_DISCOVER 12 #define SOCKET_OPT_IP_MULTICAST_IF 14 #define SOCKET_OPT_IP_MULTICAST_LOOP 15 #define SOCKET_OPT_IP_MULTICAST_TTL 16 @@ -866,6 +867,11 @@ ERL_NIF_TERM nsetopt_lvl_ip_update_membership(ErlNifEnv* env, ERL_NIF_TERM eVal, int opt); #endif +#if defined(IP_MTU_DISCOVER) +static ERL_NIF_TERM nsetopt_lvl_ip_mtu_discover(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); +#endif #if defined(IP_MULTICAST_IF) static ERL_NIF_TERM nsetopt_lvl_ip_multicast_if(ErlNifEnv* env, SocketDescriptor* descP, @@ -1053,6 +1059,10 @@ static ERL_NIF_TERM ngetopt_lvl_ip(ErlNifEnv* env, static ERL_NIF_TERM ngetopt_lvl_ip_mtu(ErlNifEnv* env, SocketDescriptor* descP); #endif +#if defined(IP_MTU_DISCOVER) +static ERL_NIF_TERM ngetopt_lvl_ip_mtu_discover(ErlNifEnv* env, + SocketDescriptor* descP); +#endif #if defined(IP_MULTICAST_IF) static ERL_NIF_TERM ngetopt_lvl_ip_multicast_if(ErlNifEnv* env, SocketDescriptor* descP); @@ -1282,9 +1292,22 @@ static void encode_address(ErlNifEnv* env, static BOOLEAN_T decode_sock_linger(ErlNifEnv* env, ERL_NIF_TERM eVal, struct linger* valP); +#if defined(IP_TOS) static BOOLEAN_T decode_ip_tos(ErlNifEnv* env, ERL_NIF_TERM eVal, int* val); +#endif +#if defined(IP_MTU_DISCOVER) +static char* decode_ip_pmtudisc(ErlNifEnv* env, + ERL_NIF_TERM eVal, + int* val); +#endif +#if defined(IP_MTU_DISCOVER) +static void encode_ip_pmtudisc(ErlNifEnv* env, + int val, + ERL_NIF_TERM* eVal); +#endif + /* static BOOLEAN_T decode_bool(ErlNifEnv* env, ERL_NIF_TERM eVal, @@ -1416,6 +1439,8 @@ static const struct in6_addr in6addr_loopback = static char str_close[] = "close"; static char str_closed[] = "closed"; static char str_closing[] = "closing"; +static char str_do[] = "do"; +static char str_dont[] = "dont"; static char str_false[] = "false"; static char str_global_counters[] = "global_counters"; static char str_in4_sockaddr[] = "in4_sockaddr"; @@ -1425,7 +1450,6 @@ static char str_interface[] = "interface"; // static char str_loopback[] = "loopback"; static char str_multiaddr[] = "multiaddr"; static char str_nif_abort[] = "nif_abort"; -static char str_select[] = "select"; static char str_num_dlocal[] = "num_domain_local"; static char str_num_dinet[] = "num_domain_inet"; static char str_num_dinet6[] = "num_domain_inet6"; @@ -1437,8 +1461,11 @@ static char str_num_sockets[] = "num_sockets"; static char str_num_tdgrams[] = "num_type_dgram"; static char str_num_tseqpkgs[] = "num_type_seqpacket"; static char str_num_tstreams[] = "num_type_stream"; +static char str_probe[] = "probe"; +static char str_select[] = "select"; static char str_timeout[] = "timeout"; static char str_true[] = "true"; +static char str_want[] = "want"; static char str_lowdelay[] = "lowdelay"; static char str_throughput[] = "throughput"; @@ -1498,6 +1525,8 @@ ERL_NIF_TERM esock_atom_einval; static ERL_NIF_TERM atom_close; static ERL_NIF_TERM atom_closed; static ERL_NIF_TERM atom_closing; +static ERL_NIF_TERM atom_do; +static ERL_NIF_TERM atom_dont; static ERL_NIF_TERM atom_false; static ERL_NIF_TERM atom_global_counters; static ERL_NIF_TERM atom_in4_sockaddr; @@ -1517,9 +1546,11 @@ static ERL_NIF_TERM atom_num_sockets; static ERL_NIF_TERM atom_num_tdgrams; static ERL_NIF_TERM atom_num_tseqpkgs; static ERL_NIF_TERM atom_num_tstreams; +static ERL_NIF_TERM atom_probe; static ERL_NIF_TERM atom_select; static ERL_NIF_TERM atom_timeout; static ERL_NIF_TERM atom_true; +static ERL_NIF_TERM atom_want; static ERL_NIF_TERM atom_lowdelay; static ERL_NIF_TERM atom_throughput; @@ -4008,6 +4039,12 @@ ERL_NIF_TERM nsetopt_lvl_ip(ErlNifEnv* env, break; #endif +#if defined(IP_MTU_DISCOVER) + case SOCKET_OPT_IP_MTU_DISCOVER: + result = nsetopt_lvl_ip_mtu_discover(env, descP, eVal); + break; +#endif + #if defined(IP_MULTICAST_IF) case SOCKET_OPT_IP_MULTICAST_IF: result = nsetopt_lvl_ip_multicast_if(env, descP, eVal); @@ -4154,6 +4191,47 @@ ERL_NIF_TERM nsetopt_lvl_ip_update_membership(ErlNifEnv* env, #endif +/* nsetopt_lvl_ip_mtu_discover - Level IP MTU_DISCOVER option + * + * The value is an atom of the type ip_pmtudisc(). + */ +#if defined(IP_MTU_DISCOVER) +static +ERL_NIF_TERM nsetopt_lvl_ip_mtu_discover(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) +{ + ERL_NIF_TERM result; + int val; + char* xres; + int res; +#if defined(SOL_IP) + int level = SOL_IP; +#else + int level = IPPROTO_IP; +#endif + + if ((xres = decode_ip_pmtudisc(env, eVal, &val)) != NULL) { + + result = esock_make_error_str(env, xres); + + } else { + + res = socket_setopt(descP->sock, level, IP_MTU_DISCOVER, + &val, sizeof(val)); + + if (res != 0) + result = esock_make_error_errno(env, sock_errno()); + else + result = esock_atom_ok; + + } + + return result; +} +#endif + + /* nsetopt_lvl_ip_multicast_if - Level IP MULTICAST_IF option * * The value is either the atom 'any' or a 4-tuple. @@ -5236,7 +5314,9 @@ ERL_NIF_TERM ngetopt_lvl_sock_domain(ErlNifEnv* env, default: result = esock_make_error(env, - MKT2(env, esock_atom_unknown, MKI(env, val))); + MKT2(env, + esock_atom_unknown, + MKI(env, val))); break; } } @@ -5464,6 +5544,12 @@ ERL_NIF_TERM ngetopt_lvl_ip(ErlNifEnv* env, break; #endif +#if defined(IP_MTU_DISCOVER) + case SOCKET_OPT_IP_MTU_DISCOVER: + result = ngetopt_lvl_ip_mtu_discover(env, descP); + break; +#endif + #if defined(IP_MULTICAST_IF) case SOCKET_OPT_IP_MULTICAST_IF: result = ngetopt_lvl_ip_multicast_if(env, descP); @@ -5533,6 +5619,40 @@ ERL_NIF_TERM ngetopt_lvl_ip_mtu(ErlNifEnv* env, #endif +/* ngetopt_lvl_ip_mtu_discover - Level IP MTU_DISCOVER option + */ +#if defined(IP_MTU_DISCOVER) +static +ERL_NIF_TERM ngetopt_lvl_ip_mtu_discover(ErlNifEnv* env, + SocketDescriptor* descP) +{ + ERL_NIF_TERM result; + ERL_NIF_TERM eMtuDisc; + int mtuDisc; + SOCKOPTLEN_T mtuDiscSz = sizeof(mtuDisc); + int res; +#if defined(SOL_IP) + int level = SOL_IP; +#else + int level = IPPROTO_IP; +#endif + + res = sock_getopt(descP->sock, level, IP_MTU_DISCOVER, + &mtuDisc, &mtuDiscSz); + + if (res != 0) { + result = esock_make_error_errno(env, sock_errno()); + } else { + encode_ip_pmtudisc(env, mtuDisc, &eMtuDisc); + result = esock_make_ok2(env, eMtuDisc); + } + + return result; + +} +#endif + + /* ngetopt_lvl_ip_multicast_if - Level IP MULTICAST_IF option */ #if defined(IP_MULTICAST_IF) @@ -6532,7 +6652,7 @@ BOOLEAN_T decode_sock_linger(ErlNifEnv* env, ERL_NIF_TERM eVal, struct linger* v -/* +++ decode the ip socket option tos +++ +/* +++ decode the ip socket option TOS +++ * The (ip) option can be provide in two ways: * * atom() | integer() @@ -6542,6 +6662,7 @@ BOOLEAN_T decode_sock_linger(ErlNifEnv* env, ERL_NIF_TERM eVal, struct linger* v * lowdelay | throughput | reliability | mincost * */ +#if defined(IP_TOS) static BOOLEAN_T decode_ip_tos(ErlNifEnv* env, ERL_NIF_TERM eVal, int* val) { @@ -6596,6 +6717,100 @@ BOOLEAN_T decode_ip_tos(ErlNifEnv* env, ERL_NIF_TERM eVal, int* val) return result; } +#endif + + + +/* +++ decode the ip socket option MTU_DISCOVER +++ + * The (ip) option can be provide in two ways: + * + * atom() | integer() + * + * When its an atom it can have the values: + * + * want | dont | do | probe + * + */ +#if defined(IP_MTU_DISCOVER) +static +char* decode_ip_pmtudisc(ErlNifEnv* env, ERL_NIF_TERM eVal, int* val) +{ + char* res = NULL; + + if (IS_ATOM(env, eVal)) { + + if (COMPARE(eVal, atom_want) == 0) { + *val = IP_PMTUDISC_WANT; + } else if (COMPARE(eVal, atom_dont) == 0) { + *val = IP_PMTUDISC_DONT; + } else if (COMPARE(eVal, atom_do) == 0) { + *val = IP_PMTUDISC_DO; + } else if (COMPARE(eVal, atom_probe) == 0) { + *val = IP_PMTUDISC_PROBE; + } else { + *val = -1; + res = ESOCK_STR_EINVAL; + } + + } else if (IS_NUM(env, eVal)) { + + if (!GET_INT(env, eVal, val)) { + *val = -1; + res = ESOCK_STR_EINVAL; + } + + } else { + + *val = -1; + res = ESOCK_STR_EINVAL; + + } + + return res; +} +#endif + + + +/* +++ encode the ip socket option MTU_DISCOVER +++ + * The (ip) option can be provide in two ways: + * + * atom() | integer() + * + * If its one of the "known" values, it will be an atom: + * + * want | dont | do | probe + * + */ +#if defined(IP_MTU_DISCOVER) +static +void encode_ip_pmtudisc(ErlNifEnv* env, int val, ERL_NIF_TERM* eVal) +{ + switch (val) { + case IP_PMTUDISC_WANT: + *eVal = atom_want; + break; + + case IP_PMTUDISC_DONT: + *eVal = atom_dont; + break; + + case IP_PMTUDISC_DO: + *eVal = atom_do; + break; + + case IP_PMTUDISC_PROBE: + *eVal = atom_probe; + break; + + default: + *eVal = MKI(env, val); + break; + } + + return; +} +#endif @@ -7664,6 +7879,8 @@ int on_load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info) atom_close = MKA(env, str_close); atom_closed = MKA(env, str_closed); atom_closing = MKA(env, str_closing); + atom_do = MKA(env, str_do); + atom_dont = MKA(env, str_dont); atom_false = MKA(env, str_false); atom_global_counters = MKA(env, str_global_counters); atom_in4_sockaddr = MKA(env, str_in4_sockaddr); @@ -7683,9 +7900,11 @@ int on_load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info) atom_num_tdgrams = MKA(env, str_num_tdgrams); atom_num_tseqpkgs = MKA(env, str_num_tseqpkgs); atom_num_tstreams = MKA(env, str_num_tstreams); + atom_probe = MKA(env, str_probe); atom_select = MKA(env, str_select); atom_timeout = MKA(env, str_timeout); atom_true = MKA(env, str_true); + atom_want = MKA(env, str_want); /* Global atom(s) */ esock_atom_addr = MKA(env, "addr"); diff --git a/erts/preloaded/ebin/socket.beam b/erts/preloaded/ebin/socket.beam index a172b48cdd..7ca451b6c5 100644 Binary files a/erts/preloaded/ebin/socket.beam and b/erts/preloaded/ebin/socket.beam differ diff --git a/erts/preloaded/src/socket.erl b/erts/preloaded/src/socket.erl index d6b2289b69..5338f0d988 100644 --- a/erts/preloaded/src/socket.erl +++ b/erts/preloaded/src/socket.erl @@ -90,6 +90,7 @@ ip_tos_flag/0, ip_mreq/0, + ip_pmtudisc/0, msg_hdr/0 @@ -149,6 +150,8 @@ -type ip_mreq() :: #{multiaddr := ip4_address(), interface := any | ip4_address()}. +-type ip_pmtudisc() :: want | dont | do | probe. + -type sockaddr_un() :: #{family := local, path := binary() | string()}. -type sockaddr_in4() :: #{family := inet, @@ -523,7 +526,7 @@ %% -define(SOCKET_OPT_IP_MINTTL, 9). %% -define(SOCKET_OPT_IP_MSFILTER, 10). -define(SOCKET_OPT_IP_MTU, 11). -%% -define(SOCKET_OPT_IP_MTU_DISCOVER, 12). +-define(SOCKET_OPT_IP_MTU_DISCOVER, 12). %% -define(SOCKET_OPT_IP_MULTICAST_ALL, 13). -define(SOCKET_OPT_IP_MULTICAST_IF, 14). -define(SOCKET_OPT_IP_MULTICAST_LOOP, 15). @@ -1982,6 +1985,13 @@ enc_setopt_value(ip, drop_membership, #{multiaddr := M, when (is_tuple(M) andalso (size(M) =:= 4)) andalso ((IF =:= any) orelse (is_tuple(IF) andalso (size(IF) =:= 4))) -> V; +enc_setopt_value(ip, mtu_discover, V, _D, _T, _P) + when (V =:= want) orelse + (V =:= dont) orelse + (V =:= do) orelse + (V =:= probe) orelse + is_integer(V) -> + V; enc_setopt_value(ip, multicast_if, V, _D, _T, _P) when (V =:= any) orelse (is_tuple(V) andalso (size(V) =:= 4)) -> V; @@ -2277,8 +2287,8 @@ enc_sockopt_key(ip = L, msfilter = Opt, _Dir, _D, _T, _P) -> not_supported({L, Opt}); enc_sockopt_key(ip = _L, mtu = _Opt, get = _Dir, _D, _T, _P) -> ?SOCKET_OPT_IP_MTU; -enc_sockopt_key(ip = L, mtu_discover = Opt, _Dir, _D, _T, _P) -> - not_supported({L, Opt}); +enc_sockopt_key(ip = _L, mtu_discover = _Opt, _Dir, _D, _T, _P) -> + ?SOCKET_OPT_IP_MTU_DISCOVER; enc_sockopt_key(ip = L, multicast_all = Opt, _Dir, _D, _T, _P) -> not_supported({L, Opt}); enc_sockopt_key(ip = _L, multicast_if = _Opt, _Dir, _D, _T, _P) -> diff --git a/lib/kernel/test/socket_client.erl b/lib/kernel/test/socket_client.erl index dce50682ab..6c6dc0444c 100644 --- a/lib/kernel/test/socket_client.erl +++ b/lib/kernel/test/socket_client.erl @@ -71,19 +71,20 @@ do_start(Domain, stream = Type, Proto, SA) -> try do_init(Domain, Type, Proto) of Sock -> connect(Sock, SA), - {ok, Name} = socket:sockname(Sock), - {ok, Peer} = socket:peername(Sock), - {ok, Domain} = socket:getopt(Sock, socket, domain), - {ok, Type} = socket:getopt(Sock, socket, type), - {ok, Proto} = socket:getopt(Sock, socket, protocol), - {ok, OOBI} = socket:getopt(Sock, socket, oobinline), - {ok, SndBuf} = socket:getopt(Sock, socket, sndbuf), - {ok, RcvBuf} = socket:getopt(Sock, socket, rcvbuf), - {ok, Linger} = socket:getopt(Sock, socket, linger), - {ok, MTU} = socket:getopt(Sock, ip, mtu), - {ok, MIF} = socket:getopt(Sock, ip, multicast_if), - {ok, MLoop} = socket:getopt(Sock, ip, multicast_loop), - {ok, MTTL} = socket:getopt(Sock, ip, multicast_ttl), + {ok, Name} = socket:sockname(Sock), + {ok, Peer} = socket:peername(Sock), + {ok, Domain} = socket:getopt(Sock, socket, domain), + {ok, Type} = socket:getopt(Sock, socket, type), + {ok, Proto} = socket:getopt(Sock, socket, protocol), + {ok, OOBI} = socket:getopt(Sock, socket, oobinline), + {ok, SndBuf} = socket:getopt(Sock, socket, sndbuf), + {ok, RcvBuf} = socket:getopt(Sock, socket, rcvbuf), + {ok, Linger} = socket:getopt(Sock, socket, linger), + {ok, MTU} = socket:getopt(Sock, ip, mtu), + {ok, MTUDisc} = socket:getopt(Sock, ip, mtu_discover), + {ok, MIF} = socket:getopt(Sock, ip, multicast_if), + {ok, MLoop} = socket:getopt(Sock, ip, multicast_loop), + {ok, MTTL} = socket:getopt(Sock, ip, multicast_ttl), i("connected: " "~n From: ~p" "~n To: ~p" @@ -96,13 +97,15 @@ do_start(Domain, stream = Type, Proto, SA) -> "~n (socket) RcvBuf: ~p" "~n (socket) Linger: ~p" "~n (ip) MTU: ~p" + "~n (ip) MTU Discovery: ~p" "~n (ip) Multicast IF: ~p" "~n (ip) Multicast Loop: ~p" "~n (ip) Multicast TTL: ~p" "~n => wait some", [Name, Peer, - Domain, Type, Proto, - OOBI, SndBuf, RcvBuf, Linger, MTU, MIF, MLoop, MTTL]), + Domain, Type, Proto, + OOBI, SndBuf, RcvBuf, Linger, + MTU, MTUDisc, MIF, MLoop, MTTL]), %% Give the server some time... ?LIB:sleep(5000), %% ok = socket:close(Sock), diff --git a/lib/kernel/test/socket_server.erl b/lib/kernel/test/socket_server.erl index f77c91996f..68a6aebc50 100644 --- a/lib/kernel/test/socket_server.erl +++ b/lib/kernel/test/socket_server.erl @@ -435,6 +435,13 @@ handler_init(Manager, ID, Peek, Sock) -> {handler, Pid, Ref, continue} -> i("got continue"), handler_reply(Pid, Ref, ok), + G = fun(K) -> case socket:getopt(Sock, ip, K) of + {ok, Val} -> + f("~w", [Val]); + {error, _} -> + "-" + end + end, {ok, Domain} = socket:getopt(Sock, socket, domain), {ok, Type} = socket:getopt(Sock, socket, type), {ok, Proto} = socket:getopt(Sock, socket, protocol), @@ -442,12 +449,8 @@ handler_init(Manager, ID, Peek, Sock) -> {ok, SndBuf} = socket:getopt(Sock, socket, sndbuf), {ok, RcvBuf} = socket:getopt(Sock, socket, rcvbuf), {ok, Linger} = socket:getopt(Sock, socket, linger), - MTU = case socket:getopt(Sock, ip, mtu) of - {ok, Val} -> - f("~w", [Val]); - {error, _} -> - "-" % We don't connect UDP (it can be done but we don't) - end, + MTU = G(mtu), + MTUDisc = G(mtu_discover), {ok, MIF} = socket:getopt(Sock, ip, multicast_if), {ok, MLoop} = socket:getopt(Sock, ip, multicast_loop), {ok, MTTL} = socket:getopt(Sock, ip, multicast_ttl), @@ -460,11 +463,13 @@ handler_init(Manager, ID, Peek, Sock) -> "~n (socket) RcvBuf: ~p" "~n (socket) Linger: ~p" "~n (ip) MTU: ~s" + "~n (ip) MTU Discovery: ~s" "~n (ip) Multicast IF: ~p" "~n (ip) Multicast Loop: ~p" "~n (ip) Multicast TTL: ~p", - [Domain, Type, Proto, - OOBI, SndBuf, RcvBuf, Linger, MTU, MIF, MLoop, MTTL]), + [Domain, Type, Proto, + OOBI, SndBuf, RcvBuf, Linger, + MTU, MTUDisc, MIF, MLoop, MTTL]), %% socket:setopt(Sock, otp, debug, true), handler_loop(#handler{peek = Peek, manager = Manager, -- cgit v1.2.3 From ea680e88b967440b2ecd925321ebb5dd48461608 Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Thu, 12 Jul 2018 10:42:19 +0200 Subject: [socket-nif] Add support for socket (level ip) option multicast_all Added support for the IP option MULTICAST_ALL. OTP-14831 --- erts/emulator/nifs/common/socket_nif.c | 53 +++++++++++++++++++++++++++++++++ erts/preloaded/ebin/socket.beam | Bin 44676 -> 44744 bytes erts/preloaded/src/socket.erl | 9 ++++-- lib/kernel/test/socket_client.erl | 15 ++++++---- lib/kernel/test/socket_server.erl | 4 ++- 5 files changed, 72 insertions(+), 9 deletions(-) diff --git a/erts/emulator/nifs/common/socket_nif.c b/erts/emulator/nifs/common/socket_nif.c index 65e530adee..a2bebcd621 100644 --- a/erts/emulator/nifs/common/socket_nif.c +++ b/erts/emulator/nifs/common/socket_nif.c @@ -366,6 +366,7 @@ typedef union { #define SOCKET_OPT_IP_DROP_MEMBERSHIP 5 #define SOCKET_OPT_IP_MTU 11 #define SOCKET_OPT_IP_MTU_DISCOVER 12 +#define SOCKET_OPT_IP_MULTICAST_ALL 13 #define SOCKET_OPT_IP_MULTICAST_IF 14 #define SOCKET_OPT_IP_MULTICAST_LOOP 15 #define SOCKET_OPT_IP_MULTICAST_TTL 16 @@ -872,6 +873,11 @@ static ERL_NIF_TERM nsetopt_lvl_ip_mtu_discover(ErlNifEnv* env, SocketDescriptor* descP, ERL_NIF_TERM eVal); #endif +#if defined(IP_MULTICAST_ALL) +static ERL_NIF_TERM nsetopt_lvl_ip_multicast_all(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); +#endif #if defined(IP_MULTICAST_IF) static ERL_NIF_TERM nsetopt_lvl_ip_multicast_if(ErlNifEnv* env, SocketDescriptor* descP, @@ -1063,6 +1069,10 @@ static ERL_NIF_TERM ngetopt_lvl_ip_mtu(ErlNifEnv* env, static ERL_NIF_TERM ngetopt_lvl_ip_mtu_discover(ErlNifEnv* env, SocketDescriptor* descP); #endif +#if defined(IP_MULTICAST_ALL) +static ERL_NIF_TERM ngetopt_lvl_ip_multicast_all(ErlNifEnv* env, + SocketDescriptor* descP); +#endif #if defined(IP_MULTICAST_IF) static ERL_NIF_TERM ngetopt_lvl_ip_multicast_if(ErlNifEnv* env, SocketDescriptor* descP); @@ -4232,6 +4242,25 @@ ERL_NIF_TERM nsetopt_lvl_ip_mtu_discover(ErlNifEnv* env, #endif +/* nsetopt_lvl_ip_multicast_all - Level IP MULTICAST_ALL option + */ +#if defined(IP_MULTICAST_ALL) +static +ERL_NIF_TERM nsetopt_lvl_ip_multicast_all(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) +{ +#if defined(SOL_IP) + int level = SOL_IP; +#else + int level = IPPROTO_IP; +#endif + + return nsetopt_bool_opt(env, descP, level, IP_MULTICAST_ALL, eVal); +} +#endif + + /* nsetopt_lvl_ip_multicast_if - Level IP MULTICAST_IF option * * The value is either the atom 'any' or a 4-tuple. @@ -5550,6 +5579,12 @@ ERL_NIF_TERM ngetopt_lvl_ip(ErlNifEnv* env, break; #endif +#if defined(IP_MULTICAST_ALL) + case SOCKET_OPT_IP_MULTICAST_ALL: + result = ngetopt_lvl_ip_multicast_all(env, descP); + break; +#endif + #if defined(IP_MULTICAST_IF) case SOCKET_OPT_IP_MULTICAST_IF: result = ngetopt_lvl_ip_multicast_if(env, descP); @@ -5653,6 +5688,24 @@ ERL_NIF_TERM ngetopt_lvl_ip_mtu_discover(ErlNifEnv* env, #endif +/* ngetopt_lvl_ip_multicast_all - Level IP MULTICAST_ALL option + */ +#if defined(IP_MULTICAST_ALL) +static +ERL_NIF_TERM ngetopt_lvl_ip_multicast_all(ErlNifEnv* env, + SocketDescriptor* descP) +{ +#if defined(SOL_IP) + int level = SOL_IP; +#else + int level = IPPROTO_IP; +#endif + + return ngetopt_bool_opt(env, descP, level, IP_MULTICAST_ALL); +} +#endif + + /* ngetopt_lvl_ip_multicast_if - Level IP MULTICAST_IF option */ #if defined(IP_MULTICAST_IF) diff --git a/erts/preloaded/ebin/socket.beam b/erts/preloaded/ebin/socket.beam index 7ca451b6c5..ca05529931 100644 Binary files a/erts/preloaded/ebin/socket.beam and b/erts/preloaded/ebin/socket.beam differ diff --git a/erts/preloaded/src/socket.erl b/erts/preloaded/src/socket.erl index 5338f0d988..643f59467b 100644 --- a/erts/preloaded/src/socket.erl +++ b/erts/preloaded/src/socket.erl @@ -527,7 +527,7 @@ %% -define(SOCKET_OPT_IP_MSFILTER, 10). -define(SOCKET_OPT_IP_MTU, 11). -define(SOCKET_OPT_IP_MTU_DISCOVER, 12). -%% -define(SOCKET_OPT_IP_MULTICAST_ALL, 13). +-define(SOCKET_OPT_IP_MULTICAST_ALL, 13). -define(SOCKET_OPT_IP_MULTICAST_IF, 14). -define(SOCKET_OPT_IP_MULTICAST_LOOP, 15). -define(SOCKET_OPT_IP_MULTICAST_TTL, 16). @@ -1992,6 +1992,9 @@ enc_setopt_value(ip, mtu_discover, V, _D, _T, _P) (V =:= probe) orelse is_integer(V) -> V; +enc_setopt_value(ip, multicast_all, V, _D, _T, _P) + when is_boolean(V) -> + V; enc_setopt_value(ip, multicast_if, V, _D, _T, _P) when (V =:= any) orelse (is_tuple(V) andalso (size(V) =:= 4)) -> V; @@ -2289,8 +2292,8 @@ enc_sockopt_key(ip = _L, mtu = _Opt, get = _Dir, _D, _T, _P) -> ?SOCKET_OPT_IP_MTU; enc_sockopt_key(ip = _L, mtu_discover = _Opt, _Dir, _D, _T, _P) -> ?SOCKET_OPT_IP_MTU_DISCOVER; -enc_sockopt_key(ip = L, multicast_all = Opt, _Dir, _D, _T, _P) -> - not_supported({L, Opt}); +enc_sockopt_key(ip = _L, multicast_all = _Opt, _Dir, _D, _T, _P) -> + ?SOCKET_OPT_IP_MULTICAST_ALL; enc_sockopt_key(ip = _L, multicast_if = _Opt, _Dir, _D, _T, _P) -> ?SOCKET_OPT_IP_MULTICAST_IF; enc_sockopt_key(ip = _L, multicast_loop = _Opt, _Dir, _D, _T, _P) -> diff --git a/lib/kernel/test/socket_client.erl b/lib/kernel/test/socket_client.erl index 6c6dc0444c..fa905de954 100644 --- a/lib/kernel/test/socket_client.erl +++ b/lib/kernel/test/socket_client.erl @@ -82,6 +82,7 @@ do_start(Domain, stream = Type, Proto, SA) -> {ok, Linger} = socket:getopt(Sock, socket, linger), {ok, MTU} = socket:getopt(Sock, ip, mtu), {ok, MTUDisc} = socket:getopt(Sock, ip, mtu_discover), + {ok, MALL} = socket:getopt(Sock, ip, multicast_all), {ok, MIF} = socket:getopt(Sock, ip, multicast_if), {ok, MLoop} = socket:getopt(Sock, ip, multicast_loop), {ok, MTTL} = socket:getopt(Sock, ip, multicast_ttl), @@ -98,6 +99,7 @@ do_start(Domain, stream = Type, Proto, SA) -> "~n (socket) Linger: ~p" "~n (ip) MTU: ~p" "~n (ip) MTU Discovery: ~p" + "~n (ip) Multicast ALL: ~p" "~n (ip) Multicast IF: ~p" "~n (ip) Multicast Loop: ~p" "~n (ip) Multicast TTL: ~p" @@ -105,7 +107,7 @@ do_start(Domain, stream = Type, Proto, SA) -> [Name, Peer, Domain, Type, Proto, OOBI, SndBuf, RcvBuf, Linger, - MTU, MTUDisc, MIF, MLoop, MTTL]), + MTU, MTUDisc, MALL, MIF, MLoop, MTTL]), %% Give the server some time... ?LIB:sleep(5000), %% ok = socket:close(Sock), @@ -127,6 +129,7 @@ do_start(Domain, dgram = Type, Proto, SA) -> {ok, SndBuf} = socket:getopt(Sock, socket, sndbuf), {ok, RcvBuf} = socket:getopt(Sock, socket, rcvbuf), {ok, Linger} = socket:getopt(Sock, socket, linger), + {ok, MALL} = socket:getopt(Sock, ip, multicast_all), {ok, MIF} = socket:getopt(Sock, ip, multicast_if), {ok, MLoop} = socket:getopt(Sock, ip, multicast_loop), {ok, MTTL} = socket:getopt(Sock, ip, multicast_ttl), @@ -138,15 +141,17 @@ do_start(Domain, dgram = Type, Proto, SA) -> "~n (socket) SndBuf: ~p" "~n (socket) RcvBuf: ~p" "~n (socket) Linger: ~p" + "~n (ip) Multicast ALL: ~p" "~n (ip) Multicast IF: ~p" "~n (ip) Multicast Loop: ~p" "~n (ip) Multicast TTL: ~p" - "~n => wait some", - [Domain, Type, Proto, - OOBI, SndBuf, RcvBuf, Linger, MIF, MLoop, MTTL]), + "~n => wait some", + [Domain, Type, Proto, + OOBI, SndBuf, RcvBuf, Linger, + MALL, MIF, MLoop, MTTL]), ?LIB:sleep(5000), %% ok = socket:close(Sock), - send_loop(#client{socket = Sock, + send_loop(#client{socket = Sock, type = Type, dest = SA}) catch diff --git a/lib/kernel/test/socket_server.erl b/lib/kernel/test/socket_server.erl index 68a6aebc50..f2d63c4dc1 100644 --- a/lib/kernel/test/socket_server.erl +++ b/lib/kernel/test/socket_server.erl @@ -451,6 +451,7 @@ handler_init(Manager, ID, Peek, Sock) -> {ok, Linger} = socket:getopt(Sock, socket, linger), MTU = G(mtu), MTUDisc = G(mtu_discover), + {ok, MALL} = socket:getopt(Sock, ip, multicast_all), {ok, MIF} = socket:getopt(Sock, ip, multicast_if), {ok, MLoop} = socket:getopt(Sock, ip, multicast_loop), {ok, MTTL} = socket:getopt(Sock, ip, multicast_ttl), @@ -464,12 +465,13 @@ handler_init(Manager, ID, Peek, Sock) -> "~n (socket) Linger: ~p" "~n (ip) MTU: ~s" "~n (ip) MTU Discovery: ~s" + "~n (ip) Multicast ALL: ~p" "~n (ip) Multicast IF: ~p" "~n (ip) Multicast Loop: ~p" "~n (ip) Multicast TTL: ~p", [Domain, Type, Proto, OOBI, SndBuf, RcvBuf, Linger, - MTU, MTUDisc, MIF, MLoop, MTTL]), + MTU, MTUDisc, MALL, MIF, MLoop, MTTL]), %% socket:setopt(Sock, otp, debug, true), handler_loop(#handler{peek = Peek, manager = Manager, -- cgit v1.2.3 From 0122d2cafdb6f44c221796f1a6b2a5188dfb153d Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Thu, 12 Jul 2018 11:04:38 +0200 Subject: [socket-nif] Add support for socket (level ip) option nodefrag Added support for the IP option NODEFRAG. OTP-14831 --- erts/emulator/nifs/common/socket_nif.c | 65 +++++++++++++++++++++++++++++++++ erts/preloaded/ebin/socket.beam | Bin 44744 -> 44804 bytes erts/preloaded/src/socket.erl | 9 +++-- lib/kernel/test/socket_server.erl | 23 ++++++++---- 4 files changed, 86 insertions(+), 11 deletions(-) diff --git a/erts/emulator/nifs/common/socket_nif.c b/erts/emulator/nifs/common/socket_nif.c index a2bebcd621..0972834cb2 100644 --- a/erts/emulator/nifs/common/socket_nif.c +++ b/erts/emulator/nifs/common/socket_nif.c @@ -370,6 +370,7 @@ typedef union { #define SOCKET_OPT_IP_MULTICAST_IF 14 #define SOCKET_OPT_IP_MULTICAST_LOOP 15 #define SOCKET_OPT_IP_MULTICAST_TTL 16 +#define SOCKET_OPT_IP_NODEFRAG 17 #define SOCKET_OPT_IP_RECVTOS 25 #define SOCKET_OPT_IP_ROUTER_ALERT 28 #define SOCKET_OPT_IP_TOS 30 @@ -893,6 +894,11 @@ static ERL_NIF_TERM nsetopt_lvl_ip_multicast_ttl(ErlNifEnv* env, SocketDescriptor* descP, ERL_NIF_TERM eVal); #endif +#if defined(IP_NODEFRAG) +static ERL_NIF_TERM nsetopt_lvl_ip_nodefrag(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); +#endif #if defined(IP_RECVTOS) static ERL_NIF_TERM nsetopt_lvl_ip_recvtos(ErlNifEnv* env, SocketDescriptor* descP, @@ -1085,6 +1091,10 @@ static ERL_NIF_TERM ngetopt_lvl_ip_multicast_loop(ErlNifEnv* env, static ERL_NIF_TERM ngetopt_lvl_ip_multicast_ttl(ErlNifEnv* env, SocketDescriptor* descP); #endif +#if defined(IP_NODEFRAG) +static ERL_NIF_TERM ngetopt_lvl_ip_nodefrag(ErlNifEnv* env, + SocketDescriptor* descP); +#endif #if defined(IP_RECVTOS) static ERL_NIF_TERM ngetopt_lvl_ip_recvtos(ErlNifEnv* env, SocketDescriptor* descP); @@ -4055,6 +4065,12 @@ ERL_NIF_TERM nsetopt_lvl_ip(ErlNifEnv* env, break; #endif +#if defined(IP_MULTICAST_ALL) + case SOCKET_OPT_IP_MULTICAST_ALL: + result = nsetopt_lvl_ip_multicast_all(env, descP, eVal); + break; +#endif + #if defined(IP_MULTICAST_IF) case SOCKET_OPT_IP_MULTICAST_IF: result = nsetopt_lvl_ip_multicast_if(env, descP, eVal); @@ -4073,6 +4089,12 @@ ERL_NIF_TERM nsetopt_lvl_ip(ErlNifEnv* env, break; #endif +#if defined(IP_NODEFRAG) + case SOCKET_OPT_IP_NODEFRAG: + result = nsetopt_lvl_ip_nodefrag(env, descP, eVal); + break; +#endif + #if defined(IP_RECVTOS) case SOCKET_OPT_IP_RECVTOS: result = nsetopt_lvl_ip_recvtos(env, descP, eVal); @@ -4338,6 +4360,25 @@ ERL_NIF_TERM nsetopt_lvl_ip_multicast_ttl(ErlNifEnv* env, #endif +/* nsetopt_lvl_ip_nodefrag - Level IP NODEFRAG option + */ +#if defined(IP_NODEFRAG) +static +ERL_NIF_TERM nsetopt_lvl_ip_nodefrag(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) +{ +#if defined(SOL_IP) + int level = SOL_IP; +#else + int level = IPPROTO_IP; +#endif + + return nsetopt_bool_opt(env, descP, level, IP_NODEFRAG, eVal); +} +#endif + + /* nsetopt_lvl_ip_recvtos - Level IP RECVTOS option */ #if defined(IP_RECVTOS) @@ -5603,6 +5644,12 @@ ERL_NIF_TERM ngetopt_lvl_ip(ErlNifEnv* env, break; #endif +#if defined(IP_NODEFRAG) + case SOCKET_OPT_IP_NODEFRAG: + result = ngetopt_lvl_ip_nodefrag(env, descP); + break; +#endif + #if defined(IP_RECVTOS) case SOCKET_OPT_IP_RECVTOS: result = ngetopt_lvl_ip_recvtos(env, descP); @@ -5779,6 +5826,24 @@ ERL_NIF_TERM ngetopt_lvl_ip_multicast_ttl(ErlNifEnv* env, #endif +/* ngetopt_lvl_ip_nodefrag - Level IP NODEFRAG option + */ +#if defined(IP_NODEFRAG) +static +ERL_NIF_TERM ngetopt_lvl_ip_nodefrag(ErlNifEnv* env, + SocketDescriptor* descP) +{ +#if defined(SOL_IP) + int level = SOL_IP; +#else + int level = IPPROTO_IP; +#endif + + return ngetopt_bool_opt(env, descP, level, IP_NODEFRAG); +} +#endif + + /* ngetopt_lvl_ip_recvtos - Level IP RECVTOS option */ #if defined(IP_RECVTOS) diff --git a/erts/preloaded/ebin/socket.beam b/erts/preloaded/ebin/socket.beam index ca05529931..359a329287 100644 Binary files a/erts/preloaded/ebin/socket.beam and b/erts/preloaded/ebin/socket.beam differ diff --git a/erts/preloaded/src/socket.erl b/erts/preloaded/src/socket.erl index 643f59467b..3077a794a5 100644 --- a/erts/preloaded/src/socket.erl +++ b/erts/preloaded/src/socket.erl @@ -531,7 +531,7 @@ -define(SOCKET_OPT_IP_MULTICAST_IF, 14). -define(SOCKET_OPT_IP_MULTICAST_LOOP, 15). -define(SOCKET_OPT_IP_MULTICAST_TTL, 16). -%% -define(SOCKET_OPT_IP_NODEFRAG, 17). +-define(SOCKET_OPT_IP_NODEFRAG, 17). %% -define(SOCKET_OPT_IP_OPTIONS, 18). %% -define(SOCKET_OPT_IP_PKTINFO, 19). %% -define(SOCKET_OPT_IP_RECVERR, 20). @@ -2004,6 +2004,9 @@ enc_setopt_value(ip, multicast_loop, V, _D, _T, _P) enc_setopt_value(ip, multicast_ttl, V, _D, _T, _P) when is_integer(V) andalso (0 =< V) andalso (V =< 255) -> V; +enc_setopt_value(ip, nodefrag, V, _D, _T, _P) + when is_boolean(V) -> + V; enc_setopt_value(ip, recvtos, V, _D, _T, _P) when is_boolean(V) -> V; @@ -2300,8 +2303,8 @@ enc_sockopt_key(ip = _L, multicast_loop = _Opt, _Dir, _D, _T, _P) -> ?SOCKET_OPT_IP_MULTICAST_LOOP; enc_sockopt_key(ip = _L, multicast_ttl = _Opt, _Dir, _D, _T, _P) -> ?SOCKET_OPT_IP_MULTICAST_TTL; -enc_sockopt_key(ip = L, nodefrag = Opt, _Dir, _D, raw = _T, _P) -> - not_supported({L, Opt}); +enc_sockopt_key(ip = _L, nodefrag = _Opt, _Dir, _D, raw = _T, _P) -> + ?SOCKET_OPT_IP_NODEFRAG; enc_sockopt_key(ip = L, options = Opt, _Dir, _D, _T, _P) -> not_supported({Opt, L}); enc_sockopt_key(ip = L, pktinfo = Opt, _Dir, _D, _T, _P) -> diff --git a/lib/kernel/test/socket_server.erl b/lib/kernel/test/socket_server.erl index f2d63c4dc1..6a207a7b1b 100644 --- a/lib/kernel/test/socket_server.erl +++ b/lib/kernel/test/socket_server.erl @@ -436,10 +436,14 @@ handler_init(Manager, ID, Peek, Sock) -> i("got continue"), handler_reply(Pid, Ref, ok), G = fun(K) -> case socket:getopt(Sock, ip, K) of - {ok, Val} -> - f("~w", [Val]); - {error, _} -> - "-" + {ok, Val} -> + f("~p", [Val]); + {error, R} when is_atom(R) -> + f("error: ~w", [R]); + {error, {T, R}} when is_atom(T) -> + f("error: ~w, ~p", [T, R]); + {error, R} -> + f("error: ~p", [R]) end end, {ok, Domain} = socket:getopt(Sock, socket, domain), @@ -449,12 +453,13 @@ handler_init(Manager, ID, Peek, Sock) -> {ok, SndBuf} = socket:getopt(Sock, socket, sndbuf), {ok, RcvBuf} = socket:getopt(Sock, socket, rcvbuf), {ok, Linger} = socket:getopt(Sock, socket, linger), - MTU = G(mtu), - MTUDisc = G(mtu_discover), + MTU = G(mtu), + MTUDisc = G(mtu_discover), {ok, MALL} = socket:getopt(Sock, ip, multicast_all), {ok, MIF} = socket:getopt(Sock, ip, multicast_if), {ok, MLoop} = socket:getopt(Sock, ip, multicast_loop), {ok, MTTL} = socket:getopt(Sock, ip, multicast_ttl), + NF = G(nodefrag), % raw only i("got continue when: " "~n (socket) Domain: ~p" "~n (socket) Type: ~p" @@ -468,10 +473,12 @@ handler_init(Manager, ID, Peek, Sock) -> "~n (ip) Multicast ALL: ~p" "~n (ip) Multicast IF: ~p" "~n (ip) Multicast Loop: ~p" - "~n (ip) Multicast TTL: ~p", + "~n (ip) Multicast TTL: ~p" + "~n (ip) NodeFrag: ~s", [Domain, Type, Proto, OOBI, SndBuf, RcvBuf, Linger, - MTU, MTUDisc, MALL, MIF, MLoop, MTTL]), + MTU, MTUDisc, MALL, MIF, MLoop, MTTL, + NF]), %% socket:setopt(Sock, otp, debug, true), handler_loop(#handler{peek = Peek, manager = Manager, -- cgit v1.2.3 From ca81f1d1602cf994fca9fcd61e892c76e4e2742c Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Thu, 12 Jul 2018 11:22:53 +0200 Subject: [socket-nif] Add support for socket (level ip) option recvttl Added support for the IP option RECVTTL. OTP-14831 --- erts/emulator/nifs/common/socket_nif.c | 59 +++++++++++++++++++++++++++++++++ erts/preloaded/ebin/socket.beam | Bin 44804 -> 44828 bytes erts/preloaded/src/socket.erl | 9 +++-- lib/kernel/test/socket_client.erl | 34 +++++++++++-------- lib/kernel/test/socket_server.erl | 8 +++-- 5 files changed, 92 insertions(+), 18 deletions(-) diff --git a/erts/emulator/nifs/common/socket_nif.c b/erts/emulator/nifs/common/socket_nif.c index 0972834cb2..22ac124802 100644 --- a/erts/emulator/nifs/common/socket_nif.c +++ b/erts/emulator/nifs/common/socket_nif.c @@ -372,6 +372,7 @@ typedef union { #define SOCKET_OPT_IP_MULTICAST_TTL 16 #define SOCKET_OPT_IP_NODEFRAG 17 #define SOCKET_OPT_IP_RECVTOS 25 +#define SOCKET_OPT_IP_RECVTTL 26 #define SOCKET_OPT_IP_ROUTER_ALERT 28 #define SOCKET_OPT_IP_TOS 30 #define SOCKET_OPT_IP_TTL 32 @@ -904,6 +905,11 @@ static ERL_NIF_TERM nsetopt_lvl_ip_recvtos(ErlNifEnv* env, SocketDescriptor* descP, ERL_NIF_TERM eVal); #endif +#if defined(IP_RECVTTL) +static ERL_NIF_TERM nsetopt_lvl_ip_recvttl(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); +#endif #if defined(IP_ROUTER_ALERT) static ERL_NIF_TERM nsetopt_lvl_ip_router_alert(ErlNifEnv* env, SocketDescriptor* descP, @@ -1099,6 +1105,10 @@ static ERL_NIF_TERM ngetopt_lvl_ip_nodefrag(ErlNifEnv* env, static ERL_NIF_TERM ngetopt_lvl_ip_recvtos(ErlNifEnv* env, SocketDescriptor* descP); #endif +#if defined(IP_RECVTTL) +static ERL_NIF_TERM ngetopt_lvl_ip_recvttl(ErlNifEnv* env, + SocketDescriptor* descP); +#endif #if defined(IP_ROUTER_ALERT) static ERL_NIF_TERM ngetopt_lvl_ip_router_alert(ErlNifEnv* env, SocketDescriptor* descP); @@ -4101,6 +4111,12 @@ ERL_NIF_TERM nsetopt_lvl_ip(ErlNifEnv* env, break; #endif +#if defined(IP_RECVTTL) + case SOCKET_OPT_IP_RECVTTL: + result = nsetopt_lvl_ip_recvttl(env, descP, eVal); + break; +#endif + #if defined(IP_ROUTER_ALERT) case SOCKET_OPT_IP_ROUTER_ALERT: result = nsetopt_lvl_ip_router_alert(env, descP, eVal); @@ -4398,6 +4414,25 @@ ERL_NIF_TERM nsetopt_lvl_ip_recvtos(ErlNifEnv* env, #endif +/* nsetopt_lvl_ip_recvttl - Level IP RECVTTL option + */ +#if defined(IP_RECVTTL) +static +ERL_NIF_TERM nsetopt_lvl_ip_recvttl(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) +{ +#if defined(SOL_IP) + int level = SOL_IP; +#else + int level = IPPROTO_IP; +#endif + + return nsetopt_bool_opt(env, descP, level, IP_RECVTTL, eVal); +} +#endif + + /* nsetopt_lvl_ip_router_alert - Level IP ROUTER_ALERT option */ #if defined(IP_ROUTER_ALERT) @@ -5656,6 +5691,12 @@ ERL_NIF_TERM ngetopt_lvl_ip(ErlNifEnv* env, break; #endif +#if defined(IP_RECVTTL) + case SOCKET_OPT_IP_RECVTTL: + result = ngetopt_lvl_ip_recvttl(env, descP); + break; +#endif + #if defined(IP_ROUTER_ALERT) case SOCKET_OPT_IP_ROUTER_ALERT: result = ngetopt_lvl_ip_router_alert(env, descP); @@ -5862,6 +5903,24 @@ ERL_NIF_TERM ngetopt_lvl_ip_recvtos(ErlNifEnv* env, #endif +/* ngetopt_lvl_ip_recvttl - Level IP RECVTTL option + */ +#if defined(IP_RECVTTL) +static +ERL_NIF_TERM ngetopt_lvl_ip_recvttl(ErlNifEnv* env, + SocketDescriptor* descP) +{ +#if defined(SOL_IP) + int level = SOL_IP; +#else + int level = IPPROTO_IP; +#endif + + return ngetopt_bool_opt(env, descP, level, IP_RECVTTL); +} +#endif + + /* ngetopt_lvl_ip_router_alert - Level IP ROUTER_ALERT option */ #if defined(IP_ROUTER_ALERT) diff --git a/erts/preloaded/ebin/socket.beam b/erts/preloaded/ebin/socket.beam index 359a329287..2f81e4cc29 100644 Binary files a/erts/preloaded/ebin/socket.beam and b/erts/preloaded/ebin/socket.beam differ diff --git a/erts/preloaded/src/socket.erl b/erts/preloaded/src/socket.erl index 3077a794a5..d54c8d5f33 100644 --- a/erts/preloaded/src/socket.erl +++ b/erts/preloaded/src/socket.erl @@ -540,7 +540,7 @@ %% -define(SOCKET_OPT_IP_RECVOPTS, 23). %% -define(SOCKET_OPT_IP_RECVORIGDSTADDR, 24). -define(SOCKET_OPT_IP_RECVTOS, 25). -%% -define(SOCKET_OPT_IP_RECVTTL, 26). +-define(SOCKET_OPT_IP_RECVTTL, 26). %% -define(SOCKET_OPT_IP_RETOPTS, 27). -define(SOCKET_OPT_IP_ROUTER_ALERT, 28). %% -define(SOCKET_OPT_IP_SNDSRCADDR, 29). @@ -2010,6 +2010,9 @@ enc_setopt_value(ip, nodefrag, V, _D, _T, _P) enc_setopt_value(ip, recvtos, V, _D, _T, _P) when is_boolean(V) -> V; +enc_setopt_value(ip, recvttl, V, _D, _T, _P) + when is_boolean(V) -> + V; enc_setopt_value(ip, router_alert, V, _D, _T, _P) when is_integer(V) -> V; @@ -2323,8 +2326,8 @@ enc_sockopt_key(ip = L, recvorigdstaddr = Opt, _Dir, _D, _T, _P) -> not_supported({L, Opt}); enc_sockopt_key(ip, recvtos = _Opt, _Dir, _D, _T, _P) -> ?SOCKET_OPT_IP_RECVTOS; -enc_sockopt_key(ip = L, recvttl = Opt, _Dir, _D, T, _P) when (T =/= stream) -> - not_supported({L, Opt}); +enc_sockopt_key(ip = _L, recvttl = _Opt, _Dir, _D, T, _P) when (T =/= stream) -> + ?SOCKET_OPT_IP_RECVTTL; enc_sockopt_key(ip = L, retopts = Opt, _Dir, _D, _T, _P) -> not_supported({L, Opt}); enc_sockopt_key(ip, router_alert = _Opt, _Dir, _D, raw = _T, _P) -> diff --git a/lib/kernel/test/socket_client.erl b/lib/kernel/test/socket_client.erl index fa905de954..8ec9a02374 100644 --- a/lib/kernel/test/socket_client.erl +++ b/lib/kernel/test/socket_client.erl @@ -86,6 +86,7 @@ do_start(Domain, stream = Type, Proto, SA) -> {ok, MIF} = socket:getopt(Sock, ip, multicast_if), {ok, MLoop} = socket:getopt(Sock, ip, multicast_loop), {ok, MTTL} = socket:getopt(Sock, ip, multicast_ttl), + {ok, RecvTOS} = socket:getopt(Sock, ip, recvtos), i("connected: " "~n From: ~p" "~n To: ~p" @@ -103,11 +104,13 @@ do_start(Domain, stream = Type, Proto, SA) -> "~n (ip) Multicast IF: ~p" "~n (ip) Multicast Loop: ~p" "~n (ip) Multicast TTL: ~p" + "~n (ip) RecvTOS: ~p" "~n => wait some", [Name, Peer, Domain, Type, Proto, OOBI, SndBuf, RcvBuf, Linger, - MTU, MTUDisc, MALL, MIF, MLoop, MTTL]), + MTU, MTUDisc, MALL, MIF, MLoop, MTTL, + RecvTOS]), %% Give the server some time... ?LIB:sleep(5000), %% ok = socket:close(Sock), @@ -122,17 +125,19 @@ do_start(Domain, dgram = Type, Proto, SA) -> try do_init(Domain, Type, Proto) of Sock -> %% Give the server some time... - {ok, Domain} = socket:getopt(Sock, socket, domain), - {ok, Type} = socket:getopt(Sock, socket, type), - {ok, Proto} = socket:getopt(Sock, socket, protocol), - {ok, OOBI} = socket:getopt(Sock, socket, oobinline), - {ok, SndBuf} = socket:getopt(Sock, socket, sndbuf), - {ok, RcvBuf} = socket:getopt(Sock, socket, rcvbuf), - {ok, Linger} = socket:getopt(Sock, socket, linger), - {ok, MALL} = socket:getopt(Sock, ip, multicast_all), - {ok, MIF} = socket:getopt(Sock, ip, multicast_if), - {ok, MLoop} = socket:getopt(Sock, ip, multicast_loop), - {ok, MTTL} = socket:getopt(Sock, ip, multicast_ttl), + {ok, Domain} = socket:getopt(Sock, socket, domain), + {ok, Type} = socket:getopt(Sock, socket, type), + {ok, Proto} = socket:getopt(Sock, socket, protocol), + {ok, OOBI} = socket:getopt(Sock, socket, oobinline), + {ok, SndBuf} = socket:getopt(Sock, socket, sndbuf), + {ok, RcvBuf} = socket:getopt(Sock, socket, rcvbuf), + {ok, Linger} = socket:getopt(Sock, socket, linger), + {ok, MALL} = socket:getopt(Sock, ip, multicast_all), + {ok, MIF} = socket:getopt(Sock, ip, multicast_if), + {ok, MLoop} = socket:getopt(Sock, ip, multicast_loop), + {ok, MTTL} = socket:getopt(Sock, ip, multicast_ttl), + {ok, RecvTOS} = socket:getopt(Sock, ip, recvtos), + {ok, RecvTTL} = socket:getopt(Sock, ip, recvttl), i("initiated when: " "~n (socket) Domain: ~p" "~n (socket) Type: ~p" @@ -145,10 +150,13 @@ do_start(Domain, dgram = Type, Proto, SA) -> "~n (ip) Multicast IF: ~p" "~n (ip) Multicast Loop: ~p" "~n (ip) Multicast TTL: ~p" + "~n (ip) RecvTOS: ~p" + "~n (ip) RecvTTL: ~p" "~n => wait some", [Domain, Type, Proto, OOBI, SndBuf, RcvBuf, Linger, - MALL, MIF, MLoop, MTTL]), + MALL, MIF, MLoop, MTTL, + RecvTOS, RecvTTL]), ?LIB:sleep(5000), %% ok = socket:close(Sock), send_loop(#client{socket = Sock, diff --git a/lib/kernel/test/socket_server.erl b/lib/kernel/test/socket_server.erl index 6a207a7b1b..ff8f6575a3 100644 --- a/lib/kernel/test/socket_server.erl +++ b/lib/kernel/test/socket_server.erl @@ -460,6 +460,8 @@ handler_init(Manager, ID, Peek, Sock) -> {ok, MLoop} = socket:getopt(Sock, ip, multicast_loop), {ok, MTTL} = socket:getopt(Sock, ip, multicast_ttl), NF = G(nodefrag), % raw only + RecvTOS = G(recvtos), + RecvTTL = G(recvttl), % not stream i("got continue when: " "~n (socket) Domain: ~p" "~n (socket) Type: ~p" @@ -474,11 +476,13 @@ handler_init(Manager, ID, Peek, Sock) -> "~n (ip) Multicast IF: ~p" "~n (ip) Multicast Loop: ~p" "~n (ip) Multicast TTL: ~p" - "~n (ip) NodeFrag: ~s", + "~n (ip) NodeFrag: ~s" + "~n (ip) RecvTOS: ~s" + "~n (ip) RecvTTL: ~s", [Domain, Type, Proto, OOBI, SndBuf, RcvBuf, Linger, MTU, MTUDisc, MALL, MIF, MLoop, MTTL, - NF]), + NF, RecvTOS, RecvTTL]), %% socket:setopt(Sock, otp, debug, true), handler_loop(#handler{peek = Peek, manager = Manager, -- cgit v1.2.3 From 3d045788e8033e2e76396900358976ac92fb184a Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Thu, 12 Jul 2018 12:43:41 +0200 Subject: [socket-nif] Add support for socket (level ip) option(s) sources Added support for the IP options: ADD_SOURCE_MEMBERSHIP, DROP_SOURCE_MEMBERSHIP, BLOCK_SOURCE and UNBLOCK_SOURCE. OTP-14831 --- erts/emulator/nifs/common/socket_nif.c | 353 ++++++++++++++++++++++++++------- erts/preloaded/ebin/socket.beam | Bin 44828 -> 45672 bytes erts/preloaded/src/socket.erl | 70 +++++-- 3 files changed, 337 insertions(+), 86 deletions(-) diff --git a/erts/emulator/nifs/common/socket_nif.c b/erts/emulator/nifs/common/socket_nif.c index 22ac124802..109d1a2d5c 100644 --- a/erts/emulator/nifs/common/socket_nif.c +++ b/erts/emulator/nifs/common/socket_nif.c @@ -362,20 +362,24 @@ typedef union { #define SOCKET_OPT_SOCK_SNDBUF 27 #define SOCKET_OPT_SOCK_TYPE 32 -#define SOCKET_OPT_IP_ADD_MEMBERSHIP 1 -#define SOCKET_OPT_IP_DROP_MEMBERSHIP 5 -#define SOCKET_OPT_IP_MTU 11 -#define SOCKET_OPT_IP_MTU_DISCOVER 12 -#define SOCKET_OPT_IP_MULTICAST_ALL 13 -#define SOCKET_OPT_IP_MULTICAST_IF 14 -#define SOCKET_OPT_IP_MULTICAST_LOOP 15 -#define SOCKET_OPT_IP_MULTICAST_TTL 16 -#define SOCKET_OPT_IP_NODEFRAG 17 -#define SOCKET_OPT_IP_RECVTOS 25 -#define SOCKET_OPT_IP_RECVTTL 26 -#define SOCKET_OPT_IP_ROUTER_ALERT 28 -#define SOCKET_OPT_IP_TOS 30 -#define SOCKET_OPT_IP_TTL 32 +#define SOCKET_OPT_IP_ADD_MEMBERSHIP 1 +#define SOCKET_OPT_IP_ADD_SOURCE_MEMBERSHIP 2 +#define SOCKET_OPT_IP_BLOCK_SOURCE 3 +#define SOCKET_OPT_IP_DROP_MEMBERSHIP 5 +#define SOCKET_OPT_IP_DROP_SOURCE_MEMBERSHIP 6 +#define SOCKET_OPT_IP_MTU 11 +#define SOCKET_OPT_IP_MTU_DISCOVER 12 +#define SOCKET_OPT_IP_MULTICAST_ALL 13 +#define SOCKET_OPT_IP_MULTICAST_IF 14 +#define SOCKET_OPT_IP_MULTICAST_LOOP 15 +#define SOCKET_OPT_IP_MULTICAST_TTL 16 +#define SOCKET_OPT_IP_NODEFRAG 17 +#define SOCKET_OPT_IP_RECVTOS 25 +#define SOCKET_OPT_IP_RECVTTL 26 +#define SOCKET_OPT_IP_ROUTER_ALERT 28 +#define SOCKET_OPT_IP_TOS 30 +#define SOCKET_OPT_IP_TTL 32 +#define SOCKET_OPT_IP_UNBLOCK_SOURCE 33 #define SOCKET_OPT_IPV6_HOPLIMIT 12 @@ -794,6 +798,8 @@ static ERL_NIF_TERM nsetopt_lvl_socket(ErlNifEnv* env, SocketDescriptor* descP, int eOpt, ERL_NIF_TERM eVal); + +/* *** Handling set of socket options for level = socket *** */ #if defined(SO_BROADCAST) static ERL_NIF_TERM nsetopt_lvl_sock_broadcast(ErlNifEnv* env, SocketDescriptor* descP, @@ -853,22 +859,32 @@ static ERL_NIF_TERM nsetopt_lvl_ip(ErlNifEnv* env, SocketDescriptor* descP, int eOpt, ERL_NIF_TERM eVal); + +/* *** Handling set of socket options for level = ip *** */ #if defined(IP_ADD_MEMBERSHIP) static ERL_NIF_TERM nsetopt_lvl_ip_add_membership(ErlNifEnv* env, SocketDescriptor* descP, ERL_NIF_TERM eVal); #endif +#if defined(IP_ADD_SOURCE_MEMBERSHIP) +static ERL_NIF_TERM nsetopt_lvl_ip_add_source_membership(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); +#endif +#if defined(IP_BLOCK_SOURCE) +static ERL_NIF_TERM nsetopt_lvl_ip_block_source(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); +#endif #if defined(IP_DROP_MEMBERSHIP) static ERL_NIF_TERM nsetopt_lvl_ip_drop_membership(ErlNifEnv* env, SocketDescriptor* descP, ERL_NIF_TERM eVal); #endif -#if defined(IP_DROP_MEMBERSHIP) || defined(IP_ADD_MEMBERSHIP) -static -ERL_NIF_TERM nsetopt_lvl_ip_update_membership(ErlNifEnv* env, - SocketDescriptor* descP, - ERL_NIF_TERM eVal, - int opt); +#if defined(IP_DROP_SOURCE_MEMBERSHIP) +static ERL_NIF_TERM nsetopt_lvl_ip_drop_source_membership(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); #endif #if defined(IP_MTU_DISCOVER) static ERL_NIF_TERM nsetopt_lvl_ip_mtu_discover(ErlNifEnv* env, @@ -925,6 +941,29 @@ static ERL_NIF_TERM nsetopt_lvl_ip_ttl(ErlNifEnv* env, SocketDescriptor* descP, ERL_NIF_TERM eVal); #endif +#if defined(IP_UNBLOCK_SOURCE) +static ERL_NIF_TERM nsetopt_lvl_ip_unblock_source(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); +#endif + +#if defined(IP_DROP_MEMBERSHIP) || defined(IP_ADD_MEMBERSHIP) +static +ERL_NIF_TERM nsetopt_lvl_ip_update_membership(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal, + int opt); +#endif +#if defined(IP_ADD_SOURCE_MEMBERSHIP) || defined(IP_DROP_SOURCE_MEMBERSHIP) || defined(IP_BLOCK_SOURCE) || defined(IP_UNBLOCK_SOURCE) +static +ERL_NIF_TERM nsetopt_lvl_ip_update_source(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal, + int opt); +#endif + + +/* *** Handling set of socket options for level = ipv6 *** */ #if defined(SOL_IPV6) static ERL_NIF_TERM nsetopt_lvl_ipv6(ErlNifEnv* env, SocketDescriptor* descP, @@ -1493,6 +1532,7 @@ static char str_num_tseqpkgs[] = "num_type_seqpacket"; static char str_num_tstreams[] = "num_type_stream"; static char str_probe[] = "probe"; static char str_select[] = "select"; +static char str_sourceaddr[] = "sourceaddr"; static char str_timeout[] = "timeout"; static char str_true[] = "true"; static char str_want[] = "want"; @@ -1578,6 +1618,7 @@ static ERL_NIF_TERM atom_num_tseqpkgs; static ERL_NIF_TERM atom_num_tstreams; static ERL_NIF_TERM atom_probe; static ERL_NIF_TERM atom_select; +static ERL_NIF_TERM atom_sourceaddr; static ERL_NIF_TERM atom_timeout; static ERL_NIF_TERM atom_true; static ERL_NIF_TERM atom_want; @@ -4063,12 +4104,30 @@ ERL_NIF_TERM nsetopt_lvl_ip(ErlNifEnv* env, break; #endif +#if defined(IP_ADD_SOURCE_MEMBERSHIP) + case SOCKET_OPT_IP_ADD_SOURCE_MEMBERSHIP: + result = nsetopt_lvl_ip_add_source_membership(env, descP, eVal); + break; +#endif + +#if defined(IP_BLOCK_SOURCE) + case SOCKET_OPT_IP_BLOCK_SOURCE: + result = nsetopt_lvl_ip_block_source(env, descP, eVal); + break; +#endif + #if defined(IP_DROP_MEMBERSHIP) case SOCKET_OPT_IP_DROP_MEMBERSHIP: result = nsetopt_lvl_ip_drop_membership(env, descP, eVal); break; #endif +#if defined(IP_DROP_SOURCE_MEMBERSHIP) + case SOCKET_OPT_IP_DROP_SOURCE_MEMBERSHIP: + result = nsetopt_lvl_ip_drop_source_membership(env, descP, eVal); + break; +#endif + #if defined(IP_MTU_DISCOVER) case SOCKET_OPT_IP_MTU_DISCOVER: result = nsetopt_lvl_ip_mtu_discover(env, descP, eVal); @@ -4135,6 +4194,12 @@ ERL_NIF_TERM nsetopt_lvl_ip(ErlNifEnv* env, break; #endif +#if defined(IP_UNBLOCK_SOURCE) + case SOCKET_OPT_IP_UNBLOCK_SOURCE: + result = nsetopt_lvl_ip_unblock_source(env, descP, eVal); + break; +#endif + default: result = esock_make_error(env, esock_atom_einval); break; @@ -4162,6 +4227,47 @@ ERL_NIF_TERM nsetopt_lvl_ip_add_membership(ErlNifEnv* env, #endif +/* nsetopt_lvl_ip_add_source_membership - Level IP ADD_SOURCE_MEMBERSHIP option + * + * The value is a map with three attributes: multiaddr, interface and + * sourceaddr. + * The attribute 'multiaddr' is always a 4-tuple (IPv4 address). + * The attribute 'interface' is always a 4-tuple (IPv4 address). + * The attribute 'sourceaddr' is always a 4-tuple (IPv4 address). + * (IPv4 address). + */ +#if defined(IP_ADD_SOURCE_MEMBERSHIP) +static +ERL_NIF_TERM nsetopt_lvl_ip_add_source_membership(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) +{ + return nsetopt_lvl_ip_update_source(env, descP, eVal, + IP_ADD_SOURCE_MEMBERSHIP); +} +#endif + + +/* nsetopt_lvl_ip_block_source - Level IP BLOCK_SOURCE option + * + * The value is a map with three attributes: multiaddr, interface and + * sourceaddr. + * The attribute 'multiaddr' is always a 4-tuple (IPv4 address). + * The attribute 'interface' is always a 4-tuple (IPv4 address). + * The attribute 'sourceaddr' is always a 4-tuple (IPv4 address). + * (IPv4 address). + */ +#if defined(IP_BLOCK_SOURCE) +static +ERL_NIF_TERM nsetopt_lvl_ip_block_source(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) +{ + return nsetopt_lvl_ip_update_source(env, descP, eVal, IP_BLOCK_SOURCE); +} +#endif + + /* nsetopt_lvl_ip_drop_membership - Level IP DROP_MEMBERSHIP option * * The value is a map with two attributes: multiaddr and interface. @@ -4179,66 +4285,35 @@ ERL_NIF_TERM nsetopt_lvl_ip_drop_membership(ErlNifEnv* env, SocketDescriptor* descP, ERL_NIF_TERM eVal) { - return nsetopt_lvl_ip_update_membership(env, descP, eVal, IP_DROP_MEMBERSHIP); + return nsetopt_lvl_ip_update_membership(env, descP, eVal, + IP_DROP_MEMBERSHIP); } #endif -#if defined(IP_DROP_MEMBERSHIP) || defined(IP_ADD_MEMBERSHIP) +/* nsetopt_lvl_ip_drop_source_membership - Level IP DROP_SOURCE_MEMBERSHIP option + * + * The value is a map with three attributes: multiaddr, interface and + * sourceaddr. + * The attribute 'multiaddr' is always a 4-tuple (IPv4 address). + * The attribute 'interface' is always a 4-tuple (IPv4 address). + * The attribute 'sourceaddr' is always a 4-tuple (IPv4 address). + * (IPv4 address). + */ +#if defined(IP_DROP_SOURCE_MEMBERSHIP) static -ERL_NIF_TERM nsetopt_lvl_ip_update_membership(ErlNifEnv* env, - SocketDescriptor* descP, - ERL_NIF_TERM eVal, - int opt) +ERL_NIF_TERM nsetopt_lvl_ip_drop_source_membership(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) { - ERL_NIF_TERM result, eMultiAddr, eInterface; - struct ip_mreq mreq; - char* xres; - int res; - size_t sz; -#if defined(SOL_IP) - int level = SOL_IP; -#else - int level = IPPROTO_IP; -#endif - - // It must be a map - if (!IS_MAP(env, eVal)) - return enif_make_badarg(env); - - // It must have exactly two attributes - if (!enif_get_map_size(env, eVal, &sz) || (sz >= 2)) - return enif_make_badarg(env); - - if (!GET_MAP_VAL(env, eVal, atom_multiaddr, &eMultiAddr)) - return enif_make_badarg(env); - - if (!GET_MAP_VAL(env, eVal, atom_interface, &eInterface)) - return enif_make_badarg(env); - - if ((xres = esock_decode_ip4_address(env, - eMultiAddr, - &mreq.imr_multiaddr)) != NULL) - return esock_make_error_str(env, xres); - - if ((xres = esock_decode_ip4_address(env, - eInterface, - &mreq.imr_interface)) != NULL) - return esock_make_error_str(env, xres); - - res = socket_setopt(descP->sock, level, opt, &mreq, sizeof(mreq)); - - if (res != 0) - result = esock_make_error_errno(env, sock_errno()); - else - result = esock_atom_ok; - - return result; + return nsetopt_lvl_ip_update_source(env, descP, eVal, + IP_DROP_SOURCE_MEMBERSHIP); } #endif + /* nsetopt_lvl_ip_mtu_discover - Level IP MTU_DISCOVER option * * The value is an atom of the type ip_pmtudisc(). @@ -4505,6 +4580,145 @@ ERL_NIF_TERM nsetopt_lvl_ip_ttl(ErlNifEnv* env, +/* nsetopt_lvl_ip_unblock_source - Level IP UNBLOCK_SOURCE option + * + * The value is a map with three attributes: multiaddr, interface and + * sourceaddr. + * The attribute 'multiaddr' is always a 4-tuple (IPv4 address). + * The attribute 'interface' is always a 4-tuple (IPv4 address). + * The attribute 'sourceaddr' is always a 4-tuple (IPv4 address). + * (IPv4 address). + */ +#if defined(IP_UNBLOCK_SOURCE) +static +ERL_NIF_TERM nsetopt_lvl_ip_unblock_source(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) +{ + return nsetopt_lvl_ip_update_source(env, descP, eVal, IP_UNBLOCK_SOURCE); +} +#endif + + + +#if defined(IP_ADD_MEMBERSHIP) || defined(IP_DROP_MEMBERSHIP) +static +ERL_NIF_TERM nsetopt_lvl_ip_update_membership(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal, + int opt) +{ + ERL_NIF_TERM result, eMultiAddr, eInterface; + struct ip_mreq mreq; + char* xres; + int res; + size_t sz; +#if defined(SOL_IP) + int level = SOL_IP; +#else + int level = IPPROTO_IP; +#endif + + // It must be a map + if (!IS_MAP(env, eVal)) + return enif_make_badarg(env); + + // It must have atleast two attributes + if (!enif_get_map_size(env, eVal, &sz) || (sz >= 2)) + return enif_make_badarg(env); + + if (!GET_MAP_VAL(env, eVal, atom_multiaddr, &eMultiAddr)) + return enif_make_badarg(env); + + if (!GET_MAP_VAL(env, eVal, atom_interface, &eInterface)) + return enif_make_badarg(env); + + if ((xres = esock_decode_ip4_address(env, + eMultiAddr, + &mreq.imr_multiaddr)) != NULL) + return esock_make_error_str(env, xres); + + if ((xres = esock_decode_ip4_address(env, + eInterface, + &mreq.imr_interface)) != NULL) + return esock_make_error_str(env, xres); + + res = socket_setopt(descP->sock, level, opt, &mreq, sizeof(mreq)); + + if (res != 0) + result = esock_make_error_errno(env, sock_errno()); + else + result = esock_atom_ok; + + return result; +} +#endif + + +#if defined(IP_ADD_SOURCE_MEMBERSHIP) || defined(IP_DROP_SOURCE_MEMBERSHIP) || defined(IP_BLOCK_SOURCE) || defined(IP_UNBLOCK_SOURCE) +static +ERL_NIF_TERM nsetopt_lvl_ip_update_source(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal, + int opt) +{ + ERL_NIF_TERM result, eMultiAddr, eInterface, eSourceAddr; + struct ip_mreq_source mreq; + char* xres; + int res; + size_t sz; +#if defined(SOL_IP) + int level = SOL_IP; +#else + int level = IPPROTO_IP; +#endif + + // It must be a map + if (!IS_MAP(env, eVal)) + return enif_make_badarg(env); + + // It must have atleast three attributes + if (!enif_get_map_size(env, eVal, &sz) || (sz >= 3)) + return enif_make_badarg(env); + + if (!GET_MAP_VAL(env, eVal, atom_multiaddr, &eMultiAddr)) + return enif_make_badarg(env); + + if (!GET_MAP_VAL(env, eVal, atom_interface, &eInterface)) + return enif_make_badarg(env); + + if (!GET_MAP_VAL(env, eVal, atom_sourceaddr, &eSourceAddr)) + return enif_make_badarg(env); + + if ((xres = esock_decode_ip4_address(env, + eMultiAddr, + &mreq.imr_multiaddr)) != NULL) + return esock_make_error_str(env, xres); + + if ((xres = esock_decode_ip4_address(env, + eInterface, + &mreq.imr_interface)) != NULL) + return esock_make_error_str(env, xres); + + if ((xres = esock_decode_ip4_address(env, + eSourceAddr, + &mreq.imr_sourceaddr)) != NULL) + return esock_make_error_str(env, xres); + + res = socket_setopt(descP->sock, level, opt, &mreq, sizeof(mreq)); + + if (res != 0) + result = esock_make_error_errno(env, sock_errno()); + else + result = esock_atom_ok; + + return result; +} +#endif + + +/* *** Handling set of socket options for level = ipv6 *** */ + /* nsetopt_lvl_ipv6 - Level *IPv6* option(s) */ #if defined(SOL_IPV6) @@ -8079,6 +8293,7 @@ int on_load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info) atom_num_tstreams = MKA(env, str_num_tstreams); atom_probe = MKA(env, str_probe); atom_select = MKA(env, str_select); + atom_sourceaddr = MKA(env, str_sourceaddr); atom_timeout = MKA(env, str_timeout); atom_true = MKA(env, str_true); atom_want = MKA(env, str_want); diff --git a/erts/preloaded/ebin/socket.beam b/erts/preloaded/ebin/socket.beam index 2f81e4cc29..4502ff6530 100644 Binary files a/erts/preloaded/ebin/socket.beam and b/erts/preloaded/ebin/socket.beam differ diff --git a/erts/preloaded/src/socket.erl b/erts/preloaded/src/socket.erl index d54c8d5f33..7efe91146b 100644 --- a/erts/preloaded/src/socket.erl +++ b/erts/preloaded/src/socket.erl @@ -90,6 +90,7 @@ ip_tos_flag/0, ip_mreq/0, + ip_mreq_source/0, ip_pmtudisc/0, @@ -149,6 +150,13 @@ %% -type ip_mreq() :: #{multiaddr := ip4_address(), interface := any | ip4_address()}. +%% -type ip_mreqn() :: #{multiaddr := ip4_address(), +%% address := any | ip4_address(), +%% ifindex := integer()}. + +-type ip_mreq_source() :: #{multiaddr := ip4_address(), + interface := ip4_address(), + sourceaddr := ip4_address()}. -type ip_pmtudisc() :: want | dont | do | probe. @@ -516,11 +524,11 @@ -define(SOCKET_OPT_SOCK_TYPE, 32). -define(SOCKET_OPT_IP_ADD_MEMBERSHIP, 1). -%% -define(SOCKET_OPT_IP_ADD_SOURCE_MEMBERSHIP, 2). -%% -define(SOCKET_OPT_IP_BLOCK_SOURCE, 3). +-define(SOCKET_OPT_IP_ADD_SOURCE_MEMBERSHIP, 2). +-define(SOCKET_OPT_IP_BLOCK_SOURCE, 3). %% -define(SOCKET_OPT_IP_DONTFRAG, 4). % Windows? MTU_DISCOVER... -define(SOCKET_OPT_IP_DROP_MEMBERSHIP, 5). -%% -define(SOCKET_OPT_IP_DROP_SOURCE_MEMBERSHIP, 6). +-define(SOCKET_OPT_IP_DROP_SOURCE_MEMBERSHIP, 6). %% -define(SOCKET_OPT_IP_FREEBIND, 7). %% -define(SOCKET_OPT_IP_HDRINCL, 8). %% -define(SOCKET_OPT_IP_MINTTL, 9). @@ -1977,14 +1985,35 @@ enc_setopt_value(socket = L, Opt, V, _D, _T, _P) -> enc_setopt_value(ip, add_membership, #{multiaddr := M, interface := IF} = V, _D, _T, _P) - when (is_tuple(M) andalso (size(M) =:= 4)) andalso + when (is_tuple(M) andalso (size(M) =:= 4)) andalso ((IF =:= any) orelse (is_tuple(IF) andalso (size(IF) =:= 4))) -> V; -enc_setopt_value(ip, drop_membership, #{multiaddr := M, +enc_setopt_value(ip, add_source_membership, #{multiaddr := MA, + interface := IF, + sourceaddr := SA} = V, _D, _T, _P) + when (is_tuple(MA) andalso (size(MA) =:= 4)) andalso + (is_tuple(IF) andalso (size(IF) =:= 4)) andalso + (is_tuple(SA) andalso (size(SA) =:= 4)) -> + V; +enc_setopt_value(ip, block_source, #{multiaddr := MA, + interface := IF, + sourceaddr := SA} = V, _D, _T, _P) + when (is_tuple(MA) andalso (size(MA) =:= 4)) andalso + (is_tuple(IF) andalso (size(IF) =:= 4)) andalso + (is_tuple(SA) andalso (size(SA) =:= 4)) -> + V; +enc_setopt_value(ip, drop_membership, #{multiaddr := MA, interface := IF} = V, _D, _T, _P) - when (is_tuple(M) andalso (size(M) =:= 4)) andalso + when (is_tuple(MA) andalso (size(MA) =:= 4)) andalso ((IF =:= any) orelse (is_tuple(IF) andalso (size(IF) =:= 4))) -> V; +enc_setopt_value(ip, drop_source_membership, #{multiaddr := MA, + interface := IF, + sourceaddr := SA} = V, _D, _T, _P) + when (is_tuple(MA) andalso (size(MA) =:= 4)) andalso + (is_tuple(IF) andalso (size(IF) =:= 4)) andalso + (is_tuple(SA) andalso (size(SA) =:= 4)) -> + V; enc_setopt_value(ip, mtu_discover, V, _D, _T, _P) when (V =:= want) orelse (V =:= dont) orelse @@ -2017,15 +2046,22 @@ enc_setopt_value(ip, router_alert, V, _D, _T, _P) when is_integer(V) -> V; enc_setopt_value(ip, tos, V, _D, _T, _P) - when (V =:= lowdelay) orelse - (V =:= throughput) orelse + when (V =:= lowdelay) orelse + (V =:= throughput) orelse (V =:= reliability) orelse - (V =:= mincost) orelse + (V =:= mincost) orelse is_integer(V) -> V; enc_setopt_value(ip, ttl, V, _D, _T, _P) when is_integer(V) -> V; +enc_setopt_value(ip, unblock_source, #{multiaddr := MA, + interface := IF, + sourceaddr := SA} = V, _D, _T, _P) + when (is_tuple(MA) andalso (size(MA) =:= 4)) andalso + (is_tuple(IF) andalso (size(IF) =:= 4)) andalso + (is_tuple(SA) andalso (size(SA) =:= 4)) -> + V; enc_setopt_value(ip = L, Opt, V, _D, _T, _P) -> not_supported({L, Opt, V}); @@ -2272,18 +2308,18 @@ enc_sockopt_key(socket = L, UnknownOpt, _Dir, _D, _T, _P) -> %% +++ IP socket options +++ enc_sockopt_key(ip = _L, add_membership = _Opt, set = _Dir, _D, _T, _P) -> ?SOCKET_OPT_IP_ADD_MEMBERSHIP; -enc_sockopt_key(ip = L, add_source_membership = Opt, set = _Dir, _D, _T, _P) -> - not_supported({L, Opt}); -enc_sockopt_key(ip = L, block_source = Opt, set = _Dir, _D, _T, _P) -> - not_supported({L, Opt}); +enc_sockopt_key(ip = _L, add_source_membership = _Opt, set = _Dir, _D, _T, _P) -> + ?SOCKET_OPT_IP_ADD_SOURCE_MEMBERSHIP; +enc_sockopt_key(ip = _L, block_source = _Opt, set = _Dir, _D, _T, _P) -> + ?SOCKET_OPT_IP_BLOCK_SOURCE; %% FreeBSD only? %% Only respected on udp and raw ip (unless the hdrincl option has been set). enc_sockopt_key(ip = L, dontfrag = Opt, _Dir, _D, _T, _P) -> not_supported({L, Opt}); enc_sockopt_key(ip = _L, drop_membership = _Opt, set = _Dir, _D, _T, _P) -> ?SOCKET_OPT_IP_DROP_MEMBERSHIP; -enc_sockopt_key(ip = L, drop_source_membership = Opt, set = _Dir, _D, _T, _P) -> - not_supported({L, Opt}); +enc_sockopt_key(ip = _L, drop_source_membership = _Opt, set = _Dir, _D, _T, _P) -> + ?SOCKET_OPT_IP_DROP_SOURCE_MEMBERSHIP; %% Linux only? enc_sockopt_key(ip = L, free_bind = Opt, _Dir, _D, _T, _P) -> not_supported({L, Opt}); @@ -2341,8 +2377,8 @@ enc_sockopt_key(ip = L, transparent = Opt, _Dir, _D, _T, _P) -> not_supported({L, Opt}); enc_sockopt_key(ip, ttl = _Opt, _Dir, _D, _T, _P) -> ?SOCKET_OPT_IP_TTL; -enc_sockopt_key(ip = L, unblock_source = Opt, set = _Dir, _D, _T, _P) -> - not_supported({L, Opt}); +enc_sockopt_key(ip = _L, unblock_source = _Opt, set = _Dir, _D, _T, _P) -> + ?SOCKET_OPT_IP_UNBLOCK_SOURCE; enc_sockopt_key(ip = L, UnknownOpt, _Dir, _D, _T, _P) -> unknown({L, UnknownOpt}); -- cgit v1.2.3 From 31ed3b1d9cff6171432459bf2de2b761e987ac97 Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Thu, 12 Jul 2018 17:00:34 +0200 Subject: [socket-nif] Add support for socket (level ip) open recvif Added support for the IP option RECVIF. OTP-14831 --- erts/emulator/nifs/common/socket_nif.c | 50 +++++++++++++++++++++++++++++++++ erts/preloaded/ebin/socket.beam | Bin 45672 -> 45720 bytes erts/preloaded/src/socket.erl | 10 +++++-- 3 files changed, 57 insertions(+), 3 deletions(-) diff --git a/erts/emulator/nifs/common/socket_nif.c b/erts/emulator/nifs/common/socket_nif.c index 109d1a2d5c..a851dc94b4 100644 --- a/erts/emulator/nifs/common/socket_nif.c +++ b/erts/emulator/nifs/common/socket_nif.c @@ -374,6 +374,7 @@ typedef union { #define SOCKET_OPT_IP_MULTICAST_LOOP 15 #define SOCKET_OPT_IP_MULTICAST_TTL 16 #define SOCKET_OPT_IP_NODEFRAG 17 +#define SOCKET_OPT_IP_RECVIF 21 #define SOCKET_OPT_IP_RECVTOS 25 #define SOCKET_OPT_IP_RECVTTL 26 #define SOCKET_OPT_IP_ROUTER_ALERT 28 @@ -4164,6 +4165,12 @@ ERL_NIF_TERM nsetopt_lvl_ip(ErlNifEnv* env, break; #endif +#if defined(IP_RECVIF) + case SOCKET_OPT_IP_RECVIF: + result = nsetopt_lvl_ip_recvif(env, descP, eVal); + break; +#endif + #if defined(IP_RECVTOS) case SOCKET_OPT_IP_RECVTOS: result = nsetopt_lvl_ip_recvtos(env, descP, eVal); @@ -4470,6 +4477,25 @@ ERL_NIF_TERM nsetopt_lvl_ip_nodefrag(ErlNifEnv* env, #endif +/* nsetopt_lvl_ip_recvif - Level IP RECVIFxs option + */ +#if defined(IP_RECVIF) +static +ERL_NIF_TERM nsetopt_lvl_ip_recvif(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) +{ +#if defined(SOL_IP) + int level = SOL_IP; +#else + int level = IPPROTO_IP; +#endif + + return nsetopt_bool_opt(env, descP, level, IP_RECVIF, eVal); +} +#endif + + /* nsetopt_lvl_ip_recvtos - Level IP RECVTOS option */ #if defined(IP_RECVTOS) @@ -5899,6 +5925,12 @@ ERL_NIF_TERM ngetopt_lvl_ip(ErlNifEnv* env, break; #endif +#if defined(IP_RECVIF) + case SOCKET_OPT_IP_RECVIF: + result = ngetopt_lvl_ip_recvif(env, descP); + break; +#endif + #if defined(IP_RECVTOS) case SOCKET_OPT_IP_RECVTOS: result = ngetopt_lvl_ip_recvtos(env, descP); @@ -6117,6 +6149,24 @@ ERL_NIF_TERM ngetopt_lvl_ip_recvtos(ErlNifEnv* env, #endif +/* ngetopt_lvl_ip_recvif - Level IP RECVIF option + */ +#if defined(IP_RECVIF) +static +ERL_NIF_TERM ngetopt_lvl_ip_recvif(ErlNifEnv* env, + SocketDescriptor* descP) +{ +#if defined(SOL_IP) + int level = SOL_IP; +#else + int level = IPPROTO_IP; +#endif + + return ngetopt_bool_opt(env, descP, level, IP_RECVIF); +} +#endif + + /* ngetopt_lvl_ip_recvttl - Level IP RECVTTL option */ #if defined(IP_RECVTTL) diff --git a/erts/preloaded/ebin/socket.beam b/erts/preloaded/ebin/socket.beam index 4502ff6530..57a32cfbad 100644 Binary files a/erts/preloaded/ebin/socket.beam and b/erts/preloaded/ebin/socket.beam differ diff --git a/erts/preloaded/src/socket.erl b/erts/preloaded/src/socket.erl index 7efe91146b..f70c45b42e 100644 --- a/erts/preloaded/src/socket.erl +++ b/erts/preloaded/src/socket.erl @@ -543,7 +543,7 @@ %% -define(SOCKET_OPT_IP_OPTIONS, 18). %% -define(SOCKET_OPT_IP_PKTINFO, 19). %% -define(SOCKET_OPT_IP_RECVERR, 20). -%% -define(SOCKET_OPT_IP_RECVIF, 21). +-define(SOCKET_OPT_IP_RECVIF, 21). %% -define(SOCKET_OPT_IP_RECVDSTADDR, 22). %% -define(SOCKET_OPT_IP_RECVOPTS, 23). %% -define(SOCKET_OPT_IP_RECVORIGDSTADDR, 24). @@ -2036,6 +2036,9 @@ enc_setopt_value(ip, multicast_ttl, V, _D, _T, _P) enc_setopt_value(ip, nodefrag, V, _D, _T, _P) when is_boolean(V) -> V; +enc_setopt_value(ip, recvif, V, _D, _T, _P) + when is_boolean(V) -> + V; enc_setopt_value(ip, recvtos, V, _D, _T, _P) when is_boolean(V) -> V; @@ -2352,8 +2355,9 @@ enc_sockopt_key(ip = L, pktinfo = Opt, _Dir, _D, _T, _P) -> %% via calling the recvmsg with the MSG_ERRQUEUE flag set, enc_sockopt_key(ip = L, recverr = Opt, _Dir, _D, _T, _P) -> not_supported({L, Opt}); -enc_sockopt_key(ip = L, recvif = Opt, _Dir, _D, dgram = _T, _P) -> - not_supported({L, Opt}); +enc_sockopt_key(ip = _L, recvif = _Opt, _Dir, _D, T, _P) + when (T =:= dgram) orelse (T =:= raw) -> + ?SOCKET_OPT_IP_RECVIF; enc_sockopt_key(ip = L, recvdstaddr = Opt, _Dir, _D, _T, _P) -> not_supported({L, Opt}); enc_sockopt_key(ip = L, recvopts = Opt, _Dir, _D, T, _P) when (T =/= stream) -> -- cgit v1.2.3 From 2151895b4eb4cb3172ba7597477fd83eb85b444a Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Thu, 12 Jul 2018 17:30:59 +0200 Subject: [socket-nif] Add support for socket (level ip) option minttl Added support for the IP option MINTTL. OTP-14831 --- erts/emulator/nifs/common/socket_nif.c | 60 +++++++++++++++++++++++++++++++++ erts/preloaded/ebin/socket.beam | Bin 45720 -> 45756 bytes erts/preloaded/src/socket.erl | 8 +++-- 3 files changed, 65 insertions(+), 3 deletions(-) diff --git a/erts/emulator/nifs/common/socket_nif.c b/erts/emulator/nifs/common/socket_nif.c index a851dc94b4..37bb307e2b 100644 --- a/erts/emulator/nifs/common/socket_nif.c +++ b/erts/emulator/nifs/common/socket_nif.c @@ -367,6 +367,7 @@ typedef union { #define SOCKET_OPT_IP_BLOCK_SOURCE 3 #define SOCKET_OPT_IP_DROP_MEMBERSHIP 5 #define SOCKET_OPT_IP_DROP_SOURCE_MEMBERSHIP 6 +#define SOCKET_OPT_IP_MINTTL 9 #define SOCKET_OPT_IP_MTU 11 #define SOCKET_OPT_IP_MTU_DISCOVER 12 #define SOCKET_OPT_IP_MULTICAST_ALL 13 @@ -887,6 +888,11 @@ static ERL_NIF_TERM nsetopt_lvl_ip_drop_source_membership(ErlNifEnv* env, SocketDescriptor* descP, ERL_NIF_TERM eVal); #endif +#if defined(IP_MINTTL) +static ERL_NIF_TERM nsetopt_lvl_ip_minttl(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); +#endif #if defined(IP_MTU_DISCOVER) static ERL_NIF_TERM nsetopt_lvl_ip_mtu_discover(ErlNifEnv* env, SocketDescriptor* descP, @@ -1113,6 +1119,10 @@ static ERL_NIF_TERM ngetopt_lvl_sock_type(ErlNifEnv* env, static ERL_NIF_TERM ngetopt_lvl_ip(ErlNifEnv* env, SocketDescriptor* descP, int eOpt); +#if defined(IP_MINTTL) +static ERL_NIF_TERM ngetopt_lvl_ip_minttl(ErlNifEnv* env, + SocketDescriptor* descP); +#endif #if defined(IP_MTU) static ERL_NIF_TERM ngetopt_lvl_ip_mtu(ErlNifEnv* env, SocketDescriptor* descP); @@ -4129,6 +4139,12 @@ ERL_NIF_TERM nsetopt_lvl_ip(ErlNifEnv* env, break; #endif +#if defined(IP_MINTTL) + case SOCKET_OPT_IP_MINTTL: + result = nsetopt_lvl_ip_minttl(env, descP, eVal); + break; +#endif + #if defined(IP_MTU_DISCOVER) case SOCKET_OPT_IP_MTU_DISCOVER: result = nsetopt_lvl_ip_mtu_discover(env, descP, eVal); @@ -4321,6 +4337,26 @@ ERL_NIF_TERM nsetopt_lvl_ip_drop_source_membership(ErlNifEnv* env, +/* nsetopt_lvl_ip_minttl - Level IP MINTTL option + */ +#if defined(IP_MINTTL) +static +ERL_NIF_TERM nsetopt_lvl_ip_minttl(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) +{ +#if defined(SOL_IP) + int level = SOL_IP; +#else + int level = IPPROTO_IP; +#endif + + return nsetopt_int_opt(env, descP, level, IP_MINTTL, eVal); +} +#endif + + + /* nsetopt_lvl_ip_mtu_discover - Level IP MTU_DISCOVER option * * The value is an atom of the type ip_pmtudisc(). @@ -5883,6 +5919,12 @@ ERL_NIF_TERM ngetopt_lvl_ip(ErlNifEnv* env, ERL_NIF_TERM result; switch (eOpt) { +#if defined(IP_MINTTL) + case SOCKET_OPT_IP_MINTTL: + result = ngetopt_lvl_ip_minttl(env, descP); + break; +#endif + #if defined(IP_MTU) case SOCKET_OPT_IP_MTU: result = ngetopt_lvl_ip_mtu(env, descP); @@ -5970,6 +6012,24 @@ ERL_NIF_TERM ngetopt_lvl_ip(ErlNifEnv* env, } +/* ngetopt_lvl_ip_minttl - Level IP MINTTL option + */ +#if defined(IP_MINTTL) +static +ERL_NIF_TERM ngetopt_lvl_ip_minttl(ErlNifEnv* env, + SocketDescriptor* descP) +{ +#if defined(SOL_IP) + int level = SOL_IP; +#else + int level = IPPROTO_IP; +#endif + + return ngetopt_int_opt(env, descP, level, IP_MINTTL); +} +#endif + + /* ngetopt_lvl_ip_mtu - Level IP MTU option */ #if defined(IP_MTU) diff --git a/erts/preloaded/ebin/socket.beam b/erts/preloaded/ebin/socket.beam index 57a32cfbad..fd19efd516 100644 Binary files a/erts/preloaded/ebin/socket.beam and b/erts/preloaded/ebin/socket.beam differ diff --git a/erts/preloaded/src/socket.erl b/erts/preloaded/src/socket.erl index f70c45b42e..c8238e9a1a 100644 --- a/erts/preloaded/src/socket.erl +++ b/erts/preloaded/src/socket.erl @@ -531,7 +531,7 @@ -define(SOCKET_OPT_IP_DROP_SOURCE_MEMBERSHIP, 6). %% -define(SOCKET_OPT_IP_FREEBIND, 7). %% -define(SOCKET_OPT_IP_HDRINCL, 8). -%% -define(SOCKET_OPT_IP_MINTTL, 9). +-define(SOCKET_OPT_IP_MINTTL, 9). %% -define(SOCKET_OPT_IP_MSFILTER, 10). -define(SOCKET_OPT_IP_MTU, 11). -define(SOCKET_OPT_IP_MTU_DISCOVER, 12). @@ -2014,6 +2014,8 @@ enc_setopt_value(ip, drop_source_membership, #{multiaddr := MA, (is_tuple(IF) andalso (size(IF) =:= 4)) andalso (is_tuple(SA) andalso (size(SA) =:= 4)) -> V; +enc_setopt_value(ip, minttl, V, _D, _T, _P) when is_integer(V) -> + V; enc_setopt_value(ip, mtu_discover, V, _D, _T, _P) when (V =:= want) orelse (V =:= dont) orelse @@ -2329,8 +2331,8 @@ enc_sockopt_key(ip = L, free_bind = Opt, _Dir, _D, _T, _P) -> enc_sockopt_key(ip = L, hdrincl = Opt, _Dir, _D, raw = _T, _P) -> not_supported({L, Opt}); %% FreeBSD only? -enc_sockopt_key(ip = L, minttl = Opt, _Dir, _D, raw = _T, _P) -> - not_supported({L, Opt}); +enc_sockopt_key(ip = _L, minttl = _Opt, _Dir, _D, raw = _T, _P) -> + ?SOCKET_OPT_IP_MINTTL; enc_sockopt_key(ip = L, msfilter = Opt, _Dir, _D, _T, _P) -> not_supported({L, Opt}); enc_sockopt_key(ip = _L, mtu = _Opt, get = _Dir, _D, _T, _P) -> -- cgit v1.2.3 From 3a2c48b2740f08d3582028fd2f8d626009aa911e Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Thu, 12 Jul 2018 21:17:14 +0200 Subject: [socket-nif] Add support for socket (level ipv6) option(s) [add|drop]_membership Added support for the IPv6 options ADD_MEMBERSHIP, DROP_MEMBERSHIP OTP-14831 --- erts/emulator/nifs/common/socket_nif.c | 108 ++++++++++++++++++++++++++++++++- erts/preloaded/ebin/socket.beam | Bin 45756 -> 46248 bytes erts/preloaded/src/socket.erl | 28 +++++++-- 3 files changed, 130 insertions(+), 6 deletions(-) diff --git a/erts/emulator/nifs/common/socket_nif.c b/erts/emulator/nifs/common/socket_nif.c index 37bb307e2b..1f9234016c 100644 --- a/erts/emulator/nifs/common/socket_nif.c +++ b/erts/emulator/nifs/common/socket_nif.c @@ -383,7 +383,9 @@ typedef union { #define SOCKET_OPT_IP_TTL 32 #define SOCKET_OPT_IP_UNBLOCK_SOURCE 33 -#define SOCKET_OPT_IPV6_HOPLIMIT 12 +#define SOCKET_OPT_IPV6_ADD_MEMBERSHIP 2 +#define SOCKET_OPT_IPV6_DROP_MEMBERSHIP 6 +#define SOCKET_OPT_IPV6_HOPLIMIT 12 #define SOCKET_OPT_TCP_CONGESTION 1 #define SOCKET_OPT_TCP_CORK 2 @@ -976,11 +978,29 @@ static ERL_NIF_TERM nsetopt_lvl_ipv6(ErlNifEnv* env, SocketDescriptor* descP, int eOpt, ERL_NIF_TERM eVal); +#if defined(IPV6_ADD_MEMBERSHIP) +static ERL_NIF_TERM nsetopt_lvl_ipv6_add_membership(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); +#endif +#if defined(IPV6_DROP_MEMBERSHIP) +static ERL_NIF_TERM nsetopt_lvl_ipv6_drop_membership(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); +#endif #if defined(IPV6_HOPLIMIT) static ERL_NIF_TERM nsetopt_lvl_ipv6_hoplimit(ErlNifEnv* env, SocketDescriptor* descP, ERL_NIF_TERM eVal); #endif + +#if defined(IPV6_ADD_MEMBERSHIP) || defined(IPV6_DROP_MEMBERSHIP) +static ERL_NIF_TERM nsetopt_lvl_ipv6_update_membership(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal, + int opt); +#endif + #endif // defined(SOL_IPV6) static ERL_NIF_TERM nsetopt_lvl_tcp(ErlNifEnv* env, SocketDescriptor* descP, @@ -4779,6 +4799,7 @@ ERL_NIF_TERM nsetopt_lvl_ip_update_source(ErlNifEnv* env, #endif + /* *** Handling set of socket options for level = ipv6 *** */ /* nsetopt_lvl_ipv6 - Level *IPv6* option(s) @@ -4793,6 +4814,18 @@ ERL_NIF_TERM nsetopt_lvl_ipv6(ErlNifEnv* env, ERL_NIF_TERM result; switch (eOpt) { +#if defined(IPV6_ADD_MEMBERSHIP) + case SOCKET_OPT_IPV6_ADD_MEMBERSHIP: + result = nsetopt_lvl_ipv6_add_membership(env, descP, eVal); + break; +#endif + +#if defined(IPV6_DROP_MEMBERSHIP) + case SOCKET_OPT_IPV6_DROP_MEMBERSHIP: + result = nsetopt_lvl_ipv6_drop_membership(env, descP, eVal); + break; +#endif + #if defined(IPV6_HOPLIMIT) case SOCKET_OPT_IPV6_HOPLIMIT: result = nsetopt_lvl_ipv6_hoplimit(env, descP, eVal); @@ -4808,6 +4841,30 @@ ERL_NIF_TERM nsetopt_lvl_ipv6(ErlNifEnv* env, } +#if defined(IPV6_ADD_MEMBERSHIP) +static +ERL_NIF_TERM nsetopt_lvl_ipv6_add_membership(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) +{ + return nsetopt_lvl_ipv6_update_membership(env, descP, eVal, + IPV6_ADD_MEMBERSHIP); +} +#endif + + +#if defined(IPV6_DROP_MEMBERSHIP) +static +ERL_NIF_TERM nsetopt_lvl_ipv6_drop_membership(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) +{ + return nsetopt_lvl_ipv6_update_membership(env, descP, eVal, + IPV6_DROP_MEMBERSHIP); +} +#endif + + #if defined(IPV6_HOPLIMIT) static ERL_NIF_TERM nsetopt_lvl_ipv6_hoplimit(ErlNifEnv* env, @@ -4819,6 +4876,55 @@ ERL_NIF_TERM nsetopt_lvl_ipv6_hoplimit(ErlNifEnv* env, #endif +#if defined(IPV6_ADD_MEMBERSHIP) || defined(IPV6_DROP_MEMBERSHIP) +static +ERL_NIF_TERM nsetopt_lvl_ipv6_update_membership(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal, + int opt) +{ + ERL_NIF_TERM result, eMultiAddr, eInterface; + struct ipv6_mreq mreq; + char* xres; + int res; + size_t sz; + int level = IPPROTO_IPV6; + + // It must be a map + if (!IS_MAP(env, eVal)) + return enif_make_badarg(env); + + // It must have atleast two attributes + if (!enif_get_map_size(env, eVal, &sz) || (sz >= 2)) + return enif_make_badarg(env); + + if (!GET_MAP_VAL(env, eVal, atom_multiaddr, &eMultiAddr)) + return enif_make_badarg(env); + + if (!GET_MAP_VAL(env, eVal, atom_interface, &eInterface)) + return enif_make_badarg(env); + + if ((xres = esock_decode_ip6_address(env, + eMultiAddr, + &mreq.ipv6mr_multiaddr)) != NULL) + return esock_make_error_str(env, xres); + + if (!GET_UINT(env, eInterface, &mreq.ipv6mr_interface)) + return esock_make_error(env, esock_atom_einval); + + res = socket_setopt(descP->sock, level, opt, &mreq, sizeof(mreq)); + + if (res != 0) + result = esock_make_error_errno(env, sock_errno()); + else + result = esock_atom_ok; + + return result; +} +#endif + + + #endif // defined(SOL_IPV6) diff --git a/erts/preloaded/ebin/socket.beam b/erts/preloaded/ebin/socket.beam index fd19efd516..03a7012131 100644 Binary files a/erts/preloaded/ebin/socket.beam and b/erts/preloaded/ebin/socket.beam differ diff --git a/erts/preloaded/src/socket.erl b/erts/preloaded/src/socket.erl index c8238e9a1a..8d377d808e 100644 --- a/erts/preloaded/src/socket.erl +++ b/erts/preloaded/src/socket.erl @@ -92,6 +92,7 @@ ip_mreq/0, ip_mreq_source/0, ip_pmtudisc/0, + ipv6_mreq/0, msg_hdr/0 @@ -160,6 +161,9 @@ -type ip_pmtudisc() :: want | dont | do | probe. +-type ipv6_mreq() :: #{multiaddr := ip6_address(), + interface := non_neg_integer()}. + -type sockaddr_un() :: #{family := local, path := binary() | string()}. -type sockaddr_in4() :: #{family := inet, @@ -526,7 +530,7 @@ -define(SOCKET_OPT_IP_ADD_MEMBERSHIP, 1). -define(SOCKET_OPT_IP_ADD_SOURCE_MEMBERSHIP, 2). -define(SOCKET_OPT_IP_BLOCK_SOURCE, 3). -%% -define(SOCKET_OPT_IP_DONTFRAG, 4). % Windows? MTU_DISCOVER... +%% -define(SOCKET_OPT_IP_DONTFRAG, 4). -define(SOCKET_OPT_IP_DROP_MEMBERSHIP, 5). -define(SOCKET_OPT_IP_DROP_SOURCE_MEMBERSHIP, 6). %% -define(SOCKET_OPT_IP_FREEBIND, 7). @@ -558,11 +562,11 @@ -define(SOCKET_OPT_IP_UNBLOCK_SOURCE, 33). %% -define(SOCKET_OPT_IPV6_ADDFORM, 1). -%% -define(SOCKET_OPT_IPV6_ADD_MEMBERSHIP, 2). +-define(SOCKET_OPT_IPV6_ADD_MEMBERSHIP, 2). %% -define(SOCKET_OPT_IPV6_AUTHHDR, 3). %% -define(SOCKET_OPT_IPV6_AUTH_LEVEL, 4). %% -define(SOCKET_OPT_IPV6_CHECKSUM, 5). -%% -define(SOCKET_OPT_IPV6_DROP_MEMBERSHIP, 6). +-define(SOCKET_OPT_IPV6_DROP_MEMBERSHIP, 6). %% -define(SOCKET_OPT_IPV6_DSTOPTS, 7). %% -define(SOCKET_OPT_IPV6_ESP_TRANS_LEVEL, 8). %% -define(SOCKET_OPT_IPV6_ESP_NETWORK_LEVEL, 9). @@ -1983,9 +1987,9 @@ enc_setopt_value(socket, sndbuf, V, _D, _T, _P) when is_integer(V) -> enc_setopt_value(socket = L, Opt, V, _D, _T, _P) -> not_supported({L, Opt, V}); -enc_setopt_value(ip, add_membership, #{multiaddr := M, +enc_setopt_value(ip, add_membership, #{multiaddr := MA, interface := IF} = V, _D, _T, _P) - when (is_tuple(M) andalso (size(M) =:= 4)) andalso + when (is_tuple(MA) andalso (size(MA) =:= 4)) andalso ((IF =:= any) orelse (is_tuple(IF) andalso (size(IF) =:= 4))) -> V; enc_setopt_value(ip, add_source_membership, #{multiaddr := MA, @@ -2070,6 +2074,16 @@ enc_setopt_value(ip, unblock_source, #{multiaddr := MA, enc_setopt_value(ip = L, Opt, V, _D, _T, _P) -> not_supported({L, Opt, V}); +enc_setopt_value(ipv6, add_membership, #{multiaddr := MA, + interface := IF} = V, _D, _T, _P) + when ((is_tuple(MA) andalso (size(MA) =:= 8)) andalso + (is_integer(IF) andalso (IF >= 0))) -> + V; +enc_setopt_value(ipv6, drop_membership, #{multiaddr := MA, + interface := IF} = V, _D, _T, _P) + when ((is_tuple(MA) andalso (size(MA) =:= 8)) andalso + (is_integer(IF) andalso (IF >= 0))) -> + V; enc_setopt_value(ipv6, hoplimit, V, _D, T, _P) when is_boolean(V) andalso ((T =:= dgram) orelse (T =:= raw)) -> V; @@ -2389,6 +2403,10 @@ enc_sockopt_key(ip = L, UnknownOpt, _Dir, _D, _T, _P) -> unknown({L, UnknownOpt}); %% IPv6 socket options +enc_sockopt_key(ipv6, add_membership = _Opt, set = _Dir, _D, _T, _P) -> + ?SOCKET_OPT_IPV6_ADD_MEMBERSHIP; +enc_sockopt_key(ipv6, drop_membership = _Opt, set = _Dir, _D, _T, _P) -> + ?SOCKET_OPT_IPV6_DROP_MEMBERSHIP; enc_sockopt_key(ipv6, hoplimit = _Opt, _Dir, _D, T, _P) when (T =:= dgram) orelse (T =:= raw) -> ?SOCKET_OPT_IPV6_HOPLIMIT; -- cgit v1.2.3 From 416644989e26ac76038523511d81ebf9e0b8fc4f Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Fri, 13 Jul 2018 12:34:06 +0200 Subject: [net+socket-nif] Updated on_load stuff The net nif-module had debug on by default. Fixed this and also made it an option, just as for the socket nif-module. OTP-14831 --- erts/emulator/nifs/common/net_nif.c | 20 ++++++++++- erts/emulator/nifs/common/socket_nif.c | 60 +++++++++++++-------------------- erts/emulator/nifs/common/socket_util.c | 27 +++++++++++++++ erts/emulator/nifs/common/socket_util.h | 5 +++ 4 files changed, 75 insertions(+), 37 deletions(-) diff --git a/erts/emulator/nifs/common/net_nif.c b/erts/emulator/nifs/common/net_nif.c index 6829de509b..309ad05a36 100644 --- a/erts/emulator/nifs/common/net_nif.c +++ b/erts/emulator/nifs/common/net_nif.c @@ -209,6 +209,9 @@ # define SOCKLEN_T size_t #endif +/* Debug stuff... */ +#define NET_NIF_DEBUG_DEFAULT FALSE + #define NDBG( proto ) ESOCK_DBG_PRINTF( data.debug , proto ) @@ -331,6 +334,8 @@ static char* make_address_info(ErlNifEnv* env, ERL_NIF_TERM addr, ERL_NIF_TERM* ai); +static BOOLEAN_T extract_debug(ErlNifEnv* env, + ERL_NIF_TERM map); static int on_load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info); @@ -1535,6 +1540,19 @@ ErlNifFunc net_funcs[] = }; +static +BOOLEAN_T extract_debug(ErlNifEnv* env, + ERL_NIF_TERM map) +{ + /* + * We need to do this here since the "proper" atom has not been + * created when this function is called. + */ + ERL_NIF_TERM debug = MKA(env, "debug"); + + return esock_extract_bool_from_map(env, map, debug, NET_NIF_DEBUG_DEFAULT); +} + /* ======================================================================= * load_info - A map of misc info (e.g global debug) @@ -1544,7 +1562,7 @@ static int on_load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info) { // We should make it possible to use load_info to get default values - data.debug = TRUE; + data.debug = extract_debug(env, load_info); NDBG( ("NET", "on_load -> entry\r\n") ); diff --git a/erts/emulator/nifs/common/socket_nif.c b/erts/emulator/nifs/common/socket_nif.c index 1f9234016c..d973633606 100644 --- a/erts/emulator/nifs/common/socket_nif.c +++ b/erts/emulator/nifs/common/socket_nif.c @@ -1499,14 +1499,10 @@ static char* send_msg(ErlNifEnv* env, ERL_NIF_TERM msg, ErlNifPid* pid); -static BOOLEAN_T extract_debug_on_load(ErlNifEnv* env, - ERL_NIF_TERM map); -static BOOLEAN_T extract_iow_on_load(ErlNifEnv* env, - ERL_NIF_TERM map); -static BOOLEAN_T extract_bool(ErlNifEnv* env, - ERL_NIF_TERM map, - ERL_NIF_TERM ekey, - BOOLEAN_T def); +static BOOLEAN_T extract_debug(ErlNifEnv* env, + ERL_NIF_TERM map); +static BOOLEAN_T extract_iow(ErlNifEnv* env, + ERL_NIF_TERM map); static int on_load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info); @@ -8423,37 +8419,29 @@ ErlNifFunc socket_funcs[] = static -BOOLEAN_T extract_debug_on_load(ErlNifEnv* env, - ERL_NIF_TERM map) +BOOLEAN_T extract_debug(ErlNifEnv* env, + ERL_NIF_TERM map) { - return extract_bool(env, map, esock_atom_debug, SOCKET_NIF_DEBUG_DEFAULT); -} - -static -BOOLEAN_T extract_iow_on_load(ErlNifEnv* env, - ERL_NIF_TERM map) -{ - return extract_bool(env, map, atom_iow, SOCKET_NIF_IOW_DEFAULT); + /* + * We need to do this here since the "proper" atom has not been + * created when this function is called. + */ + ERL_NIF_TERM debug = MKA(env, "debug"); + + return esock_extract_bool_from_map(env, map, debug, SOCKET_NIF_DEBUG_DEFAULT); } static -BOOLEAN_T extract_bool(ErlNifEnv* env, - ERL_NIF_TERM map, - ERL_NIF_TERM ekey, - BOOLEAN_T def) +BOOLEAN_T extract_iow(ErlNifEnv* env, + ERL_NIF_TERM map) { - ERL_NIF_TERM eval; - - if (!GET_MAP_VAL(env, map, ekey, &eval)) - return def; - - if (!IS_ATOM(env, eval)) - return def; - - if (COMPARE(eval, esock_atom_true) == 0) - return TRUE; - else - return FALSE; + /* + * We need to do this here since the "proper" atom has not been + * created when this function is called. + */ + ERL_NIF_TERM iow = MKA(env, "iow"); + + return esock_extract_bool_from_map(env, map, iow, SOCKET_NIF_IOW_DEFAULT); } @@ -8465,8 +8453,8 @@ BOOLEAN_T extract_bool(ErlNifEnv* env, static int on_load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info) { - data.dbg = extract_debug_on_load(env, load_info); - data.iow = extract_iow_on_load(env, load_info); + data.dbg = extract_debug(env, load_info); + data.iow = extract_iow(env, load_info); /* +++ Global Counters +++ */ data.cntMtx = MCREATE("socket[gcnt]"); diff --git a/erts/emulator/nifs/common/socket_util.c b/erts/emulator/nifs/common/socket_util.c index 512c1d38e0..e6eb21adcf 100644 --- a/erts/emulator/nifs/common/socket_util.c +++ b/erts/emulator/nifs/common/socket_util.c @@ -1098,6 +1098,33 @@ BOOLEAN_T esock_decode_string(ErlNifEnv* env, +/* *** esock_extract_bool_from_map *** + * + * Extract an boolean item from a map. + * + */ +extern +BOOLEAN_T esock_extract_bool_from_map(ErlNifEnv* env, + ERL_NIF_TERM map, + ERL_NIF_TERM key, + BOOLEAN_T def) +{ + ERL_NIF_TERM val; + + if (!GET_MAP_VAL(env, map, key, &val)) + return def; + + if (!IS_ATOM(env, val)) + return def; + + if (COMPARE(val, esock_atom_true) == 0) + return TRUE; + else + return FALSE; +} + + + /* *** esock_decode_bool *** * * Decode a boolean value. diff --git a/erts/emulator/nifs/common/socket_util.h b/erts/emulator/nifs/common/socket_util.h index f1c122e281..686ce0bac6 100644 --- a/erts/emulator/nifs/common/socket_util.h +++ b/erts/emulator/nifs/common/socket_util.h @@ -133,6 +133,11 @@ BOOLEAN_T esock_decode_string(ErlNifEnv* env, char** stringP); extern +BOOLEAN_T esock_extract_bool_from_map(ErlNifEnv* env, + ERL_NIF_TERM map, + ERL_NIF_TERM key, + BOOLEAN_T def); +extern BOOLEAN_T esock_decode_bool(ERL_NIF_TERM val); extern ERL_NIF_TERM esock_encode_bool(BOOLEAN_T val); -- cgit v1.2.3 From 5e0a36abaa984358f617541b102b4e4cbb112956 Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Fri, 13 Jul 2018 14:42:34 +0200 Subject: [socket-nif] Add support for socket (level socket) option bindtodevice Added support for socket level socket option BINDTODEVICE. OTP-14831 --- erts/emulator/nifs/common/socket_nif.c | 246 +++++++++++++++++++++++---------- erts/preloaded/ebin/socket.beam | Bin 46248 -> 46292 bytes erts/preloaded/src/socket.erl | 28 ++-- lib/kernel/test/socket_server.erl | 51 ++++--- 4 files changed, 216 insertions(+), 109 deletions(-) diff --git a/erts/emulator/nifs/common/socket_nif.c b/erts/emulator/nifs/common/socket_nif.c index d973633606..141b42a3cd 100644 --- a/erts/emulator/nifs/common/socket_nif.c +++ b/erts/emulator/nifs/common/socket_nif.c @@ -346,21 +346,22 @@ typedef union { #define SOCKET_OPT_OTP_IOW 2 #define SOCKET_OPT_OTP_CTRL_PROC 3 -#define SOCKET_OPT_SOCK_ACCEPTCONN 1 -#define SOCKET_OPT_SOCK_BROADCAST 4 -#define SOCKET_OPT_SOCK_DEBUG 6 -#define SOCKET_OPT_SOCK_DOMAIN 7 -#define SOCKET_OPT_SOCK_DONTROUTE 8 -#define SOCKET_OPT_SOCK_KEEPALIVE 10 -#define SOCKET_OPT_SOCK_LINGER 11 -#define SOCKET_OPT_SOCK_OOBINLINE 13 -#define SOCKET_OPT_SOCK_PEEK_OFF 15 -#define SOCKET_OPT_SOCK_PRIORITY 17 -#define SOCKET_OPT_SOCK_PROTOCOL 18 -#define SOCKET_OPT_SOCK_RCVBUF 19 -#define SOCKET_OPT_SOCK_REUSEADDR 23 -#define SOCKET_OPT_SOCK_SNDBUF 27 -#define SOCKET_OPT_SOCK_TYPE 32 +#define SOCKET_OPT_SOCK_ACCEPTCONN 1 +#define SOCKET_OPT_SOCK_BINDTODEVICE 3 +#define SOCKET_OPT_SOCK_BROADCAST 4 +#define SOCKET_OPT_SOCK_DEBUG 6 +#define SOCKET_OPT_SOCK_DOMAIN 7 +#define SOCKET_OPT_SOCK_DONTROUTE 8 +#define SOCKET_OPT_SOCK_KEEPALIVE 10 +#define SOCKET_OPT_SOCK_LINGER 11 +#define SOCKET_OPT_SOCK_OOBINLINE 13 +#define SOCKET_OPT_SOCK_PEEK_OFF 15 +#define SOCKET_OPT_SOCK_PRIORITY 17 +#define SOCKET_OPT_SOCK_PROTOCOL 18 +#define SOCKET_OPT_SOCK_RCVBUF 19 +#define SOCKET_OPT_SOCK_REUSEADDR 23 +#define SOCKET_OPT_SOCK_SNDBUF 27 +#define SOCKET_OPT_SOCK_TYPE 32 #define SOCKET_OPT_IP_ADD_MEMBERSHIP 1 #define SOCKET_OPT_IP_ADD_SOURCE_MEMBERSHIP 2 @@ -803,7 +804,14 @@ static ERL_NIF_TERM nsetopt_lvl_socket(ErlNifEnv* env, int eOpt, ERL_NIF_TERM eVal); + /* *** Handling set of socket options for level = socket *** */ + +#if defined(SO_BINDTODEVICE) +static ERL_NIF_TERM nsetopt_lvl_sock_bindtodevice(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); +#endif #if defined(SO_BROADCAST) static ERL_NIF_TERM nsetopt_lvl_sock_broadcast(ErlNifEnv* env, SocketDescriptor* descP, @@ -1080,6 +1088,10 @@ static ERL_NIF_TERM ngetopt_lvl_socket(ErlNifEnv* env, static ERL_NIF_TERM ngetopt_lvl_sock_acceptconn(ErlNifEnv* env, SocketDescriptor* descP); #endif +#if defined(SO_BINDTODEVICE) +static ERL_NIF_TERM ngetopt_lvl_sock_bindtodevice(ErlNifEnv* env, + SocketDescriptor* descP); +#endif #if defined(SO_BROADCAST) static ERL_NIF_TERM ngetopt_lvl_sock_broadcast(ErlNifEnv* env, SocketDescriptor* descP); @@ -3902,6 +3914,12 @@ ERL_NIF_TERM nsetopt_lvl_socket(ErlNifEnv* env, "\r\n", eOpt) ); switch (eOpt) { +#if defined(SO_BINDTODEVICE) + case SOCKET_OPT_SOCK_BINDTODEVICE: + result = nsetopt_lvl_sock_bindtodevice(env, descP, eVal); + break; +#endif + #if defined(SO_BROADCAST) case SOCKET_OPT_SOCK_BROADCAST: result = nsetopt_lvl_sock_broadcast(env, descP, eVal); @@ -3977,6 +3995,19 @@ ERL_NIF_TERM nsetopt_lvl_socket(ErlNifEnv* env, } +#if defined(SO_BINDTODEVICE) +static +ERL_NIF_TERM nsetopt_lvl_sock_bindtodevice(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) +{ + return nsetopt_str_opt(env, descP, + SOL_SOCKET, SO_BROADCAST, + IFNAMSIZ, eVal); +} +#endif + + #if defined(SO_BROADCAST) static ERL_NIF_TERM nsetopt_lvl_sock_broadcast(ErlNifEnv* env, @@ -5111,38 +5142,6 @@ ERL_NIF_TERM nsetopt_lvl_sctp_nodelay(ErlNifEnv* env, -/* nsetopt_str_opt - set an option that has an string value - */ -static -ERL_NIF_TERM nsetopt_str_opt(ErlNifEnv* env, - SocketDescriptor* descP, - int level, - int opt, - int max, - ERL_NIF_TERM eVal) -{ - ERL_NIF_TERM result; - char* val = MALLOC(max); - - if (GET_STR(env, eVal, val, max) > 0) { - int optLen = strlen(val); - int res = socket_setopt(descP->sock, level, opt, &val, optLen); - - if (res != 0) - result = esock_make_error_errno(env, sock_errno()); - else - result = esock_atom_ok; - - } else { - result = esock_make_error(env, esock_atom_einval); - } - - FREE(val); - - return result; -} - - /* nsetopt_bool_opt - set an option that has an (integer) bool value */ static @@ -5198,6 +5197,38 @@ ERL_NIF_TERM nsetopt_int_opt(ErlNifEnv* env, } +/* nsetopt_str_opt - set an option that has an string value + */ +static +ERL_NIF_TERM nsetopt_str_opt(ErlNifEnv* env, + SocketDescriptor* descP, + int level, + int opt, + int max, + ERL_NIF_TERM eVal) +{ + ERL_NIF_TERM result; + char* val = MALLOC(max); + + if (GET_STR(env, eVal, val, max) > 0) { + int optLen = strlen(val); + int res = socket_setopt(descP->sock, level, opt, &val, optLen); + + if (res != 0) + result = esock_make_error_errno(env, sock_errno()); + else + result = esock_atom_ok; + + } else { + result = esock_make_error(env, esock_atom_einval); + } + + FREE(val); + + return result; +} + + static BOOLEAN_T elevel2level(BOOLEAN_T isEncoded, int eLevel, @@ -5389,6 +5420,8 @@ ERL_NIF_TERM nif_getopt(ErlNifEnv* env, ERL_NIF_TERM eIsEncoded; BOOLEAN_T isEncoded, isOTP; + SGDBG( ("SOCKET", "nif_getopt -> entry with argc: %d\r\n", argc) ); + if ((argc != 4) || !enif_get_resource(env, argv[0], sockets, (void**) &descP) || !GET_INT(env, argv[2], &eLevel) || @@ -5397,6 +5430,14 @@ ERL_NIF_TERM nif_getopt(ErlNifEnv* env, } eIsEncoded = argv[1]; + SSDBG( descP, + ("SOCKET", "nif_getopt -> args when sock = %d:" + "\r\n Socket: %T" + "\r\n eIsEncoded: %T" + "\r\n eLevel: %d" + "\r\n eOpt: %d" + "\r\n", descP->sock, argv[0], eIsEncoded, eLevel, eOpt) ); + isEncoded = esock_decode_bool(eIsEncoded); if (!elevel2level(isEncoded, eLevel, &isOTP, &level)) @@ -5417,6 +5458,14 @@ ERL_NIF_TERM ngetopt(ErlNifEnv* env, { ERL_NIF_TERM result; + SSDBG( descP, + ("SOCKET", "ngetopt -> entry with" + "\r\n isEncoded: %d" + "\r\n isOTP: %d" + "\r\n level: %d" + "\r\n eOpt: %d" + "\r\n", isEncoded, isOTP, level, eOpt) ); + if (isOTP) { /* These are not actual socket options, * but options for our implementation. @@ -5581,6 +5630,12 @@ ERL_NIF_TERM ngetopt_level(ErlNifEnv* env, { ERL_NIF_TERM result; + SSDBG( descP, + ("SOCKET", "ngetopt_level -> entry with" + "\r\n level: %d" + "\r\n eOpt: %d" + "\r\n", level, eOpt) ); + switch (level) { case SOL_SOCKET: result = ngetopt_lvl_socket(env, descP, eOpt); @@ -5632,6 +5687,11 @@ ERL_NIF_TERM ngetopt_lvl_socket(ErlNifEnv* env, { ERL_NIF_TERM result; + SSDBG( descP, + ("SOCKET", "ngetopt_lvl_socket -> entry with" + "\r\n eOpt: %d" + "\r\n", eOpt) ); + switch (eOpt) { #if defined(SO_ACCEPTCONN) case SOCKET_OPT_SOCK_ACCEPTCONN: @@ -5639,6 +5699,12 @@ ERL_NIF_TERM ngetopt_lvl_socket(ErlNifEnv* env, break; #endif +#if defined(SO_BINDTODEVICE) + case SOCKET_OPT_SOCK_BINDTODEVICE: + result = ngetopt_lvl_sock_bindtodevice(env, descP); + break; +#endif + #if defined(SO_BROADCAST) case SOCKET_OPT_SOCK_BROADCAST: result = ngetopt_lvl_sock_broadcast(env, descP); @@ -5742,6 +5808,19 @@ ERL_NIF_TERM ngetopt_lvl_sock_acceptconn(ErlNifEnv* env, #endif +#if defined(SO_BINDTODEVICE) +static +ERL_NIF_TERM ngetopt_lvl_sock_bindtodevice(ErlNifEnv* env, + SocketDescriptor* descP) +{ + SSDBG( descP, + ("SOCKET", "ngetopt_lvl_sock_bindtodevice -> entry with\r\n") ); + + return ngetopt_str_opt(env, descP, SOL_SOCKET, SO_BROADCAST, IFNAMSIZ+1); +} +#endif + + #if defined(SO_BROADCAST) static ERL_NIF_TERM ngetopt_lvl_sock_broadcast(ErlNifEnv* env, @@ -6628,18 +6707,17 @@ ERL_NIF_TERM ngetopt_lvl_sctp_nodelay(ErlNifEnv* env, -/* ngetopt_str_opt - get an string option +/* ngetopt_bool_opt - get an (integer) bool option */ static -ERL_NIF_TERM ngetopt_str_opt(ErlNifEnv* env, - SocketDescriptor* descP, - int level, - int opt, - int max) +ERL_NIF_TERM ngetopt_bool_opt(ErlNifEnv* env, + SocketDescriptor* descP, + int level, + int opt) { ERL_NIF_TERM result; - char* val = MALLOC(max); - SOCKOPTLEN_T valSz = max; + int val; + SOCKOPTLEN_T valSz = sizeof(val); int res; res = sock_getopt(descP->sock, level, opt, &val, &valSz); @@ -6647,24 +6725,22 @@ ERL_NIF_TERM ngetopt_str_opt(ErlNifEnv* env, if (res != 0) { result = esock_make_error_errno(env, sock_errno()); } else { - ERL_NIF_TERM sval = MKSL(env, val, valSz); + ERL_NIF_TERM bval = ((val) ? atom_true : atom_false); - result = esock_make_ok2(env, sval); + result = esock_make_ok2(env, bval); } - FREE(val); - return result; } -/* nsetopt_bool_opt - get an (integer) bool option +/* ngetopt_int_opt - get an integer option */ static -ERL_NIF_TERM ngetopt_bool_opt(ErlNifEnv* env, - SocketDescriptor* descP, - int level, - int opt) +ERL_NIF_TERM ngetopt_int_opt(ErlNifEnv* env, + SocketDescriptor* descP, + int level, + int opt) { ERL_NIF_TERM result; int val; @@ -6676,42 +6752,62 @@ ERL_NIF_TERM ngetopt_bool_opt(ErlNifEnv* env, if (res != 0) { result = esock_make_error_errno(env, sock_errno()); } else { - ERL_NIF_TERM bval = ((val) ? atom_true : atom_false); - - result = esock_make_ok2(env, bval); + result = esock_make_ok2(env, MKI(env, val)); } return result; } -/* nsetopt_int_opt - get an integer option + +/* ngetopt_str_opt - get an string option + * + * We provide the max size of the string. This is the + * size of the buffer we allocate for the value. + * The actual size of the (read) value will be communicated + * in the optSz variable. */ static -ERL_NIF_TERM ngetopt_int_opt(ErlNifEnv* env, +ERL_NIF_TERM ngetopt_str_opt(ErlNifEnv* env, SocketDescriptor* descP, int level, - int opt) + int opt, + int max) { ERL_NIF_TERM result; - int val; - SOCKOPTLEN_T valSz = sizeof(val); + char* val = MALLOC(max); + SOCKOPTLEN_T valSz = max; int res; - res = sock_getopt(descP->sock, level, opt, &val, &valSz); + SSDBG( descP, + ("SOCKET", "ngetopt_str_opt -> entry with" + "\r\n level: %d" + "\r\n opt: %d" + "\r\n max: %d" + "\r\n", level, opt, max) ); + + res = sock_getopt(descP->sock, level, opt, val, &valSz); if (res != 0) { result = esock_make_error_errno(env, sock_errno()); } else { - result = esock_make_ok2(env, MKI(env, val)); + ERL_NIF_TERM sval = MKSL(env, val, valSz); + + result = esock_make_ok2(env, sval); } + SSDBG( descP, + ("SOCKET", "ngetopt_str_opt -> done when" + "\r\n result: %T" + "\r\n", result) ); + + FREE(val); + return result; } - /* ---------------------------------------------------------------------- * nif_sockname - get socket name * diff --git a/erts/preloaded/ebin/socket.beam b/erts/preloaded/ebin/socket.beam index 03a7012131..5032366c93 100644 Binary files a/erts/preloaded/ebin/socket.beam and b/erts/preloaded/ebin/socket.beam differ diff --git a/erts/preloaded/src/socket.erl b/erts/preloaded/src/socket.erl index 8d377d808e..d4bf55511c 100644 --- a/erts/preloaded/src/socket.erl +++ b/erts/preloaded/src/socket.erl @@ -366,15 +366,15 @@ status | use_ext_recvinfo. -%% -type plain_socket_options() :: integer(). -%% -type sockopts() :: otp_socket_options() | -%% socket_options() | -%% ip_socket_options() | -%% ipv6_socket_options() | -%% tcp_socket_options() | -%% udp_socket_options() | -%% sctp_socket_options() | -%% plain_socket_options(). +%% -type plain_socket_option() :: integer(). +%% -type sockopt() :: otp_socket_option() | +%% socket_option() | +%% ip_socket_option() | +%% ipv6_socket_option() | +%% tcp_socket_option() | +%% udp_socket_option() | +%% sctp_socket_option() | +%% plain_socket_option(). %% If the integer value is used its up to the caller to ensure its valid! -type ip_tos_flag() :: lowdeley | @@ -496,7 +496,7 @@ -define(SOCKET_OPT_SOCK_ACCEPTCONN, 1). %% -define(SOCKET_OPT_SOCK_ACCEPTFILTER, 2). -%% -define(SOCKET_OPT_SOCK_BINDTODEVICE, 3). +-define(SOCKET_OPT_SOCK_BINDTODEVICE, 3). -define(SOCKET_OPT_SOCK_BROADCAST, 4). %% -define(SOCKET_OPT_SOCK_BUSY_POLL, 5). -define(SOCKET_OPT_SOCK_DEBUG, 6). @@ -1959,6 +1959,8 @@ enc_setopt_value(otp, controlling_process, V, _, _, _) when is_pid(V) -> enc_setopt_value(otp = L, Opt, V, _D, _T, _P) -> not_supported({L, Opt, V}); +enc_setopt_value(socket, bindtodevice, V, _D, _T, _P) when is_list(V) -> + V; enc_setopt_value(socket, broadcast, V, _D, _T, _P) when is_boolean(V) -> V; enc_setopt_value(socket, debug, V, _D, _T, _P) when is_integer(V) -> @@ -2253,10 +2255,10 @@ enc_sockopt_key(socket = _L, acceptconn = _Opt, get = _Dir, _D, _T, _P) -> enc_sockopt_key(socket = L, acceptfilter = Opt, _Dir, _D, _T, _P) -> not_supported({L, Opt}); %% Before linux 3.8, this socket option could be set. -%% Size of buffer for name: IFNAMSZ +%% Maximum size of buffer for name: IFNAMSZIZ %% So, we let the implementation decide. -enc_sockopt_key(socket = L, bindtodevide = Opt, _Dir, _D, _T, _P) -> - not_supported({L, Opt}); +enc_sockopt_key(socket = _L, bindtodevice = _Opt, _Dir, _D, _T, _P) -> + ?SOCKET_OPT_SOCK_BINDTODEVICE; enc_sockopt_key(socket, broadcast = _Opt, _Dir, _D, dgram = _T, _P) -> ?SOCKET_OPT_SOCK_BROADCAST; enc_sockopt_key(socket = L, busy_poll = Opt, _Dir, _D, _T, _P) -> diff --git a/lib/kernel/test/socket_server.erl b/lib/kernel/test/socket_server.erl index ff8f6575a3..6d002db38e 100644 --- a/lib/kernel/test/socket_server.erl +++ b/lib/kernel/test/socket_server.erl @@ -101,7 +101,7 @@ manager_init(Domain, dgram = Type, Proto, Peek) -> {ok, Sock} -> F = fun(X) -> case socket:getopt(Sock, socket, X) of {ok, V} -> f("~p", [V]); - {error, _} -> "-" + {error, R} -> f("error: ~p", [R]) end end, i("socket opened (~s,~s,~s): " @@ -129,11 +129,16 @@ manager_init(Domain, dgram = Type, Proto, Peek) -> {error, BReason} -> throw({bind, BReason}) end, - i("try start handler for" - "~n ~p", [case socket:sockname(Sock) of - {ok, Name} -> Name; - {error, _} = E -> E - end]), + socket:setopt(Sock, otp, debug, true), + i("bound to: " + "~n ~s" + "~n (socket) Bind To Device: ~s" + "~n => try start handler", + [case socket:sockname(Sock) of + {ok, Name} -> f("~p", [Name]); + {error, R} -> f("error: ~p", [R]) + end, + F(bindtodevice)]), case handler_start(1, Sock, Peek) of {ok, {Pid, MRef}} -> i("handler (~p) started", [Pid]), @@ -435,37 +440,41 @@ handler_init(Manager, ID, Peek, Sock) -> {handler, Pid, Ref, continue} -> i("got continue"), handler_reply(Pid, Ref, ok), - G = fun(K) -> case socket:getopt(Sock, ip, K) of - {ok, Val} -> - f("~p", [Val]); - {error, R} when is_atom(R) -> - f("error: ~w", [R]); - {error, {T, R}} when is_atom(T) -> - f("error: ~w, ~p", [T, R]); - {error, R} -> - f("error: ~p", [R]) + G = fun(L, O) -> case socket:getopt(Sock, L, O) of + {ok, Val} -> + f("~p", [Val]); + {error, R} when is_atom(R) -> + f("error: ~w", [R]); + {error, {T, R}} when is_atom(T) -> + f("error: ~w, ~p", [T, R]); + {error, R} -> + f("error: ~p", [R]) end end, + GIP = fun(O) -> G(ip, O) end, + GSO = fun(O) -> G(socket, O) end, {ok, Domain} = socket:getopt(Sock, socket, domain), {ok, Type} = socket:getopt(Sock, socket, type), {ok, Proto} = socket:getopt(Sock, socket, protocol), + B2D = GSO(bindtodevice), {ok, OOBI} = socket:getopt(Sock, socket, oobinline), {ok, SndBuf} = socket:getopt(Sock, socket, sndbuf), {ok, RcvBuf} = socket:getopt(Sock, socket, rcvbuf), {ok, Linger} = socket:getopt(Sock, socket, linger), - MTU = G(mtu), - MTUDisc = G(mtu_discover), + MTU = GIP(mtu), + MTUDisc = GIP(mtu_discover), {ok, MALL} = socket:getopt(Sock, ip, multicast_all), {ok, MIF} = socket:getopt(Sock, ip, multicast_if), {ok, MLoop} = socket:getopt(Sock, ip, multicast_loop), {ok, MTTL} = socket:getopt(Sock, ip, multicast_ttl), - NF = G(nodefrag), % raw only - RecvTOS = G(recvtos), - RecvTTL = G(recvttl), % not stream + NF = GIP(nodefrag), % raw only + RecvTOS = GIP(recvtos), + RecvTTL = GIP(recvttl), % not stream i("got continue when: " "~n (socket) Domain: ~p" "~n (socket) Type: ~p" "~n (socket) Protocol: ~p" + "~n (socket) Bind To Device: ~s" "~n (socket) OOBInline: ~p" "~n (socket) SndBuf: ~p" "~n (socket) RcvBuf: ~p" @@ -480,7 +489,7 @@ handler_init(Manager, ID, Peek, Sock) -> "~n (ip) RecvTOS: ~s" "~n (ip) RecvTTL: ~s", [Domain, Type, Proto, - OOBI, SndBuf, RcvBuf, Linger, + B2D, OOBI, SndBuf, RcvBuf, Linger, MTU, MTUDisc, MALL, MIF, MLoop, MTTL, NF, RecvTOS, RecvTTL]), %% socket:setopt(Sock, otp, debug, true), -- cgit v1.2.3 From 8de18e84deaed4c9e6e7242ae2550fc6618dc44d Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Fri, 13 Jul 2018 18:38:53 +0200 Subject: [socket-nif] Add support for socket (level socket) option reuseport Added support for socket level socket option REUSEPORT. OTP-14831 --- erts/emulator/nifs/common/socket_nif.c | 43 +++++++++++++++++++++++++++++ erts/preloaded/ebin/socket.beam | Bin 46292 -> 46328 bytes erts/preloaded/src/socket.erl | 10 ++++--- lib/kernel/test/socket_server.erl | 49 +++++++++++++++++---------------- 4 files changed, 74 insertions(+), 28 deletions(-) diff --git a/erts/emulator/nifs/common/socket_nif.c b/erts/emulator/nifs/common/socket_nif.c index 141b42a3cd..713153d7c5 100644 --- a/erts/emulator/nifs/common/socket_nif.c +++ b/erts/emulator/nifs/common/socket_nif.c @@ -360,6 +360,7 @@ typedef union { #define SOCKET_OPT_SOCK_PROTOCOL 18 #define SOCKET_OPT_SOCK_RCVBUF 19 #define SOCKET_OPT_SOCK_REUSEADDR 23 +#define SOCKET_OPT_SOCK_REUSEPORT 24 #define SOCKET_OPT_SOCK_SNDBUF 27 #define SOCKET_OPT_SOCK_TYPE 32 @@ -862,6 +863,11 @@ static ERL_NIF_TERM nsetopt_lvl_sock_reuseaddr(ErlNifEnv* env, SocketDescriptor* descP, ERL_NIF_TERM eVal); #endif +#if defined(SO_REUSEPORT) +static ERL_NIF_TERM nsetopt_lvl_sock_reuseport(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); +#endif #if defined(SO_SNDBUF) static ERL_NIF_TERM nsetopt_lvl_sock_sndbuf(ErlNifEnv* env, SocketDescriptor* descP, @@ -1140,6 +1146,10 @@ static ERL_NIF_TERM ngetopt_lvl_sock_rcvbuf(ErlNifEnv* env, static ERL_NIF_TERM ngetopt_lvl_sock_reuseaddr(ErlNifEnv* env, SocketDescriptor* descP); #endif +#if defined(SO_REUSEPORT) +static ERL_NIF_TERM ngetopt_lvl_sock_reuseport(ErlNifEnv* env, + SocketDescriptor* descP); +#endif #if defined(SO_SNDBUF) static ERL_NIF_TERM ngetopt_lvl_sock_sndbuf(ErlNifEnv* env, SocketDescriptor* descP); @@ -3980,6 +3990,12 @@ ERL_NIF_TERM nsetopt_lvl_socket(ErlNifEnv* env, break; #endif +#if defined(SO_REUSEPORT) + case SOCKET_OPT_SOCK_REUSEPORT: + result = nsetopt_lvl_sock_reuseport(env, descP, eVal); + break; +#endif + #if defined(SO_SNDBUF) case SOCKET_OPT_SOCK_SNDBUF: result = nsetopt_lvl_sock_sndbuf(env, descP, eVal); @@ -4133,6 +4149,17 @@ ERL_NIF_TERM nsetopt_lvl_sock_reuseaddr(ErlNifEnv* env, #endif +#if defined(SO_REUSEPORT) +static +ERL_NIF_TERM nsetopt_lvl_sock_reuseport(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) +{ + return nsetopt_bool_opt(env, descP, SOL_SOCKET, SO_REUSEPORT, eVal); +} +#endif + + #if defined(SO_SNDBUF) static ERL_NIF_TERM nsetopt_lvl_sock_sndbuf(ErlNifEnv* env, @@ -5777,6 +5804,12 @@ ERL_NIF_TERM ngetopt_lvl_socket(ErlNifEnv* env, break; #endif +#if defined(SO_REUSEPORT) + case SOCKET_OPT_SOCK_REUSEPORT: + result = ngetopt_lvl_sock_reuseport(env, descP); + break; +#endif + #if defined(SO_SNDBUF) case SOCKET_OPT_SOCK_SNDBUF: result = ngetopt_lvl_sock_sndbuf(env, descP); @@ -6035,6 +6068,16 @@ ERL_NIF_TERM ngetopt_lvl_sock_reuseaddr(ErlNifEnv* env, #endif +#if defined(SO_REUSEPORT) +static +ERL_NIF_TERM ngetopt_lvl_sock_reuseport(ErlNifEnv* env, + SocketDescriptor* descP) +{ + return ngetopt_bool_opt(env, descP, SOL_SOCKET, SO_REUSEPORT); +} +#endif + + #if defined(SO_SNDBUF) static ERL_NIF_TERM ngetopt_lvl_sock_sndbuf(ErlNifEnv* env, diff --git a/erts/preloaded/ebin/socket.beam b/erts/preloaded/ebin/socket.beam index 5032366c93..9baf6a422e 100644 Binary files a/erts/preloaded/ebin/socket.beam and b/erts/preloaded/ebin/socket.beam differ diff --git a/erts/preloaded/src/socket.erl b/erts/preloaded/src/socket.erl index d4bf55511c..fbfd1903a1 100644 --- a/erts/preloaded/src/socket.erl +++ b/erts/preloaded/src/socket.erl @@ -517,7 +517,7 @@ %% -define(SOCKET_OPT_SOCK_RCVLOWAT, 21). %% -define(SOCKET_OPT_SOCK_RCVTIMEO, 22). -define(SOCKET_OPT_SOCK_REUSEADDR, 23). -%% -define(SOCKET_OPT_SOCK_REUSEPORT, 24). +-define(SOCKET_OPT_SOCK_REUSEPORT, 24). %% -define(SOCKET_OPT_SOCK_RXQ_OVFL, 25). %% -define(SOCKET_OPT_SOCK_SETFIB, 26). -define(SOCKET_OPT_SOCK_SNDBUF, 27). @@ -1984,6 +1984,8 @@ enc_setopt_value(socket, rcvbuf, V, _D, _T, _P) when is_integer(V) -> V; enc_setopt_value(socket, reuseaddr, V, _D, _T, _P) when is_boolean(V) -> V; +enc_setopt_value(socket, reuseport, V, _D, _T, _P) when is_boolean(V) -> + V; enc_setopt_value(socket, sndbuf, V, _D, _T, _P) when is_integer(V) -> V; enc_setopt_value(socket = L, Opt, V, _D, _T, _P) -> @@ -2301,11 +2303,11 @@ enc_sockopt_key(socket = L, rcvlowat = Opt, _Dir, _D, _T, _P) -> not_supported({L, Opt}); enc_sockopt_key(socket, rcvtimeo = Opt, _Dir, _D, _T, _P) -> not_supported(Opt); -enc_sockopt_key(socket, reuseaddr = _Opt, _Dir, _D, _T, _P) -> +enc_sockopt_key(socket = _L, reuseaddr = _Opt, _Dir, _D, _T, _P) -> ?SOCKET_OPT_SOCK_REUSEADDR; -enc_sockopt_key(socket = L, reuseport = Opt, _Dir, D, _T, _P) +enc_sockopt_key(socket = _L, reuseport = _Opt, _Dir, D, _T, _P) when ((D =:= inet) orelse (D =:= inet6)) -> - not_supported({L, Opt}); + ?SOCKET_OPT_SOCK_REUSEPORT; enc_sockopt_key(socket = L, rxq_ovfl = Opt, _Dir, _D, _T, _P) -> not_supported({L, Opt}); enc_sockopt_key(socket = L, setfib = Opt, set = _Dir, _D, _T, _P) -> diff --git a/lib/kernel/test/socket_server.erl b/lib/kernel/test/socket_server.erl index 6d002db38e..d9bbf00e85 100644 --- a/lib/kernel/test/socket_server.erl +++ b/lib/kernel/test/socket_server.erl @@ -129,16 +129,13 @@ manager_init(Domain, dgram = Type, Proto, Peek) -> {error, BReason} -> throw({bind, BReason}) end, - socket:setopt(Sock, otp, debug, true), i("bound to: " "~n ~s" - "~n (socket) Bind To Device: ~s" "~n => try start handler", [case socket:sockname(Sock) of {ok, Name} -> f("~p", [Name]); {error, R} -> f("error: ~p", [R]) - end, - F(bindtodevice)]), + end]), case handler_start(1, Sock, Peek) of {ok, {Pid, MRef}} -> i("handler (~p) started", [Pid]), @@ -457,16 +454,18 @@ handler_init(Manager, ID, Peek, Sock) -> {ok, Type} = socket:getopt(Sock, socket, type), {ok, Proto} = socket:getopt(Sock, socket, protocol), B2D = GSO(bindtodevice), - {ok, OOBI} = socket:getopt(Sock, socket, oobinline), - {ok, SndBuf} = socket:getopt(Sock, socket, sndbuf), - {ok, RcvBuf} = socket:getopt(Sock, socket, rcvbuf), - {ok, Linger} = socket:getopt(Sock, socket, linger), + RA = GSO(reuseaddr), + RP = GSO(reuseport), + OOBI = GSO(oobinline), + SndBuf = GSO(sndbuf), + RcvBuf = GSO(rcvbuf), + Linger = GSO(linger), MTU = GIP(mtu), MTUDisc = GIP(mtu_discover), - {ok, MALL} = socket:getopt(Sock, ip, multicast_all), - {ok, MIF} = socket:getopt(Sock, ip, multicast_if), - {ok, MLoop} = socket:getopt(Sock, ip, multicast_loop), - {ok, MTTL} = socket:getopt(Sock, ip, multicast_ttl), + MALL = GIP(multicast_all), + MIF = GIP(multicast_if), + MLoop = GIP(multicast_loop), + MTTL = GIP(multicast_ttl), NF = GIP(nodefrag), % raw only RecvTOS = GIP(recvtos), RecvTTL = GIP(recvttl), % not stream @@ -474,22 +473,24 @@ handler_init(Manager, ID, Peek, Sock) -> "~n (socket) Domain: ~p" "~n (socket) Type: ~p" "~n (socket) Protocol: ~p" + "~n (socket) Reuse Address: ~s" + "~n (socket) Reuse Port: ~s" "~n (socket) Bind To Device: ~s" - "~n (socket) OOBInline: ~p" - "~n (socket) SndBuf: ~p" - "~n (socket) RcvBuf: ~p" - "~n (socket) Linger: ~p" + "~n (socket) OOBInline: ~s" + "~n (socket) SndBuf: ~s" + "~n (socket) RcvBuf: ~s" + "~n (socket) Linger: ~s" "~n (ip) MTU: ~s" "~n (ip) MTU Discovery: ~s" - "~n (ip) Multicast ALL: ~p" - "~n (ip) Multicast IF: ~p" - "~n (ip) Multicast Loop: ~p" - "~n (ip) Multicast TTL: ~p" - "~n (ip) NodeFrag: ~s" - "~n (ip) RecvTOS: ~s" - "~n (ip) RecvTTL: ~s", + "~n (ip) Multicast ALL: ~s" + "~n (ip) Multicast IF: ~s" + "~n (ip) Multicast Loop: ~s" + "~n (ip) Multicast TTL: ~s" + "~n (ip) Node Frag: ~s" + "~n (ip) Recv TOS: ~s" + "~n (ip) Recv TTL: ~s", [Domain, Type, Proto, - B2D, OOBI, SndBuf, RcvBuf, Linger, + RA, RP, B2D, OOBI, SndBuf, RcvBuf, Linger, MTU, MTUDisc, MALL, MIF, MLoop, MTTL, NF, RecvTOS, RecvTTL]), %% socket:setopt(Sock, otp, debug, true), -- cgit v1.2.3 From 017565203f40860d24b80a54136a160aee460dbe Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Mon, 16 Jul 2018 18:21:48 +0200 Subject: [socket-nif] Add support for multiple acceptor processes Its now possible to have multiple (simultaneous) acceptor processes for the same listening socket. OTP-14831 --- erts/emulator/nifs/common/socket_int.h | 2 + erts/emulator/nifs/common/socket_nif.c | 310 ++++++++++++++++++++++++++++++-- erts/preloaded/ebin/socket.beam | Bin 46328 -> 46540 bytes erts/preloaded/src/socket.erl | 7 +- lib/kernel/test/socket_server.erl | 313 +++++++++++++++++++-------------- 5 files changed, 492 insertions(+), 140 deletions(-) diff --git a/erts/emulator/nifs/common/socket_int.h b/erts/emulator/nifs/common/socket_int.h index aa10260134..67e4baba27 100644 --- a/erts/emulator/nifs/common/socket_int.h +++ b/erts/emulator/nifs/common/socket_int.h @@ -92,6 +92,7 @@ typedef unsigned int BOOLEAN_T; #define BOOL2ATOM(__B__) ((__B__) ? esock_atom_true : esock_atom_false) +#define B2S(__B__) ((__B__) ? "true" : "false") /* Misc error strings */ #define ESOCK_STR_EAFNOSUPPORT "eafnosupport" @@ -141,6 +142,7 @@ extern ERL_NIF_TERM esock_atom_eagain; extern ERL_NIF_TERM esock_atom_einval; + /* ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ * Various wrapper macros for enif functions */ diff --git a/erts/emulator/nifs/common/socket_nif.c b/erts/emulator/nifs/common/socket_nif.c index 713153d7c5..224dcc9ff6 100644 --- a/erts/emulator/nifs/common/socket_nif.c +++ b/erts/emulator/nifs/common/socket_nif.c @@ -572,7 +572,7 @@ typedef struct { /* +++ Accept stuff +++ */ ErlNifMutex* accMtx; SocketRequestor currentAcceptor; - SocketRequestor* currentAcceptorP; // NULL or points to currentReader + SocketRequestor* currentAcceptorP; // NULL or points to currentAcceptor SocketRequestQueue acceptorsQ; /* +++ Config & Misc stuff +++ */ @@ -1488,6 +1488,29 @@ static void inc_socket(int domain, int type, int protocol); static void dec_socket(int domain, int type, int protocol); +static BOOLEAN_T acceptor_search4pid(ErlNifEnv* env, + SocketDescriptor* descP, + ErlNifPid* pid); +static ERL_NIF_TERM acceptor_push(ErlNifEnv* env, + SocketDescriptor* descP, + ErlNifPid pid, + ERL_NIF_TERM ref); +static BOOLEAN_T acceptor_pop(ErlNifEnv* env, + SocketDescriptor* descP, + ErlNifPid* pid, + ErlNifMonitor* mon, + ERL_NIF_TERM* ref); + +static BOOLEAN_T qsearch4pid(ErlNifEnv* env, + SocketRequestQueue* q, + ErlNifPid* pid); +static void qpush(SocketRequestQueue* q, + SocketRequestQueueElement* e); +static SocketRequestQueueElement* qpop(SocketRequestQueue* q); +static BOOLEAN_T qunqueue(ErlNifEnv* env, + SocketRequestQueue* q, + const ErlNifPid* pid); + /* #if defined(HAVE_SYS_UN_H) || defined(SO_BINDTODEVICE) static size_t my_strnlen(const char *s, size_t maxlen); @@ -2663,6 +2686,7 @@ ERL_NIF_TERM naccept_accepting(ErlNifEnv* env, HANDLE accEvent; ErlNifPid caller; int save_errno; + ERL_NIF_TERM result; SSDBG( descP, ("SOCKET", "naccept_accepting -> get caller\r\n") ); @@ -2682,11 +2706,18 @@ ERL_NIF_TERM naccept_accepting(ErlNifEnv* env, * for the select message). */ + SSDBG( descP, ("SOCKET", "naccept_accepting -> not (active) acceptor\r\n") ); + + if (!acceptor_search4pid(env, descP, &caller)) + result = acceptor_push(env, descP, caller, ref); + else + result = esock_make_error(env, esock_atom_eagain); + SSDBG( descP, ("SOCKET", - "naccept_accepting -> not current acceptor: busy\r\n") ); + "naccept_accepting -> queue (push) result: %T\r\n", result) ); - return esock_make_error(env, atom_exbusy); + return result; } n = sizeof(descP->remote); @@ -2777,8 +2808,30 @@ ERL_NIF_TERM naccept_accepting(ErlNifEnv* env, * And if so, pop it and copy the (waiting) acceptor, and then * make a new select with that info). */ - descP->state = SOCKET_STATE_LISTENING; + if (acceptor_pop(env, descP, + &descP->currentAcceptor.pid, + &descP->currentAcceptor.mon, + &descP->currentAcceptor.ref)) { + + /* There was another one */ + + SSDBG( descP, ("SOCKET", "naccept_accepting -> new (active) acceptor: " + "\r\n pid: %T" + "\r\n ref: %T" + "\r\n", + descP->currentAcceptor.pid, + descP->currentAcceptor.ref) ); + + SELECT(env, + descP->sock, + (ERL_NIF_SELECT_READ), + descP, &descP->currentAcceptor.pid, descP->currentAcceptor.ref); + } else { + descP->currentAcceptorP = NULL; + descP->state = SOCKET_STATE_LISTENING; + } + return esock_make_ok2(env, accRef); } } @@ -8237,6 +8290,186 @@ char* send_msg(ErlNifEnv* env, +/* ---------------------------------------------------------------------- + * R e q u e s t Q u e u e F u n c t i o n s + * ---------------------------------------------------------------------- + */ + +/* *** acceptor search for pid *** + * + * Search for a pid in the acceptor queue. + */ +static +BOOLEAN_T acceptor_search4pid(ErlNifEnv* env, + SocketDescriptor* descP, + ErlNifPid* pid) +{ + return qsearch4pid(env, &descP->acceptorsQ, pid); +} + + +/* *** acceptor push *** + * + * Push an acceptor onto the acceptor queue. + * This happens when we already have atleast one current acceptor. + */ +static +ERL_NIF_TERM acceptor_push(ErlNifEnv* env, + SocketDescriptor* descP, + ErlNifPid pid, + ERL_NIF_TERM ref) +{ + SocketRequestQueueElement* e = MALLOC(sizeof(SocketRequestQueueElement)); + SocketRequestor* reqP = &e->data; + + reqP->pid = pid; + reqP->ref = ref; + + if (MONP(env, descP, &pid, &reqP->mon) > 0) { + FREE(reqP); + return esock_make_error(env, atom_exmon); + } + + qpush(&descP->acceptorsQ, e); + + // THIS IS OK => MAKES THE CALLER WAIT FOR ITS TURN + return esock_make_error(env, esock_atom_eagain); +} + + +/* *** acceptor pop *** + * + * Pop an acceptor from the acceptor queue. + */ +static +BOOLEAN_T acceptor_pop(ErlNifEnv* env, + SocketDescriptor* descP, + ErlNifPid* pid, + ErlNifMonitor* mon, + ERL_NIF_TERM* ref) +{ + SocketRequestQueueElement* e = qpop(&descP->acceptorsQ); + + if (e != NULL) { + *pid = e->data.pid; + *mon = e->data.mon; + *ref = e->data.ref; + FREE(e); + return TRUE; + } else { + /* (acceptors) Queue was empty */ + // *pid = NULL; we have no null value for pids + // *mon = NULL; we have no null value for monitors + *ref = esock_atom_undefined; // Just in case + return FALSE; + } + +} + + +static +BOOLEAN_T qsearch4pid(ErlNifEnv* env, + SocketRequestQueue* q, + ErlNifPid* pid) +{ + SocketRequestQueueElement* tmp = q->first; + + while (tmp != NULL) { + if (compare_pids(env, &tmp->data.pid, pid)) + return TRUE; + else + tmp = tmp->nextP; + } + + return FALSE; +} + + +static +void qpush(SocketRequestQueue* q, + SocketRequestQueueElement* e) +{ + if (q->first != NULL) { + q->last->nextP = e; + q->last = e; + e->nextP = NULL; + } else { + q->first = e; + q->last = e; + e->nextP = NULL; + } +} + + +static +SocketRequestQueueElement* qpop(SocketRequestQueue* q) +{ + SocketRequestQueueElement* e = q->first; + + if (e != NULL) { + /* Atleast one element in the queue */ + if (e == q->last) { + /* Only one element in the queue */ + q->first = q->last = NULL; + } else { + /* More than one element in the queue */ + q->first = e->nextP; + } + } + + return e; +} + + + +static +BOOLEAN_T qunqueue(ErlNifEnv* env, + SocketRequestQueue* q, + const ErlNifPid* pid) +{ + SocketRequestQueueElement* e = q->first; + SocketRequestQueueElement* p = NULL; + + /* Check if it was one of the waiting acceptor processes */ + while (e != NULL) { + if (compare_pids(env, &e->data.pid, pid)) { + + /* We have a match */ + + if (p != NULL) { + /* Not the first, but could be the last */ + if (q->last == e) { + q->last = p; + p->nextP = NULL; + } else { + p->nextP = e->nextP; + } + + } else { + /* The first and could also be the last */ + if (q->last == e) { + q->last = NULL; + q->first = NULL; + } else { + q->first = e->nextP; + } + } + + FREE(e); + + return TRUE; + } + + /* Try next */ + p = e; + e = e->nextP; + } + + return FALSE; +} + + + /* ---------------------------------------------------------------------- * C o u n t e r F u n c t i o n s * ---------------------------------------------------------------------- @@ -8502,13 +8735,70 @@ void socket_down(ErlNifEnv* env, { SocketDescriptor* descP = (SocketDescriptor*) obj; - SSDBG( descP, - ("SOCKET", "socket_down -> entry when" - "\r\n sock: %d" - "\r\n pid: %T" - "\r\n mon: %T" - "\r\n", descP->sock, *pid, *mon) ); + SSDBG( descP, ("SOCKET", "socket_down -> entry with" + "\r\n sock: %d" + "\r\n pid: %T" + "\r\n", descP->sock, *pid) ); + + /* Eventually we should go through the other queues also, + * the process can be one of them... + */ + + /* Check first if its the current acceptor, and if not check the queue */ + if (compare_pids(env, &descP->currentAcceptor.pid, pid)) { + + SSDBG( descP, ("SOCKET", + "socket_down -> current acceptor - try pop the queue\r\n") ); + + if (acceptor_pop(env, descP, + &descP->currentAcceptor.pid, + &descP->currentAcceptor.mon, + &descP->currentAcceptor.ref)) { + int res; + + /* There was another one, so we will still be in accepting state */ + + SSDBG( descP, ("SOCKET", "socket_down -> new (active) acceptor: " + "\r\n pid: %T" + "\r\n ref: %T" + "\r\n", + descP->currentAcceptor.pid, + descP->currentAcceptor.ref) ); + + if ((res = enif_select(env, + descP->sock, + (ERL_NIF_SELECT_READ), + descP, + &descP->currentAcceptor.pid, + descP->currentAcceptor.ref) < 0)) { + + esock_warning_msg("Failed select (%d) for new acceptor " + "after current (%T) died\r\n", + res, *pid); + + } + + } else { + + SSDBG( descP, ("SOCKET", "socket_down -> no active acceptor\r\n") ); + + descP->currentAcceptorP = NULL; + descP->state = SOCKET_STATE_LISTENING; + } + + } else { + + /* Maybe unqueue one of the waiting acceptors */ + + SSDBG( descP, ("SOCKET", + "socket_down -> " + "not current acceptor - maybe a waiting acceptor\r\n") ); + + qunqueue(env, &descP->acceptorsQ, pid); + } + SSDBG( descP, ("SOCKET", "socket_down -> done\r\n") ); + } diff --git a/erts/preloaded/ebin/socket.beam b/erts/preloaded/ebin/socket.beam index 9baf6a422e..c24dc40e10 100644 Binary files a/erts/preloaded/ebin/socket.beam and b/erts/preloaded/ebin/socket.beam differ diff --git a/erts/preloaded/src/socket.erl b/erts/preloaded/src/socket.erl index fbfd1903a1..96dc89bd9e 100644 --- a/erts/preloaded/src/socket.erl +++ b/erts/preloaded/src/socket.erl @@ -984,6 +984,7 @@ do_accept(LSockRef, SI, Timeout) -> Socket = #socket{info = SocketInfo, ref = SockRef}, {ok, Socket}; + {error, eagain} -> NewTimeout = next_timeout(TS, Timeout), receive @@ -997,7 +998,11 @@ do_accept(LSockRef, SI, Timeout) -> nif_cancel(LSockRef, accept, AccRef), flush_select_msgs(LSockRef, AccRef), {error, timeout} - end + end; + + {error, _} = ERROR -> + nif_cancel(LSockRef, accept, AccRef), % Just to be on the safe side... + ERROR end. diff --git a/lib/kernel/test/socket_server.erl b/lib/kernel/test/socket_server.erl index d9bbf00e85..8a77b9b3c9 100644 --- a/lib/kernel/test/socket_server.erl +++ b/lib/kernel/test/socket_server.erl @@ -28,10 +28,12 @@ -define(LIB, socket_lib). --record(manager, {peek, acceptor, handler_id, handlers}). --record(acceptor, {socket, manager}). +-record(manager, {socket, peek, acceptors, handler_id, handlers}). +-record(acceptor, {id, socket, manager}). -record(handler, {socket, peek, type, manager}). +-define(NUM_ACCEPTORS, 5). + start() -> start_tcp(). @@ -83,17 +85,13 @@ manager_reply(Pid, Ref, Reply) -> manager_init(Domain, stream = Type, Proto, Peek) -> put(sname, "manager"), - i("try start acceptor"), - case acceptor_start(Domain, Type, Proto) of - {ok, {Pid, MRef}} -> - i("acceptor started"), - manager_loop(#manager{peek = Peek, - acceptor = {Pid, MRef}, - handler_id = 1, - handlers = []}); - {error, Reason} -> - exit({failed_starting_acceptor, Reason}) - end; + i("try start acceptor(s)"), + {Sock, Acceptors} = manager_stream_init(Domain, Type, Proto), + manager_loop(#manager{socket = Sock, + peek = Peek, + acceptors = Acceptors, + handler_id = 1, + handlers = []}); manager_init(Domain, dgram = Type, Proto, Peek) -> put(sname, "manager"), i("try open socket"), @@ -142,7 +140,7 @@ manager_init(Domain, dgram = Type, Proto, Peek) -> handler_continue(Pid), manager_loop(#manager{peek = Peek, handler_id = 2, % Just in case - handlers = [{Pid, MRef, 1}]}); + handlers = [{1, Pid, MRef}]}); {error, SReason} -> e("Failed starting handler: " "~n ~p", [SReason]), @@ -155,35 +153,142 @@ manager_init(Domain, dgram = Type, Proto, Peek) -> end. +manager_stream_init(Domain, Type, Proto) -> + i("try (socket) open"), + Sock = case socket:open(Domain, Type, Proto) of + {ok, S} -> + S; + {error, OReason} -> + throw({open, OReason}) + end, + F = fun(X) -> case socket:getopt(Sock, socket, X) of + {ok, V} -> f("~p", [V]); + {error, R} -> f("error: ~p", [R]) + end + end, + i("(socket) open (~s,~s,~s): " + "~n debug: ~s" + "~n prio: ~s" + "~n => try find (local) address", + [F(domain), F(type), F(protocol), F(debug), F(priority)]), + Addr = which_addr(Domain), + SA = #{family => Domain, + addr => Addr}, + i("found: " + "~n ~p" + "~n => try (socket) bind", [Addr]), + %% ok = socket:setopt(Sock, otp, debug, true), + %% ok = socket:setopt(Sock, socket, debug, 1), %% must have rights!! + Port = case socket:bind(Sock, SA) of + {ok, P} -> + %% ok = socket:setopt(Sock, socket, debug, 0), %% must have rights!! + %% ok = socket:setopt(Sock, otp, debug, false), + P; + {error, BReason} -> + throw({bind, BReason}) + end, + i("bound to: " + "~n ~p" + "~n => try (socket) listen (acceptconn: ~s)", + [Port, F(acceptconn)]), + case socket:listen(Sock) of + ok -> + i("listening (acceptconn: ~s)", + [F(acceptconn)]), + manager_stream_init(Sock, 1, ?NUM_ACCEPTORS, []); + {error, LReason} -> + throw({listen, LReason}) + end. + +which_addr(Domain) -> + Iflist = case inet:getifaddrs() of + {ok, IFL} -> + IFL; + {error, Reason} -> + throw({inet,getifaddrs,Reason}) + end, + which_addr(Domain, Iflist). + +which_addr(_Domain, []) -> + throw(no_address); +which_addr(Domain, [{Name, IFO}|_IFL]) when (Name =/= "lo") -> + which_addr2(Domain, IFO); +which_addr(Domain, [_|IFL]) -> + which_addr(Domain, IFL). + +which_addr2(_, []) -> + throw(no_address); +which_addr2(inet = _Domain, [{addr, Addr}|_IFO]) when (size(Addr) =:= 4) -> + Addr; +which_addr2(inet6 = _Domain, [{addr, Addr}|_IFO]) when (size(Addr) =:= 8) -> + Addr; +which_addr2(Domain, [_|IFO]) -> + which_addr2(Domain, IFO). + + +manager_stream_init(Sock, ID, NumAcceptors, Acc) + when (NumAcceptors > 0) -> + i("try start acceptor"), + case acceptor_start(Sock, ID) of + {ok, {Pid, MRef}} -> + i("acceptor ~w (~p) started", [ID, Pid]), + ?LIB:sleep(5000), + manager_stream_init(Sock, ID+1, NumAcceptors-1, + [{ID, Pid, MRef}|Acc]); + {error, Reason} -> + exit({failed_starting_acceptor, Reason}) + end; +manager_stream_init(Sock, _ID, 0, Acc) -> + %% Req = {kill_acceptor, length(Acc)}, % Last in the queue + %% Req = {kill_acceptor, 3}, % In the "middle" of the queue + %% Req = {kill_acceptor, 2}, % The first in the queue + %% Req = {kill_acceptor, 1}, % Current acceptor + %% Msg = {manager, self(), make_ref(), Req}, + %% erlang:send_after(timer:seconds(10), self(), Msg), + {Sock, lists:reverse(Acc)}. + + manager_loop(M) -> receive {'DOWN', MRef, process, Pid, Reason} -> M2 = manager_handle_down(M, MRef, Pid, Reason), manager_loop(M2); - + {manager, Pid, Ref, Request} -> M2 = manager_handle_request(M, Pid, Ref, Request), manager_loop(M2) end. -manager_handle_down(#manager{acceptor = {Pid, MRef}}, MRef, Pid, Reason) - when (Reason =/= normal) -> - e("acceptor died: " - "~n ~p", [Reason]), - exit({acceptor_died, Reason}); -manager_handle_down(#manager{acceptor = {Pid, MRef}}, MRef, Pid, Reason) -> - exit(Reason); -manager_handle_down(#manager{handlers = Handlers} = M, _MRef, Pid, Reason) -> - if - (Reason =/= normal) -> - e("handler ~p died: " - "~n ~p", [Pid, Reason]); - true -> - i("handler ~p terminated", [Pid]) - end, - Handlers2 = lists:keydelete(Pid, 1, Handlers), - M#manager{handlers = Handlers2}. +manager_handle_down(#manager{acceptors = Acceptors, + handlers = Handlers} = M, MRef, Pid, Reason) -> + case lists:keysearch(Pid, 2, Acceptors) of + {value, {ID, Pid, MRef}} when (Reason =:= normal) -> + i("acceptor ~w exited (normally)", [ID]), + case lists:keydelete(Pid, 2, Acceptors) of + [] -> + %% We are done + i("the last acceptor - we are done"), + exit(normal); + Acceptors2 -> + M#manager{acceptors = Acceptors2} + end; + {value, {ID, Pid, MRef}} -> + e("acceptor ~w crashed: " + "~n ~p", [ID, Reason]), + exit({acceptor_died, Reason}); + + false -> %% handler! + if + (Reason =/= normal) -> + e("handler ~p died: " + "~n ~p", [Pid, Reason]); + true -> + i("handler ~p terminated", [Pid]) + end, + Handlers2 = lists:keydelete(Pid, 2, Handlers), + M#manager{handlers = Handlers2} + end. manager_handle_request(#manager{peek = Peek, @@ -196,7 +301,7 @@ manager_handle_request(#manager{peek = Peek, i("handler ~w started", [HID]), manager_reply(Pid, Ref, {ok, HPid}), M#manager{handler_id = HID+1, - handlers = [{HPid, HMRef, HID}|Handlers]}; + handlers = [{HID, HPid, HMRef}|Handlers]}; {error, Reason} = ERROR -> e("Failed starting new handler: " "~n Sock: ~p" @@ -204,21 +309,50 @@ manager_handle_request(#manager{peek = Peek, manager_reply(Pid, Ref, ERROR), M end; -manager_handle_request(#manager{acceptor = {Pid, MRef}, - handlers = Handlers}, Pid, Ref, +manager_handle_request(#manager{socket = Sock, + acceptors = [{AID, APid, AMRef}]} = M, _Pid, _Ref, + {kill_acceptor, AID}) -> + i("try kill (only remeining) acceptor ~w", [AID]), + socket:setopt(Sock, otp, debug, true), + manager_stop_acceptor(APid, AMRef, AID, kill), + M#manager{acceptors = []}; +manager_handle_request(#manager{socket = Sock, + acceptors = Acceptors} = M, _Pid, _Ref, + {kill_acceptor, AID}) -> + i("try kill acceptor ~w", [AID]), + case lists:keysearch(AID, 1, Acceptors) of + {value, {AID, APid, AMRef}} -> + socket:setopt(Sock, otp, debug, true), + manager_stop_acceptor(APid, AMRef, AID, kill), + Acceptors2 = lists:keydelete(AID, 1, Acceptors), + M#manager{acceptors = Acceptors2}; + false -> + e("no such acceptor"), + M + end; +manager_handle_request(#manager{acceptors = Acceptors, + handlers = Handlers}, Pid, Ref, {stop, Reason}) -> i("stop"), manager_reply(Pid, Ref, ok), manager_stop_handlers(Handlers, Reason), - i("try stop acceptor ~p: ~p", [Pid, Reason]), - erlang:demonitor(MRef, [flush]), - acceptor_stop(Pid, Reason), - i("stop", []), + manager_stop_acceptors(Acceptors, Reason), + i("stopped", []), exit(Reason). +manager_stop_acceptors(Acceptors, Reason) -> + lists:foreach(fun({ID,P,M}) -> + manager_stop_acceptor(P, M, ID, Reason) + end, Acceptors). + +manager_stop_acceptor(Pid, MRef, ID, Reason) -> + i("try stop acceptor ~w (~p): ~p", [ID, Pid, Reason]), + erlang:demonitor(MRef, [flush]), + acceptor_stop(Pid, Reason), + ok. manager_stop_handlers(Handlers, Reason) -> - lists:foreach(fun({P,M,ID}) -> + lists:foreach(fun({ID,P,M}) -> manager_stop_handler(P, M, ID, Reason) end, Handlers). @@ -232,10 +366,10 @@ manager_stop_handler(Pid, MRef, ID, Reason) -> %% ========================================================================= -acceptor_start(Domain, Type, Proto) -> +acceptor_start(Sock, ID) -> Self = self(), A = {Pid, _} = spawn_monitor(fun() -> - acceptor_init(Self, Domain, Type, Proto) + acceptor_init(Self, Sock, ID) end), receive {acceptor, Pid, ok} -> @@ -258,93 +392,12 @@ acceptor_stop(Pid, _Reason) -> %% reply(acceptor, Pid, Ref, Reply). -acceptor_init(Manager, Domain, Type, Proto) -> - put(sname, "acceptor"), - try acceptor_do_init(Domain, Type, Proto) of - Sock -> - Manager ! {acceptor, self(), ok}, - acceptor_loop(#acceptor{manager = Manager, - socket = Sock}) - catch - throw:E:P -> - e("Failed initiate: " - "~n Error: ~p" - "~n Path: ~p", [E, P]), - Manager ! {acceptor, self(), {error, {catched, E, P}}} - end. - -acceptor_do_init(Domain, Type, Proto) -> - i("try (socket) open"), - Sock = case socket:open(Domain, Type, Proto) of - {ok, S} -> - S; - {error, OReason} -> - throw({open, OReason}) - end, - F = fun(X) -> case socket:getopt(Sock, socket, X) of - {ok, V} -> f("~p", [V]); - {error, R} -> f("error: ~p", [R]) - end - end, - i("(socket) open (~s,~s,~s): " - "~n debug: ~s" - "~n prio: ~s" - "~n => try find (local) address", - [F(domain), F(type), F(protocol), F(debug), F(priority)]), - Addr = which_addr(Domain), - SA = #{family => Domain, - addr => Addr}, - i("found: " - "~n ~p" - "~n => try (socket) bind", [Addr]), - %% ok = socket:setopt(Sock, otp, debug, true), - %% ok = socket:setopt(Sock, socket, debug, 1), %% must have rights!! - Port = case socket:bind(Sock, SA) of - {ok, P} -> - %% ok = socket:setopt(Sock, socket, debug, 0), %% must have rights!! - %% ok = socket:setopt(Sock, otp, debug, false), - P; - {error, BReason} -> - throw({bind, BReason}) - end, - i("bound to: " - "~n ~p" - "~n => try (socket) listen (acceptconn: ~s)", - [Port, F(acceptconn)]), - case socket:listen(Sock) of - ok -> - i("listening (acceptconn: ~s)", - [F(acceptconn)]), - Sock; - {error, LReason} -> - throw({listen, LReason}) - end. - -which_addr(Domain) -> - Iflist = case inet:getifaddrs() of - {ok, IFL} -> - IFL; - {error, Reason} -> - throw({inet,getifaddrs,Reason}) - end, - which_addr(Domain, Iflist). - -which_addr(_Domain, []) -> - throw(no_address); -which_addr(Domain, [{Name, IFO}|_IFL]) when (Name =/= "lo") -> - which_addr2(Domain, IFO); -which_addr(Domain, [_|IFL]) -> - which_addr(Domain, IFL). - -which_addr2(_, []) -> - throw(no_address); -which_addr2(inet = _Domain, [{addr, Addr}|_IFO]) when (size(Addr) =:= 4) -> - Addr; -which_addr2(inet6 = _Domain, [{addr, Addr}|_IFO]) when (size(Addr) =:= 8) -> - Addr; -which_addr2(Domain, [_|IFO]) -> - which_addr2(Domain, IFO). - +acceptor_init(Manager, Sock, ID) -> + put(sname, f("acceptor[~w]", [ID])), + Manager ! {acceptor, self(), ok}, + acceptor_loop(#acceptor{id = ID, + manager = Manager, + socket = Sock}). acceptor_loop(#acceptor{socket = LSock} = A) -> i("try accept"), @@ -600,6 +653,8 @@ send(#handler{socket = Sock, type = dgram}, Msg, Dest) -> f(F, A) -> ?LIB:f(F, A). +e(F) -> + e(F, []). e(F, A) -> ?LIB:e(F, A). -- cgit v1.2.3 From 0694dfbc731c109fa19bacdc86fe661f14bc1a12 Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Tue, 17 Jul 2018 16:43:34 +0200 Subject: [socket-doc-nif] Add preliminary users guide Added a very preliminary users guide. Currently where we place the socket option tables. OTP-14831 --- erts/doc/src/Makefile | 1 + erts/doc/src/part.xml | 1 + erts/doc/src/socket_usage.xml | 451 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 453 insertions(+) create mode 100644 erts/doc/src/socket_usage.xml diff --git a/erts/doc/src/Makefile b/erts/doc/src/Makefile index 9a2750b751..bd2528c536 100644 --- a/erts/doc/src/Makefile +++ b/erts/doc/src/Makefile @@ -82,6 +82,7 @@ XML_CHAPTER_FILES = \ driver.xml \ absform.xml \ inet_cfg.xml \ + socket_usage.xml \ erl_ext_dist.xml \ erl_dist_protocol.xml \ communication.xml \ diff --git a/erts/doc/src/part.xml b/erts/doc/src/part.xml index 05e9a24af8..f0b8a00b90 100644 --- a/erts/doc/src/part.xml +++ b/erts/doc/src/part.xml @@ -42,6 +42,7 @@ + diff --git a/erts/doc/src/socket_usage.xml b/erts/doc/src/socket_usage.xml new file mode 100644 index 0000000000..b4880c3989 --- /dev/null +++ b/erts/doc/src/socket_usage.xml @@ -0,0 +1,451 @@ + + + + +
+ + 20182018 + Ericsson AB. All Rights Reserved. + + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + + + Socket Usage + Micael Karlberg + + 2018-07-17 + PA1 + socket_usage.xml +
+ +
+ Introduction +

The socket interface (module) is basically an "thin" layer on top of + the OS socket interface. It is assumed that, unless you have special needs, + gen_[tcp|udp|sctp] should be sufficent.

+
+ +
+ Socket Options + +

Options for level otp:

+ + + Option Name + Value Type + Set + Get + Other + + + debug + boolean() + yes + yes + none + + + iow + boolean() + yes + yes + none + + + controlling_process + pid() + yes + yes + none + +
+ +

Options for level socket:

+ + + Option Name + Value Type + Set + Get + Other + + + acceptcon + boolean() + no + yes + none + + + bindtodevice + string() + yes + yes + none + + + broadcast + boolean() + yes + yes + type = dgram + + + debug + integer() + yes + yes + may require admin capability + + + domain + domain() + no + yes + none + + + dontroute + boolean() + yes + yes + none + + + keepalive + boolean() + yes + yes + none + + + linger + abort | linger() + yes + yes + none + + + oobinline + boolean() + yes + yes + none + + + peep_off + integer() + yes + yes + domain = local (unix) + + + priority + integer() + yes + yes + none + + + protocol + protocol() + no + yes + none + + + rcvbuf + integer() + yes + yes + none + + + reuseaddr + boolean() + yes + yes + none + + + reuseport + boolean() + yes + yes + domain = inet | inet6 + + + sndbuf + integer() + yes + yes + none + +
+ +

Options for level ip:

+ + + Option Name + Value Type + Set + Get + Other + + + add_membership + ip_mreq() + yes + no + none + + + add_source_membership + ip_mreq_source() + yes + no + none + + + block_source + ip_mreq_source() + yes + no + none + + + drop_membership + ip_mreq() + yes + no + none + + + drop_source_membership + ip_mreq_source() + yes + no + none + + + minttl + integer() + yes + yes + type = raw + + + mtu + integer() + no + yes + type = raw + + + mtu_discover + ip_mtu_discover() + yes + yes + none + + + multicast_all + boolean() + yes + yes + none + + + multicast_if + any | ip4_address() + yes + yes + none + + + multicast_loop + boolean() + yes + yes + none + + + multicast_ttl + 0..255 + yes + yes + none + + + nodefrag + boolean() + yes + yes + type = raw + + + recvif + boolean() + yes + yes + type = dgram | raw + + + recvttl + boolean() + yes + yes + type =/= stream + + + router_alert + integer() + yes + yes + type = raw + + + tos + ip_tos() + yes + yes + none + + + tos + integer() + yes + yes + none + + + unblock_source + ip_mreq_source() + yes + no + none + +
+ +

Options for level ipv6:

+ + + Option Name + Value Type + Set + Get + Other + + + add_membership + ipv6_mreq() + yes + no + none + + + drop_membership + ipv6_mreq() + yes + no + none + + + hoplimit + boolean() + yes + no + type = dgram | raw + +
+ +

Options for level tcp:

+ + + Option Name + Value Type + Set + Get + Other + + + congestion + string() + yes + yes + none + + + maxseg + integer() + yes + yes + type = stream, protocol = tcp + + + nodelay + boolean() + yes + yes + type = stream, protocol = tcp + +
+ +

Options for level udp:

+ + + Option Name + Value Type + Set + Get + Other + + + cork + boolean() + yes + yes + type = dgram, protocol = udp + +
+ +

Options for level sctp:

+ + + Option Name + Value Type + Set + Get + Other + + + autoclose + integer() + yes + yes + protocol = sctp + + + nodelay + boolean() + yes + yes + protocol = sctp + +
+ +
+
+ -- cgit v1.2.3 From 180fd01ba04ff24e5b9cb8ab0098342f1d443b17 Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Wed, 18 Jul 2018 09:30:34 +0200 Subject: [socket-nif] Preliminary (tcp) socket option updates Added default for code un-implemented TCP socket options. OTP-14831 --- erts/preloaded/ebin/socket.beam | Bin 46540 -> 51376 bytes erts/preloaded/src/socket.erl | 155 +++++++++++++++++++++++++++++++++++++++- 2 files changed, 152 insertions(+), 3 deletions(-) diff --git a/erts/preloaded/ebin/socket.beam b/erts/preloaded/ebin/socket.beam index c24dc40e10..79224124e5 100644 Binary files a/erts/preloaded/ebin/socket.beam and b/erts/preloaded/ebin/socket.beam differ diff --git a/erts/preloaded/src/socket.erl b/erts/preloaded/src/socket.erl index 96dc89bd9e..64e0e6ce5b 100644 --- a/erts/preloaded/src/socket.erl +++ b/erts/preloaded/src/socket.erl @@ -291,7 +291,7 @@ ttl | unblock_source. -type ipv6_socket_option() :: - addform | + addrform | add_membership | authhdr | auth_level | @@ -327,8 +327,17 @@ -type tcp_socket_option() :: congestion | cork | + info | + keepidle | + keepintvl | + keepcnt | maxseg | - nodelay. + md5sig | + nodelay | + noopt | + nopush | + syncnt | + user_timeout. -type udp_socket_option() :: cork. @@ -2412,13 +2421,77 @@ enc_sockopt_key(ip = L, UnknownOpt, _Dir, _D, _T, _P) -> unknown({L, UnknownOpt}); %% IPv6 socket options +enc_sockopt_key(ipv6 = L, addrform = Opt, set = _Dir, _D, _T, _P) -> + not_supported({L, Opt}); enc_sockopt_key(ipv6, add_membership = _Opt, set = _Dir, _D, _T, _P) -> ?SOCKET_OPT_IPV6_ADD_MEMBERSHIP; +enc_sockopt_key(ipv6 = L, authhdr = Opt, set = _Dir, _D, T, _P) + when ((T =:= dgram) orelse (T =:= raw)) -> + not_supported({L, Opt}); +enc_sockopt_key(ipv6 = L, auth_level = Opt, _Dir, _D, _T, _P) -> + not_supported({L, Opt}); +enc_sockopt_key(ipv6 = L, checksum = Opt, _Dir, _D, _T, _P) -> + not_supported({L, Opt}); enc_sockopt_key(ipv6, drop_membership = _Opt, set = _Dir, _D, _T, _P) -> ?SOCKET_OPT_IPV6_DROP_MEMBERSHIP; -enc_sockopt_key(ipv6, hoplimit = _Opt, _Dir, _D, T, _P) +enc_sockopt_key(ipv6 = L, dstopts = Opt, set = _Dir, _D, T, _P) + when (T =:= dgram) orelse (T =:= raw) -> + not_supported({L, Opt}); +enc_sockopt_key(ipv6 = L, esp_trans_level = Opt, _Dir, _D, _T, _P) -> + not_supported({L, Opt}); +enc_sockopt_key(ipv6 = L, esp_network_level = Opt, _Dir, _D, _T, _P) -> + not_supported({L, Opt}); +enc_sockopt_key(ipv6 = L, flowinfo = Opt, set = _Dir, _D, T, _P) + when (T =:= dgram) orelse (T =:= raw) -> + not_supported({L, Opt}); +enc_sockopt_key(ipv6, hoplimit = _Opt, _Dir, set = _D, T, _P) when (T =:= dgram) orelse (T =:= raw) -> ?SOCKET_OPT_IPV6_HOPLIMIT; +enc_sockopt_key(ipv6 = L, hopopts = Opt, set = _Dir, _D, T, _P) + when ((T =:= dgram) orelse (T =:= raw)) -> + not_supported({L, Opt}); +enc_sockopt_key(ipv6 = L, ipcomp_level = Opt, _Dir, _D, _T, _P) -> + not_supported({L, Opt}); +enc_sockopt_key(ipv6 = L, join_group = Opt, _Dir, _D, _T, _P) -> + not_supported({L, Opt}); +enc_sockopt_key(ipv6 = L, leave_group = Opt, _Dir, _D, _T, _P) -> + not_supported({L, Opt}); +enc_sockopt_key(ipv6 = L, mtu = Opt, _Dir, _D, _T, _P) -> + not_supported({L, Opt}); +enc_sockopt_key(ipv6 = L, mtu_discover = Opt, _Dir, _D, _T, _P) -> + not_supported({L, Opt}); +enc_sockopt_key(ipv6 = L, multicast_hops = Opt, _Dir, _D, _T, _P) -> + not_supported({L, Opt}); +enc_sockopt_key(ipv6 = L, multicast_if = Opt, _Dir, _D, T, _P) + when (T =:= dgram) orelse (T =:= raw) -> + not_supported({L, Opt}); +enc_sockopt_key(ipv6 = L, multicast_loop = Opt, _Dir, _D, _T, _P) -> + not_supported({L, Opt}); +enc_sockopt_key(ipv6 = L, portrange = Opt, _Dir, _D, _T, _P) -> + not_supported({L, Opt}); +enc_sockopt_key(ipv6 = L, pktinfo = Opt, _Dir, _D, _T, _P) -> + not_supported({L, Opt}); +enc_sockopt_key(ipv6 = L, pktoptions = Opt, _Dir, _D, _T, _P) -> + not_supported({L, Opt}); +enc_sockopt_key(ipv6 = L, recverr = Opt, _Dir, _D, _T, _P) -> + not_supported({L, Opt}); +enc_sockopt_key(ipv6 = L, recvpktinfo = Opt, _Dir, _D, _T, _P) -> + not_supported({L, Opt}); +enc_sockopt_key(ipv6 = L, recvtclass = Opt, _Dir, _D, _T, _P) -> + not_supported({L, Opt}); +enc_sockopt_key(ipv6 = L, router_alert = Opt, _Dir, _D, _T, _P) -> + not_supported({L, Opt}); +enc_sockopt_key(ipv6 = L, rthdr = Opt, set = _Dir, _D, T, _P) + when ((T =:= dgram) orelse (T =:= raw)) -> + not_supported({L, Opt}); +enc_sockopt_key(ipv6 = L, tclass = Opt, _Dir, _D, _T, _P) -> + not_supported({L, Opt}); +enc_sockopt_key(ipv6 = L, unicast_hops = Opt, _Dir, _D, _T, _P) -> + not_supported({L, Opt}); +enc_sockopt_key(ipv6 = L, use_min_mtu = Opt, _Dir, _D, _T, _P) -> + not_supported({L, Opt}); +enc_sockopt_key(ipv6 = L, v6only = Opt, _Dir, _D, _T, _P) -> + not_supported({L, Opt}); enc_sockopt_key(ipv6 = L, UnknownOpt, _Dir, _D, _T, _P) -> unknown({L, UnknownOpt}); @@ -2429,10 +2502,26 @@ enc_sockopt_key(tcp, congestion = _Opt, _Dir, _D, _T, _P) -> ?SOCKET_OPT_TCP_CONGESTION; enc_sockopt_key(tcp = L, cork = Opt, _Dir, _D, _T, _P) -> not_supported({L, Opt}); +enc_sockopt_key(tcp = L, keepidle = Opt, _Dir, _D, _T, _P) -> + not_supported({L, Opt}); +enc_sockopt_key(tcp = L, keepintvl = Opt, _Dir, _D, _T, _P) -> + not_supported({L, Opt}); +enc_sockopt_key(tcp = L, keepcnt = Opt, _Dir, _D, _T, _P) -> + not_supported({L, Opt}); enc_sockopt_key(tcp, maxseg = _Opt, _Dir, _D, _T, _P) -> ?SOCKET_OPT_TCP_MAXSEG; +enc_sockopt_key(tcp = L, md5sig = Opt, _Dir, _D, _T, _P) -> + not_supported({L, Opt}); enc_sockopt_key(tcp, nodelay = _Opt, _Dir, _D, _T, _P) -> ?SOCKET_OPT_TCP_NODELAY; +enc_sockopt_key(tcp = L, noopt = Opt, _Dir, _D, _T, _P) -> + not_supported({L, Opt}); +enc_sockopt_key(tcp = L, nopush = Opt, _Dir, _D, _T, _P) -> + not_supported({L, Opt}); +enc_sockopt_key(tcp = L, syncnt = Opt, _Dir, _D, _T, _P) -> % Only set? 1..255 + not_supported({L, Opt}); +enc_sockopt_key(tcp = L, user_timeout = Opt, _Dir, _D, _T, _P) -> + not_supported({L, Opt}); enc_sockopt_key(tcp = L, UnknownOpt, _Dir, _D, _T, _P) -> unknown({L, UnknownOpt}); @@ -2443,10 +2532,70 @@ enc_sockopt_key(udp = L, UnknownOpt, _Dir, _D, _T, _P) -> unknown({L, UnknownOpt}); %% SCTP socket options +enc_sockopt_key(sctp = L, adaption_layer = Opt, _Dir, _D, _T, _P) -> + not_supported({L, Opt}); +enc_sockopt_key(sctp = L, associnfo = Opt, _Dir, _D, _T, _P) -> + not_supported({L, Opt}); +enc_sockopt_key(sctp = L, auth_active_key = Opt, _Dir, _D, _T, _P) -> + not_supported({L, Opt}); +enc_sockopt_key(sctp = L, auth_asconf = Opt, _Dir, _D, _T, _P) -> + not_supported({L, Opt}); +enc_sockopt_key(sctp = L, auth_chunk = Opt, _Dir, _D, _T, _P) -> + not_supported({L, Opt}); +enc_sockopt_key(sctp = L, auth_key = Opt, _Dir, _D, _T, _P) -> + not_supported({L, Opt}); +enc_sockopt_key(sctp = L, auth_delete_key = Opt, _Dir, _D, _T, _P) -> + not_supported({L, Opt}); enc_sockopt_key(sctp, autoclose = _Opt, _Dir, _D, _T, _P) -> ?SOCKET_OPT_SCTP_AUTOCLOSE; +enc_sockopt_key(sctp = L, context = Opt, _Dir, _D, _T, _P) -> + not_supported({L, Opt}); +enc_sockopt_key(sctp = L, default_send_params = Opt, _Dir, _D, _T, _P) -> + not_supported({L, Opt}); +enc_sockopt_key(sctp = L, delayed_ack_time = Opt, _Dir, _D, _T, _P) -> + not_supported({L, Opt}); +enc_sockopt_key(sctp = L, disable_fragments = Opt, _Dir, _D, _T, _P) -> + not_supported({L, Opt}); +enc_sockopt_key(sctp = L, hmac_ident = Opt, _Dir, _D, _T, _P) -> + not_supported({L, Opt}); +enc_sockopt_key(sctp = L, events = Opt, _Dir, _D, _T, _P) -> + not_supported({L, Opt}); +enc_sockopt_key(sctp = L, explicit_eor = Opt, _Dir, _D, _T, _P) -> + not_supported({L, Opt}); +enc_sockopt_key(sctp = L, fragment_interleave = Opt, _Dir, _D, _T, _P) -> + not_supported({L, Opt}); +enc_sockopt_key(sctp = L, get_peer_addr_info = Opt, _Dir, _D, _T, _P) -> + not_supported({L, Opt}); +enc_sockopt_key(sctp = L, initmsg = Opt, _Dir, _D, _T, _P) -> + not_supported({L, Opt}); +enc_sockopt_key(sctp = L, i_want_mapped_v4_addr = Opt, _Dir, _D, _T, _P) -> + not_supported({L, Opt}); +enc_sockopt_key(sctp = L, local_auth_chunks = Opt, _Dir, _D, _T, _P) -> + not_supported({L, Opt}); +enc_sockopt_key(sctp = L, maxseg = Opt, _Dir, _D, _T, _P) -> + not_supported({L, Opt}); +enc_sockopt_key(sctp = L, maxburst = Opt, _Dir, _D, _T, _P) -> + not_supported({L, Opt}); enc_sockopt_key(sctp, nodelay = _Opt, _Dir, _D, _T, _P) -> ?SOCKET_OPT_SCTP_NODELAY; +enc_sockopt_key(sctp = L, partial_delivery_point = Opt, _Dir, _D, _T, _P) -> + not_supported({L, Opt}); +enc_sockopt_key(sctp = L, peer_addr_params = Opt, _Dir, _D, _T, _P) -> + not_supported({L, Opt}); +enc_sockopt_key(sctp = L, peer_auth_chunks = Opt, _Dir, _D, _T, _P) -> + not_supported({L, Opt}); +enc_sockopt_key(sctp = L, primary_addr = Opt, _Dir, _D, _T, _P) -> + not_supported({L, Opt}); +enc_sockopt_key(sctp = L, reset_streams = Opt, _Dir, _D, _T, _P) -> + not_supported({L, Opt}); +enc_sockopt_key(sctp = L, rtoinfo = Opt, _Dir, _D, _T, _P) -> + not_supported({L, Opt}); +enc_sockopt_key(sctp = L, set_peer_primary_addr = Opt, _Dir, _D, _T, _P) -> + not_supported({L, Opt}); +enc_sockopt_key(sctp = L, status = Opt, _Dir, _D, _T, _P) -> + not_supported({L, Opt}); +enc_sockopt_key(sctp = L, use_exp_recvinfo = Opt, _Dir, _D, _T, _P) -> + not_supported({L, Opt}); enc_sockopt_key(sctp = L, UnknownOpt, _Dir, _D, _T, _P) -> unknown({L, UnknownOpt}); -- cgit v1.2.3 From 8d759e6f00b1cf834fe21654de3f53df706e8c0f Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Wed, 18 Jul 2018 10:23:45 +0200 Subject: [socket-nif] Add support for socket (level ipv6) option v6only Added support for the IPv6 option V6ONLY. OTP-14831 --- erts/emulator/nifs/common/socket_nif.c | 112 +++++++++++++++++++++++++++++++++ erts/preloaded/ebin/socket.beam | Bin 51376 -> 51436 bytes erts/preloaded/src/socket.erl | 8 ++- 3 files changed, 117 insertions(+), 3 deletions(-) diff --git a/erts/emulator/nifs/common/socket_nif.c b/erts/emulator/nifs/common/socket_nif.c index 224dcc9ff6..15d43da9da 100644 --- a/erts/emulator/nifs/common/socket_nif.c +++ b/erts/emulator/nifs/common/socket_nif.c @@ -388,6 +388,7 @@ typedef union { #define SOCKET_OPT_IPV6_ADD_MEMBERSHIP 2 #define SOCKET_OPT_IPV6_DROP_MEMBERSHIP 6 #define SOCKET_OPT_IPV6_HOPLIMIT 12 +#define SOCKET_OPT_IPV6_V6ONLY 33 #define SOCKET_OPT_TCP_CONGESTION 1 #define SOCKET_OPT_TCP_CORK 2 @@ -1007,6 +1008,11 @@ static ERL_NIF_TERM nsetopt_lvl_ipv6_hoplimit(ErlNifEnv* env, SocketDescriptor* descP, ERL_NIF_TERM eVal); #endif +#if defined(IPV6_V6ONLY) +static ERL_NIF_TERM nsetopt_lvl_ipv6_v6only(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); +#endif #if defined(IPV6_ADD_MEMBERSHIP) || defined(IPV6_DROP_MEMBERSHIP) static ERL_NIF_TERM nsetopt_lvl_ipv6_update_membership(ErlNifEnv* env, @@ -1221,6 +1227,11 @@ static ERL_NIF_TERM ngetopt_lvl_ipv6(ErlNifEnv* env, static ERL_NIF_TERM ngetopt_lvl_ipv6_hoplimit(ErlNifEnv* env, SocketDescriptor* descP); #endif +#if defined(IPV6_V6ONLY) +static ERL_NIF_TERM ngetopt_lvl_ipv6_v6only(ErlNifEnv* env, + SocketDescriptor* descP); +#endif + #endif // defined(SOL_IPV6) static ERL_NIF_TERM ngetopt_lvl_tcp(ErlNifEnv* env, SocketDescriptor* descP, @@ -4235,6 +4246,11 @@ ERL_NIF_TERM nsetopt_lvl_ip(ErlNifEnv* env, { ERL_NIF_TERM result; + SSDBG( descP, + ("SOCKET", "nsetopt_lvl_ip -> entry with" + "\r\n opt: %d" + "\r\n", eOpt) ); + switch (eOpt) { #if defined(IP_ADD_MEMBERSHIP) case SOCKET_OPT_IP_ADD_MEMBERSHIP: @@ -4920,6 +4936,11 @@ ERL_NIF_TERM nsetopt_lvl_ipv6(ErlNifEnv* env, { ERL_NIF_TERM result; + SSDBG( descP, + ("SOCKET", "nsetopt_lvl_ipv6 -> entry with" + "\r\n opt: %d" + "\r\n", eOpt) ); + switch (eOpt) { #if defined(IPV6_ADD_MEMBERSHIP) case SOCKET_OPT_IPV6_ADD_MEMBERSHIP: @@ -4939,6 +4960,12 @@ ERL_NIF_TERM nsetopt_lvl_ipv6(ErlNifEnv* env, break; #endif +#if defined(IPV6_V6ONLY) + case SOCKET_OPT_IPV6_V6ONLY: + result = nsetopt_lvl_ipv6_v6only(env, descP, eVal); + break; +#endif + default: result = esock_make_error(env, esock_atom_einval); break; @@ -4983,6 +5010,17 @@ ERL_NIF_TERM nsetopt_lvl_ipv6_hoplimit(ErlNifEnv* env, #endif +#if defined(IPV6_V6ONLY) +static +ERL_NIF_TERM nsetopt_lvl_ipv6_v6only(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) +{ + return nsetopt_bool_opt(env, descP, SOL_IPV6, IPV6_V6ONLY, eVal); +} +#endif + + #if defined(IPV6_ADD_MEMBERSHIP) || defined(IPV6_DROP_MEMBERSHIP) static ERL_NIF_TERM nsetopt_lvl_ipv6_update_membership(ErlNifEnv* env, @@ -5046,6 +5084,11 @@ ERL_NIF_TERM nsetopt_lvl_tcp(ErlNifEnv* env, { ERL_NIF_TERM result; + SSDBG( descP, + ("SOCKET", "nsetopt_lvl_tcp -> entry with" + "\r\n opt: %d" + "\r\n", eOpt) ); + switch (eOpt) { #if defined(TCP_CONGESTION) case SOCKET_OPT_TCP_CONGESTION: @@ -5126,6 +5169,11 @@ ERL_NIF_TERM nsetopt_lvl_udp(ErlNifEnv* env, { ERL_NIF_TERM result; + SSDBG( descP, + ("SOCKET", "nsetopt_lvl_udp -> entry with" + "\r\n opt: %d" + "\r\n", eOpt) ); + switch (eOpt) { #if defined(UDP_CORK) case SOCKET_OPT_UDP_CORK: @@ -5168,6 +5216,11 @@ ERL_NIF_TERM nsetopt_lvl_sctp(ErlNifEnv* env, { ERL_NIF_TERM result; + SSDBG( descP, + ("SOCKET", "nsetopt_lvl_sctp -> entry with" + "\r\n opt: %d" + "\r\n", eOpt) ); + switch (eOpt) { #if defined(SCTP_AUTOCLOSE) case SOCKET_OPT_SCTP_AUTOCLOSE: @@ -5557,6 +5610,11 @@ ERL_NIF_TERM ngetopt(ErlNifEnv* env, result = ngetopt_level(env, descP, level, eOpt); } + SSDBG( descP, + ("SOCKET", "ngetopt -> done when" + "\r\n result: %T" + "\r\n", result) ); + return result; } @@ -5629,6 +5687,12 @@ ERL_NIF_TERM ngetopt_native(ErlNifEnv* env, uint16_t valueType; SOCKOPTLEN_T valueSz; + SSDBG( descP, + ("SOCKET", "ngetopt_native -> entry with" + "\r\n level: %d" + "\r\n eOpt: %d" + "\r\n", level, eOpt) ); + /* * We should really make it possible to specify common specific types, * such as integer or boolean (instead of the size)... @@ -5654,6 +5718,11 @@ ERL_NIF_TERM ngetopt_native(ErlNifEnv* env, result = esock_make_error(env, esock_atom_einval); } + SSDBG( descP, + ("SOCKET", "ngetopt_native -> done when" + "\r\n result: %T" + "\r\n", result) ); + return result; } @@ -5668,6 +5737,13 @@ ERL_NIF_TERM ngetopt_native_unspec(ErlNifEnv* env, ERL_NIF_TERM result = enif_make_badarg(env); int res; + SSDBG( descP, + ("SOCKET", "ngetopt_native_unspec -> entry with" + "\r\n level: %d" + "\r\n opt: %d" + "\r\n valueSz: %d" + "\r\n", level, opt, valueSz) ); + if (valueSz == 0) { res = sock_getopt(descP->sock, level, opt, NULL, NULL); if (res != 0) @@ -5695,6 +5771,11 @@ ERL_NIF_TERM ngetopt_native_unspec(ErlNifEnv* env, } } + SSDBG( descP, + ("SOCKET", "ngetopt_native_unspec -> done when" + "\r\n result: %T" + "\r\n", result) ); + return result; } @@ -5754,6 +5835,11 @@ ERL_NIF_TERM ngetopt_level(ErlNifEnv* env, break; } + SSDBG( descP, + ("SOCKET", "ngetopt_level -> done when" + "\r\n result: %T" + "\r\n", result) ); + return result; } @@ -6599,6 +6685,11 @@ ERL_NIF_TERM ngetopt_lvl_ipv6(ErlNifEnv* env, { ERL_NIF_TERM result; + SSDBG( descP, + ("SOCKET", "ngetopt_lvl_ipv6 -> entry with" + "\r\n eOpt: %d" + "\r\n", eOpt) ); + switch (eOpt) { #if defined(IPV6_HOPLIMIT) case SOCKET_OPT_IPV6_HOPLIMIT: @@ -6606,11 +6697,22 @@ ERL_NIF_TERM ngetopt_lvl_ipv6(ErlNifEnv* env, break; #endif +#if defined(IPV6_V6ONLY) + case SOCKET_OPT_IPV6_V6ONLY: + result = ngetopt_lvl_ipv6_v6only(env, descP); + break; +#endif + default: result = esock_make_error(env, esock_atom_einval); break; } + SSDBG( descP, + ("SOCKET", "ngetopt_lvl_ipv6 -> done when" + "\r\n result: %T" + "\r\n", result) ); + return result; } @@ -6625,6 +6727,16 @@ ERL_NIF_TERM ngetopt_lvl_ipv6_hoplimit(ErlNifEnv* env, #endif +#if defined(IPV6_V6ONLY) +static +ERL_NIF_TERM ngetopt_lvl_ipv6_v6only(ErlNifEnv* env, + SocketDescriptor* descP) +{ + return ngetopt_bool_opt(env, descP, SOL_IPV6, IPV6_V6ONLY); +} +#endif + + #endif // defined(SOL_IPV6) diff --git a/erts/preloaded/ebin/socket.beam b/erts/preloaded/ebin/socket.beam index 79224124e5..307a94be4e 100644 Binary files a/erts/preloaded/ebin/socket.beam and b/erts/preloaded/ebin/socket.beam differ diff --git a/erts/preloaded/src/socket.erl b/erts/preloaded/src/socket.erl index 64e0e6ce5b..3e31326759 100644 --- a/erts/preloaded/src/socket.erl +++ b/erts/preloaded/src/socket.erl @@ -602,7 +602,7 @@ %% -define(SOCKET_OPT_IPV6_TCLASS, 30). %% -define(SOCKET_OPT_IPV6_UNICAST_HOPS, 31). %% -define(SOCKET_OPT_IPV6_USE_MIN_MTU, 32). -%% -define(SOCKET_OPT_IPV6_V6ONLY, 33). +-define(SOCKET_OPT_IPV6_V6ONLY, 33). -define(SOCKET_OPT_TCP_CONGESTION, 1). -define(SOCKET_OPT_TCP_CORK, 2). @@ -2105,6 +2105,8 @@ enc_setopt_value(ipv6, drop_membership, #{multiaddr := MA, enc_setopt_value(ipv6, hoplimit, V, _D, T, _P) when is_boolean(V) andalso ((T =:= dgram) orelse (T =:= raw)) -> V; +enc_setopt_value(ipv6, v6only, V, _D, _T, _P) when is_boolean(V) -> + V; enc_setopt_value(ipv6 = L, Opt, V, _D, _T, _P) -> not_supported({L, Opt, V}); @@ -2490,8 +2492,8 @@ enc_sockopt_key(ipv6 = L, unicast_hops = Opt, _Dir, _D, _T, _P) -> not_supported({L, Opt}); enc_sockopt_key(ipv6 = L, use_min_mtu = Opt, _Dir, _D, _T, _P) -> not_supported({L, Opt}); -enc_sockopt_key(ipv6 = L, v6only = Opt, _Dir, _D, _T, _P) -> - not_supported({L, Opt}); +enc_sockopt_key(ipv6 = _L, v6only = _Opt, _Dir, _D, _T, _P) -> + ?SOCKET_OPT_IPV6_V6ONLY; enc_sockopt_key(ipv6 = L, UnknownOpt, _Dir, _D, _T, _P) -> unknown({L, UnknownOpt}); -- cgit v1.2.3 From ebd626e7b4259bdfb4ddb34ce2d298d0feb0a1c8 Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Wed, 18 Jul 2018 11:33:50 +0200 Subject: [socket-nif] Add support for socket (level ipv6) option mtu Added support for the VPv6 socket option MTU. OTP-14831. --- erts/doc/src/socket_usage.xml | 14 +++++++++++ erts/emulator/nifs/common/socket_nif.c | 44 +++++++++++++++++++++++++++++++++ erts/preloaded/ebin/socket.beam | Bin 51436 -> 51480 bytes erts/preloaded/src/socket.erl | 8 +++--- lib/kernel/test/socket_client.erl | 2 +- lib/kernel/test/socket_server.erl | 30 ++++++++++++++++++---- 6 files changed, 89 insertions(+), 9 deletions(-) diff --git a/erts/doc/src/socket_usage.xml b/erts/doc/src/socket_usage.xml index b4880c3989..cdd98090e8 100644 --- a/erts/doc/src/socket_usage.xml +++ b/erts/doc/src/socket_usage.xml @@ -369,6 +369,20 @@ no type = dgram | raw + + mtu + boolean() + yes + yes + Get: Only after the socket has been connected + + + v6only + boolean() + yes + no + none +

Options for level tcp:

diff --git a/erts/emulator/nifs/common/socket_nif.c b/erts/emulator/nifs/common/socket_nif.c index 15d43da9da..0222d58e6d 100644 --- a/erts/emulator/nifs/common/socket_nif.c +++ b/erts/emulator/nifs/common/socket_nif.c @@ -388,6 +388,7 @@ typedef union { #define SOCKET_OPT_IPV6_ADD_MEMBERSHIP 2 #define SOCKET_OPT_IPV6_DROP_MEMBERSHIP 6 #define SOCKET_OPT_IPV6_HOPLIMIT 12 +#define SOCKET_OPT_IPV6_MTU 17 #define SOCKET_OPT_IPV6_V6ONLY 33 #define SOCKET_OPT_TCP_CONGESTION 1 @@ -1008,6 +1009,11 @@ static ERL_NIF_TERM nsetopt_lvl_ipv6_hoplimit(ErlNifEnv* env, SocketDescriptor* descP, ERL_NIF_TERM eVal); #endif +#if defined(IPV6_MTU) +static ERL_NIF_TERM nsetopt_lvl_ipv6_mtu(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); +#endif #if defined(IPV6_V6ONLY) static ERL_NIF_TERM nsetopt_lvl_ipv6_v6only(ErlNifEnv* env, SocketDescriptor* descP, @@ -1227,12 +1233,17 @@ static ERL_NIF_TERM ngetopt_lvl_ipv6(ErlNifEnv* env, static ERL_NIF_TERM ngetopt_lvl_ipv6_hoplimit(ErlNifEnv* env, SocketDescriptor* descP); #endif +#if defined(IPV6_MTU) +static ERL_NIF_TERM ngetopt_lvl_ipv6_mtu(ErlNifEnv* env, + SocketDescriptor* descP); +#endif #if defined(IPV6_V6ONLY) static ERL_NIF_TERM ngetopt_lvl_ipv6_v6only(ErlNifEnv* env, SocketDescriptor* descP); #endif #endif // defined(SOL_IPV6) + static ERL_NIF_TERM ngetopt_lvl_tcp(ErlNifEnv* env, SocketDescriptor* descP, int eOpt); @@ -4960,6 +4971,12 @@ ERL_NIF_TERM nsetopt_lvl_ipv6(ErlNifEnv* env, break; #endif +#if defined(IPV6_MTU) + case SOCKET_OPT_IPV6_MTU: + result = nsetopt_lvl_ipv6_mtu(env, descP, eVal); + break; +#endif + #if defined(IPV6_V6ONLY) case SOCKET_OPT_IPV6_V6ONLY: result = nsetopt_lvl_ipv6_v6only(env, descP, eVal); @@ -5010,6 +5027,17 @@ ERL_NIF_TERM nsetopt_lvl_ipv6_hoplimit(ErlNifEnv* env, #endif +#if defined(IPV6_MTU) +static +ERL_NIF_TERM nsetopt_lvl_ipv6_mtu(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) +{ + return nsetopt_int_opt(env, descP, SOL_IPV6, IPV6_MTU, eVal); +} +#endif + + #if defined(IPV6_V6ONLY) static ERL_NIF_TERM nsetopt_lvl_ipv6_v6only(ErlNifEnv* env, @@ -6697,6 +6725,12 @@ ERL_NIF_TERM ngetopt_lvl_ipv6(ErlNifEnv* env, break; #endif +#if defined(IPV6_MTU) + case SOCKET_OPT_IPV6_MTU: + result = ngetopt_lvl_ipv6_mtu(env, descP); + break; +#endif + #if defined(IPV6_V6ONLY) case SOCKET_OPT_IPV6_V6ONLY: result = ngetopt_lvl_ipv6_v6only(env, descP); @@ -6727,6 +6761,16 @@ ERL_NIF_TERM ngetopt_lvl_ipv6_hoplimit(ErlNifEnv* env, #endif +#if defined(IPV6_MTU) +static +ERL_NIF_TERM ngetopt_lvl_ipv6_mtu(ErlNifEnv* env, + SocketDescriptor* descP) +{ + return ngetopt_int_opt(env, descP, SOL_IPV6, IPV6_MTU); +} +#endif + + #if defined(IPV6_V6ONLY) static ERL_NIF_TERM ngetopt_lvl_ipv6_v6only(ErlNifEnv* env, diff --git a/erts/preloaded/ebin/socket.beam b/erts/preloaded/ebin/socket.beam index 307a94be4e..7ab8b0b3c4 100644 Binary files a/erts/preloaded/ebin/socket.beam and b/erts/preloaded/ebin/socket.beam differ diff --git a/erts/preloaded/src/socket.erl b/erts/preloaded/src/socket.erl index 3e31326759..41df672ef5 100644 --- a/erts/preloaded/src/socket.erl +++ b/erts/preloaded/src/socket.erl @@ -586,7 +586,7 @@ %% -define(SOCKET_OPT_IPV6_IPCOMP_LEVEL, 14). %% -define(SOCKET_OPT_IPV6_JOIN_GROUP, 15). %% -define(SOCKET_OPT_IPV6_LEAVE_GROUP, 16). -%% -define(SOCKET_OPT_IPV6_MTU, 17). +-define(SOCKET_OPT_IPV6_MTU, 17). %% -define(SOCKET_OPT_IPV6_MTU_DISCOVER, 18). %% -define(SOCKET_OPT_IPV6_MULTICAST_HOPS, 19). %% -define(SOCKET_OPT_IPV6_MULTICAST_IF, 20). @@ -2105,6 +2105,8 @@ enc_setopt_value(ipv6, drop_membership, #{multiaddr := MA, enc_setopt_value(ipv6, hoplimit, V, _D, T, _P) when is_boolean(V) andalso ((T =:= dgram) orelse (T =:= raw)) -> V; +enc_setopt_value(ipv6, mtu, V, _D, _T, _P) when is_integer(V) -> + V; enc_setopt_value(ipv6, v6only, V, _D, _T, _P) when is_boolean(V) -> V; enc_setopt_value(ipv6 = L, Opt, V, _D, _T, _P) -> @@ -2458,8 +2460,8 @@ enc_sockopt_key(ipv6 = L, join_group = Opt, _Dir, _D, _T, _P) -> not_supported({L, Opt}); enc_sockopt_key(ipv6 = L, leave_group = Opt, _Dir, _D, _T, _P) -> not_supported({L, Opt}); -enc_sockopt_key(ipv6 = L, mtu = Opt, _Dir, _D, _T, _P) -> - not_supported({L, Opt}); +enc_sockopt_key(ipv6 = _L, mtu = _Opt, _Dir, _D, _T, _P) -> + ?SOCKET_OPT_IPV6_MTU; enc_sockopt_key(ipv6 = L, mtu_discover = Opt, _Dir, _D, _T, _P) -> not_supported({L, Opt}); enc_sockopt_key(ipv6 = L, multicast_hops = Opt, _Dir, _D, _T, _P) -> diff --git a/lib/kernel/test/socket_client.erl b/lib/kernel/test/socket_client.erl index 8ec9a02374..0d332e8439 100644 --- a/lib/kernel/test/socket_client.erl +++ b/lib/kernel/test/socket_client.erl @@ -21,7 +21,7 @@ -module(socket_client). -export([ - start/1, + start/1, start/5, start_tcp/1, start_tcp/2, start_tcp6/1, start_udp/1, start_udp/2, start_udp6/1 ]). diff --git a/lib/kernel/test/socket_server.erl b/lib/kernel/test/socket_server.erl index 8a77b9b3c9..65069df60b 100644 --- a/lib/kernel/test/socket_server.erl +++ b/lib/kernel/test/socket_server.erl @@ -22,8 +22,10 @@ -export([ start/0, start/4, - start_tcp/0, start_tcp/1, - start_udp/0, start_udp/1 + start_tcp/0, start_tcp/1, start_tcp/2, + start_tcp4/1, start_tcp6/1, + start_udp/0, start_udp/1, start_udp/2, + start_udp4/1, start_udp6/1 ]). -define(LIB, socket_lib). @@ -41,13 +43,31 @@ start_tcp() -> start_tcp(false). start_tcp(Peek) -> - start(inet, stream, tcp, Peek). + start_tcp4(Peek). + +start_tcp4(Peek) -> + start_tcp(inet, Peek). + +start_tcp6(Peek) -> + start_tcp(inet6, Peek). + +start_tcp(Domain, Peek) when is_boolean(Peek) -> + start(Domain, stream, tcp, Peek). start_udp() -> start_udp(false). -start_udp(Peek) when is_boolean(Peek) -> - start(inet, dgram, udp, Peek). +start_udp(Peek) -> + start_udp4(Peek). + +start_udp4(Peek) -> + start_udp(inet, Peek). + +start_udp6(Peek) -> + start_udp(inet6, Peek). + +start_udp(Domain, Peek) when is_boolean(Peek) -> + start(Domain, dgram, udp, Peek). start(Domain, Type, Proto, Peek) -> put(sname, "starter"), -- cgit v1.2.3 From 1a3aca0a849af0bae994c9cf89de0dcfe7b310c2 Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Wed, 18 Jul 2018 12:05:00 +0200 Subject: [socket-nif] Add support for socket (level ipv6) option mtu_discover Added support for the VPv6 socket option MTU_DISCOVER. OTP-14831. --- erts/doc/src/socket_usage.xml | 21 ++-- erts/emulator/nifs/common/socket_nif.c | 190 +++++++++++++++++++++++++++++++++ erts/preloaded/ebin/socket.beam | Bin 51480 -> 51660 bytes erts/preloaded/src/socket.erl | 16 ++- 4 files changed, 217 insertions(+), 10 deletions(-) diff --git a/erts/doc/src/socket_usage.xml b/erts/doc/src/socket_usage.xml index cdd98090e8..247cd0eccb 100644 --- a/erts/doc/src/socket_usage.xml +++ b/erts/doc/src/socket_usage.xml @@ -145,7 +145,7 @@ none - peep_off + peek_off integer() yes yes @@ -255,7 +255,7 @@ mtu_discover - ip_mtu_discover() + ip_pmtudisc() yes yes none @@ -376,6 +376,13 @@ yes Get: Only after the socket has been connected + + mtu_discover + ipv6_pmtudisc() + yes + yes + none + v6only boolean() @@ -406,14 +413,14 @@ integer() yes yes - type = stream, protocol = tcp + none nodelay boolean() yes yes - type = stream, protocol = tcp + none @@ -431,7 +438,7 @@ boolean() yes yes - type = dgram, protocol = udp + none @@ -449,14 +456,14 @@ integer() yes yes - protocol = sctp + none nodelay boolean() yes yes - protocol = sctp + none diff --git a/erts/emulator/nifs/common/socket_nif.c b/erts/emulator/nifs/common/socket_nif.c index 0222d58e6d..d2f7e21ad0 100644 --- a/erts/emulator/nifs/common/socket_nif.c +++ b/erts/emulator/nifs/common/socket_nif.c @@ -389,6 +389,7 @@ typedef union { #define SOCKET_OPT_IPV6_DROP_MEMBERSHIP 6 #define SOCKET_OPT_IPV6_HOPLIMIT 12 #define SOCKET_OPT_IPV6_MTU 17 +#define SOCKET_OPT_IPV6_MTU_DISCOVER 18 #define SOCKET_OPT_IPV6_V6ONLY 33 #define SOCKET_OPT_TCP_CONGESTION 1 @@ -1014,6 +1015,11 @@ static ERL_NIF_TERM nsetopt_lvl_ipv6_mtu(ErlNifEnv* env, SocketDescriptor* descP, ERL_NIF_TERM eVal); #endif +#if defined(IPV6_MTU_DISCOVER) +static ERL_NIF_TERM nsetopt_lvl_ipv6_mtu_discover(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); +#endif #if defined(IPV6_V6ONLY) static ERL_NIF_TERM nsetopt_lvl_ipv6_v6only(ErlNifEnv* env, SocketDescriptor* descP, @@ -1237,6 +1243,10 @@ static ERL_NIF_TERM ngetopt_lvl_ipv6_hoplimit(ErlNifEnv* env, static ERL_NIF_TERM ngetopt_lvl_ipv6_mtu(ErlNifEnv* env, SocketDescriptor* descP); #endif +#if defined(IPV6_MTU_DISCOVER) +static ERL_NIF_TERM ngetopt_lvl_ipv6_mtu_discover(ErlNifEnv* env, + SocketDescriptor* descP); +#endif #if defined(IPV6_V6ONLY) static ERL_NIF_TERM ngetopt_lvl_ipv6_v6only(ErlNifEnv* env, SocketDescriptor* descP); @@ -1451,6 +1461,16 @@ static void encode_ip_pmtudisc(ErlNifEnv* env, int val, ERL_NIF_TERM* eVal); #endif +#if defined(IPV6_MTU_DISCOVER) +static char* decode_ipv6_pmtudisc(ErlNifEnv* env, + ERL_NIF_TERM eVal, + int* val); +#endif +#if defined(IPV6_MTU_DISCOVER) +static void encode_ipv6_pmtudisc(ErlNifEnv* env, + int val, + ERL_NIF_TERM* eVal); +#endif /* static BOOLEAN_T decode_bool(ErlNifEnv* env, @@ -4977,6 +4997,12 @@ ERL_NIF_TERM nsetopt_lvl_ipv6(ErlNifEnv* env, break; #endif +#if defined(IPV6_MTU_DISCOVER) + case SOCKET_OPT_IPV6_MTU_DISCOVER: + result = nsetopt_lvl_ipv6_mtu_discover(env, descP, eVal); + break; +#endif + #if defined(IPV6_V6ONLY) case SOCKET_OPT_IPV6_V6ONLY: result = nsetopt_lvl_ipv6_v6only(env, descP, eVal); @@ -5038,6 +5064,42 @@ ERL_NIF_TERM nsetopt_lvl_ipv6_mtu(ErlNifEnv* env, #endif +/* nsetopt_lvl_ipv6_mtu_discover - Level IPv6 MTU_DISCOVER option + * + * The value is an atom of the type ipv6_pmtudisc(). + */ +#if defined(IPV6_MTU_DISCOVER) +static +ERL_NIF_TERM nsetopt_lvl_ipv6_mtu_discover(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) +{ + ERL_NIF_TERM result; + int val; + char* xres; + int res; + + if ((xres = decode_ipv6_pmtudisc(env, eVal, &val)) != NULL) { + + result = esock_make_error_str(env, xres); + + } else { + + res = socket_setopt(descP->sock, SOL_IPV6, IPV6_MTU_DISCOVER, + &val, sizeof(val)); + + if (res != 0) + result = esock_make_error_errno(env, sock_errno()); + else + result = esock_atom_ok; + + } + + return result; +} +#endif + + #if defined(IPV6_V6ONLY) static ERL_NIF_TERM nsetopt_lvl_ipv6_v6only(ErlNifEnv* env, @@ -6731,6 +6793,12 @@ ERL_NIF_TERM ngetopt_lvl_ipv6(ErlNifEnv* env, break; #endif +#if defined(IPV6_MTU_DISCOVER) + case SOCKET_OPT_IPV6_MTU_DISCOVER: + result = ngetopt_lvl_ipv6_mtu_discover(env, descP); + break; +#endif + #if defined(IPV6_V6ONLY) case SOCKET_OPT_IPV6_V6ONLY: result = ngetopt_lvl_ipv6_v6only(env, descP); @@ -6771,6 +6839,35 @@ ERL_NIF_TERM ngetopt_lvl_ipv6_mtu(ErlNifEnv* env, #endif +/* ngetopt_lvl_ipv6_mtu_discover - Level IPv6 MTU_DISCOVER option + */ +#if defined(IPV6_MTU_DISCOVER) +static +ERL_NIF_TERM ngetopt_lvl_ipv6_mtu_discover(ErlNifEnv* env, + SocketDescriptor* descP) +{ + ERL_NIF_TERM result; + ERL_NIF_TERM eMtuDisc; + int mtuDisc; + SOCKOPTLEN_T mtuDiscSz = sizeof(mtuDisc); + int res; + + res = sock_getopt(descP->sock, SOL_IPV6, IPV6_MTU_DISCOVER, + &mtuDisc, &mtuDiscSz); + + if (res != 0) { + result = esock_make_error_errno(env, sock_errno()); + } else { + encode_ipv6_pmtudisc(env, mtuDisc, &eMtuDisc); + result = esock_make_ok2(env, eMtuDisc); + } + + return result; + +} +#endif + + #if defined(IPV6_V6ONLY) static ERL_NIF_TERM ngetopt_lvl_ipv6_v6only(ErlNifEnv* env, @@ -7723,6 +7820,57 @@ char* decode_ip_pmtudisc(ErlNifEnv* env, ERL_NIF_TERM eVal, int* val) +/* +++ decode the ipv6 socket option MTU_DISCOVER +++ + * The (ip) option can be provide in two ways: + * + * atom() | integer() + * + * When its an atom it can have the values: + * + * want | dont | do | probe + * + */ +#if defined(IPV6_MTU_DISCOVER) +static +char* decode_ipv6_pmtudisc(ErlNifEnv* env, ERL_NIF_TERM eVal, int* val) +{ + char* res = NULL; + + if (IS_ATOM(env, eVal)) { + + if (COMPARE(eVal, atom_want) == 0) { + *val = IPV6_PMTUDISC_WANT; + } else if (COMPARE(eVal, atom_dont) == 0) { + *val = IPV6_PMTUDISC_DONT; + } else if (COMPARE(eVal, atom_do) == 0) { + *val = IPV6_PMTUDISC_DO; + } else if (COMPARE(eVal, atom_probe) == 0) { + *val = IPV6_PMTUDISC_PROBE; + } else { + *val = -1; + res = ESOCK_STR_EINVAL; + } + + } else if (IS_NUM(env, eVal)) { + + if (!GET_INT(env, eVal, val)) { + *val = -1; + res = ESOCK_STR_EINVAL; + } + + } else { + + *val = -1; + res = ESOCK_STR_EINVAL; + + } + + return res; +} +#endif + + + /* +++ encode the ip socket option MTU_DISCOVER +++ * The (ip) option can be provide in two ways: * @@ -7765,6 +7913,48 @@ void encode_ip_pmtudisc(ErlNifEnv* env, int val, ERL_NIF_TERM* eVal) +/* +++ encode the ipv6 socket option MTU_DISCOVER +++ + * The (ipv6) option can be provide in two ways: + * + * atom() | integer() + * + * If its one of the "known" values, it will be an atom: + * + * want | dont | do | probe + * + */ +#if defined(IPV6_MTU_DISCOVER) +static +void encode_ipv6_pmtudisc(ErlNifEnv* env, int val, ERL_NIF_TERM* eVal) +{ + switch (val) { + case IPV6_PMTUDISC_WANT: + *eVal = atom_want; + break; + + case IPV6_PMTUDISC_DONT: + *eVal = atom_dont; + break; + + case IPV6_PMTUDISC_DO: + *eVal = atom_do; + break; + + case IPV6_PMTUDISC_PROBE: + *eVal = atom_probe; + break; + + default: + *eVal = MKI(env, val); + break; + } + + return; +} +#endif + + + /* +++ decocde the native getopt option +++ * The option is in this case provide in the form of a two tuple: * diff --git a/erts/preloaded/ebin/socket.beam b/erts/preloaded/ebin/socket.beam index 7ab8b0b3c4..cb1c5fc815 100644 Binary files a/erts/preloaded/ebin/socket.beam and b/erts/preloaded/ebin/socket.beam differ diff --git a/erts/preloaded/src/socket.erl b/erts/preloaded/src/socket.erl index 41df672ef5..ead058c607 100644 --- a/erts/preloaded/src/socket.erl +++ b/erts/preloaded/src/socket.erl @@ -93,6 +93,7 @@ ip_mreq_source/0, ip_pmtudisc/0, ipv6_mreq/0, + ipv6_pmtudisc/0, msg_hdr/0 @@ -164,6 +165,8 @@ -type ipv6_mreq() :: #{multiaddr := ip6_address(), interface := non_neg_integer()}. +-type ipv6_pmtudisc() :: ip_pmtudisc(). + -type sockaddr_un() :: #{family := local, path := binary() | string()}. -type sockaddr_in4() :: #{family := inet, @@ -587,7 +590,7 @@ %% -define(SOCKET_OPT_IPV6_JOIN_GROUP, 15). %% -define(SOCKET_OPT_IPV6_LEAVE_GROUP, 16). -define(SOCKET_OPT_IPV6_MTU, 17). -%% -define(SOCKET_OPT_IPV6_MTU_DISCOVER, 18). +-define(SOCKET_OPT_IPV6_MTU_DISCOVER, 18). %% -define(SOCKET_OPT_IPV6_MULTICAST_HOPS, 19). %% -define(SOCKET_OPT_IPV6_MULTICAST_IF, 20). %% -define(SOCKET_OPT_IPV6_MULTICAST_LOOP, 21). @@ -2107,6 +2110,13 @@ enc_setopt_value(ipv6, hoplimit, V, _D, T, _P) V; enc_setopt_value(ipv6, mtu, V, _D, _T, _P) when is_integer(V) -> V; +enc_setopt_value(ipv6, mtu_discover, V, _D, _T, _P) + when (V =:= want) orelse + (V =:= dont) orelse + (V =:= do) orelse + (V =:= probe) orelse + is_integer(V) -> + V; enc_setopt_value(ipv6, v6only, V, _D, _T, _P) when is_boolean(V) -> V; enc_setopt_value(ipv6 = L, Opt, V, _D, _T, _P) -> @@ -2462,8 +2472,8 @@ enc_sockopt_key(ipv6 = L, leave_group = Opt, _Dir, _D, _T, _P) -> not_supported({L, Opt}); enc_sockopt_key(ipv6 = _L, mtu = _Opt, _Dir, _D, _T, _P) -> ?SOCKET_OPT_IPV6_MTU; -enc_sockopt_key(ipv6 = L, mtu_discover = Opt, _Dir, _D, _T, _P) -> - not_supported({L, Opt}); +enc_sockopt_key(ipv6 = _L, mtu_discover = _Opt, _Dir, _D, _T, _P) -> + ?SOCKET_OPT_IPV6_MTU_DISCOVER; enc_sockopt_key(ipv6 = L, multicast_hops = Opt, _Dir, _D, _T, _P) -> not_supported({L, Opt}); enc_sockopt_key(ipv6 = L, multicast_if = Opt, _Dir, _D, T, _P) -- cgit v1.2.3 From d28129b7098bce154264937862fcdafb21541433 Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Thu, 19 Jul 2018 14:00:42 +0200 Subject: [socket-nif] Add support for socket (level sctp) option events Added support for the SCTP option EVENTS. OTP-14831 --- erts/doc/src/socket.xml | 21 +- erts/doc/src/socket_usage.xml | 7 + erts/emulator/nifs/common/socket_nif.c | 578 +++++++++++++++++++++++++-------- erts/preloaded/ebin/socket.beam | Bin 51660 -> 52584 bytes erts/preloaded/src/socket.erl | 117 ++++--- lib/kernel/test/socket_server.erl | 48 ++- 6 files changed, 600 insertions(+), 171 deletions(-) diff --git a/erts/doc/src/socket.xml b/erts/doc/src/socket.xml index 2681910a72..4ecf35b8ed 100644 --- a/erts/doc/src/socket.xml +++ b/erts/doc/src/socket.xml @@ -122,6 +122,21 @@ + + + + + + + + + + + + + + + @@ -132,9 +147,9 @@

Accept a connection on a socket.

This call is used with connection-based socket types - (stream or seqpacket). It extracs the first pending connection - request for the listen socket and returns the (newly) connected - socket.

+ (stream or seqpacket). It extracs the first pending + connection request for the listen socket and returns the (newly) + connected socket.

diff --git a/erts/doc/src/socket_usage.xml b/erts/doc/src/socket_usage.xml index 247cd0eccb..1d7b98d1a3 100644 --- a/erts/doc/src/socket_usage.xml +++ b/erts/doc/src/socket_usage.xml @@ -458,6 +458,13 @@ yes none + + events + sctp_event_subscribe() + yes + no + none + nodelay boolean() diff --git a/erts/emulator/nifs/common/socket_nif.c b/erts/emulator/nifs/common/socket_nif.c index d2f7e21ad0..7271db2143 100644 --- a/erts/emulator/nifs/common/socket_nif.c +++ b/erts/emulator/nifs/common/socket_nif.c @@ -164,6 +164,149 @@ #define HAVE_UDP +/* SCTP support -- currently for UNIX platforms only: */ +#undef HAVE_SCTP +#if defined(HAVE_SCTP_H) + +#include + +/* SCTP Socket API Draft from version 11 on specifies that netinet/sctp.h must + explicitly define HAVE_SCTP in case when SCTP is supported, but Solaris 10 + still apparently uses Draft 10, and does not define that symbol, so we have + to define it explicitly: +*/ +#ifndef HAVE_SCTP +# define HAVE_SCTP +#endif + +/* These changed in draft 11, so SOLARIS10 uses the old MSG_* */ +#if ! HAVE_DECL_SCTP_UNORDERED +# define SCTP_UNORDERED MSG_UNORDERED +#endif +#if ! HAVE_DECL_SCTP_ADDR_OVER +# define SCTP_ADDR_OVER MSG_ADDR_OVER +#endif +#if ! HAVE_DECL_SCTP_ABORT +# define SCTP_ABORT MSG_ABORT +#endif +#if ! HAVE_DECL_SCTP_EOF +# define SCTP_EOF MSG_EOF +#endif + +/* More Solaris 10 fixes: */ +#if ! HAVE_DECL_SCTP_CLOSED && HAVE_DECL_SCTPS_IDLE +# define SCTP_CLOSED SCTPS_IDLE +# undef HAVE_DECL_SCTP_CLOSED +# define HAVE_DECL_SCTP_CLOSED 1 +#endif +#if ! HAVE_DECL_SCTP_BOUND && HAVE_DECL_SCTPS_BOUND +# define SCTP_BOUND SCTPS_BOUND +# undef HAVE_DECL_SCTP_BOUND +# define HAVE_DECL_SCTP_BOUND 1 +#endif +#if ! HAVE_DECL_SCTP_LISTEN && HAVE_DECL_SCTPS_LISTEN +# define SCTP_LISTEN SCTPS_LISTEN +# undef HAVE_DECL_SCTP_LISTEN +# define HAVE_DECL_SCTP_LISTEN 1 +#endif +#if ! HAVE_DECL_SCTP_COOKIE_WAIT && HAVE_DECL_SCTPS_COOKIE_WAIT +# define SCTP_COOKIE_WAIT SCTPS_COOKIE_WAIT +# undef HAVE_DECL_SCTP_COOKIE_WAIT +# define HAVE_DECL_SCTP_COOKIE_WAIT 1 +#endif +#if ! HAVE_DECL_SCTP_COOKIE_ECHOED && HAVE_DECL_SCTPS_COOKIE_ECHOED +# define SCTP_COOKIE_ECHOED SCTPS_COOKIE_ECHOED +# undef HAVE_DECL_SCTP_COOKIE_ECHOED +# define HAVE_DECL_SCTP_COOKIE_ECHOED 1 +#endif +#if ! HAVE_DECL_SCTP_ESTABLISHED && HAVE_DECL_SCTPS_ESTABLISHED +# define SCTP_ESTABLISHED SCTPS_ESTABLISHED +# undef HAVE_DECL_SCTP_ESTABLISHED +# define HAVE_DECL_SCTP_ESTABLISHED 1 +#endif +#if ! HAVE_DECL_SCTP_SHUTDOWN_PENDING && HAVE_DECL_SCTPS_SHUTDOWN_PENDING +# define SCTP_SHUTDOWN_PENDING SCTPS_SHUTDOWN_PENDING +# undef HAVE_DECL_SCTP_SHUTDOWN_PENDING +# define HAVE_DECL_SCTP_SHUTDOWN_PENDING 1 +#endif +#if ! HAVE_DECL_SCTP_SHUTDOWN_SENT && HAVE_DECL_SCTPS_SHUTDOWN_SENT +# define SCTP_SHUTDOWN_SENT SCTPS_SHUTDOWN_SENT +# undef HAVE_DECL_SCTP_SHUTDOWN_SENT +# define HAVE_DECL_SCTP_SHUTDOWN_SENT 1 +#endif +#if ! HAVE_DECL_SCTP_SHUTDOWN_RECEIVED && HAVE_DECL_SCTPS_SHUTDOWN_RECEIVED +# define SCTP_SHUTDOWN_RECEIVED SCTPS_SHUTDOWN_RECEIVED +# undef HAVE_DECL_SCTP_SHUTDOWN_RECEIVED +# define HAVE_DECL_SCTP_SHUTDOWN_RECEIVED 1 +#endif +#if ! HAVE_DECL_SCTP_SHUTDOWN_ACK_SENT && HAVE_DECL_SCTPS_SHUTDOWN_ACK_SENT +# define SCTP_SHUTDOWN_ACK_SENT SCTPS_SHUTDOWN_ACK_SENT +# undef HAVE_DECL_SCTP_SHUTDOWN_ACK_SENT +# define HAVE_DECL_SCTP_SHUTDOWN_ACK_SENT 1 +#endif +/* New spelling in lksctp 2.6.22 or maybe even earlier: + * adaption -> adaptation + */ +#if !defined(SCTP_ADAPTATION_LAYER) && defined (SCTP_ADAPTION_LAYER) +# define SCTP_ADAPTATION_LAYER SCTP_ADAPTION_LAYER +# define SCTP_ADAPTATION_INDICATION SCTP_ADAPTION_INDICATION +# define sctp_adaptation_event sctp_adaption_event +# define sctp_setadaptation sctp_setadaption +# define sn_adaptation_event sn_adaption_event +# define sai_adaptation_ind sai_adaption_ind +# define ssb_adaptation_ind ssb_adaption_ind +# define sctp_adaptation_layer_event sctp_adaption_layer_event +#endif + +/* + * We *may* need this stuff later when we *fully* implement support for SCTP + * + +#if defined(__GNUC__) && defined(HAVE_SCTP_BINDX) +static typeof(sctp_bindx) *p_sctp_bindx = NULL; +#else +static int (*p_sctp_bindx) + (int sd, struct sockaddr *addrs, int addrcnt, int flags) = NULL; +#endif + +#if defined(__GNUC__) && defined(HAVE_SCTP_PEELOFF) +static typeof(sctp_peeloff) *p_sctp_peeloff = NULL; +#else +static int (*p_sctp_peeloff) + (int sd, sctp_assoc_t assoc_id) = NULL; +#endif + +#if defined(__GNUC__) && defined(HAVE_SCTP_GETLADDRS) +static typeof(sctp_getladdrs) *p_sctp_getladdrs = NULL; +#else +static int (*p_sctp_getladdrs) + (int sd, sctp_assoc_t assoc_id, struct sockaddr **ss) = NULL; +#endif + +#if defined(__GNUC__) && defined(HAVE_SCTP_FREELADDRS) +static typeof(sctp_freeladdrs) *p_sctp_freeladdrs = NULL; +#else +static void (*p_sctp_freeladdrs)(struct sockaddr *addrs) = NULL; +#endif + +#if defined(__GNUC__) && defined(HAVE_SCTP_GETPADDRS) +static typeof(sctp_getpaddrs) *p_sctp_getpaddrs = NULL; +#else +static int (*p_sctp_getpaddrs) + (int sd, sctp_assoc_t assoc_id, struct sockaddr **ss) = NULL; +#endif + +#if defined(__GNUC__) && defined(HAVE_SCTP_FREEPADDRS) +static typeof(sctp_freepaddrs) *p_sctp_freepaddrs = NULL; +#else +static void (*p_sctp_freepaddrs)(struct sockaddr *addrs) = NULL; +#endif + +*/ + +#endif /* #if defined(HAVE_SCTP_H) */ + + #ifndef WANT_NONBLOCKING #define WANT_NONBLOCKING #endif @@ -394,12 +537,13 @@ typedef union { #define SOCKET_OPT_TCP_CONGESTION 1 #define SOCKET_OPT_TCP_CORK 2 -#define SOCKET_OPT_TCP_MAXSEG 3 -#define SOCKET_OPT_TCP_NODELAY 4 +#define SOCKET_OPT_TCP_MAXSEG 7 +#define SOCKET_OPT_TCP_NODELAY 9 #define SOCKET_OPT_UDP_CORK 1 #define SOCKET_OPT_SCTP_AUTOCLOSE 8 +#define SOCKET_OPT_SCTP_EVENTS 14 #define SOCKET_OPT_SCTP_NODELAY 23 @@ -1072,6 +1216,11 @@ static ERL_NIF_TERM nsetopt_lvl_sctp_autoclose(ErlNifEnv* env, SocketDescriptor* descP, ERL_NIF_TERM eVal); #endif +#if defined(SCTP_EVENTS) +static ERL_NIF_TERM nsetopt_lvl_sctp_events(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); +#endif #if defined(SCTP_NODELAY) static ERL_NIF_TERM nsetopt_lvl_sctp_nodelay(ErlNifEnv* env, SocketDescriptor* descP, @@ -1618,38 +1767,48 @@ static const struct in6_addr in6addr_loopback = /* *** String constants *** */ -// static char str_any[] = "any"; -static char str_close[] = "close"; -static char str_closed[] = "closed"; -static char str_closing[] = "closing"; -static char str_do[] = "do"; -static char str_dont[] = "dont"; -static char str_false[] = "false"; -static char str_global_counters[] = "global_counters"; -static char str_in4_sockaddr[] = "in4_sockaddr"; -static char str_in6_sockaddr[] = "in6_sockaddr"; -static char str_iow[] = "iow"; -static char str_interface[] = "interface"; -// static char str_loopback[] = "loopback"; -static char str_multiaddr[] = "multiaddr"; -static char str_nif_abort[] = "nif_abort"; -static char str_num_dlocal[] = "num_domain_local"; -static char str_num_dinet[] = "num_domain_inet"; -static char str_num_dinet6[] = "num_domain_inet6"; -static char str_num_pip[] = "num_proto_ip"; -static char str_num_psctp[] = "num_proto_sctp"; -static char str_num_ptcp[] = "num_proto_tcp"; -static char str_num_pudp[] = "num_proto_udp"; -static char str_num_sockets[] = "num_sockets"; -static char str_num_tdgrams[] = "num_type_dgram"; -static char str_num_tseqpkgs[] = "num_type_seqpacket"; -static char str_num_tstreams[] = "num_type_stream"; -static char str_probe[] = "probe"; -static char str_select[] = "select"; -static char str_sourceaddr[] = "sourceaddr"; -static char str_timeout[] = "timeout"; -static char str_true[] = "true"; -static char str_want[] = "want"; +static char str_adaptation_layer[] = "adaptation_layer"; +static char str_address[] = "address"; +static char str_association[] = "association"; +static char str_authentication[] = "authentication"; +// static char str_any[] = "any"; +static char str_close[] = "close"; +static char str_closed[] = "closed"; +static char str_closing[] = "closing"; +static char str_data_in[] = "data_in"; +static char str_do[] = "do"; +static char str_dont[] = "dont"; +static char str_false[] = "false"; +static char str_global_counters[] = "global_counters"; +static char str_in4_sockaddr[] = "in4_sockaddr"; +static char str_in6_sockaddr[] = "in6_sockaddr"; +static char str_iow[] = "iow"; +static char str_interface[] = "interface"; +// static char str_loopback[] = "loopback"; +static char str_multiaddr[] = "multiaddr"; +static char str_nif_abort[] = "nif_abort"; +static char str_num_dlocal[] = "num_domain_local"; +static char str_num_dinet[] = "num_domain_inet"; +static char str_num_dinet6[] = "num_domain_inet6"; +static char str_num_pip[] = "num_proto_ip"; +static char str_num_psctp[] = "num_proto_sctp"; +static char str_num_ptcp[] = "num_proto_tcp"; +static char str_num_pudp[] = "num_proto_udp"; +static char str_num_sockets[] = "num_sockets"; +static char str_num_tdgrams[] = "num_type_dgram"; +static char str_num_tseqpkgs[] = "num_type_seqpacket"; +static char str_num_tstreams[] = "num_type_stream"; +static char str_partial_delivery[] = "partial_delivery"; +static char str_peer_error[] = "peer_error"; +static char str_probe[] = "probe"; +static char str_select[] = "select"; +static char str_sender_dry[] = "sender_dry"; +static char str_send_failure[] = "send_failure"; +static char str_shutdown[] = "shutdown"; +static char str_sourceaddr[] = "sourceaddr"; +static char str_timeout[] = "timeout"; +static char str_true[] = "true"; +static char str_want[] = "want"; static char str_lowdelay[] = "lowdelay"; static char str_throughput[] = "throughput"; @@ -1706,9 +1865,14 @@ ERL_NIF_TERM esock_atom_eafnosupport; ERL_NIF_TERM esock_atom_einval; /* *** Atoms *** */ +static ERL_NIF_TERM atom_adaptation_layer; +static ERL_NIF_TERM atom_address; +static ERL_NIF_TERM atom_association; +static ERL_NIF_TERM atom_authentication; static ERL_NIF_TERM atom_close; static ERL_NIF_TERM atom_closed; static ERL_NIF_TERM atom_closing; +static ERL_NIF_TERM atom_data_in; static ERL_NIF_TERM atom_do; static ERL_NIF_TERM atom_dont; static ERL_NIF_TERM atom_false; @@ -1730,8 +1894,13 @@ static ERL_NIF_TERM atom_num_sockets; static ERL_NIF_TERM atom_num_tdgrams; static ERL_NIF_TERM atom_num_tseqpkgs; static ERL_NIF_TERM atom_num_tstreams; +static ERL_NIF_TERM atom_partial_delivery; +static ERL_NIF_TERM atom_peer_error; static ERL_NIF_TERM atom_probe; static ERL_NIF_TERM atom_select; +static ERL_NIF_TERM atom_sender_dry; +static ERL_NIF_TERM atom_send_failure; +static ERL_NIF_TERM atom_shutdown; static ERL_NIF_TERM atom_sourceaddr; static ERL_NIF_TERM atom_timeout; static ERL_NIF_TERM atom_true; @@ -1895,7 +2064,7 @@ ERL_NIF_TERM nif_open(ErlNifEnv* env, const ERL_NIF_TERM argv[]) { int edomain, etype, eproto; - int domain, type, proto; + int domain, type, proto; char* netns; ERL_NIF_TERM emap; ERL_NIF_TERM result; @@ -1920,19 +2089,27 @@ ERL_NIF_TERM nif_open(ErlNifEnv* env, "\r\n extra: %T" "\r\n", argv[0], argv[1], argv[2], argv[3]) ); - if (!edomain2domain(edomain, &domain)) - return enif_make_badarg(env); + if (!edomain2domain(edomain, &domain)) { + SGDBG( ("SOCKET", "nif_open -> domain: %d\r\n", domain) ); + return esock_make_error(env, esock_atom_einval); + } - if (!etype2type(etype, &type)) - return enif_make_badarg(env); + if (!etype2type(etype, &type)) { + SGDBG( ("SOCKET", "nif_open -> type: %d\r\n", type) ); + return esock_make_error(env, esock_atom_einval); + } - if (!eproto2proto(eproto, &proto)) - return enif_make_badarg(env); + if (!eproto2proto(eproto, &proto)) { + SGDBG( ("SOCKET", "nif_open -> protocol: %d\r\n", proto) ); + return esock_make_error(env, esock_atom_einval); + } #ifdef HAVE_SETNS /* We *currently* only support one extra option: netns */ - if (!emap2netns(env, emap, &netns)) + if (!emap2netns(env, emap, &netns)) { + SGDBG( ("SOCKET", "nif_open -> namespace: %s\r\n", netns) ); return enif_make_badarg(env); + } #else netns = NULL; #endif @@ -3733,12 +3910,13 @@ ERL_NIF_TERM nif_setopt(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { - SocketDescriptor* descP; + SocketDescriptor* descP = NULL; int eLevel, level = -1; int eOpt; ERL_NIF_TERM eIsEncoded; ERL_NIF_TERM eVal; BOOLEAN_T isEncoded, isOTP; + ERL_NIF_TERM result; SGDBG( ("SOCKET", "nif_setopt -> entry with argc: %d\r\n", argc) ); @@ -3748,6 +3926,7 @@ ERL_NIF_TERM nif_setopt(ErlNifEnv* env, !enif_get_resource(env, argv[0], sockets, (void**) &descP) || !GET_INT(env, argv[2], &eLevel) || !GET_INT(env, argv[3], &eOpt)) { + SGDBG( ("SOCKET", "nif_setopt -> failed initial arg check\r\n") ); return enif_make_badarg(env); } eIsEncoded = argv[1]; @@ -3755,23 +3934,35 @@ ERL_NIF_TERM nif_setopt(ErlNifEnv* env, isEncoded = esock_decode_bool(eIsEncoded); - if (!elevel2level(isEncoded, eLevel, &isOTP, &level)) + /* SGDBG( ("SOCKET", "nif_setopt -> eIsDecoded (%T) decoded: %d\r\n", */ + /* eIsEncoded, isEncoded) ); */ + + if (!elevel2level(isEncoded, eLevel, &isOTP, &level)) { + SSDBG( descP, ("SOCKET", "nif_seopt -> failed decode level\r\n") ); return esock_make_error(env, esock_atom_einval); + } SSDBG( descP, ("SOCKET", "nif_setopt -> args when sock = %d:" "\r\n Socket: %T" - "\r\n Encoded: %T (%d)" + "\r\n Encoded: %d (%T)" "\r\n Level: %d (%d)" "\r\n Opt: %d" "\r\n Value: %T" "\r\n", descP->sock, argv[0], - eIsEncoded, isEncoded, - eLevel, level, + isEncoded, eIsEncoded, + level, eLevel, eOpt, eVal) ); - return nsetopt(env, descP, isEncoded, isOTP, level, eOpt, eVal); + result = nsetopt(env, descP, isEncoded, isOTP, level, eOpt, eVal); + + SSDBG( descP, + ("SOCKET", "nif_setopt -> done when" + "\r\n result: %T" + "\r\n", result) ); + + return result; } @@ -5318,6 +5509,12 @@ ERL_NIF_TERM nsetopt_lvl_sctp(ErlNifEnv* env, break; #endif +#if defined(SCTP_EVENTS) + case SOCKET_OPT_SCTP_EVENTS: + result = nsetopt_lvl_sctp_events(env, descP, eVal); + break; +#endif + #if defined(SCTP_NODELAY) case SOCKET_OPT_SCTP_NODELAY: result = nsetopt_lvl_sctp_nodelay(env, descP, eVal); @@ -5346,6 +5543,104 @@ ERL_NIF_TERM nsetopt_lvl_sctp_autoclose(ErlNifEnv* env, #endif +/* nsetopt_lvl_sctp_events - Level SCTP EVENTS option + */ +#if defined(SCTP_EVENTS) +static +ERL_NIF_TERM nsetopt_lvl_sctp_events(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) +{ + ERL_NIF_TERM result; + ERL_NIF_TERM eDataIn, eAssoc, eAddr, eSndFailure; + ERL_NIF_TERM ePeerError, eShutdown, ePartialDelivery; + ERL_NIF_TERM eAdaptLayer, eAuth, eSndDry; + struct sctp_event_subscribe events; + int res; + size_t sz; + + SSDBG( descP, + ("SOCKET", "nsetopt_lvl_sctp_events -> entry with" + "\r\n eVal: %T" + "\r\n", eVal) ); + + // It must be a map + if (!IS_MAP(env, eVal)) + return esock_make_error(env, esock_atom_einval); + + // It must have atleast ten attributes + if (!enif_get_map_size(env, eVal, &sz) || (sz < 10)) + return esock_make_error(env, esock_atom_einval); + + SSDBG( descP, + ("SOCKET", "nsetopt_lvl_sctp_events -> extract attributes\r\n") ); + + if (!GET_MAP_VAL(env, eVal, atom_data_in, &eDataIn)) + return esock_make_error(env, esock_atom_einval); + + if (!GET_MAP_VAL(env, eVal, atom_association, &eAssoc)) + return esock_make_error(env, esock_atom_einval); + + if (!GET_MAP_VAL(env, eVal, atom_address, &eAddr)) + return esock_make_error(env, esock_atom_einval); + + if (!GET_MAP_VAL(env, eVal, atom_send_failure, &eSndFailure)) + return esock_make_error(env, esock_atom_einval); + + if (!GET_MAP_VAL(env, eVal, atom_peer_error, &ePeerError)) + return esock_make_error(env, esock_atom_einval); + + if (!GET_MAP_VAL(env, eVal, atom_shutdown, &eShutdown)) + return esock_make_error(env, esock_atom_einval); + + if (!GET_MAP_VAL(env, eVal, atom_partial_delivery, &ePartialDelivery)) + return esock_make_error(env, esock_atom_einval); + + if (!GET_MAP_VAL(env, eVal, atom_adaptation_layer, &eAdaptLayer)) + return esock_make_error(env, esock_atom_einval); + + if (!GET_MAP_VAL(env, eVal, atom_authentication, &eAuth)) + return esock_make_error(env, esock_atom_einval); + + if (!GET_MAP_VAL(env, eVal, atom_sender_dry, &eSndDry)) + return esock_make_error(env, esock_atom_einval); + + SSDBG( descP, + ("SOCKET", "nsetopt_lvl_sctp_events -> decode attributes\r\n") ); + + events.sctp_data_io_event = esock_decode_bool(eDataIn); + events.sctp_association_event = esock_decode_bool(eAssoc); + events.sctp_address_event = esock_decode_bool(eAddr); + events.sctp_send_failure_event = esock_decode_bool(eSndFailure); + events.sctp_peer_error_event = esock_decode_bool(ePeerError); + events.sctp_shutdown_event = esock_decode_bool(eShutdown); + events.sctp_partial_delivery_event = esock_decode_bool(ePartialDelivery); + events.sctp_adaptation_layer_event = esock_decode_bool(eAdaptLayer); + events.sctp_authentication_event = esock_decode_bool(eAuth); + events.sctp_sender_dry_event = esock_decode_bool(eSndDry); + + SSDBG( descP, + ("SOCKET", "nsetopt_lvl_sctp_events -> set events option\r\n") ); + + res = socket_setopt(descP->sock, IPPROTO_SCTP, SCTP_EVENTS, + &events, sizeof(events)); + + if (res != 0) + result = esock_make_error_errno(env, sock_errno()); + else + result = esock_atom_ok; + + SSDBG( descP, + ("SOCKET", "nsetopt_lvl_sctp_events -> done with" + "\r\n result: %T" + "\r\n", result) ); + + return result; + +} +#endif + + /* nsetopt_lvl_sctp_nodelay - Level SCTP NODELAY option */ #if defined(SCTP_NODELAY) @@ -8269,6 +8564,7 @@ BOOLEAN_T edomain2domain(int edomain, int* domain) #endif default: + *domain = -1; return FALSE; } @@ -8303,6 +8599,7 @@ BOOLEAN_T etype2type(int etype, int* type) #endif default: + *type = -1; return FALSE; } @@ -8337,11 +8634,12 @@ BOOLEAN_T eproto2proto(int eproto, int* proto) #endif default: + *proto = -1; return FALSE; } - return TRUE; - } + return TRUE; +} #ifdef HAVE_SETNS @@ -8909,8 +9207,8 @@ void socket_stop(ErlNifEnv* env, void* obj, int fd, int is_direct_call) SSDBG( descP, ("SOCKET", "socket_stop -> entry when" "\r\n sock: %d (%d)" - "\r\n is_direct_call: %d" - "\r\n", descP->sock, fd, is_direct_call) ); + "\r\n Is Direct Call: %s" + "\r\n", descP->sock, fd, B2S(is_direct_call)) ); MLOCK(descP->writeMtx); MLOCK(descP->readMtx); @@ -9051,6 +9349,10 @@ void inform_waiting_procs(ErlNifEnv* env, *
*/ + SSDBG( descP, + ("SOCKET", "inform_waiting_procs -> abort %T (%T)\r\n", + currentP->data.ref, currentP->data.pid) ); + ESOCK_ASSERT( (NULL == send_msg_nif_abort(env, currentP->data.ref, reason, @@ -9088,59 +9390,71 @@ void socket_down(ErlNifEnv* env, /* Eventually we should go through the other queues also, * the process can be one of them... + * + * Currently only the accteptors actuallu use the queues. */ - /* Check first if its the current acceptor, and if not check the queue */ - if (compare_pids(env, &descP->currentAcceptor.pid, pid)) { - - SSDBG( descP, ("SOCKET", - "socket_down -> current acceptor - try pop the queue\r\n") ); - - if (acceptor_pop(env, descP, - &descP->currentAcceptor.pid, - &descP->currentAcceptor.mon, - &descP->currentAcceptor.ref)) { - int res; - - /* There was another one, so we will still be in accepting state */ - - SSDBG( descP, ("SOCKET", "socket_down -> new (active) acceptor: " - "\r\n pid: %T" - "\r\n ref: %T" - "\r\n", - descP->currentAcceptor.pid, - descP->currentAcceptor.ref) ); + if (descP->currentAcceptorP != NULL) { - if ((res = enif_select(env, - descP->sock, - (ERL_NIF_SELECT_READ), - descP, - &descP->currentAcceptor.pid, - descP->currentAcceptor.ref) < 0)) { + /* + * We have acceptor(s) (atleast one) + * + * Check first if its the current acceptor, + * and if not check the queue. + */ - esock_warning_msg("Failed select (%d) for new acceptor " - "after current (%T) died\r\n", - res, *pid); + if (compare_pids(env, &descP->currentAcceptor.pid, pid)) { + SSDBG( descP, ("SOCKET", + "socket_down -> " + "current acceptor - try pop the queue\r\n") ); + + if (acceptor_pop(env, descP, + &descP->currentAcceptor.pid, + &descP->currentAcceptor.mon, + &descP->currentAcceptor.ref)) { + int res; + + /* There was another one, so we will still be in accepting state */ + + SSDBG( descP, ("SOCKET", "socket_down -> new (active) acceptor: " + "\r\n pid: %T" + "\r\n ref: %T" + "\r\n", + descP->currentAcceptor.pid, + descP->currentAcceptor.ref) ); + + if ((res = enif_select(env, + descP->sock, + (ERL_NIF_SELECT_READ), + descP, + &descP->currentAcceptor.pid, + descP->currentAcceptor.ref) < 0)) { + + esock_warning_msg("Failed select (%d) for new acceptor " + "after current (%T) died\r\n", + res, *pid); + + } + + } else { + + SSDBG( descP, ("SOCKET", "socket_down -> no active acceptor\r\n") ); + + descP->currentAcceptorP = NULL; + descP->state = SOCKET_STATE_LISTENING; } - + } else { - - SSDBG( descP, ("SOCKET", "socket_down -> no active acceptor\r\n") ); - - descP->currentAcceptorP = NULL; - descP->state = SOCKET_STATE_LISTENING; + + /* Maybe unqueue one of the waiting acceptors */ + + SSDBG( descP, ("SOCKET", + "socket_down -> " + "not current acceptor - maybe a waiting acceptor\r\n") ); + + qunqueue(env, &descP->acceptorsQ, pid); } - - } else { - - /* Maybe unqueue one of the waiting acceptors */ - - SSDBG( descP, ("SOCKET", - "socket_down -> " - "not current acceptor - maybe a waiting acceptor\r\n") ); - - qunqueue(env, &descP->acceptorsQ, pid); } SSDBG( descP, ("SOCKET", "socket_down -> done\r\n") ); @@ -9246,36 +9560,46 @@ int on_load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info) data.numProtoSCTP = 0; /* +++ Misc atoms +++ */ - atom_close = MKA(env, str_close); - atom_closed = MKA(env, str_closed); - atom_closing = MKA(env, str_closing); - atom_do = MKA(env, str_do); - atom_dont = MKA(env, str_dont); - atom_false = MKA(env, str_false); - atom_global_counters = MKA(env, str_global_counters); - atom_in4_sockaddr = MKA(env, str_in4_sockaddr); - atom_in6_sockaddr = MKA(env, str_in6_sockaddr); - atom_iow = MKA(env, str_iow); - atom_interface = MKA(env, str_interface); - atom_multiaddr = MKA(env, str_multiaddr); - atom_nif_abort = MKA(env, str_nif_abort); - atom_num_dinet = MKA(env, str_num_dinet); - atom_num_dinet6 = MKA(env, str_num_dinet6); - atom_num_dlocal = MKA(env, str_num_dlocal); - atom_num_pip = MKA(env, str_num_pip); - atom_num_psctp = MKA(env, str_num_psctp); - atom_num_ptcp = MKA(env, str_num_ptcp); - atom_num_pudp = MKA(env, str_num_pudp); - atom_num_sockets = MKA(env, str_num_sockets); - atom_num_tdgrams = MKA(env, str_num_tdgrams); - atom_num_tseqpkgs = MKA(env, str_num_tseqpkgs); - atom_num_tstreams = MKA(env, str_num_tstreams); - atom_probe = MKA(env, str_probe); - atom_select = MKA(env, str_select); - atom_sourceaddr = MKA(env, str_sourceaddr); - atom_timeout = MKA(env, str_timeout); - atom_true = MKA(env, str_true); - atom_want = MKA(env, str_want); + atom_adaptation_layer = MKA(env, str_adaptation_layer); + atom_address = MKA(env, str_address); + atom_association = MKA(env, str_association); + atom_authentication = MKA(env, str_authentication); + atom_close = MKA(env, str_close); + atom_closed = MKA(env, str_closed); + atom_closing = MKA(env, str_closing); + atom_data_in = MKA(env, str_data_in); + atom_do = MKA(env, str_do); + atom_dont = MKA(env, str_dont); + atom_false = MKA(env, str_false); + atom_global_counters = MKA(env, str_global_counters); + atom_in4_sockaddr = MKA(env, str_in4_sockaddr); + atom_in6_sockaddr = MKA(env, str_in6_sockaddr); + atom_iow = MKA(env, str_iow); + atom_interface = MKA(env, str_interface); + atom_multiaddr = MKA(env, str_multiaddr); + atom_nif_abort = MKA(env, str_nif_abort); + atom_num_dinet = MKA(env, str_num_dinet); + atom_num_dinet6 = MKA(env, str_num_dinet6); + atom_num_dlocal = MKA(env, str_num_dlocal); + atom_num_pip = MKA(env, str_num_pip); + atom_num_psctp = MKA(env, str_num_psctp); + atom_num_ptcp = MKA(env, str_num_ptcp); + atom_num_pudp = MKA(env, str_num_pudp); + atom_num_sockets = MKA(env, str_num_sockets); + atom_num_tdgrams = MKA(env, str_num_tdgrams); + atom_num_tseqpkgs = MKA(env, str_num_tseqpkgs); + atom_num_tstreams = MKA(env, str_num_tstreams); + atom_partial_delivery = MKA(env, str_partial_delivery); + atom_peer_error = MKA(env, str_peer_error); + atom_probe = MKA(env, str_probe); + atom_select = MKA(env, str_select); + atom_sender_dry = MKA(env, str_sender_dry); + atom_send_failure = MKA(env, str_send_failure); + atom_shutdown = MKA(env, str_shutdown); + atom_sourceaddr = MKA(env, str_sourceaddr); + atom_timeout = MKA(env, str_timeout); + atom_true = MKA(env, str_true); + atom_want = MKA(env, str_want); /* Global atom(s) */ esock_atom_addr = MKA(env, "addr"); diff --git a/erts/preloaded/ebin/socket.beam b/erts/preloaded/ebin/socket.beam index cb1c5fc815..fd62a18a49 100644 Binary files a/erts/preloaded/ebin/socket.beam and b/erts/preloaded/ebin/socket.beam differ diff --git a/erts/preloaded/src/socket.erl b/erts/preloaded/src/socket.erl index ead058c607..74b6bfd543 100644 --- a/erts/preloaded/src/socket.erl +++ b/erts/preloaded/src/socket.erl @@ -94,6 +94,7 @@ ip_pmtudisc/0, ipv6_mreq/0, ipv6_pmtudisc/0, + sctp_event_subscribe/0, msg_hdr/0 @@ -150,6 +151,14 @@ %% socket:setopt(Socket, ip, drop_membership, #{multiaddr => Addr, %% interface => any}). %% + +%% If the integer value is used its up to the caller to ensure its valid! +-type ip_tos_flag() :: lowdeley | + throughput | + reliability | + mincost | + integer(). + -type ip_mreq() :: #{multiaddr := ip4_address(), interface := any | ip4_address()}. %% -type ip_mreqn() :: #{multiaddr := ip4_address(), @@ -167,6 +176,17 @@ -type ipv6_pmtudisc() :: ip_pmtudisc(). +-type sctp_event_subscribe() :: #{data_in := boolean(), + association := boolean(), + address := boolean(), + send_failure := boolean(), + peer_error := boolean(), + shutdown := boolean(), + partial_delivery := boolean(), + adaptation_layer := boolean(), + authentication := boolean(), + sender_dry := boolean()}. + -type sockaddr_un() :: #{family := local, path := binary() | string()}. -type sockaddr_in4() :: #{family := inet, @@ -331,9 +351,9 @@ -type tcp_socket_option() :: congestion | cork | info | + keepcnt | keepidle | keepintvl | - keepcnt | maxseg | md5sig | nodelay | @@ -388,13 +408,6 @@ %% sctp_socket_option() | %% plain_socket_option(). -%% If the integer value is used its up to the caller to ensure its valid! --type ip_tos_flag() :: lowdeley | - throughput | - reliability | - mincost | - integer(). - -type socket_info() :: #{domain => domain(), type => type(), protocol => protocol()}. @@ -609,8 +622,17 @@ -define(SOCKET_OPT_TCP_CONGESTION, 1). -define(SOCKET_OPT_TCP_CORK, 2). --define(SOCKET_OPT_TCP_MAXSEG, 3). --define(SOCKET_OPT_TCP_NODELAY, 4). +%% -define(SOCKET_OPT_TCP_INFO, 3). +%% -define(SOCKET_OPT_TCP_KEEPCNT, 4). +%% -define(SOCKET_OPT_TCP_KEEPIDLE, 5). +%% -define(SOCKET_OPT_TCP_KEEPINTVL, 6). +-define(SOCKET_OPT_TCP_MAXSEG, 7). +%% -define(SOCKET_OPT_TCP_MD5SIG, 8). +-define(SOCKET_OPT_TCP_NODELAY, 9). +%% -define(SOCKET_OPT_TCP_NOOPT, 10). +%% -define(SOCKET_OPT_TCP_NOPUSH, 11). +%% -define(SOCKET_OPT_TCP_SYNCNT, 12). +%% -define(SOCKET_OPT_TCP_USER_TIMEOUT, 13). -define(SOCKET_OPT_UDP_CORK, 1). @@ -627,24 +649,25 @@ %% -define(SOCKET_OPT_SCTP_DELAYED_ACK_TIME, 11). %% -define(SOCKET_OPT_SCTP_DISABLE_FRAGMENTS, 12). %% -define(SOCKET_OPT_SCTP_HMAC_IDENT, 13). -%% -define(SOCKET_OPT_SCTP_EXPLICIT_EOR, 14). -%% -define(SOCKET_OPT_SCTP_FRAGMENT_INTERLEAVE, 15). -%% -define(SOCKET_OPT_SCTP_GET_PEER_ADDR_INFO, 16). -%% -define(SOCKET_OPT_SCTP_INITMSG, 17). -%% -define(SOCKET_OPT_SCTP_I_WANT_MAPPED_V4_ADDR, 18). -%% -define(SOCKET_OPT_SCTP_LOCAL_AUTH_CHUNKS, 19). -%% -define(SOCKET_OPT_SCTP_MAXSEG, 20). -%% -define(SOCKET_OPT_SCTP_MAXBURST, 21). --define(SOCKET_OPT_SCTP_NODELAY, 22). -%% -define(SOCKET_OPT_SCTP_PARTIAL_DELIVERY_POINT, 23). -%% -define(SOCKET_OPT_SCTP_PEER_ADDR_PARAMS, 24). -%% -define(SOCKET_OPT_SCTP_PEER_AUTH_CHUNKS, 25). -%% -define(SOCKET_OPT_SCTP_PRIMARY_ADDR, 26). -%% -define(SOCKET_OPT_SCTP_RESET_STREAMS, 27). -%% -define(SOCKET_OPT_SCTP_RTOINFO, 28). -%% -define(SOCKET_OPT_SCTP_SET_PEER_PRIMARY_ADDR, 29). -%% -define(SOCKET_OPT_SCTP_STATUS, 30). -%% -define(SOCKET_OPT_SCTP_USE_EXT_RECVINFO, 31). +-define(SOCKET_OPT_SCTP_EVENTS, 14). +%% -define(SOCKET_OPT_SCTP_EXPLICIT_EOR, 15). +%% -define(SOCKET_OPT_SCTP_FRAGMENT_INTERLEAVE, 16). +%% -define(SOCKET_OPT_SCTP_GET_PEER_ADDR_INFO, 17). +%% -define(SOCKET_OPT_SCTP_INITMSG, 18). +%% -define(SOCKET_OPT_SCTP_I_WANT_MAPPED_V4_ADDR, 19). +%% -define(SOCKET_OPT_SCTP_LOCAL_AUTH_CHUNKS, 20). +%% -define(SOCKET_OPT_SCTP_MAXSEG, 21). +%% -define(SOCKET_OPT_SCTP_MAXBURST, 22). +-define(SOCKET_OPT_SCTP_NODELAY, 23). +%% -define(SOCKET_OPT_SCTP_PARTIAL_DELIVERY_POINT, 24). +%% -define(SOCKET_OPT_SCTP_PEER_ADDR_PARAMS, 25). +%% -define(SOCKET_OPT_SCTP_PEER_AUTH_CHUNKS, 26). +%% -define(SOCKET_OPT_SCTP_PRIMARY_ADDR, 27). +%% -define(SOCKET_OPT_SCTP_RESET_STREAMS, 28). +%% -define(SOCKET_OPT_SCTP_RTOINFO, 29). +%% -define(SOCKET_OPT_SCTP_SET_PEER_PRIMARY_ADDR, 30). +%% -define(SOCKET_OPT_SCTP_STATUS, 31). +%% -define(SOCKET_OPT_SCTP_USE_EXT_RECVINFO, 32). -define(SOCKET_SHUTDOWN_HOW_READ, 0). -define(SOCKET_SHUTDOWN_HOW_WRITE, 1). @@ -1736,6 +1759,7 @@ setopt(#socket{info = Info, ref = SockRef}, Level, Key, Value) -> + %% =========================================================================== %% %% getopt - retrieve individual properties of a socket @@ -1942,6 +1966,8 @@ enc_setopt_level(tcp) -> {true, ?SOCKET_OPT_LEVEL_TCP}; enc_setopt_level(udp) -> {true, ?SOCKET_OPT_LEVEL_UDP}; +enc_setopt_level(sctp) -> + {true, ?SOCKET_OPT_LEVEL_SCTP}; %% Any option that is of an plain level must be provided as a binary %% already fully encoded! enc_setopt_level(L) when is_integer(L) -> @@ -2141,20 +2167,38 @@ enc_setopt_value(tcp = L, Opt, V, _D, _T, _P) -> not_supported({L, Opt, V}); enc_setopt_value(udp, cork, V, _D, T, P) - when is_boolean(V) andalso - (T =:= dgram) andalso - (P =:= udp) -> + when is_boolean(V) andalso (T =:= dgram) andalso (P =:= udp) -> V; enc_setopt_value(udp = L, Opt, _V, _D, _T, _P) -> not_supported({L, Opt}); enc_setopt_value(sctp, autoclose, V, _D, _T, P) - when is_integer(V) andalso + when is_integer(V) andalso (P =:= sctp) -> + V; +enc_setopt_value(sctp, events, #{data_in := DataIn, + association := Assoc, + address := Addr, + send_failure := SndFailure, + peer_error := PeerError, + shutdown := Shutdown, + partial_delivery := PartialDelivery, + adaptation_layer := AdaptLayer, + authentication := Auth, + sender_dry := SndDry} = V, _D, _T, P) + when is_boolean(DataIn) andalso + is_boolean(Assoc) andalso + is_boolean(Addr) andalso + is_boolean(SndFailure) andalso + is_boolean(PeerError) andalso + is_boolean(Shutdown) andalso + is_boolean(PartialDelivery) andalso + is_boolean(AdaptLayer) andalso + is_boolean(Auth) andalso + is_boolean(SndDry) andalso (P =:= sctp) -> V; enc_setopt_value(sctp, nodelay, V, _D, _T, P) - when is_boolean(V) andalso - (P =:= sctp) -> + when is_boolean(V) andalso (P =:= sctp) -> V; enc_setopt_value(L, Opt, V, _, _, _) @@ -2572,8 +2616,8 @@ enc_sockopt_key(sctp = L, disable_fragments = Opt, _Dir, _D, _T, _P) -> not_supported({L, Opt}); enc_sockopt_key(sctp = L, hmac_ident = Opt, _Dir, _D, _T, _P) -> not_supported({L, Opt}); -enc_sockopt_key(sctp = L, events = Opt, _Dir, _D, _T, _P) -> - not_supported({L, Opt}); +enc_sockopt_key(sctp = _L, events = _Opt, set = _Dir, _D, _T, _P) -> + ?SOCKET_OPT_SCTP_EVENTS; enc_sockopt_key(sctp = L, explicit_eor = Opt, _Dir, _D, _T, _P) -> not_supported({L, Opt}); enc_sockopt_key(sctp = L, fragment_interleave = Opt, _Dir, _D, _T, _P) -> @@ -2727,7 +2771,6 @@ tdiff(T1, T2) -> - %% p(F) -> %% p(F, []). diff --git a/lib/kernel/test/socket_server.erl b/lib/kernel/test/socket_server.erl index 65069df60b..23f30a0d03 100644 --- a/lib/kernel/test/socket_server.erl +++ b/lib/kernel/test/socket_server.erl @@ -25,7 +25,8 @@ start_tcp/0, start_tcp/1, start_tcp/2, start_tcp4/1, start_tcp6/1, start_udp/0, start_udp/1, start_udp/2, - start_udp4/1, start_udp6/1 + start_udp4/1, start_udp6/1, + start_sctp/0, start_sctp/1 ]). -define(LIB, socket_lib). @@ -69,6 +70,13 @@ start_udp6(Peek) -> start_udp(Domain, Peek) when is_boolean(Peek) -> start(Domain, dgram, udp, Peek). + +start_sctp() -> + start_sctp(inet). + +start_sctp(Domain) when ((Domain =:= inet) orelse (Domain =:= inet6)) -> + start(Domain, seqpacket, sctp, false). + start(Domain, Type, Proto, Peek) -> put(sname, "starter"), i("try start manager"), @@ -103,8 +111,11 @@ manager_reply(Pid, Ref, Reply) -> ?LIB:reply(manager, Pid, Ref, Reply). -manager_init(Domain, stream = Type, Proto, Peek) -> +manager_init(Domain, Type, Proto, Peek) -> put(sname, "manager"), + do_manager_init(Domain, Type, Proto, Peek). + +do_manager_init(Domain, stream = Type, Proto, Peek) -> i("try start acceptor(s)"), {Sock, Acceptors} = manager_stream_init(Domain, Type, Proto), manager_loop(#manager{socket = Sock, @@ -112,8 +123,7 @@ manager_init(Domain, stream = Type, Proto, Peek) -> acceptors = Acceptors, handler_id = 1, handlers = []}); -manager_init(Domain, dgram = Type, Proto, Peek) -> - put(sname, "manager"), +do_manager_init(Domain, dgram = Type, Proto, Peek) -> i("try open socket"), case socket:open(Domain, Type, Proto) of {ok, Sock} -> @@ -170,9 +180,39 @@ manager_init(Domain, dgram = Type, Proto, Peek) -> e("Failed open socket: " "~n ~p", [OReason]), exit({failed_open_socket, OReason}) + end; +do_manager_init(Domain, seqpacket = Type, sctp = Proto, _Peek) -> + %% This is as far as I have got with SCTP at the moment... + case socket:open(Domain, Type, Proto) of + {ok, Sock} -> + i("(sctp) socket opened: " + "~n ~p", [Sock]), + F = fun(_Desc, Expect, Expect) -> + Expect; + (Desc, Expect, Actual) -> + e("Unexpected result ~w: " + "~n Expect: ~p" + "~n Actual: ~p", [Desc, Expect, Actual]), + exit({Desc, Expect, Actual}) + end, + Events = #{data_in => true, + association => true, + address => true, + send_failure => true, + peer_error => true, + shutdown => true, + partial_delivery => true, + adaptation_layer => true, + authentication => true, + sender_dry => true}, + F(set_sctp_events, ok, socket:setopt(Sock, sctp, events, Events)), + F(close_socket, ok, socket:close(Sock)); + {error, Reason} -> + exit({failed_open, Reason}) end. + manager_stream_init(Domain, Type, Proto) -> i("try (socket) open"), Sock = case socket:open(Domain, Type, Proto) of -- cgit v1.2.3 From f0a2e68a31ac585780ad05f777f1b7551770420e Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Thu, 19 Jul 2018 14:36:34 +0200 Subject: [socket-nif] Add support for socket (level sctp) option disable_fragments Added support for the SCTP option DISABLE_FRAGMENTS. OTP-14831 --- erts/doc/src/socket_usage.xml | 7 +++++ erts/emulator/nifs/common/socket_nif.c | 53 +++++++++++++++++++++++++++++++-- erts/preloaded/ebin/socket.beam | Bin 52584 -> 52640 bytes erts/preloaded/src/socket.erl | 9 ++++-- lib/kernel/test/socket_server.erl | 27 ++++++++++------- 5 files changed, 80 insertions(+), 16 deletions(-) diff --git a/erts/doc/src/socket_usage.xml b/erts/doc/src/socket_usage.xml index 1d7b98d1a3..9f25e2e9b9 100644 --- a/erts/doc/src/socket_usage.xml +++ b/erts/doc/src/socket_usage.xml @@ -458,6 +458,13 @@ yes none + + disable_fragments + boolean() + yes + yes + none + events sctp_event_subscribe() diff --git a/erts/emulator/nifs/common/socket_nif.c b/erts/emulator/nifs/common/socket_nif.c index 7271db2143..2a60a840c8 100644 --- a/erts/emulator/nifs/common/socket_nif.c +++ b/erts/emulator/nifs/common/socket_nif.c @@ -542,9 +542,10 @@ typedef union { #define SOCKET_OPT_UDP_CORK 1 -#define SOCKET_OPT_SCTP_AUTOCLOSE 8 -#define SOCKET_OPT_SCTP_EVENTS 14 -#define SOCKET_OPT_SCTP_NODELAY 23 +#define SOCKET_OPT_SCTP_AUTOCLOSE 8 +#define SOCKET_OPT_SCTP_DISABLE_FRAGMENTS 12 +#define SOCKET_OPT_SCTP_EVENTS 14 +#define SOCKET_OPT_SCTP_NODELAY 23 /* =================================================================== * @@ -1216,6 +1217,11 @@ static ERL_NIF_TERM nsetopt_lvl_sctp_autoclose(ErlNifEnv* env, SocketDescriptor* descP, ERL_NIF_TERM eVal); #endif +#if defined(SCTP_DISABLE_FRAGMENTS) +static ERL_NIF_TERM nsetopt_lvl_sctp_disable_fragments(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); +#endif #if defined(SCTP_EVENTS) static ERL_NIF_TERM nsetopt_lvl_sctp_events(ErlNifEnv* env, SocketDescriptor* descP, @@ -1433,6 +1439,10 @@ static ERL_NIF_TERM ngetopt_lvl_sctp(ErlNifEnv* env, static ERL_NIF_TERM ngetopt_lvl_sctp_autoclose(ErlNifEnv* env, SocketDescriptor* descP); #endif +#if defined(SCTP_DISABLE_FRAGMENTS) +static ERL_NIF_TERM ngetopt_lvl_sctp_disable_fragments(ErlNifEnv* env, + SocketDescriptor* descP); +#endif #if defined(SCTP_NODELAY) static ERL_NIF_TERM ngetopt_lvl_sctp_nodelay(ErlNifEnv* env, SocketDescriptor* descP); @@ -5509,6 +5519,12 @@ ERL_NIF_TERM nsetopt_lvl_sctp(ErlNifEnv* env, break; #endif +#if defined(SCTP_DISABLE_FRAGMENTS) + case SOCKET_OPT_SCTP_DISABLE_FRAGMENTS: + result = nsetopt_lvl_sctp_disable_fragments(env, descP, eVal); + break; +#endif + #if defined(SCTP_EVENTS) case SOCKET_OPT_SCTP_EVENTS: result = nsetopt_lvl_sctp_events(env, descP, eVal); @@ -5543,6 +5559,19 @@ ERL_NIF_TERM nsetopt_lvl_sctp_autoclose(ErlNifEnv* env, #endif +/* nsetopt_lvl_sctp_disable_fragments - Level SCTP DISABLE_FRAGMENTS option + */ +#if defined(SCTP_DISABLE_FRAGMENTS) +static +ERL_NIF_TERM nsetopt_lvl_sctp_disable_fragments(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) +{ + return nsetopt_bool_opt(env, descP, IPPROTO_SCTP, SCTP_DISABLE_FRAGMENTS, eVal); +} +#endif + + /* nsetopt_lvl_sctp_events - Level SCTP EVENTS option */ #if defined(SCTP_EVENTS) @@ -7308,6 +7337,12 @@ ERL_NIF_TERM ngetopt_lvl_sctp(ErlNifEnv* env, break; #endif +#if defined(SCTP_DISABLE_FRAGMENTS) + case SOCKET_OPT_SCTP_DISABLE_FRAGMENTS: + result = ngetopt_lvl_sctp_disable_fragments(env, descP); + break; +#endif + #if defined(SCTP_NODELAY) case SOCKET_OPT_SCTP_NODELAY: result = ngetopt_lvl_sctp_nodelay(env, descP); @@ -7335,6 +7370,18 @@ ERL_NIF_TERM ngetopt_lvl_sctp_autoclose(ErlNifEnv* env, #endif +/* ngetopt_lvl_sctp_disable_fragments - Level SCTP DISABLE:FRAGMENTS option + */ +#if defined(SCTP_DISABLE_FRAGMENTS) +static +ERL_NIF_TERM ngetopt_lvl_sctp_disable_fragments(ErlNifEnv* env, + SocketDescriptor* descP) +{ + return ngetopt_bool_opt(env, descP, IPPROTO_SCTP, SCTP_DISABLE_FRAGMENTS); +} +#endif + + /* ngetopt_lvl_sctp_nodelay - Level SCTP NODELAY option */ #if defined(SCTP_NODELAY) diff --git a/erts/preloaded/ebin/socket.beam b/erts/preloaded/ebin/socket.beam index fd62a18a49..63887692f9 100644 Binary files a/erts/preloaded/ebin/socket.beam and b/erts/preloaded/ebin/socket.beam differ diff --git a/erts/preloaded/src/socket.erl b/erts/preloaded/src/socket.erl index 74b6bfd543..e3fb417a35 100644 --- a/erts/preloaded/src/socket.erl +++ b/erts/preloaded/src/socket.erl @@ -647,7 +647,7 @@ %% -define(SOCKET_OPT_SCTP_CONTEXT, 9). %% -define(SOCKET_OPT_SCTP_DEFAULT_SEND_PARAMS, 10). %% -define(SOCKET_OPT_SCTP_DELAYED_ACK_TIME, 11). -%% -define(SOCKET_OPT_SCTP_DISABLE_FRAGMENTS, 12). +-define(SOCKET_OPT_SCTP_DISABLE_FRAGMENTS, 12). %% -define(SOCKET_OPT_SCTP_HMAC_IDENT, 13). -define(SOCKET_OPT_SCTP_EVENTS, 14). %% -define(SOCKET_OPT_SCTP_EXPLICIT_EOR, 15). @@ -2175,6 +2175,9 @@ enc_setopt_value(udp = L, Opt, _V, _D, _T, _P) -> enc_setopt_value(sctp, autoclose, V, _D, _T, P) when is_integer(V) andalso (P =:= sctp) -> V; +enc_setopt_value(sctp, disable_fragments, V, _D, _T, P) + when is_boolean(V) andalso (P =:= sctp) -> + V; enc_setopt_value(sctp, events, #{data_in := DataIn, association := Assoc, address := Addr, @@ -2612,8 +2615,8 @@ enc_sockopt_key(sctp = L, default_send_params = Opt, _Dir, _D, _T, _P) -> not_supported({L, Opt}); enc_sockopt_key(sctp = L, delayed_ack_time = Opt, _Dir, _D, _T, _P) -> not_supported({L, Opt}); -enc_sockopt_key(sctp = L, disable_fragments = Opt, _Dir, _D, _T, _P) -> - not_supported({L, Opt}); +enc_sockopt_key(sctp = _L, disable_fragments = _Opt, _Dir, _D, _T, _P) -> + ?SOCKET_OPT_SCTP_DISABLE_FRAGMENTS; enc_sockopt_key(sctp = L, hmac_ident = Opt, _Dir, _D, _T, _P) -> not_supported({L, Opt}); enc_sockopt_key(sctp = _L, events = _Opt, set = _Dir, _D, _T, _P) -> diff --git a/lib/kernel/test/socket_server.erl b/lib/kernel/test/socket_server.erl index 23f30a0d03..a9b4aca5f8 100644 --- a/lib/kernel/test/socket_server.erl +++ b/lib/kernel/test/socket_server.erl @@ -187,14 +187,21 @@ do_manager_init(Domain, seqpacket = Type, sctp = Proto, _Peek) -> {ok, Sock} -> i("(sctp) socket opened: " "~n ~p", [Sock]), - F = fun(_Desc, Expect, Expect) -> - Expect; - (Desc, Expect, Actual) -> - e("Unexpected result ~w: " - "~n Expect: ~p" - "~n Actual: ~p", [Desc, Expect, Actual]), - exit({Desc, Expect, Actual}) - end, + EXP = fun(_Desc, Expect, Expect) -> + Expect; + (Desc, Expect, Actual) -> + e("Unexpected result ~w: " + "~n Expect: ~p" + "~n Actual: ~p", [Desc, Expect, Actual]), + exit({Desc, Expect, Actual}) + end, + GO = fun(O) -> case socket:getopt(Sock, sctp, O) of + {ok, V} -> f("~p", [V]); + {error, R} -> f("error: ~p", [R]) + end + end, + i("Miscellaneous options: " + "~n disable-fragments: ~s", [GO(disable_fragments)]), Events = #{data_in => true, association => true, address => true, @@ -205,8 +212,8 @@ do_manager_init(Domain, seqpacket = Type, sctp = Proto, _Peek) -> adaptation_layer => true, authentication => true, sender_dry => true}, - F(set_sctp_events, ok, socket:setopt(Sock, sctp, events, Events)), - F(close_socket, ok, socket:close(Sock)); + EXP(set_sctp_events, ok, socket:setopt(Sock, sctp, events, Events)), + EXP(close_socket, ok, socket:close(Sock)); {error, Reason} -> exit({failed_open, Reason}) end. -- cgit v1.2.3 From 7a5b320b5bb5ec45b21839005e8538172908fb57 Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Thu, 19 Jul 2018 16:39:13 +0200 Subject: [socket-nif] Add (partial) support for socket (level sctp) option associnfo Added support for the SCTP option ASSOCINFO. This option is a bit tricky. As the underlying structure (sctp_assocparams) contains the assoc_id, it begs the question what happens if this option is fetched for: * The own assoc (which means that we might have the assoc id in the descriptor and can initiate that part of the struct accordningly). * Another assoc: From assoc A asks for info with assoc_id set to that of assoc B. * The "owning" endpoint. * Another endpoint (an endpoint to which the assoc does not belong). So, if the user calls socket:[getopt|setopt] for an association socket, shall we require that the assoc_id field is set to -1? Or not set at all and therefor filled in automatically by the nif-code? And, if the user calls socket:[getopt|setopt] for an endpoint socket, shall we require that the assoc_id field is set to a valid id? Or shall it not be allowed? Questions, questions... OTP-14831 --- erts/doc/src/socket.xml | 3 + erts/doc/src/socket_usage.xml | 9 +- erts/emulator/nifs/common/socket_int.h | 1 + erts/emulator/nifs/common/socket_nif.c | 224 ++++++++++++++++++++++++++++++++- erts/preloaded/ebin/socket.beam | Bin 52640 -> 53256 bytes erts/preloaded/src/socket.erl | 31 ++++- lib/kernel/test/socket_server.erl | 8 +- 7 files changed, 269 insertions(+), 7 deletions(-) diff --git a/erts/doc/src/socket.xml b/erts/doc/src/socket.xml index 4ecf35b8ed..3e8e7af5c6 100644 --- a/erts/doc/src/socket.xml +++ b/erts/doc/src/socket.xml @@ -137,6 +137,9 @@ + + + diff --git a/erts/doc/src/socket_usage.xml b/erts/doc/src/socket_usage.xml index 9f25e2e9b9..60cb424cde 100644 --- a/erts/doc/src/socket_usage.xml +++ b/erts/doc/src/socket_usage.xml @@ -451,9 +451,16 @@ Get Other + + associnfo + sctp_assocparams() + yes + yes + none + autoclose - integer() + non_neg_integer() yes yes none diff --git a/erts/emulator/nifs/common/socket_int.h b/erts/emulator/nifs/common/socket_int.h index 67e4baba27..28f42a7345 100644 --- a/erts/emulator/nifs/common/socket_int.h +++ b/erts/emulator/nifs/common/socket_int.h @@ -167,6 +167,7 @@ extern ERL_NIF_TERM esock_atom_einval; #define MKT8(E,E1,E2,E3,E4,E5,E6,E7,E8) \ enif_make_tuple8((E), (E1), (E2), (E3), (E4), (E5), (E6), (E7), (E8)) #define MKTA(E, A, AL) enif_make_tuple_from_array((E), (A), (AL)) +#define MKUI(E,I) enif_make_uint((E), (I)) #define MCREATE(N) enif_mutex_create((N)) #define MDESTROY(M) enif_mutex_destroy((M)) diff --git a/erts/emulator/nifs/common/socket_nif.c b/erts/emulator/nifs/common/socket_nif.c index 2a60a840c8..50e7ec5f5e 100644 --- a/erts/emulator/nifs/common/socket_nif.c +++ b/erts/emulator/nifs/common/socket_nif.c @@ -542,6 +542,7 @@ typedef union { #define SOCKET_OPT_UDP_CORK 1 +#define SOCKET_OPT_SCTP_ASSOCINFO 2 #define SOCKET_OPT_SCTP_AUTOCLOSE 8 #define SOCKET_OPT_SCTP_DISABLE_FRAGMENTS 12 #define SOCKET_OPT_SCTP_EVENTS 14 @@ -1212,6 +1213,11 @@ static ERL_NIF_TERM nsetopt_lvl_sctp(ErlNifEnv* env, SocketDescriptor* descP, int eOpt, ERL_NIF_TERM eVal); +#if defined(SCTP_ASSOCINFO) +static ERL_NIF_TERM nsetopt_lvl_sctp_associnfo(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); +#endif #if defined(SCTP_AUTOCLOSE) static ERL_NIF_TERM nsetopt_lvl_sctp_autoclose(ErlNifEnv* env, SocketDescriptor* descP, @@ -1435,6 +1441,10 @@ static ERL_NIF_TERM ngetopt_lvl_udp_cork(ErlNifEnv* env, static ERL_NIF_TERM ngetopt_lvl_sctp(ErlNifEnv* env, SocketDescriptor* descP, int eOpt); +#if defined(SCTP_ASSOCINFO) +static ERL_NIF_TERM ngetopt_lvl_sctp_associnfo(ErlNifEnv* env, + SocketDescriptor* descP); +#endif #if defined(SCTP_AUTOCLOSE) static ERL_NIF_TERM ngetopt_lvl_sctp_autoclose(ErlNifEnv* env, SocketDescriptor* descP); @@ -1780,11 +1790,13 @@ static const struct in6_addr in6addr_loopback = static char str_adaptation_layer[] = "adaptation_layer"; static char str_address[] = "address"; static char str_association[] = "association"; +static char str_assoc_id[] = "assoc_id"; static char str_authentication[] = "authentication"; // static char str_any[] = "any"; static char str_close[] = "close"; static char str_closed[] = "closed"; static char str_closing[] = "closing"; +static char str_cookie_life[] = "cookie_life"; static char str_data_in[] = "data_in"; static char str_do[] = "do"; static char str_dont[] = "dont"; @@ -1794,12 +1806,15 @@ static char str_in4_sockaddr[] = "in4_sockaddr"; static char str_in6_sockaddr[] = "in6_sockaddr"; static char str_iow[] = "iow"; static char str_interface[] = "interface"; +static char str_local_rwnd[] = "local_rwnd"; // static char str_loopback[] = "loopback"; +static char str_max_rxt[] = "max_rxt"; static char str_multiaddr[] = "multiaddr"; static char str_nif_abort[] = "nif_abort"; static char str_num_dlocal[] = "num_domain_local"; static char str_num_dinet[] = "num_domain_inet"; static char str_num_dinet6[] = "num_domain_inet6"; +static char str_num_peer_dests[] = "num_peer_dests"; static char str_num_pip[] = "num_proto_ip"; static char str_num_psctp[] = "num_proto_sctp"; static char str_num_ptcp[] = "num_proto_tcp"; @@ -1810,6 +1825,7 @@ static char str_num_tseqpkgs[] = "num_type_seqpacket"; static char str_num_tstreams[] = "num_type_stream"; static char str_partial_delivery[] = "partial_delivery"; static char str_peer_error[] = "peer_error"; +static char str_peer_rwnd[] = "peer_rwnd"; static char str_probe[] = "probe"; static char str_select[] = "select"; static char str_sender_dry[] = "sender_dry"; @@ -1878,10 +1894,12 @@ ERL_NIF_TERM esock_atom_einval; static ERL_NIF_TERM atom_adaptation_layer; static ERL_NIF_TERM atom_address; static ERL_NIF_TERM atom_association; +static ERL_NIF_TERM atom_assoc_id; static ERL_NIF_TERM atom_authentication; static ERL_NIF_TERM atom_close; static ERL_NIF_TERM atom_closed; static ERL_NIF_TERM atom_closing; +static ERL_NIF_TERM atom_cookie_life; static ERL_NIF_TERM atom_data_in; static ERL_NIF_TERM atom_do; static ERL_NIF_TERM atom_dont; @@ -1891,11 +1909,14 @@ static ERL_NIF_TERM atom_in4_sockaddr; static ERL_NIF_TERM atom_in6_sockaddr; static ERL_NIF_TERM atom_iow; static ERL_NIF_TERM atom_interface; +static ERL_NIF_TERM atom_local_rwnd; +static ERL_NIF_TERM atom_max_rxt; static ERL_NIF_TERM atom_multiaddr; static ERL_NIF_TERM atom_nif_abort; static ERL_NIF_TERM atom_num_dinet; static ERL_NIF_TERM atom_num_dinet6; static ERL_NIF_TERM atom_num_dlocal; +static ERL_NIF_TERM atom_num_peer_dests; static ERL_NIF_TERM atom_num_pip; static ERL_NIF_TERM atom_num_psctp; static ERL_NIF_TERM atom_num_ptcp; @@ -1906,6 +1927,7 @@ static ERL_NIF_TERM atom_num_tseqpkgs; static ERL_NIF_TERM atom_num_tstreams; static ERL_NIF_TERM atom_partial_delivery; static ERL_NIF_TERM atom_peer_error; +static ERL_NIF_TERM atom_peer_rwnd; static ERL_NIF_TERM atom_probe; static ERL_NIF_TERM atom_select; static ERL_NIF_TERM atom_sender_dry; @@ -2042,7 +2064,7 @@ ERL_NIF_TERM nif_info(ErlNifEnv* env, ERL_NIF_TERM vals[] = {BOOL2ATOM(data.dbg), BOOL2ATOM(data.iow), lgcnt}; ERL_NIF_TERM info; unsigned int numKeys = sizeof(keys) / sizeof(ERL_NIF_TERM); - unsigned int numVals = sizeof(keys) / sizeof(ERL_NIF_TERM); + unsigned int numVals = sizeof(vals) / sizeof(ERL_NIF_TERM); ESOCK_ASSERT( (numKeys == numVals) ); @@ -5513,6 +5535,12 @@ ERL_NIF_TERM nsetopt_lvl_sctp(ErlNifEnv* env, "\r\n", eOpt) ); switch (eOpt) { +#if defined(SCTP_ASSOCINFO) + case SOCKET_OPT_SCTP_ASSOCINFO: + result = nsetopt_lvl_sctp_associnfo(env, descP, eVal); + break; +#endif + #if defined(SCTP_AUTOCLOSE) case SOCKET_OPT_SCTP_AUTOCLOSE: result = nsetopt_lvl_sctp_autoclose(env, descP, eVal); @@ -5546,6 +5574,116 @@ ERL_NIF_TERM nsetopt_lvl_sctp(ErlNifEnv* env, } +/* nsetopt_lvl_sctp_associnfo - Level SCTP ASSOCINFO option + */ +#if defined(SCTP_ASSOCINFO) +static +ERL_NIF_TERM nsetopt_lvl_sctp_associnfo(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) +{ + ERL_NIF_TERM result; + ERL_NIF_TERM eAssocId, eMaxRxt, eNumPeerDests; + ERL_NIF_TERM ePeerRWND, eLocalRWND, eCookieLife; + struct sctp_assocparams assocParams; + int res; + size_t sz; + unsigned int tmp; + + SSDBG( descP, + ("SOCKET", "nsetopt_lvl_sctp_associnfo -> entry with" + "\r\n eVal: %T" + "\r\n", eVal) ); + + // It must be a map + if (!IS_MAP(env, eVal)) + return esock_make_error(env, esock_atom_einval); + + // It must have atleast ten attributes + if (!enif_get_map_size(env, eVal, &sz) || (sz < 6)) + return esock_make_error(env, esock_atom_einval); + + SSDBG( descP, + ("SOCKET", "nsetopt_lvl_sctp_associnfo -> extract attributes\r\n") ); + + if (!GET_MAP_VAL(env, eVal, atom_assoc_id, &eAssocId)) + return esock_make_error(env, esock_atom_einval); + + if (!GET_MAP_VAL(env, eVal, atom_max_rxt, &eMaxRxt)) + return esock_make_error(env, esock_atom_einval); + + if (!GET_MAP_VAL(env, eVal, atom_num_peer_dests, &eNumPeerDests)) + return esock_make_error(env, esock_atom_einval); + + if (!GET_MAP_VAL(env, eVal, atom_peer_rwnd, &ePeerRWND)) + return esock_make_error(env, esock_atom_einval); + + if (!GET_MAP_VAL(env, eVal, atom_local_rwnd, &eLocalRWND)) + return esock_make_error(env, esock_atom_einval); + + if (!GET_MAP_VAL(env, eVal, atom_cookie_life, &eCookieLife)) + return esock_make_error(env, esock_atom_einval); + + SSDBG( descP, + ("SOCKET", "nsetopt_lvl_sctp_events -> decode attributes\r\n") ); + + if (!GET_INT(env, eAssocId, &assocParams.sasoc_assoc_id)) + return esock_make_error(env, esock_atom_einval); + + /* + * We should really make sure this is ok in erlang (to ensure that + * the value fits in 16-bits). + * The value should be a 16-bit unsigned int... + * Both sasoc_asocmaxrxt and sasoc_number_peer_destinations. + */ + + /* + if (!GET_UINT(env, eAssocId, (unsigned int*) &assocParams.sasoc_asocmaxrxt)) + return esock_make_error(env, esock_atom_einval); + */ + if (!GET_UINT(env, eAssocId, &tmp)) + return esock_make_error(env, esock_atom_einval); + assocParams.sasoc_asocmaxrxt = (uint16_t) tmp; + + /* + if (!GET_UINT(env, eAssocId, (unsigned int*) &assocParams.sasoc_number_peer_destinations)) + return esock_make_error(env, esock_atom_einval); + */ + if (!GET_UINT(env, eAssocId, &tmp)) + return esock_make_error(env, esock_atom_einval); + assocParams.sasoc_number_peer_destinations = (uint16_t) tmp; + + if (!GET_UINT(env, eAssocId, &assocParams.sasoc_peer_rwnd)) + return esock_make_error(env, esock_atom_einval); + + if (!GET_UINT(env, eAssocId, &assocParams.sasoc_local_rwnd)) + return esock_make_error(env, esock_atom_einval); + + if (!GET_UINT(env, eAssocId, &assocParams.sasoc_cookie_life)) + return esock_make_error(env, esock_atom_einval); + + SSDBG( descP, + ("SOCKET", "nsetopt_lvl_sctp_events -> set associnfo option\r\n") ); + + res = socket_setopt(descP->sock, IPPROTO_SCTP, SCTP_ASSOCINFO, + &assocParams, sizeof(assocParams)); + + if (res != 0) + result = esock_make_error_errno(env, sock_errno()); + else + result = esock_atom_ok; + + SSDBG( descP, + ("SOCKET", "nsetopt_lvl_sctp_associnfo -> done with" + "\r\n result: %T" + "\r\n", result) ); + + return result; + +} +#endif + + /* nsetopt_lvl_sctp_autoclose - Level SCTP AUTOCLOSE option */ #if defined(SCTP_AUTOCLOSE) @@ -7330,7 +7468,18 @@ ERL_NIF_TERM ngetopt_lvl_sctp(ErlNifEnv* env, { ERL_NIF_TERM result; + SSDBG( descP, + ("SOCKET", "ngetopt_lvl_sctp -> entry with" + "\r\n opt: %d" + "\r\n", eOpt) ); + switch (eOpt) { +#if defined(SCTP_ASSOCINFO) + case SOCKET_OPT_SCTP_ASSOCINFO: + result = ngetopt_lvl_sctp_associnfo(env, descP); + break; +#endif + #if defined(SCTP_AUTOCLOSE) case SOCKET_OPT_SCTP_AUTOCLOSE: result = ngetopt_lvl_sctp_autoclose(env, descP); @@ -7354,10 +7503,77 @@ ERL_NIF_TERM ngetopt_lvl_sctp(ErlNifEnv* env, break; } + SSDBG( descP, + ("SOCKET", "ngetopt_lvl_sctp -> done when" + "\r\n result: %T" + "\r\n", result) ); + return result; } +/* ngetopt_lvl_sctp_associnfo - Level SCTP ASSOCINFO option + * + * + * + * We should really specify which association this relates to, + * as it is now we get assoc-id = 0. If this socket is an + * association (and not an endpoint) then it will have an + * assoc id. But since the sctp support at present is "limited", + * we leave it for now. + * What do we do if this is an endpoint? Invalid op? + * + * + */ +#if defined(SCTP_ASSOCINFO) +static +ERL_NIF_TERM ngetopt_lvl_sctp_associnfo(ErlNifEnv* env, + SocketDescriptor* descP) +{ + ERL_NIF_TERM result; + struct sctp_assocparams val; + SOCKOPTLEN_T valSz = sizeof(val); + int res; + + SSDBG( descP, ("SOCKET", "ngetopt_lvl_sctp_associnfo -> entry\r\n") ); + + sys_memzero((char*) &val, valSz); + res = sock_getopt(descP->sock, IPPROTO_SCTP, SCTP_ASSOCINFO, &val, &valSz); + + if (res != 0) { + result = esock_make_error_errno(env, sock_errno()); + } else { + ERL_NIF_TERM eAssocParams; + ERL_NIF_TERM keys[] = {atom_assoc_id, atom_max_rxt, atom_num_peer_dests, + atom_peer_rwnd, atom_local_rwnd, atom_cookie_life}; + ERL_NIF_TERM vals[] = {MKUI(env, val.sasoc_assoc_id), + MKUI(env, val.sasoc_asocmaxrxt), + MKUI(env, val.sasoc_number_peer_destinations), + MKUI(env, val.sasoc_peer_rwnd), + MKUI(env, val.sasoc_local_rwnd), + MKUI(env, val.sasoc_cookie_life)}; + unsigned int numKeys = sizeof(keys) / sizeof(ERL_NIF_TERM); + unsigned int numVals = sizeof(vals) / sizeof(ERL_NIF_TERM); + + ESOCK_ASSERT( (numKeys == numVals) ); + + if (!MKMA(env, keys, vals, numKeys, &eAssocParams)) + return esock_make_error(env, esock_atom_einval);; + + result = esock_make_ok2(env, eAssocParams); + } + + SSDBG( descP, + ("SOCKET", "ngetopt_lvl_sctp_associnfo -> done with" + "\r\n res: %d" + "\r\n result: %T" + "\r\n", res, result) ); + + return result; +} +#endif + + /* ngetopt_lvl_sctp_autoclose - Level SCTP AUTOCLOSE option */ #if defined(SCTP_AUTOCLOSE) @@ -9610,10 +9826,12 @@ int on_load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info) atom_adaptation_layer = MKA(env, str_adaptation_layer); atom_address = MKA(env, str_address); atom_association = MKA(env, str_association); + atom_assoc_id = MKA(env, str_assoc_id); atom_authentication = MKA(env, str_authentication); atom_close = MKA(env, str_close); atom_closed = MKA(env, str_closed); atom_closing = MKA(env, str_closing); + atom_cookie_life = MKA(env, str_cookie_life); atom_data_in = MKA(env, str_data_in); atom_do = MKA(env, str_do); atom_dont = MKA(env, str_dont); @@ -9623,11 +9841,14 @@ int on_load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info) atom_in6_sockaddr = MKA(env, str_in6_sockaddr); atom_iow = MKA(env, str_iow); atom_interface = MKA(env, str_interface); + atom_local_rwnd = MKA(env, str_local_rwnd); + atom_max_rxt = MKA(env, str_max_rxt); atom_multiaddr = MKA(env, str_multiaddr); atom_nif_abort = MKA(env, str_nif_abort); atom_num_dinet = MKA(env, str_num_dinet); atom_num_dinet6 = MKA(env, str_num_dinet6); atom_num_dlocal = MKA(env, str_num_dlocal); + atom_num_peer_dests = MKA(env, str_num_peer_dests); atom_num_pip = MKA(env, str_num_pip); atom_num_psctp = MKA(env, str_num_psctp); atom_num_ptcp = MKA(env, str_num_ptcp); @@ -9637,6 +9858,7 @@ int on_load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info) atom_num_tseqpkgs = MKA(env, str_num_tseqpkgs); atom_num_tstreams = MKA(env, str_num_tstreams); atom_partial_delivery = MKA(env, str_partial_delivery); + atom_peer_rwnd = MKA(env, str_peer_rwnd); atom_peer_error = MKA(env, str_peer_error); atom_probe = MKA(env, str_probe); atom_select = MKA(env, str_select); diff --git a/erts/preloaded/ebin/socket.beam b/erts/preloaded/ebin/socket.beam index 63887692f9..0c496443bf 100644 Binary files a/erts/preloaded/ebin/socket.beam and b/erts/preloaded/ebin/socket.beam differ diff --git a/erts/preloaded/src/socket.erl b/erts/preloaded/src/socket.erl index e3fb417a35..feb2e25312 100644 --- a/erts/preloaded/src/socket.erl +++ b/erts/preloaded/src/socket.erl @@ -95,6 +95,7 @@ ipv6_mreq/0, ipv6_pmtudisc/0, sctp_event_subscribe/0, + sctp_assocparams/0, msg_hdr/0 @@ -187,6 +188,13 @@ authentication := boolean(), sender_dry := boolean()}. +-type sctp_assocparams() :: #{assoc_id := integer(), + max_rxt := non_neg_integer(), + num_peer_dests := non_neg_integer(), + peer_rwnd := non_neg_integer(), + local_rwnd := non_neg_integer(), + cookie_life := non_neg_integer()}. + -type sockaddr_un() :: #{family := local, path := binary() | string()}. -type sockaddr_in4() :: #{family := inet, @@ -637,7 +645,7 @@ -define(SOCKET_OPT_UDP_CORK, 1). %% -define(SOCKET_OPT_SCTP_ADAPTION_LAYER, 1). -%% -define(SOCKET_OPT_SCTP_ASSOCINFO, 2). +-define(SOCKET_OPT_SCTP_ASSOCINFO, 2). %% -define(SOCKET_OPT_SCTP_AUTH_ACTIVE_KEY, 3). %% -define(SOCKET_OPT_SCTP_AUTH_ASCONF, 4). %% -define(SOCKET_OPT_SCTP_AUTH_CHUNK, 5). @@ -2172,8 +2180,23 @@ enc_setopt_value(udp, cork, V, _D, T, P) enc_setopt_value(udp = L, Opt, _V, _D, _T, _P) -> not_supported({L, Opt}); +enc_setopt_value(sctp, associnfo, #{assoc_id := AssocId, + asocmaxrxt := MaxRxt, + num_peer_dests := NumPeerDests, + peer_rwnd := PeerRWND, + local_rwnd := LocalRWND, + cookie_life := CLife} = V, + _D, _T, P) + when is_integer(AssocId) andalso + is_integer(MaxRxt) andalso (MaxRxt >= 0) andalso + is_integer(NumPeerDests) andalso (NumPeerDests >= 0) andalso + is_integer(PeerRWND) andalso (PeerRWND >= 0) andalso + is_integer(LocalRWND) andalso (LocalRWND >= 0) andalso + is_integer(CLife) andalso (CLife >= 0) andalso + (P =:= sctp) -> + V; enc_setopt_value(sctp, autoclose, V, _D, _T, P) - when is_integer(V) andalso (P =:= sctp) -> + when is_integer(V) andalso (V >= 0) andalso (P =:= sctp) -> V; enc_setopt_value(sctp, disable_fragments, V, _D, _T, P) when is_boolean(V) andalso (P =:= sctp) -> @@ -2595,8 +2618,8 @@ enc_sockopt_key(udp = L, UnknownOpt, _Dir, _D, _T, _P) -> %% SCTP socket options enc_sockopt_key(sctp = L, adaption_layer = Opt, _Dir, _D, _T, _P) -> not_supported({L, Opt}); -enc_sockopt_key(sctp = L, associnfo = Opt, _Dir, _D, _T, _P) -> - not_supported({L, Opt}); +enc_sockopt_key(sctp = _L, associnfo = _Opt, _Dir, _D, _T, _P) -> + ?SOCKET_OPT_SCTP_ASSOCINFO; enc_sockopt_key(sctp = L, auth_active_key = Opt, _Dir, _D, _T, _P) -> not_supported({L, Opt}); enc_sockopt_key(sctp = L, auth_asconf = Opt, _Dir, _D, _T, _P) -> diff --git a/lib/kernel/test/socket_server.erl b/lib/kernel/test/socket_server.erl index a9b4aca5f8..56200e0ae9 100644 --- a/lib/kernel/test/socket_server.erl +++ b/lib/kernel/test/socket_server.erl @@ -200,8 +200,14 @@ do_manager_init(Domain, seqpacket = Type, sctp = Proto, _Peek) -> {error, R} -> f("error: ~p", [R]) end end, + %% ok = socket:setopt(Sock, otp, debug, true), i("Miscellaneous options: " - "~n disable-fragments: ~s", [GO(disable_fragments)]), + "~n associnfo: ~s" + "~n autoclose: ~s" + "~n disable-fragments: ~s", + [GO(associnfo), + GO(autoclose), + GO(disable_fragments)]), Events = #{data_in => true, association => true, address => true, -- cgit v1.2.3 From 6bb60f1fecef368991806e25a0022c63b8650f39 Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Thu, 19 Jul 2018 17:34:15 +0200 Subject: [socket-nif] Add (partial) support for socket (level sctp) option rtoinfo Added support for the SCTP option RTOINFO. We have the same questions for this option as for ASSOCINFO. Maybe the assoc field shall be made 'out' only (that is, it will be ignored for setopt). The assoc id used will be that which is stored in the descriptor (how do we get it to begin with?). Questions, questions... OTP-14831 --- erts/doc/src/socket.xml | 3 + erts/doc/src/socket_usage.xml | 14 +++ erts/emulator/nifs/common/socket_nif.c | 194 ++++++++++++++++++++++++++++++--- erts/preloaded/ebin/socket.beam | Bin 53256 -> 53668 bytes erts/preloaded/src/socket.erl | 26 ++++- lib/kernel/test/socket_server.erl | 6 +- 6 files changed, 222 insertions(+), 21 deletions(-) diff --git a/erts/doc/src/socket.xml b/erts/doc/src/socket.xml index 3e8e7af5c6..f6b25c8563 100644 --- a/erts/doc/src/socket.xml +++ b/erts/doc/src/socket.xml @@ -140,6 +140,9 @@ + + + diff --git a/erts/doc/src/socket_usage.xml b/erts/doc/src/socket_usage.xml index 60cb424cde..1e325016ff 100644 --- a/erts/doc/src/socket_usage.xml +++ b/erts/doc/src/socket_usage.xml @@ -49,6 +49,13 @@ Get Other + + assoc_id + integer() + no + yes + type = seqpacket, protocol = sctp, is an association + debug boolean() @@ -486,6 +493,13 @@ yes none + + rtoinfo + sctp_rtoinfo() + yes + yes + none +
diff --git a/erts/emulator/nifs/common/socket_nif.c b/erts/emulator/nifs/common/socket_nif.c index 50e7ec5f5e..f9643cccbf 100644 --- a/erts/emulator/nifs/common/socket_nif.c +++ b/erts/emulator/nifs/common/socket_nif.c @@ -547,6 +547,7 @@ typedef union { #define SOCKET_OPT_SCTP_DISABLE_FRAGMENTS 12 #define SOCKET_OPT_SCTP_EVENTS 14 #define SOCKET_OPT_SCTP_NODELAY 23 +#define SOCKET_OPT_SCTP_RTOINFO 29 /* =================================================================== * @@ -1238,6 +1239,11 @@ static ERL_NIF_TERM nsetopt_lvl_sctp_nodelay(ErlNifEnv* env, SocketDescriptor* descP, ERL_NIF_TERM eVal); #endif +#if defined(SCTP_RTOINFO) +static ERL_NIF_TERM nsetopt_lvl_sctp_rtoinfo(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); +#endif #endif // defined(HAVE_SCTP) static ERL_NIF_TERM ngetopt(ErlNifEnv* env, @@ -1457,6 +1463,10 @@ static ERL_NIF_TERM ngetopt_lvl_sctp_disable_fragments(ErlNifEnv* env, static ERL_NIF_TERM ngetopt_lvl_sctp_nodelay(ErlNifEnv* env, SocketDescriptor* descP); #endif +#if defined(SCTP_RTOINFO) +static ERL_NIF_TERM ngetopt_lvl_sctp_rtoinfo(ErlNifEnv* env, + SocketDescriptor* descP); +#endif #endif // defined(HAVE_SCTP) static ERL_NIF_TERM nsockname(ErlNifEnv* env, SocketDescriptor* descP); @@ -1805,10 +1815,13 @@ static char str_global_counters[] = "global_counters"; static char str_in4_sockaddr[] = "in4_sockaddr"; static char str_in6_sockaddr[] = "in6_sockaddr"; static char str_iow[] = "iow"; +static char str_initial[] = "initial"; static char str_interface[] = "interface"; static char str_local_rwnd[] = "local_rwnd"; // static char str_loopback[] = "loopback"; +static char str_max[] = "max"; static char str_max_rxt[] = "max_rxt"; +static char str_min[] = "min"; static char str_multiaddr[] = "multiaddr"; static char str_nif_abort[] = "nif_abort"; static char str_num_dlocal[] = "num_domain_local"; @@ -1908,9 +1921,12 @@ static ERL_NIF_TERM atom_global_counters; static ERL_NIF_TERM atom_in4_sockaddr; static ERL_NIF_TERM atom_in6_sockaddr; static ERL_NIF_TERM atom_iow; +static ERL_NIF_TERM atom_initial; static ERL_NIF_TERM atom_interface; static ERL_NIF_TERM atom_local_rwnd; +static ERL_NIF_TERM atom_max; static ERL_NIF_TERM atom_max_rxt; +static ERL_NIF_TERM atom_min; static ERL_NIF_TERM atom_multiaddr; static ERL_NIF_TERM atom_nif_abort; static ERL_NIF_TERM atom_num_dinet; @@ -5565,6 +5581,12 @@ ERL_NIF_TERM nsetopt_lvl_sctp(ErlNifEnv* env, break; #endif +#if defined(SCTP_RTOINFO) + case SOCKET_OPT_SCTP_RTOINFO: + result = nsetopt_lvl_sctp_rtoinfo(env, descP, eVal); + break; +#endif + default: result = esock_make_error(env, esock_atom_einval); break; @@ -5625,45 +5647,37 @@ ERL_NIF_TERM nsetopt_lvl_sctp_associnfo(ErlNifEnv* env, return esock_make_error(env, esock_atom_einval); SSDBG( descP, - ("SOCKET", "nsetopt_lvl_sctp_events -> decode attributes\r\n") ); + ("SOCKET", "nsetopt_lvl_sctp_associnfo -> decode attributes\r\n") ); if (!GET_INT(env, eAssocId, &assocParams.sasoc_assoc_id)) return esock_make_error(env, esock_atom_einval); /* * We should really make sure this is ok in erlang (to ensure that - * the value fits in 16-bits). + * the values (max-rxt and num-peer-dests) fits in 16-bits). * The value should be a 16-bit unsigned int... * Both sasoc_asocmaxrxt and sasoc_number_peer_destinations. */ - /* - if (!GET_UINT(env, eAssocId, (unsigned int*) &assocParams.sasoc_asocmaxrxt)) - return esock_make_error(env, esock_atom_einval); - */ - if (!GET_UINT(env, eAssocId, &tmp)) + if (!GET_UINT(env, eMaxRxt, &tmp)) return esock_make_error(env, esock_atom_einval); assocParams.sasoc_asocmaxrxt = (uint16_t) tmp; - /* - if (!GET_UINT(env, eAssocId, (unsigned int*) &assocParams.sasoc_number_peer_destinations)) - return esock_make_error(env, esock_atom_einval); - */ - if (!GET_UINT(env, eAssocId, &tmp)) + if (!GET_UINT(env, eNumPeerDests, &tmp)) return esock_make_error(env, esock_atom_einval); assocParams.sasoc_number_peer_destinations = (uint16_t) tmp; - if (!GET_UINT(env, eAssocId, &assocParams.sasoc_peer_rwnd)) + if (!GET_UINT(env, ePeerRWND, &assocParams.sasoc_peer_rwnd)) return esock_make_error(env, esock_atom_einval); - if (!GET_UINT(env, eAssocId, &assocParams.sasoc_local_rwnd)) + if (!GET_UINT(env, eLocalRWND, &assocParams.sasoc_local_rwnd)) return esock_make_error(env, esock_atom_einval); - if (!GET_UINT(env, eAssocId, &assocParams.sasoc_cookie_life)) + if (!GET_UINT(env, eCookieLife, &assocParams.sasoc_cookie_life)) return esock_make_error(env, esock_atom_einval); SSDBG( descP, - ("SOCKET", "nsetopt_lvl_sctp_events -> set associnfo option\r\n") ); + ("SOCKET", "nsetopt_lvl_sctp_associnfo -> set associnfo option\r\n") ); res = socket_setopt(descP->sock, IPPROTO_SCTP, SCTP_ASSOCINFO, &assocParams, sizeof(assocParams)); @@ -5821,6 +5835,85 @@ ERL_NIF_TERM nsetopt_lvl_sctp_nodelay(ErlNifEnv* env, #endif +/* nsetopt_lvl_sctp_rtoinfo - Level SCTP RTOINFO option + */ +#if defined(SCTP_RTOINFO) +static +ERL_NIF_TERM nsetopt_lvl_sctp_rtoinfo(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) +{ + ERL_NIF_TERM result; + ERL_NIF_TERM eAssocId, eInitial, eMax, eMin; + struct sctp_rtoinfo rtoInfo; + int res; + size_t sz; + + SSDBG( descP, + ("SOCKET", "nsetopt_lvl_sctp_rtoinfo -> entry with" + "\r\n eVal: %T" + "\r\n", eVal) ); + + // It must be a map + if (!IS_MAP(env, eVal)) + return esock_make_error(env, esock_atom_einval); + + // It must have atleast ten attributes + if (!enif_get_map_size(env, eVal, &sz) || (sz < 4)) + return esock_make_error(env, esock_atom_einval); + + SSDBG( descP, + ("SOCKET", "nsetopt_lvl_sctp_rtoinfo -> extract attributes\r\n") ); + + if (!GET_MAP_VAL(env, eVal, atom_assoc_id, &eAssocId)) + return esock_make_error(env, esock_atom_einval); + + if (!GET_MAP_VAL(env, eVal, atom_initial, &eInitial)) + return esock_make_error(env, esock_atom_einval); + + if (!GET_MAP_VAL(env, eVal, atom_max, &eMax)) + return esock_make_error(env, esock_atom_einval); + + if (!GET_MAP_VAL(env, eVal, atom_min, &eMin)) + return esock_make_error(env, esock_atom_einval); + + SSDBG( descP, + ("SOCKET", "nsetopt_lvl_sctp_rtoinfo -> decode attributes\r\n") ); + + if (!GET_INT(env, eAssocId, &rtoInfo.srto_assoc_id)) + return esock_make_error(env, esock_atom_einval); + + if (!GET_UINT(env, eInitial, &rtoInfo.srto_initial)) + return esock_make_error(env, esock_atom_einval); + + if (!GET_UINT(env, eMax, &rtoInfo.srto_max)) + return esock_make_error(env, esock_atom_einval); + + if (!GET_UINT(env, eMin, &rtoInfo.srto_min)) + return esock_make_error(env, esock_atom_einval); + + SSDBG( descP, + ("SOCKET", "nsetopt_lvl_sctp_rtoinfo -> set associnfo option\r\n") ); + + res = socket_setopt(descP->sock, IPPROTO_SCTP, SCTP_RTOINFO, + &rtoInfo, sizeof(rtoInfo)); + + if (res != 0) + result = esock_make_error_errno(env, sock_errno()); + else + result = esock_atom_ok; + + SSDBG( descP, + ("SOCKET", "nsetopt_lvl_sctp_rtoinfo -> done with" + "\r\n result: %T" + "\r\n", result) ); + + return result; + +} +#endif + + #endif // defined(HAVE_SCTP) @@ -7498,6 +7591,12 @@ ERL_NIF_TERM ngetopt_lvl_sctp(ErlNifEnv* env, break; #endif +#if defined(SCTP_RTOINFO) + case SOCKET_OPT_SCTP_RTOINFO: + result = ngetopt_lvl_sctp_rtoinfo(env, descP); + break; +#endif + default: result = esock_make_error(env, esock_atom_einval); break; @@ -7610,6 +7709,66 @@ ERL_NIF_TERM ngetopt_lvl_sctp_nodelay(ErlNifEnv* env, #endif +/* ngetopt_lvl_sctp_associnfo - Level SCTP ASSOCINFO option + * + * + * + * We should really specify which association this relates to, + * as it is now we get assoc-id = 0. If this socket is an + * association (and not an endpoint) then it will have an + * assoc id (we can assume). But since the sctp support at + * present is "limited", we leave it for now. + * What do we do if this is an endpoint? Invalid op? + * + * + */ +#if defined(SCTP_RTOINFO) +static +ERL_NIF_TERM ngetopt_lvl_sctp_rtoinfo(ErlNifEnv* env, + SocketDescriptor* descP) +{ + ERL_NIF_TERM result; + struct sctp_rtoinfo val; + SOCKOPTLEN_T valSz = sizeof(val); + int res; + + SSDBG( descP, ("SOCKET", "ngetopt_lvl_sctp_rtoinfo -> entry\r\n") ); + + sys_memzero((char*) &val, valSz); + res = sock_getopt(descP->sock, IPPROTO_SCTP, SCTP_RTOINFO, &val, &valSz); + + if (res != 0) { + result = esock_make_error_errno(env, sock_errno()); + } else { + ERL_NIF_TERM eRTOInfo; + ERL_NIF_TERM keys[] = {atom_assoc_id, atom_initial, atom_max, atom_min}; + ERL_NIF_TERM vals[] = {MKUI(env, val.srto_assoc_id), + MKUI(env, val.srto_initial), + MKUI(env, val.srto_max), + MKUI(env, val.srto_min)}; + unsigned int numKeys = sizeof(keys) / sizeof(ERL_NIF_TERM); + unsigned int numVals = sizeof(vals) / sizeof(ERL_NIF_TERM); + + ESOCK_ASSERT( (numKeys == numVals) ); + + if (!MKMA(env, keys, vals, numKeys, &eRTOInfo)) + return esock_make_error(env, esock_atom_einval);; + + result = esock_make_ok2(env, eRTOInfo); + } + + SSDBG( descP, + ("SOCKET", "ngetopt_lvl_sctp_rtoinfo -> done with" + "\r\n res: %d" + "\r\n result: %T" + "\r\n", res, result) ); + + return result; +} +#endif + + + #endif // defined(HAVE_SCTP) @@ -9840,9 +9999,12 @@ int on_load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info) atom_in4_sockaddr = MKA(env, str_in4_sockaddr); atom_in6_sockaddr = MKA(env, str_in6_sockaddr); atom_iow = MKA(env, str_iow); + atom_initial = MKA(env, str_initial); atom_interface = MKA(env, str_interface); atom_local_rwnd = MKA(env, str_local_rwnd); + atom_max = MKA(env, str_max); atom_max_rxt = MKA(env, str_max_rxt); + atom_min = MKA(env, str_min); atom_multiaddr = MKA(env, str_multiaddr); atom_nif_abort = MKA(env, str_nif_abort); atom_num_dinet = MKA(env, str_num_dinet); diff --git a/erts/preloaded/ebin/socket.beam b/erts/preloaded/ebin/socket.beam index 0c496443bf..427c08cd57 100644 Binary files a/erts/preloaded/ebin/socket.beam and b/erts/preloaded/ebin/socket.beam differ diff --git a/erts/preloaded/src/socket.erl b/erts/preloaded/src/socket.erl index feb2e25312..0bc3ed8ec9 100644 --- a/erts/preloaded/src/socket.erl +++ b/erts/preloaded/src/socket.erl @@ -96,6 +96,7 @@ ipv6_pmtudisc/0, sctp_event_subscribe/0, sctp_assocparams/0, + sctp_rtoinfo/0, msg_hdr/0 @@ -195,6 +196,11 @@ local_rwnd := non_neg_integer(), cookie_life := non_neg_integer()}. +-type sctp_rtoinfo() :: #{assoc_id := integer(), + initial := non_neg_integer(), + max := non_neg_integer(), + min := non_neg_integer()}. + -type sockaddr_un() :: #{family := local, path := binary() | string()}. -type sockaddr_in4() :: #{family := inet, @@ -672,7 +678,7 @@ %% -define(SOCKET_OPT_SCTP_PEER_AUTH_CHUNKS, 26). %% -define(SOCKET_OPT_SCTP_PRIMARY_ADDR, 27). %% -define(SOCKET_OPT_SCTP_RESET_STREAMS, 28). -%% -define(SOCKET_OPT_SCTP_RTOINFO, 29). +-define(SOCKET_OPT_SCTP_RTOINFO, 29). %% -define(SOCKET_OPT_SCTP_SET_PEER_PRIMARY_ADDR, 30). %% -define(SOCKET_OPT_SCTP_STATUS, 31). %% -define(SOCKET_OPT_SCTP_USE_EXT_RECVINFO, 32). @@ -2226,7 +2232,21 @@ enc_setopt_value(sctp, events, #{data_in := DataIn, enc_setopt_value(sctp, nodelay, V, _D, _T, P) when is_boolean(V) andalso (P =:= sctp) -> V; +enc_setopt_value(sctp, rtoinfo, #{assoc_id := AssocId, + initial := Init, + max := Max, + min := Min} = V, + _D, _T, P) + when is_integer(AssocId) andalso + is_integer(Init) andalso (Init >= 0) andalso + is_integer(Max) andalso (Max >= 0) andalso + is_integer(Min) andalso (Min >= 0) andalso + (P =:= sctp) -> + V; +enc_setopt_value(sctp = L, Opt, V, _D, _T, _P) -> + not_supported({L, Opt, V}); +%% Is this correct? What about getopt? enc_setopt_value(L, Opt, V, _, _, _) when is_integer(L) andalso is_integer(Opt) andalso is_binary(V) -> V. @@ -2672,8 +2692,8 @@ enc_sockopt_key(sctp = L, primary_addr = Opt, _Dir, _D, _T, _P) -> not_supported({L, Opt}); enc_sockopt_key(sctp = L, reset_streams = Opt, _Dir, _D, _T, _P) -> not_supported({L, Opt}); -enc_sockopt_key(sctp = L, rtoinfo = Opt, _Dir, _D, _T, _P) -> - not_supported({L, Opt}); +enc_sockopt_key(sctp = _L, rtoinfo = _Opt, _Dir, _D, _T, _P) -> + ?SOCKET_OPT_SCTP_RTOINFO; enc_sockopt_key(sctp = L, set_peer_primary_addr = Opt, _Dir, _D, _T, _P) -> not_supported({L, Opt}); enc_sockopt_key(sctp = L, status = Opt, _Dir, _D, _T, _P) -> diff --git a/lib/kernel/test/socket_server.erl b/lib/kernel/test/socket_server.erl index 56200e0ae9..7320192e6a 100644 --- a/lib/kernel/test/socket_server.erl +++ b/lib/kernel/test/socket_server.erl @@ -204,10 +204,12 @@ do_manager_init(Domain, seqpacket = Type, sctp = Proto, _Peek) -> i("Miscellaneous options: " "~n associnfo: ~s" "~n autoclose: ~s" - "~n disable-fragments: ~s", + "~n disable-fragments: ~s" + "~n rtoinfo: ~s", [GO(associnfo), GO(autoclose), - GO(disable_fragments)]), + GO(disable_fragments), + GO(rtoinfo)]), Events = #{data_in => true, association => true, address => true, -- cgit v1.2.3 From 08ce39bbc2cd3006475c87807042c2d08a68736f Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Thu, 19 Jul 2018 17:54:14 +0200 Subject: [socket-nif] Add support for socket (level sctp) option maxseg Added support for the SCTP option MAXSEG. OTP-14831 --- erts/doc/src/socket_usage.xml | 7 +++++ erts/emulator/nifs/common/socket_nif.c | 47 +++++++++++++++++++++++++++++++++ erts/preloaded/ebin/socket.beam | Bin 53668 -> 53748 bytes erts/preloaded/src/socket.erl | 9 ++++--- lib/kernel/test/socket_server.erl | 6 +++++ 5 files changed, 66 insertions(+), 3 deletions(-) diff --git a/erts/doc/src/socket_usage.xml b/erts/doc/src/socket_usage.xml index 1e325016ff..21f5d17e84 100644 --- a/erts/doc/src/socket_usage.xml +++ b/erts/doc/src/socket_usage.xml @@ -486,6 +486,13 @@ no none + + maxseg + non_neg_integer() + yes + yes + none + nodelay boolean() diff --git a/erts/emulator/nifs/common/socket_nif.c b/erts/emulator/nifs/common/socket_nif.c index f9643cccbf..e84eb7aa8e 100644 --- a/erts/emulator/nifs/common/socket_nif.c +++ b/erts/emulator/nifs/common/socket_nif.c @@ -546,6 +546,7 @@ typedef union { #define SOCKET_OPT_SCTP_AUTOCLOSE 8 #define SOCKET_OPT_SCTP_DISABLE_FRAGMENTS 12 #define SOCKET_OPT_SCTP_EVENTS 14 +#define SOCKET_OPT_SCTP_MAXSEG 21 #define SOCKET_OPT_SCTP_NODELAY 23 #define SOCKET_OPT_SCTP_RTOINFO 29 @@ -1234,6 +1235,11 @@ static ERL_NIF_TERM nsetopt_lvl_sctp_events(ErlNifEnv* env, SocketDescriptor* descP, ERL_NIF_TERM eVal); #endif +#if defined(SCTP_MAXSEG) +static ERL_NIF_TERM nsetopt_lvl_sctp_maxseg(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); +#endif #if defined(SCTP_NODELAY) static ERL_NIF_TERM nsetopt_lvl_sctp_nodelay(ErlNifEnv* env, SocketDescriptor* descP, @@ -1459,6 +1465,10 @@ static ERL_NIF_TERM ngetopt_lvl_sctp_autoclose(ErlNifEnv* env, static ERL_NIF_TERM ngetopt_lvl_sctp_disable_fragments(ErlNifEnv* env, SocketDescriptor* descP); #endif +#if defined(SCTP_MAXSEG) +static ERL_NIF_TERM ngetopt_lvl_sctp_maxseg(ErlNifEnv* env, + SocketDescriptor* descP); +#endif #if defined(SCTP_NODELAY) static ERL_NIF_TERM ngetopt_lvl_sctp_nodelay(ErlNifEnv* env, SocketDescriptor* descP); @@ -5575,6 +5585,12 @@ ERL_NIF_TERM nsetopt_lvl_sctp(ErlNifEnv* env, break; #endif +#if defined(SCTP_MAXSEG) + case SOCKET_OPT_SCTP_MAXSEG: + result = nsetopt_lvl_sctp_maxseg(env, descP, eVal); + break; +#endif + #if defined(SCTP_NODELAY) case SOCKET_OPT_SCTP_NODELAY: result = nsetopt_lvl_sctp_nodelay(env, descP, eVal); @@ -5822,6 +5838,19 @@ ERL_NIF_TERM nsetopt_lvl_sctp_events(ErlNifEnv* env, #endif +/* nsetopt_lvl_sctp_maxseg - Level SCTP MAXSEG option + */ +#if defined(SCTP_MAXSEG) +static +ERL_NIF_TERM nsetopt_lvl_sctp_maxseg(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) +{ + return nsetopt_int_opt(env, descP, IPPROTO_SCTP, SCTP_MAXSEG, eVal); +} +#endif + + /* nsetopt_lvl_sctp_nodelay - Level SCTP NODELAY option */ #if defined(SCTP_NODELAY) @@ -7585,6 +7614,12 @@ ERL_NIF_TERM ngetopt_lvl_sctp(ErlNifEnv* env, break; #endif +#if defined(SCTP_MAXSEG) + case SOCKET_OPT_SCTP_MAXSEG: + result = ngetopt_lvl_sctp_maxseg(env, descP); + break; +#endif + #if defined(SCTP_NODELAY) case SOCKET_OPT_SCTP_NODELAY: result = ngetopt_lvl_sctp_nodelay(env, descP); @@ -7697,6 +7732,18 @@ ERL_NIF_TERM ngetopt_lvl_sctp_disable_fragments(ErlNifEnv* env, #endif +/* ngetopt_lvl_sctp_maxseg - Level SCTP MAXSEG option + */ +#if defined(SCTP_MAXSEG) +static +ERL_NIF_TERM ngetopt_lvl_sctp_maxseg(ErlNifEnv* env, + SocketDescriptor* descP) +{ + return ngetopt_int_opt(env, descP, IPPROTO_SCTP, SCTP_MAXSEG); +} +#endif + + /* ngetopt_lvl_sctp_nodelay - Level SCTP NODELAY option */ #if defined(SCTP_NODELAY) diff --git a/erts/preloaded/ebin/socket.beam b/erts/preloaded/ebin/socket.beam index 427c08cd57..2e284e3343 100644 Binary files a/erts/preloaded/ebin/socket.beam and b/erts/preloaded/ebin/socket.beam differ diff --git a/erts/preloaded/src/socket.erl b/erts/preloaded/src/socket.erl index 0bc3ed8ec9..1c24c75dac 100644 --- a/erts/preloaded/src/socket.erl +++ b/erts/preloaded/src/socket.erl @@ -670,7 +670,7 @@ %% -define(SOCKET_OPT_SCTP_INITMSG, 18). %% -define(SOCKET_OPT_SCTP_I_WANT_MAPPED_V4_ADDR, 19). %% -define(SOCKET_OPT_SCTP_LOCAL_AUTH_CHUNKS, 20). -%% -define(SOCKET_OPT_SCTP_MAXSEG, 21). +-define(SOCKET_OPT_SCTP_MAXSEG, 21). %% -define(SOCKET_OPT_SCTP_MAXBURST, 22). -define(SOCKET_OPT_SCTP_NODELAY, 23). %% -define(SOCKET_OPT_SCTP_PARTIAL_DELIVERY_POINT, 24). @@ -2229,6 +2229,9 @@ enc_setopt_value(sctp, events, #{data_in := DataIn, is_boolean(SndDry) andalso (P =:= sctp) -> V; +enc_setopt_value(sctp, maxseg, V, _D, _T, P) + when is_integer(V) andalso (V >= 0) andalso (P =:= sctp) -> + V; enc_setopt_value(sctp, nodelay, V, _D, _T, P) when is_boolean(V) andalso (P =:= sctp) -> V; @@ -2676,8 +2679,8 @@ enc_sockopt_key(sctp = L, i_want_mapped_v4_addr = Opt, _Dir, _D, _T, _P) -> not_supported({L, Opt}); enc_sockopt_key(sctp = L, local_auth_chunks = Opt, _Dir, _D, _T, _P) -> not_supported({L, Opt}); -enc_sockopt_key(sctp = L, maxseg = Opt, _Dir, _D, _T, _P) -> - not_supported({L, Opt}); +enc_sockopt_key(sctp = _L, maxseg = _Opt, _Dir, _D, _T, _P) -> + ?SOCKET_OPT_SCTP_MAXSEG; enc_sockopt_key(sctp = L, maxburst = Opt, _Dir, _D, _T, _P) -> not_supported({L, Opt}); enc_sockopt_key(sctp, nodelay = _Opt, _Dir, _D, _T, _P) -> diff --git a/lib/kernel/test/socket_server.erl b/lib/kernel/test/socket_server.erl index 7320192e6a..6fc4c60543 100644 --- a/lib/kernel/test/socket_server.erl +++ b/lib/kernel/test/socket_server.erl @@ -201,15 +201,21 @@ do_manager_init(Domain, seqpacket = Type, sctp = Proto, _Peek) -> end end, %% ok = socket:setopt(Sock, otp, debug, true), + i("Miscellaneous options: " "~n associnfo: ~s" "~n autoclose: ~s" "~n disable-fragments: ~s" + "~n maxseg: ~s" + "~n nodelay: ~s" "~n rtoinfo: ~s", [GO(associnfo), GO(autoclose), GO(disable_fragments), + GO(maxseg), + GO(nodelay), GO(rtoinfo)]), + Events = #{data_in => true, association => true, address => true, -- cgit v1.2.3 From 8b96072a94d590ad545fa888911e5d613b32281e Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Thu, 19 Jul 2018 18:22:05 +0200 Subject: [socket-nif] Add preliminary bind/3 for sctp Added a preliminary bind/3 that shall map to sctp_bindx. We need similar functions for sctp_connectx, and maybe a bunch of other specific sctp function(s). --- erts/preloaded/ebin/socket.beam | Bin 53748 -> 55460 bytes erts/preloaded/src/socket.erl | 73 ++++++++++++++++++++++++++++++++++++++-- 2 files changed, 70 insertions(+), 3 deletions(-) diff --git a/erts/preloaded/ebin/socket.beam b/erts/preloaded/ebin/socket.beam index 2e284e3343..41b4201090 100644 Binary files a/erts/preloaded/ebin/socket.beam and b/erts/preloaded/ebin/socket.beam differ diff --git a/erts/preloaded/src/socket.erl b/erts/preloaded/src/socket.erl index 1c24c75dac..cadbf1131c 100644 --- a/erts/preloaded/src/socket.erl +++ b/erts/preloaded/src/socket.erl @@ -31,7 +31,7 @@ -export([ open/2, open/3, open/4, - bind/2, + bind/2, bind/3, connect/2, connect/3, listen/1, listen/2, accept/1, accept/2, @@ -899,6 +899,70 @@ bind(#socket{ref = SockRef} = _Socket, Addr) when is_map(Addr) -> +%% =========================================================================== +%% +%% bind - Add or remove a bind addresses on a socket +%% +%% Calling this function is only valid if the socket is: +%% type = seqpacket +%% protocol = sctp +%% +%% If the domain is inet, then all addresses *must* be IPv4. +%% If the domain is inet6, the addresses can be aither IPv4 or IPv6. +%% + +-spec bind(Socket, Addrs, Action) -> ok | {error, Reason} when + Socket :: socket(), + Addrs :: [sockaddr()], + Action :: add | remove, + Reason :: term(). + +bind(#socket{ref = SockRef, + info = #{domain := Domain, + type := seqpacket, + protocol := sctp}} = _Socket, Addrs, Action) + when is_list(Addrs) andalso ((Action =:= add) orelse (Action =:= remove)) -> + try + begin + validate_addrs(Domain, Addrs), + nif_bind(SockRef, Addrs, Action) + end + catch + throw:ERROR -> + ERROR + end. + + +validate_addrs(inet = _Domain, Addrs) -> + validate_inet_addrs(Addrs); +validate_addrs(inet6 = _Domain, Addrs) -> + validate_inet6_addrs(Addrs). + +validate_inet_addrs(Addrs) -> + Validator = fun(#{family := inet, + addrs := Addr}) when is_tuple(Addr) andalso + (size(Addr) =:= 4) -> + ok; + (X) -> + throw({error, {invalid_address, X}}) + end, + lists:foreach(Validator, Addrs). + +validate_inet6_addrs(Addrs) -> + Validator = fun(#{family := inet, + addrs := Addr}) when is_tuple(Addr) andalso + (size(Addr) =:= 4) -> + ok; + (#{family := inet6, + addrs := Addr}) when is_tuple(Addr) andalso + (size(Addr) =:= 8) -> + ok; + (X) -> + throw({error, {invalid_address, X}}) + end, + lists:foreach(Validator, Addrs). + + %% =========================================================================== %% %% connect - initiate a connection on a socket @@ -2699,8 +2763,8 @@ enc_sockopt_key(sctp = _L, rtoinfo = _Opt, _Dir, _D, _T, _P) -> ?SOCKET_OPT_SCTP_RTOINFO; enc_sockopt_key(sctp = L, set_peer_primary_addr = Opt, _Dir, _D, _T, _P) -> not_supported({L, Opt}); -enc_sockopt_key(sctp = L, status = Opt, _Dir, _D, _T, _P) -> - not_supported({L, Opt}); +enc_sockopt_key(sctp = L, status = Opt, get = _Dir, _D, _T, _P) -> + not_supported({L, Opt}); % ?SOCKET_OPT_SCTP_RTOINFO; enc_sockopt_key(sctp = L, use_exp_recvinfo = Opt, _Dir, _D, _T, _P) -> not_supported({L, Opt}); enc_sockopt_key(sctp = L, UnknownOpt, _Dir, _D, _T, _P) -> @@ -2869,6 +2933,9 @@ nif_open(_Domain, _Type, _Protocol, _Extra) -> nif_bind(_SRef, _SockAddr) -> erlang:error(badarg). +nif_bind(_SRef, _SockAddrs, _Action) -> + erlang:error(badarg). + nif_connect(_SRef, _SockAddr) -> erlang:error(badarg). -- cgit v1.2.3 From bd36af21717b138c91724128e592b3fc587bb07a Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Fri, 20 Jul 2018 10:10:28 +0200 Subject: [socket-nif] Add support for socket (level sctp) option initmsg Added support for the SCTP option INITMSG. OTP-14831 --- erts/doc/src/socket.xml | 3 + erts/doc/src/socket_usage.xml | 7 ++ erts/emulator/nifs/common/socket_nif.c | 198 ++++++++++++++++++++++++++++++--- erts/preloaded/ebin/socket.beam | Bin 55460 -> 56044 bytes erts/preloaded/src/socket.erl | 57 +++++++--- lib/kernel/test/socket_server.erl | 2 + 6 files changed, 238 insertions(+), 29 deletions(-) diff --git a/erts/doc/src/socket.xml b/erts/doc/src/socket.xml index f6b25c8563..fe23eaa138 100644 --- a/erts/doc/src/socket.xml +++ b/erts/doc/src/socket.xml @@ -140,6 +140,9 @@ + + + diff --git a/erts/doc/src/socket_usage.xml b/erts/doc/src/socket_usage.xml index 21f5d17e84..e90e682e39 100644 --- a/erts/doc/src/socket_usage.xml +++ b/erts/doc/src/socket_usage.xml @@ -486,6 +486,13 @@ no none + + initmsg + sctp_initmsg() + yes + yes + none + maxseg non_neg_integer() diff --git a/erts/emulator/nifs/common/socket_nif.c b/erts/emulator/nifs/common/socket_nif.c index e84eb7aa8e..e8e1df0842 100644 --- a/erts/emulator/nifs/common/socket_nif.c +++ b/erts/emulator/nifs/common/socket_nif.c @@ -263,43 +263,43 @@ * #if defined(__GNUC__) && defined(HAVE_SCTP_BINDX) -static typeof(sctp_bindx) *p_sctp_bindx = NULL; +static typeof(sctp_bindx) *esock_sctp_bindx = NULL; #else -static int (*p_sctp_bindx) +static int (*esock_sctp_bindx) (int sd, struct sockaddr *addrs, int addrcnt, int flags) = NULL; #endif #if defined(__GNUC__) && defined(HAVE_SCTP_PEELOFF) -static typeof(sctp_peeloff) *p_sctp_peeloff = NULL; +static typeof(sctp_peeloff) *esock_sctp_peeloff = NULL; #else -static int (*p_sctp_peeloff) +static int (*esock_sctp_peeloff) (int sd, sctp_assoc_t assoc_id) = NULL; #endif #if defined(__GNUC__) && defined(HAVE_SCTP_GETLADDRS) -static typeof(sctp_getladdrs) *p_sctp_getladdrs = NULL; +static typeof(sctp_getladdrs) *esock_sctp_getladdrs = NULL; #else -static int (*p_sctp_getladdrs) +static int (*esock_sctp_getladdrs) (int sd, sctp_assoc_t assoc_id, struct sockaddr **ss) = NULL; #endif #if defined(__GNUC__) && defined(HAVE_SCTP_FREELADDRS) -static typeof(sctp_freeladdrs) *p_sctp_freeladdrs = NULL; +static typeof(sctp_freeladdrs) *esock_sctp_freeladdrs = NULL; #else -static void (*p_sctp_freeladdrs)(struct sockaddr *addrs) = NULL; +static void (*esock_sctp_freeladdrs)(struct sockaddr *addrs) = NULL; #endif #if defined(__GNUC__) && defined(HAVE_SCTP_GETPADDRS) -static typeof(sctp_getpaddrs) *p_sctp_getpaddrs = NULL; +static typeof(sctp_getpaddrs) *esock_sctp_getpaddrs = NULL; #else -static int (*p_sctp_getpaddrs) +static int (*esock_sctp_getpaddrs) (int sd, sctp_assoc_t assoc_id, struct sockaddr **ss) = NULL; #endif #if defined(__GNUC__) && defined(HAVE_SCTP_FREEPADDRS) -static typeof(sctp_freepaddrs) *p_sctp_freepaddrs = NULL; +static typeof(sctp_freepaddrs) *esock_sctp_freepaddrs = NULL; #else -static void (*p_sctp_freepaddrs)(struct sockaddr *addrs) = NULL; +static void (*esock_sctp_freepaddrs)(struct sockaddr *addrs) = NULL; #endif */ @@ -546,6 +546,7 @@ typedef union { #define SOCKET_OPT_SCTP_AUTOCLOSE 8 #define SOCKET_OPT_SCTP_DISABLE_FRAGMENTS 12 #define SOCKET_OPT_SCTP_EVENTS 14 +#define SOCKET_OPT_SCTP_INITMSG 18 #define SOCKET_OPT_SCTP_MAXSEG 21 #define SOCKET_OPT_SCTP_NODELAY 23 #define SOCKET_OPT_SCTP_RTOINFO 29 @@ -1235,6 +1236,11 @@ static ERL_NIF_TERM nsetopt_lvl_sctp_events(ErlNifEnv* env, SocketDescriptor* descP, ERL_NIF_TERM eVal); #endif +#if defined(SCTP_INITMSG) +static ERL_NIF_TERM nsetopt_lvl_sctp_initmsg(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); +#endif #if defined(SCTP_MAXSEG) static ERL_NIF_TERM nsetopt_lvl_sctp_maxseg(ErlNifEnv* env, SocketDescriptor* descP, @@ -1469,6 +1475,10 @@ static ERL_NIF_TERM ngetopt_lvl_sctp_disable_fragments(ErlNifEnv* env, static ERL_NIF_TERM ngetopt_lvl_sctp_maxseg(ErlNifEnv* env, SocketDescriptor* descP); #endif +#if defined(SCTP_INITMSG) +static ERL_NIF_TERM ngetopt_lvl_sctp_initmsg(ErlNifEnv* env, + SocketDescriptor* descP); +#endif #if defined(SCTP_NODELAY) static ERL_NIF_TERM ngetopt_lvl_sctp_nodelay(ErlNifEnv* env, SocketDescriptor* descP); @@ -1830,6 +1840,9 @@ static char str_interface[] = "interface"; static char str_local_rwnd[] = "local_rwnd"; // static char str_loopback[] = "loopback"; static char str_max[] = "max"; +static char str_max_attempts[] = "max_attempts"; +static char str_max_init_timeo[] = "max_init_timeo"; +static char str_max_instreams[] = "max_instreams"; static char str_max_rxt[] = "max_rxt"; static char str_min[] = "min"; static char str_multiaddr[] = "multiaddr"; @@ -1837,6 +1850,7 @@ static char str_nif_abort[] = "nif_abort"; static char str_num_dlocal[] = "num_domain_local"; static char str_num_dinet[] = "num_domain_inet"; static char str_num_dinet6[] = "num_domain_inet6"; +static char str_num_outstreams[] = "num_outstreams"; static char str_num_peer_dests[] = "num_peer_dests"; static char str_num_pip[] = "num_proto_ip"; static char str_num_psctp[] = "num_proto_sctp"; @@ -1935,6 +1949,9 @@ static ERL_NIF_TERM atom_initial; static ERL_NIF_TERM atom_interface; static ERL_NIF_TERM atom_local_rwnd; static ERL_NIF_TERM atom_max; +static ERL_NIF_TERM atom_max_attempts; +static ERL_NIF_TERM atom_max_init_timeo; +static ERL_NIF_TERM atom_max_instreams; static ERL_NIF_TERM atom_max_rxt; static ERL_NIF_TERM atom_min; static ERL_NIF_TERM atom_multiaddr; @@ -1942,6 +1959,7 @@ static ERL_NIF_TERM atom_nif_abort; static ERL_NIF_TERM atom_num_dinet; static ERL_NIF_TERM atom_num_dinet6; static ERL_NIF_TERM atom_num_dlocal; +static ERL_NIF_TERM atom_num_outstreams; static ERL_NIF_TERM atom_num_peer_dests; static ERL_NIF_TERM atom_num_pip; static ERL_NIF_TERM atom_num_psctp; @@ -5585,6 +5603,12 @@ ERL_NIF_TERM nsetopt_lvl_sctp(ErlNifEnv* env, break; #endif +#if defined(SCTP_INITMSG) + case SOCKET_OPT_SCTP_INITMSG: + result = nsetopt_lvl_sctp_initmsg(env, descP, eVal); + break; +#endif + #if defined(SCTP_MAXSEG) case SOCKET_OPT_SCTP_MAXSEG: result = nsetopt_lvl_sctp_maxseg(env, descP, eVal); @@ -5745,8 +5769,8 @@ ERL_NIF_TERM nsetopt_lvl_sctp_disable_fragments(ErlNifEnv* env, #if defined(SCTP_EVENTS) static ERL_NIF_TERM nsetopt_lvl_sctp_events(ErlNifEnv* env, - SocketDescriptor* descP, - ERL_NIF_TERM eVal) + SocketDescriptor* descP, + ERL_NIF_TERM eVal) { ERL_NIF_TERM result; ERL_NIF_TERM eDataIn, eAssoc, eAddr, eSndFailure; @@ -5838,6 +5862,90 @@ ERL_NIF_TERM nsetopt_lvl_sctp_events(ErlNifEnv* env, #endif +/* nsetopt_lvl_sctp_initmsg - Level SCTP INITMSG option + */ +#if defined(SCTP_INITMSG) +static +ERL_NIF_TERM nsetopt_lvl_sctp_initmsg(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) +{ + ERL_NIF_TERM result; + ERL_NIF_TERM eNumOut, eMaxIn, eMaxAttempts, eMaxInitTO; + struct sctp_initmsg initMsg; + int res; + size_t sz; + unsigned int tmp; + + SSDBG( descP, + ("SOCKET", "nsetopt_lvl_sctp_initmsg -> entry with" + "\r\n eVal: %T" + "\r\n", eVal) ); + + // It must be a map + if (!IS_MAP(env, eVal)) + return esock_make_error(env, esock_atom_einval); + + // It must have atleast ten attributes + if (!enif_get_map_size(env, eVal, &sz) || (sz < 4)) + return esock_make_error(env, esock_atom_einval); + + SSDBG( descP, + ("SOCKET", "nsetopt_lvl_sctp_initmsg -> extract attributes\r\n") ); + + if (!GET_MAP_VAL(env, eVal, atom_num_outstreams, &eNumOut)) + return esock_make_error(env, esock_atom_einval); + + if (!GET_MAP_VAL(env, eVal, atom_max_instreams, &eMaxIn)) + return esock_make_error(env, esock_atom_einval); + + if (!GET_MAP_VAL(env, eVal, atom_max_attempts, &eMaxAttempts)) + return esock_make_error(env, esock_atom_einval); + + if (!GET_MAP_VAL(env, eVal, atom_max_init_timeo, &eMaxInitTO)) + return esock_make_error(env, esock_atom_einval); + + SSDBG( descP, + ("SOCKET", "nsetopt_lvl_sctp_initmsg -> decode attributes\r\n") ); + + if (!GET_UINT(env, eNumOut, &tmp)) + return esock_make_error(env, esock_atom_einval); + initMsg.sinit_num_ostreams = (uint16_t) tmp; + + if (!GET_UINT(env, eMaxIn, &tmp)) + return esock_make_error(env, esock_atom_einval); + initMsg.sinit_max_instreams = (uint16_t) tmp; + + if (!GET_UINT(env, eMaxAttempts, &tmp)) + return esock_make_error(env, esock_atom_einval); + initMsg.sinit_max_attempts = (uint16_t) tmp; + + if (!GET_UINT(env, eMaxInitTO, &tmp)) + return esock_make_error(env, esock_atom_einval); + initMsg.sinit_max_init_timeo = (uint16_t) tmp; + + SSDBG( descP, + ("SOCKET", "nsetopt_lvl_sctp_initmsg -> set initmsg option\r\n") ); + + res = socket_setopt(descP->sock, IPPROTO_SCTP, SCTP_INITMSG, + &initMsg, sizeof(initMsg)); + + if (res != 0) + result = esock_make_error_errno(env, sock_errno()); + else + result = esock_atom_ok; + + SSDBG( descP, + ("SOCKET", "nsetopt_lvl_sctp_initmsg -> done with" + "\r\n result: %T" + "\r\n", result) ); + + return result; + +} +#endif + + /* nsetopt_lvl_sctp_maxseg - Level SCTP MAXSEG option */ #if defined(SCTP_MAXSEG) @@ -7614,6 +7722,12 @@ ERL_NIF_TERM ngetopt_lvl_sctp(ErlNifEnv* env, break; #endif +#if defined(SCTP_INITMSG) + case SOCKET_OPT_SCTP_INITMSG: + result = ngetopt_lvl_sctp_initmsg(env, descP); + break; +#endif + #if defined(SCTP_MAXSEG) case SOCKET_OPT_SCTP_MAXSEG: result = ngetopt_lvl_sctp_maxseg(env, descP); @@ -7677,7 +7791,7 @@ ERL_NIF_TERM ngetopt_lvl_sctp_associnfo(ErlNifEnv* env, if (res != 0) { result = esock_make_error_errno(env, sock_errno()); } else { - ERL_NIF_TERM eAssocParams; + ERL_NIF_TERM eAssocParams; ERL_NIF_TERM keys[] = {atom_assoc_id, atom_max_rxt, atom_num_peer_dests, atom_peer_rwnd, atom_local_rwnd, atom_cookie_life}; ERL_NIF_TERM vals[] = {MKUI(env, val.sasoc_assoc_id), @@ -7732,6 +7846,56 @@ ERL_NIF_TERM ngetopt_lvl_sctp_disable_fragments(ErlNifEnv* env, #endif +/* ngetopt_lvl_sctp_initmsg - Level SCTP INITMSG option + * + */ +#if defined(SCTP_INITMSG) +static +ERL_NIF_TERM ngetopt_lvl_sctp_initmsg(ErlNifEnv* env, + SocketDescriptor* descP) +{ + ERL_NIF_TERM result; + struct sctp_initmsg val; + SOCKOPTLEN_T valSz = sizeof(val); + int res; + + SSDBG( descP, ("SOCKET", "ngetopt_lvl_sctp_initmsg -> entry\r\n") ); + + sys_memzero((char*) &val, valSz); + res = sock_getopt(descP->sock, IPPROTO_SCTP, SCTP_INITMSG, &val, &valSz); + + if (res != 0) { + result = esock_make_error_errno(env, sock_errno()); + } else { + ERL_NIF_TERM eInitMsg; + ERL_NIF_TERM keys[] = {atom_num_outstreams, atom_max_instreams, + atom_max_attempts, atom_max_init_timeo}; + ERL_NIF_TERM vals[] = {MKUI(env, val.sinit_num_ostreams), + MKUI(env, val.sinit_max_instreams), + MKUI(env, val.sinit_max_attempts), + MKUI(env, val.sinit_max_init_timeo)}; + unsigned int numKeys = sizeof(keys) / sizeof(ERL_NIF_TERM); + unsigned int numVals = sizeof(vals) / sizeof(ERL_NIF_TERM); + + ESOCK_ASSERT( (numKeys == numVals) ); + + if (!MKMA(env, keys, vals, numKeys, &eInitMsg)) + return esock_make_error(env, esock_atom_einval);; + + result = esock_make_ok2(env, eInitMsg); + } + + SSDBG( descP, + ("SOCKET", "ngetopt_lvl_sctp_initmsg -> done with" + "\r\n res: %d" + "\r\n result: %T" + "\r\n", res, result) ); + + return result; +} +#endif + + /* ngetopt_lvl_sctp_maxseg - Level SCTP MAXSEG option */ #if defined(SCTP_MAXSEG) @@ -10050,6 +10214,9 @@ int on_load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info) atom_interface = MKA(env, str_interface); atom_local_rwnd = MKA(env, str_local_rwnd); atom_max = MKA(env, str_max); + atom_max_attempts = MKA(env, str_max_attempts); + atom_max_init_timeo = MKA(env, str_max_init_timeo); + atom_max_instreams = MKA(env, str_max_instreams); atom_max_rxt = MKA(env, str_max_rxt); atom_min = MKA(env, str_min); atom_multiaddr = MKA(env, str_multiaddr); @@ -10057,6 +10224,7 @@ int on_load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info) atom_num_dinet = MKA(env, str_num_dinet); atom_num_dinet6 = MKA(env, str_num_dinet6); atom_num_dlocal = MKA(env, str_num_dlocal); + atom_num_outstreams = MKA(env, str_num_outstreams); atom_num_peer_dests = MKA(env, str_num_peer_dests); atom_num_pip = MKA(env, str_num_pip); atom_num_psctp = MKA(env, str_num_psctp); diff --git a/erts/preloaded/ebin/socket.beam b/erts/preloaded/ebin/socket.beam index 41b4201090..c4c830e051 100644 Binary files a/erts/preloaded/ebin/socket.beam and b/erts/preloaded/ebin/socket.beam differ diff --git a/erts/preloaded/src/socket.erl b/erts/preloaded/src/socket.erl index cadbf1131c..03c87a6df5 100644 --- a/erts/preloaded/src/socket.erl +++ b/erts/preloaded/src/socket.erl @@ -96,16 +96,30 @@ ipv6_pmtudisc/0, sctp_event_subscribe/0, sctp_assocparams/0, + sctp_initmsg/0, sctp_rtoinfo/0, - msg_hdr/0 + msg_hdr/0, + + uint8/0, + uint16/0, + uint20/0, + uint32/0 ]). + +-type uint8() :: 0..16#FF. +-type uint16() :: 0..16#FFFF. +-type uint20() :: 0..16#FFFFF. +-type uint32() :: 0..16#FFFFFFFF. + + %% We support only a subset of all domains. -type domain() :: local | inet | inet6. %% We support only a subset of all types. +%% RDM - Reliably Delivered Messages -type type() :: stream | dgram | raw | rdm | seqpacket. %% We support only a subset of all protocols: @@ -117,8 +131,6 @@ -type ip4_address() :: {0..255, 0..255, 0..255, 0..255}. --type uint20() :: 0..16#FFFFF. --type uint32() :: 0..16#FFFFFFFF. -type in6_flow_info() :: uint20(). -type in6_scope_id() :: uint32(). @@ -190,16 +202,22 @@ sender_dry := boolean()}. -type sctp_assocparams() :: #{assoc_id := integer(), - max_rxt := non_neg_integer(), - num_peer_dests := non_neg_integer(), - peer_rwnd := non_neg_integer(), - local_rwnd := non_neg_integer(), - cookie_life := non_neg_integer()}. + max_rxt := uint16(), + num_peer_dests := uint16(), + peer_rwnd := uint32(), + local_rwnd := uint32(), + cookie_life := uint32()}. + +-type sctp_initmsg() :: #{num_outstreams := uint16(), + max_instreams := uint16(), + max_attempts := uint16(), + max_init_timeo := uint16() + }. -type sctp_rtoinfo() :: #{assoc_id := integer(), - initial := non_neg_integer(), - max := non_neg_integer(), - min := non_neg_integer()}. + initial := uint32(), + max := uint32(), + min := uint32()}. -type sockaddr_un() :: #{family := local, path := binary() | string()}. @@ -667,7 +685,7 @@ %% -define(SOCKET_OPT_SCTP_EXPLICIT_EOR, 15). %% -define(SOCKET_OPT_SCTP_FRAGMENT_INTERLEAVE, 16). %% -define(SOCKET_OPT_SCTP_GET_PEER_ADDR_INFO, 17). -%% -define(SOCKET_OPT_SCTP_INITMSG, 18). +-define(SOCKET_OPT_SCTP_INITMSG, 18). %% -define(SOCKET_OPT_SCTP_I_WANT_MAPPED_V4_ADDR, 19). %% -define(SOCKET_OPT_SCTP_LOCAL_AUTH_CHUNKS, 20). -define(SOCKET_OPT_SCTP_MAXSEG, 21). @@ -2293,6 +2311,17 @@ enc_setopt_value(sctp, events, #{data_in := DataIn, is_boolean(SndDry) andalso (P =:= sctp) -> V; +enc_setopt_value(sctp, initmsg, #{num_outstreams := NumOut, + max_instreams := MaxIn, + max_attempts := MaxAttempts, + max_init_timeo := MaxInitTO} = V, + _D, _T, P) + when is_integer(NumOut) andalso (NumOut >= 0) andalso + is_integer(MaxIn) andalso (MaxIn >= 0) andalso + is_integer(MaxAttempts) andalso (MaxAttempts >= 0) andalso + is_integer(MaxInitTO) andalso (MaxInitTO >= 0) andalso + (P =:= sctp) -> + V; enc_setopt_value(sctp, maxseg, V, _D, _T, P) when is_integer(V) andalso (V >= 0) andalso (P =:= sctp) -> V; @@ -2737,8 +2766,8 @@ enc_sockopt_key(sctp = L, fragment_interleave = Opt, _Dir, _D, _T, _P) -> not_supported({L, Opt}); enc_sockopt_key(sctp = L, get_peer_addr_info = Opt, _Dir, _D, _T, _P) -> not_supported({L, Opt}); -enc_sockopt_key(sctp = L, initmsg = Opt, _Dir, _D, _T, _P) -> - not_supported({L, Opt}); +enc_sockopt_key(sctp = _L, initmsg = _Opt, _Dir, _D, _T, _P) -> + ?SOCKET_OPT_SCTP_INITMSG; enc_sockopt_key(sctp = L, i_want_mapped_v4_addr = Opt, _Dir, _D, _T, _P) -> not_supported({L, Opt}); enc_sockopt_key(sctp = L, local_auth_chunks = Opt, _Dir, _D, _T, _P) -> diff --git a/lib/kernel/test/socket_server.erl b/lib/kernel/test/socket_server.erl index 6fc4c60543..986363b56d 100644 --- a/lib/kernel/test/socket_server.erl +++ b/lib/kernel/test/socket_server.erl @@ -206,12 +206,14 @@ do_manager_init(Domain, seqpacket = Type, sctp = Proto, _Peek) -> "~n associnfo: ~s" "~n autoclose: ~s" "~n disable-fragments: ~s" + "~n initmsg: ~s" "~n maxseg: ~s" "~n nodelay: ~s" "~n rtoinfo: ~s", [GO(associnfo), GO(autoclose), GO(disable_fragments), + GO(initmsg), GO(maxseg), GO(nodelay), GO(rtoinfo)]), -- cgit v1.2.3 From 3f1d17f3031b71ca6ff1f8e051859ad55e55822b Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Fri, 20 Jul 2018 12:28:19 +0200 Subject: [socket-nif] Add support for socket (level socket) option(s) [rcv|snd]timeo Added support for socket level socket option RCVTIMEO and SNDTIMEO. These are both a little strange, at least on linux. See the man pages for more info. OTP-14831 --- erts/doc/src/socket.xml | 9 ++ erts/doc/src/socket_usage.xml | 18 ++- erts/emulator/nifs/common/socket_int.h | 16 ++- erts/emulator/nifs/common/socket_nif.c | 233 +++++++++++++++++++++++++++++++++ erts/preloaded/ebin/socket.beam | Bin 56044 -> 56284 bytes erts/preloaded/src/socket.erl | 40 +++--- lib/kernel/test/socket_server.erl | 21 ++- 7 files changed, 308 insertions(+), 29 deletions(-) diff --git a/erts/doc/src/socket.xml b/erts/doc/src/socket.xml index fe23eaa138..14a21823b8 100644 --- a/erts/doc/src/socket.xml +++ b/erts/doc/src/socket.xml @@ -116,6 +116,9 @@ + + + @@ -146,6 +149,12 @@ + + + + + + diff --git a/erts/doc/src/socket_usage.xml b/erts/doc/src/socket_usage.xml index e90e682e39..221561c3e2 100644 --- a/erts/doc/src/socket_usage.xml +++ b/erts/doc/src/socket_usage.xml @@ -114,7 +114,7 @@ integer() yes yes - may require admin capability + require admin capability domain @@ -174,7 +174,14 @@ rcvbuf - integer() + non_neg_integer() + yes + yes + none + + + rcvtimeo + timeval() yes yes none @@ -200,6 +207,13 @@ yes none + + sndtimeo + timeval() + yes + yes + none +

Options for level ip:

diff --git a/erts/emulator/nifs/common/socket_int.h b/erts/emulator/nifs/common/socket_int.h index 28f42a7345..2d5049a9eb 100644 --- a/erts/emulator/nifs/common/socket_int.h +++ b/erts/emulator/nifs/common/socket_int.h @@ -152,6 +152,7 @@ extern ERL_NIF_TERM esock_atom_einval; #define MKA(E,S) enif_make_atom((E), (S)) #define MKBIN(E,B) enif_make_binary((E), (B)) #define MKI(E,I) enif_make_int((E), (I)) +#define MKL(E,L) enif_make_long((E), (L)) #define MKLA(E,A,L) enif_make_list_from_array((E), (A), (L)) #define MKEL(E) enif_make_list((E), 0) #define MKMA(E,KA,VA,L,M) enif_make_map_from_arrays((E), (KA), (VA), (L), (M)) @@ -167,7 +168,8 @@ extern ERL_NIF_TERM esock_atom_einval; #define MKT8(E,E1,E2,E3,E4,E5,E6,E7,E8) \ enif_make_tuple8((E), (E1), (E2), (E3), (E4), (E5), (E6), (E7), (E8)) #define MKTA(E, A, AL) enif_make_tuple_from_array((E), (A), (AL)) -#define MKUI(E,I) enif_make_uint((E), (I)) +#define MKUI(E,UI) enif_make_uint((E), (UI)) +#define MKUL(E,UL) enif_make_ulong((E), (UL)) #define MCREATE(N) enif_mutex_create((N)) #define MDESTROY(M) enif_mutex_destroy((M)) @@ -194,16 +196,18 @@ extern ERL_NIF_TERM esock_atom_einval; enif_get_atom_length((E), (TE), (LP), ERL_NIF_LATIN1) #define GET_ATOM(E, TE, BP, MAX) \ enif_get_atom((E), (TE), (BP), (MAX), ERL_NIF_LATIN1) -#define GET_BIN(E, TE, BP) enif_inspect_iolist_as_binary((E), (TE), (BP)) -#define GET_INT(E, TE, IP) enif_get_int((E), (TE), (IP)) +#define GET_BIN(E, TE, BP) enif_inspect_iolist_as_binary((E), (TE), (BP)) +#define GET_INT(E, TE, IP) enif_get_int((E), (TE), (IP)) #define GET_LIST_ELEM(E, L, HP, TP) enif_get_list_cell((E), (L), (HP), (TP)) #define GET_LIST_LEN(E, L, LP) enif_get_list_length((E), (L), (LP)) +#define GET_LONG(E, TE, LP) enif_get_long((E), (TE), (LP)) #define GET_LPID(E, T, P) enif_get_local_pid((E), (T), (P)) #define GET_STR(E, L, B, SZ) \ enif_get_string((E), (L), (B), (SZ), ERL_NIF_LATIN1) -#define GET_UINT(E, TE, IP) enif_get_uint((E), (TE), (IP)) -#define GET_TUPLE(E, TE, TSZ, TA) enif_get_tuple((E), (TE), (TSZ), (TA)) -#define GET_MAP_VAL(E, M, K, V) enif_get_map_value((E), (M), (K), (V)) +#define GET_UINT(E, TE, UIP) enif_get_uint((E), (TE), (UIP)) +#define GET_ULONG(E, TE, ULP) enif_get_long((E), (TE), (ULP)) +#define GET_TUPLE(E, TE, TSZ, TA) enif_get_tuple((E), (TE), (TSZ), (TA)) +#define GET_MAP_VAL(E, M, K, V) enif_get_map_value((E), (M), (K), (V)) #define ALLOC_BIN(SZ, BP) enif_alloc_binary((SZ), (BP)) #define REALLOC_BIN(SZ, BP) enif_realloc_binary((SZ), (BP)) diff --git a/erts/emulator/nifs/common/socket_nif.c b/erts/emulator/nifs/common/socket_nif.c index e8e1df0842..53122ab957 100644 --- a/erts/emulator/nifs/common/socket_nif.c +++ b/erts/emulator/nifs/common/socket_nif.c @@ -502,9 +502,11 @@ typedef union { #define SOCKET_OPT_SOCK_PRIORITY 17 #define SOCKET_OPT_SOCK_PROTOCOL 18 #define SOCKET_OPT_SOCK_RCVBUF 19 +#define SOCKET_OPT_SOCK_RCVTIMEO 22 #define SOCKET_OPT_SOCK_REUSEADDR 23 #define SOCKET_OPT_SOCK_REUSEPORT 24 #define SOCKET_OPT_SOCK_SNDBUF 27 +#define SOCKET_OPT_SOCK_SNDTIMEO 30 #define SOCKET_OPT_SOCK_TYPE 32 #define SOCKET_OPT_IP_ADD_MEMBERSHIP 1 @@ -1010,6 +1012,11 @@ static ERL_NIF_TERM nsetopt_lvl_sock_rcvbuf(ErlNifEnv* env, SocketDescriptor* descP, ERL_NIF_TERM eVal); #endif +#if defined(SO_RCVTIMEO) +static ERL_NIF_TERM nsetopt_lvl_sock_rcvtimeo(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); +#endif #if defined(SO_REUSEADDR) static ERL_NIF_TERM nsetopt_lvl_sock_reuseaddr(ErlNifEnv* env, SocketDescriptor* descP, @@ -1025,6 +1032,11 @@ static ERL_NIF_TERM nsetopt_lvl_sock_sndbuf(ErlNifEnv* env, SocketDescriptor* descP, ERL_NIF_TERM eVal); #endif +#if defined(SO_SNDTIMEO) +static ERL_NIF_TERM nsetopt_lvl_sock_sndtimeo(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); +#endif static ERL_NIF_TERM nsetopt_lvl_ip(ErlNifEnv* env, SocketDescriptor* descP, int eOpt, @@ -1339,6 +1351,10 @@ static ERL_NIF_TERM ngetopt_lvl_sock_protocol(ErlNifEnv* env, static ERL_NIF_TERM ngetopt_lvl_sock_rcvbuf(ErlNifEnv* env, SocketDescriptor* descP); #endif +#if defined(SO_RCVTIMEO) +static ERL_NIF_TERM ngetopt_lvl_sock_rcvtimeo(ErlNifEnv* env, + SocketDescriptor* descP); +#endif #if defined(SO_REUSEADDR) static ERL_NIF_TERM ngetopt_lvl_sock_reuseaddr(ErlNifEnv* env, SocketDescriptor* descP); @@ -1351,6 +1367,10 @@ static ERL_NIF_TERM ngetopt_lvl_sock_reuseport(ErlNifEnv* env, static ERL_NIF_TERM ngetopt_lvl_sock_sndbuf(ErlNifEnv* env, SocketDescriptor* descP); #endif +#if defined(SO_SNDTIMEO) +static ERL_NIF_TERM ngetopt_lvl_sock_sndtimeo(ErlNifEnv* env, + SocketDescriptor* descP); +#endif #if defined(SO_TYPE) static ERL_NIF_TERM ngetopt_lvl_sock_type(ErlNifEnv* env, SocketDescriptor* descP); @@ -1509,6 +1529,11 @@ static ERL_NIF_TERM nsetopt_int_opt(ErlNifEnv* env, int level, int opt, ERL_NIF_TERM eVal); +static ERL_NIF_TERM nsetopt_timeval_opt(ErlNifEnv* env, + SocketDescriptor* descP, + int level, + int opt, + ERL_NIF_TERM eVal); static ERL_NIF_TERM ngetopt_str_opt(ErlNifEnv* env, SocketDescriptor* descP, @@ -1523,6 +1548,10 @@ static ERL_NIF_TERM ngetopt_int_opt(ErlNifEnv* env, SocketDescriptor* descP, int level, int opt); +static ERL_NIF_TERM ngetopt_timeval_opt(ErlNifEnv* env, + SocketDescriptor* descP, + int level, + int opt); static ERL_NIF_TERM send_check_result(ErlNifEnv* env, SocketDescriptor* descP, @@ -1864,6 +1893,7 @@ static char str_partial_delivery[] = "partial_delivery"; static char str_peer_error[] = "peer_error"; static char str_peer_rwnd[] = "peer_rwnd"; static char str_probe[] = "probe"; +static char str_sec[] = "sec"; static char str_select[] = "select"; static char str_sender_dry[] = "sender_dry"; static char str_send_failure[] = "send_failure"; @@ -1871,6 +1901,7 @@ static char str_shutdown[] = "shutdown"; static char str_sourceaddr[] = "sourceaddr"; static char str_timeout[] = "timeout"; static char str_true[] = "true"; +static char str_usec[] = "usec"; static char str_want[] = "want"; static char str_lowdelay[] = "lowdelay"; @@ -1973,6 +2004,7 @@ static ERL_NIF_TERM atom_partial_delivery; static ERL_NIF_TERM atom_peer_error; static ERL_NIF_TERM atom_peer_rwnd; static ERL_NIF_TERM atom_probe; +static ERL_NIF_TERM atom_sec; static ERL_NIF_TERM atom_select; static ERL_NIF_TERM atom_sender_dry; static ERL_NIF_TERM atom_send_failure; @@ -1980,6 +2012,7 @@ static ERL_NIF_TERM atom_shutdown; static ERL_NIF_TERM atom_sourceaddr; static ERL_NIF_TERM atom_timeout; static ERL_NIF_TERM atom_true; +static ERL_NIF_TERM atom_usec; static ERL_NIF_TERM atom_want; static ERL_NIF_TERM atom_lowdelay; @@ -4346,6 +4379,12 @@ ERL_NIF_TERM nsetopt_lvl_socket(ErlNifEnv* env, break; #endif +#if defined(SO_RCVTIMEO) + case SOCKET_OPT_SOCK_RCVTIMEO: + result = nsetopt_lvl_sock_rcvtimeo(env, descP, eVal); + break; +#endif + #if defined(SO_REUSEADDR) case SOCKET_OPT_SOCK_REUSEADDR: result = nsetopt_lvl_sock_reuseaddr(env, descP, eVal); @@ -4364,11 +4403,22 @@ ERL_NIF_TERM nsetopt_lvl_socket(ErlNifEnv* env, break; #endif +#if defined(SO_SNDTIMEO) + case SOCKET_OPT_SOCK_SNDTIMEO: + result = nsetopt_lvl_sock_sndtimeo(env, descP, eVal); + break; +#endif + default: result = esock_make_error(env, esock_atom_einval); break; } + SSDBG( descP, + ("SOCKET", "nsetopt_lvl_socket -> done when" + "\r\n result: %T" + "\r\n", result) ); + return result; } @@ -4500,6 +4550,17 @@ ERL_NIF_TERM nsetopt_lvl_sock_rcvbuf(ErlNifEnv* env, #endif +#if defined(SO_RCVTIMEO) +static +ERL_NIF_TERM nsetopt_lvl_sock_rcvtimeo(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) +{ + return nsetopt_timeval_opt(env, descP, SOL_SOCKET, SO_RCVTIMEO, eVal); +} +#endif + + #if defined(SO_REUSEADDR) static ERL_NIF_TERM nsetopt_lvl_sock_reuseaddr(ErlNifEnv* env, @@ -4533,6 +4594,22 @@ ERL_NIF_TERM nsetopt_lvl_sock_sndbuf(ErlNifEnv* env, #endif +#if defined(SO_SNDTIMEO) +static +ERL_NIF_TERM nsetopt_lvl_sock_sndtimeo(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) +{ + SSDBG( descP, + ("SOCKET", "nsetopt_lvl_sock_sndtimeo -> entry with" + "\r\n eVal: %T" + "\r\n", eVal) ); + + return nsetopt_timeval_opt(env, descP, SOL_SOCKET, SO_SNDTIMEO, eVal); +} +#endif + + /* nsetopt_lvl_ip - Level *IP* option(s) */ @@ -6144,6 +6221,73 @@ ERL_NIF_TERM nsetopt_str_opt(ErlNifEnv* env, } +/* nsetopt_timeval_opt - set an option that has an (timeval) bool value + */ +static +ERL_NIF_TERM nsetopt_timeval_opt(ErlNifEnv* env, + SocketDescriptor* descP, + int level, + int opt, + ERL_NIF_TERM eVal) +{ + ERL_NIF_TERM result; + ERL_NIF_TERM eSec, eUSec; + struct timeval timeVal; + int res; + size_t sz; + + SSDBG( descP, + ("SOCKET", "nsetopt_timeval_opt -> entry with" + "\r\n eVal: %T" + "\r\n", eVal) ); + + // It must be a map + if (!IS_MAP(env, eVal)) + return esock_make_error(env, esock_atom_einval); + + // It must have atleast ten attributes + if (!enif_get_map_size(env, eVal, &sz) || (sz < 2)) + return esock_make_error(env, esock_atom_einval); + + SSDBG( descP, + ("SOCKET", "nsetopt_lvl_sctp_rtoinfo -> extract attributes\r\n") ); + + if (!GET_MAP_VAL(env, eVal, atom_sec, &eSec)) + return esock_make_error(env, esock_atom_einval); + + if (!GET_MAP_VAL(env, eVal, atom_usec, &eUSec)) + return esock_make_error(env, esock_atom_einval); + + SSDBG( descP, + ("SOCKET", "nsetopt_timeval_opt -> decode attributes\r\n") ); + + if (!GET_LONG(env, eSec, &timeVal.tv_sec)) + return esock_make_error(env, esock_atom_einval); + + if (!GET_LONG(env, eUSec, &timeVal.tv_usec)) + return esock_make_error(env, esock_atom_einval); + + SSDBG( descP, + ("SOCKET", "nsetopt_timeval_opt -> set timeval option\r\n") ); + + res = socket_setopt(descP->sock, level, opt, &timeVal, sizeof(timeVal)); + + if (res != 0) + result = esock_make_error_errno(env, sock_errno()); + else + result = esock_atom_ok; + + SSDBG( descP, + ("SOCKET", "nsetopt_timeval_opt -> done with" + "\r\n result: %T" + "\r\n", result) ); + + return result; + +} + + + static BOOLEAN_T elevel2level(BOOLEAN_T isEncoded, int eLevel, @@ -6719,6 +6863,12 @@ ERL_NIF_TERM ngetopt_lvl_socket(ErlNifEnv* env, break; #endif +#if defined(SO_RCVTIMEO) + case SOCKET_OPT_SOCK_RCVTIMEO: + result = ngetopt_lvl_sock_rcvtimeo(env, descP); + break; +#endif + #if defined(SO_REUSEADDR) case SOCKET_OPT_SOCK_REUSEADDR: result = ngetopt_lvl_sock_reuseaddr(env, descP); @@ -6737,6 +6887,12 @@ ERL_NIF_TERM ngetopt_lvl_socket(ErlNifEnv* env, break; #endif +#if defined(SO_SNDTIMEO) + case SOCKET_OPT_SOCK_SNDTIMEO: + result = ngetopt_lvl_sock_sndtimeo(env, descP); + break; +#endif + #if defined(SO_TYPE) case SOCKET_OPT_SOCK_TYPE: result = ngetopt_lvl_sock_type(env, descP); @@ -6748,6 +6904,11 @@ ERL_NIF_TERM ngetopt_lvl_socket(ErlNifEnv* env, break; } + SSDBG( descP, + ("SOCKET", "ngetopt_lvl_socket -> done when" + "\r\n result: %T" + "\r\n", result) ); + return result; } @@ -6979,6 +7140,16 @@ ERL_NIF_TERM ngetopt_lvl_sock_rcvbuf(ErlNifEnv* env, #endif +#if defined(SO_RCVTIMEO) +static +ERL_NIF_TERM ngetopt_lvl_sock_rcvtimeo(ErlNifEnv* env, + SocketDescriptor* descP) +{ + return ngetopt_timeval_opt(env, descP, SOL_SOCKET, SO_RCVTIMEO); +} +#endif + + #if defined(SO_REUSEADDR) static ERL_NIF_TERM ngetopt_lvl_sock_reuseaddr(ErlNifEnv* env, @@ -7009,6 +7180,16 @@ ERL_NIF_TERM ngetopt_lvl_sock_sndbuf(ErlNifEnv* env, #endif +#if defined(SO_SNDTIMEO) +static +ERL_NIF_TERM ngetopt_lvl_sock_sndtimeo(ErlNifEnv* env, + SocketDescriptor* descP) +{ + return ngetopt_timeval_opt(env, descP, SOL_SOCKET, SO_SNDTIMEO); +} +#endif + + #if defined(SO_TYPE) static ERL_NIF_TERM ngetopt_lvl_sock_type(ErlNifEnv* env, @@ -8037,6 +8218,56 @@ ERL_NIF_TERM ngetopt_int_opt(ErlNifEnv* env, +/* ngetopt_timeval_opt - get an timeval option + */ +static +ERL_NIF_TERM ngetopt_timeval_opt(ErlNifEnv* env, + SocketDescriptor* descP, + int level, + int opt) +{ + ERL_NIF_TERM result; + struct timeval val; + SOCKOPTLEN_T valSz = sizeof(val); + int res; + + SSDBG( descP, + ("SOCKET", "ngetopt_timeval_opt -> entry with" + "\r\n level: %d" + "\r\n opt: %d" + "\r\n", level, opt) ); + + sys_memzero((char*) &val, valSz); + res = sock_getopt(descP->sock, level, opt, &val, &valSz); + + if (res != 0) { + result = esock_make_error_errno(env, sock_errno()); + } else { + ERL_NIF_TERM eTimeVal; + ERL_NIF_TERM keys[] = {atom_sec, atom_usec}; + ERL_NIF_TERM vals[] = {MKL(env, val.tv_sec), MKL(env, val.tv_usec)}; + + unsigned int numKeys = sizeof(keys) / sizeof(ERL_NIF_TERM); + unsigned int numVals = sizeof(vals) / sizeof(ERL_NIF_TERM); + + ESOCK_ASSERT( (numKeys == numVals) ); + + if (!MKMA(env, keys, vals, numKeys, &eTimeVal)) + return esock_make_error(env, esock_atom_einval);; + + result = esock_make_ok2(env, eTimeVal); + } + + SSDBG( descP, + ("SOCKET", "ngetopt_timeval_opt -> done when" + "\r\n result: %T" + "\r\n", result) ); + + return result; +} + + + /* ngetopt_str_opt - get an string option * * We provide the max size of the string. This is the @@ -10238,6 +10469,7 @@ int on_load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info) atom_peer_rwnd = MKA(env, str_peer_rwnd); atom_peer_error = MKA(env, str_peer_error); atom_probe = MKA(env, str_probe); + atom_sec = MKA(env, str_sec); atom_select = MKA(env, str_select); atom_sender_dry = MKA(env, str_sender_dry); atom_send_failure = MKA(env, str_send_failure); @@ -10245,6 +10477,7 @@ int on_load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info) atom_sourceaddr = MKA(env, str_sourceaddr); atom_timeout = MKA(env, str_timeout); atom_true = MKA(env, str_true); + atom_usec = MKA(env, str_usec); atom_want = MKA(env, str_want); /* Global atom(s) */ diff --git a/erts/preloaded/ebin/socket.beam b/erts/preloaded/ebin/socket.beam index c4c830e051..cd4feb8a46 100644 Binary files a/erts/preloaded/ebin/socket.beam and b/erts/preloaded/ebin/socket.beam differ diff --git a/erts/preloaded/src/socket.erl b/erts/preloaded/src/socket.erl index 03c87a6df5..15f9693490 100644 --- a/erts/preloaded/src/socket.erl +++ b/erts/preloaded/src/socket.erl @@ -88,6 +88,7 @@ udp_socket_option/0, sctp_socket_option/0, + timeval/0, ip_tos_flag/0, ip_mreq/0, ip_mreq_source/0, @@ -101,7 +102,7 @@ msg_hdr/0, - + uint8/0, uint16/0, uint20/0, @@ -154,6 +155,16 @@ %% %% +-type timeval() :: #{sec := integer(), + usec := integer()}. + +%% If the integer value is used its up to the caller to ensure its valid! +-type ip_tos_flag() :: lowdeley | + throughput | + reliability | + mincost | + integer(). + %% This type is used when requesting to become member of a multicast %% group with a call to setopt. Example: %% @@ -166,13 +177,6 @@ %% interface => any}). %% -%% If the integer value is used its up to the caller to ensure its valid! --type ip_tos_flag() :: lowdeley | - throughput | - reliability | - mincost | - integer(). - -type ip_mreq() :: #{multiaddr := ip4_address(), interface := any | ip4_address()}. %% -type ip_mreqn() :: #{multiaddr := ip4_address(), @@ -572,7 +576,7 @@ -define(SOCKET_OPT_SOCK_RCVBUF, 19). %% -define(SOCKET_OPT_SOCK_RCVBUFFORCE, 20). %% -define(SOCKET_OPT_SOCK_RCVLOWAT, 21). -%% -define(SOCKET_OPT_SOCK_RCVTIMEO, 22). +-define(SOCKET_OPT_SOCK_RCVTIMEO, 22). -define(SOCKET_OPT_SOCK_REUSEADDR, 23). -define(SOCKET_OPT_SOCK_REUSEPORT, 24). %% -define(SOCKET_OPT_SOCK_RXQ_OVFL, 25). @@ -580,7 +584,7 @@ -define(SOCKET_OPT_SOCK_SNDBUF, 27). %% -define(SOCKET_OPT_SOCK_SNDBUFFORCE, 28). %% -define(SOCKET_OPT_SOCK_SNDLOWAT, 29). -%% -define(SOCKET_OPT_SOCK_SNDTIMEO, 30). +-define(SOCKET_OPT_SOCK_SNDTIMEO, 30). %% -define(SOCKET_OPT_SOCK_TIMESTAMP, 31). -define(SOCKET_OPT_SOCK_TYPE, 32). @@ -2121,12 +2125,18 @@ enc_setopt_value(socket, priority, V, _D, _T, _P) when is_integer(V) -> V; enc_setopt_value(socket, rcvbuf, V, _D, _T, _P) when is_integer(V) -> V; +enc_setopt_value(socket, rcvtimeo, #{sec := Sec, usec := USec} = V, _D, _T, _P) + when is_integer(Sec) andalso is_integer(USec) -> + V; enc_setopt_value(socket, reuseaddr, V, _D, _T, _P) when is_boolean(V) -> V; enc_setopt_value(socket, reuseport, V, _D, _T, _P) when is_boolean(V) -> V; enc_setopt_value(socket, sndbuf, V, _D, _T, _P) when is_integer(V) -> V; +enc_setopt_value(socket, sndtimeo, #{sec := Sec, usec := USec} = V, _D, _T, _P) + when is_integer(Sec) andalso is_integer(USec) -> + V; enc_setopt_value(socket = L, Opt, V, _D, _T, _P) -> not_supported({L, Opt, V}); @@ -2515,8 +2525,8 @@ enc_sockopt_key(socket = L, rcvbufforce = Opt, _Dir, _D, _T, _P) -> %% May not work on linux. enc_sockopt_key(socket = L, rcvlowat = Opt, _Dir, _D, _T, _P) -> not_supported({L, Opt}); -enc_sockopt_key(socket, rcvtimeo = Opt, _Dir, _D, _T, _P) -> - not_supported(Opt); +enc_sockopt_key(socket = _L, rcvtimeo = _Opt, _Dir, _D, _T, _P) -> + ?SOCKET_OPT_SOCK_RCVTIMEO; enc_sockopt_key(socket = _L, reuseaddr = _Opt, _Dir, _D, _T, _P) -> ?SOCKET_OPT_SOCK_REUSEADDR; enc_sockopt_key(socket = _L, reuseport = _Opt, _Dir, D, _T, _P) @@ -2526,15 +2536,15 @@ enc_sockopt_key(socket = L, rxq_ovfl = Opt, _Dir, _D, _T, _P) -> not_supported({L, Opt}); enc_sockopt_key(socket = L, setfib = Opt, set = _Dir, _D, _T, _P) -> not_supported({L, Opt}); -enc_sockopt_key(socket, sndbuf = _Opt, _Dir, _D, _T, _P) -> +enc_sockopt_key(socket = _L, sndbuf = _Opt, _Dir, _D, _T, _P) -> ?SOCKET_OPT_SOCK_SNDBUF; enc_sockopt_key(socket = L, sndbufforce = Opt, _Dir, _D, _T, _P) -> not_supported({L, Opt}); %% Not changeable on linux. enc_sockopt_key(socket = L, sndlowat = Opt, _Dir, _D, _T, _P) -> not_supported({L, Opt}); -enc_sockopt_key(socket = L, sndtimeo = Opt, _Dir, _D, _T, _P) -> - not_supported({L, Opt}); +enc_sockopt_key(socket = _L, sndtimeo = _Opt, _Dir, _D, _T, _P) -> + ?SOCKET_OPT_SOCK_SNDTIMEO; enc_sockopt_key(socket = L, timestamp = Opt, _Dir, _D, _T, _P) -> not_supported({L, Opt}); enc_sockopt_key(socket = _L, type = _Opt, _Dir, _D, _T, _P) -> diff --git a/lib/kernel/test/socket_server.erl b/lib/kernel/test/socket_server.erl index 986363b56d..80de3574d1 100644 --- a/lib/kernel/test/socket_server.erl +++ b/lib/kernel/test/socket_server.erl @@ -141,11 +141,14 @@ do_manager_init(Domain, dgram = Type, Proto, Peek) -> "~n debug: ~s" "~n prio: ~s" "~n rcvbuf: ~s" + "~n rcvtimeo: ~s" "~n sndbuf: ~s" + "~n sndtimeo: ~s" "~n => try find (local) address", [F(domain), F(type), F(protocol), F(broadcast), F(dontroute), F(keepalive), F(reuseaddr), F(linger), - F(debug), F(priority), F(rcvbuf), F(sndbuf)]), + F(debug), F(priority), + F(rcvbuf), F(rcvtimeo), F(sndbuf), F(sndtimeo)]), Addr = which_addr(Domain), SA = #{family => Domain, addr => Addr}, @@ -315,7 +318,7 @@ manager_stream_init(Sock, ID, NumAcceptors, Acc) case acceptor_start(Sock, ID) of {ok, {Pid, MRef}} -> i("acceptor ~w (~p) started", [ID, Pid]), - ?LIB:sleep(5000), + ?LIB:sleep(2000), manager_stream_init(Sock, ID+1, NumAcceptors-1, [{ID, Pid, MRef}|Acc]); {error, Reason} -> @@ -593,8 +596,10 @@ handler_init(Manager, ID, Peek, Sock) -> RA = GSO(reuseaddr), RP = GSO(reuseport), OOBI = GSO(oobinline), - SndBuf = GSO(sndbuf), RcvBuf = GSO(rcvbuf), + RcvTO = GSO(rcvtimeo), + SndBuf = GSO(sndbuf), + SndTO = GSO(sndtimeo), Linger = GSO(linger), MTU = GIP(mtu), MTUDisc = GIP(mtu_discover), @@ -613,8 +618,10 @@ handler_init(Manager, ID, Peek, Sock) -> "~n (socket) Reuse Port: ~s" "~n (socket) Bind To Device: ~s" "~n (socket) OOBInline: ~s" - "~n (socket) SndBuf: ~s" "~n (socket) RcvBuf: ~s" + "~n (socket) RcvTO: ~s" + "~n (socket) SndBuf: ~s" + "~n (socket) SndTO: ~s" "~n (socket) Linger: ~s" "~n (ip) MTU: ~s" "~n (ip) MTU Discovery: ~s" @@ -626,10 +633,12 @@ handler_init(Manager, ID, Peek, Sock) -> "~n (ip) Recv TOS: ~s" "~n (ip) Recv TTL: ~s", [Domain, Type, Proto, - RA, RP, B2D, OOBI, SndBuf, RcvBuf, Linger, + RA, RP, B2D, OOBI, + RcvBuf, RcvTO, SndBuf, SndTO, + Linger, MTU, MTUDisc, MALL, MIF, MLoop, MTTL, NF, RecvTOS, RecvTTL]), - %% socket:setopt(Sock, otp, debug, true), + handler_loop(#handler{peek = Peek, manager = Manager, type = Type, -- cgit v1.2.3 From 75498c0dd7682bae7787c4e2cd8d2680fa2b9c45 Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Fri, 20 Jul 2018 13:00:22 +0200 Subject: [socket-nif] Add support for socket (level socket) option(s) [rcv|snd]lowat Added support for socket level socket option RCVLOWAT and SNDLOWAT. These are both a little strange, at least on Linux. See the man pages for more info. For instance, sndlowat cannot be set on Linux. OTP-14831 --- erts/doc/src/socket_usage.xml | 16 +++++- erts/emulator/nifs/common/socket_nif.c | 86 +++++++++++++++++++++++++++++++++ erts/preloaded/ebin/socket.beam | Bin 56284 -> 56372 bytes erts/preloaded/src/socket.erl | 16 +++--- lib/kernel/test/socket_server.erl | 8 ++- 5 files changed, 117 insertions(+), 9 deletions(-) diff --git a/erts/doc/src/socket_usage.xml b/erts/doc/src/socket_usage.xml index 221561c3e2..9ea585694d 100644 --- a/erts/doc/src/socket_usage.xml +++ b/erts/doc/src/socket_usage.xml @@ -179,6 +179,13 @@ yes none + + rcvlowat + non_neg_integer() + yes + yes + none + rcvtimeo timeval() @@ -202,11 +209,18 @@ sndbuf - integer() + non_neg_integer() yes yes none + + sndlowat + non_neg_integer() + yes + yes + not changeable on Linux + sndtimeo timeval() diff --git a/erts/emulator/nifs/common/socket_nif.c b/erts/emulator/nifs/common/socket_nif.c index 53122ab957..365cbd541b 100644 --- a/erts/emulator/nifs/common/socket_nif.c +++ b/erts/emulator/nifs/common/socket_nif.c @@ -502,10 +502,12 @@ typedef union { #define SOCKET_OPT_SOCK_PRIORITY 17 #define SOCKET_OPT_SOCK_PROTOCOL 18 #define SOCKET_OPT_SOCK_RCVBUF 19 +#define SOCKET_OPT_SOCK_RCVLOWAT 21 #define SOCKET_OPT_SOCK_RCVTIMEO 22 #define SOCKET_OPT_SOCK_REUSEADDR 23 #define SOCKET_OPT_SOCK_REUSEPORT 24 #define SOCKET_OPT_SOCK_SNDBUF 27 +#define SOCKET_OPT_SOCK_SNDLOWAT 29 #define SOCKET_OPT_SOCK_SNDTIMEO 30 #define SOCKET_OPT_SOCK_TYPE 32 @@ -1012,6 +1014,11 @@ static ERL_NIF_TERM nsetopt_lvl_sock_rcvbuf(ErlNifEnv* env, SocketDescriptor* descP, ERL_NIF_TERM eVal); #endif +#if defined(SO_RCVLOWAT) +static ERL_NIF_TERM nsetopt_lvl_sock_rcvlowat(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); +#endif #if defined(SO_RCVTIMEO) static ERL_NIF_TERM nsetopt_lvl_sock_rcvtimeo(ErlNifEnv* env, SocketDescriptor* descP, @@ -1032,6 +1039,11 @@ static ERL_NIF_TERM nsetopt_lvl_sock_sndbuf(ErlNifEnv* env, SocketDescriptor* descP, ERL_NIF_TERM eVal); #endif +#if defined(SO_SNDLOWAT) +static ERL_NIF_TERM nsetopt_lvl_sock_sndlowat(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); +#endif #if defined(SO_SNDTIMEO) static ERL_NIF_TERM nsetopt_lvl_sock_sndtimeo(ErlNifEnv* env, SocketDescriptor* descP, @@ -1351,6 +1363,10 @@ static ERL_NIF_TERM ngetopt_lvl_sock_protocol(ErlNifEnv* env, static ERL_NIF_TERM ngetopt_lvl_sock_rcvbuf(ErlNifEnv* env, SocketDescriptor* descP); #endif +#if defined(SO_RCVLOWAT) +static ERL_NIF_TERM ngetopt_lvl_sock_rcvlowat(ErlNifEnv* env, + SocketDescriptor* descP); +#endif #if defined(SO_RCVTIMEO) static ERL_NIF_TERM ngetopt_lvl_sock_rcvtimeo(ErlNifEnv* env, SocketDescriptor* descP); @@ -1367,6 +1383,10 @@ static ERL_NIF_TERM ngetopt_lvl_sock_reuseport(ErlNifEnv* env, static ERL_NIF_TERM ngetopt_lvl_sock_sndbuf(ErlNifEnv* env, SocketDescriptor* descP); #endif +#if defined(SO_SNDLOWAT) +static ERL_NIF_TERM ngetopt_lvl_sock_sndlowat(ErlNifEnv* env, + SocketDescriptor* descP); +#endif #if defined(SO_SNDTIMEO) static ERL_NIF_TERM ngetopt_lvl_sock_sndtimeo(ErlNifEnv* env, SocketDescriptor* descP); @@ -4379,6 +4399,12 @@ ERL_NIF_TERM nsetopt_lvl_socket(ErlNifEnv* env, break; #endif +#if defined(SO_RCVLOWAT) + case SOCKET_OPT_SOCK_RCVLOWAT: + result = nsetopt_lvl_sock_rcvlowat(env, descP, eVal); + break; +#endif + #if defined(SO_RCVTIMEO) case SOCKET_OPT_SOCK_RCVTIMEO: result = nsetopt_lvl_sock_rcvtimeo(env, descP, eVal); @@ -4403,6 +4429,12 @@ ERL_NIF_TERM nsetopt_lvl_socket(ErlNifEnv* env, break; #endif +#if defined(SO_SNDLOWAT) + case SOCKET_OPT_SOCK_SNDLOWAT: + result = nsetopt_lvl_sock_sndlowat(env, descP, eVal); + break; +#endif + #if defined(SO_SNDTIMEO) case SOCKET_OPT_SOCK_SNDTIMEO: result = nsetopt_lvl_sock_sndtimeo(env, descP, eVal); @@ -4550,6 +4582,17 @@ ERL_NIF_TERM nsetopt_lvl_sock_rcvbuf(ErlNifEnv* env, #endif +#if defined(SO_RCVLOWAT) +static +ERL_NIF_TERM nsetopt_lvl_sock_rcvlowat(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) +{ + return nsetopt_int_opt(env, descP, SOL_SOCKET, SO_RCVLOWAT, eVal); +} +#endif + + #if defined(SO_RCVTIMEO) static ERL_NIF_TERM nsetopt_lvl_sock_rcvtimeo(ErlNifEnv* env, @@ -4594,6 +4637,17 @@ ERL_NIF_TERM nsetopt_lvl_sock_sndbuf(ErlNifEnv* env, #endif +#if defined(SO_SNDLOWAT) +static +ERL_NIF_TERM nsetopt_lvl_sock_sndlowat(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) +{ + return nsetopt_int_opt(env, descP, SOL_SOCKET, SO_SNDLOWAT, eVal); +} +#endif + + #if defined(SO_SNDTIMEO) static ERL_NIF_TERM nsetopt_lvl_sock_sndtimeo(ErlNifEnv* env, @@ -6863,6 +6917,12 @@ ERL_NIF_TERM ngetopt_lvl_socket(ErlNifEnv* env, break; #endif +#if defined(SO_RCVLOWAT) + case SOCKET_OPT_SOCK_RCVLOWAT: + result = ngetopt_lvl_sock_rcvlowat(env, descP); + break; +#endif + #if defined(SO_RCVTIMEO) case SOCKET_OPT_SOCK_RCVTIMEO: result = ngetopt_lvl_sock_rcvtimeo(env, descP); @@ -6887,6 +6947,12 @@ ERL_NIF_TERM ngetopt_lvl_socket(ErlNifEnv* env, break; #endif +#if defined(SO_SNDLOWAT) + case SOCKET_OPT_SOCK_SNDLOWAT: + result = ngetopt_lvl_sock_sndlowat(env, descP); + break; +#endif + #if defined(SO_SNDTIMEO) case SOCKET_OPT_SOCK_SNDTIMEO: result = ngetopt_lvl_sock_sndtimeo(env, descP); @@ -7140,6 +7206,16 @@ ERL_NIF_TERM ngetopt_lvl_sock_rcvbuf(ErlNifEnv* env, #endif +#if defined(SO_RCVLOWAT) +static +ERL_NIF_TERM ngetopt_lvl_sock_rcvlowat(ErlNifEnv* env, + SocketDescriptor* descP) +{ + return ngetopt_int_opt(env, descP, SOL_SOCKET, SO_RCVLOWAT); +} +#endif + + #if defined(SO_RCVTIMEO) static ERL_NIF_TERM ngetopt_lvl_sock_rcvtimeo(ErlNifEnv* env, @@ -7180,6 +7256,16 @@ ERL_NIF_TERM ngetopt_lvl_sock_sndbuf(ErlNifEnv* env, #endif +#if defined(SO_SNDLOWAT) +static +ERL_NIF_TERM ngetopt_lvl_sock_sndlowat(ErlNifEnv* env, + SocketDescriptor* descP) +{ + return ngetopt_int_opt(env, descP, SOL_SOCKET, SO_SNDLOWAT); +} +#endif + + #if defined(SO_SNDTIMEO) static ERL_NIF_TERM ngetopt_lvl_sock_sndtimeo(ErlNifEnv* env, diff --git a/erts/preloaded/ebin/socket.beam b/erts/preloaded/ebin/socket.beam index cd4feb8a46..b8d0e86a20 100644 Binary files a/erts/preloaded/ebin/socket.beam and b/erts/preloaded/ebin/socket.beam differ diff --git a/erts/preloaded/src/socket.erl b/erts/preloaded/src/socket.erl index 15f9693490..3ccba06f6b 100644 --- a/erts/preloaded/src/socket.erl +++ b/erts/preloaded/src/socket.erl @@ -575,7 +575,7 @@ -define(SOCKET_OPT_SOCK_PROTOCOL, 18). -define(SOCKET_OPT_SOCK_RCVBUF, 19). %% -define(SOCKET_OPT_SOCK_RCVBUFFORCE, 20). -%% -define(SOCKET_OPT_SOCK_RCVLOWAT, 21). +-define(SOCKET_OPT_SOCK_RCVLOWAT, 21). -define(SOCKET_OPT_SOCK_RCVTIMEO, 22). -define(SOCKET_OPT_SOCK_REUSEADDR, 23). -define(SOCKET_OPT_SOCK_REUSEPORT, 24). @@ -583,7 +583,7 @@ %% -define(SOCKET_OPT_SOCK_SETFIB, 26). -define(SOCKET_OPT_SOCK_SNDBUF, 27). %% -define(SOCKET_OPT_SOCK_SNDBUFFORCE, 28). -%% -define(SOCKET_OPT_SOCK_SNDLOWAT, 29). +-define(SOCKET_OPT_SOCK_SNDLOWAT, 29). -define(SOCKET_OPT_SOCK_SNDTIMEO, 30). %% -define(SOCKET_OPT_SOCK_TIMESTAMP, 31). -define(SOCKET_OPT_SOCK_TYPE, 32). @@ -2125,6 +2125,8 @@ enc_setopt_value(socket, priority, V, _D, _T, _P) when is_integer(V) -> V; enc_setopt_value(socket, rcvbuf, V, _D, _T, _P) when is_integer(V) -> V; +enc_setopt_value(socket, rcvlowat, V, _D, _T, _P) when is_integer(V) -> + V; enc_setopt_value(socket, rcvtimeo, #{sec := Sec, usec := USec} = V, _D, _T, _P) when is_integer(Sec) andalso is_integer(USec) -> V; @@ -2134,6 +2136,8 @@ enc_setopt_value(socket, reuseport, V, _D, _T, _P) when is_boolean(V) -> V; enc_setopt_value(socket, sndbuf, V, _D, _T, _P) when is_integer(V) -> V; +enc_setopt_value(socket, sndlowat, V, _D, _T, _P) when is_integer(V) -> + V; enc_setopt_value(socket, sndtimeo, #{sec := Sec, usec := USec} = V, _D, _T, _P) when is_integer(Sec) andalso is_integer(USec) -> V; @@ -2523,8 +2527,8 @@ enc_sockopt_key(socket, rcvbuf = _Opt, _Dir, _D, _T, _P) -> enc_sockopt_key(socket = L, rcvbufforce = Opt, _Dir, _D, _T, _P) -> not_supported({L, Opt}); %% May not work on linux. -enc_sockopt_key(socket = L, rcvlowat = Opt, _Dir, _D, _T, _P) -> - not_supported({L, Opt}); +enc_sockopt_key(socket = _L, rcvlowat = _Opt, _Dir, _D, _T, _P) -> + ?SOCKET_OPT_SOCK_RCVLOWAT; enc_sockopt_key(socket = _L, rcvtimeo = _Opt, _Dir, _D, _T, _P) -> ?SOCKET_OPT_SOCK_RCVTIMEO; enc_sockopt_key(socket = _L, reuseaddr = _Opt, _Dir, _D, _T, _P) -> @@ -2541,8 +2545,8 @@ enc_sockopt_key(socket = _L, sndbuf = _Opt, _Dir, _D, _T, _P) -> enc_sockopt_key(socket = L, sndbufforce = Opt, _Dir, _D, _T, _P) -> not_supported({L, Opt}); %% Not changeable on linux. -enc_sockopt_key(socket = L, sndlowat = Opt, _Dir, _D, _T, _P) -> - not_supported({L, Opt}); +enc_sockopt_key(socket = _L, sndlowat = _Opt, _Dir, _D, _T, _P) -> + ?SOCKET_OPT_SOCK_SNDLOWAT; enc_sockopt_key(socket = _L, sndtimeo = _Opt, _Dir, _D, _T, _P) -> ?SOCKET_OPT_SOCK_SNDTIMEO; enc_sockopt_key(socket = L, timestamp = Opt, _Dir, _D, _T, _P) -> diff --git a/lib/kernel/test/socket_server.erl b/lib/kernel/test/socket_server.erl index 80de3574d1..10239b0265 100644 --- a/lib/kernel/test/socket_server.erl +++ b/lib/kernel/test/socket_server.erl @@ -597,8 +597,10 @@ handler_init(Manager, ID, Peek, Sock) -> RP = GSO(reuseport), OOBI = GSO(oobinline), RcvBuf = GSO(rcvbuf), + RcvLW = GSO(rcvlowat), RcvTO = GSO(rcvtimeo), SndBuf = GSO(sndbuf), + SndLW = GSO(sndlowat), SndTO = GSO(sndtimeo), Linger = GSO(linger), MTU = GIP(mtu), @@ -619,8 +621,10 @@ handler_init(Manager, ID, Peek, Sock) -> "~n (socket) Bind To Device: ~s" "~n (socket) OOBInline: ~s" "~n (socket) RcvBuf: ~s" + "~n (socket) RcvLW: ~s" "~n (socket) RcvTO: ~s" "~n (socket) SndBuf: ~s" + "~n (socket) SndLW: ~s" "~n (socket) SndTO: ~s" "~n (socket) Linger: ~s" "~n (ip) MTU: ~s" @@ -634,11 +638,11 @@ handler_init(Manager, ID, Peek, Sock) -> "~n (ip) Recv TTL: ~s", [Domain, Type, Proto, RA, RP, B2D, OOBI, - RcvBuf, RcvTO, SndBuf, SndTO, + RcvBuf, RcvLW, RcvTO, SndBuf, SndLW, SndTO, Linger, MTU, MTUDisc, MALL, MIF, MLoop, MTTL, NF, RecvTOS, RecvTTL]), - + handler_loop(#handler{peek = Peek, manager = Manager, type = Type, -- cgit v1.2.3 From 84f62ae80dd08874d0d5fbedc532605394e897c1 Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Fri, 20 Jul 2018 14:01:14 +0200 Subject: [socket-nif] Add support for socket (level socket) option timestamp Added support for socket level socket option TIMESTAMP. OTP-14831 --- erts/doc/src/socket_usage.xml | 14 +++++++++++ erts/emulator/nifs/common/socket_nif.c | 43 +++++++++++++++++++++++++++++++++ erts/preloaded/ebin/socket.beam | Bin 56372 -> 56432 bytes erts/preloaded/src/socket.erl | 12 +++++---- lib/kernel/test/socket_server.erl | 4 ++- 5 files changed, 67 insertions(+), 6 deletions(-) diff --git a/erts/doc/src/socket_usage.xml b/erts/doc/src/socket_usage.xml index 9ea585694d..c875ecd6f0 100644 --- a/erts/doc/src/socket_usage.xml +++ b/erts/doc/src/socket_usage.xml @@ -228,6 +228,20 @@ yes none + + timestamp + boolean() + yes + yes + none + + + type + type() + no + yes + none +

Options for level ip:

diff --git a/erts/emulator/nifs/common/socket_nif.c b/erts/emulator/nifs/common/socket_nif.c index 365cbd541b..6df2020e5e 100644 --- a/erts/emulator/nifs/common/socket_nif.c +++ b/erts/emulator/nifs/common/socket_nif.c @@ -509,6 +509,7 @@ typedef union { #define SOCKET_OPT_SOCK_SNDBUF 27 #define SOCKET_OPT_SOCK_SNDLOWAT 29 #define SOCKET_OPT_SOCK_SNDTIMEO 30 +#define SOCKET_OPT_SOCK_TIMESTAMP 31 #define SOCKET_OPT_SOCK_TYPE 32 #define SOCKET_OPT_IP_ADD_MEMBERSHIP 1 @@ -1049,6 +1050,11 @@ static ERL_NIF_TERM nsetopt_lvl_sock_sndtimeo(ErlNifEnv* env, SocketDescriptor* descP, ERL_NIF_TERM eVal); #endif +#if defined(SO_TIMESTAMP) +static ERL_NIF_TERM nsetopt_lvl_sock_timestamp(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); +#endif static ERL_NIF_TERM nsetopt_lvl_ip(ErlNifEnv* env, SocketDescriptor* descP, int eOpt, @@ -1391,6 +1397,10 @@ static ERL_NIF_TERM ngetopt_lvl_sock_sndlowat(ErlNifEnv* env, static ERL_NIF_TERM ngetopt_lvl_sock_sndtimeo(ErlNifEnv* env, SocketDescriptor* descP); #endif +#if defined(SO_TIMESTAMP) +static ERL_NIF_TERM ngetopt_lvl_sock_timestamp(ErlNifEnv* env, + SocketDescriptor* descP); +#endif #if defined(SO_TYPE) static ERL_NIF_TERM ngetopt_lvl_sock_type(ErlNifEnv* env, SocketDescriptor* descP); @@ -4441,6 +4451,12 @@ ERL_NIF_TERM nsetopt_lvl_socket(ErlNifEnv* env, break; #endif +#if defined(SO_TIMESTAMP) + case SOCKET_OPT_SOCK_TIMESTAMP: + result = nsetopt_lvl_sock_timestamp(env, descP, eVal); + break; +#endif + default: result = esock_make_error(env, esock_atom_einval); break; @@ -4664,6 +4680,17 @@ ERL_NIF_TERM nsetopt_lvl_sock_sndtimeo(ErlNifEnv* env, #endif +#if defined(SO_TIMESTAMP) +static +ERL_NIF_TERM nsetopt_lvl_sock_timestamp(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) +{ + return nsetopt_bool_opt(env, descP, SOL_SOCKET, SO_TIMESTAMP, eVal); +} +#endif + + /* nsetopt_lvl_ip - Level *IP* option(s) */ @@ -6959,6 +6986,12 @@ ERL_NIF_TERM ngetopt_lvl_socket(ErlNifEnv* env, break; #endif +#if defined(SO_TIMESTAMP) + case SOCKET_OPT_SOCK_TIMESTAMP: + result = ngetopt_lvl_sock_timestamp(env, descP); + break; +#endif + #if defined(SO_TYPE) case SOCKET_OPT_SOCK_TYPE: result = ngetopt_lvl_sock_type(env, descP); @@ -7276,6 +7309,16 @@ ERL_NIF_TERM ngetopt_lvl_sock_sndtimeo(ErlNifEnv* env, #endif +#if defined(SO_TIMESTAMP) +static +ERL_NIF_TERM ngetopt_lvl_sock_timestamp(ErlNifEnv* env, + SocketDescriptor* descP) +{ + return ngetopt_bool_opt(env, descP, SOL_SOCKET, SO_TIMESTAMP); +} +#endif + + #if defined(SO_TYPE) static ERL_NIF_TERM ngetopt_lvl_sock_type(ErlNifEnv* env, diff --git a/erts/preloaded/ebin/socket.beam b/erts/preloaded/ebin/socket.beam index b8d0e86a20..8799f07bc0 100644 Binary files a/erts/preloaded/ebin/socket.beam and b/erts/preloaded/ebin/socket.beam differ diff --git a/erts/preloaded/src/socket.erl b/erts/preloaded/src/socket.erl index 3ccba06f6b..c231647f8c 100644 --- a/erts/preloaded/src/socket.erl +++ b/erts/preloaded/src/socket.erl @@ -556,7 +556,7 @@ -define(SOCKET_OPT_OTP_CTRL_PROC, 3). -define(SOCKET_OPT_SOCK_ACCEPTCONN, 1). -%% -define(SOCKET_OPT_SOCK_ACCEPTFILTER, 2). +%% -define(SOCKET_OPT_SOCK_ACCEPTFILTER, 2). % FreeBSD -define(SOCKET_OPT_SOCK_BINDTODEVICE, 3). -define(SOCKET_OPT_SOCK_BROADCAST, 4). %% -define(SOCKET_OPT_SOCK_BUSY_POLL, 5). @@ -580,12 +580,12 @@ -define(SOCKET_OPT_SOCK_REUSEADDR, 23). -define(SOCKET_OPT_SOCK_REUSEPORT, 24). %% -define(SOCKET_OPT_SOCK_RXQ_OVFL, 25). -%% -define(SOCKET_OPT_SOCK_SETFIB, 26). +%% -define(SOCKET_OPT_SOCK_SETFIB, 26). % FreeBSD -define(SOCKET_OPT_SOCK_SNDBUF, 27). %% -define(SOCKET_OPT_SOCK_SNDBUFFORCE, 28). -define(SOCKET_OPT_SOCK_SNDLOWAT, 29). -define(SOCKET_OPT_SOCK_SNDTIMEO, 30). -%% -define(SOCKET_OPT_SOCK_TIMESTAMP, 31). +-define(SOCKET_OPT_SOCK_TIMESTAMP, 31). -define(SOCKET_OPT_SOCK_TYPE, 32). -define(SOCKET_OPT_IP_ADD_MEMBERSHIP, 1). @@ -2141,6 +2141,8 @@ enc_setopt_value(socket, sndlowat, V, _D, _T, _P) when is_integer(V) -> enc_setopt_value(socket, sndtimeo, #{sec := Sec, usec := USec} = V, _D, _T, _P) when is_integer(Sec) andalso is_integer(USec) -> V; +enc_setopt_value(socket, timestamp, V, _D, _T, _P) when is_boolean(V) -> + V; enc_setopt_value(socket = L, Opt, V, _D, _T, _P) -> not_supported({L, Opt, V}); @@ -2549,8 +2551,8 @@ enc_sockopt_key(socket = _L, sndlowat = _Opt, _Dir, _D, _T, _P) -> ?SOCKET_OPT_SOCK_SNDLOWAT; enc_sockopt_key(socket = _L, sndtimeo = _Opt, _Dir, _D, _T, _P) -> ?SOCKET_OPT_SOCK_SNDTIMEO; -enc_sockopt_key(socket = L, timestamp = Opt, _Dir, _D, _T, _P) -> - not_supported({L, Opt}); +enc_sockopt_key(socket = _L, timestamp = _Opt, _Dir, _D, _T, _P) -> + ?SOCKET_OPT_SOCK_TIMESTAMP; enc_sockopt_key(socket = _L, type = _Opt, _Dir, _D, _T, _P) -> ?SOCKET_OPT_SOCK_TYPE; enc_sockopt_key(socket = L, UnknownOpt, _Dir, _D, _T, _P) -> diff --git a/lib/kernel/test/socket_server.erl b/lib/kernel/test/socket_server.erl index 10239b0265..6aa26494f7 100644 --- a/lib/kernel/test/socket_server.erl +++ b/lib/kernel/test/socket_server.erl @@ -603,6 +603,7 @@ handler_init(Manager, ID, Peek, Sock) -> SndLW = GSO(sndlowat), SndTO = GSO(sndtimeo), Linger = GSO(linger), + Timestamp = GSO(timestamp), MTU = GIP(mtu), MTUDisc = GIP(mtu_discover), MALL = GIP(multicast_all), @@ -627,6 +628,7 @@ handler_init(Manager, ID, Peek, Sock) -> "~n (socket) SndLW: ~s" "~n (socket) SndTO: ~s" "~n (socket) Linger: ~s" + "~n (socket) Timestamp: ~s" "~n (ip) MTU: ~s" "~n (ip) MTU Discovery: ~s" "~n (ip) Multicast ALL: ~s" @@ -639,7 +641,7 @@ handler_init(Manager, ID, Peek, Sock) -> [Domain, Type, Proto, RA, RP, B2D, OOBI, RcvBuf, RcvLW, RcvTO, SndBuf, SndLW, SndTO, - Linger, + Linger, Timestamp, MTU, MTUDisc, MALL, MIF, MLoop, MTTL, NF, RecvTOS, RecvTTL]), -- cgit v1.2.3 From 1c26ae984a79224ce64b40dbc7239bf9721bb096 Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Fri, 20 Jul 2018 15:14:06 +0200 Subject: [socket-nif] Add support for socket (level ip) option freebind Added support for ip level socket option FREEBIND. Note that there is an option available on FreeBSD called IP_BINDANY, which seems to have similar properties (FREEBIND is *not* available on FreeBSD). There are some restrictions for this option though (which is not mentioned in the Linux man page). OTP-14831 --- erts/doc/src/socket_usage.xml | 7 ++++ erts/emulator/nifs/common/socket_nif.c | 60 +++++++++++++++++++++++++++++++++ erts/preloaded/ebin/socket.beam | Bin 56432 -> 56564 bytes erts/preloaded/src/socket.erl | 17 +++++++--- lib/kernel/test/socket_server.erl | 4 ++- 5 files changed, 83 insertions(+), 5 deletions(-) diff --git a/erts/doc/src/socket_usage.xml b/erts/doc/src/socket_usage.xml index c875ecd6f0..a286171a1b 100644 --- a/erts/doc/src/socket_usage.xml +++ b/erts/doc/src/socket_usage.xml @@ -288,6 +288,13 @@ no none + + freebind + boolean() + yes + yes + none + minttl integer() diff --git a/erts/emulator/nifs/common/socket_nif.c b/erts/emulator/nifs/common/socket_nif.c index 6df2020e5e..f319f171ad 100644 --- a/erts/emulator/nifs/common/socket_nif.c +++ b/erts/emulator/nifs/common/socket_nif.c @@ -517,6 +517,7 @@ typedef union { #define SOCKET_OPT_IP_BLOCK_SOURCE 3 #define SOCKET_OPT_IP_DROP_MEMBERSHIP 5 #define SOCKET_OPT_IP_DROP_SOURCE_MEMBERSHIP 6 +#define SOCKET_OPT_IP_FREEBIND 7 #define SOCKET_OPT_IP_MINTTL 9 #define SOCKET_OPT_IP_MTU 11 #define SOCKET_OPT_IP_MTU_DISCOVER 12 @@ -1086,6 +1087,11 @@ static ERL_NIF_TERM nsetopt_lvl_ip_drop_source_membership(ErlNifEnv* env, SocketDescriptor* descP, ERL_NIF_TERM eVal); #endif +#if defined(IP_FREEBIND) +static ERL_NIF_TERM nsetopt_lvl_ip_freebind(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); +#endif #if defined(IP_MINTTL) static ERL_NIF_TERM nsetopt_lvl_ip_minttl(ErlNifEnv* env, SocketDescriptor* descP, @@ -1408,6 +1414,10 @@ static ERL_NIF_TERM ngetopt_lvl_sock_type(ErlNifEnv* env, static ERL_NIF_TERM ngetopt_lvl_ip(ErlNifEnv* env, SocketDescriptor* descP, int eOpt); +#if defined(IP_FREEBIND) +static ERL_NIF_TERM ngetopt_lvl_ip_freebind(ErlNifEnv* env, + SocketDescriptor* descP); +#endif #if defined(IP_MINTTL) static ERL_NIF_TERM ngetopt_lvl_ip_minttl(ErlNifEnv* env, SocketDescriptor* descP); @@ -4738,6 +4748,12 @@ ERL_NIF_TERM nsetopt_lvl_ip(ErlNifEnv* env, break; #endif +#if defined(IP_FREEBIND) + case SOCKET_OPT_IP_FREEBIND: + result = nsetopt_lvl_ip_freebind(env, descP, eVal); + break; +#endif + #if defined(IP_MINTTL) case SOCKET_OPT_IP_MINTTL: result = nsetopt_lvl_ip_minttl(env, descP, eVal); @@ -4936,6 +4952,26 @@ ERL_NIF_TERM nsetopt_lvl_ip_drop_source_membership(ErlNifEnv* env, +/* nsetopt_lvl_ip_freebind - Level IP FREEBIND option + */ +#if defined(IP_FREEBIND) +static +ERL_NIF_TERM nsetopt_lvl_ip_freebind(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) +{ +#if defined(SOL_IP) + int level = SOL_IP; +#else + int level = IPPROTO_IP; +#endif + + return nsetopt_bool_opt(env, descP, level, IP_FREEBIND, eVal); +} +#endif + + + /* nsetopt_lvl_ip_minttl - Level IP MINTTL option */ #if defined(IP_MINTTL) @@ -7374,6 +7410,12 @@ ERL_NIF_TERM ngetopt_lvl_ip(ErlNifEnv* env, ERL_NIF_TERM result; switch (eOpt) { +#if defined(IP_FREEBIND) + case SOCKET_OPT_IP_FREEBIND: + result = ngetopt_lvl_ip_freebind(env, descP); + break; +#endif + #if defined(IP_MINTTL) case SOCKET_OPT_IP_MINTTL: result = ngetopt_lvl_ip_minttl(env, descP); @@ -7485,6 +7527,24 @@ ERL_NIF_TERM ngetopt_lvl_ip_minttl(ErlNifEnv* env, #endif +/* ngetopt_lvl_ip_freebind - Level IP FREEBIND option + */ +#if defined(IP_FREEBIND) +static +ERL_NIF_TERM ngetopt_lvl_ip_freebind(ErlNifEnv* env, + SocketDescriptor* descP) +{ +#if defined(SOL_IP) + int level = SOL_IP; +#else + int level = IPPROTO_IP; +#endif + + return ngetopt_bool_opt(env, descP, level, IP_FREEBIND); +} +#endif + + /* ngetopt_lvl_ip_mtu - Level IP MTU option */ #if defined(IP_MTU) diff --git a/erts/preloaded/ebin/socket.beam b/erts/preloaded/ebin/socket.beam index 8799f07bc0..3657212f81 100644 Binary files a/erts/preloaded/ebin/socket.beam and b/erts/preloaded/ebin/socket.beam differ diff --git a/erts/preloaded/src/socket.erl b/erts/preloaded/src/socket.erl index c231647f8c..329223c443 100644 --- a/erts/preloaded/src/socket.erl +++ b/erts/preloaded/src/socket.erl @@ -87,6 +87,7 @@ tcp_socket_option/0, udp_socket_option/0, sctp_socket_option/0, + raw_socket_option/0, timeval/0, ip_tos_flag/0, @@ -258,7 +259,7 @@ %% Int - Raw level, sent down and used "as is". -type sockopt_level() :: otp | socket | - ip | ipv6 | tcp | udp | sctp | + ip | ipv6 | tcp | udp | sctp | raw | non_neg_integer(). %% There are some options that are 'read-only'. @@ -434,6 +435,8 @@ status | use_ext_recvinfo. +-type raw_socket_option() :: filter. + %% -type plain_socket_option() :: integer(). %% -type sockopt() :: otp_socket_option() | %% socket_option() | @@ -442,6 +445,7 @@ %% tcp_socket_option() | %% udp_socket_option() | %% sctp_socket_option() | +%% raw_socket_option() | %% plain_socket_option(). -type socket_info() :: #{domain => domain(), @@ -594,7 +598,7 @@ %% -define(SOCKET_OPT_IP_DONTFRAG, 4). -define(SOCKET_OPT_IP_DROP_MEMBERSHIP, 5). -define(SOCKET_OPT_IP_DROP_SOURCE_MEMBERSHIP, 6). -%% -define(SOCKET_OPT_IP_FREEBIND, 7). +-define(SOCKET_OPT_IP_FREEBIND, 7). %% -define(SOCKET_OPT_IP_HDRINCL, 8). -define(SOCKET_OPT_IP_MINTTL, 9). %% -define(SOCKET_OPT_IP_MSFILTER, 10). @@ -2177,6 +2181,8 @@ enc_setopt_value(ip, drop_source_membership, #{multiaddr := MA, (is_tuple(IF) andalso (size(IF) =:= 4)) andalso (is_tuple(SA) andalso (size(SA) =:= 4)) -> V; +enc_setopt_value(ip, freebind, V, _D, _T, _P) when is_boolean(V) -> + V; enc_setopt_value(ip, minttl, V, _D, _T, _P) when is_integer(V) -> V; enc_setopt_value(ip, mtu_discover, V, _D, _T, _P) @@ -2358,6 +2364,9 @@ enc_setopt_value(sctp, rtoinfo, #{assoc_id := AssocId, enc_setopt_value(sctp = L, Opt, V, _D, _T, _P) -> not_supported({L, Opt, V}); +enc_setopt_value(raw = L, Opt, _V, _D, _T, _P) -> + not_supported({L, Opt}); + %% Is this correct? What about getopt? enc_setopt_value(L, Opt, V, _, _, _) when is_integer(L) andalso is_integer(Opt) andalso is_binary(V) -> @@ -2574,8 +2583,8 @@ enc_sockopt_key(ip = _L, drop_membership = _Opt, set = _Dir, _D, _T, _P) -> enc_sockopt_key(ip = _L, drop_source_membership = _Opt, set = _Dir, _D, _T, _P) -> ?SOCKET_OPT_IP_DROP_SOURCE_MEMBERSHIP; %% Linux only? -enc_sockopt_key(ip = L, free_bind = Opt, _Dir, _D, _T, _P) -> - not_supported({L, Opt}); +enc_sockopt_key(ip = _L, freebind = _Opt, _Dir, _D, _T, _P) -> + ?SOCKET_OPT_IP_FREEBIND; enc_sockopt_key(ip = L, hdrincl = Opt, _Dir, _D, raw = _T, _P) -> not_supported({L, Opt}); %% FreeBSD only? diff --git a/lib/kernel/test/socket_server.erl b/lib/kernel/test/socket_server.erl index 6aa26494f7..971ceb2093 100644 --- a/lib/kernel/test/socket_server.erl +++ b/lib/kernel/test/socket_server.erl @@ -604,6 +604,7 @@ handler_init(Manager, ID, Peek, Sock) -> SndTO = GSO(sndtimeo), Linger = GSO(linger), Timestamp = GSO(timestamp), + FreeBind = GIP(freebind), MTU = GIP(mtu), MTUDisc = GIP(mtu_discover), MALL = GIP(multicast_all), @@ -629,6 +630,7 @@ handler_init(Manager, ID, Peek, Sock) -> "~n (socket) SndTO: ~s" "~n (socket) Linger: ~s" "~n (socket) Timestamp: ~s" + "~n (ip) FreeBind: ~s" "~n (ip) MTU: ~s" "~n (ip) MTU Discovery: ~s" "~n (ip) Multicast ALL: ~s" @@ -642,7 +644,7 @@ handler_init(Manager, ID, Peek, Sock) -> RA, RP, B2D, OOBI, RcvBuf, RcvLW, RcvTO, SndBuf, SndLW, SndTO, Linger, Timestamp, - MTU, MTUDisc, MALL, MIF, MLoop, MTTL, + FreeBind, MTU, MTUDisc, MALL, MIF, MLoop, MTTL, NF, RecvTOS, RecvTTL]), handler_loop(#handler{peek = Peek, -- cgit v1.2.3 From 31ef72ceda0bf5bba902bf18f3b445950516d6af Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Mon, 23 Jul 2018 10:57:25 +0200 Subject: [socket-nif] Add support for socket (level ip) option recvopts Added support for ip level socket option RECVOPTS. OTP-14831 --- erts/doc/src/socket_usage.xml | 7 +++ erts/emulator/nifs/common/socket_nif.c | 91 ++++++++++++++++++++++++++++++++- erts/preloaded/ebin/socket.beam | Bin 56564 -> 56604 bytes erts/preloaded/src/socket.erl | 9 ++-- lib/kernel/test/socket_server.erl | 8 ++- 5 files changed, 109 insertions(+), 6 deletions(-) diff --git a/erts/doc/src/socket_usage.xml b/erts/doc/src/socket_usage.xml index a286171a1b..eb1cbf6984 100644 --- a/erts/doc/src/socket_usage.xml +++ b/erts/doc/src/socket_usage.xml @@ -358,6 +358,13 @@ yes type = dgram | raw + + recvopts + boolean() + yes + yes + type =/= stream + recvttl boolean() diff --git a/erts/emulator/nifs/common/socket_nif.c b/erts/emulator/nifs/common/socket_nif.c index f319f171ad..7e4504cd2d 100644 --- a/erts/emulator/nifs/common/socket_nif.c +++ b/erts/emulator/nifs/common/socket_nif.c @@ -527,6 +527,7 @@ typedef union { #define SOCKET_OPT_IP_MULTICAST_TTL 16 #define SOCKET_OPT_IP_NODEFRAG 17 #define SOCKET_OPT_IP_RECVIF 21 +#define SOCKET_OPT_IP_RECVOPTS 23 #define SOCKET_OPT_IP_RECVTOS 25 #define SOCKET_OPT_IP_RECVTTL 26 #define SOCKET_OPT_IP_ROUTER_ALERT 28 @@ -1127,6 +1128,16 @@ static ERL_NIF_TERM nsetopt_lvl_ip_nodefrag(ErlNifEnv* env, SocketDescriptor* descP, ERL_NIF_TERM eVal); #endif +#if defined(IP_RECVIF) +static ERL_NIF_TERM nsetopt_lvl_ip_recvif(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); +#endif +#if defined(IP_RECVOPTS) +static ERL_NIF_TERM nsetopt_lvl_ip_recvopts(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); +#endif #if defined(IP_RECVTOS) static ERL_NIF_TERM nsetopt_lvl_ip_recvtos(ErlNifEnv* env, SocketDescriptor* descP, @@ -1450,6 +1461,14 @@ static ERL_NIF_TERM ngetopt_lvl_ip_multicast_ttl(ErlNifEnv* env, static ERL_NIF_TERM ngetopt_lvl_ip_nodefrag(ErlNifEnv* env, SocketDescriptor* descP); #endif +#if defined(IP_RECVIF) +static ERL_NIF_TERM ngetopt_lvl_ip_recvif(ErlNifEnv* env, + SocketDescriptor* descP); +#endif +#if defined(IP_RECVOPTS) +static ERL_NIF_TERM ngetopt_lvl_ip_recvopts(ErlNifEnv* env, + SocketDescriptor* descP); +#endif #if defined(IP_RECVTOS) static ERL_NIF_TERM ngetopt_lvl_ip_recvtos(ErlNifEnv* env, SocketDescriptor* descP); @@ -4802,6 +4821,12 @@ ERL_NIF_TERM nsetopt_lvl_ip(ErlNifEnv* env, break; #endif +#if defined(IP_RECVOPTS) + case SOCKET_OPT_IP_RECVOPTS: + result = nsetopt_lvl_ip_recvopts(env, descP, eVal); + break; +#endif + #if defined(IP_RECVTOS) case SOCKET_OPT_IP_RECVTOS: result = nsetopt_lvl_ip_recvtos(env, descP, eVal); @@ -5148,7 +5173,7 @@ ERL_NIF_TERM nsetopt_lvl_ip_nodefrag(ErlNifEnv* env, #endif -/* nsetopt_lvl_ip_recvif - Level IP RECVIFxs option +/* nsetopt_lvl_ip_recvif - Level IP RECVIF option */ #if defined(IP_RECVIF) static @@ -5167,6 +5192,25 @@ ERL_NIF_TERM nsetopt_lvl_ip_recvif(ErlNifEnv* env, #endif +/* nsetopt_lvl_ip_recvopts - Level IP RECVOPTS option + */ +#if defined(IP_RECVOPTS) +static +ERL_NIF_TERM nsetopt_lvl_ip_recvopts(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) +{ +#if defined(SOL_IP) + int level = SOL_IP; +#else + int level = IPPROTO_IP; +#endif + + return nsetopt_bool_opt(env, descP, level, IP_RECVOPTS, eVal); +} +#endif + + /* nsetopt_lvl_ip_recvtos - Level IP RECVTOS option */ #if defined(IP_RECVTOS) @@ -6672,6 +6716,11 @@ ERL_NIF_TERM ngetopt_otp(ErlNifEnv* env, { ERL_NIF_TERM result; + SSDBG( descP, + ("SOCKET", "ngetopt_opt -> entry with" + "\r\n eOpt: %d" + "\r\n", eOpt) ); + switch (eOpt) { case SOCKET_OPT_OTP_DEBUG: result = ngetopt_otp_debug(env, descP); @@ -6686,6 +6735,11 @@ ERL_NIF_TERM ngetopt_otp(ErlNifEnv* env, break; } + SSDBG( descP, + ("SOCKET", "ngetopt_opt -> done when" + "\r\n result: %T" + "\r\n", result) ); + return result; } @@ -7409,6 +7463,11 @@ ERL_NIF_TERM ngetopt_lvl_ip(ErlNifEnv* env, { ERL_NIF_TERM result; + SSDBG( descP, + ("SOCKET", "ngetopt_lvl_ip -> entry with" + "\r\n eOpt: %d" + "\r\n", eOpt) ); + switch (eOpt) { #if defined(IP_FREEBIND) case SOCKET_OPT_IP_FREEBIND: @@ -7470,6 +7529,12 @@ ERL_NIF_TERM ngetopt_lvl_ip(ErlNifEnv* env, break; #endif +#if defined(IP_RECVOPTS) + case SOCKET_OPT_IP_RECVOPTS: + result = ngetopt_lvl_ip_recvopts(env, descP); + break; +#endif + #if defined(IP_RECVTOS) case SOCKET_OPT_IP_RECVTOS: result = ngetopt_lvl_ip_recvtos(env, descP); @@ -7501,10 +7566,16 @@ ERL_NIF_TERM ngetopt_lvl_ip(ErlNifEnv* env, #endif default: + SSDBG( descP, ("SOCKET", "ngetopt_lvl_ip -> unknown opt %d\r\n", eOpt) ); result = esock_make_error(env, esock_atom_einval); break; } + SSDBG( descP, + ("SOCKET", "ngetopt_lvl_ip -> done when" + "\r\n result: %T" + "\r\n", result) ); + return result; } @@ -7742,6 +7813,24 @@ ERL_NIF_TERM ngetopt_lvl_ip_recvif(ErlNifEnv* env, #endif +/* ngetopt_lvl_ip_recvopt - Level IP RECVOPTS option + */ +#if defined(IP_RECVOPTS) +static +ERL_NIF_TERM ngetopt_lvl_ip_recvopts(ErlNifEnv* env, + SocketDescriptor* descP) +{ +#if defined(SOL_IP) + int level = SOL_IP; +#else + int level = IPPROTO_IP; +#endif + + return ngetopt_bool_opt(env, descP, level, IP_RECVOPTS); +} +#endif + + /* ngetopt_lvl_ip_recvttl - Level IP RECVTTL option */ #if defined(IP_RECVTTL) diff --git a/erts/preloaded/ebin/socket.beam b/erts/preloaded/ebin/socket.beam index 3657212f81..54cc44db54 100644 Binary files a/erts/preloaded/ebin/socket.beam and b/erts/preloaded/ebin/socket.beam differ diff --git a/erts/preloaded/src/socket.erl b/erts/preloaded/src/socket.erl index 329223c443..49c7a3f56d 100644 --- a/erts/preloaded/src/socket.erl +++ b/erts/preloaded/src/socket.erl @@ -614,7 +614,7 @@ %% -define(SOCKET_OPT_IP_RECVERR, 20). -define(SOCKET_OPT_IP_RECVIF, 21). %% -define(SOCKET_OPT_IP_RECVDSTADDR, 22). -%% -define(SOCKET_OPT_IP_RECVOPTS, 23). +-define(SOCKET_OPT_IP_RECVOPTS, 23). %% -define(SOCKET_OPT_IP_RECVORIGDSTADDR, 24). -define(SOCKET_OPT_IP_RECVTOS, 25). -define(SOCKET_OPT_IP_RECVTTL, 26). @@ -2210,6 +2210,9 @@ enc_setopt_value(ip, nodefrag, V, _D, _T, _P) enc_setopt_value(ip, recvif, V, _D, _T, _P) when is_boolean(V) -> V; +enc_setopt_value(ip, recvopts, V, _D, _T, _P) + when is_boolean(V) -> + V; enc_setopt_value(ip, recvtos, V, _D, _T, _P) when is_boolean(V) -> V; @@ -2619,8 +2622,8 @@ enc_sockopt_key(ip = _L, recvif = _Opt, _Dir, _D, T, _P) ?SOCKET_OPT_IP_RECVIF; enc_sockopt_key(ip = L, recvdstaddr = Opt, _Dir, _D, _T, _P) -> not_supported({L, Opt}); -enc_sockopt_key(ip = L, recvopts = Opt, _Dir, _D, T, _P) when (T =/= stream) -> - not_supported({L, Opt}); +enc_sockopt_key(ip = _L, recvopts = _Opt, _Dir, _D, T, _P) when (T =/= stream) -> + ?SOCKET_OPT_IP_RECVOPTS; enc_sockopt_key(ip = L, recvorigdstaddr = Opt, _Dir, _D, _T, _P) -> not_supported({L, Opt}); enc_sockopt_key(ip, recvtos = _Opt, _Dir, _D, _T, _P) -> diff --git a/lib/kernel/test/socket_server.erl b/lib/kernel/test/socket_server.erl index 971ceb2093..aa577b6289 100644 --- a/lib/kernel/test/socket_server.erl +++ b/lib/kernel/test/socket_server.erl @@ -612,6 +612,8 @@ handler_init(Manager, ID, Peek, Sock) -> MLoop = GIP(multicast_loop), MTTL = GIP(multicast_ttl), NF = GIP(nodefrag), % raw only + RecvIF = GIP(recvif), % Only dgram and raw (and FreeBSD) + RecvOPTS = GIP(recvopts), % Not stream RecvTOS = GIP(recvtos), RecvTTL = GIP(recvttl), % not stream i("got continue when: " @@ -638,6 +640,8 @@ handler_init(Manager, ID, Peek, Sock) -> "~n (ip) Multicast Loop: ~s" "~n (ip) Multicast TTL: ~s" "~n (ip) Node Frag: ~s" + "~n (ip) Recv IF: ~s" + "~n (ip) Recv OPTS: ~s" "~n (ip) Recv TOS: ~s" "~n (ip) Recv TTL: ~s", [Domain, Type, Proto, @@ -645,8 +649,8 @@ handler_init(Manager, ID, Peek, Sock) -> RcvBuf, RcvLW, RcvTO, SndBuf, SndLW, SndTO, Linger, Timestamp, FreeBind, MTU, MTUDisc, MALL, MIF, MLoop, MTTL, - NF, RecvTOS, RecvTTL]), - + NF, RecvIF, RecvOPTS, RecvTOS, RecvTTL]), + handler_loop(#handler{peek = Peek, manager = Manager, type = Type, -- cgit v1.2.3 From ba4b0ff7bf355a4fec434792495945872bb3efc5 Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Mon, 23 Jul 2018 12:41:36 +0200 Subject: [socket-nif] Updated types for [recv|send]msg Updated and added types for the sendmsg and recvmsg functions. OTP-14831 --- erts/preloaded/ebin/socket.beam | Bin 56604 -> 56700 bytes erts/preloaded/src/socket.erl | 52 +++++++++++++++++++++------------------- 2 files changed, 28 insertions(+), 24 deletions(-) diff --git a/erts/preloaded/ebin/socket.beam b/erts/preloaded/ebin/socket.beam index 54cc44db54..b00d272f8b 100644 Binary files a/erts/preloaded/ebin/socket.beam and b/erts/preloaded/ebin/socket.beam differ diff --git a/erts/preloaded/src/socket.erl b/erts/preloaded/src/socket.erl index 49c7a3f56d..e9b9f44a2c 100644 --- a/erts/preloaded/src/socket.erl +++ b/erts/preloaded/src/socket.erl @@ -102,7 +102,10 @@ sctp_rtoinfo/0, - msg_hdr/0, + msghdr_flag/0, + msghdr_flags/0, + msghdr/0, + cmsghdr/0, uint8/0, uint16/0, @@ -485,26 +488,28 @@ -type shutdown_how() :: read | write | read_write. -%% This is just a place-holder --record(msg_hdr, - { - %% Optional address - %% On an unconnected socket this is used to specify the target - %% address for a datagram. - %% For a connected socket, this field should be specifiedset to []. - name :: list(), - - %% Scatter/gather array - iov :: [binary()], % iovec(), - - %% Ancillary (control) data - ctrl :: binary(), - - %% Unused - flags = [] :: list() - }). --type msg_hdr() :: #msg_hdr{}. - +%% These are just place-holder(s) - used by the sendmsg/recvmsg functions... +-type msghdr_flag() :: eor | trunc | ctrunc | oob | errqueue. +-type msghdr_flags() :: [msghdr_flag()]. +-type msghdr() :: #{ + %% *Optional* target address + %% *If* this field is specified for an unconnected + %% socket, then it will be used as destination for the + %% datagram. + target => sockaddr(), + + iov => [binary()], + + ctrl => cmsghdr(), + + %% Only valid with recvmsg + flags => msghdr_flags() + }. +-type cmsghdr() :: #{ + level => protocol(), + type => integer(), + data => binary() + }. -define(SOCKET_DOMAIN_LOCAL, 1). -define(SOCKET_DOMAIN_UNIX, ?SOCKET_DOMAIN_LOCAL). @@ -1721,11 +1726,10 @@ do_recvfrom(SockRef, BufSz, EFlags, Timeout) -> %% --------------------------------------------------------------------------- %% -%% -spec recvmsg(Socket, [out] MsgHdr, Flags) -> {ok, Data} | {error, Reason} when +%% -spec recvmsg(Socket, Flags) -> {ok, MsgHdr} | {error, Reason} when %% Socket :: socket(), -%% MsgHdr :: msg_hdr(), +%% MsgHdr :: msghdr(), %% Flags :: recv_flags(), -%% Data :: binary(), %% Reason :: term(). -- cgit v1.2.3 From 2f99a47953404a18965fe6fe434593ee851e8677 Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Mon, 23 Jul 2018 14:26:25 +0200 Subject: [socket-nif] Add support for socket (level ipv6) option multicast_hops Added support for the IPv6 socket option MULTICAST_HOPS. OTP-14831. --- erts/doc/src/socket_usage.xml | 7 ++++++ erts/emulator/nifs/common/socket_nif.c | 44 +++++++++++++++++++++++++++++++++ erts/preloaded/ebin/socket.beam | Bin 56700 -> 56884 bytes erts/preloaded/src/socket.erl | 12 ++++++--- lib/kernel/test/socket_server.erl | 36 +++++++++++++++------------ 5 files changed, 80 insertions(+), 19 deletions(-) diff --git a/erts/doc/src/socket_usage.xml b/erts/doc/src/socket_usage.xml index eb1cbf6984..4f18471eb3 100644 --- a/erts/doc/src/socket_usage.xml +++ b/erts/doc/src/socket_usage.xml @@ -446,6 +446,13 @@ yes none + + multicast_hops + default | uint8() + yes + yes + none + v6only boolean() diff --git a/erts/emulator/nifs/common/socket_nif.c b/erts/emulator/nifs/common/socket_nif.c index 7e4504cd2d..5ac190b3ec 100644 --- a/erts/emulator/nifs/common/socket_nif.c +++ b/erts/emulator/nifs/common/socket_nif.c @@ -540,6 +540,7 @@ typedef union { #define SOCKET_OPT_IPV6_HOPLIMIT 12 #define SOCKET_OPT_IPV6_MTU 17 #define SOCKET_OPT_IPV6_MTU_DISCOVER 18 +#define SOCKET_OPT_IPV6_MULTICAST_HOPS 19 #define SOCKET_OPT_IPV6_V6ONLY 33 #define SOCKET_OPT_TCP_CONGESTION 1 @@ -1216,6 +1217,11 @@ static ERL_NIF_TERM nsetopt_lvl_ipv6_mtu_discover(ErlNifEnv* env, SocketDescriptor* descP, ERL_NIF_TERM eVal); #endif +#if defined(IPV6_MULTICAST_HOPS) +static ERL_NIF_TERM nsetopt_lvl_ipv6_multicast_hops(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); +#endif #if defined(IPV6_V6ONLY) static ERL_NIF_TERM nsetopt_lvl_ipv6_v6only(ErlNifEnv* env, SocketDescriptor* descP, @@ -1505,6 +1511,10 @@ static ERL_NIF_TERM ngetopt_lvl_ipv6_mtu(ErlNifEnv* env, static ERL_NIF_TERM ngetopt_lvl_ipv6_mtu_discover(ErlNifEnv* env, SocketDescriptor* descP); #endif +#if defined(IPV6_MULTICAST_HOPS) +static ERL_NIF_TERM ngetopt_lvl_ipv6_multicast_hops(ErlNifEnv* env, + SocketDescriptor* descP); +#endif #if defined(IPV6_V6ONLY) static ERL_NIF_TERM ngetopt_lvl_ipv6_v6only(ErlNifEnv* env, SocketDescriptor* descP); @@ -5508,6 +5518,12 @@ ERL_NIF_TERM nsetopt_lvl_ipv6(ErlNifEnv* env, break; #endif +#if defined(IPV6_MULTICAST_HOPS) + case SOCKET_OPT_IPV6_MULTICAST_HOPS: + result = nsetopt_lvl_ipv6_multicast_hops(env, descP, eVal); + break; +#endif + #if defined(IPV6_V6ONLY) case SOCKET_OPT_IPV6_V6ONLY: result = nsetopt_lvl_ipv6_v6only(env, descP, eVal); @@ -5605,6 +5621,18 @@ ERL_NIF_TERM nsetopt_lvl_ipv6_mtu_discover(ErlNifEnv* env, #endif +#if defined(IPV6_MULTICAST_HOPS) +static +ERL_NIF_TERM nsetopt_lvl_ipv6_multicast_hops(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) +{ + return nsetopt_int_opt(env, descP, SOL_IPV6, IPV6_MULTICAST_HOPS, eVal); +} +#endif + + + #if defined(IPV6_V6ONLY) static ERL_NIF_TERM nsetopt_lvl_ipv6_v6only(ErlNifEnv* env, @@ -7950,6 +7978,12 @@ ERL_NIF_TERM ngetopt_lvl_ipv6(ErlNifEnv* env, break; #endif +#if defined(IPV6_MULTICAST_HOPS) + case SOCKET_OPT_IPV6_MULTICAST_HOPS: + result = ngetopt_lvl_ipv6_multicast_hops(env, descP); + break; +#endif + #if defined(IPV6_V6ONLY) case SOCKET_OPT_IPV6_V6ONLY: result = ngetopt_lvl_ipv6_v6only(env, descP); @@ -8019,6 +8053,16 @@ ERL_NIF_TERM ngetopt_lvl_ipv6_mtu_discover(ErlNifEnv* env, #endif +#if defined(IPV6_MULTICAST_HOPS) +static +ERL_NIF_TERM ngetopt_lvl_ipv6_multicast_hops(ErlNifEnv* env, + SocketDescriptor* descP) +{ + return ngetopt_int_opt(env, descP, SOL_IPV6, IPV6_MULTICAST_HOPS); +} +#endif + + #if defined(IPV6_V6ONLY) static ERL_NIF_TERM ngetopt_lvl_ipv6_v6only(ErlNifEnv* env, diff --git a/erts/preloaded/ebin/socket.beam b/erts/preloaded/ebin/socket.beam index b00d272f8b..0b16eb23e0 100644 Binary files a/erts/preloaded/ebin/socket.beam and b/erts/preloaded/ebin/socket.beam differ diff --git a/erts/preloaded/src/socket.erl b/erts/preloaded/src/socket.erl index e9b9f44a2c..fd273951bd 100644 --- a/erts/preloaded/src/socket.erl +++ b/erts/preloaded/src/socket.erl @@ -649,7 +649,7 @@ %% -define(SOCKET_OPT_IPV6_LEAVE_GROUP, 16). -define(SOCKET_OPT_IPV6_MTU, 17). -define(SOCKET_OPT_IPV6_MTU_DISCOVER, 18). -%% -define(SOCKET_OPT_IPV6_MULTICAST_HOPS, 19). +-define(SOCKET_OPT_IPV6_MULTICAST_HOPS, 19). %% -define(SOCKET_OPT_IPV6_MULTICAST_IF, 20). %% -define(SOCKET_OPT_IPV6_MULTICAST_LOOP, 21). %% -define(SOCKET_OPT_IPV6_PORTRANGE, 22). @@ -2268,6 +2268,12 @@ enc_setopt_value(ipv6, mtu_discover, V, _D, _T, _P) (V =:= probe) orelse is_integer(V) -> V; +enc_setopt_value(ipv6, multicast_hops, V, _D, _T, _P) + when (V =:= default) -> + -1; +enc_setopt_value(ipv6, multicast_hops, V, _D, _T, _P) + when is_integer(V) andalso (V >= 0) andalso (V =< 255) -> + V; enc_setopt_value(ipv6, v6only, V, _D, _T, _P) when is_boolean(V) -> V; enc_setopt_value(ipv6 = L, Opt, V, _D, _T, _P) -> @@ -2692,8 +2698,8 @@ enc_sockopt_key(ipv6 = _L, mtu = _Opt, _Dir, _D, _T, _P) -> ?SOCKET_OPT_IPV6_MTU; enc_sockopt_key(ipv6 = _L, mtu_discover = _Opt, _Dir, _D, _T, _P) -> ?SOCKET_OPT_IPV6_MTU_DISCOVER; -enc_sockopt_key(ipv6 = L, multicast_hops = Opt, _Dir, _D, _T, _P) -> - not_supported({L, Opt}); +enc_sockopt_key(ipv6 = _L, multicast_hops = _Opt, _Dir, _D, _T, _P) -> + ?SOCKET_OPT_IPV6_MULTICAST_HOPS; enc_sockopt_key(ipv6 = L, multicast_if = Opt, _Dir, _D, T, _P) when (T =:= dgram) orelse (T =:= raw) -> not_supported({L, Opt}); diff --git a/lib/kernel/test/socket_server.erl b/lib/kernel/test/socket_server.erl index aa577b6289..28e21c3b8b 100644 --- a/lib/kernel/test/socket_server.erl +++ b/lib/kernel/test/socket_server.erl @@ -587,8 +587,9 @@ handler_init(Manager, ID, Peek, Sock) -> f("error: ~p", [R]) end end, - GIP = fun(O) -> G(ip, O) end, - GSO = fun(O) -> G(socket, O) end, + GSO = fun(O) -> G(socket, O) end, + GIP4 = fun(O) -> G(ip, O) end, + GIP6 = fun(O) -> G(ipv6, O) end, {ok, Domain} = socket:getopt(Sock, socket, domain), {ok, Type} = socket:getopt(Sock, socket, type), {ok, Proto} = socket:getopt(Sock, socket, protocol), @@ -604,18 +605,19 @@ handler_init(Manager, ID, Peek, Sock) -> SndTO = GSO(sndtimeo), Linger = GSO(linger), Timestamp = GSO(timestamp), - FreeBind = GIP(freebind), - MTU = GIP(mtu), - MTUDisc = GIP(mtu_discover), - MALL = GIP(multicast_all), - MIF = GIP(multicast_if), - MLoop = GIP(multicast_loop), - MTTL = GIP(multicast_ttl), - NF = GIP(nodefrag), % raw only - RecvIF = GIP(recvif), % Only dgram and raw (and FreeBSD) - RecvOPTS = GIP(recvopts), % Not stream - RecvTOS = GIP(recvtos), - RecvTTL = GIP(recvttl), % not stream + FreeBind = GIP4(freebind), + MTU = GIP4(mtu), + MTUDisc = GIP4(mtu_discover), + MALL = GIP4(multicast_all), + MIF = GIP4(multicast_if), + MLoop = GIP4(multicast_loop), + MTTL = GIP4(multicast_ttl), + NF = GIP4(nodefrag), % raw only + RecvIF = GIP4(recvif), % Only dgram and raw (and FreeBSD) + RecvOPTS = GIP4(recvopts), % Not stream + RecvTOS = GIP4(recvtos), + RecvTTL = GIP4(recvttl), % not stream + MHops = GIP6(multicast_hops), i("got continue when: " "~n (socket) Domain: ~p" "~n (socket) Type: ~p" @@ -643,13 +645,15 @@ handler_init(Manager, ID, Peek, Sock) -> "~n (ip) Recv IF: ~s" "~n (ip) Recv OPTS: ~s" "~n (ip) Recv TOS: ~s" - "~n (ip) Recv TTL: ~s", + "~n (ip) Recv TTL: ~s" + "~n (ipv6) Multicast Hops: ~s", [Domain, Type, Proto, RA, RP, B2D, OOBI, RcvBuf, RcvLW, RcvTO, SndBuf, SndLW, SndTO, Linger, Timestamp, FreeBind, MTU, MTUDisc, MALL, MIF, MLoop, MTTL, - NF, RecvIF, RecvOPTS, RecvTOS, RecvTTL]), + NF, RecvIF, RecvOPTS, RecvTOS, RecvTTL, + MHops]), handler_loop(#handler{peek = Peek, manager = Manager, -- cgit v1.2.3 From c077834b1465a8285f0c18e1d164c812aaadac9c Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Mon, 23 Jul 2018 15:02:39 +0200 Subject: [socket-nif] Add support for socket (level ipv6) option multicast_if Added support for the IPv6 socket option MULTICAST_IF. OTP-14831. --- erts/doc/src/socket_usage.xml | 7 ++++++ erts/emulator/nifs/common/socket_nif.c | 44 +++++++++++++++++++++++++++++++++ erts/preloaded/ebin/socket.beam | Bin 56884 -> 56912 bytes erts/preloaded/src/socket.erl | 9 ++++--- lib/kernel/test/socket_server.erl | 10 +++++--- 5 files changed, 63 insertions(+), 7 deletions(-) diff --git a/erts/doc/src/socket_usage.xml b/erts/doc/src/socket_usage.xml index 4f18471eb3..f9e094b11a 100644 --- a/erts/doc/src/socket_usage.xml +++ b/erts/doc/src/socket_usage.xml @@ -453,6 +453,13 @@ yes none + + multicast_if + integer() + yes + yes + type = dgram | raw + v6only boolean() diff --git a/erts/emulator/nifs/common/socket_nif.c b/erts/emulator/nifs/common/socket_nif.c index 5ac190b3ec..632b3469fa 100644 --- a/erts/emulator/nifs/common/socket_nif.c +++ b/erts/emulator/nifs/common/socket_nif.c @@ -541,6 +541,7 @@ typedef union { #define SOCKET_OPT_IPV6_MTU 17 #define SOCKET_OPT_IPV6_MTU_DISCOVER 18 #define SOCKET_OPT_IPV6_MULTICAST_HOPS 19 +#define SOCKET_OPT_IPV6_MULTICAST_IF 20 #define SOCKET_OPT_IPV6_V6ONLY 33 #define SOCKET_OPT_TCP_CONGESTION 1 @@ -1222,6 +1223,11 @@ static ERL_NIF_TERM nsetopt_lvl_ipv6_multicast_hops(ErlNifEnv* env, SocketDescriptor* descP, ERL_NIF_TERM eVal); #endif +#if defined(IPV6_MULTICAST_IF) +static ERL_NIF_TERM nsetopt_lvl_ipv6_multicast_if(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); +#endif #if defined(IPV6_V6ONLY) static ERL_NIF_TERM nsetopt_lvl_ipv6_v6only(ErlNifEnv* env, SocketDescriptor* descP, @@ -1515,6 +1521,10 @@ static ERL_NIF_TERM ngetopt_lvl_ipv6_mtu_discover(ErlNifEnv* env, static ERL_NIF_TERM ngetopt_lvl_ipv6_multicast_hops(ErlNifEnv* env, SocketDescriptor* descP); #endif +#if defined(IPV6_MULTICAST_IF) +static ERL_NIF_TERM ngetopt_lvl_ipv6_multicast_if(ErlNifEnv* env, + SocketDescriptor* descP); +#endif #if defined(IPV6_V6ONLY) static ERL_NIF_TERM ngetopt_lvl_ipv6_v6only(ErlNifEnv* env, SocketDescriptor* descP); @@ -5524,6 +5534,12 @@ ERL_NIF_TERM nsetopt_lvl_ipv6(ErlNifEnv* env, break; #endif +#if defined(IPV6_MULTICAST_IF) + case SOCKET_OPT_IPV6_MULTICAST_IF: + result = nsetopt_lvl_ipv6_multicast_if(env, descP, eVal); + break; +#endif + #if defined(IPV6_V6ONLY) case SOCKET_OPT_IPV6_V6ONLY: result = nsetopt_lvl_ipv6_v6only(env, descP, eVal); @@ -5633,6 +5649,18 @@ ERL_NIF_TERM nsetopt_lvl_ipv6_multicast_hops(ErlNifEnv* env, +#if defined(IPV6_MULTICAST_IF) +static +ERL_NIF_TERM nsetopt_lvl_ipv6_multicast_if(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) +{ + return nsetopt_int_opt(env, descP, SOL_IPV6, IPV6_MULTICAST_IF, eVal); +} +#endif + + + #if defined(IPV6_V6ONLY) static ERL_NIF_TERM nsetopt_lvl_ipv6_v6only(ErlNifEnv* env, @@ -7984,6 +8012,12 @@ ERL_NIF_TERM ngetopt_lvl_ipv6(ErlNifEnv* env, break; #endif +#if defined(IPV6_MULTICAST_IF) + case SOCKET_OPT_IPV6_MULTICAST_IF: + result = ngetopt_lvl_ipv6_multicast_if(env, descP); + break; +#endif + #if defined(IPV6_V6ONLY) case SOCKET_OPT_IPV6_V6ONLY: result = ngetopt_lvl_ipv6_v6only(env, descP); @@ -8063,6 +8097,16 @@ ERL_NIF_TERM ngetopt_lvl_ipv6_multicast_hops(ErlNifEnv* env, #endif +#if defined(IPV6_MULTICAST_IF) +static +ERL_NIF_TERM ngetopt_lvl_ipv6_multicast_if(ErlNifEnv* env, + SocketDescriptor* descP) +{ + return ngetopt_int_opt(env, descP, SOL_IPV6, IPV6_MULTICAST_IF); +} +#endif + + #if defined(IPV6_V6ONLY) static ERL_NIF_TERM ngetopt_lvl_ipv6_v6only(ErlNifEnv* env, diff --git a/erts/preloaded/ebin/socket.beam b/erts/preloaded/ebin/socket.beam index 0b16eb23e0..f5652bff1e 100644 Binary files a/erts/preloaded/ebin/socket.beam and b/erts/preloaded/ebin/socket.beam differ diff --git a/erts/preloaded/src/socket.erl b/erts/preloaded/src/socket.erl index fd273951bd..02232ebd68 100644 --- a/erts/preloaded/src/socket.erl +++ b/erts/preloaded/src/socket.erl @@ -650,7 +650,7 @@ -define(SOCKET_OPT_IPV6_MTU, 17). -define(SOCKET_OPT_IPV6_MTU_DISCOVER, 18). -define(SOCKET_OPT_IPV6_MULTICAST_HOPS, 19). -%% -define(SOCKET_OPT_IPV6_MULTICAST_IF, 20). +-define(SOCKET_OPT_IPV6_MULTICAST_IF, 20). %% -define(SOCKET_OPT_IPV6_MULTICAST_LOOP, 21). %% -define(SOCKET_OPT_IPV6_PORTRANGE, 22). %% -define(SOCKET_OPT_IPV6_PKTINFO, 23). @@ -2274,6 +2274,9 @@ enc_setopt_value(ipv6, multicast_hops, V, _D, _T, _P) enc_setopt_value(ipv6, multicast_hops, V, _D, _T, _P) when is_integer(V) andalso (V >= 0) andalso (V =< 255) -> V; +enc_setopt_value(ipv6, multicast_if, V, _D, _T, _P) + when is_integer(V) -> + V; enc_setopt_value(ipv6, v6only, V, _D, _T, _P) when is_boolean(V) -> V; enc_setopt_value(ipv6 = L, Opt, V, _D, _T, _P) -> @@ -2700,9 +2703,9 @@ enc_sockopt_key(ipv6 = _L, mtu_discover = _Opt, _Dir, _D, _T, _P) -> ?SOCKET_OPT_IPV6_MTU_DISCOVER; enc_sockopt_key(ipv6 = _L, multicast_hops = _Opt, _Dir, _D, _T, _P) -> ?SOCKET_OPT_IPV6_MULTICAST_HOPS; -enc_sockopt_key(ipv6 = L, multicast_if = Opt, _Dir, _D, T, _P) +enc_sockopt_key(ipv6 = _L, multicast_if = _Opt, _Dir, _D, T, _P) when (T =:= dgram) orelse (T =:= raw) -> - not_supported({L, Opt}); + ?SOCKET_OPT_IPV6_MULTICAST_IF; enc_sockopt_key(ipv6 = L, multicast_loop = Opt, _Dir, _D, _T, _P) -> not_supported({L, Opt}); enc_sockopt_key(ipv6 = L, portrange = Opt, _Dir, _D, _T, _P) -> diff --git a/lib/kernel/test/socket_server.erl b/lib/kernel/test/socket_server.erl index 28e21c3b8b..cf13620997 100644 --- a/lib/kernel/test/socket_server.erl +++ b/lib/kernel/test/socket_server.erl @@ -609,7 +609,7 @@ handler_init(Manager, ID, Peek, Sock) -> MTU = GIP4(mtu), MTUDisc = GIP4(mtu_discover), MALL = GIP4(multicast_all), - MIF = GIP4(multicast_if), + MIF4 = GIP4(multicast_if), MLoop = GIP4(multicast_loop), MTTL = GIP4(multicast_ttl), NF = GIP4(nodefrag), % raw only @@ -618,6 +618,7 @@ handler_init(Manager, ID, Peek, Sock) -> RecvTOS = GIP4(recvtos), RecvTTL = GIP4(recvttl), % not stream MHops = GIP6(multicast_hops), + MIF6 = GIP6(multicast_if), % Only dgram and raw i("got continue when: " "~n (socket) Domain: ~p" "~n (socket) Type: ~p" @@ -646,14 +647,15 @@ handler_init(Manager, ID, Peek, Sock) -> "~n (ip) Recv OPTS: ~s" "~n (ip) Recv TOS: ~s" "~n (ip) Recv TTL: ~s" - "~n (ipv6) Multicast Hops: ~s", + "~n (ipv6) Multicast Hops: ~s" + "~n (ipv6) Multicast IF: ~s", [Domain, Type, Proto, RA, RP, B2D, OOBI, RcvBuf, RcvLW, RcvTO, SndBuf, SndLW, SndTO, Linger, Timestamp, - FreeBind, MTU, MTUDisc, MALL, MIF, MLoop, MTTL, + FreeBind, MTU, MTUDisc, MALL, MIF4, MLoop, MTTL, NF, RecvIF, RecvOPTS, RecvTOS, RecvTTL, - MHops]), + MHops, MIF6]), handler_loop(#handler{peek = Peek, manager = Manager, -- cgit v1.2.3 From 4d14b84183c3c17f0ec03bf3631f1fd8575f07b9 Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Mon, 23 Jul 2018 17:36:16 +0200 Subject: [socket-nif] Add support for socket (level ipv6) option multicast_loop Added support for the IPv6 socket option MULTICAST_LOOP. OTP-14831. --- erts/doc/src/socket_usage.xml | 7 ++++++ erts/emulator/nifs/common/socket_nif.c | 44 +++++++++++++++++++++++++++++++++ erts/preloaded/ebin/socket.beam | Bin 56912 -> 56980 bytes erts/preloaded/src/socket.erl | 9 ++++--- lib/kernel/test/socket_server.erl | 12 +++++---- 5 files changed, 64 insertions(+), 8 deletions(-) diff --git a/erts/doc/src/socket_usage.xml b/erts/doc/src/socket_usage.xml index f9e094b11a..af21806243 100644 --- a/erts/doc/src/socket_usage.xml +++ b/erts/doc/src/socket_usage.xml @@ -460,6 +460,13 @@ yes type = dgram | raw + + multicast_loop + boolean() + yes + yes + none + v6only boolean() diff --git a/erts/emulator/nifs/common/socket_nif.c b/erts/emulator/nifs/common/socket_nif.c index 632b3469fa..218ed66f52 100644 --- a/erts/emulator/nifs/common/socket_nif.c +++ b/erts/emulator/nifs/common/socket_nif.c @@ -542,6 +542,7 @@ typedef union { #define SOCKET_OPT_IPV6_MTU_DISCOVER 18 #define SOCKET_OPT_IPV6_MULTICAST_HOPS 19 #define SOCKET_OPT_IPV6_MULTICAST_IF 20 +#define SOCKET_OPT_IPV6_MULTICAST_LOOP 21 #define SOCKET_OPT_IPV6_V6ONLY 33 #define SOCKET_OPT_TCP_CONGESTION 1 @@ -1228,6 +1229,11 @@ static ERL_NIF_TERM nsetopt_lvl_ipv6_multicast_if(ErlNifEnv* env, SocketDescriptor* descP, ERL_NIF_TERM eVal); #endif +#if defined(IPV6_MULTICAST_LOOP) +static ERL_NIF_TERM nsetopt_lvl_ipv6_multicast_loop(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); +#endif #if defined(IPV6_V6ONLY) static ERL_NIF_TERM nsetopt_lvl_ipv6_v6only(ErlNifEnv* env, SocketDescriptor* descP, @@ -1525,6 +1531,10 @@ static ERL_NIF_TERM ngetopt_lvl_ipv6_multicast_hops(ErlNifEnv* env, static ERL_NIF_TERM ngetopt_lvl_ipv6_multicast_if(ErlNifEnv* env, SocketDescriptor* descP); #endif +#if defined(IPV6_MULTICAST_LOOP) +static ERL_NIF_TERM ngetopt_lvl_ipv6_multicast_loop(ErlNifEnv* env, + SocketDescriptor* descP); +#endif #if defined(IPV6_V6ONLY) static ERL_NIF_TERM ngetopt_lvl_ipv6_v6only(ErlNifEnv* env, SocketDescriptor* descP); @@ -5540,6 +5550,12 @@ ERL_NIF_TERM nsetopt_lvl_ipv6(ErlNifEnv* env, break; #endif +#if defined(IPV6_MULTICAST_LOOP) + case SOCKET_OPT_IPV6_MULTICAST_LOOP: + result = nsetopt_lvl_ipv6_multicast_loop(env, descP, eVal); + break; +#endif + #if defined(IPV6_V6ONLY) case SOCKET_OPT_IPV6_V6ONLY: result = nsetopt_lvl_ipv6_v6only(env, descP, eVal); @@ -5661,6 +5677,18 @@ ERL_NIF_TERM nsetopt_lvl_ipv6_multicast_if(ErlNifEnv* env, +#if defined(IPV6_MULTICAST_LOOP) +static +ERL_NIF_TERM nsetopt_lvl_ipv6_multicast_loop(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) +{ + return nsetopt_bool_opt(env, descP, SOL_IPV6, IPV6_MULTICAST_LOOP, eVal); +} +#endif + + + #if defined(IPV6_V6ONLY) static ERL_NIF_TERM nsetopt_lvl_ipv6_v6only(ErlNifEnv* env, @@ -8018,6 +8046,12 @@ ERL_NIF_TERM ngetopt_lvl_ipv6(ErlNifEnv* env, break; #endif +#if defined(IPV6_MULTICAST_LOOP) + case SOCKET_OPT_IPV6_MULTICAST_LOOP: + result = ngetopt_lvl_ipv6_multicast_loop(env, descP); + break; +#endif + #if defined(IPV6_V6ONLY) case SOCKET_OPT_IPV6_V6ONLY: result = ngetopt_lvl_ipv6_v6only(env, descP); @@ -8107,6 +8141,16 @@ ERL_NIF_TERM ngetopt_lvl_ipv6_multicast_if(ErlNifEnv* env, #endif +#if defined(IPV6_MULTICAST_LOOP) +static +ERL_NIF_TERM ngetopt_lvl_ipv6_multicast_loop(ErlNifEnv* env, + SocketDescriptor* descP) +{ + return ngetopt_bool_opt(env, descP, SOL_IPV6, IPV6_MULTICAST_LOOP); +} +#endif + + #if defined(IPV6_V6ONLY) static ERL_NIF_TERM ngetopt_lvl_ipv6_v6only(ErlNifEnv* env, diff --git a/erts/preloaded/ebin/socket.beam b/erts/preloaded/ebin/socket.beam index f5652bff1e..5decdd72c2 100644 Binary files a/erts/preloaded/ebin/socket.beam and b/erts/preloaded/ebin/socket.beam differ diff --git a/erts/preloaded/src/socket.erl b/erts/preloaded/src/socket.erl index 02232ebd68..7f160207b1 100644 --- a/erts/preloaded/src/socket.erl +++ b/erts/preloaded/src/socket.erl @@ -651,7 +651,7 @@ -define(SOCKET_OPT_IPV6_MTU_DISCOVER, 18). -define(SOCKET_OPT_IPV6_MULTICAST_HOPS, 19). -define(SOCKET_OPT_IPV6_MULTICAST_IF, 20). -%% -define(SOCKET_OPT_IPV6_MULTICAST_LOOP, 21). +-define(SOCKET_OPT_IPV6_MULTICAST_LOOP, 21). %% -define(SOCKET_OPT_IPV6_PORTRANGE, 22). %% -define(SOCKET_OPT_IPV6_PKTINFO, 23). %% -define(SOCKET_OPT_IPV6_PKTOPTIONS, 24). @@ -2277,6 +2277,9 @@ enc_setopt_value(ipv6, multicast_hops, V, _D, _T, _P) enc_setopt_value(ipv6, multicast_if, V, _D, _T, _P) when is_integer(V) -> V; +enc_setopt_value(ipv6, multicast_loop, V, _D, _T, _P) + when is_boolean(V) -> + V; enc_setopt_value(ipv6, v6only, V, _D, _T, _P) when is_boolean(V) -> V; enc_setopt_value(ipv6 = L, Opt, V, _D, _T, _P) -> @@ -2706,8 +2709,8 @@ enc_sockopt_key(ipv6 = _L, multicast_hops = _Opt, _Dir, _D, _T, _P) -> enc_sockopt_key(ipv6 = _L, multicast_if = _Opt, _Dir, _D, T, _P) when (T =:= dgram) orelse (T =:= raw) -> ?SOCKET_OPT_IPV6_MULTICAST_IF; -enc_sockopt_key(ipv6 = L, multicast_loop = Opt, _Dir, _D, _T, _P) -> - not_supported({L, Opt}); +enc_sockopt_key(ipv6 = _L, multicast_loop = _Opt, _Dir, _D, _T, _P) -> + ?SOCKET_OPT_IPV6_MULTICAST_LOOP; enc_sockopt_key(ipv6 = L, portrange = Opt, _Dir, _D, _T, _P) -> not_supported({L, Opt}); enc_sockopt_key(ipv6 = L, pktinfo = Opt, _Dir, _D, _T, _P) -> diff --git a/lib/kernel/test/socket_server.erl b/lib/kernel/test/socket_server.erl index cf13620997..f3f397aa20 100644 --- a/lib/kernel/test/socket_server.erl +++ b/lib/kernel/test/socket_server.erl @@ -154,7 +154,7 @@ do_manager_init(Domain, dgram = Type, Proto, Peek) -> addr => Addr}, i("try bind to: " "~n ~p", [Addr]), - case socket:bind(Sock, SA) of + case socket:bind(Sock, any) of {ok, _P} -> ok; {error, BReason} -> @@ -610,7 +610,7 @@ handler_init(Manager, ID, Peek, Sock) -> MTUDisc = GIP4(mtu_discover), MALL = GIP4(multicast_all), MIF4 = GIP4(multicast_if), - MLoop = GIP4(multicast_loop), + MLoop4 = GIP4(multicast_loop), MTTL = GIP4(multicast_ttl), NF = GIP4(nodefrag), % raw only RecvIF = GIP4(recvif), % Only dgram and raw (and FreeBSD) @@ -619,6 +619,7 @@ handler_init(Manager, ID, Peek, Sock) -> RecvTTL = GIP4(recvttl), % not stream MHops = GIP6(multicast_hops), MIF6 = GIP6(multicast_if), % Only dgram and raw + MLoop6 = GIP6(multicast_loop), i("got continue when: " "~n (socket) Domain: ~p" "~n (socket) Type: ~p" @@ -648,14 +649,15 @@ handler_init(Manager, ID, Peek, Sock) -> "~n (ip) Recv TOS: ~s" "~n (ip) Recv TTL: ~s" "~n (ipv6) Multicast Hops: ~s" - "~n (ipv6) Multicast IF: ~s", + "~n (ipv6) Multicast IF: ~s" + "~n (ipv6) Multicast Loop: ~s", [Domain, Type, Proto, RA, RP, B2D, OOBI, RcvBuf, RcvLW, RcvTO, SndBuf, SndLW, SndTO, Linger, Timestamp, - FreeBind, MTU, MTUDisc, MALL, MIF4, MLoop, MTTL, + FreeBind, MTU, MTUDisc, MALL, MIF4, MLoop4, MTTL, NF, RecvIF, RecvOPTS, RecvTOS, RecvTTL, - MHops, MIF6]), + MHops, MIF6, MLoop6]), handler_loop(#handler{peek = Peek, manager = Manager, -- cgit v1.2.3 From de82310c32063b8556add3fe7cf62b26eef27841 Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Mon, 23 Jul 2018 18:26:42 +0200 Subject: [socket-nif] Add support for socket (level ipv6) option recvpktinfo Added support for the IPv6 socket option RECVPKTINFO. This option is called PKTINFO on FreeBSD, so that value will also be accepted. OTP-14831. --- erts/doc/src/socket_usage.xml | 7 +++++ erts/emulator/nifs/common/socket_nif.c | 56 ++++++++++++++++++++++++++++++++- erts/preloaded/ebin/socket.beam | Bin 56980 -> 57144 bytes erts/preloaded/src/socket.erl | 36 +++++++++++---------- lib/kernel/test/socket_server.erl | 10 ++++-- 5 files changed, 88 insertions(+), 21 deletions(-) diff --git a/erts/doc/src/socket_usage.xml b/erts/doc/src/socket_usage.xml index af21806243..78662e7d0d 100644 --- a/erts/doc/src/socket_usage.xml +++ b/erts/doc/src/socket_usage.xml @@ -467,6 +467,13 @@ yes none + + recvpktinfo | pktinfo + boolean() + yes + yes + type = dgram | raw + v6only boolean() diff --git a/erts/emulator/nifs/common/socket_nif.c b/erts/emulator/nifs/common/socket_nif.c index 218ed66f52..35997c359d 100644 --- a/erts/emulator/nifs/common/socket_nif.c +++ b/erts/emulator/nifs/common/socket_nif.c @@ -543,7 +543,8 @@ typedef union { #define SOCKET_OPT_IPV6_MULTICAST_HOPS 19 #define SOCKET_OPT_IPV6_MULTICAST_IF 20 #define SOCKET_OPT_IPV6_MULTICAST_LOOP 21 -#define SOCKET_OPT_IPV6_V6ONLY 33 +#define SOCKET_OPT_IPV6_RECVPKTINFO 25 // PKTINFO on FreeBSD +#define SOCKET_OPT_IPV6_V6ONLY 32 #define SOCKET_OPT_TCP_CONGESTION 1 #define SOCKET_OPT_TCP_CORK 2 @@ -1234,6 +1235,11 @@ static ERL_NIF_TERM nsetopt_lvl_ipv6_multicast_loop(ErlNifEnv* env, SocketDescriptor* descP, ERL_NIF_TERM eVal); #endif +#if defined(IPV6_RECVPKTINFO) || defined(IPV6_PKTINFO) +static ERL_NIF_TERM nsetopt_lvl_ipv6_recvpktinfo(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); +#endif #if defined(IPV6_V6ONLY) static ERL_NIF_TERM nsetopt_lvl_ipv6_v6only(ErlNifEnv* env, SocketDescriptor* descP, @@ -1535,6 +1541,10 @@ static ERL_NIF_TERM ngetopt_lvl_ipv6_multicast_if(ErlNifEnv* env, static ERL_NIF_TERM ngetopt_lvl_ipv6_multicast_loop(ErlNifEnv* env, SocketDescriptor* descP); #endif +#if defined(IPV6_RECVPKTINFO) || defined(IPV6_PKTINFO) +static ERL_NIF_TERM ngetopt_lvl_ipv6_recvpktinfo(ErlNifEnv* env, + SocketDescriptor* descP); +#endif #if defined(IPV6_V6ONLY) static ERL_NIF_TERM ngetopt_lvl_ipv6_v6only(ErlNifEnv* env, SocketDescriptor* descP); @@ -5556,6 +5566,12 @@ ERL_NIF_TERM nsetopt_lvl_ipv6(ErlNifEnv* env, break; #endif +#if defined(IPV6_RECVPKTINFO) || defined(IPV6_PKTINFO) + case SOCKET_OPT_IPV6_RECVPKTINFO: + result = nsetopt_lvl_ipv6_recvpktinfo(env, descP, eVal); + break; +#endif + #if defined(IPV6_V6ONLY) case SOCKET_OPT_IPV6_V6ONLY: result = nsetopt_lvl_ipv6_v6only(env, descP, eVal); @@ -5688,6 +5704,22 @@ ERL_NIF_TERM nsetopt_lvl_ipv6_multicast_loop(ErlNifEnv* env, #endif +#if defined(IPV6_RECVPKTINFO) || defined(IPV6_PKTINFO) +static +ERL_NIF_TERM nsetopt_lvl_ipv6_recvpktinfo(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) +{ +#if defined(IPV6_RECVPKTINFO) + int opt = IPV6_RECVPKTINFO; +#else + int opt = IPV6_PKTINFO; +#endif + + return nsetopt_bool_opt(env, descP, SOL_IPV6, opt, eVal); +} +#endif + #if defined(IPV6_V6ONLY) static @@ -8052,6 +8084,12 @@ ERL_NIF_TERM ngetopt_lvl_ipv6(ErlNifEnv* env, break; #endif +#if defined(IPV6_RECVPKTINFO) || defined(IPV6_PKTINFO) + case SOCKET_OPT_IPV6_RECVPKTINFO: + result = ngetopt_lvl_ipv6_recvpktinfo(env, descP); + break; +#endif + #if defined(IPV6_V6ONLY) case SOCKET_OPT_IPV6_V6ONLY: result = ngetopt_lvl_ipv6_v6only(env, descP); @@ -8151,6 +8189,22 @@ ERL_NIF_TERM ngetopt_lvl_ipv6_multicast_loop(ErlNifEnv* env, #endif +#if defined(IPV6_RECVPKTINFO) || defined(IPV6_PKTINFO) +static +ERL_NIF_TERM ngetopt_lvl_ipv6_recvpktinfo(ErlNifEnv* env, + SocketDescriptor* descP) +{ +#if defined(IPV6_RECVPKTINFO) + int opt = IPV6_RECVPKTINFO; +#else + int opt = IPV6_PKTINFO; +#endif + + return ngetopt_bool_opt(env, descP, SOL_IPV6, opt); +} +#endif + + #if defined(IPV6_V6ONLY) static ERL_NIF_TERM ngetopt_lvl_ipv6_v6only(ErlNifEnv* env, diff --git a/erts/preloaded/ebin/socket.beam b/erts/preloaded/ebin/socket.beam index 5decdd72c2..15bc762ae9 100644 Binary files a/erts/preloaded/ebin/socket.beam and b/erts/preloaded/ebin/socket.beam differ diff --git a/erts/preloaded/src/socket.erl b/erts/preloaded/src/socket.erl index 7f160207b1..7e9b61ba8f 100644 --- a/erts/preloaded/src/socket.erl +++ b/erts/preloaded/src/socket.erl @@ -376,10 +376,9 @@ multicast_if | multicast_loop | portrange | - pktinfo | pktoptions | recverr | - recvpktinfo | + recvpktinfo | pktinfo | recvtclass | router_alert | rthdr | @@ -653,17 +652,16 @@ -define(SOCKET_OPT_IPV6_MULTICAST_IF, 20). -define(SOCKET_OPT_IPV6_MULTICAST_LOOP, 21). %% -define(SOCKET_OPT_IPV6_PORTRANGE, 22). -%% -define(SOCKET_OPT_IPV6_PKTINFO, 23). -%% -define(SOCKET_OPT_IPV6_PKTOPTIONS, 24). -%% -define(SOCKET_OPT_IPV6_RECVERR, 25). -%% -define(SOCKET_OPT_IPV6_RECVPKTINFO, 26). -%% -define(SOCKET_OPT_IPV6_RECVTCLASS, 27). -%% -define(SOCKET_OPT_IPV6_ROUTER_ALERT, 28). -%% -define(SOCKET_OPT_IPV6_RTHDR, 29). -%% -define(SOCKET_OPT_IPV6_TCLASS, 30). -%% -define(SOCKET_OPT_IPV6_UNICAST_HOPS, 31). -%% -define(SOCKET_OPT_IPV6_USE_MIN_MTU, 32). --define(SOCKET_OPT_IPV6_V6ONLY, 33). +%% -define(SOCKET_OPT_IPV6_PKTOPTIONS, 23). +%% -define(SOCKET_OPT_IPV6_RECVERR, 24). +-define(SOCKET_OPT_IPV6_RECVPKTINFO, 25). % On FreeBSD: PKTINFO +%% -define(SOCKET_OPT_IPV6_RECVTCLASS, 26). +%% -define(SOCKET_OPT_IPV6_ROUTER_ALERT, 27). +%% -define(SOCKET_OPT_IPV6_RTHDR, 28). +%% -define(SOCKET_OPT_IPV6_TCLASS, 29). +%% -define(SOCKET_OPT_IPV6_UNICAST_HOPS, 30). +%% -define(SOCKET_OPT_IPV6_USE_MIN_MTU, 31). +-define(SOCKET_OPT_IPV6_V6ONLY, 32). -define(SOCKET_OPT_TCP_CONGESTION, 1). -define(SOCKET_OPT_TCP_CORK, 2). @@ -2280,6 +2278,10 @@ enc_setopt_value(ipv6, multicast_if, V, _D, _T, _P) enc_setopt_value(ipv6, multicast_loop, V, _D, _T, _P) when is_boolean(V) -> V; +enc_setopt_value(ipv6, Opt, V, _D, _T, _P) + when ((Opt =:= recvpktinfo) orelse (Opt =:= pktinfo)) andalso + is_boolean(V) -> + V; enc_setopt_value(ipv6, v6only, V, _D, _T, _P) when is_boolean(V) -> V; enc_setopt_value(ipv6 = L, Opt, V, _D, _T, _P) -> @@ -2713,14 +2715,14 @@ enc_sockopt_key(ipv6 = _L, multicast_loop = _Opt, _Dir, _D, _T, _P) -> ?SOCKET_OPT_IPV6_MULTICAST_LOOP; enc_sockopt_key(ipv6 = L, portrange = Opt, _Dir, _D, _T, _P) -> not_supported({L, Opt}); -enc_sockopt_key(ipv6 = L, pktinfo = Opt, _Dir, _D, _T, _P) -> - not_supported({L, Opt}); enc_sockopt_key(ipv6 = L, pktoptions = Opt, _Dir, _D, _T, _P) -> not_supported({L, Opt}); enc_sockopt_key(ipv6 = L, recverr = Opt, _Dir, _D, _T, _P) -> not_supported({L, Opt}); -enc_sockopt_key(ipv6 = L, recvpktinfo = Opt, _Dir, _D, _T, _P) -> - not_supported({L, Opt}); +enc_sockopt_key(ipv6 = _L, Opt, _Dir, _D, T, _P) + when ((Opt =:= recvpktinfo) orelse (Opt =:= pktinfo)) andalso + ((T =:= dgram) orelse (T =:= raw)) -> + ?SOCKET_OPT_IPV6_RECVPKTINFO; enc_sockopt_key(ipv6 = L, recvtclass = Opt, _Dir, _D, _T, _P) -> not_supported({L, Opt}); enc_sockopt_key(ipv6 = L, router_alert = Opt, _Dir, _D, _T, _P) -> diff --git a/lib/kernel/test/socket_server.erl b/lib/kernel/test/socket_server.erl index f3f397aa20..ba74964ed1 100644 --- a/lib/kernel/test/socket_server.erl +++ b/lib/kernel/test/socket_server.erl @@ -154,7 +154,7 @@ do_manager_init(Domain, dgram = Type, Proto, Peek) -> addr => Addr}, i("try bind to: " "~n ~p", [Addr]), - case socket:bind(Sock, any) of + case socket:bind(Sock, SA) of {ok, _P} -> ok; {error, BReason} -> @@ -620,6 +620,7 @@ handler_init(Manager, ID, Peek, Sock) -> MHops = GIP6(multicast_hops), MIF6 = GIP6(multicast_if), % Only dgram and raw MLoop6 = GIP6(multicast_loop), + RecvPktInfo = GIP6(recvpktinfo), i("got continue when: " "~n (socket) Domain: ~p" "~n (socket) Type: ~p" @@ -650,14 +651,16 @@ handler_init(Manager, ID, Peek, Sock) -> "~n (ip) Recv TTL: ~s" "~n (ipv6) Multicast Hops: ~s" "~n (ipv6) Multicast IF: ~s" - "~n (ipv6) Multicast Loop: ~s", + "~n (ipv6) Multicast Loop: ~s" + "~n (ipv6) Recv Pkt Info: ~s", [Domain, Type, Proto, RA, RP, B2D, OOBI, RcvBuf, RcvLW, RcvTO, SndBuf, SndLW, SndTO, Linger, Timestamp, FreeBind, MTU, MTUDisc, MALL, MIF4, MLoop4, MTTL, NF, RecvIF, RecvOPTS, RecvTOS, RecvTTL, - MHops, MIF6, MLoop6]), + MHops, MIF6, MLoop6, + RecvPktInfo]), handler_loop(#handler{peek = Peek, manager = Manager, @@ -665,6 +668,7 @@ handler_init(Manager, ID, Peek, Sock) -> socket = Sock}) end. + handler_loop(H) -> i("try read message"), case recv(H) of -- cgit v1.2.3 From d7ca5ba9d8a7f094037878acfecb52b0bfbacc2e Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Tue, 24 Jul 2018 10:25:25 +0200 Subject: [socket-nif] Add support for socket (level ipv6) option rthdr Added support for the IPv6 socket option RTHDR. On FreeBSD this option requires superuser privileges to update. There is no mention of this on linux, but its still not possible to update (einval), so I assume that its the same there. OTP-14831. --- erts/doc/src/socket_usage.xml | 7 ++++ erts/emulator/nifs/common/socket_nif.c | 71 +++++++++++++++++++++++++++++++-- erts/preloaded/ebin/socket.beam | Bin 57144 -> 57224 bytes erts/preloaded/src/socket.erl | 11 +++-- lib/kernel/test/socket_server.erl | 27 +++++++++---- 5 files changed, 102 insertions(+), 14 deletions(-) diff --git a/erts/doc/src/socket_usage.xml b/erts/doc/src/socket_usage.xml index 78662e7d0d..b3ee38dbcb 100644 --- a/erts/doc/src/socket_usage.xml +++ b/erts/doc/src/socket_usage.xml @@ -474,6 +474,13 @@ yes type = dgram | raw + + rthdr + boolean() + yes + yes + type = dgram | raw, requires superuser privileges to update + v6only boolean() diff --git a/erts/emulator/nifs/common/socket_nif.c b/erts/emulator/nifs/common/socket_nif.c index 35997c359d..f4ac43a9cd 100644 --- a/erts/emulator/nifs/common/socket_nif.c +++ b/erts/emulator/nifs/common/socket_nif.c @@ -544,6 +544,7 @@ typedef union { #define SOCKET_OPT_IPV6_MULTICAST_IF 20 #define SOCKET_OPT_IPV6_MULTICAST_LOOP 21 #define SOCKET_OPT_IPV6_RECVPKTINFO 25 // PKTINFO on FreeBSD +#define SOCKET_OPT_IPV6_RTHDR 28 #define SOCKET_OPT_IPV6_V6ONLY 32 #define SOCKET_OPT_TCP_CONGESTION 1 @@ -1240,6 +1241,11 @@ static ERL_NIF_TERM nsetopt_lvl_ipv6_recvpktinfo(ErlNifEnv* env, SocketDescriptor* descP, ERL_NIF_TERM eVal); #endif +#if defined(IPV6_RTHDR) +static ERL_NIF_TERM nsetopt_lvl_ipv6_rthdr(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); +#endif #if defined(IPV6_V6ONLY) static ERL_NIF_TERM nsetopt_lvl_ipv6_v6only(ErlNifEnv* env, SocketDescriptor* descP, @@ -1545,6 +1551,10 @@ static ERL_NIF_TERM ngetopt_lvl_ipv6_multicast_loop(ErlNifEnv* env, static ERL_NIF_TERM ngetopt_lvl_ipv6_recvpktinfo(ErlNifEnv* env, SocketDescriptor* descP); #endif +#if defined(IPV6_RTHDR) +static ERL_NIF_TERM ngetopt_lvl_ipv6_rthdr(ErlNifEnv* env, + SocketDescriptor* descP); +#endif #if defined(IPV6_V6ONLY) static ERL_NIF_TERM ngetopt_lvl_ipv6_v6only(ErlNifEnv* env, SocketDescriptor* descP); @@ -4323,9 +4333,10 @@ ERL_NIF_TERM nsetopt_native(ErlNifEnv* env, SSDBG( descP, ("SOCKET", "nsetopt_native -> entry with" - "\r\n opt: %d" - "\r\n eVal: %T" - "\r\n", opt, eVal) ); + "\r\n level: %d" + "\r\n opt: %d" + "\r\n eVal: %T" + "\r\n", level, opt, eVal) ); if (GET_BIN(env, eVal, &val)) { int res = socket_setopt(descP->sock, level, opt, @@ -4338,6 +4349,11 @@ ERL_NIF_TERM nsetopt_native(ErlNifEnv* env, result = esock_make_error(env, esock_atom_einval); } + SSDBG( descP, + ("SOCKET", "nsetopt_native -> done when" + "\r\n result: %T" + "\r\n", result) ); + return result; } @@ -4393,10 +4409,17 @@ ERL_NIF_TERM nsetopt_level(ErlNifEnv* env, #endif default: + SSDBG( descP, + ("SOCKET", "nsetopt_level -> unknown level (%d)\r\n", level) ); result = esock_make_error(env, esock_atom_einval); break; } + SSDBG( descP, + ("SOCKET", "nsetopt_level -> done when" + "\r\n result: %T" + "\r\n", result) ); + return result; } @@ -4527,6 +4550,8 @@ ERL_NIF_TERM nsetopt_lvl_socket(ErlNifEnv* env, #endif default: + SSDBG( descP, + ("SOCKET", "nsetopt_lvl_socket -> unknown opt (%d)\r\n", eOpt) ); result = esock_make_error(env, esock_atom_einval); break; } @@ -5572,6 +5597,12 @@ ERL_NIF_TERM nsetopt_lvl_ipv6(ErlNifEnv* env, break; #endif +#if defined(IPV6_RTHDR) + case SOCKET_OPT_IPV6_RTHDR: + result = nsetopt_lvl_ipv6_rthdr(env, descP, eVal); + break; +#endif + #if defined(IPV6_V6ONLY) case SOCKET_OPT_IPV6_V6ONLY: result = nsetopt_lvl_ipv6_v6only(env, descP, eVal); @@ -5579,10 +5610,17 @@ ERL_NIF_TERM nsetopt_lvl_ipv6(ErlNifEnv* env, #endif default: + SSDBG( descP, + ("SOCKET", "nsetopt_lvl_ipv6 -> unknown opt (%d)\r\n", eOpt) ); result = esock_make_error(env, esock_atom_einval); break; } + SSDBG( descP, + ("SOCKET", "nsetopt_lvl_ipv6 -> done when" + "\r\n result: %T" + "\r\n", result) ); + return result; } @@ -5721,6 +5759,17 @@ ERL_NIF_TERM nsetopt_lvl_ipv6_recvpktinfo(ErlNifEnv* env, #endif +#if defined(IPV6_RTHDR) +static +ERL_NIF_TERM nsetopt_lvl_ipv6_rthdr(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) +{ + return nsetopt_bool_opt(env, descP, SOL_IPV6, IPV6_RTHDR, eVal); +} +#endif + + #if defined(IPV6_V6ONLY) static ERL_NIF_TERM nsetopt_lvl_ipv6_v6only(ErlNifEnv* env, @@ -8090,6 +8139,12 @@ ERL_NIF_TERM ngetopt_lvl_ipv6(ErlNifEnv* env, break; #endif +#if defined(IPV6_RTHDR) + case SOCKET_OPT_IPV6_RTHDR: + result = ngetopt_lvl_ipv6_rthdr(env, descP); + break; +#endif + #if defined(IPV6_V6ONLY) case SOCKET_OPT_IPV6_V6ONLY: result = ngetopt_lvl_ipv6_v6only(env, descP); @@ -8205,6 +8260,16 @@ ERL_NIF_TERM ngetopt_lvl_ipv6_recvpktinfo(ErlNifEnv* env, #endif +#if defined(IPV6_RTHDR) +static +ERL_NIF_TERM ngetopt_lvl_ipv6_rthdr(ErlNifEnv* env, + SocketDescriptor* descP) +{ + return ngetopt_bool_opt(env, descP, SOL_IPV6, IPV6_RTHDR); +} +#endif + + #if defined(IPV6_V6ONLY) static ERL_NIF_TERM ngetopt_lvl_ipv6_v6only(ErlNifEnv* env, diff --git a/erts/preloaded/ebin/socket.beam b/erts/preloaded/ebin/socket.beam index 15bc762ae9..1873289486 100644 Binary files a/erts/preloaded/ebin/socket.beam and b/erts/preloaded/ebin/socket.beam differ diff --git a/erts/preloaded/src/socket.erl b/erts/preloaded/src/socket.erl index 7e9b61ba8f..d2267192e5 100644 --- a/erts/preloaded/src/socket.erl +++ b/erts/preloaded/src/socket.erl @@ -657,7 +657,7 @@ -define(SOCKET_OPT_IPV6_RECVPKTINFO, 25). % On FreeBSD: PKTINFO %% -define(SOCKET_OPT_IPV6_RECVTCLASS, 26). %% -define(SOCKET_OPT_IPV6_ROUTER_ALERT, 27). -%% -define(SOCKET_OPT_IPV6_RTHDR, 28). +-define(SOCKET_OPT_IPV6_RTHDR, 28). %% -define(SOCKET_OPT_IPV6_TCLASS, 29). %% -define(SOCKET_OPT_IPV6_UNICAST_HOPS, 30). %% -define(SOCKET_OPT_IPV6_USE_MIN_MTU, 31). @@ -2282,6 +2282,9 @@ enc_setopt_value(ipv6, Opt, V, _D, _T, _P) when ((Opt =:= recvpktinfo) orelse (Opt =:= pktinfo)) andalso is_boolean(V) -> V; +enc_setopt_value(ipv6, rthdr, V, _D, _T, _P) + when is_boolean(V) -> + V; enc_setopt_value(ipv6, v6only, V, _D, _T, _P) when is_boolean(V) -> V; enc_setopt_value(ipv6 = L, Opt, V, _D, _T, _P) -> @@ -2727,9 +2730,9 @@ enc_sockopt_key(ipv6 = L, recvtclass = Opt, _Dir, _D, _T, _P) -> not_supported({L, Opt}); enc_sockopt_key(ipv6 = L, router_alert = Opt, _Dir, _D, _T, _P) -> not_supported({L, Opt}); -enc_sockopt_key(ipv6 = L, rthdr = Opt, set = _Dir, _D, T, _P) +enc_sockopt_key(ipv6 = _L, rthdr = _Opt, _Dir, _D, T, _P) when ((T =:= dgram) orelse (T =:= raw)) -> - not_supported({L, Opt}); + ?SOCKET_OPT_IPV6_RTHDR; enc_sockopt_key(ipv6 = L, tclass = Opt, _Dir, _D, _T, _P) -> not_supported({L, Opt}); enc_sockopt_key(ipv6 = L, unicast_hops = Opt, _Dir, _D, _T, _P) -> @@ -2739,7 +2742,7 @@ enc_sockopt_key(ipv6 = L, use_min_mtu = Opt, _Dir, _D, _T, _P) -> enc_sockopt_key(ipv6 = _L, v6only = _Opt, _Dir, _D, _T, _P) -> ?SOCKET_OPT_IPV6_V6ONLY; enc_sockopt_key(ipv6 = L, UnknownOpt, _Dir, _D, _T, _P) -> - unknown({L, UnknownOpt}); + unknown({L, UnknownOpt, _Dir, _D, _T, _P}); %% TCP socket options %% There are other options that would be useful; info, diff --git a/lib/kernel/test/socket_server.erl b/lib/kernel/test/socket_server.erl index ba74964ed1..75cede75e5 100644 --- a/lib/kernel/test/socket_server.erl +++ b/lib/kernel/test/socket_server.erl @@ -23,9 +23,9 @@ -export([ start/0, start/4, start_tcp/0, start_tcp/1, start_tcp/2, - start_tcp4/1, start_tcp6/1, + start_tcp4/0, start_tcp4/1, start_tcp6/0, start_tcp6/1, start_udp/0, start_udp/1, start_udp/2, - start_udp4/1, start_udp6/1, + start_udp4/0, start_udp4/1, start_udp6/0, start_udp6/1, start_sctp/0, start_sctp/1 ]). @@ -41,14 +41,20 @@ start() -> start_tcp(). start_tcp() -> - start_tcp(false). + start_tcp4(). start_tcp(Peek) -> start_tcp4(Peek). +start_tcp4() -> + start_tcp4(false). + start_tcp4(Peek) -> start_tcp(inet, Peek). +start_tcp6() -> + start_tcp6(false). + start_tcp6(Peek) -> start_tcp(inet6, Peek). @@ -56,14 +62,20 @@ start_tcp(Domain, Peek) when is_boolean(Peek) -> start(Domain, stream, tcp, Peek). start_udp() -> - start_udp(false). + start_udp4(). start_udp(Peek) -> start_udp4(Peek). +start_udp4() -> + start_udp4(false). + start_udp4(Peek) -> start_udp(inet, Peek). +start_udp6() -> + start_udp6(false). + start_udp6(Peek) -> start_udp(inet6, Peek). @@ -621,6 +633,7 @@ handler_init(Manager, ID, Peek, Sock) -> MIF6 = GIP6(multicast_if), % Only dgram and raw MLoop6 = GIP6(multicast_loop), RecvPktInfo = GIP6(recvpktinfo), + RtHdr = GIP6(rthdr), i("got continue when: " "~n (socket) Domain: ~p" "~n (socket) Type: ~p" @@ -652,15 +665,15 @@ handler_init(Manager, ID, Peek, Sock) -> "~n (ipv6) Multicast Hops: ~s" "~n (ipv6) Multicast IF: ~s" "~n (ipv6) Multicast Loop: ~s" - "~n (ipv6) Recv Pkt Info: ~s", + "~n (ipv6) Recv Pkt Info: ~s" + "~n (ipv6) RT Hdr: ~s", [Domain, Type, Proto, RA, RP, B2D, OOBI, RcvBuf, RcvLW, RcvTO, SndBuf, SndLW, SndTO, Linger, Timestamp, FreeBind, MTU, MTUDisc, MALL, MIF4, MLoop4, MTTL, NF, RecvIF, RecvOPTS, RecvTOS, RecvTTL, - MHops, MIF6, MLoop6, - RecvPktInfo]), + MHops, MIF6, MLoop6, RecvPktInfo, RtHdr]), handler_loop(#handler{peek = Peek, manager = Manager, -- cgit v1.2.3 From 8b61022bdca354e541380391e3b0b2606f7af3f8 Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Tue, 24 Jul 2018 11:08:20 +0200 Subject: [socket-nif] Add support for socket (level ipv6) option authhdr & hopopts Added support for the IPv6 socket option(s) AUTHHDR and HOPOPTS. Its possible that the option is AUTHHDR is obsolete. It says so in the include files and when trying to get it (getsockopt) it returns with enoprotoopt. The option HOPOPTS returns with einval when calling setsockopt, so either you need to be a privileged user to update, or its not actually possible to update this option (even though it says nothing about that in the man page. It only talks about set). This is the same behaviour as with RTHDR and HOPLIMIT. On FreeBSD, it says that HOPOPTS requires superuser privileges. Needs furher checking. OTP-14831. --- erts/doc/src/socket_usage.xml | 18 +++++- erts/emulator/nifs/common/socket_nif.c | 102 ++++++++++++++++++++++++++++++++- erts/preloaded/ebin/socket.beam | Bin 57224 -> 57388 bytes erts/preloaded/src/socket.erl | 27 ++++++--- lib/kernel/test/socket_server.erl | 9 ++- 5 files changed, 142 insertions(+), 14 deletions(-) diff --git a/erts/doc/src/socket_usage.xml b/erts/doc/src/socket_usage.xml index b3ee38dbcb..b13582dfbc 100644 --- a/erts/doc/src/socket_usage.xml +++ b/erts/doc/src/socket_usage.xml @@ -418,6 +418,13 @@ no none + + authhdr + boolean() + yes + yes + type = dgram | raw, obsolete? + drop_membership ipv6_mreq() @@ -429,8 +436,15 @@ hoplimit boolean() yes - no - type = dgram | raw + yes + type = dgram | raw, requires superuser privileges to update + + + hopopts + boolean() + yes + yes + type = dgram | raw, requires superuser privileges to update mtu diff --git a/erts/emulator/nifs/common/socket_nif.c b/erts/emulator/nifs/common/socket_nif.c index f4ac43a9cd..42a9bc3cb6 100644 --- a/erts/emulator/nifs/common/socket_nif.c +++ b/erts/emulator/nifs/common/socket_nif.c @@ -536,8 +536,10 @@ typedef union { #define SOCKET_OPT_IP_UNBLOCK_SOURCE 33 #define SOCKET_OPT_IPV6_ADD_MEMBERSHIP 2 +#define SOCKET_OPT_IPV6_AUTHHDR 3 #define SOCKET_OPT_IPV6_DROP_MEMBERSHIP 6 #define SOCKET_OPT_IPV6_HOPLIMIT 12 +#define SOCKET_OPT_IPV6_HOPOPTS 13 #define SOCKET_OPT_IPV6_MTU 17 #define SOCKET_OPT_IPV6_MTU_DISCOVER 18 #define SOCKET_OPT_IPV6_MULTICAST_HOPS 19 @@ -1201,6 +1203,11 @@ static ERL_NIF_TERM nsetopt_lvl_ipv6_add_membership(ErlNifEnv* env, SocketDescriptor* descP, ERL_NIF_TERM eVal); #endif +#if defined(IPV6_AUTHHDR) +static ERL_NIF_TERM nsetopt_lvl_ipv6_authhdr(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); +#endif #if defined(IPV6_DROP_MEMBERSHIP) static ERL_NIF_TERM nsetopt_lvl_ipv6_drop_membership(ErlNifEnv* env, SocketDescriptor* descP, @@ -1211,6 +1218,11 @@ static ERL_NIF_TERM nsetopt_lvl_ipv6_hoplimit(ErlNifEnv* env, SocketDescriptor* descP, ERL_NIF_TERM eVal); #endif +#if defined(IPV6_HOPOPTS) +static ERL_NIF_TERM nsetopt_lvl_ipv6_hopopts(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); +#endif #if defined(IPV6_MTU) static ERL_NIF_TERM nsetopt_lvl_ipv6_mtu(ErlNifEnv* env, SocketDescriptor* descP, @@ -1523,10 +1535,18 @@ static ERL_NIF_TERM ngetopt_lvl_ip_ttl(ErlNifEnv* env, static ERL_NIF_TERM ngetopt_lvl_ipv6(ErlNifEnv* env, SocketDescriptor* descP, int eOpt); +#if defined(IPV6_AUTHHDR) +static ERL_NIF_TERM ngetopt_lvl_ipv6_authhdr(ErlNifEnv* env, + SocketDescriptor* descP); +#endif #if defined(IPV6_HOPLIMIT) static ERL_NIF_TERM ngetopt_lvl_ipv6_hoplimit(ErlNifEnv* env, SocketDescriptor* descP); #endif +#if defined(IPV6_HOPOPTS) +static ERL_NIF_TERM ngetopt_lvl_ipv6_hopopts(ErlNifEnv* env, + SocketDescriptor* descP); +#endif #if defined(IPV6_MTU) static ERL_NIF_TERM ngetopt_lvl_ipv6_mtu(ErlNifEnv* env, SocketDescriptor* descP); @@ -1553,7 +1573,7 @@ static ERL_NIF_TERM ngetopt_lvl_ipv6_recvpktinfo(ErlNifEnv* env, #endif #if defined(IPV6_RTHDR) static ERL_NIF_TERM ngetopt_lvl_ipv6_rthdr(ErlNifEnv* env, - SocketDescriptor* descP); + SocketDescriptor* descP); #endif #if defined(IPV6_V6ONLY) static ERL_NIF_TERM ngetopt_lvl_ipv6_v6only(ErlNifEnv* env, @@ -5549,6 +5569,12 @@ ERL_NIF_TERM nsetopt_lvl_ipv6(ErlNifEnv* env, break; #endif +#if defined(IPV6_AUTHHDR) + case SOCKET_OPT_IPV6_AUTHHDR: + result = nsetopt_lvl_ipv6_authhdr(env, descP, eVal); + break; +#endif + #if defined(IPV6_DROP_MEMBERSHIP) case SOCKET_OPT_IPV6_DROP_MEMBERSHIP: result = nsetopt_lvl_ipv6_drop_membership(env, descP, eVal); @@ -5561,6 +5587,12 @@ ERL_NIF_TERM nsetopt_lvl_ipv6(ErlNifEnv* env, break; #endif +#if defined(IPV6_HOPOPTS) + case SOCKET_OPT_IPV6_HOPOPTS: + result = nsetopt_lvl_ipv6_hopopts(env, descP, eVal); + break; +#endif + #if defined(IPV6_MTU) case SOCKET_OPT_IPV6_MTU: result = nsetopt_lvl_ipv6_mtu(env, descP, eVal); @@ -5637,6 +5669,17 @@ ERL_NIF_TERM nsetopt_lvl_ipv6_add_membership(ErlNifEnv* env, #endif +#if defined(IPV6_AUTHHDR) +static +ERL_NIF_TERM nsetopt_lvl_ipv6_authhdr(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) +{ + return nsetopt_bool_opt(env, descP, SOL_IPV6, IPV6_AUTHHDR, eVal); +} +#endif + + #if defined(IPV6_DROP_MEMBERSHIP) static ERL_NIF_TERM nsetopt_lvl_ipv6_drop_membership(ErlNifEnv* env, @@ -5660,6 +5703,17 @@ ERL_NIF_TERM nsetopt_lvl_ipv6_hoplimit(ErlNifEnv* env, #endif +#if defined(IPV6_HOPOPTS) +static +ERL_NIF_TERM nsetopt_lvl_ipv6_hopopts(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) +{ + return nsetopt_bool_opt(env, descP, SOL_IPV6, IPV6_HOPOPTS, eVal); +} +#endif + + #if defined(IPV6_MTU) static ERL_NIF_TERM nsetopt_lvl_ipv6_mtu(ErlNifEnv* env, @@ -8097,12 +8151,24 @@ ERL_NIF_TERM ngetopt_lvl_ipv6(ErlNifEnv* env, "\r\n", eOpt) ); switch (eOpt) { +#if defined(IPV6_AUTHHDR) + case SOCKET_OPT_IPV6_AUTHHDR: + result = ngetopt_lvl_ipv6_authhdr(env, descP); + break; +#endif + #if defined(IPV6_HOPLIMIT) case SOCKET_OPT_IPV6_HOPLIMIT: result = ngetopt_lvl_ipv6_hoplimit(env, descP); break; #endif +#if defined(IPV6_HOPOPTS) + case SOCKET_OPT_IPV6_HOPOPTS: + result = ngetopt_lvl_ipv6_hopopts(env, descP); + break; +#endif + #if defined(IPV6_MTU) case SOCKET_OPT_IPV6_MTU: result = ngetopt_lvl_ipv6_mtu(env, descP); @@ -8165,6 +8231,16 @@ ERL_NIF_TERM ngetopt_lvl_ipv6(ErlNifEnv* env, } +#if defined(IPV6_AUTHHDR) +static +ERL_NIF_TERM ngetopt_lvl_ipv6_authhdr(ErlNifEnv* env, + SocketDescriptor* descP) +{ + return ngetopt_bool_opt(env, descP, SOL_IPV6, IPV6_AUTHHDR); +} +#endif + + #if defined(IPV6_HOPLIMIT) static ERL_NIF_TERM ngetopt_lvl_ipv6_hoplimit(ErlNifEnv* env, @@ -8175,6 +8251,16 @@ ERL_NIF_TERM ngetopt_lvl_ipv6_hoplimit(ErlNifEnv* env, #endif +#if defined(IPV6_HOPOPTS) +static +ERL_NIF_TERM ngetopt_lvl_ipv6_hopopts(ErlNifEnv* env, + SocketDescriptor* descP) +{ + return ngetopt_bool_opt(env, descP, SOL_IPV6, IPV6_HOPOPTS); +} +#endif + + #if defined(IPV6_MTU) static ERL_NIF_TERM ngetopt_lvl_ipv6_mtu(ErlNifEnv* env, @@ -8707,6 +8793,13 @@ ERL_NIF_TERM ngetopt_bool_opt(ErlNifEnv* env, SOCKOPTLEN_T valSz = sizeof(val); int res; + /* + SSDBG( descP, ("SOCKET", "ngetopt_bool_opt -> entry with" + "\r\n: level: %d" + "\r\n: opt: %d" + "\r\n", level, opt) ); + */ + res = sock_getopt(descP->sock, level, opt, &val, &valSz); if (res != 0) { @@ -8717,6 +8810,13 @@ ERL_NIF_TERM ngetopt_bool_opt(ErlNifEnv* env, result = esock_make_ok2(env, bval); } + /* + SSDBG( descP, ("SOCKET", "ngetopt_bool_opt -> done when" + "\r\n: res: %d" + "\r\n: result: %T" + "\r\n", res, result) ); + */ + return result; } diff --git a/erts/preloaded/ebin/socket.beam b/erts/preloaded/ebin/socket.beam index 1873289486..da825abb98 100644 Binary files a/erts/preloaded/ebin/socket.beam and b/erts/preloaded/ebin/socket.beam differ diff --git a/erts/preloaded/src/socket.erl b/erts/preloaded/src/socket.erl index d2267192e5..3c0dc6e95b 100644 --- a/erts/preloaded/src/socket.erl +++ b/erts/preloaded/src/socket.erl @@ -632,7 +632,7 @@ %% -define(SOCKET_OPT_IPV6_ADDFORM, 1). -define(SOCKET_OPT_IPV6_ADD_MEMBERSHIP, 2). -%% -define(SOCKET_OPT_IPV6_AUTHHDR, 3). +-define(SOCKET_OPT_IPV6_AUTHHDR, 3). % Obsolete? %% -define(SOCKET_OPT_IPV6_AUTH_LEVEL, 4). %% -define(SOCKET_OPT_IPV6_CHECKSUM, 5). -define(SOCKET_OPT_IPV6_DROP_MEMBERSHIP, 6). @@ -642,7 +642,7 @@ %% -define(SOCKET_OPT_IPV6_FAITH, 10). %% -define(SOCKET_OPT_IPV6_FLOWINFO, 11). -define(SOCKET_OPT_IPV6_HOPLIMIT, 12). -%% -define(SOCKET_OPT_IPV6_HOPOPTS, 13). +-define(SOCKET_OPT_IPV6_HOPOPTS, 13). %% -define(SOCKET_OPT_IPV6_IPCOMP_LEVEL, 14). %% -define(SOCKET_OPT_IPV6_JOIN_GROUP, 15). %% -define(SOCKET_OPT_IPV6_LEAVE_GROUP, 16). @@ -2249,6 +2249,12 @@ enc_setopt_value(ipv6, add_membership, #{multiaddr := MA, when ((is_tuple(MA) andalso (size(MA) =:= 8)) andalso (is_integer(IF) andalso (IF >= 0))) -> V; +%% Is this obsolete? When get, the result is enoprotoopt and in the +%% header file it says 'obsolete'... +%% But there might be (old?) versions of linux where it still works... +enc_setopt_value(ipv6, authhdr, V, _D, T, _P) + when is_boolean(V) andalso ((T =:= dgram) orelse (T =:= raw)) -> + V; enc_setopt_value(ipv6, drop_membership, #{multiaddr := MA, interface := IF} = V, _D, _T, _P) when ((is_tuple(MA) andalso (size(MA) =:= 8)) andalso @@ -2257,6 +2263,9 @@ enc_setopt_value(ipv6, drop_membership, #{multiaddr := MA, enc_setopt_value(ipv6, hoplimit, V, _D, T, _P) when is_boolean(V) andalso ((T =:= dgram) orelse (T =:= raw)) -> V; +enc_setopt_value(ipv6, hopopts, V, _D, T, _P) + when is_boolean(V) andalso ((T =:= dgram) orelse (T =:= raw)) -> + V; enc_setopt_value(ipv6, mtu, V, _D, _T, _P) when is_integer(V) -> V; enc_setopt_value(ipv6, mtu_discover, V, _D, _T, _P) @@ -2282,8 +2291,8 @@ enc_setopt_value(ipv6, Opt, V, _D, _T, _P) when ((Opt =:= recvpktinfo) orelse (Opt =:= pktinfo)) andalso is_boolean(V) -> V; -enc_setopt_value(ipv6, rthdr, V, _D, _T, _P) - when is_boolean(V) -> +enc_setopt_value(ipv6, rthdr, V, _D, T, _P) + when is_boolean(V) andalso ((T =:= dgram) orelse (T =:= raw)) -> V; enc_setopt_value(ipv6, v6only, V, _D, _T, _P) when is_boolean(V) -> V; @@ -2674,9 +2683,9 @@ enc_sockopt_key(ipv6 = L, addrform = Opt, set = _Dir, _D, _T, _P) -> not_supported({L, Opt}); enc_sockopt_key(ipv6, add_membership = _Opt, set = _Dir, _D, _T, _P) -> ?SOCKET_OPT_IPV6_ADD_MEMBERSHIP; -enc_sockopt_key(ipv6 = L, authhdr = Opt, set = _Dir, _D, T, _P) +enc_sockopt_key(ipv6 = _L, authhdr = _Opt, _Dir, _D, T, _P) when ((T =:= dgram) orelse (T =:= raw)) -> - not_supported({L, Opt}); + ?SOCKET_OPT_IPV6_AUTHHDR; enc_sockopt_key(ipv6 = L, auth_level = Opt, _Dir, _D, _T, _P) -> not_supported({L, Opt}); enc_sockopt_key(ipv6 = L, checksum = Opt, _Dir, _D, _T, _P) -> @@ -2693,12 +2702,12 @@ enc_sockopt_key(ipv6 = L, esp_network_level = Opt, _Dir, _D, _T, _P) -> enc_sockopt_key(ipv6 = L, flowinfo = Opt, set = _Dir, _D, T, _P) when (T =:= dgram) orelse (T =:= raw) -> not_supported({L, Opt}); -enc_sockopt_key(ipv6, hoplimit = _Opt, _Dir, set = _D, T, _P) +enc_sockopt_key(ipv6, hoplimit = _Opt, _Dir, _D, T, _P) when (T =:= dgram) orelse (T =:= raw) -> ?SOCKET_OPT_IPV6_HOPLIMIT; -enc_sockopt_key(ipv6 = L, hopopts = Opt, set = _Dir, _D, T, _P) +enc_sockopt_key(ipv6 = _L, hopopts = _Opt, _Dir, _D, T, _P) when ((T =:= dgram) orelse (T =:= raw)) -> - not_supported({L, Opt}); + ?SOCKET_OPT_IPV6_HOPOPTS; enc_sockopt_key(ipv6 = L, ipcomp_level = Opt, _Dir, _D, _T, _P) -> not_supported({L, Opt}); enc_sockopt_key(ipv6 = L, join_group = Opt, _Dir, _D, _T, _P) -> diff --git a/lib/kernel/test/socket_server.erl b/lib/kernel/test/socket_server.erl index 75cede75e5..1cd0ec1202 100644 --- a/lib/kernel/test/socket_server.erl +++ b/lib/kernel/test/socket_server.erl @@ -634,6 +634,8 @@ handler_init(Manager, ID, Peek, Sock) -> MLoop6 = GIP6(multicast_loop), RecvPktInfo = GIP6(recvpktinfo), RtHdr = GIP6(rthdr), + AuthHdr = GIP6(authhdr), + HopOpts = GIP6(hopopts), i("got continue when: " "~n (socket) Domain: ~p" "~n (socket) Type: ~p" @@ -666,14 +668,17 @@ handler_init(Manager, ID, Peek, Sock) -> "~n (ipv6) Multicast IF: ~s" "~n (ipv6) Multicast Loop: ~s" "~n (ipv6) Recv Pkt Info: ~s" - "~n (ipv6) RT Hdr: ~s", + "~n (ipv6) RT Hdr: ~s" + "~n (ipv6) Auth Hdr: ~s" + "~n (ipv6) Hop Opts: ~s", [Domain, Type, Proto, RA, RP, B2D, OOBI, RcvBuf, RcvLW, RcvTO, SndBuf, SndLW, SndTO, Linger, Timestamp, FreeBind, MTU, MTUDisc, MALL, MIF4, MLoop4, MTTL, NF, RecvIF, RecvOPTS, RecvTOS, RecvTTL, - MHops, MIF6, MLoop6, RecvPktInfo, RtHdr]), + MHops, MIF6, MLoop6, RecvPktInfo, + RtHdr, AuthHdr, HopOpts]), handler_loop(#handler{peek = Peek, manager = Manager, -- cgit v1.2.3 From 9ca6de6efbe844bcf7dc996cfcf51bcd50325007 Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Tue, 24 Jul 2018 11:49:12 +0200 Subject: [socket-nif] Add support for socket (level ipv6) option dstopts Added support for the IPv6 socket option(s) DSTOPTS. The option returns with einval when calling setsockopt, so either you need to be a privileged user to update, or its not actually possible to update this option (even though it says nothing about that in the man page. It only talks about set). This is the same behaviour as with RTHDR and HOPLIMIT. On FreeBSD, it says that HOPOPTS requires superuser privileges. Needs furher checking. OTP-14831. --- erts/doc/src/socket_usage.xml | 7 ++++++ erts/emulator/nifs/common/socket_nif.c | 43 +++++++++++++++++++++++++++++++++ erts/preloaded/ebin/socket.beam | Bin 57388 -> 57468 bytes erts/preloaded/src/socket.erl | 11 ++++++--- lib/kernel/test/socket_server.erl | 8 ++++-- 5 files changed, 63 insertions(+), 6 deletions(-) diff --git a/erts/doc/src/socket_usage.xml b/erts/doc/src/socket_usage.xml index b13582dfbc..87b44f139f 100644 --- a/erts/doc/src/socket_usage.xml +++ b/erts/doc/src/socket_usage.xml @@ -432,6 +432,13 @@ no none + + dstopts + boolean() + yes + yes + type = dgram | raw, requires superuser privileges to update + hoplimit boolean() diff --git a/erts/emulator/nifs/common/socket_nif.c b/erts/emulator/nifs/common/socket_nif.c index 42a9bc3cb6..9504643cb8 100644 --- a/erts/emulator/nifs/common/socket_nif.c +++ b/erts/emulator/nifs/common/socket_nif.c @@ -538,6 +538,7 @@ typedef union { #define SOCKET_OPT_IPV6_ADD_MEMBERSHIP 2 #define SOCKET_OPT_IPV6_AUTHHDR 3 #define SOCKET_OPT_IPV6_DROP_MEMBERSHIP 6 +#define SOCKET_OPT_IPV6_DSTOPTS 7 #define SOCKET_OPT_IPV6_HOPLIMIT 12 #define SOCKET_OPT_IPV6_HOPOPTS 13 #define SOCKET_OPT_IPV6_MTU 17 @@ -1213,6 +1214,11 @@ static ERL_NIF_TERM nsetopt_lvl_ipv6_drop_membership(ErlNifEnv* env, SocketDescriptor* descP, ERL_NIF_TERM eVal); #endif +#if defined(IPV6_DSTOPTS) +static ERL_NIF_TERM nsetopt_lvl_ipv6_dstopts(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); +#endif #if defined(IPV6_HOPLIMIT) static ERL_NIF_TERM nsetopt_lvl_ipv6_hoplimit(ErlNifEnv* env, SocketDescriptor* descP, @@ -1539,6 +1545,10 @@ static ERL_NIF_TERM ngetopt_lvl_ipv6(ErlNifEnv* env, static ERL_NIF_TERM ngetopt_lvl_ipv6_authhdr(ErlNifEnv* env, SocketDescriptor* descP); #endif +#if defined(IPV6_DSTOPTS) +static ERL_NIF_TERM ngetopt_lvl_ipv6_dstopts(ErlNifEnv* env, + SocketDescriptor* descP); +#endif #if defined(IPV6_HOPLIMIT) static ERL_NIF_TERM ngetopt_lvl_ipv6_hoplimit(ErlNifEnv* env, SocketDescriptor* descP); @@ -5581,6 +5591,12 @@ ERL_NIF_TERM nsetopt_lvl_ipv6(ErlNifEnv* env, break; #endif +#if defined(IPV6_DSTOPTS) + case SOCKET_OPT_IPV6_DSTOPTS: + result = nsetopt_lvl_ipv6_dstopts(env, descP, eVal); + break; +#endif + #if defined(IPV6_HOPLIMIT) case SOCKET_OPT_IPV6_HOPLIMIT: result = nsetopt_lvl_ipv6_hoplimit(env, descP, eVal); @@ -5692,6 +5708,17 @@ ERL_NIF_TERM nsetopt_lvl_ipv6_drop_membership(ErlNifEnv* env, #endif +#if defined(IPV6_DSTOPTS) +static +ERL_NIF_TERM nsetopt_lvl_ipv6_dstopts(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) +{ + return nsetopt_bool_opt(env, descP, SOL_IPV6, IPV6_DSTOPTS, eVal); +} +#endif + + #if defined(IPV6_HOPLIMIT) static ERL_NIF_TERM nsetopt_lvl_ipv6_hoplimit(ErlNifEnv* env, @@ -8157,6 +8184,12 @@ ERL_NIF_TERM ngetopt_lvl_ipv6(ErlNifEnv* env, break; #endif +#if defined(IPV6_DSTOPTS) + case SOCKET_OPT_IPV6_DSTOPTS: + result = ngetopt_lvl_ipv6_dstopts(env, descP); + break; +#endif + #if defined(IPV6_HOPLIMIT) case SOCKET_OPT_IPV6_HOPLIMIT: result = ngetopt_lvl_ipv6_hoplimit(env, descP); @@ -8241,6 +8274,16 @@ ERL_NIF_TERM ngetopt_lvl_ipv6_authhdr(ErlNifEnv* env, #endif +#if defined(IPV6_DSTOPTS) +static +ERL_NIF_TERM ngetopt_lvl_ipv6_dstopts(ErlNifEnv* env, + SocketDescriptor* descP) +{ + return ngetopt_bool_opt(env, descP, SOL_IPV6, IPV6_DSTOPTS); +} +#endif + + #if defined(IPV6_HOPLIMIT) static ERL_NIF_TERM ngetopt_lvl_ipv6_hoplimit(ErlNifEnv* env, diff --git a/erts/preloaded/ebin/socket.beam b/erts/preloaded/ebin/socket.beam index da825abb98..17622c5341 100644 Binary files a/erts/preloaded/ebin/socket.beam and b/erts/preloaded/ebin/socket.beam differ diff --git a/erts/preloaded/src/socket.erl b/erts/preloaded/src/socket.erl index 3c0dc6e95b..2bab42267a 100644 --- a/erts/preloaded/src/socket.erl +++ b/erts/preloaded/src/socket.erl @@ -636,7 +636,7 @@ %% -define(SOCKET_OPT_IPV6_AUTH_LEVEL, 4). %% -define(SOCKET_OPT_IPV6_CHECKSUM, 5). -define(SOCKET_OPT_IPV6_DROP_MEMBERSHIP, 6). -%% -define(SOCKET_OPT_IPV6_DSTOPTS, 7). +-define(SOCKET_OPT_IPV6_DSTOPTS, 7). %% -define(SOCKET_OPT_IPV6_ESP_TRANS_LEVEL, 8). %% -define(SOCKET_OPT_IPV6_ESP_NETWORK_LEVEL, 9). %% -define(SOCKET_OPT_IPV6_FAITH, 10). @@ -2255,6 +2255,9 @@ enc_setopt_value(ipv6, add_membership, #{multiaddr := MA, enc_setopt_value(ipv6, authhdr, V, _D, T, _P) when is_boolean(V) andalso ((T =:= dgram) orelse (T =:= raw)) -> V; +enc_setopt_value(ipv6, dstopts, V, _D, T, _P) + when is_boolean(V) andalso ((T =:= dgram) orelse (T =:= raw)) -> + V; enc_setopt_value(ipv6, drop_membership, #{multiaddr := MA, interface := IF} = V, _D, _T, _P) when ((is_tuple(MA) andalso (size(MA) =:= 8)) andalso @@ -2692,14 +2695,14 @@ enc_sockopt_key(ipv6 = L, checksum = Opt, _Dir, _D, _T, _P) -> not_supported({L, Opt}); enc_sockopt_key(ipv6, drop_membership = _Opt, set = _Dir, _D, _T, _P) -> ?SOCKET_OPT_IPV6_DROP_MEMBERSHIP; -enc_sockopt_key(ipv6 = L, dstopts = Opt, set = _Dir, _D, T, _P) +enc_sockopt_key(ipv6 = _L, dstopts = _Opt, _Dir, _D, T, _P) when (T =:= dgram) orelse (T =:= raw) -> - not_supported({L, Opt}); + ?SOCKET_OPT_IPV6_DSTOPTS; enc_sockopt_key(ipv6 = L, esp_trans_level = Opt, _Dir, _D, _T, _P) -> not_supported({L, Opt}); enc_sockopt_key(ipv6 = L, esp_network_level = Opt, _Dir, _D, _T, _P) -> not_supported({L, Opt}); -enc_sockopt_key(ipv6 = L, flowinfo = Opt, set = _Dir, _D, T, _P) +enc_sockopt_key(ipv6 = L, flowinfo = Opt, _Dir, _D, T, _P) when (T =:= dgram) orelse (T =:= raw) -> not_supported({L, Opt}); enc_sockopt_key(ipv6, hoplimit = _Opt, _Dir, _D, T, _P) diff --git a/lib/kernel/test/socket_server.erl b/lib/kernel/test/socket_server.erl index 1cd0ec1202..8436b39372 100644 --- a/lib/kernel/test/socket_server.erl +++ b/lib/kernel/test/socket_server.erl @@ -635,7 +635,9 @@ handler_init(Manager, ID, Peek, Sock) -> RecvPktInfo = GIP6(recvpktinfo), RtHdr = GIP6(rthdr), AuthHdr = GIP6(authhdr), + HopLimit = GIP6(hoplimit), HopOpts = GIP6(hopopts), + DstOpts = GIP6(dstopts), i("got continue when: " "~n (socket) Domain: ~p" "~n (socket) Type: ~p" @@ -670,7 +672,9 @@ handler_init(Manager, ID, Peek, Sock) -> "~n (ipv6) Recv Pkt Info: ~s" "~n (ipv6) RT Hdr: ~s" "~n (ipv6) Auth Hdr: ~s" - "~n (ipv6) Hop Opts: ~s", + "~n (ipv6) Hop Limit: ~s" + "~n (ipv6) Hop Opts: ~s" + "~n (ipv6) Dst Opts: ~s", [Domain, Type, Proto, RA, RP, B2D, OOBI, RcvBuf, RcvLW, RcvTO, SndBuf, SndLW, SndTO, @@ -678,7 +682,7 @@ handler_init(Manager, ID, Peek, Sock) -> FreeBind, MTU, MTUDisc, MALL, MIF4, MLoop4, MTTL, NF, RecvIF, RecvOPTS, RecvTOS, RecvTTL, MHops, MIF6, MLoop6, RecvPktInfo, - RtHdr, AuthHdr, HopOpts]), + RtHdr, AuthHdr, HopLimit, HopOpts, DstOpts]), handler_loop(#handler{peek = Peek, manager = Manager, -- cgit v1.2.3 From e54642b537177941ff361b1eebdec10e02cfc22d Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Tue, 24 Jul 2018 12:05:00 +0200 Subject: [socket-nif] Add support for socket (level ipv6) option flowinfo Added support for the IPv6 socket option(s) FLOWINFO. The option returns with einval when calling setsockopt, so either you need to be a privileged user to update, or its not actually possible to update this option (even though it says nothing about that in the man page. It only talks about set). This is the same behaviour as with DSTOPTS. Needs furher checking. OTP-14831. --- erts/doc/src/socket_usage.xml | 7 ++++++ erts/emulator/nifs/common/socket_nif.c | 43 +++++++++++++++++++++++++++++++++ erts/preloaded/ebin/socket.beam | Bin 57468 -> 57524 bytes erts/preloaded/src/socket.erl | 9 ++++--- lib/kernel/test/socket_server.erl | 8 +++--- 5 files changed, 61 insertions(+), 6 deletions(-) diff --git a/erts/doc/src/socket_usage.xml b/erts/doc/src/socket_usage.xml index 87b44f139f..09a65c7152 100644 --- a/erts/doc/src/socket_usage.xml +++ b/erts/doc/src/socket_usage.xml @@ -439,6 +439,13 @@ yes type = dgram | raw, requires superuser privileges to update + + flowinfo + boolean() + yes + yes + type = dgram | raw, requires superuser privileges to update + hoplimit boolean() diff --git a/erts/emulator/nifs/common/socket_nif.c b/erts/emulator/nifs/common/socket_nif.c index 9504643cb8..6a2904f75f 100644 --- a/erts/emulator/nifs/common/socket_nif.c +++ b/erts/emulator/nifs/common/socket_nif.c @@ -539,6 +539,7 @@ typedef union { #define SOCKET_OPT_IPV6_AUTHHDR 3 #define SOCKET_OPT_IPV6_DROP_MEMBERSHIP 6 #define SOCKET_OPT_IPV6_DSTOPTS 7 +#define SOCKET_OPT_IPV6_FLOWINFO 11 #define SOCKET_OPT_IPV6_HOPLIMIT 12 #define SOCKET_OPT_IPV6_HOPOPTS 13 #define SOCKET_OPT_IPV6_MTU 17 @@ -1219,6 +1220,11 @@ static ERL_NIF_TERM nsetopt_lvl_ipv6_dstopts(ErlNifEnv* env, SocketDescriptor* descP, ERL_NIF_TERM eVal); #endif +#if defined(IPV6_FLOWINFO) +static ERL_NIF_TERM nsetopt_lvl_ipv6_flowinfo(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); +#endif #if defined(IPV6_HOPLIMIT) static ERL_NIF_TERM nsetopt_lvl_ipv6_hoplimit(ErlNifEnv* env, SocketDescriptor* descP, @@ -1549,6 +1555,10 @@ static ERL_NIF_TERM ngetopt_lvl_ipv6_authhdr(ErlNifEnv* env, static ERL_NIF_TERM ngetopt_lvl_ipv6_dstopts(ErlNifEnv* env, SocketDescriptor* descP); #endif +#if defined(IPV6_FLOWINFO) +static ERL_NIF_TERM ngetopt_lvl_ipv6_flowinfo(ErlNifEnv* env, + SocketDescriptor* descP); +#endif #if defined(IPV6_HOPLIMIT) static ERL_NIF_TERM ngetopt_lvl_ipv6_hoplimit(ErlNifEnv* env, SocketDescriptor* descP); @@ -5597,6 +5607,12 @@ ERL_NIF_TERM nsetopt_lvl_ipv6(ErlNifEnv* env, break; #endif +#if defined(IPV6_FLOWINFO) + case SOCKET_OPT_IPV6_FLOWINFO: + result = nsetopt_lvl_ipv6_flowinfo(env, descP, eVal); + break; +#endif + #if defined(IPV6_HOPLIMIT) case SOCKET_OPT_IPV6_HOPLIMIT: result = nsetopt_lvl_ipv6_hoplimit(env, descP, eVal); @@ -5719,6 +5735,17 @@ ERL_NIF_TERM nsetopt_lvl_ipv6_dstopts(ErlNifEnv* env, #endif +#if defined(IPV6_FLOWINFO) +static +ERL_NIF_TERM nsetopt_lvl_ipv6_flowinfo(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) +{ + return nsetopt_bool_opt(env, descP, SOL_IPV6, IPV6_FLOWINFO, eVal); +} +#endif + + #if defined(IPV6_HOPLIMIT) static ERL_NIF_TERM nsetopt_lvl_ipv6_hoplimit(ErlNifEnv* env, @@ -8190,6 +8217,12 @@ ERL_NIF_TERM ngetopt_lvl_ipv6(ErlNifEnv* env, break; #endif +#if defined(IPV6_FLOWINFO) + case SOCKET_OPT_IPV6_FLOWINFO: + result = ngetopt_lvl_ipv6_flowinfo(env, descP); + break; +#endif + #if defined(IPV6_HOPLIMIT) case SOCKET_OPT_IPV6_HOPLIMIT: result = ngetopt_lvl_ipv6_hoplimit(env, descP); @@ -8284,6 +8317,16 @@ ERL_NIF_TERM ngetopt_lvl_ipv6_dstopts(ErlNifEnv* env, #endif +#if defined(IPV6_FLOWINFO) +static +ERL_NIF_TERM ngetopt_lvl_ipv6_flowinfo(ErlNifEnv* env, + SocketDescriptor* descP) +{ + return ngetopt_bool_opt(env, descP, SOL_IPV6, IPV6_FLOWINFO); +} +#endif + + #if defined(IPV6_HOPLIMIT) static ERL_NIF_TERM ngetopt_lvl_ipv6_hoplimit(ErlNifEnv* env, diff --git a/erts/preloaded/ebin/socket.beam b/erts/preloaded/ebin/socket.beam index 17622c5341..f0788f2378 100644 Binary files a/erts/preloaded/ebin/socket.beam and b/erts/preloaded/ebin/socket.beam differ diff --git a/erts/preloaded/src/socket.erl b/erts/preloaded/src/socket.erl index 2bab42267a..95bd5ce094 100644 --- a/erts/preloaded/src/socket.erl +++ b/erts/preloaded/src/socket.erl @@ -640,7 +640,7 @@ %% -define(SOCKET_OPT_IPV6_ESP_TRANS_LEVEL, 8). %% -define(SOCKET_OPT_IPV6_ESP_NETWORK_LEVEL, 9). %% -define(SOCKET_OPT_IPV6_FAITH, 10). -%% -define(SOCKET_OPT_IPV6_FLOWINFO, 11). +-define(SOCKET_OPT_IPV6_FLOWINFO, 11). -define(SOCKET_OPT_IPV6_HOPLIMIT, 12). -define(SOCKET_OPT_IPV6_HOPOPTS, 13). %% -define(SOCKET_OPT_IPV6_IPCOMP_LEVEL, 14). @@ -2263,6 +2263,9 @@ enc_setopt_value(ipv6, drop_membership, #{multiaddr := MA, when ((is_tuple(MA) andalso (size(MA) =:= 8)) andalso (is_integer(IF) andalso (IF >= 0))) -> V; +enc_setopt_value(ipv6, flowinfo, V, _D, T, _P) + when is_boolean(V) andalso ((T =:= dgram) orelse (T =:= raw)) -> + V; enc_setopt_value(ipv6, hoplimit, V, _D, T, _P) when is_boolean(V) andalso ((T =:= dgram) orelse (T =:= raw)) -> V; @@ -2702,9 +2705,9 @@ enc_sockopt_key(ipv6 = L, esp_trans_level = Opt, _Dir, _D, _T, _P) -> not_supported({L, Opt}); enc_sockopt_key(ipv6 = L, esp_network_level = Opt, _Dir, _D, _T, _P) -> not_supported({L, Opt}); -enc_sockopt_key(ipv6 = L, flowinfo = Opt, _Dir, _D, T, _P) +enc_sockopt_key(ipv6 = _L, flowinfo = _Opt, _Dir, _D, T, _P) when (T =:= dgram) orelse (T =:= raw) -> - not_supported({L, Opt}); + ?SOCKET_OPT_IPV6_DSTOPTS; enc_sockopt_key(ipv6, hoplimit = _Opt, _Dir, _D, T, _P) when (T =:= dgram) orelse (T =:= raw) -> ?SOCKET_OPT_IPV6_HOPLIMIT; diff --git a/lib/kernel/test/socket_server.erl b/lib/kernel/test/socket_server.erl index 8436b39372..a1be73593b 100644 --- a/lib/kernel/test/socket_server.erl +++ b/lib/kernel/test/socket_server.erl @@ -166,7 +166,7 @@ do_manager_init(Domain, dgram = Type, Proto, Peek) -> addr => Addr}, i("try bind to: " "~n ~p", [Addr]), - case socket:bind(Sock, SA) of + case socket:bind(Sock, any) of {ok, _P} -> ok; {error, BReason} -> @@ -638,6 +638,7 @@ handler_init(Manager, ID, Peek, Sock) -> HopLimit = GIP6(hoplimit), HopOpts = GIP6(hopopts), DstOpts = GIP6(dstopts), + FlowInfo = GIP6(flowinfo), i("got continue when: " "~n (socket) Domain: ~p" "~n (socket) Type: ~p" @@ -674,7 +675,8 @@ handler_init(Manager, ID, Peek, Sock) -> "~n (ipv6) Auth Hdr: ~s" "~n (ipv6) Hop Limit: ~s" "~n (ipv6) Hop Opts: ~s" - "~n (ipv6) Dst Opts: ~s", + "~n (ipv6) Dst Opts: ~s" + "~n (ipv6) Flow Info: ~s", [Domain, Type, Proto, RA, RP, B2D, OOBI, RcvBuf, RcvLW, RcvTO, SndBuf, SndLW, SndTO, @@ -682,7 +684,7 @@ handler_init(Manager, ID, Peek, Sock) -> FreeBind, MTU, MTUDisc, MALL, MIF4, MLoop4, MTTL, NF, RecvIF, RecvOPTS, RecvTOS, RecvTTL, MHops, MIF6, MLoop6, RecvPktInfo, - RtHdr, AuthHdr, HopLimit, HopOpts, DstOpts]), + RtHdr, AuthHdr, HopLimit, HopOpts, DstOpts, FlowInfo]), handler_loop(#handler{peek = Peek, manager = Manager, -- cgit v1.2.3 From b598160c2f1162658ea948284aee5b53951a3b9e Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Tue, 24 Jul 2018 12:29:22 +0200 Subject: [socket-nif] Add support for socket (level ipv6) option unicast_hops Added support for the IPv6 socket option UNICAST_HOPS. OTP-14831. --- erts/doc/src/socket_usage.xml | 7 ++++++ erts/emulator/nifs/common/socket_nif.c | 44 +++++++++++++++++++++++++++++++++ erts/preloaded/ebin/socket.beam | Bin 57524 -> 57664 bytes erts/preloaded/src/socket.erl | 12 ++++++--- lib/kernel/test/socket_server.erl | 9 ++++--- 5 files changed, 66 insertions(+), 6 deletions(-) diff --git a/erts/doc/src/socket_usage.xml b/erts/doc/src/socket_usage.xml index 09a65c7152..383aebdab1 100644 --- a/erts/doc/src/socket_usage.xml +++ b/erts/doc/src/socket_usage.xml @@ -509,6 +509,13 @@ yes type = dgram | raw, requires superuser privileges to update + + unicast_hops + default | uint8() + yes + yes + none + v6only boolean() diff --git a/erts/emulator/nifs/common/socket_nif.c b/erts/emulator/nifs/common/socket_nif.c index 6a2904f75f..5aba0bca03 100644 --- a/erts/emulator/nifs/common/socket_nif.c +++ b/erts/emulator/nifs/common/socket_nif.c @@ -549,6 +549,7 @@ typedef union { #define SOCKET_OPT_IPV6_MULTICAST_LOOP 21 #define SOCKET_OPT_IPV6_RECVPKTINFO 25 // PKTINFO on FreeBSD #define SOCKET_OPT_IPV6_RTHDR 28 +#define SOCKET_OPT_IPV6_UNICAST_HOPS 30 #define SOCKET_OPT_IPV6_V6ONLY 32 #define SOCKET_OPT_TCP_CONGESTION 1 @@ -1270,6 +1271,11 @@ static ERL_NIF_TERM nsetopt_lvl_ipv6_rthdr(ErlNifEnv* env, SocketDescriptor* descP, ERL_NIF_TERM eVal); #endif +#if defined(IPV6_UNICAST_HOPS) +static ERL_NIF_TERM nsetopt_lvl_ipv6_unicast_hops(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); +#endif #if defined(IPV6_V6ONLY) static ERL_NIF_TERM nsetopt_lvl_ipv6_v6only(ErlNifEnv* env, SocketDescriptor* descP, @@ -1595,6 +1601,10 @@ static ERL_NIF_TERM ngetopt_lvl_ipv6_recvpktinfo(ErlNifEnv* env, static ERL_NIF_TERM ngetopt_lvl_ipv6_rthdr(ErlNifEnv* env, SocketDescriptor* descP); #endif +#if defined(IPV6_UNICAST_HOPS) +static ERL_NIF_TERM ngetopt_lvl_ipv6_unicast_hops(ErlNifEnv* env, + SocketDescriptor* descP); +#endif #if defined(IPV6_V6ONLY) static ERL_NIF_TERM ngetopt_lvl_ipv6_v6only(ErlNifEnv* env, SocketDescriptor* descP); @@ -5667,6 +5677,12 @@ ERL_NIF_TERM nsetopt_lvl_ipv6(ErlNifEnv* env, break; #endif +#if defined(IPV6_UNICAST_HOPS) + case SOCKET_OPT_IPV6_UNICAST_HOPS: + result = nsetopt_lvl_ipv6_unicast_hops(env, descP, eVal); + break; +#endif + #if defined(IPV6_V6ONLY) case SOCKET_OPT_IPV6_V6ONLY: result = nsetopt_lvl_ipv6_v6only(env, descP, eVal); @@ -5878,6 +5894,18 @@ ERL_NIF_TERM nsetopt_lvl_ipv6_rthdr(ErlNifEnv* env, #endif +#if defined(IPV6_UNICAST_HOPS) +static +ERL_NIF_TERM nsetopt_lvl_ipv6_unicast_hops(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) +{ + return nsetopt_int_opt(env, descP, SOL_IPV6, IPV6_UNICAST_HOPS, eVal); +} +#endif + + + #if defined(IPV6_V6ONLY) static ERL_NIF_TERM nsetopt_lvl_ipv6_v6only(ErlNifEnv* env, @@ -8277,6 +8305,12 @@ ERL_NIF_TERM ngetopt_lvl_ipv6(ErlNifEnv* env, break; #endif +#if defined(IPV6_UNICAST_HOPS) + case SOCKET_OPT_IPV6_UNICAST_HOPS: + result = ngetopt_lvl_ipv6_unicast_hops(env, descP); + break; +#endif + #if defined(IPV6_V6ONLY) case SOCKET_OPT_IPV6_V6ONLY: result = ngetopt_lvl_ipv6_v6only(env, descP); @@ -8442,6 +8476,16 @@ ERL_NIF_TERM ngetopt_lvl_ipv6_rthdr(ErlNifEnv* env, #endif +#if defined(IPV6_UNICAST_HOPS) +static +ERL_NIF_TERM ngetopt_lvl_ipv6_unicast_hops(ErlNifEnv* env, + SocketDescriptor* descP) +{ + return ngetopt_int_opt(env, descP, SOL_IPV6, IPV6_UNICAST_HOPS); +} +#endif + + #if defined(IPV6_V6ONLY) static ERL_NIF_TERM ngetopt_lvl_ipv6_v6only(ErlNifEnv* env, diff --git a/erts/preloaded/ebin/socket.beam b/erts/preloaded/ebin/socket.beam index f0788f2378..5ab64f442f 100644 Binary files a/erts/preloaded/ebin/socket.beam and b/erts/preloaded/ebin/socket.beam differ diff --git a/erts/preloaded/src/socket.erl b/erts/preloaded/src/socket.erl index 95bd5ce094..3bc3c7dc28 100644 --- a/erts/preloaded/src/socket.erl +++ b/erts/preloaded/src/socket.erl @@ -659,7 +659,7 @@ %% -define(SOCKET_OPT_IPV6_ROUTER_ALERT, 27). -define(SOCKET_OPT_IPV6_RTHDR, 28). %% -define(SOCKET_OPT_IPV6_TCLASS, 29). -%% -define(SOCKET_OPT_IPV6_UNICAST_HOPS, 30). +-define(SOCKET_OPT_IPV6_UNICAST_HOPS, 30). %% -define(SOCKET_OPT_IPV6_USE_MIN_MTU, 31). -define(SOCKET_OPT_IPV6_V6ONLY, 32). @@ -2300,6 +2300,12 @@ enc_setopt_value(ipv6, Opt, V, _D, _T, _P) enc_setopt_value(ipv6, rthdr, V, _D, T, _P) when is_boolean(V) andalso ((T =:= dgram) orelse (T =:= raw)) -> V; +enc_setopt_value(ipv6, unicast_hops, V, _D, _T, _P) + when (V =:= default) -> + -1; +enc_setopt_value(ipv6, unicast_hops, V, _D, _T, _P) + when is_integer(V) andalso (V >= 0) andalso (V =< 255) -> + V; enc_setopt_value(ipv6, v6only, V, _D, _T, _P) when is_boolean(V) -> V; enc_setopt_value(ipv6 = L, Opt, V, _D, _T, _P) -> @@ -2750,8 +2756,8 @@ enc_sockopt_key(ipv6 = _L, rthdr = _Opt, _Dir, _D, T, _P) ?SOCKET_OPT_IPV6_RTHDR; enc_sockopt_key(ipv6 = L, tclass = Opt, _Dir, _D, _T, _P) -> not_supported({L, Opt}); -enc_sockopt_key(ipv6 = L, unicast_hops = Opt, _Dir, _D, _T, _P) -> - not_supported({L, Opt}); +enc_sockopt_key(ipv6 = _L, unicast_hops = _Opt, _Dir, _D, _T, _P) -> + ?SOCKET_OPT_IPV6_UNICAST_HOPS; enc_sockopt_key(ipv6 = L, use_min_mtu = Opt, _Dir, _D, _T, _P) -> not_supported({L, Opt}); enc_sockopt_key(ipv6 = _L, v6only = _Opt, _Dir, _D, _T, _P) -> diff --git a/lib/kernel/test/socket_server.erl b/lib/kernel/test/socket_server.erl index a1be73593b..2cb505cabc 100644 --- a/lib/kernel/test/socket_server.erl +++ b/lib/kernel/test/socket_server.erl @@ -166,7 +166,7 @@ do_manager_init(Domain, dgram = Type, Proto, Peek) -> addr => Addr}, i("try bind to: " "~n ~p", [Addr]), - case socket:bind(Sock, any) of + case socket:bind(Sock, SA) of {ok, _P} -> ok; {error, BReason} -> @@ -639,6 +639,7 @@ handler_init(Manager, ID, Peek, Sock) -> HopOpts = GIP6(hopopts), DstOpts = GIP6(dstopts), FlowInfo = GIP6(flowinfo), + UHops = GIP6(unicast_hops), i("got continue when: " "~n (socket) Domain: ~p" "~n (socket) Type: ~p" @@ -676,7 +677,8 @@ handler_init(Manager, ID, Peek, Sock) -> "~n (ipv6) Hop Limit: ~s" "~n (ipv6) Hop Opts: ~s" "~n (ipv6) Dst Opts: ~s" - "~n (ipv6) Flow Info: ~s", + "~n (ipv6) Flow Info: ~s" + "~n (ipv6) Unicast Hops: ~s", [Domain, Type, Proto, RA, RP, B2D, OOBI, RcvBuf, RcvLW, RcvTO, SndBuf, SndLW, SndTO, @@ -684,7 +686,8 @@ handler_init(Manager, ID, Peek, Sock) -> FreeBind, MTU, MTUDisc, MALL, MIF4, MLoop4, MTTL, NF, RecvIF, RecvOPTS, RecvTOS, RecvTTL, MHops, MIF6, MLoop6, RecvPktInfo, - RtHdr, AuthHdr, HopLimit, HopOpts, DstOpts, FlowInfo]), + RtHdr, AuthHdr, HopLimit, HopOpts, DstOpts, FlowInfo, + UHops]), handler_loop(#handler{peek = Peek, manager = Manager, -- cgit v1.2.3 From 7d5b6e7bf640eb5d64679e3bf7b440b8e21e3a4d Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Tue, 24 Jul 2018 15:03:36 +0200 Subject: [socket-nif] Add support for socket (level ipv6) option router_alert Added support for the IPv6 socket option ROUTER_ALERT. Only supported for raw sockets. OTP-14831. --- erts/doc/src/socket_usage.xml | 7 ++ erts/emulator/nifs/common/socket_nif.c | 138 +++++++++++++++++++++++++++------ erts/preloaded/ebin/socket.beam | Bin 57664 -> 57952 bytes erts/preloaded/src/socket.erl | 25 ++++-- lib/kernel/test/socket_server.erl | 11 +++ 5 files changed, 151 insertions(+), 30 deletions(-) diff --git a/erts/doc/src/socket_usage.xml b/erts/doc/src/socket_usage.xml index 383aebdab1..1423571f5c 100644 --- a/erts/doc/src/socket_usage.xml +++ b/erts/doc/src/socket_usage.xml @@ -502,6 +502,13 @@ yes type = dgram | raw + + router_alert + integer() + yes + yes + type = raw + rthdr boolean() diff --git a/erts/emulator/nifs/common/socket_nif.c b/erts/emulator/nifs/common/socket_nif.c index 5aba0bca03..4d78e89a4f 100644 --- a/erts/emulator/nifs/common/socket_nif.c +++ b/erts/emulator/nifs/common/socket_nif.c @@ -470,6 +470,8 @@ typedef union { #define SOCKET_PROTOCOL_TCP 2 #define SOCKET_PROTOCOL_UDP 3 #define SOCKET_PROTOCOL_SCTP 4 +#define SOCKET_PROTOCOL_ICMP 5 +#define SOCKET_PROTOCOL_IGMP 6 /* shutdown how */ #define SOCKET_SHUTDOWN_HOW_RD 0 @@ -548,6 +550,7 @@ typedef union { #define SOCKET_OPT_IPV6_MULTICAST_IF 20 #define SOCKET_OPT_IPV6_MULTICAST_LOOP 21 #define SOCKET_OPT_IPV6_RECVPKTINFO 25 // PKTINFO on FreeBSD +#define SOCKET_OPT_IPV6_ROUTER_ALERT 27 #define SOCKET_OPT_IPV6_RTHDR 28 #define SOCKET_OPT_IPV6_UNICAST_HOPS 30 #define SOCKET_OPT_IPV6_V6ONLY 32 @@ -1266,6 +1269,11 @@ static ERL_NIF_TERM nsetopt_lvl_ipv6_recvpktinfo(ErlNifEnv* env, SocketDescriptor* descP, ERL_NIF_TERM eVal); #endif +#if defined(IPV6_ROUTER_ALERT) +static ERL_NIF_TERM nsetopt_lvl_ipv6_router_alert(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); +#endif #if defined(IPV6_RTHDR) static ERL_NIF_TERM nsetopt_lvl_ipv6_rthdr(ErlNifEnv* env, SocketDescriptor* descP, @@ -1597,6 +1605,10 @@ static ERL_NIF_TERM ngetopt_lvl_ipv6_multicast_loop(ErlNifEnv* env, static ERL_NIF_TERM ngetopt_lvl_ipv6_recvpktinfo(ErlNifEnv* env, SocketDescriptor* descP); #endif +#if defined(IPV6_ROUTER_ALERT) +static ERL_NIF_TERM ngetopt_lvl_ipv6_router_alert(ErlNifEnv* env, + SocketDescriptor* descP); +#endif #if defined(IPV6_RTHDR) static ERL_NIF_TERM ngetopt_lvl_ipv6_rthdr(ErlNifEnv* env, SocketDescriptor* descP); @@ -1896,7 +1908,9 @@ static int compare_pids(ErlNifEnv* env, static BOOLEAN_T edomain2domain(int edomain, int* domain); static BOOLEAN_T etype2type(int etype, int* type); -static BOOLEAN_T eproto2proto(int eproto, int* proto); +static BOOLEAN_T eproto2proto(ErlNifEnv* env, + const ERL_NIF_TERM eproto, + int* proto); static BOOLEAN_T ehow2how(unsigned int ehow, int* how); static BOOLEAN_T esendflags2sendflags(unsigned int esendflags, int* sendflags); static BOOLEAN_T erecvflags2recvflags(unsigned int erecvflags, int* recvflags); @@ -2344,18 +2358,18 @@ ERL_NIF_TERM nif_open(ErlNifEnv* env, if ((argc != 4) || !GET_INT(env, argv[0], &edomain) || !GET_INT(env, argv[1], &etype) || - !GET_INT(env, argv[2], &eproto) || !IS_MAP(env, argv[3])) { return enif_make_badarg(env); } - emap = argv[3]; + eproto = argv[2]; + emap = argv[3]; SGDBG( ("SOCKET", "nif_open -> " "\r\n edomain: %T" "\r\n etype: %T" "\r\n eproto: %T" "\r\n extra: %T" - "\r\n", argv[0], argv[1], argv[2], argv[3]) ); + "\r\n", argv[0], argv[1], eproto, emap) ); if (!edomain2domain(edomain, &domain)) { SGDBG( ("SOCKET", "nif_open -> domain: %d\r\n", domain) ); @@ -2367,7 +2381,7 @@ ERL_NIF_TERM nif_open(ErlNifEnv* env, return esock_make_error(env, esock_atom_einval); } - if (!eproto2proto(eproto, &proto)) { + if (!eproto2proto(env, eproto, &proto)) { SGDBG( ("SOCKET", "nif_open -> protocol: %d\r\n", proto) ); return esock_make_error(env, esock_atom_einval); } @@ -5671,6 +5685,12 @@ ERL_NIF_TERM nsetopt_lvl_ipv6(ErlNifEnv* env, break; #endif +#if defined(IPV6_ROUTER_ALERT) + case SOCKET_OPT_IPV6_ROUTER_ALERT: + result = nsetopt_lvl_ipv6_router_alert(env, descP, eVal); + break; +#endif + #if defined(IPV6_RTHDR) case SOCKET_OPT_IPV6_RTHDR: result = nsetopt_lvl_ipv6_rthdr(env, descP, eVal); @@ -5883,6 +5903,18 @@ ERL_NIF_TERM nsetopt_lvl_ipv6_recvpktinfo(ErlNifEnv* env, #endif +#if defined(IPV6_ROUTER_ALERT) +static +ERL_NIF_TERM nsetopt_lvl_ipv6_router_alert(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) +{ + return nsetopt_int_opt(env, descP, SOL_IPV6, IPV6_ROUTER_ALERT, eVal); +} +#endif + + + #if defined(IPV6_RTHDR) static ERL_NIF_TERM nsetopt_lvl_ipv6_rthdr(ErlNifEnv* env, @@ -8299,6 +8331,12 @@ ERL_NIF_TERM ngetopt_lvl_ipv6(ErlNifEnv* env, break; #endif +#if defined(IPV6_ROUTER_ALERT) + case SOCKET_OPT_IPV6_ROUTER_ALERT: + result = ngetopt_lvl_ipv6_router_alert(env, descP); + break; +#endif + #if defined(IPV6_RTHDR) case SOCKET_OPT_IPV6_RTHDR: result = ngetopt_lvl_ipv6_rthdr(env, descP); @@ -8466,6 +8504,16 @@ ERL_NIF_TERM ngetopt_lvl_ipv6_recvpktinfo(ErlNifEnv* env, #endif +#if defined(IPV6_ROUTER_ALERT) +static +ERL_NIF_TERM ngetopt_lvl_ipv6_router_alert(ErlNifEnv* env, + SocketDescriptor* descP) +{ + return ngetopt_int_opt(env, descP, SOL_IPV6, IPV6_ROUTER_ALERT); +} +#endif + + #if defined(IPV6_RTHDR) static ERL_NIF_TERM ngetopt_lvl_ipv6_rthdr(ErlNifEnv* env, @@ -8479,7 +8527,7 @@ ERL_NIF_TERM ngetopt_lvl_ipv6_rthdr(ErlNifEnv* env, #if defined(IPV6_UNICAST_HOPS) static ERL_NIF_TERM ngetopt_lvl_ipv6_unicast_hops(ErlNifEnv* env, - SocketDescriptor* descP) + SocketDescriptor* descP) { return ngetopt_int_opt(env, descP, SOL_IPV6, IPV6_UNICAST_HOPS); } @@ -10235,30 +10283,72 @@ BOOLEAN_T etype2type(int etype, int* type) * Note that only a subset is supported. */ static -BOOLEAN_T eproto2proto(int eproto, int* proto) +BOOLEAN_T eproto2proto(ErlNifEnv* env, + ERL_NIF_TERM eproto, + int* proto) { - switch (eproto) { - case SOCKET_PROTOCOL_IP: - *proto = IPPROTO_IP; - break; - - case SOCKET_PROTOCOL_TCP: - *proto = IPPROTO_TCP; - break; + if (IS_NUM(env, eproto)) { + int ep; - case SOCKET_PROTOCOL_UDP: - *proto = IPPROTO_UDP; - break; + if (!GET_INT(env, eproto, &ep)) { + *proto = -1; + return FALSE; + } + switch (ep) { + case SOCKET_PROTOCOL_IP: + *proto = IPPROTO_IP; + break; + + case SOCKET_PROTOCOL_TCP: + *proto = IPPROTO_TCP; + break; + + case SOCKET_PROTOCOL_UDP: + *proto = IPPROTO_UDP; + break; + #if defined(HAVE_SCTP) - case SOCKET_PROTOCOL_SCTP: - *proto = IPPROTO_SCTP; - break; + case SOCKET_PROTOCOL_SCTP: + *proto = IPPROTO_SCTP; + break; #endif + + case SOCKET_PROTOCOL_ICMP: + *proto = IPPROTO_ICMP; + break; + + case SOCKET_PROTOCOL_IGMP: + *proto = IPPROTO_IGMP; + break; + + default: + *proto = -2; + return FALSE; + } + } else { + const ERL_NIF_TERM* a; + int sz; - default: - *proto = -1; - return FALSE; + if (!GET_TUPLE(env, eproto, &sz, &a)) { + *proto = -3; + return FALSE; + } + + if (sz != 2) { + *proto = -4; + return FALSE; + } + + if (COMPARE(a[0], esock_atom_raw) != 0) { + *proto = -5; + return FALSE; + } + + if (!GET_INT(env, a[1], proto)) { + *proto = -6; + return FALSE; + } } return TRUE; diff --git a/erts/preloaded/ebin/socket.beam b/erts/preloaded/ebin/socket.beam index 5ab64f442f..cdc403055b 100644 Binary files a/erts/preloaded/ebin/socket.beam and b/erts/preloaded/ebin/socket.beam differ diff --git a/erts/preloaded/src/socket.erl b/erts/preloaded/src/socket.erl index 3bc3c7dc28..82a51ee245 100644 --- a/erts/preloaded/src/socket.erl +++ b/erts/preloaded/src/socket.erl @@ -128,7 +128,10 @@ -type type() :: stream | dgram | raw | rdm | seqpacket. %% We support only a subset of all protocols: --type protocol() :: ip | tcp | udp | sctp. +%% Note that the '{raw, integer()}' construct is intended +%% to be used with type = raw. +%% Note also that only the "superuser" can create a raw socket. +-type protocol() :: ip | tcp | udp | sctp | icmp | igmp | {raw, integer()}. -type port_number() :: 0..65535. @@ -525,6 +528,8 @@ -define(SOCKET_PROTOCOL_TCP, 2). -define(SOCKET_PROTOCOL_UDP, 3). -define(SOCKET_PROTOCOL_SCTP, 4). +-define(SOCKET_PROTOCOL_ICMP, 5). +-define(SOCKET_PROTOCOL_IGMP, 6). -define(SOCKET_LISTEN_BACKLOG_DEFAULT, 5). @@ -656,7 +661,7 @@ %% -define(SOCKET_OPT_IPV6_RECVERR, 24). -define(SOCKET_OPT_IPV6_RECVPKTINFO, 25). % On FreeBSD: PKTINFO %% -define(SOCKET_OPT_IPV6_RECVTCLASS, 26). -%% -define(SOCKET_OPT_IPV6_ROUTER_ALERT, 27). +-define(SOCKET_OPT_IPV6_ROUTER_ALERT, 27). -define(SOCKET_OPT_IPV6_RTHDR, 28). %% -define(SOCKET_OPT_IPV6_TCLASS, 29). -define(SOCKET_OPT_IPV6_UNICAST_HOPS, 30). @@ -2004,7 +2009,8 @@ enc_type(_, raw) -> ?SOCKET_TYPE_RAW; enc_type(_, seqpacket) -> ?SOCKET_TYPE_SEQPACKET; enc_type(_, Type) -> throw({error, {invalid_type, Type}}). --spec enc_protocol(Type, Protocol) -> non_neg_integer() when +-spec enc_protocol(Type, Protocol) -> non_neg_integer() | + {raw, non_neg_integer()} when Type :: type(), Protocol :: protocol(). @@ -2012,7 +2018,11 @@ enc_protocol(dgram, ip) -> ?SOCKET_PROTOCOL_IP; enc_protocol(stream, tcp) -> ?SOCKET_PROTOCOL_TCP; enc_protocol(dgram, udp) -> ?SOCKET_PROTOCOL_UDP; enc_protocol(seqpacket, sctp) -> ?SOCKET_PROTOCOL_SCTP; -enc_protocol(Type, Proto) -> throw({error, {invalid_protocol, {Type, Proto}}}). +enc_protocol(raw, icmp) -> ?SOCKET_PROTOCOL_ICMP; +enc_protocol(raw, igmp) -> ?SOCKET_PROTOCOL_IGMP; +enc_protocol(raw, {raw, P} = RAW) when is_integer(P) -> RAW; +enc_protocol(Type, Proto) -> + throw({error, {invalid_protocol, {Type, Proto}}}). -spec enc_send_flags(Flags) -> non_neg_integer() when @@ -2297,6 +2307,9 @@ enc_setopt_value(ipv6, Opt, V, _D, _T, _P) when ((Opt =:= recvpktinfo) orelse (Opt =:= pktinfo)) andalso is_boolean(V) -> V; +enc_setopt_value(ipv6, router_alert, V, _D, T, _P) + when is_integer(V) andalso (T =:= raw) -> + V; enc_setopt_value(ipv6, rthdr, V, _D, T, _P) when is_boolean(V) andalso ((T =:= dgram) orelse (T =:= raw)) -> V; @@ -2749,8 +2762,8 @@ enc_sockopt_key(ipv6 = _L, Opt, _Dir, _D, T, _P) ?SOCKET_OPT_IPV6_RECVPKTINFO; enc_sockopt_key(ipv6 = L, recvtclass = Opt, _Dir, _D, _T, _P) -> not_supported({L, Opt}); -enc_sockopt_key(ipv6 = L, router_alert = Opt, _Dir, _D, _T, _P) -> - not_supported({L, Opt}); +enc_sockopt_key(ipv6 = _L, router_alert = _Opt, _Dir, _D, T, _P) when (T =:= raw) -> + ?SOCKET_OPT_IPV6_ROUTER_ALERT; enc_sockopt_key(ipv6 = _L, rthdr = _Opt, _Dir, _D, T, _P) when ((T =:= dgram) orelse (T =:= raw)) -> ?SOCKET_OPT_IPV6_RTHDR; diff --git a/lib/kernel/test/socket_server.erl b/lib/kernel/test/socket_server.erl index 2cb505cabc..54e9244d19 100644 --- a/lib/kernel/test/socket_server.erl +++ b/lib/kernel/test/socket_server.erl @@ -247,6 +247,17 @@ do_manager_init(Domain, seqpacket = Type, sctp = Proto, _Peek) -> EXP(close_socket, ok, socket:close(Sock)); {error, Reason} -> exit({failed_open, Reason}) + end; +do_manager_init(Domain, raw = Type, Proto, Peek) when is_integer(Proto) -> + do_manager_init(Domain, Type, {raw, Proto}, Peek); +do_manager_init(Domain, raw = Type, Proto, _Peek) -> + case socket:open(Domain, Type, Proto) of + {ok, Sock} -> + i("(sctp) socket opened: " + "~n ~p", [Sock]), + socket:close(Sock); + {error, Reason} -> + exit({failed_open, Reason}) end. -- cgit v1.2.3 From b9237c96b2b86c82bb128625cc532b3528222560 Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Tue, 24 Jul 2018 16:11:32 +0200 Subject: [socket-nif] Add support for socket (level ipv6) option addrform Added support for the IPv6 socket option ADDRFORM. Only allowed for IPv6 sockets that are connected and bound to a v4-mapped-on-v6 address. OTP-14831. --- erts/doc/src/socket_usage.xml | 8 +++++ erts/emulator/nifs/common/socket_nif.c | 54 +++++++++++++++++++++++++++++++++ erts/preloaded/ebin/socket.beam | Bin 57952 -> 58020 bytes erts/preloaded/src/socket.erl | 8 +++-- 4 files changed, 67 insertions(+), 3 deletions(-) diff --git a/erts/doc/src/socket_usage.xml b/erts/doc/src/socket_usage.xml index 1423571f5c..8480af315e 100644 --- a/erts/doc/src/socket_usage.xml +++ b/erts/doc/src/socket_usage.xml @@ -411,6 +411,14 @@ Get Other + + addrform + inet + yes + no + allowed only for IPv6 sockets that are connected and bound to a + v4-mapped-on-v6 address + add_membership ipv6_mreq() diff --git a/erts/emulator/nifs/common/socket_nif.c b/erts/emulator/nifs/common/socket_nif.c index 4d78e89a4f..f7053d77fa 100644 --- a/erts/emulator/nifs/common/socket_nif.c +++ b/erts/emulator/nifs/common/socket_nif.c @@ -537,6 +537,7 @@ typedef union { #define SOCKET_OPT_IP_TTL 32 #define SOCKET_OPT_IP_UNBLOCK_SOURCE 33 +#define SOCKET_OPT_IPV6_ADDRFORM 1 #define SOCKET_OPT_IPV6_ADD_MEMBERSHIP 2 #define SOCKET_OPT_IPV6_AUTHHDR 3 #define SOCKET_OPT_IPV6_DROP_MEMBERSHIP 6 @@ -1204,6 +1205,11 @@ static ERL_NIF_TERM nsetopt_lvl_ipv6(ErlNifEnv* env, SocketDescriptor* descP, int eOpt, ERL_NIF_TERM eVal); +#if defined(IPV6_ADDRFORM) +static ERL_NIF_TERM nsetopt_lvl_ipv6_addrform(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); +#endif #if defined(IPV6_ADD_MEMBERSHIP) static ERL_NIF_TERM nsetopt_lvl_ipv6_add_membership(ErlNifEnv* env, SocketDescriptor* descP, @@ -5607,6 +5613,12 @@ ERL_NIF_TERM nsetopt_lvl_ipv6(ErlNifEnv* env, "\r\n", eOpt) ); switch (eOpt) { +#if defined(IPV6_ADDRFORM) + case SOCKET_OPT_IPV6_ADDRFORM: + result = nsetopt_lvl_ipv6_addrform(env, descP, eVal); + break; +#endif + #if defined(IPV6_ADD_MEMBERSHIP) case SOCKET_OPT_IPV6_ADD_MEMBERSHIP: result = nsetopt_lvl_ipv6_add_membership(env, descP, eVal); @@ -5725,6 +5737,48 @@ ERL_NIF_TERM nsetopt_lvl_ipv6(ErlNifEnv* env, } +#if defined(IPV6_ADDRFORM) +static +ERL_NIF_TERM nsetopt_lvl_ipv6_addrform(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) +{ + ERL_NIF_TERM result; + int res, edomain, domain; + + SSDBG( descP, + ("SOCKET", "nsetopt_lvl_ipv6_addrform -> entry with" + "\r\n eVal: %T" + "\r\n", eVal) ); + + if (!GET_INT(env, eVal, &edomain)) + return esock_make_error(env, esock_atom_einval); + + SSDBG( descP, + ("SOCKET", "nsetopt_lvl_ipv6_addrform -> decode" + "\r\n edomain: %d" + "\r\n", edomain) ); + + if (!edomain2domain(edomain, &domain)) + return esock_make_error(env, esock_atom_einval); + + SSDBG( descP, ("SOCKET", "nsetopt_lvl_ipv6_addrform -> try set opt to %d\r\n", + domain) ); + + res = socket_setopt(descP->sock, + SOL_IPV6, IPV6_ADDRFORM, + &domain, sizeof(domain)); + + if (res != 0) + result = esock_make_error_errno(env, sock_errno()); + else + result = esock_atom_ok; + + return result; +} +#endif + + #if defined(IPV6_ADD_MEMBERSHIP) static ERL_NIF_TERM nsetopt_lvl_ipv6_add_membership(ErlNifEnv* env, diff --git a/erts/preloaded/ebin/socket.beam b/erts/preloaded/ebin/socket.beam index cdc403055b..20f2cf659b 100644 Binary files a/erts/preloaded/ebin/socket.beam and b/erts/preloaded/ebin/socket.beam differ diff --git a/erts/preloaded/src/socket.erl b/erts/preloaded/src/socket.erl index 82a51ee245..0c3a17b54d 100644 --- a/erts/preloaded/src/socket.erl +++ b/erts/preloaded/src/socket.erl @@ -635,7 +635,7 @@ -define(SOCKET_OPT_IP_TTL, 32). -define(SOCKET_OPT_IP_UNBLOCK_SOURCE, 33). -%% -define(SOCKET_OPT_IPV6_ADDFORM, 1). +-define(SOCKET_OPT_IPV6_ADDRFORM, 1). -define(SOCKET_OPT_IPV6_ADD_MEMBERSHIP, 2). -define(SOCKET_OPT_IPV6_AUTHHDR, 3). % Obsolete? %% -define(SOCKET_OPT_IPV6_AUTH_LEVEL, 4). @@ -2254,6 +2254,8 @@ enc_setopt_value(ip, unblock_source, #{multiaddr := MA, enc_setopt_value(ip = L, Opt, V, _D, _T, _P) -> not_supported({L, Opt, V}); +enc_setopt_value(ipv6, addrform, inet = V, _D, _T, _P) -> + enc_domain(V); enc_setopt_value(ipv6, add_membership, #{multiaddr := MA, interface := IF} = V, _D, _T, _P) when ((is_tuple(MA) andalso (size(MA) =:= 8)) andalso @@ -2704,8 +2706,8 @@ enc_sockopt_key(ip = L, UnknownOpt, _Dir, _D, _T, _P) -> unknown({L, UnknownOpt}); %% IPv6 socket options -enc_sockopt_key(ipv6 = L, addrform = Opt, set = _Dir, _D, _T, _P) -> - not_supported({L, Opt}); +enc_sockopt_key(ipv6 = _L, addrform = _Opt, set = _Dir, _D, _T, _P) -> + ?SOCKET_OPT_IPV6_ADDRFORM; enc_sockopt_key(ipv6, add_membership = _Opt, set = _Dir, _D, _T, _P) -> ?SOCKET_OPT_IPV6_ADD_MEMBERSHIP; enc_sockopt_key(ipv6 = _L, authhdr = _Opt, _Dir, _D, T, _P) -- cgit v1.2.3 From 00a2425bde77ddb9ae4c03b8c4e5470064773981 Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Tue, 24 Jul 2018 16:40:55 +0200 Subject: [socket-nif] Add support for socket (level ip) option recverr Added support for the IP socket option RECVERR. To actually make use of this option, we need the recvmsg function, which we don't have yet. Baby steps. OTP-14831. --- erts/doc/src/socket_usage.xml | 9 ++++- erts/emulator/nifs/common/socket_nif.c | 59 +++++++++++++++++++++++++++++++++ erts/preloaded/ebin/socket.beam | Bin 58020 -> 58076 bytes erts/preloaded/src/socket.erl | 33 +++++++++--------- lib/kernel/test/socket_server.erl | 4 ++- 5 files changed, 88 insertions(+), 17 deletions(-) diff --git a/erts/doc/src/socket_usage.xml b/erts/doc/src/socket_usage.xml index 8480af315e..b791567c22 100644 --- a/erts/doc/src/socket_usage.xml +++ b/erts/doc/src/socket_usage.xml @@ -339,7 +339,7 @@ multicast_ttl - 0..255 + uint8() yes yes none @@ -351,6 +351,13 @@ yes type = raw + + recverr + boolean() + yes + yes + none + recvif boolean() diff --git a/erts/emulator/nifs/common/socket_nif.c b/erts/emulator/nifs/common/socket_nif.c index f7053d77fa..86a81c882b 100644 --- a/erts/emulator/nifs/common/socket_nif.c +++ b/erts/emulator/nifs/common/socket_nif.c @@ -528,6 +528,7 @@ typedef union { #define SOCKET_OPT_IP_MULTICAST_LOOP 15 #define SOCKET_OPT_IP_MULTICAST_TTL 16 #define SOCKET_OPT_IP_NODEFRAG 17 +#define SOCKET_OPT_IP_RECVERR 20 #define SOCKET_OPT_IP_RECVIF 21 #define SOCKET_OPT_IP_RECVOPTS 23 #define SOCKET_OPT_IP_RECVTOS 25 @@ -1142,6 +1143,11 @@ static ERL_NIF_TERM nsetopt_lvl_ip_nodefrag(ErlNifEnv* env, SocketDescriptor* descP, ERL_NIF_TERM eVal); #endif +#if defined(IP_RECVERR) +static ERL_NIF_TERM nsetopt_lvl_ip_recverr(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); +#endif #if defined(IP_RECVIF) static ERL_NIF_TERM nsetopt_lvl_ip_recvif(ErlNifEnv* env, SocketDescriptor* descP, @@ -1535,6 +1541,10 @@ static ERL_NIF_TERM ngetopt_lvl_ip_multicast_ttl(ErlNifEnv* env, static ERL_NIF_TERM ngetopt_lvl_ip_nodefrag(ErlNifEnv* env, SocketDescriptor* descP); #endif +#if defined(IP_RECVERR) +static ERL_NIF_TERM ngetopt_lvl_ip_recverr(ErlNifEnv* env, + SocketDescriptor* descP); +#endif #if defined(IP_RECVIF) static ERL_NIF_TERM ngetopt_lvl_ip_recvif(ErlNifEnv* env, SocketDescriptor* descP); @@ -4950,6 +4960,12 @@ ERL_NIF_TERM nsetopt_lvl_ip(ErlNifEnv* env, break; #endif +#if defined(IP_RECVERR) + case SOCKET_OPT_IP_RECVERR: + result = nsetopt_lvl_ip_recverr(env, descP, eVal); + break; +#endif + #if defined(IP_RECVIF) case SOCKET_OPT_IP_RECVIF: result = nsetopt_lvl_ip_recvif(env, descP, eVal); @@ -5308,6 +5324,25 @@ ERL_NIF_TERM nsetopt_lvl_ip_nodefrag(ErlNifEnv* env, #endif +/* nsetopt_lvl_ip_recverr - Level IP RECVERR option + */ +#if defined(IP_RECVERR) +static +ERL_NIF_TERM nsetopt_lvl_ip_recverr(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) +{ +#if defined(SOL_IP) + int level = SOL_IP; +#else + int level = IPPROTO_IP; +#endif + + return nsetopt_bool_opt(env, descP, level, IP_RECVERR, eVal); +} +#endif + + /* nsetopt_lvl_ip_recvif - Level IP RECVIF option */ #if defined(IP_RECVIF) @@ -7910,6 +7945,12 @@ ERL_NIF_TERM ngetopt_lvl_ip(ErlNifEnv* env, break; #endif +#if defined(IP_RECVERR) + case SOCKET_OPT_IP_RECVERR: + result = ngetopt_lvl_ip_recverr(env, descP); + break; +#endif + #if defined(IP_RECVIF) case SOCKET_OPT_IP_RECVIF: result = ngetopt_lvl_ip_recvif(env, descP); @@ -8200,6 +8241,24 @@ ERL_NIF_TERM ngetopt_lvl_ip_recvif(ErlNifEnv* env, #endif +/* ngetopt_lvl_ip_recverr - Level IP RECVERR option + */ +#if defined(IP_RECVERR) +static +ERL_NIF_TERM ngetopt_lvl_ip_recverr(ErlNifEnv* env, + SocketDescriptor* descP) +{ +#if defined(SOL_IP) + int level = SOL_IP; +#else + int level = IPPROTO_IP; +#endif + + return ngetopt_bool_opt(env, descP, level, IP_RECVERR); +} +#endif + + /* ngetopt_lvl_ip_recvopt - Level IP RECVOPTS option */ #if defined(IP_RECVOPTS) diff --git a/erts/preloaded/ebin/socket.beam b/erts/preloaded/ebin/socket.beam index 20f2cf659b..e2b988f8a6 100644 Binary files a/erts/preloaded/ebin/socket.beam and b/erts/preloaded/ebin/socket.beam differ diff --git a/erts/preloaded/src/socket.erl b/erts/preloaded/src/socket.erl index 0c3a17b54d..0a101008c6 100644 --- a/erts/preloaded/src/socket.erl +++ b/erts/preloaded/src/socket.erl @@ -620,7 +620,7 @@ -define(SOCKET_OPT_IP_NODEFRAG, 17). %% -define(SOCKET_OPT_IP_OPTIONS, 18). %% -define(SOCKET_OPT_IP_PKTINFO, 19). -%% -define(SOCKET_OPT_IP_RECVERR, 20). +-define(SOCKET_OPT_IP_RECVERR, 20). -define(SOCKET_OPT_IP_RECVIF, 21). %% -define(SOCKET_OPT_IP_RECVDSTADDR, 22). -define(SOCKET_OPT_IP_RECVOPTS, 23). @@ -638,34 +638,34 @@ -define(SOCKET_OPT_IPV6_ADDRFORM, 1). -define(SOCKET_OPT_IPV6_ADD_MEMBERSHIP, 2). -define(SOCKET_OPT_IPV6_AUTHHDR, 3). % Obsolete? -%% -define(SOCKET_OPT_IPV6_AUTH_LEVEL, 4). -%% -define(SOCKET_OPT_IPV6_CHECKSUM, 5). +%% -define(SOCKET_OPT_IPV6_AUTH_LEVEL, 4). % FreeBSD +%% -define(SOCKET_OPT_IPV6_CHECKSUM, 5). % FreeBSD -define(SOCKET_OPT_IPV6_DROP_MEMBERSHIP, 6). -define(SOCKET_OPT_IPV6_DSTOPTS, 7). -%% -define(SOCKET_OPT_IPV6_ESP_TRANS_LEVEL, 8). -%% -define(SOCKET_OPT_IPV6_ESP_NETWORK_LEVEL, 9). -%% -define(SOCKET_OPT_IPV6_FAITH, 10). +%% -define(SOCKET_OPT_IPV6_ESP_TRANS_LEVEL, 8). % FreeBSD +%% -define(SOCKET_OPT_IPV6_ESP_NETWORK_LEVEL, 9). % FreeBSD +%% -define(SOCKET_OPT_IPV6_FAITH, 10). % FreeBSD -define(SOCKET_OPT_IPV6_FLOWINFO, 11). -define(SOCKET_OPT_IPV6_HOPLIMIT, 12). -define(SOCKET_OPT_IPV6_HOPOPTS, 13). -%% -define(SOCKET_OPT_IPV6_IPCOMP_LEVEL, 14). -%% -define(SOCKET_OPT_IPV6_JOIN_GROUP, 15). -%% -define(SOCKET_OPT_IPV6_LEAVE_GROUP, 16). +%% -define(SOCKET_OPT_IPV6_IPCOMP_LEVEL, 14). % FreeBSD +%% -define(SOCKET_OPT_IPV6_JOIN_GROUP, 15). % FreeBSD +%% -define(SOCKET_OPT_IPV6_LEAVE_GROUP, 16). % FreeBSD -define(SOCKET_OPT_IPV6_MTU, 17). -define(SOCKET_OPT_IPV6_MTU_DISCOVER, 18). -define(SOCKET_OPT_IPV6_MULTICAST_HOPS, 19). -define(SOCKET_OPT_IPV6_MULTICAST_IF, 20). -define(SOCKET_OPT_IPV6_MULTICAST_LOOP, 21). -%% -define(SOCKET_OPT_IPV6_PORTRANGE, 22). -%% -define(SOCKET_OPT_IPV6_PKTOPTIONS, 23). +%% -define(SOCKET_OPT_IPV6_PORTRANGE, 22). % FreeBSD +%% -define(SOCKET_OPT_IPV6_PKTOPTIONS, 23). % FreeBSD %% -define(SOCKET_OPT_IPV6_RECVERR, 24). -define(SOCKET_OPT_IPV6_RECVPKTINFO, 25). % On FreeBSD: PKTINFO %% -define(SOCKET_OPT_IPV6_RECVTCLASS, 26). -define(SOCKET_OPT_IPV6_ROUTER_ALERT, 27). -define(SOCKET_OPT_IPV6_RTHDR, 28). -%% -define(SOCKET_OPT_IPV6_TCLASS, 29). +%% -define(SOCKET_OPT_IPV6_TCLASS, 29). % FreeBSD -define(SOCKET_OPT_IPV6_UNICAST_HOPS, 30). -%% -define(SOCKET_OPT_IPV6_USE_MIN_MTU, 31). +%% -define(SOCKET_OPT_IPV6_USE_MIN_MTU, 31). % FreeBSD -define(SOCKET_OPT_IPV6_V6ONLY, 32). -define(SOCKET_OPT_TCP_CONGESTION, 1). @@ -2219,6 +2219,9 @@ enc_setopt_value(ip, multicast_ttl, V, _D, _T, _P) enc_setopt_value(ip, nodefrag, V, _D, _T, _P) when is_boolean(V) -> V; +enc_setopt_value(ip, recverr, V, _D, _T, _P) + when is_boolean(V) -> + V; enc_setopt_value(ip, recvif, V, _D, _T, _P) when is_boolean(V) -> V; @@ -2672,8 +2675,8 @@ enc_sockopt_key(ip = L, pktinfo = Opt, _Dir, _D, _T, _P) -> not_supported({L, Opt}); %% This require special code for accessing the errors. %% via calling the recvmsg with the MSG_ERRQUEUE flag set, -enc_sockopt_key(ip = L, recverr = Opt, _Dir, _D, _T, _P) -> - not_supported({L, Opt}); +enc_sockopt_key(ip = _L, recverr = _Opt, _Dir, _D, _T, _P) -> + ?SOCKET_OPT_IP_RECVERR; enc_sockopt_key(ip = _L, recvif = _Opt, _Dir, _D, T, _P) when (T =:= dgram) orelse (T =:= raw) -> ?SOCKET_OPT_IP_RECVIF; diff --git a/lib/kernel/test/socket_server.erl b/lib/kernel/test/socket_server.erl index 54e9244d19..43f6f5ac75 100644 --- a/lib/kernel/test/socket_server.erl +++ b/lib/kernel/test/socket_server.erl @@ -636,6 +636,7 @@ handler_init(Manager, ID, Peek, Sock) -> MLoop4 = GIP4(multicast_loop), MTTL = GIP4(multicast_ttl), NF = GIP4(nodefrag), % raw only + RecvErr4 = GIP4(recverr), RecvIF = GIP4(recvif), % Only dgram and raw (and FreeBSD) RecvOPTS = GIP4(recvopts), % Not stream RecvTOS = GIP4(recvtos), @@ -675,6 +676,7 @@ handler_init(Manager, ID, Peek, Sock) -> "~n (ip) Multicast Loop: ~s" "~n (ip) Multicast TTL: ~s" "~n (ip) Node Frag: ~s" + "~n (ip) Recv Err: ~s" "~n (ip) Recv IF: ~s" "~n (ip) Recv OPTS: ~s" "~n (ip) Recv TOS: ~s" @@ -695,7 +697,7 @@ handler_init(Manager, ID, Peek, Sock) -> RcvBuf, RcvLW, RcvTO, SndBuf, SndLW, SndTO, Linger, Timestamp, FreeBind, MTU, MTUDisc, MALL, MIF4, MLoop4, MTTL, - NF, RecvIF, RecvOPTS, RecvTOS, RecvTTL, + NF, RecvErr4, RecvIF, RecvOPTS, RecvTOS, RecvTTL, MHops, MIF6, MLoop6, RecvPktInfo, RtHdr, AuthHdr, HopLimit, HopOpts, DstOpts, FlowInfo, UHops]), -- cgit v1.2.3 From 4e24993aff4c5a0cb2bec96e5499131a660a79f9 Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Tue, 24 Jul 2018 17:01:46 +0200 Subject: [socket-nif] Add support for socket (level ipv6) option recverr Added support for the IPv6 socket option RECVERR. To actually make use of this option, we need the recvmsg function, which we don't have yet. Baby steps. OTP-14831. --- erts/doc/src/socket_usage.xml | 7 ++++++ erts/emulator/nifs/common/socket_nif.c | 43 +++++++++++++++++++++++++++++++++ erts/preloaded/ebin/socket.beam | Bin 58076 -> 58080 bytes erts/preloaded/src/socket.erl | 11 ++++++--- lib/kernel/test/socket_server.erl | 4 ++- 5 files changed, 60 insertions(+), 5 deletions(-) diff --git a/erts/doc/src/socket_usage.xml b/erts/doc/src/socket_usage.xml index b791567c22..30b855f925 100644 --- a/erts/doc/src/socket_usage.xml +++ b/erts/doc/src/socket_usage.xml @@ -510,6 +510,13 @@ yes none + + recverr + boolean() + yes + yes + none + recvpktinfo | pktinfo boolean() diff --git a/erts/emulator/nifs/common/socket_nif.c b/erts/emulator/nifs/common/socket_nif.c index 86a81c882b..3f87232000 100644 --- a/erts/emulator/nifs/common/socket_nif.c +++ b/erts/emulator/nifs/common/socket_nif.c @@ -551,6 +551,7 @@ typedef union { #define SOCKET_OPT_IPV6_MULTICAST_HOPS 19 #define SOCKET_OPT_IPV6_MULTICAST_IF 20 #define SOCKET_OPT_IPV6_MULTICAST_LOOP 21 +#define SOCKET_OPT_IPV6_RECVERR 24 #define SOCKET_OPT_IPV6_RECVPKTINFO 25 // PKTINFO on FreeBSD #define SOCKET_OPT_IPV6_ROUTER_ALERT 27 #define SOCKET_OPT_IPV6_RTHDR 28 @@ -1276,6 +1277,11 @@ static ERL_NIF_TERM nsetopt_lvl_ipv6_multicast_loop(ErlNifEnv* env, SocketDescriptor* descP, ERL_NIF_TERM eVal); #endif +#if defined(IPV6_RECVERR) +static ERL_NIF_TERM nsetopt_lvl_ipv6_recverr(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); +#endif #if defined(IPV6_RECVPKTINFO) || defined(IPV6_PKTINFO) static ERL_NIF_TERM nsetopt_lvl_ipv6_recvpktinfo(ErlNifEnv* env, SocketDescriptor* descP, @@ -1617,6 +1623,10 @@ static ERL_NIF_TERM ngetopt_lvl_ipv6_multicast_if(ErlNifEnv* env, static ERL_NIF_TERM ngetopt_lvl_ipv6_multicast_loop(ErlNifEnv* env, SocketDescriptor* descP); #endif +#if defined(IPV6_RECVERR) +static ERL_NIF_TERM ngetopt_lvl_ipv6_recverr(ErlNifEnv* env, + SocketDescriptor* descP); +#endif #if defined(IPV6_RECVPKTINFO) || defined(IPV6_PKTINFO) static ERL_NIF_TERM ngetopt_lvl_ipv6_recvpktinfo(ErlNifEnv* env, SocketDescriptor* descP); @@ -5726,6 +5736,12 @@ ERL_NIF_TERM nsetopt_lvl_ipv6(ErlNifEnv* env, break; #endif +#if defined(IPV6_RECVERR) + case SOCKET_OPT_IPV6_RECVERR: + result = nsetopt_lvl_ipv6_recverr(env, descP, eVal); + break; +#endif + #if defined(IPV6_RECVPKTINFO) || defined(IPV6_PKTINFO) case SOCKET_OPT_IPV6_RECVPKTINFO: result = nsetopt_lvl_ipv6_recvpktinfo(env, descP, eVal); @@ -5975,6 +5991,17 @@ ERL_NIF_TERM nsetopt_lvl_ipv6_multicast_loop(ErlNifEnv* env, #endif +#if defined(IPV6_RECVERR) +static +ERL_NIF_TERM nsetopt_lvl_ipv6_recverr(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) +{ + return nsetopt_bool_opt(env, descP, SOL_IPV6, IPV6_RECVERR, eVal); +} +#endif + + #if defined(IPV6_RECVPKTINFO) || defined(IPV6_PKTINFO) static ERL_NIF_TERM nsetopt_lvl_ipv6_recvpktinfo(ErlNifEnv* env, @@ -8438,6 +8465,12 @@ ERL_NIF_TERM ngetopt_lvl_ipv6(ErlNifEnv* env, break; #endif +#if defined(IPV6_RECVERR) + case SOCKET_OPT_IPV6_RECVERR: + result = ngetopt_lvl_ipv6_recverr(env, descP); + break; +#endif + #if defined(IPV6_RECVPKTINFO) || defined(IPV6_PKTINFO) case SOCKET_OPT_IPV6_RECVPKTINFO: result = ngetopt_lvl_ipv6_recvpktinfo(env, descP); @@ -8601,6 +8634,16 @@ ERL_NIF_TERM ngetopt_lvl_ipv6_multicast_loop(ErlNifEnv* env, #endif +#if defined(IPV6_RECVERR) +static +ERL_NIF_TERM ngetopt_lvl_ipv6_recverr(ErlNifEnv* env, + SocketDescriptor* descP) +{ + return ngetopt_bool_opt(env, descP, SOL_IPV6, IPV6_RECVERR); +} +#endif + + #if defined(IPV6_RECVPKTINFO) || defined(IPV6_PKTINFO) static ERL_NIF_TERM ngetopt_lvl_ipv6_recvpktinfo(ErlNifEnv* env, diff --git a/erts/preloaded/ebin/socket.beam b/erts/preloaded/ebin/socket.beam index e2b988f8a6..8ca75307bc 100644 Binary files a/erts/preloaded/ebin/socket.beam and b/erts/preloaded/ebin/socket.beam differ diff --git a/erts/preloaded/src/socket.erl b/erts/preloaded/src/socket.erl index 0a101008c6..5695e45ac5 100644 --- a/erts/preloaded/src/socket.erl +++ b/erts/preloaded/src/socket.erl @@ -658,7 +658,7 @@ -define(SOCKET_OPT_IPV6_MULTICAST_LOOP, 21). %% -define(SOCKET_OPT_IPV6_PORTRANGE, 22). % FreeBSD %% -define(SOCKET_OPT_IPV6_PKTOPTIONS, 23). % FreeBSD -%% -define(SOCKET_OPT_IPV6_RECVERR, 24). +-define(SOCKET_OPT_IPV6_RECVERR, 24). -define(SOCKET_OPT_IPV6_RECVPKTINFO, 25). % On FreeBSD: PKTINFO %% -define(SOCKET_OPT_IPV6_RECVTCLASS, 26). -define(SOCKET_OPT_IPV6_ROUTER_ALERT, 27). @@ -2308,6 +2308,9 @@ enc_setopt_value(ipv6, multicast_if, V, _D, _T, _P) enc_setopt_value(ipv6, multicast_loop, V, _D, _T, _P) when is_boolean(V) -> V; +enc_setopt_value(ipv6, recverr, V, _D, _T, _P) + when is_boolean(V) -> + V; enc_setopt_value(ipv6, Opt, V, _D, _T, _P) when ((Opt =:= recvpktinfo) orelse (Opt =:= pktinfo)) andalso is_boolean(V) -> @@ -2759,8 +2762,8 @@ enc_sockopt_key(ipv6 = L, portrange = Opt, _Dir, _D, _T, _P) -> not_supported({L, Opt}); enc_sockopt_key(ipv6 = L, pktoptions = Opt, _Dir, _D, _T, _P) -> not_supported({L, Opt}); -enc_sockopt_key(ipv6 = L, recverr = Opt, _Dir, _D, _T, _P) -> - not_supported({L, Opt}); +enc_sockopt_key(ipv6 = _L, recverr = _Opt, _Dir, _D, _T, _P) -> + ?SOCKET_OPT_IPV6_RECVERR; enc_sockopt_key(ipv6 = _L, Opt, _Dir, _D, T, _P) when ((Opt =:= recvpktinfo) orelse (Opt =:= pktinfo)) andalso ((T =:= dgram) orelse (T =:= raw)) -> @@ -2781,7 +2784,7 @@ enc_sockopt_key(ipv6 = L, use_min_mtu = Opt, _Dir, _D, _T, _P) -> enc_sockopt_key(ipv6 = _L, v6only = _Opt, _Dir, _D, _T, _P) -> ?SOCKET_OPT_IPV6_V6ONLY; enc_sockopt_key(ipv6 = L, UnknownOpt, _Dir, _D, _T, _P) -> - unknown({L, UnknownOpt, _Dir, _D, _T, _P}); + unknown({L, UnknownOpt}); %% TCP socket options %% There are other options that would be useful; info, diff --git a/lib/kernel/test/socket_server.erl b/lib/kernel/test/socket_server.erl index 43f6f5ac75..4b032c586c 100644 --- a/lib/kernel/test/socket_server.erl +++ b/lib/kernel/test/socket_server.erl @@ -644,6 +644,7 @@ handler_init(Manager, ID, Peek, Sock) -> MHops = GIP6(multicast_hops), MIF6 = GIP6(multicast_if), % Only dgram and raw MLoop6 = GIP6(multicast_loop), + RecvErr6 = GIP6(recverr), RecvPktInfo = GIP6(recvpktinfo), RtHdr = GIP6(rthdr), AuthHdr = GIP6(authhdr), @@ -684,6 +685,7 @@ handler_init(Manager, ID, Peek, Sock) -> "~n (ipv6) Multicast Hops: ~s" "~n (ipv6) Multicast IF: ~s" "~n (ipv6) Multicast Loop: ~s" + "~n (ipv6) Recv Err: ~s" "~n (ipv6) Recv Pkt Info: ~s" "~n (ipv6) RT Hdr: ~s" "~n (ipv6) Auth Hdr: ~s" @@ -698,7 +700,7 @@ handler_init(Manager, ID, Peek, Sock) -> Linger, Timestamp, FreeBind, MTU, MTUDisc, MALL, MIF4, MLoop4, MTTL, NF, RecvErr4, RecvIF, RecvOPTS, RecvTOS, RecvTTL, - MHops, MIF6, MLoop6, RecvPktInfo, + MHops, MIF6, MLoop6, RecvErr6, RecvPktInfo, RtHdr, AuthHdr, HopLimit, HopOpts, DstOpts, FlowInfo, UHops]), -- cgit v1.2.3 From 5d9de1cdc46c75117f15f1ab17f017cdb700eb4c Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Wed, 25 Jul 2018 11:28:49 +0200 Subject: [socket-nif] Add support for socket (level ip) option msfilter Added support for ip level socket option MSFILTER. This option has not been tested *in any way*... OTP-14831 --- erts/doc/src/socket.xml | 6 ++ erts/doc/src/socket_usage.xml | 7 ++ erts/emulator/nifs/common/socket_nif.c | 173 ++++++++++++++++++++++++++++++++- erts/preloaded/ebin/socket.beam | Bin 58080 -> 59008 bytes erts/preloaded/src/socket.erl | 40 +++++++- 5 files changed, 219 insertions(+), 7 deletions(-) diff --git a/erts/doc/src/socket.xml b/erts/doc/src/socket.xml index 14a21823b8..53d1516f1e 100644 --- a/erts/doc/src/socket.xml +++ b/erts/doc/src/socket.xml @@ -131,6 +131,12 @@ + + + + + + diff --git a/erts/doc/src/socket_usage.xml b/erts/doc/src/socket_usage.xml index 30b855f925..e7e27629ac 100644 --- a/erts/doc/src/socket_usage.xml +++ b/erts/doc/src/socket_usage.xml @@ -302,6 +302,13 @@ yes type = raw + + msfilter + null | ip_msfilter() + yes + no + none + mtu integer() diff --git a/erts/emulator/nifs/common/socket_nif.c b/erts/emulator/nifs/common/socket_nif.c index 3f87232000..ae14e6b4ab 100644 --- a/erts/emulator/nifs/common/socket_nif.c +++ b/erts/emulator/nifs/common/socket_nif.c @@ -521,6 +521,7 @@ typedef union { #define SOCKET_OPT_IP_DROP_SOURCE_MEMBERSHIP 6 #define SOCKET_OPT_IP_FREEBIND 7 #define SOCKET_OPT_IP_MINTTL 9 +#define SOCKET_OPT_IP_MSFILTER 10 #define SOCKET_OPT_IP_MTU 11 #define SOCKET_OPT_IP_MTU_DISCOVER 12 #define SOCKET_OPT_IP_MULTICAST_ALL 13 @@ -1114,6 +1115,18 @@ static ERL_NIF_TERM nsetopt_lvl_ip_minttl(ErlNifEnv* env, SocketDescriptor* descP, ERL_NIF_TERM eVal); #endif +#if defined(IP_MSFILTER) +static ERL_NIF_TERM nsetopt_lvl_ip_msfilter(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); +static BOOLEAN_T decode_ip_msfilter_mode(ErlNifEnv* env, + ERL_NIF_TERM eVal, + uint32_t* mode); +static ERL_NIF_TERM nsetopt_lvl_ip_msfilter_set(ErlNifEnv* env, + SOCKET sock, + struct ip_msfilter* msfP, + SOCKLEN_T optLen); +#endif #if defined(IP_MTU_DISCOVER) static ERL_NIF_TERM nsetopt_lvl_ip_mtu_discover(ErlNifEnv* env, SocketDescriptor* descP, @@ -2058,13 +2071,15 @@ static char str_cookie_life[] = "cookie_life"; static char str_data_in[] = "data_in"; static char str_do[] = "do"; static char str_dont[] = "dont"; +static char str_exclude[] = "exclude"; static char str_false[] = "false"; static char str_global_counters[] = "global_counters"; static char str_in4_sockaddr[] = "in4_sockaddr"; static char str_in6_sockaddr[] = "in6_sockaddr"; -static char str_iow[] = "iow"; +static char str_include[] = "include"; static char str_initial[] = "initial"; static char str_interface[] = "interface"; +static char str_iow[] = "iow"; static char str_local_rwnd[] = "local_rwnd"; // static char str_loopback[] = "loopback"; static char str_max[] = "max"; @@ -2073,8 +2088,10 @@ static char str_max_init_timeo[] = "max_init_timeo"; static char str_max_instreams[] = "max_instreams"; static char str_max_rxt[] = "max_rxt"; static char str_min[] = "min"; +static char str_mode[] = "mode"; static char str_multiaddr[] = "multiaddr"; static char str_nif_abort[] = "nif_abort"; +static char str_null[] = "null"; static char str_num_dlocal[] = "num_domain_local"; static char str_num_dinet[] = "num_domain_inet"; static char str_num_dinet6[] = "num_domain_inet6"; @@ -2097,6 +2114,7 @@ static char str_select[] = "select"; static char str_sender_dry[] = "sender_dry"; static char str_send_failure[] = "send_failure"; static char str_shutdown[] = "shutdown"; +static char str_slist[] = "slist"; static char str_sourceaddr[] = "sourceaddr"; static char str_timeout[] = "timeout"; static char str_true[] = "true"; @@ -2170,13 +2188,15 @@ static ERL_NIF_TERM atom_cookie_life; static ERL_NIF_TERM atom_data_in; static ERL_NIF_TERM atom_do; static ERL_NIF_TERM atom_dont; +static ERL_NIF_TERM atom_exclude; static ERL_NIF_TERM atom_false; static ERL_NIF_TERM atom_global_counters; static ERL_NIF_TERM atom_in4_sockaddr; static ERL_NIF_TERM atom_in6_sockaddr; -static ERL_NIF_TERM atom_iow; +static ERL_NIF_TERM atom_include; static ERL_NIF_TERM atom_initial; static ERL_NIF_TERM atom_interface; +static ERL_NIF_TERM atom_iow; static ERL_NIF_TERM atom_local_rwnd; static ERL_NIF_TERM atom_max; static ERL_NIF_TERM atom_max_attempts; @@ -2184,8 +2204,10 @@ static ERL_NIF_TERM atom_max_init_timeo; static ERL_NIF_TERM atom_max_instreams; static ERL_NIF_TERM atom_max_rxt; static ERL_NIF_TERM atom_min; +static ERL_NIF_TERM atom_mode; static ERL_NIF_TERM atom_multiaddr; static ERL_NIF_TERM atom_nif_abort; +static ERL_NIF_TERM atom_null; static ERL_NIF_TERM atom_num_dinet; static ERL_NIF_TERM atom_num_dinet6; static ERL_NIF_TERM atom_num_dlocal; @@ -2208,6 +2230,7 @@ static ERL_NIF_TERM atom_select; static ERL_NIF_TERM atom_sender_dry; static ERL_NIF_TERM atom_send_failure; static ERL_NIF_TERM atom_shutdown; +static ERL_NIF_TERM atom_slist; static ERL_NIF_TERM atom_sourceaddr; static ERL_NIF_TERM atom_timeout; static ERL_NIF_TERM atom_true; @@ -4934,6 +4957,12 @@ ERL_NIF_TERM nsetopt_lvl_ip(ErlNifEnv* env, break; #endif +#if defined(IP_MSFILTER) + case SOCKET_OPT_IP_MSFILTER: + result = nsetopt_lvl_ip_msfilter(env, descP, eVal); + break; +#endif + #if defined(IP_MTU_DISCOVER) case SOCKET_OPT_IP_MTU_DISCOVER: result = nsetopt_lvl_ip_mtu_discover(env, descP, eVal); @@ -5178,6 +5207,139 @@ ERL_NIF_TERM nsetopt_lvl_ip_minttl(ErlNifEnv* env, +/* nsetopt_lvl_ip_msfilter - Level IP MSFILTER option + * + * The value can be *either* the atom 'null' or a map of type ip_msfilter(). + */ +#if defined(IP_MSFILTER) +static +ERL_NIF_TERM nsetopt_lvl_ip_msfilter(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) +{ + ERL_NIF_TERM result; + + if (COMPARE(eVal, atom_null) == 0) { + return nsetopt_lvl_ip_msfilter_set(env, descP->sock, NULL, 0); + } else { + struct ip_msfilter* msfP; + uint32_t msfSz; + ERL_NIF_TERM eMultiAddr, eInterface, eFMode, eSList, elem, tail; + size_t sz; + unsigned int slistLen, idx; + + if (!IS_MAP(env, eVal)) + return esock_make_error(env, esock_atom_einval); + + // It must have atleast four attributes + if (!enif_get_map_size(env, eVal, &sz) || (sz < 4)) + return esock_make_error(env, esock_atom_einval); + + if (!GET_MAP_VAL(env, eVal, atom_multiaddr, &eMultiAddr)) + return esock_make_error(env, esock_atom_einval); + + if (!GET_MAP_VAL(env, eVal, atom_interface, &eInterface)) + return esock_make_error(env, esock_atom_einval); + + if (!GET_MAP_VAL(env, eVal, atom_mode, &eFMode)) + return esock_make_error(env, esock_atom_einval); + + if (!GET_MAP_VAL(env, eVal, atom_slist, &eSList)) + return esock_make_error(env, esock_atom_einval); + + /* We start (decoding) with the slist, since without it we don't + * really know how much (memory) to allocate. + */ + if (!GET_LIST_LEN(env, eSList, &slistLen)) + return esock_make_error(env, esock_atom_einval); + + msfSz = IP_MSFILTER_SIZE(slistLen); + msfP = MALLOC(msfSz); + + if (!esock_decode_ip4_address(env, eMultiAddr, &msfP->imsf_multiaddr)) { + FREE(msfP); + return esock_make_error(env, esock_atom_einval); + } + + if (!esock_decode_ip4_address(env, eInterface, &msfP->imsf_interface)) { + FREE(msfP); + return esock_make_error(env, esock_atom_einval); + } + + if (!decode_ip_msfilter_mode(env, eFMode, &msfP->imsf_fmode)) { + FREE(msfP); + return esock_make_error(env, esock_atom_einval); + } + + /* And finally, extract the source addresses */ + msfP->imsf_numsrc = slistLen; + for (idx = 0; idx < slistLen; idx++) { + if (GET_LIST_ELEM(env, eSList, &elem, &tail)) { + if (!esock_decode_ip4_address(env, elem, &msfP->imsf_slist[idx])) { + FREE(msfP); + return esock_make_error(env, esock_atom_einval); + } else { + eSList = tail; + } + } + } + + /* And now, finally, set the option */ + result = nsetopt_lvl_ip_msfilter_set(env, descP->sock, msfP, msfSz); + FREE(msfP); + return result; + } + +} + + +static +BOOLEAN_T decode_ip_msfilter_mode(ErlNifEnv* env, + ERL_NIF_TERM eVal, + uint32_t* mode) +{ + BOOLEAN_T result; + + if (COMPARE(eVal, atom_include) == 0) { + *mode = MCAST_INCLUDE; + result = TRUE; + } else if (COMPARE(eVal, atom_exclude) == 0) { + *mode = MCAST_EXCLUDE; + result = TRUE; + } else { + result = FALSE; + } + + return result; +} + + +static +ERL_NIF_TERM nsetopt_lvl_ip_msfilter_set(ErlNifEnv* env, + SOCKET sock, + struct ip_msfilter* msfP, + SOCKLEN_T optLen) +{ + ERL_NIF_TERM result; + int res; +#if defined(SOL_IP) + int level = SOL_IP; +#else + int level = IPPROTO_IP; +#endif + + res = socket_setopt(sock, level, IP_MSFILTER, (void*) msfP, optLen); + if (res != 0) + result = esock_make_error_errno(env, sock_errno()); + else + result = esock_atom_ok; + + return result; +} +#endif // IP_MSFILTER + + + /* nsetopt_lvl_ip_mtu_discover - Level IP MTU_DISCOVER option * * The value is an atom of the type ip_pmtudisc(). @@ -11441,13 +11603,15 @@ int on_load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info) atom_data_in = MKA(env, str_data_in); atom_do = MKA(env, str_do); atom_dont = MKA(env, str_dont); + atom_exclude = MKA(env, str_exclude); atom_false = MKA(env, str_false); atom_global_counters = MKA(env, str_global_counters); atom_in4_sockaddr = MKA(env, str_in4_sockaddr); atom_in6_sockaddr = MKA(env, str_in6_sockaddr); - atom_iow = MKA(env, str_iow); + atom_include = MKA(env, str_include); atom_initial = MKA(env, str_initial); atom_interface = MKA(env, str_interface); + atom_iow = MKA(env, str_iow); atom_local_rwnd = MKA(env, str_local_rwnd); atom_max = MKA(env, str_max); atom_max_attempts = MKA(env, str_max_attempts); @@ -11455,8 +11619,10 @@ int on_load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info) atom_max_instreams = MKA(env, str_max_instreams); atom_max_rxt = MKA(env, str_max_rxt); atom_min = MKA(env, str_min); + atom_mode = MKA(env, str_mode); atom_multiaddr = MKA(env, str_multiaddr); atom_nif_abort = MKA(env, str_nif_abort); + atom_null = MKA(env, str_null); atom_num_dinet = MKA(env, str_num_dinet); atom_num_dinet6 = MKA(env, str_num_dinet6); atom_num_dlocal = MKA(env, str_num_dlocal); @@ -11479,6 +11645,7 @@ int on_load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info) atom_sender_dry = MKA(env, str_sender_dry); atom_send_failure = MKA(env, str_send_failure); atom_shutdown = MKA(env, str_shutdown); + atom_slist = MKA(env, str_slist); atom_sourceaddr = MKA(env, str_sourceaddr); atom_timeout = MKA(env, str_timeout); atom_true = MKA(env, str_true); diff --git a/erts/preloaded/ebin/socket.beam b/erts/preloaded/ebin/socket.beam index 8ca75307bc..14ee41257b 100644 Binary files a/erts/preloaded/ebin/socket.beam and b/erts/preloaded/ebin/socket.beam differ diff --git a/erts/preloaded/src/socket.erl b/erts/preloaded/src/socket.erl index 5695e45ac5..acda4e3811 100644 --- a/erts/preloaded/src/socket.erl +++ b/erts/preloaded/src/socket.erl @@ -94,6 +94,8 @@ ip_mreq/0, ip_mreq_source/0, ip_pmtudisc/0, + ip_msfilter_mode/0, + ip_msfilter/0, ipv6_mreq/0, ipv6_pmtudisc/0, sctp_event_subscribe/0, @@ -196,6 +198,17 @@ -type ip_pmtudisc() :: want | dont | do | probe. +%% multiaddr: Multicast group address +%% interface: Address of local interface +%% mode: Filter mode +%% slist: List of source addresses +-type ip_msfilter_mode() :: include | exclude. + +-type ip_msfilter() :: #{multiaddr => ip4_address(), + interface => ip4_address(), + mode => ip_msfilter_mode(), + slist => [ip4_address()]}. + -type ipv6_mreq() :: #{multiaddr := ip6_address(), interface := non_neg_integer()}. @@ -604,13 +617,13 @@ -define(SOCKET_OPT_IP_ADD_MEMBERSHIP, 1). -define(SOCKET_OPT_IP_ADD_SOURCE_MEMBERSHIP, 2). -define(SOCKET_OPT_IP_BLOCK_SOURCE, 3). -%% -define(SOCKET_OPT_IP_DONTFRAG, 4). +%% -define(SOCKET_OPT_IP_DONTFRAG, 4). % FreeBSD -define(SOCKET_OPT_IP_DROP_MEMBERSHIP, 5). -define(SOCKET_OPT_IP_DROP_SOURCE_MEMBERSHIP, 6). -define(SOCKET_OPT_IP_FREEBIND, 7). %% -define(SOCKET_OPT_IP_HDRINCL, 8). -define(SOCKET_OPT_IP_MINTTL, 9). -%% -define(SOCKET_OPT_IP_MSFILTER, 10). +-define(SOCKET_OPT_IP_MSFILTER, 10). -define(SOCKET_OPT_IP_MTU, 11). -define(SOCKET_OPT_IP_MTU_DISCOVER, 12). -define(SOCKET_OPT_IP_MULTICAST_ALL, 13). @@ -2197,6 +2210,18 @@ enc_setopt_value(ip, freebind, V, _D, _T, _P) when is_boolean(V) -> V; enc_setopt_value(ip, minttl, V, _D, _T, _P) when is_integer(V) -> V; +enc_setopt_value(ip, msfilter, null = V, _D, _T, _P) -> + V; +enc_setopt_value(ip, msfilter, #{multiaddr := MA, + interface := IF, + fmode := FMode, + slist := SL} = V, _D, _T, _P) + when (is_tuple(MA) andalso (size(MA) =:= 4)) andalso + (is_tuple(IF) andalso (size(IF) =:= 4)) andalso + ((FMode =:= include) orelse (FMode =:= exclude)) andalso + is_list(SL) -> + ensure_ip_msfilter_slist(SL), + V; enc_setopt_value(ip, mtu_discover, V, _D, _T, _P) when (V =:= want) orelse (V =:= dont) orelse @@ -2656,8 +2681,8 @@ enc_sockopt_key(ip = L, hdrincl = Opt, _Dir, _D, raw = _T, _P) -> %% FreeBSD only? enc_sockopt_key(ip = _L, minttl = _Opt, _Dir, _D, raw = _T, _P) -> ?SOCKET_OPT_IP_MINTTL; -enc_sockopt_key(ip = L, msfilter = Opt, _Dir, _D, _T, _P) -> - not_supported({L, Opt}); +enc_sockopt_key(ip = _L, msfilter = _Opt, set = _Dir, _D, _T, _P) -> + ?SOCKET_OPT_IP_MSFILTER; enc_sockopt_key(ip = _L, mtu = _Opt, get = _Dir, _D, _T, _P) -> ?SOCKET_OPT_IP_MTU; enc_sockopt_key(ip = _L, mtu_discover = _Opt, _Dir, _D, _T, _P) -> @@ -2922,6 +2947,13 @@ enc_shutdown_how(read_write) -> %% %% =========================================================================== +ensure_ip_msfilter_slist(SL) -> + EnsureSA = fun(SA) when is_tuple(SA) andalso (size(SA) =:= 4) -> ok; + (_) -> einval() + end, + lists:foreach(EnsureSA, SL). + + ensure_sockaddr(#{family := inet} = SockAddr) -> maps:merge(?SOCKADDR_IN4_DEFAULTS, SockAddr); ensure_sockaddr(#{family := inet6} = SockAddr) -> -- cgit v1.2.3 From 587d3a9a76b6ef2c88b850d007d39d34c37b5825 Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Wed, 25 Jul 2018 12:00:34 +0200 Subject: [socket-nif] Add support for socket (level ip) option hdrincl Added support for ip level socket option HDRINCL. As this option is raw only, it has not yet been tested! OTP-14831 --- erts/doc/src/socket_usage.xml | 7 ++++ erts/emulator/nifs/common/socket_nif.c | 60 +++++++++++++++++++++++++++++++++ erts/preloaded/ebin/socket.beam | Bin 59008 -> 59016 bytes erts/preloaded/src/socket.erl | 11 +++--- 4 files changed, 73 insertions(+), 5 deletions(-) diff --git a/erts/doc/src/socket_usage.xml b/erts/doc/src/socket_usage.xml index e7e27629ac..2ddd51d1e6 100644 --- a/erts/doc/src/socket_usage.xml +++ b/erts/doc/src/socket_usage.xml @@ -295,6 +295,13 @@ yes none + + hdrincl + boolean() + yes + yes + type = raw + minttl integer() diff --git a/erts/emulator/nifs/common/socket_nif.c b/erts/emulator/nifs/common/socket_nif.c index ae14e6b4ab..19b40920d3 100644 --- a/erts/emulator/nifs/common/socket_nif.c +++ b/erts/emulator/nifs/common/socket_nif.c @@ -520,6 +520,7 @@ typedef union { #define SOCKET_OPT_IP_DROP_MEMBERSHIP 5 #define SOCKET_OPT_IP_DROP_SOURCE_MEMBERSHIP 6 #define SOCKET_OPT_IP_FREEBIND 7 +#define SOCKET_OPT_IP_HDRINCL 8 #define SOCKET_OPT_IP_MINTTL 9 #define SOCKET_OPT_IP_MSFILTER 10 #define SOCKET_OPT_IP_MTU 11 @@ -1110,6 +1111,11 @@ static ERL_NIF_TERM nsetopt_lvl_ip_freebind(ErlNifEnv* env, SocketDescriptor* descP, ERL_NIF_TERM eVal); #endif +#if defined(IP_HDRINCL) +static ERL_NIF_TERM nsetopt_lvl_ip_hdrincl(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); +#endif #if defined(IP_MINTTL) static ERL_NIF_TERM nsetopt_lvl_ip_minttl(ErlNifEnv* env, SocketDescriptor* descP, @@ -1528,6 +1534,10 @@ static ERL_NIF_TERM ngetopt_lvl_ip(ErlNifEnv* env, static ERL_NIF_TERM ngetopt_lvl_ip_freebind(ErlNifEnv* env, SocketDescriptor* descP); #endif +#if defined(IP_HDRINCL) +static ERL_NIF_TERM ngetopt_lvl_ip_hdrincl(ErlNifEnv* env, + SocketDescriptor* descP); +#endif #if defined(IP_MINTTL) static ERL_NIF_TERM ngetopt_lvl_ip_minttl(ErlNifEnv* env, SocketDescriptor* descP); @@ -4951,6 +4961,12 @@ ERL_NIF_TERM nsetopt_lvl_ip(ErlNifEnv* env, break; #endif +#if defined(IP_HDRINCL) + case SOCKET_OPT_IP_HDRINCL: + result = nsetopt_lvl_ip_hdrincl(env, descP, eVal); + break; +#endif + #if defined(IP_MINTTL) case SOCKET_OPT_IP_MINTTL: result = nsetopt_lvl_ip_minttl(env, descP, eVal); @@ -5187,6 +5203,26 @@ ERL_NIF_TERM nsetopt_lvl_ip_freebind(ErlNifEnv* env, +/* nsetopt_lvl_ip_hdrincl - Level IP HDRINCL option + */ +#if defined(IP_HDRINCL) +static +ERL_NIF_TERM nsetopt_lvl_ip_hdrincl(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) +{ +#if defined(SOL_IP) + int level = SOL_IP; +#else + int level = IPPROTO_IP; +#endif + + return nsetopt_bool_opt(env, descP, level, IP_HDRINCL, eVal); +} +#endif + + + /* nsetopt_lvl_ip_minttl - Level IP MINTTL option */ #if defined(IP_MINTTL) @@ -8086,6 +8122,12 @@ ERL_NIF_TERM ngetopt_lvl_ip(ErlNifEnv* env, break; #endif +#if defined(IP_HDRINCL) + case SOCKET_OPT_IP_HDRINCL: + result = ngetopt_lvl_ip_hdrincl(env, descP); + break; +#endif + #if defined(IP_MINTTL) case SOCKET_OPT_IP_MINTTL: result = ngetopt_lvl_ip_minttl(env, descP); @@ -8233,6 +8275,24 @@ ERL_NIF_TERM ngetopt_lvl_ip_freebind(ErlNifEnv* env, #endif +/* ngetopt_lvl_ip_hdrincl - Level IP HDRINCL option + */ +#if defined(IP_HDRINCL) +static +ERL_NIF_TERM ngetopt_lvl_ip_hdrincl(ErlNifEnv* env, + SocketDescriptor* descP) +{ +#if defined(SOL_IP) + int level = SOL_IP; +#else + int level = IPPROTO_IP; +#endif + + return ngetopt_bool_opt(env, descP, level, IP_HDRINCL); +} +#endif + + /* ngetopt_lvl_ip_mtu - Level IP MTU option */ #if defined(IP_MTU) diff --git a/erts/preloaded/ebin/socket.beam b/erts/preloaded/ebin/socket.beam index 14ee41257b..be5413e116 100644 Binary files a/erts/preloaded/ebin/socket.beam and b/erts/preloaded/ebin/socket.beam differ diff --git a/erts/preloaded/src/socket.erl b/erts/preloaded/src/socket.erl index acda4e3811..4028fa9191 100644 --- a/erts/preloaded/src/socket.erl +++ b/erts/preloaded/src/socket.erl @@ -621,7 +621,7 @@ -define(SOCKET_OPT_IP_DROP_MEMBERSHIP, 5). -define(SOCKET_OPT_IP_DROP_SOURCE_MEMBERSHIP, 6). -define(SOCKET_OPT_IP_FREEBIND, 7). -%% -define(SOCKET_OPT_IP_HDRINCL, 8). +-define(SOCKET_OPT_IP_HDRINCL, 8). -define(SOCKET_OPT_IP_MINTTL, 9). -define(SOCKET_OPT_IP_MSFILTER, 10). -define(SOCKET_OPT_IP_MTU, 11). @@ -631,7 +631,7 @@ -define(SOCKET_OPT_IP_MULTICAST_LOOP, 15). -define(SOCKET_OPT_IP_MULTICAST_TTL, 16). -define(SOCKET_OPT_IP_NODEFRAG, 17). -%% -define(SOCKET_OPT_IP_OPTIONS, 18). +%% -define(SOCKET_OPT_IP_OPTIONS, 18). % FreeBSD %% -define(SOCKET_OPT_IP_PKTINFO, 19). -define(SOCKET_OPT_IP_RECVERR, 20). -define(SOCKET_OPT_IP_RECVIF, 21). @@ -2208,6 +2208,8 @@ enc_setopt_value(ip, drop_source_membership, #{multiaddr := MA, V; enc_setopt_value(ip, freebind, V, _D, _T, _P) when is_boolean(V) -> V; +enc_setopt_value(ip, hdrincl, V, _D, _T, _P) when is_boolean(V) -> + V; enc_setopt_value(ip, minttl, V, _D, _T, _P) when is_integer(V) -> V; enc_setopt_value(ip, msfilter, null = V, _D, _T, _P) -> @@ -2676,9 +2678,8 @@ enc_sockopt_key(ip = _L, drop_source_membership = _Opt, set = _Dir, _D, _T, _P) %% Linux only? enc_sockopt_key(ip = _L, freebind = _Opt, _Dir, _D, _T, _P) -> ?SOCKET_OPT_IP_FREEBIND; -enc_sockopt_key(ip = L, hdrincl = Opt, _Dir, _D, raw = _T, _P) -> - not_supported({L, Opt}); -%% FreeBSD only? +enc_sockopt_key(ip = _L, hdrincl = _Opt, _Dir, _D, raw = _T, _P) -> + ?SOCKET_OPT_IP_HDRINCL; enc_sockopt_key(ip = _L, minttl = _Opt, _Dir, _D, raw = _T, _P) -> ?SOCKET_OPT_IP_MINTTL; enc_sockopt_key(ip = _L, msfilter = _Opt, set = _Dir, _D, _T, _P) -> -- cgit v1.2.3 From 70a5b8d6a01b91a6044c6a5a0f8ed8919afd509b Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Wed, 25 Jul 2018 12:29:41 +0200 Subject: [socket-nif] Add support for socket (level ip) option pktinfo Added support for ip level socket option PKTINFO. This option requires sendmsg and/or recvmsg to actually use, so we cannot test this fully at the moment (although both set and get works). OTP-14831 --- erts/doc/src/socket_usage.xml | 9 ++++- erts/emulator/nifs/common/socket_nif.c | 59 +++++++++++++++++++++++++++++++++ erts/preloaded/ebin/socket.beam | Bin 59016 -> 59056 bytes erts/preloaded/src/socket.erl | 9 +++-- lib/kernel/test/socket_server.erl | 17 ++++++++-- 5 files changed, 88 insertions(+), 6 deletions(-) diff --git a/erts/doc/src/socket_usage.xml b/erts/doc/src/socket_usage.xml index 2ddd51d1e6..443d4edec7 100644 --- a/erts/doc/src/socket_usage.xml +++ b/erts/doc/src/socket_usage.xml @@ -365,6 +365,13 @@ yes type = raw + + pktinfo + boolean() + yes + yes + type = dgram + recverr boolean() @@ -408,7 +415,7 @@ none - tos + ttl integer() yes yes diff --git a/erts/emulator/nifs/common/socket_nif.c b/erts/emulator/nifs/common/socket_nif.c index 19b40920d3..f4da7cdbb0 100644 --- a/erts/emulator/nifs/common/socket_nif.c +++ b/erts/emulator/nifs/common/socket_nif.c @@ -530,6 +530,7 @@ typedef union { #define SOCKET_OPT_IP_MULTICAST_LOOP 15 #define SOCKET_OPT_IP_MULTICAST_TTL 16 #define SOCKET_OPT_IP_NODEFRAG 17 +#define SOCKET_OPT_IP_PKTINFO 19 #define SOCKET_OPT_IP_RECVERR 20 #define SOCKET_OPT_IP_RECVIF 21 #define SOCKET_OPT_IP_RECVOPTS 23 @@ -1163,6 +1164,11 @@ static ERL_NIF_TERM nsetopt_lvl_ip_nodefrag(ErlNifEnv* env, SocketDescriptor* descP, ERL_NIF_TERM eVal); #endif +#if defined(IP_PKTINFO) +static ERL_NIF_TERM nsetopt_lvl_ip_pktinfo(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); +#endif #if defined(IP_RECVERR) static ERL_NIF_TERM nsetopt_lvl_ip_recverr(ErlNifEnv* env, SocketDescriptor* descP, @@ -1570,6 +1576,10 @@ static ERL_NIF_TERM ngetopt_lvl_ip_multicast_ttl(ErlNifEnv* env, static ERL_NIF_TERM ngetopt_lvl_ip_nodefrag(ErlNifEnv* env, SocketDescriptor* descP); #endif +#if defined(IP_PKTINFO) +static ERL_NIF_TERM ngetopt_lvl_ip_pktinfo(ErlNifEnv* env, + SocketDescriptor* descP); +#endif #if defined(IP_RECVERR) static ERL_NIF_TERM ngetopt_lvl_ip_recverr(ErlNifEnv* env, SocketDescriptor* descP); @@ -5015,6 +5025,12 @@ ERL_NIF_TERM nsetopt_lvl_ip(ErlNifEnv* env, break; #endif +#if defined(IP_PKTINFO) + case SOCKET_OPT_IP_PKTINFO: + result = nsetopt_lvl_ip_pktinfo(env, descP, eVal); + break; +#endif + #if defined(IP_RECVERR) case SOCKET_OPT_IP_RECVERR: result = nsetopt_lvl_ip_recverr(env, descP, eVal); @@ -5532,6 +5548,25 @@ ERL_NIF_TERM nsetopt_lvl_ip_nodefrag(ErlNifEnv* env, #endif +/* nsetopt_lvl_ip_pktinfo - Level IP PKTINFO option + */ +#if defined(IP_PKTINFO) +static +ERL_NIF_TERM nsetopt_lvl_ip_pktinfo(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) +{ +#if defined(SOL_IP) + int level = SOL_IP; +#else + int level = IPPROTO_IP; +#endif + + return nsetopt_bool_opt(env, descP, level, IP_PKTINFO, eVal); +} +#endif + + /* nsetopt_lvl_ip_recverr - Level IP RECVERR option */ #if defined(IP_RECVERR) @@ -8176,6 +8211,12 @@ ERL_NIF_TERM ngetopt_lvl_ip(ErlNifEnv* env, break; #endif +#if defined(IP_PKTINFO) + case SOCKET_OPT_IP_PKTINFO: + result = ngetopt_lvl_ip_pktinfo(env, descP); + break; +#endif + #if defined(IP_RECVERR) case SOCKET_OPT_IP_RECVERR: result = ngetopt_lvl_ip_recverr(env, descP); @@ -8454,6 +8495,24 @@ ERL_NIF_TERM ngetopt_lvl_ip_nodefrag(ErlNifEnv* env, #endif +/* ngetopt_lvl_ip_pktinfo - Level IP PKTINFO option + */ +#if defined(IP_PKTINFO) +static +ERL_NIF_TERM ngetopt_lvl_ip_pktinfo(ErlNifEnv* env, + SocketDescriptor* descP) +{ +#if defined(SOL_IP) + int level = SOL_IP; +#else + int level = IPPROTO_IP; +#endif + + return ngetopt_bool_opt(env, descP, level, IP_PKTINFO); +} +#endif + + /* ngetopt_lvl_ip_recvtos - Level IP RECVTOS option */ #if defined(IP_RECVTOS) diff --git a/erts/preloaded/ebin/socket.beam b/erts/preloaded/ebin/socket.beam index be5413e116..b5b18e1c3a 100644 Binary files a/erts/preloaded/ebin/socket.beam and b/erts/preloaded/ebin/socket.beam differ diff --git a/erts/preloaded/src/socket.erl b/erts/preloaded/src/socket.erl index 4028fa9191..28931e4ed0 100644 --- a/erts/preloaded/src/socket.erl +++ b/erts/preloaded/src/socket.erl @@ -632,7 +632,7 @@ -define(SOCKET_OPT_IP_MULTICAST_TTL, 16). -define(SOCKET_OPT_IP_NODEFRAG, 17). %% -define(SOCKET_OPT_IP_OPTIONS, 18). % FreeBSD -%% -define(SOCKET_OPT_IP_PKTINFO, 19). +-define(SOCKET_OPT_IP_PKTINFO, 19). -define(SOCKET_OPT_IP_RECVERR, 20). -define(SOCKET_OPT_IP_RECVIF, 21). %% -define(SOCKET_OPT_IP_RECVDSTADDR, 22). @@ -2246,6 +2246,9 @@ enc_setopt_value(ip, multicast_ttl, V, _D, _T, _P) enc_setopt_value(ip, nodefrag, V, _D, _T, _P) when is_boolean(V) -> V; +enc_setopt_value(ip, pktinfo, V, _D, _T, _P) + when is_boolean(V) -> + V; enc_setopt_value(ip, recverr, V, _D, _T, _P) when is_boolean(V) -> V; @@ -2700,8 +2703,8 @@ enc_sockopt_key(ip = _L, nodefrag = _Opt, _Dir, _D, raw = _T, _P) -> ?SOCKET_OPT_IP_NODEFRAG; enc_sockopt_key(ip = L, options = Opt, _Dir, _D, _T, _P) -> not_supported({Opt, L}); -enc_sockopt_key(ip = L, pktinfo = Opt, _Dir, _D, _T, _P) -> - not_supported({L, Opt}); +enc_sockopt_key(ip = _L, pktinfo = _Opt, _Dir, _D, dgram = _T, _P) -> + ?SOCKET_OPT_IP_PKTINFO; %% This require special code for accessing the errors. %% via calling the recvmsg with the MSG_ERRQUEUE flag set, enc_sockopt_key(ip = _L, recverr = _Opt, _Dir, _D, _T, _P) -> diff --git a/lib/kernel/test/socket_server.erl b/lib/kernel/test/socket_server.erl index 4b032c586c..0ae6bb2dd0 100644 --- a/lib/kernel/test/socket_server.erl +++ b/lib/kernel/test/socket_server.erl @@ -636,6 +636,7 @@ handler_init(Manager, ID, Peek, Sock) -> MLoop4 = GIP4(multicast_loop), MTTL = GIP4(multicast_ttl), NF = GIP4(nodefrag), % raw only + PktInfo = GIP4(pktinfo), % dgram only RecvErr4 = GIP4(recverr), RecvIF = GIP4(recvif), % Only dgram and raw (and FreeBSD) RecvOPTS = GIP4(recvopts), % Not stream @@ -677,6 +678,7 @@ handler_init(Manager, ID, Peek, Sock) -> "~n (ip) Multicast Loop: ~s" "~n (ip) Multicast TTL: ~s" "~n (ip) Node Frag: ~s" + "~n (ip) Pkt Info: ~s" "~n (ip) Recv Err: ~s" "~n (ip) Recv IF: ~s" "~n (ip) Recv OPTS: ~s" @@ -699,17 +701,28 @@ handler_init(Manager, ID, Peek, Sock) -> RcvBuf, RcvLW, RcvTO, SndBuf, SndLW, SndTO, Linger, Timestamp, FreeBind, MTU, MTUDisc, MALL, MIF4, MLoop4, MTTL, - NF, RecvErr4, RecvIF, RecvOPTS, RecvTOS, RecvTTL, + NF, PktInfo,RecvErr4, RecvIF, RecvOPTS, RecvTOS, RecvTTL, MHops, MIF6, MLoop6, RecvErr6, RecvPktInfo, RtHdr, AuthHdr, HopLimit, HopOpts, DstOpts, FlowInfo, UHops]), - + handler_loop(#handler{peek = Peek, manager = Manager, type = Type, socket = Sock}) end. +%% so(Sock, Lvl, Opt, Val) -> +%% ok = socket:setopt(Sock, Lvl, Opt, Val). + +%% soso(Sock, Opt, Val) -> +%% so(Sock, socket, Opt, Val). + +%% soip(Sock, Opt, Val) -> +%% so(Sock, ip, Opt, Val). + +%% soipv6(Sock, Opt, Val) -> +%% so(Sock, ipv6, Opt, Val). handler_loop(H) -> i("try read message"), -- cgit v1.2.3 From 673367a0c17349a8b57dfad5dbc349c68417c6a5 Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Wed, 25 Jul 2018 13:07:34 +0200 Subject: [socket-nif] Add support for socket (level ip) option transparent Added support for ip level socket option TRANSPARENT. OTP-14831 --- erts/doc/src/socket_usage.xml | 9 ++++- erts/emulator/nifs/common/socket_nif.c | 67 +++++++++++++++++++++++++++++++++ erts/preloaded/ebin/socket.beam | Bin 59056 -> 59092 bytes erts/preloaded/src/socket.erl | 9 +++-- lib/kernel/test/socket_server.erl | 7 ++++ 5 files changed, 88 insertions(+), 4 deletions(-) diff --git a/erts/doc/src/socket_usage.xml b/erts/doc/src/socket_usage.xml index 443d4edec7..b1fe22b231 100644 --- a/erts/doc/src/socket_usage.xml +++ b/erts/doc/src/socket_usage.xml @@ -412,7 +412,14 @@ ip_tos() yes yes - none + may require admin capability + + + transparent + boolean() + yes + yes + require admin capability ttl diff --git a/erts/emulator/nifs/common/socket_nif.c b/erts/emulator/nifs/common/socket_nif.c index f4da7cdbb0..9e9ac333e7 100644 --- a/erts/emulator/nifs/common/socket_nif.c +++ b/erts/emulator/nifs/common/socket_nif.c @@ -538,6 +538,7 @@ typedef union { #define SOCKET_OPT_IP_RECVTTL 26 #define SOCKET_OPT_IP_ROUTER_ALERT 28 #define SOCKET_OPT_IP_TOS 30 +#define SOCKET_OPT_IP_TRANSPARENT 31 #define SOCKET_OPT_IP_TTL 32 #define SOCKET_OPT_IP_UNBLOCK_SOURCE 33 @@ -1204,6 +1205,11 @@ static ERL_NIF_TERM nsetopt_lvl_ip_tos(ErlNifEnv* env, SocketDescriptor* descP, ERL_NIF_TERM eVal); #endif +#if defined(IP_TRANSPARENT) +static ERL_NIF_TERM nsetopt_lvl_ip_transparent(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); +#endif #if defined(IP_TTL) static ERL_NIF_TERM nsetopt_lvl_ip_ttl(ErlNifEnv* env, SocketDescriptor* descP, @@ -1608,6 +1614,10 @@ static ERL_NIF_TERM ngetopt_lvl_ip_router_alert(ErlNifEnv* env, static ERL_NIF_TERM ngetopt_lvl_ip_tos(ErlNifEnv* env, SocketDescriptor* descP); #endif +#if defined(IP_TRANSPARENT) +static ERL_NIF_TERM ngetopt_lvl_ip_transparent(ErlNifEnv* env, + SocketDescriptor* descP); +#endif #if defined(IP_TTL) static ERL_NIF_TERM ngetopt_lvl_ip_ttl(ErlNifEnv* env, SocketDescriptor* descP); @@ -5073,6 +5083,12 @@ ERL_NIF_TERM nsetopt_lvl_ip(ErlNifEnv* env, break; #endif +#if defined(IP_TRANSPARENT) + case SOCKET_OPT_IP_TRANSPARENT: + result = nsetopt_lvl_ip_transparent(env, descP, eVal); + break; +#endif + #if defined(IP_TTL) case SOCKET_OPT_IP_TTL: result = nsetopt_lvl_ip_ttl(env, descP, eVal); @@ -5086,10 +5102,16 @@ ERL_NIF_TERM nsetopt_lvl_ip(ErlNifEnv* env, #endif default: + SSDBG( descP, ("SOCKET", "nsetopt_lvl_ip -> unknown opt (%d)\r\n", eOpt) ); result = esock_make_error(env, esock_atom_einval); break; } + SSDBG( descP, + ("SOCKET", "nsetopt_lvl_ip -> done when" + "\r\n result: %T" + "\r\n", result) ); + return result; } @@ -5714,6 +5736,26 @@ ERL_NIF_TERM nsetopt_lvl_ip_tos(ErlNifEnv* env, #endif +/* nsetopt_lvl_ip_transparent - Level IP TRANSPARENT option + */ +#if defined(IP_TRANSPARENT) +static +ERL_NIF_TERM nsetopt_lvl_ip_transparent(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) +{ +#if defined(SOL_IP) + int level = SOL_IP; +#else + int level = IPPROTO_IP; +#endif + + return nsetopt_bool_opt(env, descP, level, IP_TRANSPARENT, eVal); +} +#endif + + + /* nsetopt_lvl_ip_ttl - Level IP TTL option */ #if defined(IP_TTL) @@ -8259,6 +8301,12 @@ ERL_NIF_TERM ngetopt_lvl_ip(ErlNifEnv* env, break; #endif +#if defined(IP_TRANSPARENT) + case SOCKET_OPT_IP_TRANSPARENT: + result = ngetopt_lvl_ip_transparent(env, descP); + break; +#endif + #if defined(IP_TTL) case SOCKET_OPT_IP_TTL: result = ngetopt_lvl_ip_ttl(env, descP); @@ -8651,6 +8699,25 @@ ERL_NIF_TERM ngetopt_lvl_ip_tos(ErlNifEnv* env, #endif +/* ngetopt_lvl_ip_transparent - Level IP TRANSPARENT option + */ +#if defined(IP_TRANSPARENT) +static +ERL_NIF_TERM ngetopt_lvl_ip_transparent(ErlNifEnv* env, + SocketDescriptor* descP) +{ +#if defined(SOL_IP) + int level = SOL_IP; +#else + int level = IPPROTO_IP; +#endif + + return ngetopt_bool_opt(env, descP, level, IP_TRANSPARENT); +} +#endif + + + /* ngetopt_lvl_ip_ttl - Level IP TTL option */ #if defined(IP_TTL) diff --git a/erts/preloaded/ebin/socket.beam b/erts/preloaded/ebin/socket.beam index b5b18e1c3a..960a465fb6 100644 Binary files a/erts/preloaded/ebin/socket.beam and b/erts/preloaded/ebin/socket.beam differ diff --git a/erts/preloaded/src/socket.erl b/erts/preloaded/src/socket.erl index 28931e4ed0..c7589cbd48 100644 --- a/erts/preloaded/src/socket.erl +++ b/erts/preloaded/src/socket.erl @@ -644,7 +644,7 @@ -define(SOCKET_OPT_IP_ROUTER_ALERT, 28). %% -define(SOCKET_OPT_IP_SNDSRCADDR, 29). -define(SOCKET_OPT_IP_TOS, 30). -%% -define(SOCKET_OPT_IP_TRANSPARENT, 31). +-define(SOCKET_OPT_IP_TRANSPARENT, 31). -define(SOCKET_OPT_IP_TTL, 32). -define(SOCKET_OPT_IP_UNBLOCK_SOURCE, 33). @@ -2274,6 +2274,9 @@ enc_setopt_value(ip, tos, V, _D, _T, _P) (V =:= mincost) orelse is_integer(V) -> V; +enc_setopt_value(ip, transparent, V, _D, _T, _P) + when is_boolean(V) -> + V; enc_setopt_value(ip, ttl, V, _D, _T, _P) when is_integer(V) -> V; @@ -2731,8 +2734,8 @@ enc_sockopt_key(ip, router_alert = _Opt, _Dir, _D, raw = _T, _P) -> %% No such condition on linux (in the man page)... enc_sockopt_key(ip, tos = _Opt, _Dir, _D, _T, _P) -> ?SOCKET_OPT_IP_TOS; -enc_sockopt_key(ip = L, transparent = Opt, _Dir, _D, _T, _P) -> - not_supported({L, Opt}); +enc_sockopt_key(ip = _L, transparent = _Opt, _Dir, _D, _T, _P) -> + ?SOCKET_OPT_IP_TRANSPARENT; enc_sockopt_key(ip, ttl = _Opt, _Dir, _D, _T, _P) -> ?SOCKET_OPT_IP_TTL; enc_sockopt_key(ip = _L, unblock_source = _Opt, set = _Dir, _D, _T, _P) -> diff --git a/lib/kernel/test/socket_server.erl b/lib/kernel/test/socket_server.erl index 0ae6bb2dd0..447e742895 100644 --- a/lib/kernel/test/socket_server.erl +++ b/lib/kernel/test/socket_server.erl @@ -642,6 +642,9 @@ handler_init(Manager, ID, Peek, Sock) -> RecvOPTS = GIP4(recvopts), % Not stream RecvTOS = GIP4(recvtos), RecvTTL = GIP4(recvttl), % not stream + TOS = GIP4(tos), + Transparent = GIP4(transparent), + TTL = GIP4(ttl), MHops = GIP6(multicast_hops), MIF6 = GIP6(multicast_if), % Only dgram and raw MLoop6 = GIP6(multicast_loop), @@ -684,6 +687,9 @@ handler_init(Manager, ID, Peek, Sock) -> "~n (ip) Recv OPTS: ~s" "~n (ip) Recv TOS: ~s" "~n (ip) Recv TTL: ~s" + "~n (ip) TOS: ~s" + "~n (ip) Transparent: ~s" + "~n (ip) TTL: ~s" "~n (ipv6) Multicast Hops: ~s" "~n (ipv6) Multicast IF: ~s" "~n (ipv6) Multicast Loop: ~s" @@ -702,6 +708,7 @@ handler_init(Manager, ID, Peek, Sock) -> Linger, Timestamp, FreeBind, MTU, MTUDisc, MALL, MIF4, MLoop4, MTTL, NF, PktInfo,RecvErr4, RecvIF, RecvOPTS, RecvTOS, RecvTTL, + TOS, Transparent, TTL, MHops, MIF6, MLoop6, RecvErr6, RecvPktInfo, RtHdr, AuthHdr, HopLimit, HopOpts, DstOpts, FlowInfo, UHops]), -- cgit v1.2.3 From cb8877a5561ac64704337441936b62c8c87f8d13 Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Wed, 25 Jul 2018 14:09:24 +0200 Subject: [socket-nif] Add support for socket (level ip) option retopts Added support for ip level socket option RETOPTS. OTP-14831 --- erts/doc/src/socket_usage.xml | 7 ++++ erts/emulator/nifs/common/socket_nif.c | 59 +++++++++++++++++++++++++++++++++ erts/preloaded/ebin/socket.beam | Bin 59092 -> 59148 bytes erts/preloaded/src/socket.erl | 9 +++-- lib/kernel/test/socket_client.erl | 10 ++++-- lib/kernel/test/socket_server.erl | 6 ++-- 6 files changed, 84 insertions(+), 7 deletions(-) diff --git a/erts/doc/src/socket_usage.xml b/erts/doc/src/socket_usage.xml index b1fe22b231..79cafa0162 100644 --- a/erts/doc/src/socket_usage.xml +++ b/erts/doc/src/socket_usage.xml @@ -400,6 +400,13 @@ yes type =/= stream + + retopts + boolean() + yes + yes + type =/= stream + router_alert integer() diff --git a/erts/emulator/nifs/common/socket_nif.c b/erts/emulator/nifs/common/socket_nif.c index 9e9ac333e7..288188d2dd 100644 --- a/erts/emulator/nifs/common/socket_nif.c +++ b/erts/emulator/nifs/common/socket_nif.c @@ -536,6 +536,7 @@ typedef union { #define SOCKET_OPT_IP_RECVOPTS 23 #define SOCKET_OPT_IP_RECVTOS 25 #define SOCKET_OPT_IP_RECVTTL 26 +#define SOCKET_OPT_IP_RETOPTS 27 #define SOCKET_OPT_IP_ROUTER_ALERT 28 #define SOCKET_OPT_IP_TOS 30 #define SOCKET_OPT_IP_TRANSPARENT 31 @@ -1195,6 +1196,11 @@ static ERL_NIF_TERM nsetopt_lvl_ip_recvttl(ErlNifEnv* env, SocketDescriptor* descP, ERL_NIF_TERM eVal); #endif +#if defined(IP_RETOPTS) +static ERL_NIF_TERM nsetopt_lvl_ip_retopts(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); +#endif #if defined(IP_ROUTER_ALERT) static ERL_NIF_TERM nsetopt_lvl_ip_router_alert(ErlNifEnv* env, SocketDescriptor* descP, @@ -1606,6 +1612,10 @@ static ERL_NIF_TERM ngetopt_lvl_ip_recvtos(ErlNifEnv* env, static ERL_NIF_TERM ngetopt_lvl_ip_recvttl(ErlNifEnv* env, SocketDescriptor* descP); #endif +#if defined(IP_RETOPTS) +static ERL_NIF_TERM ngetopt_lvl_ip_retopts(ErlNifEnv* env, + SocketDescriptor* descP); +#endif #if defined(IP_ROUTER_ALERT) static ERL_NIF_TERM ngetopt_lvl_ip_router_alert(ErlNifEnv* env, SocketDescriptor* descP); @@ -5071,6 +5081,12 @@ ERL_NIF_TERM nsetopt_lvl_ip(ErlNifEnv* env, break; #endif +#if defined(IP_RETOPTS) + case SOCKET_OPT_IP_RETOPTS: + result = nsetopt_lvl_ip_retopts(env, descP, eVal); + break; +#endif + #if defined(IP_ROUTER_ALERT) case SOCKET_OPT_IP_ROUTER_ALERT: result = nsetopt_lvl_ip_router_alert(env, descP, eVal); @@ -5684,6 +5700,25 @@ ERL_NIF_TERM nsetopt_lvl_ip_recvttl(ErlNifEnv* env, #endif +/* nsetopt_lvl_ip_retopts - Level IP RETOPTS option + */ +#if defined(IP_RETOPTS) +static +ERL_NIF_TERM nsetopt_lvl_ip_retopts(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) +{ +#if defined(SOL_IP) + int level = SOL_IP; +#else + int level = IPPROTO_IP; +#endif + + return nsetopt_bool_opt(env, descP, level, IP_RETOPTS, eVal); +} +#endif + + /* nsetopt_lvl_ip_router_alert - Level IP ROUTER_ALERT option */ #if defined(IP_ROUTER_ALERT) @@ -8289,6 +8324,12 @@ ERL_NIF_TERM ngetopt_lvl_ip(ErlNifEnv* env, break; #endif +#if defined(IP_RETOPTS) + case SOCKET_OPT_IP_RETOPTS: + result = ngetopt_lvl_ip_retopts(env, descP); + break; +#endif + #if defined(IP_ROUTER_ALERT) case SOCKET_OPT_IP_ROUTER_ALERT: result = ngetopt_lvl_ip_router_alert(env, descP); @@ -8651,6 +8692,24 @@ ERL_NIF_TERM ngetopt_lvl_ip_recvttl(ErlNifEnv* env, #endif +/* ngetopt_lvl_ip_retopts - Level IP RETOPTS option + */ +#if defined(IP_RETOPTS) +static +ERL_NIF_TERM ngetopt_lvl_ip_retopts(ErlNifEnv* env, + SocketDescriptor* descP) +{ +#if defined(SOL_IP) + int level = SOL_IP; +#else + int level = IPPROTO_IP; +#endif + + return ngetopt_bool_opt(env, descP, level, IP_RETOPTS); +} +#endif + + /* ngetopt_lvl_ip_router_alert - Level IP ROUTER_ALERT option */ #if defined(IP_ROUTER_ALERT) diff --git a/erts/preloaded/ebin/socket.beam b/erts/preloaded/ebin/socket.beam index 960a465fb6..93b224391b 100644 Binary files a/erts/preloaded/ebin/socket.beam and b/erts/preloaded/ebin/socket.beam differ diff --git a/erts/preloaded/src/socket.erl b/erts/preloaded/src/socket.erl index c7589cbd48..e7438cd856 100644 --- a/erts/preloaded/src/socket.erl +++ b/erts/preloaded/src/socket.erl @@ -640,7 +640,7 @@ %% -define(SOCKET_OPT_IP_RECVORIGDSTADDR, 24). -define(SOCKET_OPT_IP_RECVTOS, 25). -define(SOCKET_OPT_IP_RECVTTL, 26). -%% -define(SOCKET_OPT_IP_RETOPTS, 27). +-define(SOCKET_OPT_IP_RETOPTS, 27). -define(SOCKET_OPT_IP_ROUTER_ALERT, 28). %% -define(SOCKET_OPT_IP_SNDSRCADDR, 29). -define(SOCKET_OPT_IP_TOS, 30). @@ -2264,6 +2264,9 @@ enc_setopt_value(ip, recvtos, V, _D, _T, _P) enc_setopt_value(ip, recvttl, V, _D, _T, _P) when is_boolean(V) -> V; +enc_setopt_value(ip, retopts, V, _D, _T, _P) + when is_boolean(V) -> + V; enc_setopt_value(ip, router_alert, V, _D, _T, _P) when is_integer(V) -> V; @@ -2725,8 +2728,8 @@ enc_sockopt_key(ip, recvtos = _Opt, _Dir, _D, _T, _P) -> ?SOCKET_OPT_IP_RECVTOS; enc_sockopt_key(ip = _L, recvttl = _Opt, _Dir, _D, T, _P) when (T =/= stream) -> ?SOCKET_OPT_IP_RECVTTL; -enc_sockopt_key(ip = L, retopts = Opt, _Dir, _D, _T, _P) -> - not_supported({L, Opt}); +enc_sockopt_key(ip = _L, retopts = _Opt, _Dir, _D, T, _P) when (T =/= stream) -> + ?SOCKET_OPT_IP_RETOPTS; enc_sockopt_key(ip, router_alert = _Opt, _Dir, _D, raw = _T, _P) -> ?SOCKET_OPT_IP_ROUTER_ALERT; %% On FreeBSD it specifies that this option is only valid diff --git a/lib/kernel/test/socket_client.erl b/lib/kernel/test/socket_client.erl index 0d332e8439..094b7eebc5 100644 --- a/lib/kernel/test/socket_client.erl +++ b/lib/kernel/test/socket_client.erl @@ -22,8 +22,8 @@ -export([ start/1, start/5, - start_tcp/1, start_tcp/2, start_tcp6/1, - start_udp/1, start_udp/2, start_udp6/1 + start_tcp/1, start_tcp/2, start_tcp4/1, start_tcp6/1, + start_udp/1, start_udp/2, start_udp4/1, start_udp6/1 ]). -define(LIB, socket_lib). @@ -34,6 +34,9 @@ start(Port) -> start_tcp(Port). start_tcp(Port) -> + start_tcp4(Port). + +start_tcp4(Port) -> start(inet, stream, tcp, Port). start_tcp6(Port) -> @@ -46,6 +49,9 @@ start_tcp(Addr, Port) when (size(Addr) =:= 8) -> start_udp(Port) -> + start_udp4(Port). + +start_udp4(Port) -> start(inet, dgram, udp, Port). start_udp6(Port) -> diff --git a/lib/kernel/test/socket_server.erl b/lib/kernel/test/socket_server.erl index 447e742895..8087a0ddda 100644 --- a/lib/kernel/test/socket_server.erl +++ b/lib/kernel/test/socket_server.erl @@ -642,6 +642,7 @@ handler_init(Manager, ID, Peek, Sock) -> RecvOPTS = GIP4(recvopts), % Not stream RecvTOS = GIP4(recvtos), RecvTTL = GIP4(recvttl), % not stream + RetOpts = GIP4(retopts), % not stream TOS = GIP4(tos), Transparent = GIP4(transparent), TTL = GIP4(ttl), @@ -687,6 +688,7 @@ handler_init(Manager, ID, Peek, Sock) -> "~n (ip) Recv OPTS: ~s" "~n (ip) Recv TOS: ~s" "~n (ip) Recv TTL: ~s" + "~n (ip) Ret Opts: ~s" "~n (ip) TOS: ~s" "~n (ip) Transparent: ~s" "~n (ip) TTL: ~s" @@ -707,12 +709,12 @@ handler_init(Manager, ID, Peek, Sock) -> RcvBuf, RcvLW, RcvTO, SndBuf, SndLW, SndTO, Linger, Timestamp, FreeBind, MTU, MTUDisc, MALL, MIF4, MLoop4, MTTL, - NF, PktInfo,RecvErr4, RecvIF, RecvOPTS, RecvTOS, RecvTTL, + NF, PktInfo,RecvErr4, RecvIF, RecvOPTS, RecvTOS, RecvTTL, RetOpts, TOS, Transparent, TTL, MHops, MIF6, MLoop6, RecvErr6, RecvPktInfo, RtHdr, AuthHdr, HopLimit, HopOpts, DstOpts, FlowInfo, UHops]), - + handler_loop(#handler{peek = Peek, manager = Manager, type = Type, -- cgit v1.2.3 From 91d965f2ddfb420100ba685a059ec84eba18d0f9 Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Wed, 25 Jul 2018 18:06:48 +0200 Subject: [socket-nif] Updated socket type Updated the socket type. No longer store "stuff" that can be retrieved by other means (domain, type and protocol). OTP-14831 --- erts/preloaded/ebin/socket.beam | Bin 59148 -> 59600 bytes erts/preloaded/src/socket.erl | 116 +++++++++++++++++++++++++++------------- 2 files changed, 78 insertions(+), 38 deletions(-) diff --git a/erts/preloaded/ebin/socket.beam b/erts/preloaded/ebin/socket.beam index 93b224391b..9c626feff2 100644 Binary files a/erts/preloaded/ebin/socket.beam and b/erts/preloaded/ebin/socket.beam differ diff --git a/erts/preloaded/src/socket.erl b/erts/preloaded/src/socket.erl index e7438cd856..fcc052a25d 100644 --- a/erts/preloaded/src/socket.erl +++ b/erts/preloaded/src/socket.erl @@ -466,12 +466,8 @@ %% raw_socket_option() | %% plain_socket_option(). --type socket_info() :: #{domain => domain(), - type => type(), - protocol => protocol()}. --record(socket, {info :: socket_info(), - ref :: reference()}). -%% -opaque socket() :: {socket, socket_info(), reference()}. +-record(socket, {ref :: reference()}). + -opaque socket() :: #socket{}. -type accept_flags() :: [accept_flag()]. @@ -890,11 +886,7 @@ open(Domain, Type, Protocol0, Extra) when is_map(Extra) -> EProtocol = enc_protocol(Type, Protocol), case nif_open(EDomain, EType, EProtocol, Extra) of {ok, SockRef} -> - SocketInfo = #{domain => Domain, - type => Type, - protocol => Protocol}, - Socket = #socket{info = SocketInfo, - ref = SockRef}, + Socket = #socket{ref = SockRef}, {ok, Socket}; {error, _} = ERROR -> ERROR @@ -928,12 +920,17 @@ default_protocol(Protocol, _) -> Protocol. Addr :: any | loopback | sockaddr(), Reason :: term(). -bind(#socket{ref = SockRef, info = #{domain := inet}} = _Socket, Addr) - when ((Addr =:= any) orelse (Addr =:= loopback)) -> - nif_bind(SockRef, ?SOCKADDR_IN4_DEFAULT(Addr)); -bind(#socket{ref = SockRef, info = #{domain := inet6}} = _Socket, Addr) +bind(#socket{ref = SockRef}, Addr) when ((Addr =:= any) orelse (Addr =:= loopback)) -> - nif_bind(SockRef, ?SOCKADDR_IN6_DEFAULT(Addr)); + try which_domain(SockRef) of + inet -> + nif_bind(SockRef, ?SOCKADDR_IN4_DEFAULT(Addr)); + inet6 -> + nif_bind(SockRef, ?SOCKADDR_IN6_DEFAULT(Addr)) + catch + throw:ERROR -> + ERROR + end; bind(#socket{ref = SockRef} = _Socket, Addr) when is_map(Addr) -> try begin @@ -964,14 +961,13 @@ bind(#socket{ref = SockRef} = _Socket, Addr) when is_map(Addr) -> Action :: add | remove, Reason :: term(). -bind(#socket{ref = SockRef, - info = #{domain := Domain, - type := seqpacket, - protocol := sctp}} = _Socket, Addrs, Action) +bind(#socket{ref = SockRef}, Addrs, Action) when is_list(Addrs) andalso ((Action =:= add) orelse (Action =:= remove)) -> try begin - validate_addrs(Domain, Addrs), + ensure_type(seqpacket, which_type(SockRef)), + ensure_proto(sctp, which_protocol(SockRef)), + validate_addrs(which_domain(SockRef), Addrs), nif_bind(SockRef, Addrs, Action) end catch @@ -979,6 +975,21 @@ bind(#socket{ref = SockRef, ERROR end. +ensure_type(SockRef, Type) -> + case which_type(SockRef) of + Type -> + ok; + _InvalidType -> + einval() + end. + +ensure_proto(SockRef, Proto) -> + case which_protocol(SockRef) of + Proto -> + ok; + _InvalidProto -> + einval() + end. validate_addrs(inet = _Domain, Addrs) -> validate_inet_addrs(Addrs); @@ -1119,11 +1130,11 @@ accept(Socket) -> %% Do we really need this optimization? accept(_, Timeout) when is_integer(Timeout) andalso (Timeout =< 0) -> {error, timeout}; -accept(#socket{info = SI, ref = LSockRef}, Timeout) +accept(#socket{ref = LSockRef}, Timeout) when is_integer(Timeout) orelse (Timeout =:= infinity) -> - do_accept(LSockRef, SI, Timeout). + do_accept(LSockRef, Timeout). -do_accept(LSockRef, SI, Timeout) -> +do_accept(LSockRef, Timeout) -> TS = timestamp(Timeout), AccRef = make_ref(), case nif_accept(LSockRef, AccRef) of @@ -1138,18 +1149,17 @@ do_accept(LSockRef, SI, Timeout) -> %% message). %% %% - SocketInfo = #{domain => maps:get(domain, SI), - type => maps:get(type, SI), - protocol => maps:get(protocol, SI)}, - Socket = #socket{info = SocketInfo, - ref = SockRef}, + Socket = #socket{ref = SockRef}, {ok, Socket}; {error, eagain} -> + %% Each call is non-blocking, but even then it takes + %% *some* time, so just to be sure, recalculate before + %% the receive. NewTimeout = next_timeout(TS, Timeout), receive {select, LSockRef, AccRef, ready_input} -> - do_accept(LSockRef, SI, next_timeout(TS, Timeout)); + do_accept(LSockRef, next_timeout(TS, Timeout)); {nif_abort, AccRef, Reason} -> {error, Reason} @@ -1863,12 +1873,12 @@ shutdown(#socket{ref = SockRef}, How) -> Value :: term(), Reason :: term(). -setopt(#socket{info = Info, ref = SockRef}, Level, Key, Value) -> +setopt(#socket{ref = SockRef}, Level, Key, Value) -> try begin - Domain = maps:get(domain, Info), - Type = maps:get(type, Info), - Protocol = maps:get(protocol, Info), + Domain = which_domain(SockRef), + Type = which_type(SockRef), + Protocol = which_protocol(SockRef), {EIsEncoded, ELevel} = enc_setopt_level(Level), EKey = enc_setopt_key(Level, Key, Domain, Type, Protocol), EVal = enc_setopt_value(Level, Key, Value, Domain, Type, Protocol), @@ -1935,12 +1945,12 @@ setopt(#socket{info = Info, ref = SockRef}, Level, Key, Value) -> Value :: term(), Reason :: term(). -getopt(#socket{info = Info, ref = SockRef}, Level, Key) -> +getopt(#socket{ref = SockRef}, Level, Key) -> try begin - Domain = maps:get(domain, Info), - Type = maps:get(type, Info), - Protocol = maps:get(protocol, Info), + Domain = which_domain(SockRef), + Type = which_type(SockRef), + Protocol = which_protocol(SockRef), {EIsEncoded, ELevel} = enc_getopt_level(Level), EKey = enc_getopt_key(Level, Key, Domain, Type, Protocol), %% We may need to decode the value (for the same reason @@ -1964,6 +1974,36 @@ getopt(#socket{info = Info, ref = SockRef}, Level, Key) -> end. +%% These are internal "shortcut" functions for the options +%% domain, type and protocol. +which_domain(SockRef) -> + case nif_getopt(SockRef, true, + ?SOCKET_OPT_LEVEL_SOCKET, ?SOCKET_OPT_SOCK_DOMAIN) of + {ok, Domain} -> + Domain; + {error, _} = ERROR -> + throw(ERROR) + end. + + +which_type(SockRef) -> + case nif_getopt(SockRef, true, + ?SOCKET_OPT_LEVEL_SOCKET, ?SOCKET_OPT_SOCK_TYPE) of + {ok, Type} -> + Type; + {error, _} = ERROR -> + throw(ERROR) + end. + +which_protocol(SockRef) -> + case nif_getopt(SockRef, true, + ?SOCKET_OPT_LEVEL_SOCKET, ?SOCKET_OPT_SOCK_PROTOCOL) of + {ok, Type} -> + Type; + {error, _} = ERROR -> + throw(ERROR) + end. + %% =========================================================================== %% -- cgit v1.2.3 From 8ed757c8df2df54e19e67ca0a0734cd5a0f9ab23 Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Thu, 26 Jul 2018 10:10:16 +0200 Subject: [socket-nif] Add support for socket (level ip) option recvorigdstaddr Added support for ip level socket option RECVORIGDSTADDR. This option requires recvmsg to actually use, so we cannot test this fully at the moment (although both set and get works). OTP-14831 --- erts/doc/src/socket_usage.xml | 14 ++++ erts/emulator/nifs/common/socket_nif.c | 132 +++++++++++++++++++++++++++++++-- erts/preloaded/ebin/socket.beam | Bin 59600 -> 59660 bytes erts/preloaded/src/socket.erl | 24 +++--- lib/kernel/test/socket_server.erl | 103 ++++++++++++------------- 5 files changed, 206 insertions(+), 67 deletions(-) diff --git a/erts/doc/src/socket_usage.xml b/erts/doc/src/socket_usage.xml index 79cafa0162..f69aa75820 100644 --- a/erts/doc/src/socket_usage.xml +++ b/erts/doc/src/socket_usage.xml @@ -372,6 +372,13 @@ yes type = dgram + + recvdstaddr + boolean() + yes + yes + type = dgram + recverr boolean() @@ -393,6 +400,13 @@ yes type =/= stream + + recvorigdstaddr + boolean() + yes + yes + none + recvttl boolean() diff --git a/erts/emulator/nifs/common/socket_nif.c b/erts/emulator/nifs/common/socket_nif.c index 288188d2dd..4ba94f4f60 100644 --- a/erts/emulator/nifs/common/socket_nif.c +++ b/erts/emulator/nifs/common/socket_nif.c @@ -531,9 +531,11 @@ typedef union { #define SOCKET_OPT_IP_MULTICAST_TTL 16 #define SOCKET_OPT_IP_NODEFRAG 17 #define SOCKET_OPT_IP_PKTINFO 19 -#define SOCKET_OPT_IP_RECVERR 20 -#define SOCKET_OPT_IP_RECVIF 21 +#define SOCKET_OPT_IP_RECVDSTADDR 20 +#define SOCKET_OPT_IP_RECVERR 21 +#define SOCKET_OPT_IP_RECVIF 22 #define SOCKET_OPT_IP_RECVOPTS 23 +#define SOCKET_OPT_IP_RECVORIGDSTADDR 24 #define SOCKET_OPT_IP_RECVTOS 25 #define SOCKET_OPT_IP_RECVTTL 26 #define SOCKET_OPT_IP_RETOPTS 27 @@ -1171,6 +1173,11 @@ static ERL_NIF_TERM nsetopt_lvl_ip_pktinfo(ErlNifEnv* env, SocketDescriptor* descP, ERL_NIF_TERM eVal); #endif +#if defined(IP_RECVDSTADDR) +static ERL_NIF_TERM nsetopt_lvl_ip_recvdstaddr(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); +#endif #if defined(IP_RECVERR) static ERL_NIF_TERM nsetopt_lvl_ip_recverr(ErlNifEnv* env, SocketDescriptor* descP, @@ -1186,6 +1193,11 @@ static ERL_NIF_TERM nsetopt_lvl_ip_recvopts(ErlNifEnv* env, SocketDescriptor* descP, ERL_NIF_TERM eVal); #endif +#if defined(IP_RECVORIGDSTADDR) +static ERL_NIF_TERM nsetopt_lvl_ip_recvorigdstaddr(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); +#endif #if defined(IP_RECVTOS) static ERL_NIF_TERM nsetopt_lvl_ip_recvtos(ErlNifEnv* env, SocketDescriptor* descP, @@ -1592,6 +1604,10 @@ static ERL_NIF_TERM ngetopt_lvl_ip_nodefrag(ErlNifEnv* env, static ERL_NIF_TERM ngetopt_lvl_ip_pktinfo(ErlNifEnv* env, SocketDescriptor* descP); #endif +#if defined(IP_RECVDSTADDRS) +static ERL_NIF_TERM ngetopt_lvl_ip_recvdstaddr(ErlNifEnv* env, + SocketDescriptor* descP); +#endif #if defined(IP_RECVERR) static ERL_NIF_TERM ngetopt_lvl_ip_recverr(ErlNifEnv* env, SocketDescriptor* descP); @@ -1604,6 +1620,10 @@ static ERL_NIF_TERM ngetopt_lvl_ip_recvif(ErlNifEnv* env, static ERL_NIF_TERM ngetopt_lvl_ip_recvopts(ErlNifEnv* env, SocketDescriptor* descP); #endif +#if defined(IP_RECVORIGDSTADDR) +static ERL_NIF_TERM ngetopt_lvl_ip_recvorigdstaddr(ErlNifEnv* env, + SocketDescriptor* descP); +#endif #if defined(IP_RECVTOS) static ERL_NIF_TERM ngetopt_lvl_ip_recvtos(ErlNifEnv* env, SocketDescriptor* descP); @@ -5051,6 +5071,12 @@ ERL_NIF_TERM nsetopt_lvl_ip(ErlNifEnv* env, break; #endif +#if defined(IP_RECVDSTADDR) + case SOCKET_OPT_IP_RECVDSTADDR: + result = nsetopt_lvl_ip_recvdstaddr(env, descP, eVal); + break; +#endif + #if defined(IP_RECVERR) case SOCKET_OPT_IP_RECVERR: result = nsetopt_lvl_ip_recverr(env, descP, eVal); @@ -5069,6 +5095,12 @@ ERL_NIF_TERM nsetopt_lvl_ip(ErlNifEnv* env, break; #endif +#if defined(IP_RECVORIGDSTADDR) + case SOCKET_OPT_IP_RECVORIGDSTADDR: + result = nsetopt_lvl_ip_recvorigdstaddr(env, descP, eVal); + break; +#endif + #if defined(IP_RECVTOS) case SOCKET_OPT_IP_RECVTOS: result = nsetopt_lvl_ip_recvtos(env, descP, eVal); @@ -5605,6 +5637,25 @@ ERL_NIF_TERM nsetopt_lvl_ip_pktinfo(ErlNifEnv* env, #endif +/* nsetopt_lvl_ip_recvdstaddr - Level IP RECVDSTADDR option + */ +#if defined(IP_RECVDSTADDR) +static +ERL_NIF_TERM nsetopt_lvl_ip_recvdstaddr(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) +{ +#if defined(SOL_IP) + int level = SOL_IP; +#else + int level = IPPROTO_IP; +#endif + + return nsetopt_bool_opt(env, descP, level, IP_RECVDSTADDR, eVal); +} +#endif + + /* nsetopt_lvl_ip_recverr - Level IP RECVERR option */ #if defined(IP_RECVERR) @@ -5662,6 +5713,25 @@ ERL_NIF_TERM nsetopt_lvl_ip_recvopts(ErlNifEnv* env, #endif +/* nsetopt_lvl_ip_recvorigdstaddr - Level IP RECVORIGDSTADDR option + */ +#if defined(IP_RECVORIGDSTADDR) +static +ERL_NIF_TERM nsetopt_lvl_ip_recvorigdstaddr(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) +{ +#if defined(SOL_IP) + int level = SOL_IP; +#else + int level = IPPROTO_IP; +#endif + + return nsetopt_bool_opt(env, descP, level, IP_RECVORIGDSTADDR, eVal); +} +#endif + + /* nsetopt_lvl_ip_recvtos - Level IP RECVTOS option */ #if defined(IP_RECVTOS) @@ -8294,6 +8364,12 @@ ERL_NIF_TERM ngetopt_lvl_ip(ErlNifEnv* env, break; #endif +#if defined(IP_RECVDSTADDR) + case SOCKET_OPT_IP_RECVDSTADDR: + result = ngetopt_lvl_ip_recvdstaddr(env, descP); + break; +#endif + #if defined(IP_RECVERR) case SOCKET_OPT_IP_RECVERR: result = ngetopt_lvl_ip_recverr(env, descP); @@ -8312,6 +8388,12 @@ ERL_NIF_TERM ngetopt_lvl_ip(ErlNifEnv* env, break; #endif +#if defined(IP_RECVORIGDSTADDR) + case SOCKET_OPT_IP_RECVORIGDSTADDR: + result = ngetopt_lvl_ip_recvorigdstaddr(env, descP); + break; +#endif + #if defined(IP_RECVTOS) case SOCKET_OPT_IP_RECVTOS: result = ngetopt_lvl_ip_recvtos(env, descP); @@ -8620,12 +8702,12 @@ ERL_NIF_TERM ngetopt_lvl_ip_recvtos(ErlNifEnv* env, #endif -/* ngetopt_lvl_ip_recvif - Level IP RECVIF option +/* ngetopt_lvl_ip_recvdstaddr - Level IP RECVDSTADDR option */ -#if defined(IP_RECVIF) +#if defined(IP_RECVDSTADDR) static -ERL_NIF_TERM ngetopt_lvl_ip_recvif(ErlNifEnv* env, - SocketDescriptor* descP) +ERL_NIF_TERM ngetopt_lvl_ip_recvdstaddr(ErlNifEnv* env, + SocketDescriptor* descP) { #if defined(SOL_IP) int level = SOL_IP; @@ -8633,7 +8715,7 @@ ERL_NIF_TERM ngetopt_lvl_ip_recvif(ErlNifEnv* env, int level = IPPROTO_IP; #endif - return ngetopt_bool_opt(env, descP, level, IP_RECVIF); + return ngetopt_bool_opt(env, descP, level, IP_RECVDSTADDR); } #endif @@ -8656,6 +8738,24 @@ ERL_NIF_TERM ngetopt_lvl_ip_recverr(ErlNifEnv* env, #endif +/* ngetopt_lvl_ip_recvif - Level IP RECVIF option + */ +#if defined(IP_RECVIF) +static +ERL_NIF_TERM ngetopt_lvl_ip_recvif(ErlNifEnv* env, + SocketDescriptor* descP) +{ +#if defined(SOL_IP) + int level = SOL_IP; +#else + int level = IPPROTO_IP; +#endif + + return ngetopt_bool_opt(env, descP, level, IP_RECVIF); +} +#endif + + /* ngetopt_lvl_ip_recvopt - Level IP RECVOPTS option */ #if defined(IP_RECVOPTS) @@ -8674,6 +8774,24 @@ ERL_NIF_TERM ngetopt_lvl_ip_recvopts(ErlNifEnv* env, #endif +/* ngetopt_lvl_ip_recvorigdstaddr - Level IP RECVORIGDSTADDR option + */ +#if defined(IP_RECVORIGDSTADDR) +static +ERL_NIF_TERM ngetopt_lvl_ip_recvorigdstaddr(ErlNifEnv* env, + SocketDescriptor* descP) +{ +#if defined(SOL_IP) + int level = SOL_IP; +#else + int level = IPPROTO_IP; +#endif + + return ngetopt_bool_opt(env, descP, level, IP_RECVORIGDSTADDR); +} +#endif + + /* ngetopt_lvl_ip_recvttl - Level IP RECVTTL option */ #if defined(IP_RECVTTL) diff --git a/erts/preloaded/ebin/socket.beam b/erts/preloaded/ebin/socket.beam index 9c626feff2..90cb657178 100644 Binary files a/erts/preloaded/ebin/socket.beam and b/erts/preloaded/ebin/socket.beam differ diff --git a/erts/preloaded/src/socket.erl b/erts/preloaded/src/socket.erl index fcc052a25d..68b7d3f4b0 100644 --- a/erts/preloaded/src/socket.erl +++ b/erts/preloaded/src/socket.erl @@ -629,11 +629,11 @@ -define(SOCKET_OPT_IP_NODEFRAG, 17). %% -define(SOCKET_OPT_IP_OPTIONS, 18). % FreeBSD -define(SOCKET_OPT_IP_PKTINFO, 19). --define(SOCKET_OPT_IP_RECVERR, 20). --define(SOCKET_OPT_IP_RECVIF, 21). -%% -define(SOCKET_OPT_IP_RECVDSTADDR, 22). +-define(SOCKET_OPT_IP_RECVDSTADDR, 20). % FreeBSD +-define(SOCKET_OPT_IP_RECVERR, 21). +-define(SOCKET_OPT_IP_RECVIF, 22). -define(SOCKET_OPT_IP_RECVOPTS, 23). -%% -define(SOCKET_OPT_IP_RECVORIGDSTADDR, 24). +-define(SOCKET_OPT_IP_RECVORIGDSTADDR, 24). -define(SOCKET_OPT_IP_RECVTOS, 25). -define(SOCKET_OPT_IP_RECVTTL, 26). -define(SOCKET_OPT_IP_RETOPTS, 27). @@ -2289,6 +2289,9 @@ enc_setopt_value(ip, nodefrag, V, _D, _T, _P) enc_setopt_value(ip, pktinfo, V, _D, _T, _P) when is_boolean(V) -> V; +enc_setopt_value(ip, recvdstaddr, V, _D, _T, _P) + when is_boolean(V) -> + V; enc_setopt_value(ip, recverr, V, _D, _T, _P) when is_boolean(V) -> V; @@ -2298,6 +2301,9 @@ enc_setopt_value(ip, recvif, V, _D, _T, _P) enc_setopt_value(ip, recvopts, V, _D, _T, _P) when is_boolean(V) -> V; +enc_setopt_value(ip, recvorigdstaddr, V, _D, _T, _P) + when is_boolean(V) -> + V; enc_setopt_value(ip, recvtos, V, _D, _T, _P) when is_boolean(V) -> V; @@ -2751,19 +2757,17 @@ enc_sockopt_key(ip = L, options = Opt, _Dir, _D, _T, _P) -> not_supported({Opt, L}); enc_sockopt_key(ip = _L, pktinfo = _Opt, _Dir, _D, dgram = _T, _P) -> ?SOCKET_OPT_IP_PKTINFO; -%% This require special code for accessing the errors. -%% via calling the recvmsg with the MSG_ERRQUEUE flag set, +enc_sockopt_key(ip = _L, recvdstaddr = _Opt, _Dir, _D, T, _P) when (T =:= dgram) -> + ?SOCKET_OPT_IP_RECVDSTADDR; enc_sockopt_key(ip = _L, recverr = _Opt, _Dir, _D, _T, _P) -> ?SOCKET_OPT_IP_RECVERR; enc_sockopt_key(ip = _L, recvif = _Opt, _Dir, _D, T, _P) when (T =:= dgram) orelse (T =:= raw) -> ?SOCKET_OPT_IP_RECVIF; -enc_sockopt_key(ip = L, recvdstaddr = Opt, _Dir, _D, _T, _P) -> - not_supported({L, Opt}); enc_sockopt_key(ip = _L, recvopts = _Opt, _Dir, _D, T, _P) when (T =/= stream) -> ?SOCKET_OPT_IP_RECVOPTS; -enc_sockopt_key(ip = L, recvorigdstaddr = Opt, _Dir, _D, _T, _P) -> - not_supported({L, Opt}); +enc_sockopt_key(ip = _L, recvorigdstaddr = _Opt, _Dir, _D, _T, _P) -> + ?SOCKET_OPT_IP_RECVORIGDSTADDR; enc_sockopt_key(ip, recvtos = _Opt, _Dir, _D, _T, _P) -> ?SOCKET_OPT_IP_RECVTOS; enc_sockopt_key(ip = _L, recvttl = _Opt, _Dir, _D, T, _P) when (T =/= stream) -> diff --git a/lib/kernel/test/socket_server.erl b/lib/kernel/test/socket_server.erl index 8087a0ddda..3dad94b751 100644 --- a/lib/kernel/test/socket_server.erl +++ b/lib/kernel/test/socket_server.erl @@ -640,6 +640,7 @@ handler_init(Manager, ID, Peek, Sock) -> RecvErr4 = GIP4(recverr), RecvIF = GIP4(recvif), % Only dgram and raw (and FreeBSD) RecvOPTS = GIP4(recvopts), % Not stream + RecvOrigDstAddr = GIP4(recvorigdstaddr), RecvTOS = GIP4(recvtos), RecvTTL = GIP4(recvttl), % not stream RetOpts = GIP4(retopts), % not stream @@ -659,57 +660,59 @@ handler_init(Manager, ID, Peek, Sock) -> FlowInfo = GIP6(flowinfo), UHops = GIP6(unicast_hops), i("got continue when: " - "~n (socket) Domain: ~p" - "~n (socket) Type: ~p" - "~n (socket) Protocol: ~p" - "~n (socket) Reuse Address: ~s" - "~n (socket) Reuse Port: ~s" - "~n (socket) Bind To Device: ~s" - "~n (socket) OOBInline: ~s" - "~n (socket) RcvBuf: ~s" - "~n (socket) RcvLW: ~s" - "~n (socket) RcvTO: ~s" - "~n (socket) SndBuf: ~s" - "~n (socket) SndLW: ~s" - "~n (socket) SndTO: ~s" - "~n (socket) Linger: ~s" - "~n (socket) Timestamp: ~s" - "~n (ip) FreeBind: ~s" - "~n (ip) MTU: ~s" - "~n (ip) MTU Discovery: ~s" - "~n (ip) Multicast ALL: ~s" - "~n (ip) Multicast IF: ~s" - "~n (ip) Multicast Loop: ~s" - "~n (ip) Multicast TTL: ~s" - "~n (ip) Node Frag: ~s" - "~n (ip) Pkt Info: ~s" - "~n (ip) Recv Err: ~s" - "~n (ip) Recv IF: ~s" - "~n (ip) Recv OPTS: ~s" - "~n (ip) Recv TOS: ~s" - "~n (ip) Recv TTL: ~s" - "~n (ip) Ret Opts: ~s" - "~n (ip) TOS: ~s" - "~n (ip) Transparent: ~s" - "~n (ip) TTL: ~s" - "~n (ipv6) Multicast Hops: ~s" - "~n (ipv6) Multicast IF: ~s" - "~n (ipv6) Multicast Loop: ~s" - "~n (ipv6) Recv Err: ~s" - "~n (ipv6) Recv Pkt Info: ~s" - "~n (ipv6) RT Hdr: ~s" - "~n (ipv6) Auth Hdr: ~s" - "~n (ipv6) Hop Limit: ~s" - "~n (ipv6) Hop Opts: ~s" - "~n (ipv6) Dst Opts: ~s" - "~n (ipv6) Flow Info: ~s" - "~n (ipv6) Unicast Hops: ~s", + "~n (socket) Domain: ~p" + "~n (socket) Type: ~p" + "~n (socket) Protocol: ~p" + "~n (socket) Reuse Address: ~s" + "~n (socket) Reuse Port: ~s" + "~n (socket) Bind To Device: ~s" + "~n (socket) OOBInline: ~s" + "~n (socket) RcvBuf: ~s" + "~n (socket) RcvLW: ~s" + "~n (socket) RcvTO: ~s" + "~n (socket) SndBuf: ~s" + "~n (socket) SndLW: ~s" + "~n (socket) SndTO: ~s" + "~n (socket) Linger: ~s" + "~n (socket) Timestamp: ~s" + "~n (ip) FreeBind: ~s" + "~n (ip) MTU: ~s" + "~n (ip) MTU Discovery: ~s" + "~n (ip) Multicast ALL: ~s" + "~n (ip) Multicast IF: ~s" + "~n (ip) Multicast Loop: ~s" + "~n (ip) Multicast TTL: ~s" + "~n (ip) Node Frag: ~s" + "~n (ip) Pkt Info: ~s" + "~n (ip) Recv Err: ~s" + "~n (ip) Recv IF: ~s" + "~n (ip) Recv OPTS: ~s" + "~n (ip) Recv Orig Dst Addr: ~s" + "~n (ip) Recv TOS: ~s" + "~n (ip) Recv TTL: ~s" + "~n (ip) Ret Opts: ~s" + "~n (ip) TOS: ~s" + "~n (ip) Transparent: ~s" + "~n (ip) TTL: ~s" + "~n (ipv6) Multicast Hops: ~s" + "~n (ipv6) Multicast IF: ~s" + "~n (ipv6) Multicast Loop: ~s" + "~n (ipv6) Recv Err: ~s" + "~n (ipv6) Recv Pkt Info: ~s" + "~n (ipv6) RT Hdr: ~s" + "~n (ipv6) Auth Hdr: ~s" + "~n (ipv6) Hop Limit: ~s" + "~n (ipv6) Hop Opts: ~s" + "~n (ipv6) Dst Opts: ~s" + "~n (ipv6) Flow Info: ~s" + "~n (ipv6) Unicast Hops: ~s", [Domain, Type, Proto, RA, RP, B2D, OOBI, RcvBuf, RcvLW, RcvTO, SndBuf, SndLW, SndTO, Linger, Timestamp, FreeBind, MTU, MTUDisc, MALL, MIF4, MLoop4, MTTL, - NF, PktInfo,RecvErr4, RecvIF, RecvOPTS, RecvTOS, RecvTTL, RetOpts, + NF, PktInfo,RecvErr4, + RecvIF, RecvOPTS, RecvOrigDstAddr, RecvTOS, RecvTTL, RetOpts, TOS, Transparent, TTL, MHops, MIF6, MLoop6, RecvErr6, RecvPktInfo, RtHdr, AuthHdr, HopLimit, HopOpts, DstOpts, FlowInfo, @@ -721,14 +724,14 @@ handler_init(Manager, ID, Peek, Sock) -> socket = Sock}) end. -%% so(Sock, Lvl, Opt, Val) -> -%% ok = socket:setopt(Sock, Lvl, Opt, Val). +so(Sock, Lvl, Opt, Val) -> + ok = socket:setopt(Sock, Lvl, Opt, Val). %% soso(Sock, Opt, Val) -> %% so(Sock, socket, Opt, Val). -%% soip(Sock, Opt, Val) -> -%% so(Sock, ip, Opt, Val). +soip(Sock, Opt, Val) -> + so(Sock, ip, Opt, Val). %% soipv6(Sock, Opt, Val) -> %% so(Sock, ipv6, Opt, Val). -- cgit v1.2.3 From 6b01561dc13a0152f56da0a2c61ad88236f87de7 Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Thu, 26 Jul 2018 11:07:05 +0200 Subject: [socket-nif] Add support for socket (level ip) option sendsrcaddr Added support for ip level socket option SENDSRCADDR. This option requires sendmsg to actually use, so we cannot test this fully at the moment. OTP-14831 --- erts/doc/src/socket_usage.xml | 11 ++++++ erts/emulator/nifs/common/socket_nif.c | 59 +++++++++++++++++++++++++++++++++ erts/preloaded/ebin/socket.beam | Bin 59660 -> 59768 bytes erts/preloaded/src/socket.erl | 9 +++-- lib/kernel/test/socket_server.erl | 12 ++++--- 5 files changed, 84 insertions(+), 7 deletions(-) diff --git a/erts/doc/src/socket_usage.xml b/erts/doc/src/socket_usage.xml index f69aa75820..933341bd35 100644 --- a/erts/doc/src/socket_usage.xml +++ b/erts/doc/src/socket_usage.xml @@ -35,6 +35,10 @@

The socket interface (module) is basically an "thin" layer on top of the OS socket interface. It is assumed that, unless you have special needs, gen_[tcp|udp|sctp] should be sufficent.

+

Note that just because we have a documented and described option, + it does not mean that the OS supports it. So its recommended + that the user reads the platform specific documentation for the + option used.

@@ -428,6 +432,13 @@ yes type = raw + + sendsrcaddr + boolean() + yes + yes + none + tos ip_tos() diff --git a/erts/emulator/nifs/common/socket_nif.c b/erts/emulator/nifs/common/socket_nif.c index 4ba94f4f60..13250349db 100644 --- a/erts/emulator/nifs/common/socket_nif.c +++ b/erts/emulator/nifs/common/socket_nif.c @@ -540,6 +540,7 @@ typedef union { #define SOCKET_OPT_IP_RECVTTL 26 #define SOCKET_OPT_IP_RETOPTS 27 #define SOCKET_OPT_IP_ROUTER_ALERT 28 +#define SOCKET_OPT_IP_SENDSRCADDR 29 // Same as IP_RECVDSTADDR? #define SOCKET_OPT_IP_TOS 30 #define SOCKET_OPT_IP_TRANSPARENT 31 #define SOCKET_OPT_IP_TTL 32 @@ -1218,6 +1219,11 @@ static ERL_NIF_TERM nsetopt_lvl_ip_router_alert(ErlNifEnv* env, SocketDescriptor* descP, ERL_NIF_TERM eVal); #endif +#if defined(IP_SENDSRCADDR) +static ERL_NIF_TERM nsetopt_lvl_ip_sendsrcaddr(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); +#endif #if defined(IP_TOS) static ERL_NIF_TERM nsetopt_lvl_ip_tos(ErlNifEnv* env, SocketDescriptor* descP, @@ -1640,6 +1646,10 @@ static ERL_NIF_TERM ngetopt_lvl_ip_retopts(ErlNifEnv* env, static ERL_NIF_TERM ngetopt_lvl_ip_router_alert(ErlNifEnv* env, SocketDescriptor* descP); #endif +#if defined(IP_SENDSRCADDR) +static ERL_NIF_TERM ngetopt_lvl_ip_sendsrcaddr(ErlNifEnv* env, + SocketDescriptor* descP); +#endif #if defined(IP_TOS) static ERL_NIF_TERM ngetopt_lvl_ip_tos(ErlNifEnv* env, SocketDescriptor* descP); @@ -5125,6 +5135,12 @@ ERL_NIF_TERM nsetopt_lvl_ip(ErlNifEnv* env, break; #endif +#if defined(IP_SENDSRCADDR) + case SOCKET_OPT_IP_SENDSRCADDR: + result = nsetopt_lvl_ip_sendsrcaddr(env, descP, eVal); + break; +#endif + #if defined(IP_TOS) case SOCKET_OPT_IP_TOS: result = nsetopt_lvl_ip_tos(env, descP, eVal); @@ -5808,6 +5824,25 @@ ERL_NIF_TERM nsetopt_lvl_ip_router_alert(ErlNifEnv* env, #endif +/* nsetopt_lvl_ip_sendsrcaddr - Level IP SENDSRCADDR option + */ +#if defined(IP_SENDSRCADDR) +static +ERL_NIF_TERM nsetopt_lvl_ip_sendsrcaddr(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) +{ +#if defined(SOL_IP) + int level = SOL_IP; +#else + int level = IPPROTO_IP; +#endif + + return nsetopt_bool_opt(env, descP, level, IP_SENDSRCADDR, eVal); +} +#endif + + /* nsetopt_lvl_ip_tos - Level IP TOS option */ #if defined(IP_TOS) @@ -8418,6 +8453,12 @@ ERL_NIF_TERM ngetopt_lvl_ip(ErlNifEnv* env, break; #endif +#if defined(IP_SENDSRCADDR) + case SOCKET_OPT_IP_SENDSRCADDR: + result = ngetopt_lvl_ip_sendsrcaddr(env, descP); + break; +#endif + #if defined(IP_TOS) case SOCKET_OPT_IP_TOS: result = ngetopt_lvl_ip_tos(env, descP); @@ -8846,6 +8887,24 @@ ERL_NIF_TERM ngetopt_lvl_ip_router_alert(ErlNifEnv* env, #endif +/* ngetopt_lvl_ip_sendsrcaddr - Level IP SENDSRCADDR option + */ +#if defined(IP_SENDSRCADDR) +static +ERL_NIF_TERM ngetopt_lvl_ip_sendsrcaddr(ErlNifEnv* env, + SocketDescriptor* descP) +{ +#if defined(SOL_IP) + int level = SOL_IP; +#else + int level = IPPROTO_IP; +#endif + + return ngetopt_bool_opt(env, descP, level, IP_SENDSRCADDR); +} +#endif + + /* ngetopt_lvl_ip_tos - Level IP TOS option */ #if defined(IP_TOS) diff --git a/erts/preloaded/ebin/socket.beam b/erts/preloaded/ebin/socket.beam index 90cb657178..b2bd8f2728 100644 Binary files a/erts/preloaded/ebin/socket.beam and b/erts/preloaded/ebin/socket.beam differ diff --git a/erts/preloaded/src/socket.erl b/erts/preloaded/src/socket.erl index 68b7d3f4b0..1983c993a5 100644 --- a/erts/preloaded/src/socket.erl +++ b/erts/preloaded/src/socket.erl @@ -638,11 +638,11 @@ -define(SOCKET_OPT_IP_RECVTTL, 26). -define(SOCKET_OPT_IP_RETOPTS, 27). -define(SOCKET_OPT_IP_ROUTER_ALERT, 28). -%% -define(SOCKET_OPT_IP_SNDSRCADDR, 29). +-define(SOCKET_OPT_IP_SENDSRCADDR, 29). % FreeBSD -define(SOCKET_OPT_IP_TOS, 30). -define(SOCKET_OPT_IP_TRANSPARENT, 31). -define(SOCKET_OPT_IP_TTL, 32). --define(SOCKET_OPT_IP_UNBLOCK_SOURCE, 33). +-define(SOCKET_OPT_IP_UNBLOCK_SOURCE, 33). -define(SOCKET_OPT_IPV6_ADDRFORM, 1). -define(SOCKET_OPT_IPV6_ADD_MEMBERSHIP, 2). @@ -2316,6 +2316,9 @@ enc_setopt_value(ip, retopts, V, _D, _T, _P) enc_setopt_value(ip, router_alert, V, _D, _T, _P) when is_integer(V) -> V; +enc_setopt_value(ip, sendsrcaddr, V, _D, _T, _P) + when is_boolean(V) -> + V; enc_setopt_value(ip, tos, V, _D, _T, _P) when (V =:= lowdelay) orelse (V =:= throughput) orelse @@ -2776,6 +2779,8 @@ enc_sockopt_key(ip = _L, retopts = _Opt, _Dir, _D, T, _P) when (T =/= stream) -> ?SOCKET_OPT_IP_RETOPTS; enc_sockopt_key(ip, router_alert = _Opt, _Dir, _D, raw = _T, _P) -> ?SOCKET_OPT_IP_ROUTER_ALERT; +enc_sockopt_key(ip, sendsrcaddr = _Opt, _Dir, _D, _T, _P) -> + ?SOCKET_OPT_IP_SENDSRCADDR; %% On FreeBSD it specifies that this option is only valid %% for stream, dgram and "some" raw sockets... %% No such condition on linux (in the man page)... diff --git a/lib/kernel/test/socket_server.erl b/lib/kernel/test/socket_server.erl index 3dad94b751..88b63ecf2d 100644 --- a/lib/kernel/test/socket_server.erl +++ b/lib/kernel/test/socket_server.erl @@ -644,6 +644,7 @@ handler_init(Manager, ID, Peek, Sock) -> RecvTOS = GIP4(recvtos), RecvTTL = GIP4(recvttl), % not stream RetOpts = GIP4(retopts), % not stream + SendSrcAddr = GIP4(sendsrcaddr), TOS = GIP4(tos), Transparent = GIP4(transparent), TTL = GIP4(ttl), @@ -691,6 +692,7 @@ handler_init(Manager, ID, Peek, Sock) -> "~n (ip) Recv TOS: ~s" "~n (ip) Recv TTL: ~s" "~n (ip) Ret Opts: ~s" + "~n (ip) Send Src Addr: ~s" "~n (ip) TOS: ~s" "~n (ip) Transparent: ~s" "~n (ip) TTL: ~s" @@ -713,7 +715,7 @@ handler_init(Manager, ID, Peek, Sock) -> FreeBind, MTU, MTUDisc, MALL, MIF4, MLoop4, MTTL, NF, PktInfo,RecvErr4, RecvIF, RecvOPTS, RecvOrigDstAddr, RecvTOS, RecvTTL, RetOpts, - TOS, Transparent, TTL, + SendSrcAddr, TOS, Transparent, TTL, MHops, MIF6, MLoop6, RecvErr6, RecvPktInfo, RtHdr, AuthHdr, HopLimit, HopOpts, DstOpts, FlowInfo, UHops]), @@ -724,14 +726,14 @@ handler_init(Manager, ID, Peek, Sock) -> socket = Sock}) end. -so(Sock, Lvl, Opt, Val) -> - ok = socket:setopt(Sock, Lvl, Opt, Val). +%% so(Sock, Lvl, Opt, Val) -> +%% ok = socket:setopt(Sock, Lvl, Opt, Val). %% soso(Sock, Opt, Val) -> %% so(Sock, socket, Opt, Val). -soip(Sock, Opt, Val) -> - so(Sock, ip, Opt, Val). +%% soip(Sock, Opt, Val) -> +%% so(Sock, ip, Opt, Val). %% soipv6(Sock, Opt, Val) -> %% so(Sock, ipv6, Opt, Val). -- cgit v1.2.3 From 165666d8b8b1b21ffaf43ac436cfc1657ba83649 Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Mon, 30 Jul 2018 18:22:46 +0200 Subject: [socket-nif] Add support for recvmsg Added preliminary support for function recvmsg. At the moment this only works on *nix (Windows has another function, WSARecvMsg, which has a slightly different API). Also we have "no" cmsg decode at the moment (just level and type). OTP-14831 --- erts/emulator/Makefile.in | 3 +- erts/emulator/nifs/common/socket_int.h | 11 + erts/emulator/nifs/common/socket_nif.c | 343 +++++++++++++++++++++++++++++- erts/emulator/nifs/common/socket_tarray.c | 139 ++++++++++++ erts/emulator/nifs/common/socket_tarray.h | 47 ++++ erts/emulator/nifs/common/socket_util.c | 267 ++++++++++++++++++++++- erts/emulator/nifs/common/socket_util.h | 25 +++ erts/preloaded/ebin/socket.beam | Bin 59768 -> 61376 bytes erts/preloaded/src/socket.erl | 80 ++++++- 9 files changed, 891 insertions(+), 24 deletions(-) create mode 100644 erts/emulator/nifs/common/socket_tarray.c create mode 100644 erts/emulator/nifs/common/socket_tarray.h diff --git a/erts/emulator/Makefile.in b/erts/emulator/Makefile.in index 60f9b36491..1964d27134 100644 --- a/erts/emulator/Makefile.in +++ b/erts/emulator/Makefile.in @@ -878,7 +878,8 @@ RUN_OBJS += \ $(OBJDIR)/erl_ptab.o $(OBJDIR)/erl_map.o \ $(OBJDIR)/erl_msacc.o $(OBJDIR)/erl_lock_flags.o \ $(OBJDIR)/erl_io_queue.o \ - $(OBJDIR)/socket_dbg.o $(OBJDIR)/socket_util.o + $(OBJDIR)/socket_dbg.o $(OBJDIR)/socket_tarray.o \ + $(OBJDIR)/socket_util.o LTTNG_OBJS = $(OBJDIR)/erlang_lttng.o NIF_OBJS = \ diff --git a/erts/emulator/nifs/common/socket_int.h b/erts/emulator/nifs/common/socket_int.h index 2d5049a9eb..18e94e80ef 100644 --- a/erts/emulator/nifs/common/socket_int.h +++ b/erts/emulator/nifs/common/socket_int.h @@ -105,19 +105,28 @@ typedef unsigned int BOOLEAN_T; */ extern ERL_NIF_TERM esock_atom_addr; extern ERL_NIF_TERM esock_atom_any; +extern ERL_NIF_TERM esock_atom_ctrl; +extern ERL_NIF_TERM esock_atom_ctrunc; +extern ERL_NIF_TERM esock_atom_data; extern ERL_NIF_TERM esock_atom_debug; extern ERL_NIF_TERM esock_atom_dgram; +extern ERL_NIF_TERM esock_atom_eor; extern ERL_NIF_TERM esock_atom_error; +extern ERL_NIF_TERM esock_atom_errqueue; extern ERL_NIF_TERM esock_atom_false; extern ERL_NIF_TERM esock_atom_family; +extern ERL_NIF_TERM esock_atom_flags; extern ERL_NIF_TERM esock_atom_flowinfo; extern ERL_NIF_TERM esock_atom_inet; extern ERL_NIF_TERM esock_atom_inet6; +extern ERL_NIF_TERM esock_atom_iov; extern ERL_NIF_TERM esock_atom_ip; extern ERL_NIF_TERM esock_atom_ipv6; +extern ERL_NIF_TERM esock_atom_level; extern ERL_NIF_TERM esock_atom_local; extern ERL_NIF_TERM esock_atom_loopback; extern ERL_NIF_TERM esock_atom_ok; +extern ERL_NIF_TERM esock_atom_oob; extern ERL_NIF_TERM esock_atom_path; extern ERL_NIF_TERM esock_atom_port; extern ERL_NIF_TERM esock_atom_protocol; @@ -129,6 +138,7 @@ extern ERL_NIF_TERM esock_atom_seqpacket; extern ERL_NIF_TERM esock_atom_stream; extern ERL_NIF_TERM esock_atom_tcp; extern ERL_NIF_TERM esock_atom_true; +extern ERL_NIF_TERM esock_atom_trunc; extern ERL_NIF_TERM esock_atom_type; extern ERL_NIF_TERM esock_atom_udp; extern ERL_NIF_TERM esock_atom_undefined; @@ -147,6 +157,7 @@ extern ERL_NIF_TERM esock_atom_einval; * Various wrapper macros for enif functions */ #define MALLOC(SZ) enif_alloc((SZ)) +#define REALLOC(P, SZ) enif_realloc((P), (SZ)) #define FREE(P) enif_free((P)) #define MKA(E,S) enif_make_atom((E), (S)) diff --git a/erts/emulator/nifs/common/socket_nif.c b/erts/emulator/nifs/common/socket_nif.c index 13250349db..6b6ddb29ca 100644 --- a/erts/emulator/nifs/common/socket_nif.c +++ b/erts/emulator/nifs/common/socket_nif.c @@ -417,7 +417,8 @@ static void (*esock_sctp_freepaddrs)(struct sockaddr *addrs) = NULL; #define SOCKET_RECV_FLAG_LOW SOCKET_RECV_FLAG_CMSG_CLOEXEC #define SOCKET_RECV_FLAG_HIGH SOCKET_RECV_FLAG_TRUNC -#define SOCKET_RECV_BUFFER_SIZE_DEFAULT 2048 +#define SOCKET_RECV_BUFFER_SIZE_DEFAULT 2048 +#define SOCKET_RECV_CTRL_BUFFER_SIZE_DEFAULT 1024 #define SOCKET_OPT_VALUE_TYPE_UNSPEC 0 #define SOCKET_OPT_VALUE_TYPE_INT 1 @@ -582,6 +583,10 @@ typedef union { #define SOCKET_OPT_SCTP_NODELAY 23 #define SOCKET_OPT_SCTP_RTOINFO 29 +/* We should *eventually* use this instead of hard-coding the size (to 1) */ +#define ESOCK_RECVMSG_IOVEC_SZ 1 + + /* =================================================================== * * * @@ -663,6 +668,7 @@ static unsigned long one_value = 1; #define sock_recv(s,buf,len,flag) recv((s),(buf),(len),(flag)) #define sock_recvfrom(s,buf,blen,flag,addr,alen) \ recvfrom((s),(buf),(blen),(flag),(addr),(alen)) +#define sock_recvmsg(s,msghdr,flag) recvmsg((s),(msghdr),(flag)) #define sock_send(s,buf,len,flag) send((s), (buf), (len), (flag)) #define sock_sendto(s,buf,blen,flag,addr,alen) \ sendto((s),(buf),(blen),(flag),(addr),(alen)) @@ -759,8 +765,9 @@ typedef struct { SocketRequestQueue acceptorsQ; /* +++ Config & Misc stuff +++ */ - size_t rBufSz; // Read buffer size (when data length = 0 is specified) - BOOLEAN_T iow; // Inform On Wrap + size_t rBufSz; // Read buffer size (when data length = 0 is specified) + size_t rCtrlSz; // Read control buffer size + BOOLEAN_T iow; // Inform On Wrap BOOLEAN_T dbg; /* +++ Close stuff +++ */ @@ -871,6 +878,9 @@ static ERL_NIF_TERM nif_recv(ErlNifEnv* env, static ERL_NIF_TERM nif_recvfrom(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); +static ERL_NIF_TERM nif_recvmsg(ErlNifEnv* env, + int argc, + const ERL_NIF_TERM argv[]); static ERL_NIF_TERM nif_close(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); @@ -947,6 +957,12 @@ static ERL_NIF_TERM nrecvfrom(ErlNifEnv* env, ERL_NIF_TERM recvRef, uint16_t bufSz, int flags); +static ERL_NIF_TERM nrecvmsg(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM recvRef, + uint16_t bufLen, + uint16_t ctrlLen, + int flags); static ERL_NIF_TERM nclose(ErlNifEnv* env, SocketDescriptor* descP); static ERL_NIF_TERM nshutdown(ErlNifEnv* env, @@ -1854,6 +1870,13 @@ static ERL_NIF_TERM recvfrom_check_result(ErlNifEnv* env, SocketAddress* fromAddrP, unsigned int fromAddrLen, ERL_NIF_TERM recvRef); +static ERL_NIF_TERM recvmsg_check_result(ErlNifEnv* env, + SocketDescriptor* descP, + int read, + int saveErrno, + struct msghdr* msgHdrP, + ErlNifBinary* ctrlBufP, + ERL_NIF_TERM recvRef); static ERL_NIF_TERM nfinalize_connection(ErlNifEnv* env, SocketDescriptor* descP); @@ -2211,19 +2234,28 @@ static char str_exsend[] = "exsend"; // failed send /* *** "Global" Atoms *** */ ERL_NIF_TERM esock_atom_addr; ERL_NIF_TERM esock_atom_any; +ERL_NIF_TERM esock_atom_ctrl; +ERL_NIF_TERM esock_atom_ctrunc; +ERL_NIF_TERM esock_atom_data; ERL_NIF_TERM esock_atom_dgram; ERL_NIF_TERM esock_atom_debug; +ERL_NIF_TERM esock_atom_eor; ERL_NIF_TERM esock_atom_error; +ERL_NIF_TERM esock_atom_errqueue; ERL_NIF_TERM esock_atom_false; ERL_NIF_TERM esock_atom_family; +ERL_NIF_TERM esock_atom_flags; ERL_NIF_TERM esock_atom_flowinfo; ERL_NIF_TERM esock_atom_inet; ERL_NIF_TERM esock_atom_inet6; +ERL_NIF_TERM esock_atom_iov; ERL_NIF_TERM esock_atom_ip; ERL_NIF_TERM esock_atom_ipv6; +ERL_NIF_TERM esock_atom_level; ERL_NIF_TERM esock_atom_local; ERL_NIF_TERM esock_atom_loopback; ERL_NIF_TERM esock_atom_ok; +ERL_NIF_TERM esock_atom_oob; ERL_NIF_TERM esock_atom_path; ERL_NIF_TERM esock_atom_protocol; ERL_NIF_TERM esock_atom_port; @@ -2235,6 +2267,7 @@ ERL_NIF_TERM esock_atom_seqpacket; ERL_NIF_TERM esock_atom_stream; ERL_NIF_TERM esock_atom_tcp; ERL_NIF_TERM esock_atom_true; +ERL_NIF_TERM esock_atom_trunc; ERL_NIF_TERM esock_atom_type; ERL_NIF_TERM esock_atom_udp; ERL_NIF_TERM esock_atom_undefined; @@ -2355,7 +2388,8 @@ static SocketData data; * nif_send(Sock, SendRef, Data, Flags) * nif_sendto(Sock, SendRef, Data, Dest, Flags) * nif_recv(Sock, RecvRef, Length, Flags) - * nif_recvfrom(Sock, Flags) + * nif_recvfrom(Sock, RecvRef, BufSz, Flags) + * nif_recvmsg(Sock, RecvRef, BufSz, CtrlSz, Flags) * nif_close(Sock) * nif_shutdown(Sock, How) * nif_sockname(Sock) @@ -4023,6 +4057,189 @@ ERL_NIF_TERM nrecvfrom(ErlNifEnv* env, +/* ---------------------------------------------------------------------- + * nif_recvmsg + * + * Description: + * Receive a message on a socket. + * Normally used only on a (un-) connected socket! + * If a buffer size = 0 is specified, then we will use the default + * buffer size for this socket (whatever has been configured). + * If ctrl (buffer) size = 0 is specified, then the default ctrl + * (buffer) size is used (1024). + * + * Arguments: + * Socket (ref) - Points to the socket descriptor. + * RecvRef - A unique id for this (send) request. + * BufSz - Size of the buffer into which we put the received message. + * CtrlSz - Size of the ctrl (buffer) into which we put the received + * ancillary data. + * Flags - Receive flags. + * + * + * + * How do we handle if the peek flag is set? We need to basically keep + * track of if we expect any data from the read. Regardless of the + * number of bytes we try to read. + * + * + */ + +static +ERL_NIF_TERM nif_recvmsg(ErlNifEnv* env, + int argc, + const ERL_NIF_TERM argv[]) +{ + SocketDescriptor* descP; + ERL_NIF_TERM recvRef; + unsigned int bufSz; + unsigned int ctrlSz; + unsigned int eflags; + int flags; + ERL_NIF_TERM res; + + SGDBG( ("SOCKET", "nif_recvmsg -> entry with argc: %d\r\n", argc) ); + + /* Extract arguments and perform preliminary validation */ + + if ((argc != 4) || + !enif_get_resource(env, argv[0], sockets, (void**) &descP) || + !GET_UINT(env, argv[2], &bufSz) || + !GET_UINT(env, argv[3], &ctrlSz) || + !GET_UINT(env, argv[4], &eflags)) { + return enif_make_badarg(env); + } + recvRef = argv[1]; + + SSDBG( descP, + ("SOCKET", "nif_recvmsg -> args when sock = %d:" + "\r\n Socket: %T" + "\r\n recvRef: %T" + "\r\n bufSz: %d" + "\r\n ctrlSz: %d" + "\r\n eflags: %d" + "\r\n", descP->sock, argv[0], recvRef, bufSz, ctrlSz, eflags) ); + + /* if (IS_OPEN(descP)) */ + /* return esock_make_error(env, atom_enotconn); */ + + if (!erecvflags2recvflags(eflags, &flags)) + return enif_make_badarg(env); + + MLOCK(descP->readMtx); + + /* + * + * We need to handle the case when another process tries + * to receive at the same time. + * If the current recv could not read its entire package + * this time (resulting in an select). The read of the + * other process must be made to wait until current + * is done! + * Basically, we need a read queue! + * + * A 'reading' field (boolean), which is set if we did + * not manage to read the entire message and reset every + * time we do. + * + * + */ + + res = nrecvmsg(env, descP, recvRef, bufSz, ctrlSz, flags); + + MUNLOCK(descP->readMtx); + + return res; + +} + + +/* The (read) buffer handling *must* be optimized! + * But for now we make it easy for ourselves by + * allocating a binary (of the specified or default + * size) and then throwing it away... + */ +static +ERL_NIF_TERM nrecvmsg(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM recvRef, + uint16_t bufLen, + uint16_t ctrlLen, + int flags) +{ + unsigned int addrLen; + ssize_t read; + int save_errno; + ErlNifBinary buf, ctrl; + int bufSz = (bufLen ? bufLen : descP->rBufSz); + int ctrlSz = (ctrlLen ? ctrlLen : descP->rCtrlSz); + struct msghdr msgHdr; + struct iovec iov[1]; // Shall we always use 1? + SocketAddress addr; + + SSDBG( descP, ("SOCKET", "nrecvfrom -> entry with" + "\r\n bufSz: %d (%d)" + "\r\n ctrlSz: %d (%d)" + "\r\n flags: %d" + "\r\n", bufSz, bufLen, ctrlSz, ctrlLen, flags) ); + + if (!descP->isReadable) + return enif_make_badarg(env); + + /* + for (i = 0; i < sizeof(buf); i++) { + if (!ALLOC_BIN(bifSz, &buf[i])) + return esock_make_error(env, atom_exalloc); + iov[i].iov_base = buf[i].data; + iov[i].iov_len = buf[i].size; + } + */ + + /* Allocate the (msg) data buffer: + */ + if (!ALLOC_BIN(bufSz, &buf)) + return esock_make_error(env, atom_exalloc); + + /* Allocate the ctrl (buffer): + */ + if (!ALLOC_BIN(ctrlSz, &ctrl)) + return esock_make_error(env, atom_exalloc); + + /* We ignore the wrap for the moment. + * Maybe we should issue a wrap-message to controlling process... + */ + cnt_inc(&descP->readTries, 1); + + addrLen = sizeof(addr); + sys_memzero((char*) &addr, addrLen); + sys_memzero((char*) &msgHdr, sizeof(msgHdr)); + + iov[0].iov_base = buf.data; + iov[0].iov_len = buf.size; + + msgHdr.msg_name = &addr; + msgHdr.msg_namelen = addrLen; + msgHdr.msg_iov = iov; + msgHdr.msg_iovlen = 1; // Should use a constant or calculate... + msgHdr.msg_control = ctrl.data; + msgHdr.msg_controllen = ctrl.size; + + read = sock_recvmsg(descP->sock, &msgHdr, flags); + if (IS_SOCKET_ERROR(read)) + save_errno = sock_errno(); + else + save_errno = -1; // The value does not actually matter in this case + + return recvmsg_check_result(env, descP, + read, + save_errno, + &msgHdr, + &ctrl, // Needed for ctrl header decode + recvRef); +} + + + /* ---------------------------------------------------------------------- * nif_close * @@ -9800,7 +10017,7 @@ ERL_NIF_TERM ngetopt_timeval_opt(ErlNifEnv* env, ESOCK_ASSERT( (numKeys == numVals) ); if (!MKMA(env, keys, vals, numKeys, &eTimeVal)) - return esock_make_error(env, esock_atom_einval);; + return esock_make_error(env, esock_atom_einval); result = esock_make_ok2(env, eTimeVal); } @@ -10370,6 +10587,110 @@ ERL_NIF_TERM recvfrom_check_result(ErlNifEnv* env, +/* The recvmsg function delivers one (1) message. If our buffer + * is to small, the message will be truncated. So, regardless + * if we filled the buffer or not, we have got what we are going + * to get regarding this message. + */ +static +ERL_NIF_TERM recvmsg_check_result(ErlNifEnv* env, + SocketDescriptor* descP, + int read, + int saveErrno, + struct msghdr* msgHdrP, + ErlNifBinary* ctrlBufP, + ERL_NIF_TERM recvRef) +{ + + SSDBG( descP, + ("SOCKET", "recvmsg_check_result -> entry with" + "\r\n read: %d" + "\r\n saveErrno: %d" + "\r\n recvRef: %T" + "\r\n", read, saveErrno, recvRef) ); + + + /* There is a special case: If the provided 'to read' value is + * zero (0). That means that we reads as much as we can, using + * the default read buffer size. + */ + + if (read < 0) { + + /* +++ Error handling +++ */ + + if (saveErrno == ECONNRESET) { + + /* +++ Oups - closed +++ */ + + SSDBG( descP, ("SOCKET", "recvfrom_check_result -> closed\r\n") ); + + /* + * IF THE CURRENT PROCESS IS *NOT* THE CONTROLLING + * PROCESS, WE NEED TO INFORM IT!!! + * + * ALL WAITING PROCESSES MUST ALSO GET THE ERROR!! + * + * + */ + + descP->closeLocal = FALSE; + descP->state = SOCKET_STATE_CLOSING; + + SELECT(env, + descP->sock, + (ERL_NIF_SELECT_STOP), + descP, NULL, recvRef); + + return esock_make_error(env, atom_closed); + + } else if ((saveErrno == ERRNO_BLOCK) || + (saveErrno == EAGAIN)) { + + SSDBG( descP, ("SOCKET", "recvfrom_check_result -> eagain\r\n") ); + + SELECT(env, descP->sock, (ERL_NIF_SELECT_READ), + descP, NULL, recvRef); + + return esock_make_error(env, esock_atom_eagain); + } else { + + SSDBG( descP, + ("SOCKET", + "recvfrom_check_result -> errno: %d\r\n", saveErrno) ); + + return esock_make_error_errno(env, saveErrno); + } + + } else { + + /* +++ We sucessfully got a message - time to encode it +++ */ + + ERL_NIF_TERM eMsgHdr; + char* xres; + + /* + * + * + * The return value of recvmsg is the *total* number of bytes + * that where successfully read. This data has been put into + * the *IO vector*. + * + * + */ + + if ((xres = esock_encode_msghdr(env, read, + msgHdrP, ctrlBufP, + &eMsgHdr)) != NULL) + return esock_make_error_str(env, xres); + else + return esock_make_ok2(env, eMsgHdr); + + } +} + + + /* +++ decode the linger value +++ * The (socket) linger option is provided as a two tuple: * @@ -10831,6 +11152,7 @@ SocketDescriptor* alloc_descriptor(SOCKET sock, HANDLE event) descP->closeMtx = MCREATE(buf); descP->rBufSz = SOCKET_RECV_BUFFER_SIZE_DEFAULT; + descP->rCtrlSz = SOCKET_RECV_CTRL_BUFFER_SIZE_DEFAULT; descP->iow = FALSE; descP->dbg = SOCKET_DEBUG_DEFAULT; @@ -11941,6 +12263,7 @@ ErlNifFunc socket_funcs[] = {"nif_sendto", 5, nif_sendto, 0}, {"nif_recv", 4, nif_recv, 0}, {"nif_recvfrom", 4, nif_recvfrom, 0}, + {"nif_recvmsg", 5, nif_recvmsg, 0}, {"nif_close", 1, nif_close, 0}, {"nif_shutdown", 2, nif_shutdown, 0}, {"nif_setopt", 5, nif_setopt, 0}, @@ -12077,16 +12400,23 @@ int on_load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info) /* Global atom(s) */ esock_atom_addr = MKA(env, "addr"); esock_atom_any = MKA(env, "any"); + esock_atom_ctrl = MKA(env, "ctrl"); + esock_atom_ctrunc = MKA(env, "ctrunc"); + esock_atom_data = MKA(env, "data"); esock_atom_debug = MKA(env, "debug"); esock_atom_dgram = MKA(env, "dgram"); + esock_atom_eor = MKA(env, "eor"); esock_atom_error = MKA(env, "error"); + esock_atom_errqueue = MKA(env, "errqueue"); esock_atom_false = MKA(env, "false"); esock_atom_family = MKA(env, "family"); + esock_atom_flags = MKA(env, "flags"); esock_atom_flowinfo = MKA(env, "flowinfo"); esock_atom_inet = MKA(env, "inet"); esock_atom_inet6 = MKA(env, "inet6"); - esock_atom_ip = MKA(env, "ip"); + esock_atom_ip = MKA(env, "iov"); esock_atom_ipv6 = MKA(env, "ipvp"); + esock_atom_level = MKA(env, "level"); esock_atom_local = MKA(env, "local"); esock_atom_loopback = MKA(env, "loopback"); esock_atom_ok = MKA(env, "ok"); @@ -12101,6 +12431,7 @@ int on_load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info) esock_atom_stream = MKA(env, "stream"); esock_atom_tcp = MKA(env, "tcp"); esock_atom_true = MKA(env, "true"); + esock_atom_trunc = MKA(env, "trunc"); esock_atom_type = MKA(env, "type"); esock_atom_udp = MKA(env, "udp"); esock_atom_undefined = MKA(env, "undefined"); diff --git a/erts/emulator/nifs/common/socket_tarray.c b/erts/emulator/nifs/common/socket_tarray.c new file mode 100644 index 0000000000..bf37e5bc0e --- /dev/null +++ b/erts/emulator/nifs/common/socket_tarray.c @@ -0,0 +1,139 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 2018-2018. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * %CopyrightEnd% + * + * ---------------------------------------------------------------------- + * Purpose : Build and "maintain" a (erlang) term array of + * variable length. + * ---------------------------------------------------------------------- + * + */ + +#include +#include + +#include + +#include "socket_int.h" +#include "socket_util.h" +#include "socket_tarray.h" + + + +/* ---------------------------------------------------------------------- + * Types + */ + +typedef struct { + uint32_t sz; + uint32_t idx; + ERL_NIF_TERM* array; +} SocketTArrayInt; + + +/* ---------------------------------------------------------------------- + * Forward for internal functions + */ + +static void esock_tarray_add1(SocketTArrayInt* taP, ERL_NIF_TERM t); +static void esock_tarray_ensure_fits(SocketTArrayInt* taP, uint32_t needs); + + +/* ---------------------------------------------------------------------- + * API + */ + +extern +void* esock_tarray_create(uint32_t sz) +{ + SocketTArrayInt* tarrayP; + + ESOCK_ASSERT( (sz == 0) ); + + tarrayP = MALLOC(sizeof(SocketTArrayInt)); + ESOCK_ASSERT( (tarrayP == NULL) ); + + tarrayP->array = MALLOC(sz * sizeof(ERL_NIF_TERM)); + ESOCK_ASSERT( (tarrayP->array == NULL) ); + tarrayP->sz = sz; + tarrayP->idx = 0; + + return ((SocketTArray) tarrayP); +} + +extern +void esock_tarray_delete(SocketTArray ta) +{ + SocketTArrayInt* taP = (SocketTArrayInt*) ta; + + FREE(taP->array); + FREE(taP); +} + + +extern +uint32_t esock_tarray_sz(SocketTArray a) +{ + return ( ((SocketTArrayInt*) a)->idx ); +} + +extern +void esock_tarray_add(SocketTArray ta, ERL_NIF_TERM t) +{ + esock_tarray_add1((SocketTArrayInt*) ta, t); +} + +extern +void esock_tarray_tolist(SocketTArray ta, + ErlNifEnv* env, + ERL_NIF_TERM* list) +{ + SocketTArrayInt* taP = (SocketTArrayInt*) ta; + + *list = MKLA(env, taP->array, taP->idx); + + esock_tarray_delete(taP); +} + + + +/* ---------------------------------------------------------------------- + * "Internal" functions + */ + +static +void esock_tarray_add1(SocketTArrayInt* taP, ERL_NIF_TERM t) +{ + esock_tarray_ensure_fits(taP, 1); + + taP->array[taP->idx++] = t; +} + +static +void esock_tarray_ensure_fits(SocketTArrayInt* taP, uint32_t needs) +{ + if (taP->sz < (taP->idx + needs)) { + uint32_t newSz = (needs < taP->sz) ? 2*taP->sz : 2*needs; + void* mem = REALLOC(taP->array, newSz * sizeof(ERL_NIF_TERM)); + + ESOCK_ASSERT( (mem == NULL) ); + + taP->sz = newSz; + taP->array = (ERL_NIF_TERM*) mem; + } +} diff --git a/erts/emulator/nifs/common/socket_tarray.h b/erts/emulator/nifs/common/socket_tarray.h new file mode 100644 index 0000000000..2e9506d288 --- /dev/null +++ b/erts/emulator/nifs/common/socket_tarray.h @@ -0,0 +1,47 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 2018-2018. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * %CopyrightEnd% + * + * ---------------------------------------------------------------------- + * Purpose : Build and "maintain" a (erlang) term array of + * variable length. + * ---------------------------------------------------------------------- + * + */ + +#ifndef SOCKET_TARRAY_H__ +#define SOCKET_TARRAY_H__ + +typedef void* SocketTArray; + +extern SocketTArray esock_tarray_create(uint32_t sz); +extern void esock_tarray_delete(SocketTArray ta); +extern uint32_t esock_tarray_sz(SocketTArray ta); +extern void esock_tarray_add(SocketTArray ta, ERL_NIF_TERM t); +extern void esock_tarray_tolist(SocketTArray ta, + ErlNifEnv* env, + ERL_NIF_TERM* list); + +#define TARRAY_CREATE(SZ) esock_tarray_create((SZ)) +#define TARRAY_DELETE(TA) esock_tarray_delete((TA)) +#define TARRAY_SZ(TA) esock_tarray_sz((TA)) +#define TARRAY_ADD(TA, T) esock_tarray_add((TA), (T)) +#define TARRAY_TOLIST(TA, E, L) esock_tarray_tolist((TA), (E), (L)) + + +#endif // SOCKET_TARRAY_H__ diff --git a/erts/emulator/nifs/common/socket_util.c b/erts/emulator/nifs/common/socket_util.c index e6eb21adcf..5998ff35a4 100644 --- a/erts/emulator/nifs/common/socket_util.c +++ b/erts/emulator/nifs/common/socket_util.c @@ -23,17 +23,18 @@ * */ -#include -#include "socket_int.h" -#include "socket_util.h" -#include "socket_dbg.h" -#include "sys.h" - #include #include #include #include #include +#include + +#include "socket_int.h" +#include "socket_tarray.h" +#include "socket_util.h" +#include "socket_dbg.h" +#include "sys.h" /* We don't have a "debug flag" to check here, so we * should use the compile debug flag, whatever that is... @@ -69,6 +70,260 @@ static char* make_sockaddr_un(ErlNifEnv* env, ERL_NIF_TERM* sa); +/* +++ esock_encode_msghdr +++ + * + * Encode a msghdr (recvmsg). In erlang its represented as + * a map, which has a specific set of attributes: + * + * addr (source address) - sockaddr() + * iov - [binary()] + * ctrl - [cmsghdr()] + * flags - msghdr_flags() + */ + +extern +char* esock_encode_msghdr(ErlNifEnv* env, + int read, + struct msghdr* msgHdrP, + ErlNifBinary* ctrlBufP, + ERL_NIF_TERM* eSockAddr) +{ + char* xres; + ERL_NIF_TERM addr, iov, ctrl, flags; + + if ((xres = esock_encode_sockaddr(env, + (SocketAddress*) msgHdrP->msg_name, + msgHdrP->msg_namelen, + &addr)) != NULL) + return xres; + + if ((xres = esock_encode_iov(env, + read, + msgHdrP->msg_iov, + msgHdrP->msg_iovlen, + &iov)) != NULL) + return xres; + + if ((xres = esock_encode_cmsghdrs(env, + ctrlBufP, + msgHdrP, + &ctrl)) != NULL) + return xres; + + if ((xres = esock_encode_mshghdr_flags(env, + msgHdrP->msg_flags, + &flags)) != NULL) + return xres; + + { + ERL_NIF_TERM keys[] = {esock_atom_addr, + esock_atom_iov, + esock_atom_ctrl, + esock_atom_flags}; + ERL_NIF_TERM vals[] = {addr, iov, ctrl, flags}; + + unsigned int numKeys = sizeof(keys) / sizeof(ERL_NIF_TERM); + unsigned int numVals = sizeof(vals) / sizeof(ERL_NIF_TERM); + + ESOCK_ASSERT( (numKeys == numVals) ); + + if (!MKMA(env, keys, vals, numKeys, eSockAddr)) + return ESOCK_STR_EINVAL; + + } + + return NULL; +} + + + +/* +++ esock_encode_iov +++ + * + * Encode a IO Vector. In erlang we represented this as a list of binaries. + * + * We iterate through the IO vector, and as long as the remaining (rem) + * number of bytes is greater than the size of the current buffer, we + * contunue. When we have a buffer that is greater than rem, we have found + * the last buffer (it may be empty, and then the previous was last). + * We may need to split this (if 0 < rem < bufferSz). + */ + +extern +char* esock_encode_iov(ErlNifEnv* env, + int read, + struct iovec* iov, + size_t len, + ERL_NIF_TERM* eIOV) +{ + int rem = read; + uint16_t i; + BOOLEAN_T done = FALSE; + ERL_NIF_TERM a[len]; // At most this length + + if (len == 0) { + *eIOV = MKEL(env); + return NULL; + } + + for (i = 0; (!done) && (i < len); i++) { + if (iov[i].iov_len == rem) { + /* We have the exact amount - we are done */ + a[i] = MKBIN(env, iov[i].iov_base); + done = TRUE; + } else if (iov[i].iov_len < rem) { + /* Filled another buffer - continue */ + a[i] = MKBIN(env, iov[i].iov_base); + } else if (iov[i].iov_len > rem) { + /* Partly filled buffer (=> split) - we are done */ + a[i] = MKBIN(env, iov[i].iov_base); + a[i] = MKSBIN(env, a[i], 0, rem); + done = TRUE; + } + } + + *eIOV = MKLA(env, a, i+1); + + return NULL; +} + + + +/* +++ esock_encode_cmsghdrs +++ + * + * Encode a list of cmsghdr(). The X can 0 or more cmsghdr blocks. + * + * Our problem is that we have no idea how many control messages + * we have. + * + * The cmsgHdrP arguments points to the start of the control data buffer, + * an actual binary. Its the only way to create sub-binaries. So, what we + * need to continue processing this is to tern that into an binary erlang + * term (which can then in turn be turned into sub-binaries). + * + * We need the cmsgBufP (even though cmsgHdrP points to it) to be able + * to create sub-binaries (one for each HDR). + * + * The TArray is created with the size of 128, which should be enough. + * But if its not, then it will be automatically realloc'ed during add. + * Once we are done adding hdr's to it, we convert it to a list. + */ + +extern +char* esock_encode_cmsghdrs(ErlNifEnv* env, + ErlNifBinary* cmsgBinP, + struct msghdr* msgHdrP, + ERL_NIF_TERM* eCMsgHdr) +{ + ERL_NIF_TERM ctrlBuf = MKBIN(env, cmsgBinP); // The *entire* binary + SocketTArray cmsghdrs = TARRAY_CREATE(128); + struct cmsghdr* firstP = CMSG_FIRSTHDR(msgHdrP); + struct cmsghdr* currentP; + + for (currentP = firstP; + currentP != NULL; + currentP = CMSG_NXTHDR(msgHdrP, currentP)) { + + /* MUST check this since on Linux the returned "cmsg" may actually + * go too far! + */ + if (((CHARP(currentP) + currentP->cmsg_len) - CHARP(firstP)) > + msgHdrP->msg_controllen) { + /* Ouch, fatal error - give up + * We assume we cannot trust any data if this is wrong. + */ + TARRAY_DELETE(cmsghdrs); + return ESOCK_STR_EINVAL; + } else { + ERL_NIF_TERM level; + ERL_NIF_TERM type = MKI(env, currentP->cmsg_type); + unsigned char* dataP = (unsigned char*) CMSG_DATA(currentP); + size_t dataPos = dataP - cmsgBinP->data; + size_t dataLen = currentP->cmsg_len - (CHARP(currentP)-CHARP(dataP)); + ERL_NIF_TERM dataBin = MKSBIN(env, ctrlBuf, dataPos, dataLen); + + /* We can't give up just because its an unknown protocol, + * so if its a protocol we don't know, we return its integer + * value and leave it to the user. + */ + if (esock_encode_protocol(env, currentP->cmsg_level, &level) != NULL) + level = MKI(env, currentP->cmsg_level); + + /* And finally create the 'cmsghdr' map - + * and if successfull add it to the tarray. + */ + { + ERL_NIF_TERM keys[] = {esock_atom_level, + esock_atom_type, + esock_atom_data}; + ERL_NIF_TERM vals[] = {level, type, dataBin}; + unsigned int numKeys = sizeof(keys) / sizeof(ERL_NIF_TERM); + unsigned int numVals = sizeof(vals) / sizeof(ERL_NIF_TERM); + ERL_NIF_TERM cmsgHdr; + + /* Guard agains cut-and-paste errors */ + ESOCK_ASSERT( (numKeys == numVals) ); + + if (!MKMA(env, keys, vals, numKeys, &cmsgHdr)) { + TARRAY_DELETE(cmsghdrs); + return ESOCK_STR_EINVAL; + } + + /* And finally add it to the list... */ + TARRAY_ADD(cmsghdrs, cmsgHdr); + } + } + } + + /* The tarray is populated - convert it to a list */ + TARRAY_TOLIST(cmsghdrs, env, eCMsgHdr); + + return NULL; +} + + + +/* +++ esock_encode_mshghdr_flags +++ + * + * Encode a list of msghdr_flag(). + * + * The following flags are handled: eor | trunc | ctrunc | oob | errqueue. + */ + +extern +char* esock_encode_mshghdr_flags(ErlNifEnv* env, + int msgFlags, + ERL_NIF_TERM* flags) +{ + if (msgFlags == 0) { + *flags = MKEL(env); + return NULL; + } else { + SocketTArray ta = TARRAY_CREATE(10); // Just to be on the safe side + + if ((msgFlags & MSG_EOR) == MSG_EOR) + TARRAY_ADD(ta, esock_atom_eor); + + if ((msgFlags & MSG_TRUNC) == MSG_TRUNC) + TARRAY_ADD(ta, esock_atom_trunc); + + if ((msgFlags & MSG_CTRUNC) == MSG_CTRUNC) + TARRAY_ADD(ta, esock_atom_ctrunc); + + if ((msgFlags & MSG_OOB) == MSG_OOB) + TARRAY_ADD(ta, esock_atom_oob); + + if ((msgFlags & MSG_ERRQUEUE) == MSG_ERRQUEUE) + TARRAY_ADD(ta, esock_atom_errqueue); + + TARRAY_TOLIST(ta, env, flags); + + return NULL; + } +} + + + + /* +++ esock_decode_sockaddr +++ * * Decode a socket address - sockaddr. In erlang its represented as diff --git a/erts/emulator/nifs/common/socket_util.h b/erts/emulator/nifs/common/socket_util.h index 686ce0bac6..af0bf70d8f 100644 --- a/erts/emulator/nifs/common/socket_util.h +++ b/erts/emulator/nifs/common/socket_util.h @@ -29,9 +29,34 @@ #include #include "socket_int.h" +#define VOIDP(P) ((void*)P) +#define CHARP(P) ((char*)P) + #define ESOCK_ABORT(E) esock_abort(E, __func__, __FILE__, __LINE__) #define ESOCK_ASSERT(e) ((void) ((e) ? 1 : (ESOCK_ABORT(#e), 0))) +extern +char* esock_encode_msghdr(ErlNifEnv* env, + int read, + struct msghdr* msgHdrP, + ErlNifBinary* ctrlBufP, + ERL_NIF_TERM* eSockAddr); +extern +char* esock_encode_iov(ErlNifEnv* env, + int read, + struct iovec* iov, + size_t len, + ERL_NIF_TERM* eIOV); +extern +char* esock_encode_cmsghdrs(ErlNifEnv* env, + ErlNifBinary* cmsgBinP, + struct msghdr* msgHdrP, + ERL_NIF_TERM* eCMsgHdr); + +extern +char* esock_encode_mshghdr_flags(ErlNifEnv* env, + int msgFlags, + ERL_NIF_TERM* flags); extern char* esock_decode_sockaddr(ErlNifEnv* env, ERL_NIF_TERM eSockAddr, diff --git a/erts/preloaded/ebin/socket.beam b/erts/preloaded/ebin/socket.beam index b2bd8f2728..f6ca653fed 100644 Binary files a/erts/preloaded/ebin/socket.beam and b/erts/preloaded/ebin/socket.beam differ diff --git a/erts/preloaded/src/socket.erl b/erts/preloaded/src/socket.erl index 1983c993a5..b9d1705d45 100644 --- a/erts/preloaded/src/socket.erl +++ b/erts/preloaded/src/socket.erl @@ -43,7 +43,7 @@ recv/1, recv/2, recv/3, recv/4, recvfrom/1, recvfrom/2, recvfrom/3, recvfrom/4, - %% recvmsg/4, + recvmsg/1, recvmsg/2, recvmsg/5, %% readv/3, close/1, @@ -500,24 +500,28 @@ -type shutdown_how() :: read | write | read_write. %% These are just place-holder(s) - used by the sendmsg/recvmsg functions... --type msghdr_flag() :: eor | trunc | ctrunc | oob | errqueue. +-type msghdr_flag() :: ctrunc | eor | errqueue | oob | trunc. -type msghdr_flags() :: [msghdr_flag()]. -type msghdr() :: #{ %% *Optional* target address %% *If* this field is specified for an unconnected %% socket, then it will be used as destination for the %% datagram. - target => sockaddr(), + addr => sockaddr(), - iov => [binary()], + iov => [binary()], - ctrl => cmsghdr(), + ctrl => [cmsghdr()], %% Only valid with recvmsg flags => msghdr_flags() }. +%% At some point we should be able to encode/decode the most common types +%% of control message headers. For now, we leave/take the data part raw +%% (as a binary) and leave it to the user to figure out (how to encode/decode +%% that bit). -type cmsghdr() :: #{ - level => protocol(), + level => protocol() | integer(), type => integer(), data => binary() }. @@ -1752,11 +1756,62 @@ do_recvfrom(SockRef, BufSz, EFlags, Timeout) -> %% --------------------------------------------------------------------------- %% -%% -spec recvmsg(Socket, Flags) -> {ok, MsgHdr} | {error, Reason} when -%% Socket :: socket(), -%% MsgHdr :: msghdr(), -%% Flags :: recv_flags(), -%% Reason :: term(). +recvmsg(Socket) -> + recvmsg(Socket, 0, 0, ?SOCKET_RECV_FLAGS_DEFAULT, ?SOCKET_RECV_TIMEOUT_DEFAULT). + +recvmsg(Socket, Flags) when is_list(Flags) -> + recvmsg(Socket, 0, 0, Flags, ?SOCKET_RECV_TIMEOUT_DEFAULT); +recvmsg(Socket, Timeout) -> + recvmsg(Socket, 0, 0, ?SOCKET_RECV_FLAGS_DEFAULT, Timeout). + +-spec recvmsg(Socket, + BufSz, CtrlSz, + Flags, Timeout) -> {ok, MsgHdr} | {error, Reason} when + Socket :: socket(), + BufSz :: non_neg_integer(), + CtrlSz :: non_neg_integer(), + Flags :: recv_flags(), + Timeout :: timeout(), + MsgHdr :: msghdr(), + Reason :: term(). + +recvmsg(#socket{ref = SockRef}, BufSz, CtrlSz, Flags, Timeout) + when (is_integer(BufSz) andalso (BufSz >= 0)) andalso + (is_integer(CtrlSz) andalso (CtrlSz >= 0)) andalso + is_list(Flags) andalso + (is_integer(Timeout) orelse (Timeout =:= infinity)) -> + EFlags = enc_recv_flags(Flags), + do_recvmsg(SockRef, BufSz, CtrlSz, EFlags, Timeout). + +do_recvmsg(SockRef, BufSz, CtrlSz, EFlags, Timeout) -> + TS = timestamp(Timeout), + RecvRef = make_ref(), + case nif_recvmsg(SockRef, RecvRef, BufSz, CtrlSz, EFlags) of + {ok, _MsgHdr} = OK -> + OK; + + {error, eagain} -> + %% There is nothing just now, but we will be notified when there + %% is something to read (a select message). + NewTimeout = next_timeout(TS, Timeout), + receive + {select, SockRef, RecvRef, ready_input} -> + do_recvmsg(SockRef, BufSz, CtrlSz, EFlags, + next_timeout(TS, Timeout)); + + {nif_abort, RecvRef, Reason} -> + {error, Reason} + + after NewTimeout -> + nif_cancel(SockRef, recvmsg, RecvRef), + flush_select_msgs(SockRef, RecvRef), + {error, timeout} + end; + + {error, _Reason} = ERROR -> + ERROR + + end. @@ -3171,6 +3226,9 @@ nif_recv(_SRef, _RecvRef, _Length, _Flags) -> nif_recvfrom(_SRef, _RecvRef, _Length, _Flags) -> erlang:error(badarg). +nif_recvmsg(_SRef, _RecvRef, _BufSz, _CtrlSz, _Flags) -> + erlang:error(badarg). + nif_cancel(_SRef, _Op, _Ref) -> erlang:error(badarg). -- cgit v1.2.3 From d4c6b555aea77198d662822c7f5a134a16cff8af Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Tue, 31 Jul 2018 12:53:52 +0200 Subject: [socket-nif] Debugged and stuff It seems to work, with atleast some of the cmsg options. More testing needed... OTP-14831 --- erts/emulator/nifs/common/socket_nif.c | 44 +++++++++---- erts/emulator/nifs/common/socket_tarray.c | 8 +-- erts/emulator/nifs/common/socket_tarray.h | 2 +- erts/emulator/nifs/common/socket_util.c | 104 +++++++++++++++++++++++++----- erts/emulator/nifs/common/socket_util.h | 2 + lib/kernel/test/socket_server.erl | 45 +++++++++---- 6 files changed, 159 insertions(+), 46 deletions(-) diff --git a/erts/emulator/nifs/common/socket_nif.c b/erts/emulator/nifs/common/socket_nif.c index 6b6ddb29ca..205b3335e1 100644 --- a/erts/emulator/nifs/common/socket_nif.c +++ b/erts/emulator/nifs/common/socket_nif.c @@ -1875,6 +1875,7 @@ static ERL_NIF_TERM recvmsg_check_result(ErlNifEnv* env, int read, int saveErrno, struct msghdr* msgHdrP, + ErlNifBinary* dataBufP, ErlNifBinary* ctrlBufP, ERL_NIF_TERM recvRef); @@ -4102,7 +4103,7 @@ ERL_NIF_TERM nif_recvmsg(ErlNifEnv* env, /* Extract arguments and perform preliminary validation */ - if ((argc != 4) || + if ((argc != 5) || !enif_get_resource(env, argv[0], sockets, (void**) &descP) || !GET_UINT(env, argv[2], &bufSz) || !GET_UINT(env, argv[3], &ctrlSz) || @@ -4170,14 +4171,15 @@ ERL_NIF_TERM nrecvmsg(ErlNifEnv* env, unsigned int addrLen; ssize_t read; int save_errno; - ErlNifBinary buf, ctrl; int bufSz = (bufLen ? bufLen : descP->rBufSz); int ctrlSz = (ctrlLen ? ctrlLen : descP->rCtrlSz); struct msghdr msgHdr; - struct iovec iov[1]; // Shall we always use 1? + struct iovec iov[1]; // Shall we always use 1? + ErlNifBinary data[1]; // Shall we always use 1? + ErlNifBinary ctrl; SocketAddress addr; - SSDBG( descP, ("SOCKET", "nrecvfrom -> entry with" + SSDBG( descP, ("SOCKET", "nrecvmsg -> entry with" "\r\n bufSz: %d (%d)" "\r\n ctrlSz: %d (%d)" "\r\n flags: %d" @@ -4197,7 +4199,7 @@ ERL_NIF_TERM nrecvmsg(ErlNifEnv* env, /* Allocate the (msg) data buffer: */ - if (!ALLOC_BIN(bufSz, &buf)) + if (!ALLOC_BIN(bufSz, &data[0])) return esock_make_error(env, atom_exalloc); /* Allocate the ctrl (buffer): @@ -4214,8 +4216,8 @@ ERL_NIF_TERM nrecvmsg(ErlNifEnv* env, sys_memzero((char*) &addr, addrLen); sys_memzero((char*) &msgHdr, sizeof(msgHdr)); - iov[0].iov_base = buf.data; - iov[0].iov_len = buf.size; + iov[0].iov_base = data[0].data; + iov[0].iov_len = data[0].size; msgHdr.msg_name = &addr; msgHdr.msg_namelen = addrLen; @@ -4234,7 +4236,8 @@ ERL_NIF_TERM nrecvmsg(ErlNifEnv* env, read, save_errno, &msgHdr, - &ctrl, // Needed for ctrl header decode + data, // Needed for iov encode + &ctrl, // Needed for ctrl header encode recvRef); } @@ -10598,6 +10601,7 @@ ERL_NIF_TERM recvmsg_check_result(ErlNifEnv* env, int read, int saveErrno, struct msghdr* msgHdrP, + ErlNifBinary* dataBufP, ErlNifBinary* ctrlBufP, ERL_NIF_TERM recvRef) { @@ -10680,11 +10684,24 @@ ERL_NIF_TERM recvmsg_check_result(ErlNifEnv* env, */ if ((xres = esock_encode_msghdr(env, read, - msgHdrP, ctrlBufP, - &eMsgHdr)) != NULL) + msgHdrP, dataBufP, ctrlBufP, + &eMsgHdr)) != NULL) { + + SSDBG( descP, + ("SOCKET", + "recvfrom_check_result -> " + "(msghdr) encode failed: %s\r\n", xres) ); + return esock_make_error_str(env, xres); - else + } else { + + SSDBG( descP, + ("SOCKET", + "recvfrom_check_result -> " + "(msghdr) encode ok: %T\r\n", eMsgHdr) ); + return esock_make_ok2(env, eMsgHdr); + } } } @@ -12414,8 +12431,9 @@ int on_load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info) esock_atom_flowinfo = MKA(env, "flowinfo"); esock_atom_inet = MKA(env, "inet"); esock_atom_inet6 = MKA(env, "inet6"); - esock_atom_ip = MKA(env, "iov"); - esock_atom_ipv6 = MKA(env, "ipvp"); + esock_atom_iov = MKA(env, "iov"); + esock_atom_ip = MKA(env, "ip"); + esock_atom_ipv6 = MKA(env, "ipv6"); esock_atom_level = MKA(env, "level"); esock_atom_local = MKA(env, "local"); esock_atom_loopback = MKA(env, "loopback"); diff --git a/erts/emulator/nifs/common/socket_tarray.c b/erts/emulator/nifs/common/socket_tarray.c index bf37e5bc0e..a327e014c4 100644 --- a/erts/emulator/nifs/common/socket_tarray.c +++ b/erts/emulator/nifs/common/socket_tarray.c @@ -63,13 +63,13 @@ void* esock_tarray_create(uint32_t sz) { SocketTArrayInt* tarrayP; - ESOCK_ASSERT( (sz == 0) ); + ESOCK_ASSERT( (sz > 0) ); tarrayP = MALLOC(sizeof(SocketTArrayInt)); - ESOCK_ASSERT( (tarrayP == NULL) ); + ESOCK_ASSERT( (tarrayP != NULL) ); tarrayP->array = MALLOC(sz * sizeof(ERL_NIF_TERM)); - ESOCK_ASSERT( (tarrayP->array == NULL) ); + ESOCK_ASSERT( (tarrayP->array != NULL) ); tarrayP->sz = sz; tarrayP->idx = 0; @@ -131,7 +131,7 @@ void esock_tarray_ensure_fits(SocketTArrayInt* taP, uint32_t needs) uint32_t newSz = (needs < taP->sz) ? 2*taP->sz : 2*needs; void* mem = REALLOC(taP->array, newSz * sizeof(ERL_NIF_TERM)); - ESOCK_ASSERT( (mem == NULL) ); + ESOCK_ASSERT( (mem != NULL) ); taP->sz = newSz; taP->array = (ERL_NIF_TERM*) mem; diff --git a/erts/emulator/nifs/common/socket_tarray.h b/erts/emulator/nifs/common/socket_tarray.h index 2e9506d288..4d78d2ccb7 100644 --- a/erts/emulator/nifs/common/socket_tarray.h +++ b/erts/emulator/nifs/common/socket_tarray.h @@ -33,7 +33,7 @@ extern SocketTArray esock_tarray_create(uint32_t sz); extern void esock_tarray_delete(SocketTArray ta); extern uint32_t esock_tarray_sz(SocketTArray ta); extern void esock_tarray_add(SocketTArray ta, ERL_NIF_TERM t); -extern void esock_tarray_tolist(SocketTArray ta, +extern void esock_tarray_tolist(SocketTArray ta, ErlNifEnv* env, ERL_NIF_TERM* list); diff --git a/erts/emulator/nifs/common/socket_util.c b/erts/emulator/nifs/common/socket_util.c index 5998ff35a4..50cc94c263 100644 --- a/erts/emulator/nifs/common/socket_util.c +++ b/erts/emulator/nifs/common/socket_util.c @@ -40,7 +40,7 @@ * should use the compile debug flag, whatever that is... */ -// #define COMPILE_DEBUG_FLAG_WE_NEED_TO_CHECK 1 +#define COMPILE_DEBUG_FLAG_WE_NEED_TO_CHECK 1 #if defined(COMPILE_DEBUG_FLAG_WE_NEED_TO_CHECK) #define UTIL_DEBUG TRUE #else @@ -85,53 +85,76 @@ extern char* esock_encode_msghdr(ErlNifEnv* env, int read, struct msghdr* msgHdrP, + ErlNifBinary* dataBufP, ErlNifBinary* ctrlBufP, ERL_NIF_TERM* eSockAddr) { char* xres; ERL_NIF_TERM addr, iov, ctrl, flags; + UDBG( ("SUTIL", "esock_encode_msghdr -> entry with" + "\r\n read: %d" + "\r\n", read) ); + if ((xres = esock_encode_sockaddr(env, (SocketAddress*) msgHdrP->msg_name, msgHdrP->msg_namelen, &addr)) != NULL) return xres; + UDBG( ("SUTIL", "esock_encode_msghdr -> try encode iov\r\n") ); if ((xres = esock_encode_iov(env, read, msgHdrP->msg_iov, msgHdrP->msg_iovlen, + dataBufP, &iov)) != NULL) return xres; + UDBG( ("SUTIL", "esock_encode_msghdr -> try encode cmsghdrs\r\n") ); if ((xres = esock_encode_cmsghdrs(env, ctrlBufP, msgHdrP, &ctrl)) != NULL) return xres; + UDBG( ("SUTIL", "esock_encode_msghdr -> try encode flags\r\n") ); if ((xres = esock_encode_mshghdr_flags(env, msgHdrP->msg_flags, &flags)) != NULL) return xres; + UDBG( ("SUTIL", "esock_encode_msghdr -> components encoded:" + "\r\n addr: %T" + "\r\n iov: %T" + "\r\n ctrl: %T" + "\r\n flags: %T" + "\r\n", addr, iov, ctrl, flags) ); { - ERL_NIF_TERM keys[] = {esock_atom_addr, - esock_atom_iov, - esock_atom_ctrl, - esock_atom_flags}; - ERL_NIF_TERM vals[] = {addr, iov, ctrl, flags}; - + ERL_NIF_TERM keys[] = {esock_atom_addr, + esock_atom_iov, + esock_atom_ctrl, + esock_atom_flags}; + ERL_NIF_TERM vals[] = {addr, iov, ctrl, flags}; unsigned int numKeys = sizeof(keys) / sizeof(ERL_NIF_TERM); unsigned int numVals = sizeof(vals) / sizeof(ERL_NIF_TERM); + ERL_NIF_TERM tmp; ESOCK_ASSERT( (numKeys == numVals) ); - if (!MKMA(env, keys, vals, numKeys, eSockAddr)) + UDBG( ("SUTIL", "esock_encode_msghdr -> create msghdr map\r\n") ); + if (!MKMA(env, keys, vals, numKeys, &tmp)) return ESOCK_STR_EINVAL; + UDBG( ("SUTIL", "esock_encode_msghdr -> msghdr: " + "\r\n %T" + "\r\n", tmp) ); + + *eSockAddr = tmp; } + UDBG( ("SUTIL", "esock_encode_msghdr -> done\r\n") ); + return NULL; } @@ -153,6 +176,7 @@ char* esock_encode_iov(ErlNifEnv* env, int read, struct iovec* iov, size_t len, + ErlNifBinary* data, ERL_NIF_TERM* eIOV) { int rem = read; @@ -160,28 +184,51 @@ char* esock_encode_iov(ErlNifEnv* env, BOOLEAN_T done = FALSE; ERL_NIF_TERM a[len]; // At most this length + UDBG( ("SUTIL", "esock_encode_iov -> entry with" + "\r\n read: %d" + "\r\n (IOV) len: %d" + "\r\n", read, len) ); + if (len == 0) { *eIOV = MKEL(env); return NULL; } for (i = 0; (!done) && (i < len); i++) { + UDBG( ("SUTIL", "esock_encode_iov -> process iov:" + "\r\n iov[%d].iov_len: %d" + "\r\n rem: %d" + "\r\n", i, iov[i].iov_len, rem) ); if (iov[i].iov_len == rem) { /* We have the exact amount - we are done */ - a[i] = MKBIN(env, iov[i].iov_base); + UDBG( ("SUTIL", "esock_encode_iov -> exact => done\r\n") ); + a[i] = MKBIN(env, &data[i]); + UDBG( ("SUTIL", "esock_encode_iov -> a[%d]: %T\r\n", i, a[i]) ); + rem = 0; // Besserwisser done = TRUE; } else if (iov[i].iov_len < rem) { /* Filled another buffer - continue */ - a[i] = MKBIN(env, iov[i].iov_base); + UDBG( ("SUTIL", "esock_encode_iov -> filled => continue\r\n") ); + a[i] = MKBIN(env, &data[i]); + rem -= iov[i].iov_len; + UDBG( ("SUTIL", "esock_encode_iov -> a[%d]: %T\r\n", i, a[i]) ); } else if (iov[i].iov_len > rem) { /* Partly filled buffer (=> split) - we are done */ - a[i] = MKBIN(env, iov[i].iov_base); - a[i] = MKSBIN(env, a[i], 0, rem); + ERL_NIF_TERM tmp; + UDBG( ("SUTIL", "esock_encode_iov -> split => done\r\n") ); + tmp = MKBIN(env, &data[i]); + a[i] = MKSBIN(env, tmp, 0, rem); + UDBG( ("SUTIL", "esock_encode_iov -> a[%d]: %T\r\n", i, a[i]) ); + rem = 0; // Besserwisser done = TRUE; } } - *eIOV = MKLA(env, a, i+1); + UDBG( ("SUTIL", "esock_encode_iov -> create the IOV list (%d)\r\n", i) ); + + *eIOV = MKLA(env, a, i); + + UDBG( ("SUTIL", "esock_encode_msghdr -> done\r\n") ); return NULL; } @@ -201,11 +248,11 @@ char* esock_encode_iov(ErlNifEnv* env, * term (which can then in turn be turned into sub-binaries). * * We need the cmsgBufP (even though cmsgHdrP points to it) to be able - * to create sub-binaries (one for each HDR). + * to create sub-binaries (one for each cmsg hdr). * - * The TArray is created with the size of 128, which should be enough. - * But if its not, then it will be automatically realloc'ed during add. - * Once we are done adding hdr's to it, we convert it to a list. + * The TArray (term array) is created with the size of 128, which should + * be enough. But if its not, then it will be automatically realloc'ed during + * add. Once we are done adding hdr's to it, we convert the tarray to a list. */ extern @@ -219,10 +266,16 @@ char* esock_encode_cmsghdrs(ErlNifEnv* env, struct cmsghdr* firstP = CMSG_FIRSTHDR(msgHdrP); struct cmsghdr* currentP; + UDBG( ("SUTIL", "esock_encode_cmsghdrs -> entry\r\n") ); + for (currentP = firstP; currentP != NULL; currentP = CMSG_NXTHDR(msgHdrP, currentP)) { + UDBG( ("SUTIL", "esock_encode_cmsghdrs -> process cmsg header when" + "\r\n TArray Size: %d" + "\r\n", TARRAY_SZ(cmsghdrs)) ); + /* MUST check this since on Linux the returned "cmsg" may actually * go too far! */ @@ -248,6 +301,11 @@ char* esock_encode_cmsghdrs(ErlNifEnv* env, if (esock_encode_protocol(env, currentP->cmsg_level, &level) != NULL) level = MKI(env, currentP->cmsg_level); + UDBG( ("SUTIL", "esock_encode_cmsghdrs -> " + "\r\n level: %T" + "\r\n type: %T" + "\r\n", level, type) ); + /* And finally create the 'cmsghdr' map - * and if successfull add it to the tarray. */ @@ -274,6 +332,10 @@ char* esock_encode_cmsghdrs(ErlNifEnv* env, } } + UDBG( ("SUTIL", "esock_encode_cmsghdrs -> cmsg headers processed when" + "\r\n TArray Size: %d" + "\r\n", TARRAY_SZ(cmsghdrs)) ); + /* The tarray is populated - convert it to a list */ TARRAY_TOLIST(cmsghdrs, env, eCMsgHdr); @@ -294,6 +356,10 @@ char* esock_encode_mshghdr_flags(ErlNifEnv* env, int msgFlags, ERL_NIF_TERM* flags) { + UDBG( ("SUTIL", "esock_encode_cmsghdrs -> entry with" + "\r\n msgFlags: %d (0x%lX)" + "\r\n", msgFlags, msgFlags) ); + if (msgFlags == 0) { *flags = MKEL(env); return NULL; @@ -315,6 +381,10 @@ char* esock_encode_mshghdr_flags(ErlNifEnv* env, if ((msgFlags & MSG_ERRQUEUE) == MSG_ERRQUEUE) TARRAY_ADD(ta, esock_atom_errqueue); + UDBG( ("SUTIL", "esock_encode_cmsghdrs -> flags processed when" + "\r\n TArray size: %d" + "\r\n", TARRAY_SZ(ta)) ); + TARRAY_TOLIST(ta, env, flags); return NULL; diff --git a/erts/emulator/nifs/common/socket_util.h b/erts/emulator/nifs/common/socket_util.h index af0bf70d8f..5a79eb3d09 100644 --- a/erts/emulator/nifs/common/socket_util.h +++ b/erts/emulator/nifs/common/socket_util.h @@ -39,6 +39,7 @@ extern char* esock_encode_msghdr(ErlNifEnv* env, int read, struct msghdr* msgHdrP, + ErlNifBinary* dataBufP, ErlNifBinary* ctrlBufP, ERL_NIF_TERM* eSockAddr); extern @@ -46,6 +47,7 @@ char* esock_encode_iov(ErlNifEnv* env, int read, struct iovec* iov, size_t len, + ErlNifBinary* data, ERL_NIF_TERM* eIOV); extern char* esock_encode_cmsghdrs(ErlNifEnv* env, diff --git a/lib/kernel/test/socket_server.erl b/lib/kernel/test/socket_server.erl index 88b63ecf2d..5d00f78b2a 100644 --- a/lib/kernel/test/socket_server.erl +++ b/lib/kernel/test/socket_server.erl @@ -33,7 +33,7 @@ -record(manager, {socket, peek, acceptors, handler_id, handlers}). -record(acceptor, {id, socket, manager}). --record(handler, {socket, peek, type, manager}). +-record(handler, {socket, peek, msg, type, manager}). -define(NUM_ACCEPTORS, 5). @@ -179,7 +179,7 @@ do_manager_init(Domain, dgram = Type, Proto, Peek) -> {ok, Name} -> f("~p", [Name]); {error, R} -> f("error: ~p", [R]) end]), - case handler_start(1, Sock, Peek) of + case handler_start(1, true, Sock, Peek) of {ok, {Pid, MRef}} -> i("handler (~p) started", [Pid]), handler_continue(Pid), @@ -405,7 +405,7 @@ manager_handle_request(#manager{peek = Peek, handlers = Handlers} = M, Pid, Ref, {start_handler, Sock}) -> i("try start handler (~w)", [HID]), - case handler_start(HID, Sock, Peek) of + case handler_start(HID, false, Sock, Peek) of {ok, {HPid, HMRef}} -> i("handler ~w started", [HID]), manager_reply(Pid, Ref, {ok, HPid}), @@ -564,10 +564,10 @@ acceptor_handle_accept_success(#acceptor{manager = Manager}, Sock) -> %% ========================================================================= -handler_start(ID, Sock, Peek) -> +handler_start(ID, Msg, Sock, Peek) -> Self = self(), H = {Pid, _} = spawn_monitor(fun() -> - handler_init(Self, ID, Peek, Sock) + handler_init(Self, ID, Msg, Peek, Sock) end), receive {handler, Pid, ok} -> @@ -591,7 +591,7 @@ handler_reply(Pid, Ref, Reply) -> ?LIB:reply(handler, Pid, Ref, Reply). -handler_init(Manager, ID, Peek, Sock) -> +handler_init(Manager, ID, Msg, Peek, Sock) -> put(sname, f("handler:~w", [ID])), i("starting"), Manager ! {handler, self(), ok}, @@ -720,20 +720,26 @@ handler_init(Manager, ID, Peek, Sock) -> RtHdr, AuthHdr, HopLimit, HopOpts, DstOpts, FlowInfo, UHops]), - handler_loop(#handler{peek = Peek, + ok = soip(Sock, pktinfo, true), + ok = soip(Sock, recvtos, true), + ok = soip(Sock, recvttl, true), + %% ok = soip(Sock, recvopts, true), + + handler_loop(#handler{msg = Msg, + peek = Peek, manager = Manager, type = Type, socket = Sock}) end. -%% so(Sock, Lvl, Opt, Val) -> -%% ok = socket:setopt(Sock, Lvl, Opt, Val). +so(Sock, Lvl, Opt, Val) -> + ok = socket:setopt(Sock, Lvl, Opt, Val). %% soso(Sock, Opt, Val) -> %% so(Sock, socket, Opt, Val). -%% soip(Sock, Opt, Val) -> -%% so(Sock, ip, Opt, Val). +soip(Sock, Opt, Val) -> + so(Sock, ip, Opt, Val). %% soipv6(Sock, Opt, Val) -> %% so(Sock, ipv6, Opt, Val). @@ -779,6 +785,23 @@ recv(#handler{peek = true, socket = Sock, type = stream}) -> peek_recv(Sock); recv(#handler{peek = false, socket = Sock, type = stream}) -> do_recv(Sock); +recv(#handler{socket = Sock, msg = true, type = dgram}) -> + ok = socket:setopt(Sock, otp, debug, true), + case socket:recvmsg(Sock) of + {ok, #{addr := Source, + iov := [Data], + ctrl := CMsgHdrs, + flags := Flags}} -> + ok = socket:setopt(Sock, otp, debug, false), + i("received message: " + "~n CMsgHdrs: ~p" + "~n Flags: ~p", [CMsgHdrs, Flags]), + {ok, {Source, Data}}; + {ok, X} -> + {error, {unexpected, X}}; + {error, _} = ERROR -> + ERROR + end; recv(#handler{peek = Peek, socket = Sock, type = dgram}) when (Peek =:= true) -> %% ok = socket:setopt(Sock, otp, debug, true), -- cgit v1.2.3 From 25c38eff5c1e8d4dc6325afa62031874e23262dc Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Tue, 31 Jul 2018 18:01:55 +0200 Subject: [socket-nif] Add more control message decoding Added decoding of the "known" control message options: pktinfo, recvtos, recvttl and recvorigdstaddr). OTP-14831 --- erts/emulator/nifs/common/socket_int.h | 12 + erts/emulator/nifs/common/socket_nif.c | 878 +++++++++++++++++++++++++------- erts/emulator/nifs/common/socket_util.c | 259 +--------- erts/emulator/nifs/common/socket_util.h | 17 - lib/kernel/test/socket_server.erl | 18 +- 5 files changed, 728 insertions(+), 456 deletions(-) diff --git a/erts/emulator/nifs/common/socket_int.h b/erts/emulator/nifs/common/socket_int.h index 18e94e80ef..80a9eb0734 100644 --- a/erts/emulator/nifs/common/socket_int.h +++ b/erts/emulator/nifs/common/socket_int.h @@ -105,6 +105,7 @@ typedef unsigned int BOOLEAN_T; */ extern ERL_NIF_TERM esock_atom_addr; extern ERL_NIF_TERM esock_atom_any; +extern ERL_NIF_TERM esock_atom_credentials; extern ERL_NIF_TERM esock_atom_ctrl; extern ERL_NIF_TERM esock_atom_ctrunc; extern ERL_NIF_TERM esock_atom_data; @@ -117,6 +118,7 @@ extern ERL_NIF_TERM esock_atom_false; extern ERL_NIF_TERM esock_atom_family; extern ERL_NIF_TERM esock_atom_flags; extern ERL_NIF_TERM esock_atom_flowinfo; +extern ERL_NIF_TERM esock_atom_ifindex; extern ERL_NIF_TERM esock_atom_inet; extern ERL_NIF_TERM esock_atom_inet6; extern ERL_NIF_TERM esock_atom_iov; @@ -125,20 +127,30 @@ extern ERL_NIF_TERM esock_atom_ipv6; extern ERL_NIF_TERM esock_atom_level; extern ERL_NIF_TERM esock_atom_local; extern ERL_NIF_TERM esock_atom_loopback; +extern ERL_NIF_TERM esock_atom_lowdelay; +extern ERL_NIF_TERM esock_atom_mincost; extern ERL_NIF_TERM esock_atom_ok; extern ERL_NIF_TERM esock_atom_oob; +extern ERL_NIF_TERM esock_atom_origdstaddr; extern ERL_NIF_TERM esock_atom_path; +extern ERL_NIF_TERM esock_atom_pktinfo; extern ERL_NIF_TERM esock_atom_port; extern ERL_NIF_TERM esock_atom_protocol; extern ERL_NIF_TERM esock_atom_raw; extern ERL_NIF_TERM esock_atom_rdm; +extern ERL_NIF_TERM esock_atom_reliability; +extern ERL_NIF_TERM esock_atom_rights; extern ERL_NIF_TERM esock_atom_scope_id; extern ERL_NIF_TERM esock_atom_sctp; extern ERL_NIF_TERM esock_atom_seqpacket; +extern ERL_NIF_TERM esock_atom_spec_dst; extern ERL_NIF_TERM esock_atom_stream; extern ERL_NIF_TERM esock_atom_tcp; +extern ERL_NIF_TERM esock_atom_throughput; +extern ERL_NIF_TERM esock_atom_tos; extern ERL_NIF_TERM esock_atom_true; extern ERL_NIF_TERM esock_atom_trunc; +extern ERL_NIF_TERM esock_atom_ttl; extern ERL_NIF_TERM esock_atom_type; extern ERL_NIF_TERM esock_atom_udp; extern ERL_NIF_TERM esock_atom_undefined; diff --git a/erts/emulator/nifs/common/socket_nif.c b/erts/emulator/nifs/common/socket_nif.c index 205b3335e1..4b4082062b 100644 --- a/erts/emulator/nifs/common/socket_nif.c +++ b/erts/emulator/nifs/common/socket_nif.c @@ -317,6 +317,7 @@ static void (*esock_sctp_freepaddrs)(struct sockaddr *addrs) = NULL; #include #include "socket_dbg.h" +#include "socket_tarray.h" #include "socket_int.h" #include "socket_util.h" @@ -1884,97 +1885,42 @@ static ERL_NIF_TERM nfinalize_connection(ErlNifEnv* env, static ERL_NIF_TERM nfinalize_close(ErlNifEnv* env, SocketDescriptor* descP); -/* -static char* decode_in_sockaddr(ErlNifEnv* env, - ERL_NIF_TERM eSockAddr, - SocketAddress* sockAddrP, - unsigned int* addrLenP); -static char* decode_in4_sockaddr(ErlNifEnv* env, - const ERL_NIF_TERM* eIn4SockAddr, - SocketAddress* sockAddrP, - unsigned int* addrLenP); -static char* decode_in4_sockaddr_atomaddr(ErlNifEnv* env, - ERL_NIF_TERM eAddr, - int port, - SocketAddress* sockAddrP, - unsigned int* addrLenP); -static char* decode_in4_sockaddr_addr(ErlNifEnv* env, - ERL_NIF_TERM eAddr, - int port, - SocketAddress* sockAddrP, - unsigned int* addrLenP); -#if defined(HAVE_IN6) && defined(AF_INET6) -static char* decode_in6_sockaddr(ErlNifEnv* env, - const ERL_NIF_TERM* eIn6SockAddr, - SocketAddress* sockAddrP, - unsigned int* addrLenP); -static char* decode_in6_sockaddr_atomaddr(ErlNifEnv* env, - ERL_NIF_TERM eAddr, - int port, - unsigned int flowInfo, - unsigned int scopeId, - SocketAddress* sockAddrP, - unsigned int* addrLenP); -/ * Decode an in6_sockaddr where the address field is a tuple * / -static char* decode_in6_sockaddr_addr(ErlNifEnv* env, - ERL_NIF_TERM eAddr, - int port, - unsigned int flowInfo, - unsigned int scopeId, - SocketAddress* sockAddrP, - unsigned int* addrLenP); -#endif -*/ -/* -static char* decode_laddress(ErlNifEnv* env, - int domain, - ERL_NIF_TERM localAddr, - SocketAddress* localP, - unsigned int* addrLenP); -static char* decode_laddress_binary(ErlNifEnv* env, - int domain, - ERL_NIF_TERM localAddr, - SocketAddress* localP, - unsigned int* addrLenP); -*/ -/* -static char* decode_address_tuple(ErlNifEnv* env, - int domain, - const ERL_NIF_TERM* addrt, - int port, - SocketAddress* localP, - unsigned int* addrLenP); -static char* decode_address_atom(ErlNifEnv* env, - int domain, - char* addr, - int addrLen, - int port, - SocketAddress* localP, - unsigned int* addrLenP); -*/ -/* -static char* decode_send_addr(ErlNifEnv* env, - int domain, - ERL_NIF_TERM addr, - int port, - SocketAddress** toAddrP, - unsigned int* addrLenP); -*/ -/* -static char* decode_send_addr_tuple(ErlNifEnv* env, - int domain, - ERL_NIF_TERM addr, - int port, - SocketAddress* toAddrP, - unsigned int* addrLenP); -*/ -/* -static void encode_address(ErlNifEnv* env, - SocketAddress* fromAddrP, - unsigned int fromAddrLen, - ERL_NIF_TERM* fromDomainT, - ERL_NIF_TERM* fromSourceT); -*/ +extern char* encode_msghdr(ErlNifEnv* env, + SocketDescriptor* descP, + int read, + struct msghdr* msgHdrP, + ErlNifBinary* dataBufP, + ErlNifBinary* ctrlBufP, + ERL_NIF_TERM* eSockAddr); +extern char* encode_cmsghdrs(ErlNifEnv* env, + SocketDescriptor* descP, + ErlNifBinary* cmsgBinP, + struct msghdr* msgHdrP, + ERL_NIF_TERM* eCMsgHdr); +static char* encode_cmsghdr_type(ErlNifEnv* env, + int level, + int type, + ERL_NIF_TERM* eType); +static char* encode_cmsghdr_data(ErlNifEnv* env, + ERL_NIF_TERM ctrlBuf, + int level, + int type, + unsigned char* dataP, + size_t dataPos, + size_t dataLen, + ERL_NIF_TERM* eCMsgHdrData); +static char* encode_cmsghdr_data_ip(ErlNifEnv* env, + ERL_NIF_TERM ctrlBuf, + int type, + unsigned char* dataP, + size_t dataPos, + size_t dataLen, + ERL_NIF_TERM* eCMsgHdrData); +extern char* encode_msghdr_flags(ErlNifEnv* env, + SocketDescriptor* descP, + int msgFlags, + ERL_NIF_TERM* flags); + static BOOLEAN_T decode_sock_linger(ErlNifEnv* env, ERL_NIF_TERM eVal, struct linger* valP); @@ -2215,11 +2161,6 @@ static char str_true[] = "true"; static char str_usec[] = "usec"; static char str_want[] = "want"; -static char str_lowdelay[] = "lowdelay"; -static char str_throughput[] = "throughput"; -static char str_reliability[] = "reliability"; -static char str_mincost[] = "mincost"; - /* (special) error string constants */ static char str_eisconn[] = "eisconn"; static char str_enotclosing[] = "enotclosing"; @@ -2235,6 +2176,7 @@ static char str_exsend[] = "exsend"; // failed send /* *** "Global" Atoms *** */ ERL_NIF_TERM esock_atom_addr; ERL_NIF_TERM esock_atom_any; +ERL_NIF_TERM esock_atom_credentials; ERL_NIF_TERM esock_atom_ctrl; ERL_NIF_TERM esock_atom_ctrunc; ERL_NIF_TERM esock_atom_data; @@ -2247,6 +2189,7 @@ ERL_NIF_TERM esock_atom_false; ERL_NIF_TERM esock_atom_family; ERL_NIF_TERM esock_atom_flags; ERL_NIF_TERM esock_atom_flowinfo; +ERL_NIF_TERM esock_atom_ifindex; ERL_NIF_TERM esock_atom_inet; ERL_NIF_TERM esock_atom_inet6; ERL_NIF_TERM esock_atom_iov; @@ -2255,20 +2198,30 @@ ERL_NIF_TERM esock_atom_ipv6; ERL_NIF_TERM esock_atom_level; ERL_NIF_TERM esock_atom_local; ERL_NIF_TERM esock_atom_loopback; +ERL_NIF_TERM esock_atom_lowdelay; +ERL_NIF_TERM esock_atom_mincost; ERL_NIF_TERM esock_atom_ok; ERL_NIF_TERM esock_atom_oob; +ERL_NIF_TERM esock_atom_origdstaddr; ERL_NIF_TERM esock_atom_path; -ERL_NIF_TERM esock_atom_protocol; +ERL_NIF_TERM esock_atom_pktinfo; ERL_NIF_TERM esock_atom_port; +ERL_NIF_TERM esock_atom_protocol; ERL_NIF_TERM esock_atom_raw; ERL_NIF_TERM esock_atom_rdm; +ERL_NIF_TERM esock_atom_rights; +ERL_NIF_TERM esock_atom_reliability; ERL_NIF_TERM esock_atom_scope_id; ERL_NIF_TERM esock_atom_sctp; ERL_NIF_TERM esock_atom_seqpacket; +ERL_NIF_TERM esock_atom_spec_dst; ERL_NIF_TERM esock_atom_stream; ERL_NIF_TERM esock_atom_tcp; +ERL_NIF_TERM esock_atom_throughput; +ERL_NIF_TERM esock_atom_tos; ERL_NIF_TERM esock_atom_true; ERL_NIF_TERM esock_atom_trunc; +ERL_NIF_TERM esock_atom_ttl; ERL_NIF_TERM esock_atom_type; ERL_NIF_TERM esock_atom_udp; ERL_NIF_TERM esock_atom_undefined; @@ -2341,11 +2294,6 @@ static ERL_NIF_TERM atom_true; static ERL_NIF_TERM atom_usec; static ERL_NIF_TERM atom_want; -static ERL_NIF_TERM atom_lowdelay; -static ERL_NIF_TERM atom_throughput; -static ERL_NIF_TERM atom_reliability; -static ERL_NIF_TERM atom_mincost; - static ERL_NIF_TERM atom_eisconn; static ERL_NIF_TERM atom_enotclosing; static ERL_NIF_TERM atom_enotconn; @@ -10683,9 +10631,9 @@ ERL_NIF_TERM recvmsg_check_result(ErlNifEnv* env, * */ - if ((xres = esock_encode_msghdr(env, read, - msgHdrP, dataBufP, ctrlBufP, - &eMsgHdr)) != NULL) { + if ((xres = encode_msghdr(env, descP, + read, msgHdrP, dataBufP, ctrlBufP, + &eMsgHdr)) != NULL) { SSDBG( descP, ("SOCKET", @@ -10708,6 +10656,586 @@ ERL_NIF_TERM recvmsg_check_result(ErlNifEnv* env, + +/* +++ encode_msghdr +++ + * + * Encode a msghdr (recvmsg). In erlang its represented as + * a map, which has a specific set of attributes: + * + * addr (source address) - sockaddr() + * iov - [binary()] + * ctrl - [cmsghdr()] + * flags - msghdr_flags() + */ + +extern +char* encode_msghdr(ErlNifEnv* env, + SocketDescriptor* descP, + int read, + struct msghdr* msgHdrP, + ErlNifBinary* dataBufP, + ErlNifBinary* ctrlBufP, + ERL_NIF_TERM* eSockAddr) +{ + char* xres; + ERL_NIF_TERM addr, iov, ctrl, flags; + + SSDBG( descP, + ("SOCKET", "encode_msghdr -> entry with" + "\r\n read: %d" + "\r\n", read) ); + + if ((xres = esock_encode_sockaddr(env, + (SocketAddress*) msgHdrP->msg_name, + msgHdrP->msg_namelen, + &addr)) != NULL) + return xres; + + SSDBG( descP, ("SOCKET", "encode_msghdr -> try encode iov\r\n") ); + if ((xres = esock_encode_iov(env, + read, + msgHdrP->msg_iov, + msgHdrP->msg_iovlen, + dataBufP, + &iov)) != NULL) + return xres; + + SSDBG( descP, ("SOCKET", "encode_msghdr -> try encode cmsghdrs\r\n") ); + if ((xres = encode_cmsghdrs(env, descP, ctrlBufP, msgHdrP, &ctrl)) != NULL) + return xres; + + SSDBG( descP, ("SOCKET", "encode_msghdr -> try encode flags\r\n") ); + if ((xres = encode_msghdr_flags(env, descP, msgHdrP->msg_flags, &flags)) != NULL) + return xres; + + SSDBG( descP, + ("SOCKET", "encode_msghdr -> components encoded:" + "\r\n addr: %T" + "\r\n iov: %T" + "\r\n ctrl: %T" + "\r\n flags: %T" + "\r\n", addr, iov, ctrl, flags) ); + { + ERL_NIF_TERM keys[] = {esock_atom_addr, + esock_atom_iov, + esock_atom_ctrl, + esock_atom_flags}; + ERL_NIF_TERM vals[] = {addr, iov, ctrl, flags}; + unsigned int numKeys = sizeof(keys) / sizeof(ERL_NIF_TERM); + unsigned int numVals = sizeof(vals) / sizeof(ERL_NIF_TERM); + ERL_NIF_TERM tmp; + + ESOCK_ASSERT( (numKeys == numVals) ); + + SSDBG( descP, ("SOCKET", "encode_msghdr -> create msghdr map\r\n") ); + if (!MKMA(env, keys, vals, numKeys, &tmp)) + return ESOCK_STR_EINVAL; + + SSDBG( descP, ("SOCKET", "encode_msghdr -> msghdr: " + "\r\n %T" + "\r\n", tmp) ); + + *eSockAddr = tmp; + } + + SSDBG( descP, ("SOCKET", "encode_msghdr -> done\r\n") ); + + return NULL; +} + + + +/* +++ encode_cmsghdrs +++ + * + * Encode a list of cmsghdr(). There can be 0 or more cmsghdr "blocks". + * + * Our "problem" is that we have no idea how many control messages + * we have. + * + * The cmsgHdrP arguments points to the start of the control data buffer, + * an actual binary. Its the only way to create sub-binaries. So, what we + * need to continue processing this is to tern that into an binary erlang + * term (which can then in turn be turned into sub-binaries). + * + * We need the cmsgBufP (even though cmsgHdrP points to it) to be able + * to create sub-binaries (one for each cmsg hdr). + * + * The TArray (term array) is created with the size of 128, which should + * be enough. But if its not, then it will be automatically realloc'ed during + * add. Once we are done adding hdr's to it, we convert the tarray to a list. + */ + +extern +char* encode_cmsghdrs(ErlNifEnv* env, + SocketDescriptor* descP, + ErlNifBinary* cmsgBinP, + struct msghdr* msgHdrP, + ERL_NIF_TERM* eCMsgHdr) +{ + ERL_NIF_TERM ctrlBuf = MKBIN(env, cmsgBinP); // The *entire* binary + SocketTArray cmsghdrs = TARRAY_CREATE(128); + struct cmsghdr* firstP = CMSG_FIRSTHDR(msgHdrP); + struct cmsghdr* currentP; + + SSDBG( descP, ("SOCKET", "encode_cmsghdrs -> entry\r\n") ); + + for (currentP = firstP; + currentP != NULL; + currentP = CMSG_NXTHDR(msgHdrP, currentP)) { + + SSDBG( descP, + ("SOCKET", "encode_cmsghdrs -> process cmsg header when" + "\r\n TArray Size: %d" + "\r\n", TARRAY_SZ(cmsghdrs)) ); + + /* MUST check this since on Linux the returned "cmsg" may actually + * go too far! + */ + if (((CHARP(currentP) + currentP->cmsg_len) - CHARP(firstP)) > + msgHdrP->msg_controllen) { + /* Ouch, fatal error - give up + * We assume we cannot trust any data if this is wrong. + */ + TARRAY_DELETE(cmsghdrs); + return ESOCK_STR_EINVAL; + } else { + ERL_NIF_TERM level, type, data; + unsigned char* dataP = (unsigned char*) CMSG_DATA(currentP); + size_t dataPos = dataP - cmsgBinP->data; + size_t dataLen = currentP->cmsg_len - (CHARP(currentP)-CHARP(dataP)); + + SSDBG( descP, + ("SOCKET", "encode_cmsghdrs -> cmsg header data: " + "\r\n dataPos: %d" + "\r\n dataLen: %d" + "\r\n", dataPos, dataLen) ); + + /* We can't give up just because its an unknown protocol, + * so if its a protocol we don't know, we return its integer + * value and leave it to the user. + */ + if (esock_encode_protocol(env, currentP->cmsg_level, &level) != NULL) + level = MKI(env, currentP->cmsg_level); + + if (encode_cmsghdr_type(env, + currentP->cmsg_level, currentP->cmsg_type, + &type) != NULL) + type = MKI(env, currentP->cmsg_type); + + if (encode_cmsghdr_data(env, ctrlBuf, + currentP->cmsg_level, + currentP->cmsg_type, + dataP, dataPos, dataLen, + &data) != NULL) + data = MKSBIN(env, ctrlBuf, dataPos, dataLen); + + SSDBG( descP, + ("SOCKET", "encode_cmsghdrs -> " + "\r\n level: %T" + "\r\n type: %T" + "\r\n data: %T" + "\r\n", level, type, data) ); + + /* And finally create the 'cmsghdr' map - + * and if successfull add it to the tarray. + */ + { + ERL_NIF_TERM keys[] = {esock_atom_level, + esock_atom_type, + esock_atom_data}; + ERL_NIF_TERM vals[] = {level, type, data}; + unsigned int numKeys = sizeof(keys) / sizeof(ERL_NIF_TERM); + unsigned int numVals = sizeof(vals) / sizeof(ERL_NIF_TERM); + ERL_NIF_TERM cmsgHdr; + + /* Guard agains cut-and-paste errors */ + ESOCK_ASSERT( (numKeys == numVals) ); + + if (!MKMA(env, keys, vals, numKeys, &cmsgHdr)) { + TARRAY_DELETE(cmsghdrs); + return ESOCK_STR_EINVAL; + } + + /* And finally add it to the list... */ + TARRAY_ADD(cmsghdrs, cmsgHdr); + } + } + } + + SSDBG( descP, + ("SOCKET", "encode_cmsghdrs -> cmsg headers processed when" + "\r\n TArray Size: %d" + "\r\n", TARRAY_SZ(cmsghdrs)) ); + + /* The tarray is populated - convert it to a list */ + TARRAY_TOLIST(cmsghdrs, env, eCMsgHdr); + + return NULL; +} + + + +/* +++ encode_cmsghdr_type +++ + * + * Encode the type part of the cmsghdr(). + * + */ + +static +char* encode_cmsghdr_type(ErlNifEnv* env, + int level, + int type, + ERL_NIF_TERM* eType) +{ + char* xres = NULL; + + switch (level) { + case SOL_SOCKET: + switch (type) { +#if defined(SCM_RIGHTS) + case SCM_RIGHTS: + *eType = esock_atom_rights; + break; +#endif + +#if defined(SCM_CREDENTIALS) + case SCM_CREDENTIALS: + *eType = esock_atom_credentials; + break; +#endif + + default: + xres = ESOCK_STR_EINVAL; + break; + } + break; + +#if defined(SOL_IP) + case SOL_IP: +#else + case IPPROTO_IP: +#endif + switch (type) { +#if defined(IP_TOS) + case IP_TOS: + *eType = esock_atom_tos; + break; +#endif + +#if defined(IP_TTL) + case IP_TTL: + *eType = esock_atom_ttl; + break; +#endif + +#if defined(IP_PKTINFO) + case IP_PKTINFO: + *eType = esock_atom_pktinfo; + break; +#endif + +#if defined(IP_ORIGDSTADDR) + case IP_ORIGDSTADDR: + *eType = esock_atom_origdstaddr; + break; +#endif + + default: + xres = ESOCK_STR_EINVAL; + break; + } + break; + +#if defined(SOL_IPV6) + case SOL_IPV6: + switch (type) { + default: + xres = ESOCK_STR_EINVAL; + break; + } + break; +#endif + + case IPPROTO_TCP: + switch (type) { + default: + xres = ESOCK_STR_EINVAL; + break; + } + break; + + case IPPROTO_UDP: + switch (type) { + default: + xres = ESOCK_STR_EINVAL; + break; + } + break; + +#if defined(HAVE_SCTP) + case IPPROTO_SCTP: + switch (type) { + default: + xres = ESOCK_STR_EINVAL; + break; + } + break; +#endif + + default: + xres = ESOCK_STR_EINVAL; + break; + } + + return xres; +} + + + +/* +++ encode_cmsghdr_data +++ + * + * Encode the data part of the cmsghdr(). + * + */ + +static +char* encode_cmsghdr_data(ErlNifEnv* env, + ERL_NIF_TERM ctrlBuf, + int level, + int type, + unsigned char* dataP, + size_t dataPos, + size_t dataLen, + ERL_NIF_TERM* eCMsgHdrData) +{ + char* xres; + + switch (level) { + /* + #if defined(SOL_SOCKET) + case SOL_SOCKET: + xres = encode_cmsghdr_data_socket(env, type, dataP, eCMsgHdrData); + break; + #endif + */ + +#if defined(SOL_IP) + case SOL_IP: +#else + case IPPROTO_IP: +#endif + xres = encode_cmsghdr_data_ip(env, ctrlBuf, type, + dataP, dataPos, dataLen, + eCMsgHdrData); + break; + + /* + #if defined(SOL_IPV6) + case SOL_IPV6: + xres = encode_cmsghdr_data_ipv6(env, type, dataP, eCMsgHdrData); + break; + #endif + */ + + /* + case IPPROTO_TCP: + xres = encode_cmsghdr_data_tcp(env, type, dataP, eCMsgHdrData); + break; + */ + + /* + case IPPROTO_UDP: + xres = encode_cmsghdr_data_udp(env, type, dataP, eCMsgHdrData); + break; + */ + + /* + #if defined(HAVE_SCTP) + case IPPROTO_SCTP: + xres = encode_cmsghdr_data_sctp(env, type, dataP, eCMsgHdrData); + break; + #endif + */ + + default: + *eCMsgHdrData = MKSBIN(env, ctrlBuf, dataPos, dataLen); + xres = NULL; + break; + } + + return xres; +} + + + +/* +++ encode_cmsghdr_data_ip +++ + * + * Encode the data part when protocol = IP of the cmsghdr(). + * + */ + +static +char* encode_cmsghdr_data_ip(ErlNifEnv* env, + ERL_NIF_TERM ctrlBuf, + int type, + unsigned char* dataP, + size_t dataPos, + size_t dataLen, + ERL_NIF_TERM* eCMsgHdrData) +{ + char* xres; + + switch (type) { +#if defined(IP_TOS) + case IP_TOS: + { + unsigned char tos = IPTOS_TOS(*dataP); + switch (tos) { + case IPTOS_LOWDELAY: + *eCMsgHdrData = esock_atom_lowdelay; + break; + case IPTOS_THROUGHPUT: + *eCMsgHdrData = esock_atom_throughput; + break; + case IPTOS_RELIABILITY: + *eCMsgHdrData = esock_atom_reliability; + break; + case IPTOS_MINCOST: + *eCMsgHdrData = esock_atom_mincost; + break; + default: + *eCMsgHdrData = MKUI(env, tos); + break; + } + } + break; +#endif + +#if defined(IP_TTL) + case IP_TTL: + { + int ttl = *((int*) dataP); + *eCMsgHdrData = MKI(env, ttl); + } + break; +#endif + +#if defined(IP_PKTINFO) + case IP_PKTINFO: + { + struct in_pktinfo* pktInfoP = (struct in_pktinfo*) dataP; + ERL_NIF_TERM ifIndex = MKUI(env, pktInfoP->ipi_ifindex); + ERL_NIF_TERM specDst, addr; + + if ((xres = esock_encode_ip4_address(env, + &pktInfoP->ipi_spec_dst, + &specDst)) != NULL) { + *eCMsgHdrData = esock_atom_undefined; + return xres; + } + + if ((xres = esock_encode_ip4_address(env, + &pktInfoP->ipi_addr, + &addr)) != NULL) { + *eCMsgHdrData = esock_atom_undefined; + return xres; + } + + + { + ERL_NIF_TERM keys[] = {esock_atom_ifindex, + esock_atom_spec_dst, + esock_atom_addr}; + ERL_NIF_TERM vals[] = {ifIndex, specDst, addr}; + unsigned int numKeys = sizeof(keys) / sizeof(ERL_NIF_TERM); + unsigned int numVals = sizeof(vals) / sizeof(ERL_NIF_TERM); + + ESOCK_ASSERT( (numKeys == numVals) ); + + if (!MKMA(env, keys, vals, numKeys, eCMsgHdrData)) { + *eCMsgHdrData = esock_atom_undefined; + return ESOCK_STR_EINVAL; + } + } + } + break; +#endif + +#if defined(IP_ORIGDSTADDR) + case IP_ORIGDSTADDR: + if ((xres = esock_encode_sockaddr_in4(env, + (struct sockaddr_in*) dataP, + dataLen, + eCMsgHdrData)) != NULL) { + *eCMsgHdrData = esock_atom_undefined; + return xres; + } + break; +#endif + + default: + *eCMsgHdrData = MKSBIN(env, ctrlBuf, dataPos, dataLen); + break; + } + + return NULL; +} + + + +/* +++ encode_msghdr_flags +++ + * + * Encode a list of msghdr_flag(). + * + * The following flags are handled: eor | trunc | ctrunc | oob | errqueue. + */ + +extern +char* encode_msghdr_flags(ErlNifEnv* env, + SocketDescriptor* descP, + int msgFlags, + ERL_NIF_TERM* flags) +{ + SSDBG( descP, + ("SOCKET", "encode_cmsghdrs_flags -> entry with" + "\r\n msgFlags: %d (0x%lX)" + "\r\n", msgFlags, msgFlags) ); + + if (msgFlags == 0) { + *flags = MKEL(env); + return NULL; + } else { + SocketTArray ta = TARRAY_CREATE(10); // Just to be on the safe side + + if ((msgFlags & MSG_EOR) == MSG_EOR) + TARRAY_ADD(ta, esock_atom_eor); + + if ((msgFlags & MSG_TRUNC) == MSG_TRUNC) + TARRAY_ADD(ta, esock_atom_trunc); + + if ((msgFlags & MSG_CTRUNC) == MSG_CTRUNC) + TARRAY_ADD(ta, esock_atom_ctrunc); + + if ((msgFlags & MSG_OOB) == MSG_OOB) + TARRAY_ADD(ta, esock_atom_oob); + + if ((msgFlags & MSG_ERRQUEUE) == MSG_ERRQUEUE) + TARRAY_ADD(ta, esock_atom_errqueue); + + SSDBG( descP, + ("SOCKET", "esock_encode_cmsghdrs -> flags processed when" + "\r\n TArray size: %d" + "\r\n", TARRAY_SZ(ta)) ); + + TARRAY_TOLIST(ta, env, flags); + + return NULL; + } +} + + + + /* +++ decode the linger value +++ * The (socket) linger option is provided as a two tuple: * @@ -10761,38 +11289,24 @@ BOOLEAN_T decode_ip_tos(ErlNifEnv* env, ERL_NIF_TERM eVal, int* val) BOOLEAN_T result = FALSE; if (IS_ATOM(env, eVal)) { - unsigned int len; - char b[sizeof(str_reliability)+1]; // Just in case... - if (!(GET_ATOM_LEN(env, eVal, &len) && - (len > 0) && - (len <= (sizeof(str_reliability))))) { - *val = -1; - return FALSE; - } - - if (!GET_ATOM(env, eVal, b, sizeof(b))) { - *val = -1; - return FALSE; - } - - if (strncmp(b, str_lowdelay, len) == 0) { - *val = IPTOS_LOWDELAY; + if (COMPARE(eVal, esock_atom_lowdelay) == 0) { + *val = IPTOS_LOWDELAY; result = TRUE; - } else if (strncmp(b, str_throughput, len) == 0) { - *val = IPTOS_THROUGHPUT; + } else if (COMPARE(eVal, esock_atom_throughput) == 0) { + *val = IPTOS_THROUGHPUT; result = TRUE; - } else if (strncmp(b, str_reliability, len) == 0) { - *val = IPTOS_RELIABILITY; + } else if (COMPARE(eVal, esock_atom_reliability) == 0) { + *val = IPTOS_RELIABILITY; result = TRUE; - } else if (strncmp(b, str_mincost, len) == 0) { - *val = IPTOS_MINCOST; + } else if (COMPARE(eVal, esock_atom_mincost) == 0) { + *val = IPTOS_MINCOST; result = TRUE; } else { - *val = -1; + *val = -1; result = FALSE; } - + } else if (IS_NUM(env, eVal)) { if (GET_INT(env, eVal, val)) { @@ -11095,21 +11609,21 @@ ERL_NIF_TERM encode_ip_tos(ErlNifEnv* env, int val) { ERL_NIF_TERM result; - switch (val) { + switch (IPTOS_TOS(val)) { case IPTOS_LOWDELAY: - result = esock_make_ok2(env, atom_lowdelay); + result = esock_make_ok2(env, esock_atom_lowdelay); break; case IPTOS_THROUGHPUT: - result = esock_make_ok2(env, atom_throughput); + result = esock_make_ok2(env, esock_atom_throughput); break; case IPTOS_RELIABILITY: - result = esock_make_ok2(env, atom_reliability); + result = esock_make_ok2(env, esock_atom_reliability); break; case IPTOS_MINCOST: - result = esock_make_ok2(env, atom_mincost); + result = esock_make_ok2(env, esock_atom_mincost); break; default: @@ -12415,56 +12929,64 @@ int on_load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info) atom_want = MKA(env, str_want); /* Global atom(s) */ - esock_atom_addr = MKA(env, "addr"); - esock_atom_any = MKA(env, "any"); - esock_atom_ctrl = MKA(env, "ctrl"); - esock_atom_ctrunc = MKA(env, "ctrunc"); - esock_atom_data = MKA(env, "data"); - esock_atom_debug = MKA(env, "debug"); - esock_atom_dgram = MKA(env, "dgram"); - esock_atom_eor = MKA(env, "eor"); - esock_atom_error = MKA(env, "error"); - esock_atom_errqueue = MKA(env, "errqueue"); - esock_atom_false = MKA(env, "false"); - esock_atom_family = MKA(env, "family"); - esock_atom_flags = MKA(env, "flags"); - esock_atom_flowinfo = MKA(env, "flowinfo"); - esock_atom_inet = MKA(env, "inet"); - esock_atom_inet6 = MKA(env, "inet6"); - esock_atom_iov = MKA(env, "iov"); - esock_atom_ip = MKA(env, "ip"); - esock_atom_ipv6 = MKA(env, "ipv6"); - esock_atom_level = MKA(env, "level"); - esock_atom_local = MKA(env, "local"); - esock_atom_loopback = MKA(env, "loopback"); - esock_atom_ok = MKA(env, "ok"); - esock_atom_path = MKA(env, "path"); - esock_atom_port = MKA(env, "port"); - esock_atom_protocol = MKA(env, "protocol"); - esock_atom_raw = MKA(env, "raw"); - esock_atom_rdm = MKA(env, "rdm"); - esock_atom_scope_id = MKA(env, "scope_id"); - esock_atom_sctp = MKA(env, "sctp"); - esock_atom_seqpacket = MKA(env, "seqpacket"); - esock_atom_stream = MKA(env, "stream"); - esock_atom_tcp = MKA(env, "tcp"); - esock_atom_true = MKA(env, "true"); - esock_atom_trunc = MKA(env, "trunc"); - esock_atom_type = MKA(env, "type"); - esock_atom_udp = MKA(env, "udp"); - esock_atom_undefined = MKA(env, "undefined"); - esock_atom_unknown = MKA(env, "unknown"); + esock_atom_addr = MKA(env, "addr"); + esock_atom_any = MKA(env, "any"); + esock_atom_credentials = MKA(env, "credentials"); + esock_atom_ctrl = MKA(env, "ctrl"); + esock_atom_ctrunc = MKA(env, "ctrunc"); + esock_atom_data = MKA(env, "data"); + esock_atom_debug = MKA(env, "debug"); + esock_atom_dgram = MKA(env, "dgram"); + esock_atom_eor = MKA(env, "eor"); + esock_atom_error = MKA(env, "error"); + esock_atom_errqueue = MKA(env, "errqueue"); + esock_atom_false = MKA(env, "false"); + esock_atom_family = MKA(env, "family"); + esock_atom_flags = MKA(env, "flags"); + esock_atom_flowinfo = MKA(env, "flowinfo"); + esock_atom_ifindex = MKA(env, "ifindex"); + esock_atom_inet = MKA(env, "inet"); + esock_atom_inet6 = MKA(env, "inet6"); + esock_atom_iov = MKA(env, "iov"); + esock_atom_ip = MKA(env, "ip"); + esock_atom_ipv6 = MKA(env, "ipv6"); + esock_atom_level = MKA(env, "level"); + esock_atom_local = MKA(env, "local"); + esock_atom_loopback = MKA(env, "loopback"); + esock_atom_lowdelay = MKA(env, "lowdelay"); + esock_atom_mincost = MKA(env, "mincost"); + esock_atom_ok = MKA(env, "ok"); + esock_atom_oob = MKA(env, "oob"); + esock_atom_origdstaddr = MKA(env, "origdstaddr"); + esock_atom_path = MKA(env, "path"); + esock_atom_pktinfo = MKA(env, "pktinfo"); + esock_atom_port = MKA(env, "port"); + esock_atom_protocol = MKA(env, "protocol"); + esock_atom_raw = MKA(env, "raw"); + esock_atom_rdm = MKA(env, "rdm"); + esock_atom_reliability = MKA(env, "reliability"); + esock_atom_rights = MKA(env, "rights"); + esock_atom_scope_id = MKA(env, "scope_id"); + esock_atom_sctp = MKA(env, "sctp"); + esock_atom_seqpacket = MKA(env, "seqpacket"); + esock_atom_spec_dst = MKA(env, "spec_dst"); + esock_atom_stream = MKA(env, "stream"); + esock_atom_tcp = MKA(env, "tcp"); + esock_atom_throughput = MKA(env, "throughput"); + esock_atom_tos = MKA(env, "tos"); + esock_atom_true = MKA(env, "true"); + esock_atom_trunc = MKA(env, "trunc"); + esock_atom_ttl = MKA(env, "ttl"); + esock_atom_type = MKA(env, "type"); + esock_atom_udp = MKA(env, "udp"); + esock_atom_undefined = MKA(env, "undefined"); + esock_atom_unknown = MKA(env, "unknown"); /* Global error codes */ esock_atom_eafnosupport = MKA(env, ESOCK_STR_EAFNOSUPPORT); esock_atom_eagain = MKA(env, ESOCK_STR_EAGAIN); esock_atom_einval = MKA(env, ESOCK_STR_EINVAL); - atom_lowdelay = MKA(env, str_lowdelay); - atom_throughput = MKA(env, str_throughput); - atom_reliability = MKA(env, str_reliability); - atom_mincost = MKA(env, str_mincost); - /* Error codes */ atom_eisconn = MKA(env, str_eisconn); atom_enotclosing = MKA(env, str_enotclosing); diff --git a/erts/emulator/nifs/common/socket_util.c b/erts/emulator/nifs/common/socket_util.c index 50cc94c263..232e8200df 100644 --- a/erts/emulator/nifs/common/socket_util.c +++ b/erts/emulator/nifs/common/socket_util.c @@ -31,7 +31,6 @@ #include #include "socket_int.h" -#include "socket_tarray.h" #include "socket_util.h" #include "socket_dbg.h" #include "sys.h" @@ -40,7 +39,7 @@ * should use the compile debug flag, whatever that is... */ -#define COMPILE_DEBUG_FLAG_WE_NEED_TO_CHECK 1 +// #define COMPILE_DEBUG_FLAG_WE_NEED_TO_CHECK 1 #if defined(COMPILE_DEBUG_FLAG_WE_NEED_TO_CHECK) #define UTIL_DEBUG TRUE #else @@ -70,96 +69,6 @@ static char* make_sockaddr_un(ErlNifEnv* env, ERL_NIF_TERM* sa); -/* +++ esock_encode_msghdr +++ - * - * Encode a msghdr (recvmsg). In erlang its represented as - * a map, which has a specific set of attributes: - * - * addr (source address) - sockaddr() - * iov - [binary()] - * ctrl - [cmsghdr()] - * flags - msghdr_flags() - */ - -extern -char* esock_encode_msghdr(ErlNifEnv* env, - int read, - struct msghdr* msgHdrP, - ErlNifBinary* dataBufP, - ErlNifBinary* ctrlBufP, - ERL_NIF_TERM* eSockAddr) -{ - char* xres; - ERL_NIF_TERM addr, iov, ctrl, flags; - - UDBG( ("SUTIL", "esock_encode_msghdr -> entry with" - "\r\n read: %d" - "\r\n", read) ); - - if ((xres = esock_encode_sockaddr(env, - (SocketAddress*) msgHdrP->msg_name, - msgHdrP->msg_namelen, - &addr)) != NULL) - return xres; - - UDBG( ("SUTIL", "esock_encode_msghdr -> try encode iov\r\n") ); - if ((xres = esock_encode_iov(env, - read, - msgHdrP->msg_iov, - msgHdrP->msg_iovlen, - dataBufP, - &iov)) != NULL) - return xres; - - UDBG( ("SUTIL", "esock_encode_msghdr -> try encode cmsghdrs\r\n") ); - if ((xres = esock_encode_cmsghdrs(env, - ctrlBufP, - msgHdrP, - &ctrl)) != NULL) - return xres; - - UDBG( ("SUTIL", "esock_encode_msghdr -> try encode flags\r\n") ); - if ((xres = esock_encode_mshghdr_flags(env, - msgHdrP->msg_flags, - &flags)) != NULL) - return xres; - - UDBG( ("SUTIL", "esock_encode_msghdr -> components encoded:" - "\r\n addr: %T" - "\r\n iov: %T" - "\r\n ctrl: %T" - "\r\n flags: %T" - "\r\n", addr, iov, ctrl, flags) ); - { - ERL_NIF_TERM keys[] = {esock_atom_addr, - esock_atom_iov, - esock_atom_ctrl, - esock_atom_flags}; - ERL_NIF_TERM vals[] = {addr, iov, ctrl, flags}; - unsigned int numKeys = sizeof(keys) / sizeof(ERL_NIF_TERM); - unsigned int numVals = sizeof(vals) / sizeof(ERL_NIF_TERM); - ERL_NIF_TERM tmp; - - ESOCK_ASSERT( (numKeys == numVals) ); - - UDBG( ("SUTIL", "esock_encode_msghdr -> create msghdr map\r\n") ); - if (!MKMA(env, keys, vals, numKeys, &tmp)) - return ESOCK_STR_EINVAL; - - UDBG( ("SUTIL", "esock_encode_msghdr -> msghdr: " - "\r\n %T" - "\r\n", tmp) ); - - *eSockAddr = tmp; - } - - UDBG( ("SUTIL", "esock_encode_msghdr -> done\r\n") ); - - return NULL; -} - - - /* +++ esock_encode_iov +++ * * Encode a IO Vector. In erlang we represented this as a list of binaries. @@ -203,23 +112,20 @@ char* esock_encode_iov(ErlNifEnv* env, /* We have the exact amount - we are done */ UDBG( ("SUTIL", "esock_encode_iov -> exact => done\r\n") ); a[i] = MKBIN(env, &data[i]); - UDBG( ("SUTIL", "esock_encode_iov -> a[%d]: %T\r\n", i, a[i]) ); - rem = 0; // Besserwisser + rem = 0; // Besserwisser done = TRUE; } else if (iov[i].iov_len < rem) { /* Filled another buffer - continue */ UDBG( ("SUTIL", "esock_encode_iov -> filled => continue\r\n") ); a[i] = MKBIN(env, &data[i]); rem -= iov[i].iov_len; - UDBG( ("SUTIL", "esock_encode_iov -> a[%d]: %T\r\n", i, a[i]) ); } else if (iov[i].iov_len > rem) { /* Partly filled buffer (=> split) - we are done */ ERL_NIF_TERM tmp; UDBG( ("SUTIL", "esock_encode_iov -> split => done\r\n") ); tmp = MKBIN(env, &data[i]); a[i] = MKSBIN(env, tmp, 0, rem); - UDBG( ("SUTIL", "esock_encode_iov -> a[%d]: %T\r\n", i, a[i]) ); - rem = 0; // Besserwisser + rem = 0; // Besserwisser done = TRUE; } } @@ -235,165 +141,6 @@ char* esock_encode_iov(ErlNifEnv* env, -/* +++ esock_encode_cmsghdrs +++ - * - * Encode a list of cmsghdr(). The X can 0 or more cmsghdr blocks. - * - * Our problem is that we have no idea how many control messages - * we have. - * - * The cmsgHdrP arguments points to the start of the control data buffer, - * an actual binary. Its the only way to create sub-binaries. So, what we - * need to continue processing this is to tern that into an binary erlang - * term (which can then in turn be turned into sub-binaries). - * - * We need the cmsgBufP (even though cmsgHdrP points to it) to be able - * to create sub-binaries (one for each cmsg hdr). - * - * The TArray (term array) is created with the size of 128, which should - * be enough. But if its not, then it will be automatically realloc'ed during - * add. Once we are done adding hdr's to it, we convert the tarray to a list. - */ - -extern -char* esock_encode_cmsghdrs(ErlNifEnv* env, - ErlNifBinary* cmsgBinP, - struct msghdr* msgHdrP, - ERL_NIF_TERM* eCMsgHdr) -{ - ERL_NIF_TERM ctrlBuf = MKBIN(env, cmsgBinP); // The *entire* binary - SocketTArray cmsghdrs = TARRAY_CREATE(128); - struct cmsghdr* firstP = CMSG_FIRSTHDR(msgHdrP); - struct cmsghdr* currentP; - - UDBG( ("SUTIL", "esock_encode_cmsghdrs -> entry\r\n") ); - - for (currentP = firstP; - currentP != NULL; - currentP = CMSG_NXTHDR(msgHdrP, currentP)) { - - UDBG( ("SUTIL", "esock_encode_cmsghdrs -> process cmsg header when" - "\r\n TArray Size: %d" - "\r\n", TARRAY_SZ(cmsghdrs)) ); - - /* MUST check this since on Linux the returned "cmsg" may actually - * go too far! - */ - if (((CHARP(currentP) + currentP->cmsg_len) - CHARP(firstP)) > - msgHdrP->msg_controllen) { - /* Ouch, fatal error - give up - * We assume we cannot trust any data if this is wrong. - */ - TARRAY_DELETE(cmsghdrs); - return ESOCK_STR_EINVAL; - } else { - ERL_NIF_TERM level; - ERL_NIF_TERM type = MKI(env, currentP->cmsg_type); - unsigned char* dataP = (unsigned char*) CMSG_DATA(currentP); - size_t dataPos = dataP - cmsgBinP->data; - size_t dataLen = currentP->cmsg_len - (CHARP(currentP)-CHARP(dataP)); - ERL_NIF_TERM dataBin = MKSBIN(env, ctrlBuf, dataPos, dataLen); - - /* We can't give up just because its an unknown protocol, - * so if its a protocol we don't know, we return its integer - * value and leave it to the user. - */ - if (esock_encode_protocol(env, currentP->cmsg_level, &level) != NULL) - level = MKI(env, currentP->cmsg_level); - - UDBG( ("SUTIL", "esock_encode_cmsghdrs -> " - "\r\n level: %T" - "\r\n type: %T" - "\r\n", level, type) ); - - /* And finally create the 'cmsghdr' map - - * and if successfull add it to the tarray. - */ - { - ERL_NIF_TERM keys[] = {esock_atom_level, - esock_atom_type, - esock_atom_data}; - ERL_NIF_TERM vals[] = {level, type, dataBin}; - unsigned int numKeys = sizeof(keys) / sizeof(ERL_NIF_TERM); - unsigned int numVals = sizeof(vals) / sizeof(ERL_NIF_TERM); - ERL_NIF_TERM cmsgHdr; - - /* Guard agains cut-and-paste errors */ - ESOCK_ASSERT( (numKeys == numVals) ); - - if (!MKMA(env, keys, vals, numKeys, &cmsgHdr)) { - TARRAY_DELETE(cmsghdrs); - return ESOCK_STR_EINVAL; - } - - /* And finally add it to the list... */ - TARRAY_ADD(cmsghdrs, cmsgHdr); - } - } - } - - UDBG( ("SUTIL", "esock_encode_cmsghdrs -> cmsg headers processed when" - "\r\n TArray Size: %d" - "\r\n", TARRAY_SZ(cmsghdrs)) ); - - /* The tarray is populated - convert it to a list */ - TARRAY_TOLIST(cmsghdrs, env, eCMsgHdr); - - return NULL; -} - - - -/* +++ esock_encode_mshghdr_flags +++ - * - * Encode a list of msghdr_flag(). - * - * The following flags are handled: eor | trunc | ctrunc | oob | errqueue. - */ - -extern -char* esock_encode_mshghdr_flags(ErlNifEnv* env, - int msgFlags, - ERL_NIF_TERM* flags) -{ - UDBG( ("SUTIL", "esock_encode_cmsghdrs -> entry with" - "\r\n msgFlags: %d (0x%lX)" - "\r\n", msgFlags, msgFlags) ); - - if (msgFlags == 0) { - *flags = MKEL(env); - return NULL; - } else { - SocketTArray ta = TARRAY_CREATE(10); // Just to be on the safe side - - if ((msgFlags & MSG_EOR) == MSG_EOR) - TARRAY_ADD(ta, esock_atom_eor); - - if ((msgFlags & MSG_TRUNC) == MSG_TRUNC) - TARRAY_ADD(ta, esock_atom_trunc); - - if ((msgFlags & MSG_CTRUNC) == MSG_CTRUNC) - TARRAY_ADD(ta, esock_atom_ctrunc); - - if ((msgFlags & MSG_OOB) == MSG_OOB) - TARRAY_ADD(ta, esock_atom_oob); - - if ((msgFlags & MSG_ERRQUEUE) == MSG_ERRQUEUE) - TARRAY_ADD(ta, esock_atom_errqueue); - - UDBG( ("SUTIL", "esock_encode_cmsghdrs -> flags processed when" - "\r\n TArray size: %d" - "\r\n", TARRAY_SZ(ta)) ); - - TARRAY_TOLIST(ta, env, flags); - - return NULL; - } -} - - - - /* +++ esock_decode_sockaddr +++ * * Decode a socket address - sockaddr. In erlang its represented as diff --git a/erts/emulator/nifs/common/socket_util.h b/erts/emulator/nifs/common/socket_util.h index 5a79eb3d09..5d720936f3 100644 --- a/erts/emulator/nifs/common/socket_util.h +++ b/erts/emulator/nifs/common/socket_util.h @@ -35,13 +35,6 @@ #define ESOCK_ABORT(E) esock_abort(E, __func__, __FILE__, __LINE__) #define ESOCK_ASSERT(e) ((void) ((e) ? 1 : (ESOCK_ABORT(#e), 0))) -extern -char* esock_encode_msghdr(ErlNifEnv* env, - int read, - struct msghdr* msgHdrP, - ErlNifBinary* dataBufP, - ErlNifBinary* ctrlBufP, - ERL_NIF_TERM* eSockAddr); extern char* esock_encode_iov(ErlNifEnv* env, int read, @@ -50,16 +43,6 @@ char* esock_encode_iov(ErlNifEnv* env, ErlNifBinary* data, ERL_NIF_TERM* eIOV); extern -char* esock_encode_cmsghdrs(ErlNifEnv* env, - ErlNifBinary* cmsgBinP, - struct msghdr* msgHdrP, - ERL_NIF_TERM* eCMsgHdr); - -extern -char* esock_encode_mshghdr_flags(ErlNifEnv* env, - int msgFlags, - ERL_NIF_TERM* flags); -extern char* esock_decode_sockaddr(ErlNifEnv* env, ERL_NIF_TERM eSockAddr, SocketAddress* sockAddrP, diff --git a/lib/kernel/test/socket_server.erl b/lib/kernel/test/socket_server.erl index 5d00f78b2a..a804078917 100644 --- a/lib/kernel/test/socket_server.erl +++ b/lib/kernel/test/socket_server.erl @@ -720,10 +720,18 @@ handler_init(Manager, ID, Msg, Peek, Sock) -> RtHdr, AuthHdr, HopLimit, HopOpts, DstOpts, FlowInfo, UHops]), - ok = soip(Sock, pktinfo, true), + SIP = + fun(O, V) -> + if + (Type =:= dgram) -> + ok = soip(Sock, O, V); + true -> + ok + end + end, ok = soip(Sock, recvtos, true), - ok = soip(Sock, recvttl, true), - %% ok = soip(Sock, recvopts, true), + SIP(recvttl, true), + ok = soip(Sock, recvorigdstaddr, true), handler_loop(#handler{msg = Msg, peek = Peek, @@ -786,13 +794,13 @@ recv(#handler{peek = true, socket = Sock, type = stream}) -> recv(#handler{peek = false, socket = Sock, type = stream}) -> do_recv(Sock); recv(#handler{socket = Sock, msg = true, type = dgram}) -> - ok = socket:setopt(Sock, otp, debug, true), + %% ok = socket:setopt(Sock, otp, debug, true), case socket:recvmsg(Sock) of {ok, #{addr := Source, iov := [Data], ctrl := CMsgHdrs, flags := Flags}} -> - ok = socket:setopt(Sock, otp, debug, false), + %% ok = socket:setopt(Sock, otp, debug, false), i("received message: " "~n CMsgHdrs: ~p" "~n Flags: ~p", [CMsgHdrs, Flags]), -- cgit v1.2.3 From 90a150771faa3cf01e82919b0c17854de9987783 Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Wed, 1 Aug 2018 19:42:32 +0200 Subject: [socket-nif] Processing of more cmsg headers Added processing or more cmsg headers (for more options). Now (also) supports: socket:timestamp. Also various fixes and cleanups. For some reason calling getopt(Sock, 0, {13, int}) (or similar) fails with badarg even though the nif-function (nif_getopt) actually returns a valid value (for instance: {ok, 0}). OTP-14831 --- erts/doc/src/socket.xml | 26 ++- erts/doc/src/socket_usage.xml | 2 +- erts/emulator/nifs/common/socket_int.h | 5 + erts/emulator/nifs/common/socket_nif.c | 345 +++++++++++++++++++++----------- erts/emulator/nifs/common/socket_util.c | 73 +++++++ erts/emulator/nifs/common/socket_util.h | 6 + erts/preloaded/ebin/socket.beam | Bin 61376 -> 61704 bytes erts/preloaded/src/socket.erl | 52 +++-- lib/kernel/test/socket_server.erl | 123 ++++++++---- 9 files changed, 458 insertions(+), 174 deletions(-) diff --git a/erts/doc/src/socket.xml b/erts/doc/src/socket.xml index 53d1516f1e..2fb922408b 100644 --- a/erts/doc/src/socket.xml +++ b/erts/doc/src/socket.xml @@ -120,7 +120,7 @@ - + @@ -137,6 +137,9 @@ + + + @@ -155,6 +158,27 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/erts/doc/src/socket_usage.xml b/erts/doc/src/socket_usage.xml index 933341bd35..b7459e97fa 100644 --- a/erts/doc/src/socket_usage.xml +++ b/erts/doc/src/socket_usage.xml @@ -444,7 +444,7 @@ ip_tos() yes yes - may require admin capability + some high-priority levels may require superuser capability transparent diff --git a/erts/emulator/nifs/common/socket_int.h b/erts/emulator/nifs/common/socket_int.h index 80a9eb0734..3595c483d7 100644 --- a/erts/emulator/nifs/common/socket_int.h +++ b/erts/emulator/nifs/common/socket_int.h @@ -142,11 +142,14 @@ extern ERL_NIF_TERM esock_atom_reliability; extern ERL_NIF_TERM esock_atom_rights; extern ERL_NIF_TERM esock_atom_scope_id; extern ERL_NIF_TERM esock_atom_sctp; +extern ERL_NIF_TERM esock_atom_sec; extern ERL_NIF_TERM esock_atom_seqpacket; +extern ERL_NIF_TERM esock_atom_socket; extern ERL_NIF_TERM esock_atom_spec_dst; extern ERL_NIF_TERM esock_atom_stream; extern ERL_NIF_TERM esock_atom_tcp; extern ERL_NIF_TERM esock_atom_throughput; +extern ERL_NIF_TERM esock_atom_timestamp; extern ERL_NIF_TERM esock_atom_tos; extern ERL_NIF_TERM esock_atom_true; extern ERL_NIF_TERM esock_atom_trunc; @@ -154,6 +157,8 @@ extern ERL_NIF_TERM esock_atom_ttl; extern ERL_NIF_TERM esock_atom_type; extern ERL_NIF_TERM esock_atom_udp; extern ERL_NIF_TERM esock_atom_undefined; +extern ERL_NIF_TERM esock_atom_unknown; +extern ERL_NIF_TERM esock_atom_usec; /* ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ diff --git a/erts/emulator/nifs/common/socket_nif.c b/erts/emulator/nifs/common/socket_nif.c index 4b4082062b..4144341d71 100644 --- a/erts/emulator/nifs/common/socket_nif.c +++ b/erts/emulator/nifs/common/socket_nif.c @@ -421,6 +421,11 @@ static void (*esock_sctp_freepaddrs)(struct sockaddr *addrs) = NULL; #define SOCKET_RECV_BUFFER_SIZE_DEFAULT 2048 #define SOCKET_RECV_CTRL_BUFFER_SIZE_DEFAULT 1024 +#define VT2S(__VT__) (((__VT__) == SOCKET_OPT_VALUE_TYPE_UNSPEC) ? "unspec" : \ + (((__VT__) == SOCKET_OPT_VALUE_TYPE_INT) ? "int" : \ + ((__VT__) == SOCKET_OPT_VALUE_TYPE_BOOL) ? "bool" : \ + "undef")) + #define SOCKET_OPT_VALUE_TYPE_UNSPEC 0 #define SOCKET_OPT_VALUE_TYPE_INT 1 #define SOCKET_OPT_VALUE_TYPE_BOOL 2 @@ -1468,7 +1473,7 @@ static ERL_NIF_TERM ngetopt(ErlNifEnv* env, BOOLEAN_T isEncoded, BOOLEAN_T isOTP, int level, - int eOpt); + ERL_NIF_TERM eOpt); static ERL_NIF_TERM ngetopt_otp(ErlNifEnv* env, SocketDescriptor* descP, int eOpt); @@ -1479,7 +1484,7 @@ static ERL_NIF_TERM ngetopt_otp_iow(ErlNifEnv* env, static ERL_NIF_TERM ngetopt_native(ErlNifEnv* env, SocketDescriptor* descP, int level, - int eOpt); + ERL_NIF_TERM eOpt); static ERL_NIF_TERM ngetopt_native_unspec(ErlNifEnv* env, SocketDescriptor* descP, int level, @@ -1897,6 +1902,9 @@ extern char* encode_cmsghdrs(ErlNifEnv* env, ErlNifBinary* cmsgBinP, struct msghdr* msgHdrP, ERL_NIF_TERM* eCMsgHdr); +static char* encode_cmsghdr_level(ErlNifEnv* env, + int level, + ERL_NIF_TERM* eLevel); static char* encode_cmsghdr_type(ErlNifEnv* env, int level, int type, @@ -1909,6 +1917,13 @@ static char* encode_cmsghdr_data(ErlNifEnv* env, size_t dataPos, size_t dataLen, ERL_NIF_TERM* eCMsgHdrData); +static char* encode_cmsghdr_data_socket(ErlNifEnv* env, + ERL_NIF_TERM ctrlBuf, + int type, + unsigned char* dataP, + size_t dataPos, + size_t dataLen, + ERL_NIF_TERM* eCMsgHdrData); static char* encode_cmsghdr_data_ip(ErlNifEnv* env, ERL_NIF_TERM ctrlBuf, int type, @@ -2104,6 +2119,7 @@ static char str_association[] = "association"; static char str_assoc_id[] = "assoc_id"; static char str_authentication[] = "authentication"; // static char str_any[] = "any"; +static char str_bool[] = "bool"; static char str_close[] = "close"; static char str_closed[] = "closed"; static char str_closing[] = "closing"; @@ -2118,6 +2134,7 @@ static char str_in4_sockaddr[] = "in4_sockaddr"; static char str_in6_sockaddr[] = "in6_sockaddr"; static char str_include[] = "include"; static char str_initial[] = "initial"; +static char str_int[] = "int"; static char str_interface[] = "interface"; static char str_iow[] = "iow"; static char str_local_rwnd[] = "local_rwnd"; @@ -2149,7 +2166,6 @@ static char str_partial_delivery[] = "partial_delivery"; static char str_peer_error[] = "peer_error"; static char str_peer_rwnd[] = "peer_rwnd"; static char str_probe[] = "probe"; -static char str_sec[] = "sec"; static char str_select[] = "select"; static char str_sender_dry[] = "sender_dry"; static char str_send_failure[] = "send_failure"; @@ -2158,7 +2174,6 @@ static char str_slist[] = "slist"; static char str_sourceaddr[] = "sourceaddr"; static char str_timeout[] = "timeout"; static char str_true[] = "true"; -static char str_usec[] = "usec"; static char str_want[] = "want"; /* (special) error string constants */ @@ -2213,11 +2228,14 @@ ERL_NIF_TERM esock_atom_rights; ERL_NIF_TERM esock_atom_reliability; ERL_NIF_TERM esock_atom_scope_id; ERL_NIF_TERM esock_atom_sctp; +ERL_NIF_TERM esock_atom_sec; ERL_NIF_TERM esock_atom_seqpacket; +ERL_NIF_TERM esock_atom_socket; ERL_NIF_TERM esock_atom_spec_dst; ERL_NIF_TERM esock_atom_stream; ERL_NIF_TERM esock_atom_tcp; ERL_NIF_TERM esock_atom_throughput; +ERL_NIF_TERM esock_atom_timestamp; ERL_NIF_TERM esock_atom_tos; ERL_NIF_TERM esock_atom_true; ERL_NIF_TERM esock_atom_trunc; @@ -2226,6 +2244,7 @@ ERL_NIF_TERM esock_atom_type; ERL_NIF_TERM esock_atom_udp; ERL_NIF_TERM esock_atom_undefined; ERL_NIF_TERM esock_atom_unknown; +ERL_NIF_TERM esock_atom_usec; /* *** "Global" error (=reason) atoms *** */ ERL_NIF_TERM esock_atom_eagain; @@ -2238,6 +2257,7 @@ static ERL_NIF_TERM atom_address; static ERL_NIF_TERM atom_association; static ERL_NIF_TERM atom_assoc_id; static ERL_NIF_TERM atom_authentication; +static ERL_NIF_TERM atom_bool; static ERL_NIF_TERM atom_close; static ERL_NIF_TERM atom_closed; static ERL_NIF_TERM atom_closing; @@ -2252,6 +2272,7 @@ static ERL_NIF_TERM atom_in4_sockaddr; static ERL_NIF_TERM atom_in6_sockaddr; static ERL_NIF_TERM atom_include; static ERL_NIF_TERM atom_initial; +static ERL_NIF_TERM atom_int; static ERL_NIF_TERM atom_interface; static ERL_NIF_TERM atom_iow; static ERL_NIF_TERM atom_local_rwnd; @@ -2282,7 +2303,6 @@ static ERL_NIF_TERM atom_partial_delivery; static ERL_NIF_TERM atom_peer_error; static ERL_NIF_TERM atom_peer_rwnd; static ERL_NIF_TERM atom_probe; -static ERL_NIF_TERM atom_sec; static ERL_NIF_TERM atom_select; static ERL_NIF_TERM atom_sender_dry; static ERL_NIF_TERM atom_send_failure; @@ -2291,7 +2311,6 @@ static ERL_NIF_TERM atom_slist; static ERL_NIF_TERM atom_sourceaddr; static ERL_NIF_TERM atom_timeout; static ERL_NIF_TERM atom_true; -static ERL_NIF_TERM atom_usec; static ERL_NIF_TERM atom_want; static ERL_NIF_TERM atom_eisconn; @@ -7424,42 +7443,18 @@ ERL_NIF_TERM nsetopt_timeval_opt(ErlNifEnv* env, ERL_NIF_TERM eVal) { ERL_NIF_TERM result; - ERL_NIF_TERM eSec, eUSec; struct timeval timeVal; int res; - size_t sz; + char* xres; SSDBG( descP, ("SOCKET", "nsetopt_timeval_opt -> entry with" "\r\n eVal: %T" "\r\n", eVal) ); - // It must be a map - if (!IS_MAP(env, eVal)) - return esock_make_error(env, esock_atom_einval); - - // It must have atleast ten attributes - if (!enif_get_map_size(env, eVal, &sz) || (sz < 2)) - return esock_make_error(env, esock_atom_einval); - - SSDBG( descP, - ("SOCKET", "nsetopt_lvl_sctp_rtoinfo -> extract attributes\r\n") ); - - if (!GET_MAP_VAL(env, eVal, atom_sec, &eSec)) - return esock_make_error(env, esock_atom_einval); - - if (!GET_MAP_VAL(env, eVal, atom_usec, &eUSec)) - return esock_make_error(env, esock_atom_einval); + if ((xres = esock_decode_timeval(env, eVal, &timeVal)) != NULL) + return esock_make_error_str(env, xres); - SSDBG( descP, - ("SOCKET", "nsetopt_timeval_opt -> decode attributes\r\n") ); - - if (!GET_LONG(env, eSec, &timeVal.tv_sec)) - return esock_make_error(env, esock_atom_einval); - - if (!GET_LONG(env, eUSec, &timeVal.tv_usec)) - return esock_make_error(env, esock_atom_einval); - SSDBG( descP, ("SOCKET", "nsetopt_timeval_opt -> set timeval option\r\n") ); @@ -7668,26 +7663,26 @@ ERL_NIF_TERM nif_getopt(ErlNifEnv* env, { SocketDescriptor* descP; int eLevel, level = -1; - int eOpt; - ERL_NIF_TERM eIsEncoded; + ERL_NIF_TERM eIsEncoded, eOpt; BOOLEAN_T isEncoded, isOTP; SGDBG( ("SOCKET", "nif_getopt -> entry with argc: %d\r\n", argc) ); if ((argc != 4) || !enif_get_resource(env, argv[0], sockets, (void**) &descP) || - !GET_INT(env, argv[2], &eLevel) || - !GET_INT(env, argv[3], &eOpt)) { + !GET_INT(env, argv[2], &eLevel)) { + SGDBG( ("SOCKET", "nif_getopt -> failed processing args\r\n") ); return enif_make_badarg(env); } eIsEncoded = argv[1]; + eOpt = argv[3]; // Is "normally" an int, but if raw mode: {Int, ValueSz} SSDBG( descP, ("SOCKET", "nif_getopt -> args when sock = %d:" "\r\n Socket: %T" "\r\n eIsEncoded: %T" "\r\n eLevel: %d" - "\r\n eOpt: %d" + "\r\n eOpt: %T" "\r\n", descP->sock, argv[0], eIsEncoded, eLevel, eOpt) ); isEncoded = esock_decode_bool(eIsEncoded); @@ -7706,27 +7701,34 @@ ERL_NIF_TERM ngetopt(ErlNifEnv* env, BOOLEAN_T isEncoded, BOOLEAN_T isOTP, int level, - int eOpt) + ERL_NIF_TERM eOpt) { ERL_NIF_TERM result; + int opt; SSDBG( descP, ("SOCKET", "ngetopt -> entry with" - "\r\n isEncoded: %d" - "\r\n isOTP: %d" + "\r\n isEncoded: %s" + "\r\n isOTP: %s" "\r\n level: %d" - "\r\n eOpt: %d" - "\r\n", isEncoded, isOTP, level, eOpt) ); + "\r\n eOpt: %T" + "\r\n", B2S(isEncoded), B2S(isOTP), level, eOpt) ); if (isOTP) { /* These are not actual socket options, * but options for our implementation. */ - result = ngetopt_otp(env, descP, eOpt); + if (GET_INT(env, eOpt, &opt)) + result = ngetopt_otp(env, descP, opt); + else + result = esock_make_error(env, esock_atom_einval); } else if (!isEncoded) { result = ngetopt_native(env, descP, level, eOpt); } else { - result = ngetopt_level(env, descP, level, eOpt); + if (GET_INT(env, eOpt, &opt)) + result = ngetopt_level(env, descP, level, opt); + else + result = esock_make_error(env, esock_atom_einval); } SSDBG( descP, @@ -7809,7 +7811,7 @@ static ERL_NIF_TERM ngetopt_native(ErlNifEnv* env, SocketDescriptor* descP, int level, - int eOpt) + ERL_NIF_TERM eOpt) { ERL_NIF_TERM result = enif_make_badarg(env); int opt; @@ -7819,16 +7821,23 @@ ERL_NIF_TERM ngetopt_native(ErlNifEnv* env, SSDBG( descP, ("SOCKET", "ngetopt_native -> entry with" "\r\n level: %d" - "\r\n eOpt: %d" + "\r\n eOpt: %T" "\r\n", level, eOpt) ); /* - * We should really make it possible to specify common specific types, + * We should really make it possible to specify more common specific types, * such as integer or boolean (instead of the size)... * */ if (decode_native_get_opt(env, eOpt, &opt, &valueType, (int*) &valueSz)) { + + SSDBG( descP, + ("SOCKET", "ngetopt_native -> decoded opt" + "\r\n valueType: %d (%s)" + "\r\n ValueSize: %d" + "\r\n", valueType, VT2S(valueType), valueSz) ); + switch (valueType) { case SOCKET_OPT_VALUE_TYPE_UNSPEC: result = ngetopt_native_unspec(env, descP, level, opt, valueSz); @@ -7863,7 +7872,7 @@ ERL_NIF_TERM ngetopt_native_unspec(ErlNifEnv* env, int opt, SOCKOPTLEN_T valueSz) { - ERL_NIF_TERM result = enif_make_badarg(env); + ERL_NIF_TERM result = esock_make_error(env, esock_atom_einval); int res; SSDBG( descP, @@ -7880,19 +7889,33 @@ ERL_NIF_TERM ngetopt_native_unspec(ErlNifEnv* env, else result = esock_atom_ok; } else { + SOCKOPTLEN_T vsz = valueSz; ErlNifBinary val; - if (ALLOC_BIN(valueSz, &val)) { - res = sock_getopt(descP->sock, level, opt, val.data, &valueSz); + SSDBG( descP, ("SOCKET", "ngetopt_native_unspec -> try alloc buffer\r\n") ); + + if (ALLOC_BIN(vsz, &val)) { + int saveErrno; + res = sock_getopt(descP->sock, level, opt, val.data, &vsz); if (res != 0) { - result = esock_make_error_errno(env, sock_errno()); + saveErrno = sock_errno(); + + result = esock_make_error_errno(env, saveErrno); } else { - if (valueSz < val.size) { - if (REALLOC_BIN(&val, valueSz)) { - result = esock_make_ok2(env, MKBIN(env, &val)); - } else { - result = enif_make_badarg(env); - } + + /* Did we use all of the buffer? */ + if (vsz == val.size) { + + result = esock_make_ok2(env, MKBIN(env, &val)); + + } else { + + ERL_NIF_TERM tmp; + + tmp = MKBIN(env, &val); + tmp = MKSBIN(env, tmp, 0, vsz); + + result = esock_make_ok2(env, tmp); } } } else { @@ -9959,18 +9982,12 @@ ERL_NIF_TERM ngetopt_timeval_opt(ErlNifEnv* env, result = esock_make_error_errno(env, sock_errno()); } else { ERL_NIF_TERM eTimeVal; - ERL_NIF_TERM keys[] = {atom_sec, atom_usec}; - ERL_NIF_TERM vals[] = {MKL(env, val.tv_sec), MKL(env, val.tv_usec)}; - - unsigned int numKeys = sizeof(keys) / sizeof(ERL_NIF_TERM); - unsigned int numVals = sizeof(vals) / sizeof(ERL_NIF_TERM); - - ESOCK_ASSERT( (numKeys == numVals) ); - - if (!MKMA(env, keys, vals, numKeys, &eTimeVal)) - return esock_make_error(env, esock_atom_einval); - - result = esock_make_ok2(env, eTimeVal); + char* xres; + + if ((xres = esock_encode_timeval(env, &val, &eTimeVal)) != NULL) + result = esock_make_error_str(env, xres); + else + result = esock_make_ok2(env, eTimeVal); } SSDBG( descP, @@ -10562,6 +10579,28 @@ ERL_NIF_TERM recvmsg_check_result(ErlNifEnv* env, "\r\n", read, saveErrno, recvRef) ); + /* + * + * We need to handle read = 0 for other type(s) (DGRAM) when + * its actually valid to read 0 bytes. + * + * + */ + + if ((read == 0) && (descP->type == SOCK_STREAM)) { + + /* + * When a stream socket peer has performed an orderly shutdown, the return + * value will be 0 (the traditional "end-of-file" return). + * + * *We* do never actually try to read 0 bytes from a stream socket! + */ + + return esock_make_error(env, atom_closed); + + } + + /* There is a special case: If the provided 'to read' value is * zero (0). That means that we reads as much as we can, using * the default read buffer size. @@ -10575,7 +10614,7 @@ ERL_NIF_TERM recvmsg_check_result(ErlNifEnv* env, /* +++ Oups - closed +++ */ - SSDBG( descP, ("SOCKET", "recvfrom_check_result -> closed\r\n") ); + SSDBG( descP, ("SOCKET", "recvmsg_check_result -> closed\r\n") ); /* * IF THE CURRENT PROCESS IS *NOT* THE CONTROLLING @@ -10599,7 +10638,7 @@ ERL_NIF_TERM recvmsg_check_result(ErlNifEnv* env, } else if ((saveErrno == ERRNO_BLOCK) || (saveErrno == EAGAIN)) { - SSDBG( descP, ("SOCKET", "recvfrom_check_result -> eagain\r\n") ); + SSDBG( descP, ("SOCKET", "recvmsg_check_result -> eagain\r\n") ); SELECT(env, descP->sock, (ERL_NIF_SELECT_READ), descP, NULL, recvRef); @@ -10609,7 +10648,7 @@ ERL_NIF_TERM recvmsg_check_result(ErlNifEnv* env, SSDBG( descP, ("SOCKET", - "recvfrom_check_result -> errno: %d\r\n", saveErrno) ); + "recvmsg_check_result -> errno: %d\r\n", saveErrno) ); return esock_make_error_errno(env, saveErrno); } @@ -10637,7 +10676,7 @@ ERL_NIF_TERM recvmsg_check_result(ErlNifEnv* env, SSDBG( descP, ("SOCKET", - "recvfrom_check_result -> " + "recvmsg_check_result -> " "(msghdr) encode failed: %s\r\n", xres) ); return esock_make_error_str(env, xres); @@ -10645,7 +10684,7 @@ ERL_NIF_TERM recvmsg_check_result(ErlNifEnv* env, SSDBG( descP, ("SOCKET", - "recvfrom_check_result -> " + "recvmsg_check_result -> " "(msghdr) encode ok: %T\r\n", eMsgHdr) ); return esock_make_ok2(env, eMsgHdr); @@ -10684,12 +10723,19 @@ char* encode_msghdr(ErlNifEnv* env, ("SOCKET", "encode_msghdr -> entry with" "\r\n read: %d" "\r\n", read) ); - - if ((xres = esock_encode_sockaddr(env, - (SocketAddress*) msgHdrP->msg_name, - msgHdrP->msg_namelen, - &addr)) != NULL) - return xres; + + /* The address is not used if we are connected, + * so check (length = 0) before we try to encodel + */ + if (msgHdrP->msg_namelen != 0) { + if ((xres = esock_encode_sockaddr(env, + (SocketAddress*) msgHdrP->msg_name, + msgHdrP->msg_namelen, + &addr)) != NULL) + return xres; + } else { + addr = esock_atom_undefined; + } SSDBG( descP, ("SOCKET", "encode_msghdr -> try encode iov\r\n") ); if ((xres = esock_encode_iov(env, @@ -10814,7 +10860,7 @@ char* encode_cmsghdrs(ErlNifEnv* env, * so if its a protocol we don't know, we return its integer * value and leave it to the user. */ - if (esock_encode_protocol(env, currentP->cmsg_level, &level) != NULL) + if (encode_cmsghdr_level(env, currentP->cmsg_level, &level) != NULL) level = MKI(env, currentP->cmsg_level); if (encode_cmsghdr_type(env, @@ -10875,6 +10921,35 @@ char* encode_cmsghdrs(ErlNifEnv* env, +/* +++ encode_cmsghdr_level +++ + * + * Encode the type part of the cmsghdr(). + * + */ + +static +char* encode_cmsghdr_level(ErlNifEnv* env, + int level, + ERL_NIF_TERM* eLevel) +{ + char* xres; + + switch (level) { + case SOL_SOCKET: + *eLevel = esock_atom_socket; + xres = NULL; + break; + + default: + xres = esock_encode_protocol(env, level, eLevel); + break; + } + + return xres; +} + + + /* +++ encode_cmsghdr_type +++ * * Encode the type part of the cmsghdr(). @@ -10892,6 +10967,12 @@ char* encode_cmsghdr_type(ErlNifEnv* env, switch (level) { case SOL_SOCKET: switch (type) { +#if defined(SO_TIMESTAMP) + case SO_TIMESTAMP: + *eType = esock_atom_timestamp; + break; +#endif + #if defined(SCM_RIGHTS) case SCM_RIGHTS: *eType = esock_atom_rights; @@ -11011,13 +11092,13 @@ char* encode_cmsghdr_data(ErlNifEnv* env, char* xres; switch (level) { - /* - #if defined(SOL_SOCKET) - case SOL_SOCKET: - xres = encode_cmsghdr_data_socket(env, type, dataP, eCMsgHdrData); - break; - #endif - */ +#if defined(SOL_SOCKET) + case SOL_SOCKET: + xres = encode_cmsghdr_data_socket(env, ctrlBuf, type, + dataP, dataPos, dataLen, + eCMsgHdrData); + break; +#endif #if defined(SOL_IP) case SOL_IP: @@ -11068,6 +11149,45 @@ char* encode_cmsghdr_data(ErlNifEnv* env, +/* +++ encode_cmsghdr_data_socket +++ + * + * Encode the data part when "protocol" = socket of the cmsghdr(). + * + */ + +static +char* encode_cmsghdr_data_socket(ErlNifEnv* env, + ERL_NIF_TERM ctrlBuf, + int type, + unsigned char* dataP, + size_t dataPos, + size_t dataLen, + ERL_NIF_TERM* eCMsgHdrData) +{ + // char* xres; + + switch (type) { +#if defined(SO_TIMESTAMP) + case SO_TIMESTAMP: + { + struct timeval* timeP = (struct timeval*) dataP; + + if (esock_encode_timeval(env, timeP, eCMsgHdrData) != NULL) + *eCMsgHdrData = MKSBIN(env, ctrlBuf, dataPos, dataLen); + } + break; +#endif + + default: + *eCMsgHdrData = MKSBIN(env, ctrlBuf, dataPos, dataLen); + break; + } + + return NULL; +} + + + /* +++ encode_cmsghdr_data_ip +++ * * Encode the data part when protocol = IP of the cmsghdr(). @@ -11089,8 +11209,8 @@ char* encode_cmsghdr_data_ip(ErlNifEnv* env, #if defined(IP_TOS) case IP_TOS: { - unsigned char tos = IPTOS_TOS(*dataP); - switch (tos) { + unsigned char tos = *dataP; + switch (IPTOS_TOS(tos)) { case IPTOS_LOWDELAY: *eCMsgHdrData = esock_atom_lowdelay; break; @@ -11551,29 +11671,21 @@ BOOLEAN_T decode_native_get_opt(ErlNifEnv* env, ERL_NIF_TERM eVal, return FALSE; if (IS_ATOM(env, nativeOptT[1])) { - unsigned int len; - char t[16]; // Just in case - - if (!(GET_ATOM_LEN(env, nativeOptT[1], &len) && - (len > 0) && - (len <= (sizeof("bool"))))) - return FALSE; - if (!GET_ATOM(env, nativeOptT[1], t, sizeof(t))) - return FALSE; - - if (strncmp(t, "bool", len) == 0) { - *valueType = SOCKET_OPT_VALUE_TYPE_BOOL; - *valueSz = sizeof(int); // Just to be sure - } else if (strncmp(t, "int", len) == 0) { + if (COMPARE(nativeOptT[1], atom_int) == 0) { + SGDBG( ("SOCKET", "decode_native_get_opt -> int\r\n") ); *valueType = SOCKET_OPT_VALUE_TYPE_INT; *valueSz = sizeof(int); // Just to be sure + } else if (COMPARE(nativeOptT[1], atom_bool) == 0) { + SGDBG( ("SOCKET", "decode_native_get_opt -> bool\r\n") ); + *valueType = SOCKET_OPT_VALUE_TYPE_BOOL; + *valueSz = sizeof(int); // Just to be sure } else { return FALSE; } - } else if (IS_NUM(env, nativeOptT[1])) { if (GET_INT(env, nativeOptT[1], valueSz)) { + SGDBG( ("SOCKET", "decode_native_get_opt -> unspec\r\n") ); *valueType = SOCKET_OPT_VALUE_TYPE_UNSPEC; } else { return FALSE; @@ -11582,21 +11694,12 @@ BOOLEAN_T decode_native_get_opt(ErlNifEnv* env, ERL_NIF_TERM eVal, return FALSE; } + SGDBG( ("SOCKET", "decode_native_get_opt -> done\r\n") ); + return TRUE; } -/* -static -void encode_bool(BOOLEAN_T val, ERL_NIF_TERM* eVal) -{ - if (val) - *eVal = esock_atom_true; - else - *eVal = esock_atom_false; -} -*/ - /* +++ encode the ip socket option tos +++ * The (ip) option can be provide as: @@ -12872,6 +12975,7 @@ int on_load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info) atom_association = MKA(env, str_association); atom_assoc_id = MKA(env, str_assoc_id); atom_authentication = MKA(env, str_authentication); + atom_bool = MKA(env, str_bool); atom_close = MKA(env, str_close); atom_closed = MKA(env, str_closed); atom_closing = MKA(env, str_closing); @@ -12886,6 +12990,7 @@ int on_load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info) atom_in6_sockaddr = MKA(env, str_in6_sockaddr); atom_include = MKA(env, str_include); atom_initial = MKA(env, str_initial); + atom_int = MKA(env, str_int); atom_interface = MKA(env, str_interface); atom_iow = MKA(env, str_iow); atom_local_rwnd = MKA(env, str_local_rwnd); @@ -12916,7 +13021,6 @@ int on_load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info) atom_peer_rwnd = MKA(env, str_peer_rwnd); atom_peer_error = MKA(env, str_peer_error); atom_probe = MKA(env, str_probe); - atom_sec = MKA(env, str_sec); atom_select = MKA(env, str_select); atom_sender_dry = MKA(env, str_sender_dry); atom_send_failure = MKA(env, str_send_failure); @@ -12925,7 +13029,6 @@ int on_load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info) atom_sourceaddr = MKA(env, str_sourceaddr); atom_timeout = MKA(env, str_timeout); atom_true = MKA(env, str_true); - atom_usec = MKA(env, str_usec); atom_want = MKA(env, str_want); /* Global atom(s) */ @@ -12968,11 +13071,14 @@ int on_load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info) esock_atom_rights = MKA(env, "rights"); esock_atom_scope_id = MKA(env, "scope_id"); esock_atom_sctp = MKA(env, "sctp"); + esock_atom_sec = MKA(env, "sec"); esock_atom_seqpacket = MKA(env, "seqpacket"); + esock_atom_socket = MKA(env, "socket"); esock_atom_spec_dst = MKA(env, "spec_dst"); esock_atom_stream = MKA(env, "stream"); esock_atom_tcp = MKA(env, "tcp"); esock_atom_throughput = MKA(env, "throughput"); + esock_atom_timestamp = MKA(env, "timestamp"); esock_atom_tos = MKA(env, "tos"); esock_atom_true = MKA(env, "true"); esock_atom_trunc = MKA(env, "trunc"); @@ -12981,6 +13087,7 @@ int on_load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info) esock_atom_udp = MKA(env, "udp"); esock_atom_undefined = MKA(env, "undefined"); esock_atom_unknown = MKA(env, "unknown"); + esock_atom_usec = MKA(env, "usec"); /* Global error codes */ esock_atom_eafnosupport = MKA(env, ESOCK_STR_EAFNOSUPPORT); diff --git a/erts/emulator/nifs/common/socket_util.c b/erts/emulator/nifs/common/socket_util.c index 232e8200df..59cd1a3408 100644 --- a/erts/emulator/nifs/common/socket_util.c +++ b/erts/emulator/nifs/common/socket_util.c @@ -227,6 +227,11 @@ char* esock_encode_sockaddr(ErlNifEnv* env, { char* xres; + UDBG( ("SUTIL", "esock_encode_sockaddr -> entry with" + "\r\n family: %d" + "\r\n addrLen: %d" + "\r\n", sockAddrP->sa.sa_family, addrLen) ); + switch (sockAddrP->sa.sa_family) { case AF_INET: xres = esock_encode_sockaddr_in4(env, &sockAddrP->in4, addrLen, eSockAddr); @@ -860,6 +865,74 @@ char* esock_encode_ip6_address(ErlNifEnv* env, +/* +++ esock_encode_timeval +++ + * + * Encode a timeval struct into its erlang form, a map with two fields: + * + * sec + * usec + * + */ +extern +char* esock_encode_timeval(ErlNifEnv* env, + struct timeval* timeP, + ERL_NIF_TERM* eTime) +{ + ERL_NIF_TERM keys[] = {esock_atom_sec, esock_atom_usec}; + ERL_NIF_TERM vals[] = {MKL(env, timeP->tv_sec), MKL(env, timeP->tv_usec)}; + + unsigned int numKeys = sizeof(keys) / sizeof(ERL_NIF_TERM); + unsigned int numVals = sizeof(vals) / sizeof(ERL_NIF_TERM); + + ESOCK_ASSERT( (numKeys == numVals) ); + + if (!MKMA(env, keys, vals, numKeys, eTime)) + return ESOCK_STR_EINVAL; + + return NULL; +} + + + +/* +++ esock_decode_timeval +++ + * + * Decode a timeval in its erlang form (a map) into its native form, + * a timeval struct. + * + */ +extern +char* esock_decode_timeval(ErlNifEnv* env, + ERL_NIF_TERM eTime, + struct timeval* timeP) +{ + ERL_NIF_TERM eSec, eUSec; + size_t sz; + + // It must be a map + if (!IS_MAP(env, eTime)) + return ESOCK_STR_EINVAL; + + // It must have atleast two attributes + if (!enif_get_map_size(env, eTime, &sz) || (sz < 2)) + return ESOCK_STR_EINVAL; + + if (!GET_MAP_VAL(env, eTime, esock_atom_sec, &eSec)) + return ESOCK_STR_EINVAL; + + if (!GET_MAP_VAL(env, eTime, esock_atom_usec, &eUSec)) + return ESOCK_STR_EINVAL; + + if (!GET_LONG(env, eSec, &timeP->tv_sec)) + return ESOCK_STR_EINVAL; + + if (!GET_LONG(env, eUSec, &timeP->tv_usec)) + return ESOCK_STR_EINVAL; + + return NULL; +} + + + /* +++ esock_decode_domain +++ * * Decode the Erlang form of the 'domain' type, that is: diff --git a/erts/emulator/nifs/common/socket_util.h b/erts/emulator/nifs/common/socket_util.h index 5d720936f3..22eed77d6e 100644 --- a/erts/emulator/nifs/common/socket_util.h +++ b/erts/emulator/nifs/common/socket_util.h @@ -110,6 +110,12 @@ char* esock_encode_ip6_address(ErlNifEnv* env, ERL_NIF_TERM* eAddr); #endif +extern char* esock_encode_timeval(ErlNifEnv* env, + struct timeval* timeP, + ERL_NIF_TERM* eTime); +extern char* esock_decode_timeval(ErlNifEnv* env, + ERL_NIF_TERM eTime, + struct timeval* timeP); extern char* esock_decode_domain(ErlNifEnv* env, ERL_NIF_TERM eDomain, diff --git a/erts/preloaded/ebin/socket.beam b/erts/preloaded/ebin/socket.beam index f6ca653fed..2219b1b271 100644 Binary files a/erts/preloaded/ebin/socket.beam and b/erts/preloaded/ebin/socket.beam differ diff --git a/erts/preloaded/src/socket.erl b/erts/preloaded/src/socket.erl index b9d1705d45..5902c161db 100644 --- a/erts/preloaded/src/socket.erl +++ b/erts/preloaded/src/socket.erl @@ -90,12 +90,13 @@ raw_socket_option/0, timeval/0, - ip_tos_flag/0, + ip_tos/0, ip_mreq/0, ip_mreq_source/0, ip_pmtudisc/0, ip_msfilter_mode/0, ip_msfilter/0, + ip_pktinfo/0, ipv6_mreq/0, ipv6_pmtudisc/0, sctp_event_subscribe/0, @@ -107,6 +108,9 @@ msghdr_flag/0, msghdr_flags/0, msghdr/0, + cmsghdr_level/0, + cmsghdr_type/0, + cmsghdr_data/0, cmsghdr/0, uint8/0, @@ -168,11 +172,11 @@ usec := integer()}. %% If the integer value is used its up to the caller to ensure its valid! --type ip_tos_flag() :: lowdeley | - throughput | - reliability | - mincost | - integer(). +-type ip_tos() :: lowdeley | + throughput | + reliability | + mincost | + integer(). %% This type is used when requesting to become member of a multicast %% group with a call to setopt. Example: @@ -516,14 +520,32 @@ %% Only valid with recvmsg flags => msghdr_flags() }. -%% At some point we should be able to encode/decode the most common types -%% of control message headers. For now, we leave/take the data part raw -%% (as a binary) and leave it to the user to figure out (how to encode/decode -%% that bit). +%% We are able to (completely) decode *some* control message headers. +%% Even if we are able to decode both level and type, we may not be +%% able to decode the data, in which case it will be a binary. +-type ip_pktinfo() :: #{ + ifindex => non_neg_integer(), % Interface Index + spec_dst => ip4_address(), % Local Address + addr => ip4_address() % Header Destination address + }. +-type cmsghdr_level() :: socket | protocol() | integer(). +-type cmsghdr_type() :: timestamp | + rights | + credentials | + tos | + ttl | + origdstaddr | + integer(). +-type cmsghdr_data() :: timeval() | % if level = socket and type = timstamp + ip_pktinfo() | % if level = ip and type = pktinfo + ip_tos() | % if level = ip and type = tos + integer() | % if level = ip and type = ttl + sockaddr_in4() | % if level = ip and type = origdstaddr + binary(). -type cmsghdr() :: #{ - level => protocol() | integer(), - type => integer(), - data => binary() + level => cmsghdr_level(), + type => cmsghdr_type(), + data => cmsghdr_data() }. -define(SOCKET_DOMAIN_LOCAL, 1). @@ -1808,6 +1830,10 @@ do_recvmsg(SockRef, BufSz, CtrlSz, EFlags, Timeout) -> {error, timeout} end; + {error, closed} = ERROR -> + do_close(SockRef), + ERROR; + {error, _Reason} = ERROR -> ERROR diff --git a/lib/kernel/test/socket_server.erl b/lib/kernel/test/socket_server.erl index a804078917..f252be1683 100644 --- a/lib/kernel/test/socket_server.erl +++ b/lib/kernel/test/socket_server.erl @@ -21,17 +21,19 @@ -module(socket_server). -export([ - start/0, start/4, - start_tcp/0, start_tcp/1, start_tcp/2, - start_tcp4/0, start_tcp4/1, start_tcp6/0, start_tcp6/1, - start_udp/0, start_udp/1, start_udp/2, - start_udp4/0, start_udp4/1, start_udp6/0, start_udp6/1, + start/0, start/5, + start_tcp/0, start_tcp/1, start_tcp/3, + start_tcp4/0, start_tcp4/1, start_tcp4/2, + start_tcp6/0, start_tcp6/1, start_tcp6/2, + start_udp/0, start_udp/1, start_udp/3, + start_udp4/0, start_udp4/1, start_udp4/2, + start_udp6/0, start_udp6/1, start_udp6/2, start_sctp/0, start_sctp/1 ]). -define(LIB, socket_lib). --record(manager, {socket, peek, acceptors, handler_id, handlers}). +-record(manager, {socket, msg, peek, acceptors, handler_id, handlers}). -record(acceptor, {id, socket, manager}). -record(handler, {socket, peek, msg, type, manager}). @@ -50,16 +52,22 @@ start_tcp4() -> start_tcp4(false). start_tcp4(Peek) -> - start_tcp(inet, Peek). + start_tcp4(false, Peek). + +start_tcp4(UseMsg, Peek) -> + start_tcp(inet, UseMsg, Peek). start_tcp6() -> start_tcp6(false). start_tcp6(Peek) -> - start_tcp(inet6, Peek). + start_tcp6(false, Peek). + +start_tcp6(UseMsg, Peek) -> + start_tcp(inet6, UseMsg, Peek). -start_tcp(Domain, Peek) when is_boolean(Peek) -> - start(Domain, stream, tcp, Peek). +start_tcp(Domain, UseMsg, Peek) when is_boolean(UseMsg) andalso is_boolean(Peek) -> + start(Domain, stream, tcp, UseMsg, Peek). start_udp() -> start_udp4(). @@ -71,28 +79,34 @@ start_udp4() -> start_udp4(false). start_udp4(Peek) -> - start_udp(inet, Peek). + start_udp4(false, Peek). + +start_udp4(UseMsg, Peek) -> + start_udp(inet, UseMsg, Peek). start_udp6() -> - start_udp6(false). + start_udp6(false, false). start_udp6(Peek) -> - start_udp(inet6, Peek). + start_udp6(false, Peek). + +start_udp6(UseMsg, Peek) -> + start_udp(inet6, UseMsg, Peek). -start_udp(Domain, Peek) when is_boolean(Peek) -> - start(Domain, dgram, udp, Peek). +start_udp(Domain, UseMsg, Peek) when is_boolean(UseMsg) andalso is_boolean(Peek) -> + start(Domain, dgram, udp, UseMsg, Peek). start_sctp() -> start_sctp(inet). start_sctp(Domain) when ((Domain =:= inet) orelse (Domain =:= inet6)) -> - start(Domain, seqpacket, sctp, false). + start(Domain, seqpacket, sctp, true, false). -start(Domain, Type, Proto, Peek) -> +start(Domain, Type, Proto, UseMsg, Peek) -> put(sname, "starter"), i("try start manager"), - {Pid, MRef} = manager_start(Domain, Type, Proto, Peek), + {Pid, MRef} = manager_start(Domain, Type, Proto, UseMsg, Peek), i("manager (~p) started", [Pid]), loop(Pid, MRef). @@ -107,8 +121,8 @@ loop(Pid, MRef) -> %% ========================================================================= -manager_start(Domain, Type, Proto, Peek) -> - spawn_monitor(fun() -> manager_init(Domain, Type, Proto, Peek) end). +manager_start(Domain, Type, Proto, UseMsg, Peek) -> + spawn_monitor(fun() -> manager_init(Domain, Type, Proto, UseMsg, Peek) end). manager_start_handler(Pid, Sock) -> manager_request(Pid, {start_handler, Sock}). @@ -123,19 +137,20 @@ manager_reply(Pid, Ref, Reply) -> ?LIB:reply(manager, Pid, Ref, Reply). -manager_init(Domain, Type, Proto, Peek) -> +manager_init(Domain, Type, Proto, UseMsg, Peek) -> put(sname, "manager"), - do_manager_init(Domain, Type, Proto, Peek). + do_manager_init(Domain, Type, Proto, UseMsg, Peek). -do_manager_init(Domain, stream = Type, Proto, Peek) -> +do_manager_init(Domain, stream = Type, Proto, UseMsg, Peek) -> i("try start acceptor(s)"), {Sock, Acceptors} = manager_stream_init(Domain, Type, Proto), manager_loop(#manager{socket = Sock, + msg = UseMsg, peek = Peek, acceptors = Acceptors, handler_id = 1, handlers = []}); -do_manager_init(Domain, dgram = Type, Proto, Peek) -> +do_manager_init(Domain, dgram = Type, Proto, UseMsg, Peek) -> i("try open socket"), case socket:open(Domain, Type, Proto) of {ok, Sock} -> @@ -179,11 +194,12 @@ do_manager_init(Domain, dgram = Type, Proto, Peek) -> {ok, Name} -> f("~p", [Name]); {error, R} -> f("error: ~p", [R]) end]), - case handler_start(1, true, Sock, Peek) of + case handler_start(1, Sock, UseMsg, Peek) of {ok, {Pid, MRef}} -> i("handler (~p) started", [Pid]), handler_continue(Pid), manager_loop(#manager{peek = Peek, + msg = UseMsg, handler_id = 2, % Just in case handlers = [{1, Pid, MRef}]}); {error, SReason} -> @@ -196,7 +212,7 @@ do_manager_init(Domain, dgram = Type, Proto, Peek) -> "~n ~p", [OReason]), exit({failed_open_socket, OReason}) end; -do_manager_init(Domain, seqpacket = Type, sctp = Proto, _Peek) -> +do_manager_init(Domain, seqpacket = Type, sctp = Proto, _UseMsg, _Peek) -> %% This is as far as I have got with SCTP at the moment... case socket:open(Domain, Type, Proto) of {ok, Sock} -> @@ -248,9 +264,9 @@ do_manager_init(Domain, seqpacket = Type, sctp = Proto, _Peek) -> {error, Reason} -> exit({failed_open, Reason}) end; -do_manager_init(Domain, raw = Type, Proto, Peek) when is_integer(Proto) -> - do_manager_init(Domain, Type, {raw, Proto}, Peek); -do_manager_init(Domain, raw = Type, Proto, _Peek) -> +do_manager_init(Domain, raw = Type, Proto, UseMsg, Peek) when is_integer(Proto) -> + do_manager_init(Domain, Type, {raw, Proto}, UseMsg, Peek); +do_manager_init(Domain, raw = Type, Proto, _UseMsg, _Peek) -> case socket:open(Domain, Type, Proto) of {ok, Sock} -> i("(sctp) socket opened: " @@ -401,11 +417,12 @@ manager_handle_down(#manager{acceptors = Acceptors, manager_handle_request(#manager{peek = Peek, + msg = UseMsg, handler_id = HID, handlers = Handlers} = M, Pid, Ref, {start_handler, Sock}) -> i("try start handler (~w)", [HID]), - case handler_start(HID, false, Sock, Peek) of + case handler_start(HID, Sock, UseMsg, Peek) of {ok, {HPid, HMRef}} -> i("handler ~w started", [HID]), manager_reply(Pid, Ref, {ok, HPid}), @@ -564,10 +581,10 @@ acceptor_handle_accept_success(#acceptor{manager = Manager}, Sock) -> %% ========================================================================= -handler_start(ID, Msg, Sock, Peek) -> +handler_start(ID, Sock, UseMsg, Peek) -> Self = self(), H = {Pid, _} = spawn_monitor(fun() -> - handler_init(Self, ID, Msg, Peek, Sock) + handler_init(Self, ID, UseMsg, Peek, Sock) end), receive {handler, Pid, ok} -> @@ -720,7 +737,16 @@ handler_init(Manager, ID, Msg, Peek, Sock) -> RtHdr, AuthHdr, HopLimit, HopOpts, DstOpts, FlowInfo, UHops]), - SIP = + %% ok = socket:setopt(Sock, otp, debug, true), + %% case socket:getopt(Sock, 0, {13, int}) of + %% {ok, Val} -> + %% i("PktOpts ok: ~p", [Val]); + %% {error, Reason} -> + %% e("PktOpts err: ~p", [Reason]) + %% end, + %% ok = socket:setopt(Sock, otp, debug, false), + SSO = fun(O, V) -> soso(Sock, O, V) end, + SIP4 = fun(O, V) -> if (Type =:= dgram) -> @@ -729,8 +755,10 @@ handler_init(Manager, ID, Msg, Peek, Sock) -> ok end end, + SSO(timestamp, true), + SIP4(pktinfo, true), ok = soip(Sock, recvtos, true), - SIP(recvttl, true), + SIP4(recvttl, true), ok = soip(Sock, recvorigdstaddr, true), handler_loop(#handler{msg = Msg, @@ -743,8 +771,8 @@ handler_init(Manager, ID, Msg, Peek, Sock) -> so(Sock, Lvl, Opt, Val) -> ok = socket:setopt(Sock, Lvl, Opt, Val). -%% soso(Sock, Opt, Val) -> -%% so(Sock, socket, Opt, Val). +soso(Sock, Opt, Val) -> + so(Sock, socket, Opt, Val). soip(Sock, Opt, Val) -> so(Sock, ip, Opt, Val). @@ -791,16 +819,29 @@ handler_loop(H) -> recv(#handler{peek = true, socket = Sock, type = stream}) -> peek_recv(Sock); -recv(#handler{peek = false, socket = Sock, type = stream}) -> - do_recv(Sock); +recv(#handler{socket = Sock, msg = true, type = stream}) -> + case socket:recvmsg(Sock) of + {ok, #{addr := undefined = Source, + iov := [Data], + ctrl := CMsgHdrs, + flags := Flags}} -> + i("received message: " + "~n CMsgHdrs: ~p" + "~n Flags: ~p", [CMsgHdrs, Flags]), + {ok, {Source, Data}}; + {ok, X} -> + e("received *unexpected* message: " + "~n ~p", [X]), + {error, {unexpected, X}}; + {error, _} = ERROR -> + ERROR + end; recv(#handler{socket = Sock, msg = true, type = dgram}) -> - %% ok = socket:setopt(Sock, otp, debug, true), case socket:recvmsg(Sock) of {ok, #{addr := Source, iov := [Data], ctrl := CMsgHdrs, flags := Flags}} -> - %% ok = socket:setopt(Sock, otp, debug, false), i("received message: " "~n CMsgHdrs: ~p" "~n Flags: ~p", [CMsgHdrs, Flags]), @@ -810,6 +851,8 @@ recv(#handler{socket = Sock, msg = true, type = dgram}) -> {error, _} = ERROR -> ERROR end; +recv(#handler{peek = false, socket = Sock, type = stream}) -> + do_recv(Sock); recv(#handler{peek = Peek, socket = Sock, type = dgram}) when (Peek =:= true) -> %% ok = socket:setopt(Sock, otp, debug, true), -- cgit v1.2.3 From d8b1eace1cfe3184497752f345e5f6bc5def9769 Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Fri, 3 Aug 2018 12:33:22 +0200 Subject: [socket-nif] Add preliminary support for sendmsg Added function sendmsg/2,3,4. Actually worked on the first try. Something must be wrong... Still no supported cmsghdr's (only support headers where the data part is already a binary, which therefor does not require any processing). So if the cmsghdrs actually work is unclear. OTP-14831 --- erts/doc/src/socket.xml | 49 +++ erts/emulator/nifs/common/socket_nif.c | 526 +++++++++++++++++++++++++++++++- erts/emulator/nifs/common/socket_util.c | 59 +++- erts/emulator/nifs/common/socket_util.h | 7 + erts/preloaded/ebin/socket.beam | Bin 61704 -> 64304 bytes erts/preloaded/src/socket.erl | 172 ++++++++++- lib/kernel/test/socket_client.erl | 15 +- lib/kernel/test/socket_server.erl | 10 + 8 files changed, 813 insertions(+), 25 deletions(-) diff --git a/erts/doc/src/socket.xml b/erts/doc/src/socket.xml index 2fb922408b..93a3a8172e 100644 --- a/erts/doc/src/socket.xml +++ b/erts/doc/src/socket.xml @@ -402,6 +402,40 @@ + + + + + + + Receive a message from a socket. + +

Receive a message from a socket.

+

This function reads "messages", which means that regardless of + how much we want to read, it returns when we get a message.

+

The message will be delivered in the form of a msghdr(), + which may contain the source address (if socket not connected), + a list of cmsghdr() (depends on what socket options have + been set and what the protocol and platform supports) and + also a set of flags, providing further info about the read .

+ +

The BufSz argument basically defines the size of the + receive buffer. By setting the value to zero (0), the configured + size (setopt with Level = otp) is used.

+ +

The CtrlSz argument basically defines the size of the + receive buffer for the control messages. + By setting the value to zero (0), the configured size (setopt + with Level = otp) is used.

+ +

It may be impossible to know what (buffer) size is appropriate + "in advance", and in those cases it may be convenient to use the + (recv) 'peek' flag. When this flag is provided, the message is *not* + "consumed" from the underlying buffers, so another recvmsg call + is needed, possibly with a then adjusted buffer size.

+
+
+ @@ -413,6 +447,21 @@ + + + + + + Send a message on a socket. + +

Send a message on a socket. The destination, if needed (socket not + connected) is provided in the MsgHdr, which also + contains the message to send, The MsgHdr may also contain + an list of optional cmsghdr() (depends on what the protocol and + platform supports).

+
+
+ diff --git a/erts/emulator/nifs/common/socket_nif.c b/erts/emulator/nifs/common/socket_nif.c index 4144341d71..0d584306f1 100644 --- a/erts/emulator/nifs/common/socket_nif.c +++ b/erts/emulator/nifs/common/socket_nif.c @@ -397,9 +397,10 @@ static void (*esock_sctp_freepaddrs)(struct sockaddr *addrs) = NULL; #define IS_CONNECTING(d) \ (((d)->state & SOCKET_FLAG_CON) == SOCKET_FLAG_CON) +/* #define IS_BUSY(d) \ (((d)->state & SOCKET_FLAG_BUSY) == SOCKET_FLAG_BUSY) - +*/ #define SOCKET_SEND_FLAG_CONFIRM 0 #define SOCKET_SEND_FLAG_DONTROUTE 1 @@ -420,6 +421,7 @@ static void (*esock_sctp_freepaddrs)(struct sockaddr *addrs) = NULL; #define SOCKET_RECV_BUFFER_SIZE_DEFAULT 2048 #define SOCKET_RECV_CTRL_BUFFER_SIZE_DEFAULT 1024 +#define SOCKET_SEND_CTRL_BUFFER_SIZE_DEFAULT 1024 #define VT2S(__VT__) (((__VT__) == SOCKET_OPT_VALUE_TYPE_UNSPEC) ? "unspec" : \ (((__VT__) == SOCKET_OPT_VALUE_TYPE_INT) ? "int" : \ @@ -676,6 +678,7 @@ static unsigned long one_value = 1; recvfrom((s),(buf),(blen),(flag),(addr),(alen)) #define sock_recvmsg(s,msghdr,flag) recvmsg((s),(msghdr),(flag)) #define sock_send(s,buf,len,flag) send((s), (buf), (len), (flag)) +#define sock_sendmsg(s,msghdr,flag) sendmsg((s),(msghdr),(flag)) #define sock_sendto(s,buf,blen,flag,addr,alen) \ sendto((s),(buf),(blen),(flag),(addr),(alen)) #define sock_setopt(s,l,o,v,ln) setsockopt((s),(l),(o),(v),(ln)) @@ -773,6 +776,7 @@ typedef struct { /* +++ Config & Misc stuff +++ */ size_t rBufSz; // Read buffer size (when data length = 0 is specified) size_t rCtrlSz; // Read control buffer size + size_t wCtrlSz; // Write control buffer size BOOLEAN_T iow; // Inform On Wrap BOOLEAN_T dbg; @@ -878,6 +882,9 @@ static ERL_NIF_TERM nif_send(ErlNifEnv* env, static ERL_NIF_TERM nif_sendto(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); +static ERL_NIF_TERM nif_sendmsg(ErlNifEnv* env, + int argc, + const ERL_NIF_TERM argv[]); static ERL_NIF_TERM nif_recv(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); @@ -953,6 +960,11 @@ static ERL_NIF_TERM nsendto(ErlNifEnv* env, int flags, SocketAddress* toAddrP, unsigned int toAddrLen); +static ERL_NIF_TERM nsendmsg(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM sendRef, + ERL_NIF_TERM eMsgHdr, + int flags); static ERL_NIF_TERM nrecv(ErlNifEnv* env, SocketDescriptor* descP, ERL_NIF_TERM recvRef, @@ -1902,13 +1914,30 @@ extern char* encode_cmsghdrs(ErlNifEnv* env, ErlNifBinary* cmsgBinP, struct msghdr* msgHdrP, ERL_NIF_TERM* eCMsgHdr); +extern char* decode_cmsghdrs(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eCMsgHdr, + void* cmsgHdrBufP, + size_t cmsgHdrBufLen); +extern char* decode_cmsghdr(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eCMsgHdr, + void** bufP, + size_t* rem); static char* encode_cmsghdr_level(ErlNifEnv* env, int level, ERL_NIF_TERM* eLevel); +static char* decode_cmsghdr_level(ErlNifEnv* env, + ERL_NIF_TERM eLevel, + int* level); static char* encode_cmsghdr_type(ErlNifEnv* env, int level, int type, ERL_NIF_TERM* eType); +static char* decode_cmsghdr_type(ErlNifEnv* env, + int level, + ERL_NIF_TERM eType, + int* type); static char* encode_cmsghdr_data(ErlNifEnv* env, ERL_NIF_TERM ctrlBuf, int level, @@ -2355,6 +2384,7 @@ static SocketData data; * nif_accept(LSock, Ref) * nif_send(Sock, SendRef, Data, Flags) * nif_sendto(Sock, SendRef, Data, Dest, Flags) + * nif_sendmsg(Sock, SendRef, MsgHdr, Flags) * nif_recv(Sock, RecvRef, Length, Flags) * nif_recvfrom(Sock, RecvRef, BufSz, Flags) * nif_recvmsg(Sock, RecvRef, BufSz, CtrlSz, Flags) @@ -3641,8 +3671,8 @@ ERL_NIF_TERM nif_sendto(ErlNifEnv* env, &remoteAddr, remoteAddrLen); SGDBG( ("SOCKET", "nif_sendto -> done with result: " - "\r\n %T" - "\r\n", res) ); + "\r\n %T" + "\r\n", res) ); return res; } @@ -3687,6 +3717,197 @@ ERL_NIF_TERM nsendto(ErlNifEnv* env, +/* ---------------------------------------------------------------------- + * nif_sendmsg + * + * Description: + * Send a message on a socket + * + * Arguments: + * Socket (ref) - Points to the socket descriptor. + * SendRef - A unique id for this (send) request. + * MsgHdr - Message Header - data and (maybe) control and dest + * Flags - Send flags. + */ + +static +ERL_NIF_TERM nif_sendmsg(ErlNifEnv* env, + int argc, + const ERL_NIF_TERM argv[]) +{ + ERL_NIF_TERM res, sendRef, eMsgHdr; + SocketDescriptor* descP; + unsigned int eflags; + int flags; + + SGDBG( ("SOCKET", "nif_sendmsg -> entry with argc: %d\r\n", argc) ); + + /* Extract arguments and perform preliminary validation */ + + if ((argc != 4) || + !enif_get_resource(env, argv[0], sockets, (void**) &descP) || + !IS_MAP(env, argv[2]) || + !GET_UINT(env, argv[3], &eflags)) { + return enif_make_badarg(env); + } + sendRef = argv[1]; + eMsgHdr = argv[2]; + + SSDBG( descP, + ("SOCKET", "nif_sendmsg -> args when sock = %d:" + "\r\n Socket: %T" + "\r\n sendRef: %T" + "\r\n eflags: %d" + "\r\n", + descP->sock, argv[0], sendRef, eflags) ); + + /* THIS TEST IS NOT CORRECT!!! */ + if (!IS_OPEN(descP)) + return esock_make_error(env, esock_atom_einval); + + if (!esendflags2sendflags(eflags, &flags)) + return esock_make_error(env, esock_atom_einval); + + res = nsendmsg(env, descP, sendRef, eMsgHdr, flags); + + SGDBG( ("SOCKET", "nif_sendmsg -> done with result: " + "\r\n %T" + "\r\n", res) ); + + return res; +} + + +static +ERL_NIF_TERM nsendmsg(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM sendRef, + ERL_NIF_TERM eMsgHdr, + int flags) +{ + ERL_NIF_TERM res, eAddr, eIOV, eCtrl; + SocketAddress addr; + struct msghdr msgHdr; + ErlNifBinary* iovBins; + struct iovec* iov; + unsigned int iovLen; + void* ctrlBuf; + size_t ctrlBufLen; + int save_errno; + ssize_t written, dataSize; + char* xres; + + if (!descP->isWritable) + return enif_make_badarg(env); + + + /* Depending on if we are *connected* or not, we require + * different things in the msghdr map. + */ + if (IS_CONNECTED(descP)) { + + /* We don't need the address */ + + msgHdr.msg_name = NULL; + msgHdr.msg_namelen = 0; + + } else { + + /* We need the address */ + + msgHdr.msg_name = (void*) &addr; + msgHdr.msg_namelen = sizeof(addr); + sys_memzero((char *) msgHdr.msg_name, msgHdr.msg_namelen); + if (!GET_MAP_VAL(env, eMsgHdr, esock_atom_addr, &eAddr)) + return esock_make_error(env, esock_atom_einval); + if ((xres = esock_decode_sockaddr(env, eAddr, + msgHdr.msg_name, + &msgHdr.msg_namelen)) != NULL) + return esock_make_error_str(env, xres); + } + + + /* Extract the (other) attributes of the msghdr map: iov and maybe ctrl */ + + /* The *mandatory* iov, which must be a list */ + if (!GET_MAP_VAL(env, eMsgHdr, esock_atom_iov, &eIOV)) + return esock_make_error(env, esock_atom_einval); + + if (!GET_LIST_LEN(env, eIOV, &iovLen) && (iovLen > 0)) + return esock_make_error(env, esock_atom_einval); + + iovBins = MALLOC(iovLen * sizeof(ErlNifBinary)); + ESOCK_ASSERT( (iovBins != NULL) ); + + iov = MALLOC(iovLen * sizeof(struct iovec)); + ESOCK_ASSERT( (iov != NULL) ); + + /* The *opional* ctrl */ + if (GET_MAP_VAL(env, eMsgHdr, esock_atom_ctrl, &eCtrl)) { + ctrlBufLen = descP->wCtrlSz; + ctrlBuf = MALLOC(ctrlBufLen); + ESOCK_ASSERT( (ctrlBuf != NULL) ); + } else { + eCtrl = esock_atom_undefined; + ctrlBufLen = 0; + ctrlBuf = NULL; + } + + + /* Decode the iov and initiate that part of the msghdr */ + if ((xres = esock_decode_iov(env, eIOV, + iovBins, iov, iovLen, &dataSize)) != NULL) { + FREE(iovBins); + FREE(iov); + if (ctrlBuf != NULL) FREE(ctrlBuf); + return esock_make_error_str(env, xres); + } + msgHdr.msg_iov = iov; + msgHdr.msg_iovlen = iovLen; + + + /* Decode the ctrl and initiate that part of the msghdr */ + if (ctrlBuf != NULL) { + if ((xres = decode_cmsghdrs(env, descP, + eCtrl, ctrlBuf, ctrlBufLen)) != NULL) { + FREE(iovBins); + FREE(iov); + if (ctrlBuf != NULL) FREE(ctrlBuf); + return esock_make_error_str(env, xres); + } + } + msgHdr.msg_control = ctrlBuf; + msgHdr.msg_controllen = ctrlBufLen; + + + /* The msg-flags field is not used when sending, but zero it just in case */ + msgHdr.msg_flags = 0; + + + /* We ignore the wrap for the moment. + * Maybe we should issue a wrap-message to controlling process... + */ + cnt_inc(&descP->writeTries, 1); + + /* And now, finally, try to send the message */ + written = sock_sendmsg(descP->sock, &msgHdr, flags); + + if (IS_SOCKET_ERROR(written)) + save_errno = sock_errno(); + else + save_errno = -1; // The value does not actually matter in this case + + res = send_check_result(env, descP, written, dataSize, save_errno, sendRef); + + FREE(iovBins); + FREE(iov); + if (ctrlBuf != NULL) FREE(ctrlBuf); + + return res; +} + + + /* ---------------------------------------------------------------------- * nif_writev / nif_sendv * @@ -10198,13 +10419,14 @@ ERL_NIF_TERM send_check_result(ErlNifEnv* env, "\r\n saveErrno: %d" "\r\n", written, dataSize, saveErrno) ); - if (written == dataSize) { + if (written >= dataSize) { cnt_inc(&descP->writePkgCnt, 1); cnt_inc(&descP->writeByteCnt, written); SSDBG( descP, - ("SOCKET", "send_check_result -> everything written - done\r\n") ); + ("SOCKET", "send_check_result -> " + "everything written (%d,%d) - done\r\n", dataSize, written) ); return esock_atom_ok; @@ -10248,7 +10470,7 @@ ERL_NIF_TERM send_check_result(ErlNifEnv* env, SSDBG( descP, ("SOCKET", "send_check_result -> not entire package written\r\n") ); - return esock_make_ok2(env, enif_make_int(env, written)); + return esock_make_ok2(env, MKI(env, written)); } @@ -10791,6 +11013,7 @@ char* encode_msghdr(ErlNifEnv* env, + /* +++ encode_cmsghdrs +++ * * Encode a list of cmsghdr(). There can be 0 or more cmsghdr "blocks". @@ -10921,9 +11144,157 @@ char* encode_cmsghdrs(ErlNifEnv* env, +/* +++ decode_cmsghdrs +++ + * + * Decode a list of cmsghdr(). There can be 0 or more cmsghdr "blocks". + * + * Each element can either be a (erlang) map that needds to be decoded, + * or a (erlang) binary that just needs to be appended to the control + * buffer. + * + * Our "problem" is that we have no idea much memory we actually need. + * + */ + +extern +char* decode_cmsghdrs(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eCMsgHdr, + void* cmsgHdrBufP, + size_t cmsgHdrBufLen) +{ + ERL_NIF_TERM elem, tail, list; + void* bufP; + size_t rem; + unsigned int len; + int i; + char* xres; + + if (IS_LIST(env, eCMsgHdr) && GET_LIST_LEN(env, eCMsgHdr, &len)) { + for (i = 0, list = eCMsgHdr, rem = cmsgHdrBufLen, bufP = cmsgHdrBufP; + i < len; i++) { + + /* Extract the (current) head of the (cmsg hdr) list */ + if (!GET_LIST_ELEM(env, list, &elem, &tail)) + return ESOCK_STR_EINVAL; + + if ((xres = decode_cmsghdr(env, descP, elem, &bufP, &rem)) != NULL) + return xres; + + list = tail; + } + + xres = NULL; + } else { + xres = ESOCK_STR_EINVAL; + } + + return xres; +} + + +/* +++ decode_cmsghdr +++ + * + * Decode one cmsghdr(). Put the "result" into the buffer and advance the + * pointer (of the buffer) afterwards. Also update 'rem' accordingly. + * But before the actual decode, make sure that there is enough room in + * the buffer for the cmsg header (sizeof(*hdr) < rem). + * + * The eCMsgHdr should be a map with three fields: + * + * level :: cmsghdr_level() (socket | protocol() | integer()) + * type :: cmsghdr_type() (atom() | integer()) + * What values are valid depend on the level + * data :: cmsghdr_data() (term() | binary()) + * The type of the data depends on + * level and type, but can be a binary, + * which means that the data already coded. + */ +extern +char* decode_cmsghdr(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eCMsgHdr, + void** bufP, + size_t* rem) +{ + if (IS_MAP(env, eCMsgHdr)) { + ERL_NIF_TERM eLevel, eType, eData; + int level, type; + char* xres; + + /* First extract all three attributes (as terms) */ + + if (!GET_MAP_VAL(env, eCMsgHdr, esock_atom_level, &eLevel)) + return ESOCK_STR_EINVAL; + + if (!GET_MAP_VAL(env, eCMsgHdr, esock_atom_type, &eType)) + return ESOCK_STR_EINVAL; + + if (!GET_MAP_VAL(env, eCMsgHdr, esock_atom_data, &eData)) + return ESOCK_STR_EINVAL; + + /* Second, decode level */ + if ((xres = decode_cmsghdr_level(env, eLevel, &level)) != NULL) + return xres; + + /* third, decode type */ + if ((xres = decode_cmsghdr_type(env, level, eType, &type)) != NULL) + return xres; + + /* And finally data + * If its a binary, we are done. Otherwise, we need to check + * level and type to know what kind of data to expect. + * + * + * + * At the moment, the only data we support is a binary... + * + * + */ + + if (IS_BIN(env, eData)) { + ErlNifBinary bin; + size_t currentRem = *rem; + + if (!GET_BIN(env, eData, &bin)) { + return ESOCK_STR_EINVAL; + } else { + int len = CMSG_LEN(bin.size); // The cmsghdr + int space = CMSG_SPACE(bin.size); // With padding + /* Make sure it fits before we copy */ + if (currentRem >= space) { + struct cmsghdr* cmsgP = (struct cmsghdr*) bufP; + + /* The header */ + cmsgP->cmsg_len = len; + cmsgP->cmsg_level = level; + cmsgP->cmsg_type = type; + + /* And the data */ + sys_memcpy(CMSG_DATA(cmsgP), bin.data, bin.size); + *bufP += space; + *rem -= space; + } else { + return ESOCK_STR_EINVAL; + } + } + } else { + + /* Here is where we should have the proper data decode */ + + return ESOCK_STR_EINVAL; + } + } else { + return ESOCK_STR_EINVAL; + } + + return NULL; +} + + /* +++ encode_cmsghdr_level +++ * - * Encode the type part of the cmsghdr(). + * Encode the level part of the cmsghdr(). * */ @@ -10950,6 +11321,38 @@ char* encode_cmsghdr_level(ErlNifEnv* env, +/* +++ decode_cmsghdr_level +++ + * + * Decode the level part of the cmsghdr(). + * + */ + +static +char* decode_cmsghdr_level(ErlNifEnv* env, + ERL_NIF_TERM eLevel, + int* level) +{ + char* xres = NULL; + + if (IS_ATOM(env, eLevel)) { + if (COMPARE(eLevel, esock_atom_socket) == 0) { + *level = SOL_SOCKET; + xres = NULL; + } else { + xres = esock_decode_protocol(env, eLevel, level); + } + } else if (IS_NUM(env, eLevel)) { + if (!GET_INT(env, eLevel, level)) + xres = ESOCK_STR_EINVAL; + } else { + xres = ESOCK_STR_EINVAL; + } + + return xres; +} + + + /* +++ encode_cmsghdr_type +++ * * Encode the type part of the cmsghdr(). @@ -11073,6 +11476,113 @@ char* encode_cmsghdr_type(ErlNifEnv* env, +/* +++ decode_cmsghdr_type +++ + * + * Decode the type part of the cmsghdr(). + * + */ + +static +char* decode_cmsghdr_type(ErlNifEnv* env, + int level, + ERL_NIF_TERM eType, + int* type) +{ + char* xres = NULL; + + switch (level) { + case SOL_SOCKET: + if (COMPARE(eType, esock_atom_timestamp) == 0) { +#if defined(SO_TIMESTAMP) + *type = SO_TIMESTAMP; +#else + xres = ESOCK_STR_EINVAL; +#endif + } else if (COMPARE(eType, esock_atom_rights) == 0) { +#if defined(SCM_RIGHTS) + *type = SCM_RIGHTS; +#else + xres = ESOCK_STR_EINVAL; +#endif + } else if (COMPARE(eType, esock_atom_credentials) == 0) { +#if defined(SCM_CREDENTIALS) + *type = SCM_CREDENTIALS; +#else + xres = ESOCK_STR_EINVAL; +#endif + } else { + xres = ESOCK_STR_EINVAL; + } + break; + + +#if defined(SOL_IP) + case SOL_IP: +#else + case IPPROTO_IP: +#endif + if (COMPARE(eType, esock_atom_tos) == 0) { +#if defined(IP_TOS) + *type = IP_TOS; +#else + xres = ESOCK_STR_EINVAL; +#endif + } else if (COMPARE(eType, esock_atom_ttl) == 0) { +#if defined(IP_TTL) + *type = IP_TTL; +#else + xres = ESOCK_STR_EINVAL; +#endif + } else if (COMPARE(eType, esock_atom_pktinfo) == 0) { +#if defined(IP_PKTINFO) + *type = IP_PKTINFO; +#else + xres = ESOCK_STR_EINVAL; +#endif + } else if (COMPARE(eType, esock_atom_origdstaddr) == 0) { +#if defined(IP_ORIGDSTADDR) + *type = IP_ORIGDSTADDR; +#else + xres = ESOCK_STR_EINVAL; +#endif + } else { + xres = ESOCK_STR_EINVAL; + } + break; + +#if defined(SOL_IPV6) + case SOL_IPV6: + xres = ESOCK_STR_EINVAL; + break; +#endif + + case IPPROTO_TCP: + xres = ESOCK_STR_EINVAL; + break; + break; + + case IPPROTO_UDP: + xres = ESOCK_STR_EINVAL; + break; + break; + +#if defined(HAVE_SCTP) + case IPPROTO_SCTP: + xres = ESOCK_STR_EINVAL; + break; + break; +#endif + + default: + xres = ESOCK_STR_EINVAL; + break; + } + + return xres; +} + + + /* +++ encode_cmsghdr_data +++ * * Encode the data part of the cmsghdr(). @@ -11787,6 +12297,7 @@ SocketDescriptor* alloc_descriptor(SOCKET sock, HANDLE event) descP->rBufSz = SOCKET_RECV_BUFFER_SIZE_DEFAULT; descP->rCtrlSz = SOCKET_RECV_CTRL_BUFFER_SIZE_DEFAULT; + descP->wCtrlSz = SOCKET_SEND_CTRL_BUFFER_SIZE_DEFAULT; descP->iow = FALSE; descP->dbg = SOCKET_DEBUG_DEFAULT; @@ -12895,6 +13406,7 @@ ErlNifFunc socket_funcs[] = {"nif_accept", 2, nif_accept, 0}, {"nif_send", 4, nif_send, 0}, {"nif_sendto", 5, nif_sendto, 0}, + {"nif_sendmsg", 4, nif_sendmsg, 0}, {"nif_recv", 4, nif_recv, 0}, {"nif_recvfrom", 4, nif_recvfrom, 0}, {"nif_recvmsg", 5, nif_recvmsg, 0}, diff --git a/erts/emulator/nifs/common/socket_util.c b/erts/emulator/nifs/common/socket_util.c index 59cd1a3408..a73b40cd29 100644 --- a/erts/emulator/nifs/common/socket_util.c +++ b/erts/emulator/nifs/common/socket_util.c @@ -71,7 +71,7 @@ static char* make_sockaddr_un(ErlNifEnv* env, /* +++ esock_encode_iov +++ * - * Encode a IO Vector. In erlang we represented this as a list of binaries. + * Encode an IO Vector. In erlang we represented this as a list of binaries. * * We iterate through the IO vector, and as long as the remaining (rem) * number of bytes is greater than the size of the current buffer, we @@ -141,6 +141,61 @@ char* esock_encode_iov(ErlNifEnv* env, +/* +++ esock_decode_iov +++ + * + * Decode an IO Vector. In erlang we represented this as a list of binaries. + * + * We assume that we have already figured out how long the iov (actually + * eIOV) is (len), and therefor allocated an array of bins and iov to be + * used. + */ + +extern +char* esock_decode_iov(ErlNifEnv* env, + ERL_NIF_TERM eIOV, + ErlNifBinary* bufs, + struct iovec* iov, + size_t len, + ssize_t* totSize) +{ + uint16_t i; + ssize_t sz; + ERL_NIF_TERM elem, tail, list; + + UDBG( ("SUTIL", "esock_decode_iov -> entry with" + "\r\n (IOV) len: %d" + "\r\n", read, len) ); + + for (i = 0, list = eIOV, sz = 0; (i < len); i++) { + + UDBG( ("SUTIL", "esock_decode_iov -> " + "\r\n iov[%d].iov_len: %d" + "\r\n rem: %d" + "\r\n", i) ); + + if (!GET_LIST_ELEM(env, list, &elem, &tail)) + return ESOCK_STR_EINVAL; + + if (IS_BIN(env, elem) && GET_BIN(env, elem, &bufs[i])) { + iov[i].iov_base = bufs[i].data; + iov[i].iov_len = bufs[i].size; + sz += bufs[i].size; + } else { + return ESOCK_STR_EINVAL; + } + + list = tail; + } + + *totSize = sz; + + UDBG( ("SUTIL", "esock_decode_msghdr -> done (%d)\r\n", sz) ); + + return NULL; +} + + + /* +++ esock_decode_sockaddr +++ * * Decode a socket address - sockaddr. In erlang its represented as @@ -1100,7 +1155,7 @@ char* esock_encode_type(ErlNifEnv* env, -/* +++ esock_decode_protocol +++ +/* +++ esock_encode_protocol +++ * * Encode the native protocol to the Erlang form, that is: * diff --git a/erts/emulator/nifs/common/socket_util.h b/erts/emulator/nifs/common/socket_util.h index 22eed77d6e..d0b3076df1 100644 --- a/erts/emulator/nifs/common/socket_util.h +++ b/erts/emulator/nifs/common/socket_util.h @@ -43,6 +43,13 @@ char* esock_encode_iov(ErlNifEnv* env, ErlNifBinary* data, ERL_NIF_TERM* eIOV); extern +char* esock_decode_iov(ErlNifEnv* env, + ERL_NIF_TERM eIOV, + ErlNifBinary* bufs, + struct iovec* iov, + size_t len, + ssize_t* totSize); +extern char* esock_decode_sockaddr(ErlNifEnv* env, ERL_NIF_TERM eSockAddr, SocketAddress* sockAddrP, diff --git a/erts/preloaded/ebin/socket.beam b/erts/preloaded/ebin/socket.beam index 2219b1b271..f84b9b8369 100644 Binary files a/erts/preloaded/ebin/socket.beam and b/erts/preloaded/ebin/socket.beam differ diff --git a/erts/preloaded/src/socket.erl b/erts/preloaded/src/socket.erl index 5902c161db..1459ee4869 100644 --- a/erts/preloaded/src/socket.erl +++ b/erts/preloaded/src/socket.erl @@ -38,12 +38,12 @@ send/2, send/3, send/4, sendto/3, sendto/4, sendto/5, - %% sendmsg/4, + sendmsg/2, sendmsg/3, sendmsg/4, %% writev/4, OR SENDV? It will be strange for recv then: recvv (instead of readv) recv/1, recv/2, recv/3, recv/4, recvfrom/1, recvfrom/2, recvfrom/3, recvfrom/4, - recvmsg/1, recvmsg/2, recvmsg/5, + recvmsg/1, recvmsg/2, recvmsg/3, recvmsg/5, %% readv/3, close/1, @@ -508,13 +508,15 @@ -type msghdr_flags() :: [msghdr_flag()]. -type msghdr() :: #{ %% *Optional* target address - %% *If* this field is specified for an unconnected - %% socket, then it will be used as destination for the - %% datagram. + %% Used on an unconnected socket to specify the + %% target address for a datagram. addr => sockaddr(), iov => [binary()], - + + %% The maximum size of the control buffer is platform + %% specific. It is the users responsibility to ensure + %% that its not exceeded. ctrl => [cmsghdr()], %% Only valid with recvmsg @@ -577,10 +579,12 @@ -define(SOCKET_SEND_FLAG_NOSIGNAL, 4). -define(SOCKET_SEND_FLAG_OOB, 5). --define(SOCKET_SEND_FLAGS_DEFAULT, []). --define(SOCKET_SEND_TIMEOUT_DEFAULT, infinity). --define(SOCKET_SENDTO_FLAGS_DEFAULT, []). --define(SOCKET_SENDTO_TIMEOUT_DEFAULT, ?SOCKET_SEND_TIMEOUT_DEFAULT). +-define(SOCKET_SEND_FLAGS_DEFAULT, []). +-define(SOCKET_SEND_TIMEOUT_DEFAULT, infinity). +-define(SOCKET_SENDTO_FLAGS_DEFAULT, []). +-define(SOCKET_SENDTO_TIMEOUT_DEFAULT, ?SOCKET_SEND_TIMEOUT_DEFAULT). +-define(SOCKET_SENDMSG_FLAGS_DEFAULT, []). +-define(SOCKET_SENDMSG_TIMEOUT_DEFAULT, ?SOCKET_SEND_TIMEOUT_DEFAULT). -define(SOCKET_RECV_FLAG_CMSG_CLOEXEC, 0). -define(SOCKET_RECV_FLAG_ERRQUEUE, 1). @@ -1390,14 +1394,123 @@ do_sendto(SockRef, Data, Dest, EFlags, Timeout) -> %% --------------------------------------------------------------------------- +%% +%% The only part of the msghdr() that *must* exist (a connected +%% socket need not specify the addr field) is the iov. +%% The ctrl field is optional, and the addr and flags are not +%% used when sending. +%% -%% -spec sendmsg(Socket, MsgHdr, Flags) -> ok | {error, Reason} when -%% Socket :: socket(), -%% MsgHdr :: msg_hdr(), -%% Flags :: send_flags(), -%% Reason :: term(). +-spec sendmsg(Socket, MsgHdr) -> ok | {error, Reason} when + Socket :: socket(), + MsgHdr :: msghdr(), + Reason :: term(). + +sendmsg(Socket, MsgHdr) -> + sendmsg(Socket, MsgHdr, + ?SOCKET_SENDMSG_FLAGS_DEFAULT, ?SOCKET_SENDMSG_TIMEOUT_DEFAULT). + + +-spec sendmsg(Socket, MsgHdr, Flags) -> ok | {error, Reason} when + Socket :: socket(), + MsgHdr :: msghdr(), + Flags :: send_flags(), + Reason :: term() + ; (Socket, MsgHdr, Timeout) -> ok | {error, Reason} when + Socket :: socket(), + MsgHdr :: msghdr(), + Timeout :: timeout(), + Reason :: term(). + +sendmsg(Socket, MsgHdr, Flags) when is_list(Flags) -> + sendmsg(Socket, MsgHdr, Flags, ?SOCKET_SENDMSG_TIMEOUT_DEFAULT); +sendmsg(Socket, MsgHdr, Timeout) + when is_integer(Timeout) orelse (Timeout =:= infinity) -> + sendmsg(Socket, MsgHdr, ?SOCKET_SENDMSG_FLAGS_DEFAULT, Timeout). + + +-spec sendmsg(Socket, MsgHdr, Flags, Timeout) -> ok | {error, Reason} when + Socket :: socket(), + MsgHdr :: msghdr(), + Flags :: send_flags(), + Timeout :: timeout(), + Reason :: term(). + +sendmsg(#socket{ref = SockRef}, #{iov := IOV} = MsgHdr, Flags, Timeout) + when is_list(IOV) andalso + is_list(Flags) andalso + (is_integer(Timeout) orelse (Timeout =:= infinity)) -> + try ensure_msghdr(MsgHdr) of + M -> + EFlags = enc_send_flags(Flags), + do_sendmsg(SockRef, M, EFlags, Timeout) + catch + throw:T -> + T; + error:Reason -> + {error, Reason} + end. + +do_sendmsg(SockRef, MsgHdr, EFlags, Timeout) -> + TS = timestamp(Timeout), + SendRef = make_ref(), + case nif_sendmsg(SockRef, SendRef, MsgHdr, EFlags) of + ok -> + %% We are done + ok; + + {error, eagain} -> + receive + {select, SockRef, SendRef, ready_output} -> + do_sendmsg(SockRef, MsgHdr, EFlags, + next_timeout(TS, Timeout)) + after Timeout -> + nif_cancel(SockRef, sendmsg, SendRef), + flush_select_msgs(SockRef, SendRef), + {error, timeout} + end; + + {error, _} = ERROR -> + ERROR + end. + +ensure_msghdr(#{iov := IOV} = M) when is_list(IOV) andalso (IOV =/= []) -> + M#{iov := erlang:iolist_to_iovec(IOV)}; +ensure_msghdr(_) -> + einval(). + + + +%% send(Sock, #{ctrl = Ctrl} = MsgHdr, Flags) when is_list(Ctrl) -> +%% case encode_cmsghdrs(Ctrl) of +%% undefined -> +%% send(Sock, maps:remove(ctrl, MsgHdr), Flags); +%% Ctrl2 -> +%% send(Sock, MsgHdr#{ctrl = Ctrl2}, Flags) +%% end. + +%% encode_cmsghdrs([]) -> +%% undefined; +%% encode_cmsghdrs(Hdrs) -> +%% encode_cmsghdrs(Hdrs, []). + +%% encode_cmsghdrs([], Acc) -> +%% list_to_binary(lists:reverse(Acc)); +%% encode_cmsghdrs([H|T], Acc) when is_binary(H) -> +%% encode_cmsghdrs(T, [H|Acc]); +%% encode_cmsghdrs([#{level := Level, +%% type := Type, +%% data := Data} | T], Acc) -> +%% case nif_encode_cmsghdr(Level, Type, Data) of +%% {ok, Bin} when is_binary(Bin) -> +%% encode_cmsghdrs(T, [Bin | Acc]); +%% {error, _} = ERROR -> +%% ERROR +%% end. + + %% =========================================================================== %% @@ -1778,14 +1891,40 @@ do_recvfrom(SockRef, BufSz, EFlags, Timeout) -> %% --------------------------------------------------------------------------- %% +-spec recvmsg(Socket) -> {ok, MsgHdr} | {error, Reason} when + Socket :: socket(), + MsgHdr :: msghdr(), + Reason :: term(). + recvmsg(Socket) -> recvmsg(Socket, 0, 0, ?SOCKET_RECV_FLAGS_DEFAULT, ?SOCKET_RECV_TIMEOUT_DEFAULT). +-spec recvmsg(Socket, Flags) -> {ok, MsgHdr} | {error, Reason} when + Socket :: socket(), + Flags :: recv_flags(), + MsgHdr :: msghdr(), + Reason :: term() + ; (Socket, Timeout) -> {ok, MsgHdr} | {error, Reason} when + Socket :: socket(), + Timeout :: timeout(), + MsgHdr :: msghdr(), + Reason :: term(). + recvmsg(Socket, Flags) when is_list(Flags) -> recvmsg(Socket, 0, 0, Flags, ?SOCKET_RECV_TIMEOUT_DEFAULT); recvmsg(Socket, Timeout) -> recvmsg(Socket, 0, 0, ?SOCKET_RECV_FLAGS_DEFAULT, Timeout). +-spec recvmsg(Socket, Flags, Timeout) -> {ok, MsgHdr} | {error, Reason} when + Socket :: socket(), + Flags :: recv_flags(), + Timeout :: timeout(), + MsgHdr :: msghdr(), + Reason :: term(). + +recvmsg(Socket, Flags, Timeout) -> + recvmsg(Socket, 0, 0, Flags, Timeout). + -spec recvmsg(Socket, BufSz, CtrlSz, Flags, Timeout) -> {ok, MsgHdr} | {error, Reason} when @@ -3246,6 +3385,9 @@ nif_send(_SockRef, _SendRef, _Data, _Flags) -> nif_sendto(_SRef, _SendRef, _Data, _Dest, _Flags) -> erlang:error(badarg). +nif_sendmsg(_SRef, _SendRef, _MsgHdr, _Flags) -> + erlang:error(badarg). + nif_recv(_SRef, _RecvRef, _Length, _Flags) -> erlang:error(badarg). diff --git a/lib/kernel/test/socket_client.erl b/lib/kernel/test/socket_client.erl index 094b7eebc5..56424457e0 100644 --- a/lib/kernel/test/socket_client.erl +++ b/lib/kernel/test/socket_client.erl @@ -71,6 +71,10 @@ start(Domain, Type, Proto, Addr, Port) -> SA = #{family => Domain, addr => Addr, port => Port}, + %% The way we use tos only works because we + %% send so few messages (a new value for every + %% message). + put(tos, 1), do_start(Domain, Type, Proto, SA). do_start(Domain, stream = Type, Proto, SA) -> @@ -186,6 +190,7 @@ do_init(Domain, stream = Type, Proto) -> i("try (socket) bind"), case socket:bind(Sock, any) of {ok, _P} -> + ok = socket:setopt(Sock, ip, tos, mincost), Sock; {error, BReason} -> throw({bind, BReason}) @@ -271,7 +276,15 @@ send(#client{socket = Sock, type = dgram, dest = Dest}, Msg) -> %% i("try send to: " %% "~n ~p", [Dest]), %% ok = socket:setopt(Sock, otp, debug, true), - socket:sendto(Sock, Msg, Dest). + TOS = get(tos), + ok = socket:setopt(Sock, ip, tos, TOS), + case socket:sendto(Sock, Msg, Dest) of + ok = OK -> + put(tos, TOS+1), + OK; + {error, _} = ERROR -> + ERROR + end. recv(#client{socket = Sock, type = stream}) -> case socket:recv(Sock) of diff --git a/lib/kernel/test/socket_server.erl b/lib/kernel/test/socket_server.erl index f252be1683..34f354be32 100644 --- a/lib/kernel/test/socket_server.erl +++ b/lib/kernel/test/socket_server.erl @@ -900,8 +900,18 @@ peek_recvfrom(Sock, BufSz) -> end. +send(#handler{socket = Sock, msg = true, type = stream}, Msg, _) -> + MsgHdr = #{iov => [Msg]}, + socket:sendmsg(Sock, MsgHdr); send(#handler{socket = Sock, type = stream}, Msg, _) -> socket:send(Sock, Msg); +send(#handler{socket = Sock, msg = true, type = dgram}, Msg, Dest) -> + MsgHdr = #{addr => Dest, + iov => [Msg]}, + %% ok = socket:setopt(Sock, otp, debug, true), + Res = socket:sendmsg(Sock, MsgHdr), + %% ok = socket:setopt(Sock, otp, debug, false), + Res; send(#handler{socket = Sock, type = dgram}, Msg, Dest) -> socket:sendto(Sock, Msg, Dest). -- cgit v1.2.3 From 01601a4db44b3adccfbcc07129a4584649252736 Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Fri, 3 Aug 2018 15:08:30 +0200 Subject: [socket-nif] Add more data types (with doc) and (C) debug Add more debug printouts for the new sendmsg. Also added new (erlang) types with doc. OTP-14831 --- erts/doc/src/socket.xml | 12 +++++++ erts/emulator/nifs/common/socket_nif.c | 16 ++++++++- erts/preloaded/ebin/socket.beam | Bin 64304 -> 64540 bytes erts/preloaded/src/socket.erl | 64 +++++++++++++++++++++------------ lib/kernel/test/socket_server.erl | 3 ++ 5 files changed, 72 insertions(+), 23 deletions(-) diff --git a/erts/doc/src/socket.xml b/erts/doc/src/socket.xml index 93a3a8172e..b11b68cba5 100644 --- a/erts/doc/src/socket.xml +++ b/erts/doc/src/socket.xml @@ -146,6 +146,9 @@ + + + @@ -179,12 +182,21 @@ + + + + + + + + + diff --git a/erts/emulator/nifs/common/socket_nif.c b/erts/emulator/nifs/common/socket_nif.c index 0d584306f1..90b727eff7 100644 --- a/erts/emulator/nifs/common/socket_nif.c +++ b/erts/emulator/nifs/common/socket_nif.c @@ -3770,7 +3770,8 @@ ERL_NIF_TERM nif_sendmsg(ErlNifEnv* env, res = nsendmsg(env, descP, sendRef, eMsgHdr, flags); - SGDBG( ("SOCKET", "nif_sendmsg -> done with result: " + SSDBG( descP, + ("SOCKET", "nif_sendmsg -> done with result: " "\r\n %T" "\r\n", res) ); @@ -3808,6 +3809,8 @@ ERL_NIF_TERM nsendmsg(ErlNifEnv* env, /* We don't need the address */ + SSDBG( descP, ("SOCKET", "nsendmsg -> connected: no address\r\n") ); + msgHdr.msg_name = NULL; msgHdr.msg_namelen = 0; @@ -3820,6 +3823,11 @@ ERL_NIF_TERM nsendmsg(ErlNifEnv* env, sys_memzero((char *) msgHdr.msg_name, msgHdr.msg_namelen); if (!GET_MAP_VAL(env, eMsgHdr, esock_atom_addr, &eAddr)) return esock_make_error(env, esock_atom_einval); + + SSDBG( descP, ("SOCKET", "nsendmsg -> not connected: " + "\r\n %T" + "\r\n", eAddr) ); + if ((xres = esock_decode_sockaddr(env, eAddr, msgHdr.msg_name, &msgHdr.msg_namelen)) != NULL) @@ -3836,6 +3844,8 @@ ERL_NIF_TERM nsendmsg(ErlNifEnv* env, if (!GET_LIST_LEN(env, eIOV, &iovLen) && (iovLen > 0)) return esock_make_error(env, esock_atom_einval); + SSDBG( descP, ("SOCKET", "nsendmsg -> iov length: %d\r\n", iovLen) ); + iovBins = MALLOC(iovLen * sizeof(ErlNifBinary)); ESOCK_ASSERT( (iovBins != NULL) ); @@ -3866,6 +3876,10 @@ ERL_NIF_TERM nsendmsg(ErlNifEnv* env, msgHdr.msg_iovlen = iovLen; + SSDBG( descP, ("SOCKET", + "nsendmsg -> total (iov) data size: %d\r\n", dataSize) ); + + /* Decode the ctrl and initiate that part of the msghdr */ if (ctrlBuf != NULL) { if ((xres = decode_cmsghdrs(env, descP, diff --git a/erts/preloaded/ebin/socket.beam b/erts/preloaded/ebin/socket.beam index f84b9b8369..125191c1d0 100644 Binary files a/erts/preloaded/ebin/socket.beam and b/erts/preloaded/ebin/socket.beam differ diff --git a/erts/preloaded/src/socket.erl b/erts/preloaded/src/socket.erl index 1459ee4869..3d65f52a2b 100644 --- a/erts/preloaded/src/socket.erl +++ b/erts/preloaded/src/socket.erl @@ -99,6 +99,8 @@ ip_pktinfo/0, ipv6_mreq/0, ipv6_pmtudisc/0, + sctp_assoc_id/0, + sctp_sndrcvinfo/0, sctp_event_subscribe/0, sctp_assocparams/0, sctp_initmsg/0, @@ -116,7 +118,8 @@ uint8/0, uint16/0, uint20/0, - uint32/0 + uint32/0, + int32/0 ]). @@ -124,6 +127,7 @@ -type uint16() :: 0..16#FFFF. -type uint20() :: 0..16#FFFFF. -type uint32() :: 0..16#FFFFFFFF. +-type int32() :: -2147483648..2147483647. %% We support only a subset of all domains. @@ -171,6 +175,12 @@ -type timeval() :: #{sec := integer(), usec := integer()}. +-type ip_pktinfo() :: #{ + ifindex := non_neg_integer(), % Interface Index + spec_dst := ip4_address(), % Local Address + addr := ip4_address() % Header Destination address + }. + %% If the integer value is used its up to the caller to ensure its valid! -type ip_tos() :: lowdeley | throughput | @@ -208,16 +218,29 @@ %% slist: List of source addresses -type ip_msfilter_mode() :: include | exclude. --type ip_msfilter() :: #{multiaddr => ip4_address(), - interface => ip4_address(), - mode => ip_msfilter_mode(), - slist => [ip4_address()]}. +-type ip_msfilter() :: #{multiaddr := ip4_address(), + interface := ip4_address(), + mode := ip_msfilter_mode(), + slist := [ip4_address()]}. -type ipv6_mreq() :: #{multiaddr := ip6_address(), interface := non_neg_integer()}. -type ipv6_pmtudisc() :: ip_pmtudisc(). +-type sctp_assoc_id() :: int32(). +-type sctp_sndrcvinfo() :: #{ + stream := uint16(), + ssn := uint16(), + flags := uint16(), + ppid := uint16(), + context := uint16(), + timetolive := uint16(), + tsn := uint16(), + cumtsn := uint16(), + assoc_id := sctp_assoc_id() + }. + -type sctp_event_subscribe() :: #{data_in := boolean(), association := boolean(), address := boolean(), @@ -229,7 +252,7 @@ authentication := boolean(), sender_dry := boolean()}. --type sctp_assocparams() :: #{assoc_id := integer(), +-type sctp_assocparams() :: #{assoc_id := sctp_assoc_id(), max_rxt := uint16(), num_peer_dests := uint16(), peer_rwnd := uint32(), @@ -242,7 +265,7 @@ max_init_timeo := uint16() }. --type sctp_rtoinfo() :: #{assoc_id := integer(), +-type sctp_rtoinfo() :: #{assoc_id := sctp_assoc_id(), initial := uint32(), max := uint32(), min := uint32()}. @@ -510,32 +533,29 @@ %% *Optional* target address %% Used on an unconnected socket to specify the %% target address for a datagram. - addr => sockaddr(), + addr := sockaddr(), - iov => [binary()], + iov := [binary()], %% The maximum size of the control buffer is platform %% specific. It is the users responsibility to ensure %% that its not exceeded. - ctrl => [cmsghdr()], + ctrl := [cmsghdr()], %% Only valid with recvmsg - flags => msghdr_flags() + flags := msghdr_flags() }. %% We are able to (completely) decode *some* control message headers. %% Even if we are able to decode both level and type, we may not be %% able to decode the data, in which case it will be a binary. --type ip_pktinfo() :: #{ - ifindex => non_neg_integer(), % Interface Index - spec_dst => ip4_address(), % Local Address - addr => ip4_address() % Header Destination address - }. + -type cmsghdr_level() :: socket | protocol() | integer(). -type cmsghdr_type() :: timestamp | - rights | - credentials | + pktinfo | tos | ttl | + rights | + credentials | origdstaddr | integer(). -type cmsghdr_data() :: timeval() | % if level = socket and type = timstamp @@ -545,9 +565,9 @@ sockaddr_in4() | % if level = ip and type = origdstaddr binary(). -type cmsghdr() :: #{ - level => cmsghdr_level(), - type => cmsghdr_type(), - data => cmsghdr_data() + level := cmsghdr_level(), + type := cmsghdr_type(), + data := cmsghdr_data() }. -define(SOCKET_DOMAIN_LOCAL, 1). @@ -1476,6 +1496,7 @@ do_sendmsg(SockRef, MsgHdr, EFlags, Timeout) -> ensure_msghdr(#{iov := IOV} = M) when is_list(IOV) andalso (IOV =/= []) -> M#{iov := erlang:iolist_to_iovec(IOV)}; + %% M; ensure_msghdr(_) -> einval(). @@ -1887,7 +1908,6 @@ do_recvfrom(SockRef, BufSz, EFlags, Timeout) -> end. - %% --------------------------------------------------------------------------- %% diff --git a/lib/kernel/test/socket_server.erl b/lib/kernel/test/socket_server.erl index 34f354be32..b43642131b 100644 --- a/lib/kernel/test/socket_server.erl +++ b/lib/kernel/test/socket_server.erl @@ -915,6 +915,9 @@ send(#handler{socket = Sock, msg = true, type = dgram}, Msg, Dest) -> send(#handler{socket = Sock, type = dgram}, Msg, Dest) -> socket:sendto(Sock, Msg, Dest). +%% filler() -> +%% list_to_binary(lists:duplicate(2048, " FILLER ")). + %% ========================================================================= -- cgit v1.2.3 From ee2eadd1c61d4237ee4044260665c82edf559228 Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Fri, 3 Aug 2018 18:55:30 +0200 Subject: [socket-nif] Add support for (recvmsg) control message ipv6_pktinfo Added support for (recvmsg) control message ipv6_pktinfo, for level = ipv6 and type = pktinfo. This is enabled by setting the socket option: recvpktinfo for level ipv6. Not yet tested! OTP-14831 --- erts/doc/src/socket_usage.xml | 18 +++--- erts/emulator/nifs/common/socket_nif.c | 99 +++++++++++++++++++++++++++++---- erts/preloaded/ebin/socket.beam | Bin 64540 -> 64588 bytes erts/preloaded/src/socket.erl | 7 +++ 4 files changed, 103 insertions(+), 21 deletions(-) diff --git a/erts/doc/src/socket_usage.xml b/erts/doc/src/socket_usage.xml index b7459e97fa..9785830637 100644 --- a/erts/doc/src/socket_usage.xml +++ b/erts/doc/src/socket_usage.xml @@ -51,7 +51,7 @@ Value Type Set Get - Other + Other Requirements and comments
assoc_id @@ -90,7 +90,7 @@ Value Type Set Get - Other + Other Requirements and comments acceptcon @@ -118,7 +118,7 @@ integer() yes yes - require admin capability + requires admin capability domain @@ -255,7 +255,7 @@ Value Type Set Get - Other + Other Requirements and comments add_membership @@ -451,7 +451,7 @@ boolean() yes yes - require admin capability + requires admin capability ttl @@ -476,7 +476,7 @@ Value Type Set Get - Other + Other Requirements and comments addrform @@ -621,7 +621,7 @@ Value Type Set Get - Other + Other Requirements and comments congestion @@ -653,7 +653,7 @@ Value Type Set Get - Other + Other Requirements and comments cork @@ -671,7 +671,7 @@ Value Type Set Get - Other + Other Requirements and comments associnfo diff --git a/erts/emulator/nifs/common/socket_nif.c b/erts/emulator/nifs/common/socket_nif.c index 90b727eff7..f7e59678bb 100644 --- a/erts/emulator/nifs/common/socket_nif.c +++ b/erts/emulator/nifs/common/socket_nif.c @@ -1960,6 +1960,15 @@ static char* encode_cmsghdr_data_ip(ErlNifEnv* env, size_t dataPos, size_t dataLen, ERL_NIF_TERM* eCMsgHdrData); +#if defined(SOL_IPV6) +static char* encode_cmsghdr_data_ipv6(ErlNifEnv* env, + ERL_NIF_TERM ctrlBuf, + int type, + unsigned char* dataP, + size_t dataPos, + size_t dataLen, + ERL_NIF_TERM* eCMsgHdrData); +#endif extern char* encode_msghdr_flags(ErlNifEnv* env, SocketDescriptor* descP, int msgFlags, @@ -11419,19 +11428,19 @@ char* encode_cmsghdr_type(ErlNifEnv* env, *eType = esock_atom_tos; break; #endif - + #if defined(IP_TTL) case IP_TTL: *eType = esock_atom_ttl; break; #endif - + #if defined(IP_PKTINFO) case IP_PKTINFO: *eType = esock_atom_pktinfo; break; #endif - + #if defined(IP_ORIGDSTADDR) case IP_ORIGDSTADDR: *eType = esock_atom_origdstaddr; @@ -11443,17 +11452,23 @@ char* encode_cmsghdr_type(ErlNifEnv* env, break; } break; - + #if defined(SOL_IPV6) case SOL_IPV6: switch (type) { +#if defined(IPV6_PKTINFO) + case IPV6_PKTINFO: + *eType = esock_atom_pktinfo; + break; +#endif + default: xres = ESOCK_STR_EINVAL; break; } break; #endif - + case IPPROTO_TCP: switch (type) { default: @@ -11634,13 +11649,13 @@ char* encode_cmsghdr_data(ErlNifEnv* env, eCMsgHdrData); break; - /* - #if defined(SOL_IPV6) - case SOL_IPV6: - xres = encode_cmsghdr_data_ipv6(env, type, dataP, eCMsgHdrData); - break; - #endif - */ +#if defined(SOL_IPV6) + case SOL_IPV6: + xres = encode_cmsghdr_data_ipv6(env, ctrlBuf, type, + dataP, dataPos, dataLen, + eCMsgHdrData); + break; +#endif /* case IPPROTO_TCP: @@ -11827,6 +11842,66 @@ char* encode_cmsghdr_data_ip(ErlNifEnv* env, +/* +++ encode_cmsghdr_data_ipv6 +++ + * + * Encode the data part when protocol = IPv6 of the cmsghdr(). + * + */ +#if defined(SOL_IPV6) +static +char* encode_cmsghdr_data_ipv6(ErlNifEnv* env, + ERL_NIF_TERM ctrlBuf, + int type, + unsigned char* dataP, + size_t dataPos, + size_t dataLen, + ERL_NIF_TERM* eCMsgHdrData) +{ + char* xres; + + switch (type) { +#if defined(IPV6_PKTINFO) + case IPV6_PKTINFO: + { + struct in6_pktinfo* pktInfoP = (struct in6_pktinfo*) dataP; + ERL_NIF_TERM ifIndex = MKI(env, pktInfoP->ipi6_ifindex); + ERL_NIF_TERM addr; + + if ((xres = esock_encode_ip6_address(env, + &pktInfoP->ipi6_addr, + &addr)) != NULL) { + *eCMsgHdrData = esock_atom_undefined; + return xres; + } + + { + ERL_NIF_TERM keys[] = {esock_atom_addr, esock_atom_ifindex}; + ERL_NIF_TERM vals[] = {addr, ifIndex}; + unsigned int numKeys = sizeof(keys) / sizeof(ERL_NIF_TERM); + unsigned int numVals = sizeof(vals) / sizeof(ERL_NIF_TERM); + + ESOCK_ASSERT( (numKeys == numVals) ); + + if (!MKMA(env, keys, vals, numKeys, eCMsgHdrData)) { + *eCMsgHdrData = esock_atom_undefined; + return ESOCK_STR_EINVAL; + } + } + } + break; +#endif + + default: + *eCMsgHdrData = MKSBIN(env, ctrlBuf, dataPos, dataLen); + break; + } + + return NULL; +} +#endif + + + /* +++ encode_msghdr_flags +++ * * Encode a list of msghdr_flag(). diff --git a/erts/preloaded/ebin/socket.beam b/erts/preloaded/ebin/socket.beam index 125191c1d0..5b14af3ad8 100644 Binary files a/erts/preloaded/ebin/socket.beam and b/erts/preloaded/ebin/socket.beam differ diff --git a/erts/preloaded/src/socket.erl b/erts/preloaded/src/socket.erl index 3d65f52a2b..8af052e149 100644 --- a/erts/preloaded/src/socket.erl +++ b/erts/preloaded/src/socket.erl @@ -228,6 +228,12 @@ -type ipv6_pmtudisc() :: ip_pmtudisc(). +-type ipv6_pktinfo() :: #{ + addr := ip6_address(), + ifindex := integer() + }. + + -type sctp_assoc_id() :: int32(). -type sctp_sndrcvinfo() :: #{ stream := uint16(), @@ -560,6 +566,7 @@ integer(). -type cmsghdr_data() :: timeval() | % if level = socket and type = timstamp ip_pktinfo() | % if level = ip and type = pktinfo + ipv6_pktinfo() | % if level = ipv6 and type = pktinfo ip_tos() | % if level = ip and type = tos integer() | % if level = ip and type = ttl sockaddr_in4() | % if level = ip and type = origdstaddr -- cgit v1.2.3 From 929ae46220f402d6f36072c46fe27ba39ad48d1b Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Thu, 13 Sep 2018 15:44:21 +0200 Subject: [socket-nif] CMsgHdr and various doc related changes Updated the (send) cmsghdr type and the handling of it (in the nif code). Still not tested! Removed the is_loaded nif function. Tried to get fix the doc build problem (socket.erl *i think*), which causes socket.html generation to fail with: "cannot find module exporting type" To solve this I tried to run dialyzer on preloaded, and ran into problems with enc_setopt_value. Update various specs and types to "solve" this (which did not work). Updated the nif-stub functions to make dialyzer happy. --- erts/doc/src/socket.xml | 20 +- erts/emulator/nifs/common/net_nif.c | 27 +-- erts/emulator/nifs/common/socket_nif.c | 326 +++++++++++++++++++++------------ erts/preloaded/ebin/net.beam | Bin 6036 -> 5632 bytes erts/preloaded/ebin/socket.beam | Bin 64588 -> 66000 bytes erts/preloaded/src/Makefile | 29 ++- erts/preloaded/src/net.erl | 42 ++--- erts/preloaded/src/socket.erl | 263 ++++++++++++++++---------- 8 files changed, 430 insertions(+), 277 deletions(-) diff --git a/erts/doc/src/socket.xml b/erts/doc/src/socket.xml index b11b68cba5..35f7e8502d 100644 --- a/erts/doc/src/socket.xml +++ b/erts/doc/src/socket.xml @@ -176,11 +176,16 @@ + - + + + + @@ -427,7 +432,7 @@ how much we want to read, it returns when we get a message.

The message will be delivered in the form of a msghdr(), which may contain the source address (if socket not connected), - a list of cmsghdr() (depends on what socket options have + a list of cmsghdr_recv() (depends on what socket options have been set and what the protocol and platform supports) and also a set of flags, providing further info about the read .

@@ -466,11 +471,11 @@ Send a message on a socket. -

Send a message on a socket. The destination, if needed (socket not - connected) is provided in the MsgHdr, which also - contains the message to send, The MsgHdr may also contain - an list of optional cmsghdr() (depends on what the protocol and - platform supports).

+

Send a message on a socket. The destination, if needed + (socket not connected) is provided in the MsgHdr, + which also contains the message to send, + The MsgHdr may also contain an list of optional cmsghdr_send() + (depends on what the protocol and platform supports).

@@ -492,6 +497,7 @@ + Set options on a socket.

Set options on a socket.

diff --git a/erts/emulator/nifs/common/net_nif.c b/erts/emulator/nifs/common/net_nif.c index 309ad05a36..9905d99a04 100644 --- a/erts/emulator/nifs/common/net_nif.c +++ b/erts/emulator/nifs/common/net_nif.c @@ -241,9 +241,6 @@ static NetData data; extern char* erl_errno_id(int error); -static ERL_NIF_TERM nif_is_loaded(ErlNifEnv* env, - int argc, - const ERL_NIF_TERM argv[]); static ERL_NIF_TERM nif_info(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); @@ -439,8 +436,8 @@ static ErlNifResourceTypeInit netInit = { * * Utility and admin functions: * ---------------------------- - * nif_is_loaded/0 * nif_info/0 + * nif_command/1 * * The "proper" net functions: * ------------------------------ @@ -454,27 +451,6 @@ static ErlNifResourceTypeInit netInit = { */ -/* ---------------------------------------------------------------------- - * nif_is_loaded - * - * Description: - * This functions only purpose is to return the atom 'true'. - * This will happen *if* the (socket) nif library is loaded. - * If its not, the erlang (nif_is_loaded) will instead return - * 'false'. - */ -static -ERL_NIF_TERM nif_is_loaded(ErlNifEnv* env, - int argc, - const ERL_NIF_TERM argv[]) -{ - if (argc != 0) - return enif_make_badarg(env); - - return esock_atom_true; -} - - /* ---------------------------------------------------------------------- * nif_info * @@ -1522,7 +1498,6 @@ static ErlNifFunc net_funcs[] = { // Some utility functions - {"nif_is_loaded", 0, nif_is_loaded, 0}, {"nif_info", 0, nif_info, 0}, {"nif_command", 1, nif_command, 0}, // Shall we let this be dirty? diff --git a/erts/emulator/nifs/common/socket_nif.c b/erts/emulator/nifs/common/socket_nif.c index f7e59678bb..68a9730d0c 100644 --- a/erts/emulator/nifs/common/socket_nif.c +++ b/erts/emulator/nifs/common/socket_nif.c @@ -848,9 +848,6 @@ typedef struct { -static ERL_NIF_TERM nif_is_loaded(ErlNifEnv* env, - int argc, - const ERL_NIF_TERM argv[]); static ERL_NIF_TERM nif_info(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); @@ -1973,7 +1970,18 @@ extern char* encode_msghdr_flags(ErlNifEnv* env, SocketDescriptor* descP, int msgFlags, ERL_NIF_TERM* flags); - +static char* decode_cmsghdr_data(ErlNifEnv* env, + void** bufP, + size_t* rem, + int level, + int type, + ERL_NIF_TERM eData); +static char* decode_cmsghdr_final(void** bufP, + size_t* rem, + int level, + int type, + char* data, + int sz); static BOOLEAN_T decode_sock_linger(ErlNifEnv* env, ERL_NIF_TERM eVal, struct linger* valP); @@ -2380,7 +2388,6 @@ static SocketData data; * * Utility and admin functions: * ---------------------------- - * nif_is_loaded/0 * nif_info/0 * (nif_debug/1) * @@ -2416,27 +2423,6 @@ static SocketData data; */ -/* ---------------------------------------------------------------------- - * nif_is_loaded - * - * Description: - * This functions only purpose is to return the atom 'true'. - * This will happen *if* the (socket) nif library is loaded. - * If its not, the erlang (nif_is_loaded) will instead return - * 'false'. - */ -static -ERL_NIF_TERM nif_is_loaded(ErlNifEnv* env, - int argc, - const ERL_NIF_TERM argv[]) -{ - if (argc != 0) - return enif_make_badarg(env); - - return atom_true; -} - - /* ---------------------------------------------------------------------- * nif_info * @@ -11046,7 +11032,7 @@ char* encode_msghdr(ErlNifEnv* env, * * The cmsgHdrP arguments points to the start of the control data buffer, * an actual binary. Its the only way to create sub-binaries. So, what we - * need to continue processing this is to tern that into an binary erlang + * need to continue processing this is to turn that into an binary erlang * term (which can then in turn be turned into sub-binaries). * * We need the cmsgBufP (even though cmsgHdrP points to it) to be able @@ -11171,7 +11157,7 @@ char* encode_cmsghdrs(ErlNifEnv* env, * * Decode a list of cmsghdr(). There can be 0 or more cmsghdr "blocks". * - * Each element can either be a (erlang) map that needds to be decoded, + * Each element can either be a (erlang) map that needs to be decoded, * or a (erlang) binary that just needs to be appended to the control * buffer. * @@ -11267,46 +11253,121 @@ char* decode_cmsghdr(ErlNifEnv* env, /* And finally data * If its a binary, we are done. Otherwise, we need to check * level and type to know what kind of data to expect. - * - * - * - * At the moment, the only data we support is a binary... - * - * */ + + return decode_cmsghdr_data(env, bufP, rem, level, type, eData); + + } else { + return ESOCK_STR_EINVAL; + } + + return NULL; +} + + +/* *** decode_cmsghdr_data *** + * + * For all combinations of level and type we accept a binary as data, + * so we begin by testing for that. If its not a binary, then we check + * level (ip) and type (tos or ttl), in which case the data *must* be + * an integer (we have already taken care of the binary). + */ +static +char* decode_cmsghdr_data(ErlNifEnv* env, + void** bufP, + size_t* rem, + int level, + int type, + ERL_NIF_TERM eData) +{ + char* xres = ESOCK_STR_EINVAL; + + if (IS_BIN(env, eData)) { + ErlNifBinary bin; - if (IS_BIN(env, eData)) { - ErlNifBinary bin; - size_t currentRem = *rem; + if (GET_BIN(env, eData, &bin)) { + return decode_cmsghdr_final(bufP, rem, level, type, + (char*) bin.data, bin.size); + } + } else { - if (!GET_BIN(env, eData, &bin)) { - return ESOCK_STR_EINVAL; - } else { - int len = CMSG_LEN(bin.size); // The cmsghdr - int space = CMSG_SPACE(bin.size); // With padding - /* Make sure it fits before we copy */ - if (currentRem >= space) { - struct cmsghdr* cmsgP = (struct cmsghdr*) bufP; - - /* The header */ - cmsgP->cmsg_len = len; - cmsgP->cmsg_level = level; - cmsgP->cmsg_type = type; - - /* And the data */ - sys_memcpy(CMSG_DATA(cmsgP), bin.data, bin.size); - *bufP += space; - *rem -= space; - } else { - return ESOCK_STR_EINVAL; + /* Its *not* a binary so we need to look at what level and type + * we have and treat them individually. + */ + + switch (level) { +#if defined(SOL_IP) + case SOL_IP: +#else + case IPPROTO_IP: +#endif + switch (type) { +#if defined(IP_TOS) + case IP_TOS: + { + int data; + if (decode_ip_tos(env, eData, &data)) { + return decode_cmsghdr_final(bufP, rem, level, type, + (char*) &data, + sizeof(data)); + } + } + break; +#endif + +#if defined(IP_TTL) + case IP_TTL: + { + int data; + if (GET_INT(env, eData, &data)) { + return decode_cmsghdr_final(bufP, rem, level, type, + (char*) &data, + sizeof(data)); + } } + break; +#endif + } - } else { + break; + + default: + break; + } - /* Here is where we should have the proper data decode */ + } - return ESOCK_STR_EINVAL; - } + return xres; +} + + +/* *** decode_cmsghdr_final *** + * + * This does the final create of the cmsghdr (including the data copy). + */ +static +char* decode_cmsghdr_final(void** bufP, + size_t* rem, + int level, + int type, + char* data, + int sz) +{ + int currentRem = *rem; + int len = CMSG_LEN(sz); + int space = CMSG_SPACE(sz); + + if (currentRem >= space) { + struct cmsghdr* cmsgP = (struct cmsghdr*) bufP; + + /* The header */ + cmsgP->cmsg_len = len; + cmsgP->cmsg_level = level; + cmsgP->cmsg_type = type; + + sys_memcpy(CMSG_DATA(cmsgP), &data, sz); + *bufP += space; + *rem -= space; } else { return ESOCK_STR_EINVAL; } @@ -11334,8 +11395,30 @@ char* encode_cmsghdr_level(ErlNifEnv* env, xres = NULL; break; +#if defined(SOL_IP) + case SOL_IP: +#else + case IPPROTO_IP: +#endif + *eLevel = esock_atom_ip; + xres = NULL; + break; + +#if defined(SOL_IPV6) + case SOL_IPV6: + *eLevel = esock_atom_ip; + xres = NULL; + break; +#endif + + case IPPROTO_UDP: + *eLevel = esock_atom_udp; + xres = NULL; + break; + default: - xres = esock_encode_protocol(env, level, eLevel); + *eLevel = MKI(env, level); + xres = NULL; break; } @@ -11358,17 +11441,35 @@ char* decode_cmsghdr_level(ErlNifEnv* env, char* xres = NULL; if (IS_ATOM(env, eLevel)) { + if (COMPARE(eLevel, esock_atom_socket) == 0) { *level = SOL_SOCKET; xres = NULL; + } else if (COMPARE(eLevel, esock_atom_ip) == 0) { +#if defined(SOL_IP) + *level = SOL_IP; +#else + *level = IPPROTO_IP; +#endif + xres = NULL; +#if defined(SOL_IPV6) + } else if (COMPARE(eLevel, esock_atom_ipv6) == 0) { + *level = SOL_IPV6; + xres = NULL; +#endif + } else if (COMPARE(eLevel, esock_atom_udp) == 0) { + *level = IPPROTO_UDP; + xres = NULL; } else { - xres = esock_decode_protocol(env, eLevel, level); + *level = -1; + xres = ESOCK_STR_EINVAL; } } else if (IS_NUM(env, eLevel)) { if (!GET_INT(env, eLevel, level)) xres = ESOCK_STR_EINVAL; } else { - xres = ESOCK_STR_EINVAL; + *level = -1; + xres = ESOCK_STR_EINVAL; } return xres; @@ -11521,25 +11622,13 @@ char* decode_cmsghdr_type(ErlNifEnv* env, switch (level) { case SOL_SOCKET: - if (COMPARE(eType, esock_atom_timestamp) == 0) { -#if defined(SO_TIMESTAMP) - *type = SO_TIMESTAMP; -#else - xres = ESOCK_STR_EINVAL; -#endif - } else if (COMPARE(eType, esock_atom_rights) == 0) { -#if defined(SCM_RIGHTS) - *type = SCM_RIGHTS; -#else - xres = ESOCK_STR_EINVAL; -#endif - } else if (COMPARE(eType, esock_atom_credentials) == 0) { -#if defined(SCM_CREDENTIALS) - *type = SCM_CREDENTIALS; -#else - xres = ESOCK_STR_EINVAL; -#endif + if (IS_NUM(env, eType)) { + if (!GET_INT(env, eType, type)) { + *type = -1; + xres = ESOCK_STR_EINVAL; + } } else { + *type = -1; xres = ESOCK_STR_EINVAL; } break; @@ -11550,60 +11639,62 @@ char* decode_cmsghdr_type(ErlNifEnv* env, #else case IPPROTO_IP: #endif - if (COMPARE(eType, esock_atom_tos) == 0) { + if (IS_ATOM(env, eType)) { + if (COMPARE(eType, esock_atom_tos) == 0) { #if defined(IP_TOS) - *type = IP_TOS; + *type = IP_TOS; #else - xres = ESOCK_STR_EINVAL; + xres = ESOCK_STR_EINVAL; #endif - } else if (COMPARE(eType, esock_atom_ttl) == 0) { + } else if (COMPARE(eType, esock_atom_ttl) == 0) { #if defined(IP_TTL) - *type = IP_TTL; + *type = IP_TTL; #else - xres = ESOCK_STR_EINVAL; -#endif - } else if (COMPARE(eType, esock_atom_pktinfo) == 0) { -#if defined(IP_PKTINFO) - *type = IP_PKTINFO; -#else - xres = ESOCK_STR_EINVAL; -#endif - } else if (COMPARE(eType, esock_atom_origdstaddr) == 0) { -#if defined(IP_ORIGDSTADDR) - *type = IP_ORIGDSTADDR; -#else - xres = ESOCK_STR_EINVAL; + xres = ESOCK_STR_EINVAL; #endif + } else { + xres = ESOCK_STR_EINVAL; + } + } else if (IS_NUM(env, eType)) { + if (!GET_INT(env, eType, type)) { + *type = -1; + xres = ESOCK_STR_EINVAL; + } } else { - xres = ESOCK_STR_EINVAL; + *type = -1; + xres = ESOCK_STR_EINVAL; } break; #if defined(SOL_IPV6) case SOL_IPV6: - xres = ESOCK_STR_EINVAL; + if (IS_NUM(env, eType)) { + if (!GET_INT(env, eType, type)) { + *type = -1; + xres = ESOCK_STR_EINVAL; + } + } else { + *type = -1; + xres = ESOCK_STR_EINVAL; + } break; #endif - case IPPROTO_TCP: - xres = ESOCK_STR_EINVAL; - break; - break; - case IPPROTO_UDP: - xres = ESOCK_STR_EINVAL; - break; - break; - -#if defined(HAVE_SCTP) - case IPPROTO_SCTP: - xres = ESOCK_STR_EINVAL; - break; + if (IS_NUM(env, eType)) { + if (!GET_INT(env, eType, type)) { + *type = -1; + xres = ESOCK_STR_EINVAL; + } + } else { + *type = -1; + xres = ESOCK_STR_EINVAL; + } break; -#endif default: - xres = ESOCK_STR_EINVAL; + *type = -1; + xres = ESOCK_STR_EINVAL; break; } @@ -13481,7 +13572,6 @@ static ErlNifFunc socket_funcs[] = { // Some utility functions - {"nif_is_loaded", 0, nif_is_loaded, 0}, {"nif_info", 0, nif_info, 0}, // {"nif_debug", 1, nif_debug_, 0}, diff --git a/erts/preloaded/ebin/net.beam b/erts/preloaded/ebin/net.beam index b52fa01b07..e7a393391a 100644 Binary files a/erts/preloaded/ebin/net.beam and b/erts/preloaded/ebin/net.beam differ diff --git a/erts/preloaded/ebin/socket.beam b/erts/preloaded/ebin/socket.beam index 5b14af3ad8..b3f3e385d2 100644 Binary files a/erts/preloaded/ebin/socket.beam and b/erts/preloaded/ebin/socket.beam differ diff --git a/erts/preloaded/src/Makefile b/erts/preloaded/src/Makefile index fae60a4491..2a80837719 100644 --- a/erts/preloaded/src/Makefile +++ b/erts/preloaded/src/Makefile @@ -78,6 +78,10 @@ STDLIB_INCLUDE=$(ERL_TOP)/lib/stdlib/include ERL_COMPILE_FLAGS += +debug_info -I$(KERNEL_SRC) -I$(KERNEL_INCLUDE) +DIA_PLT = erts-preloaded.plt +DIA_ANALYSIS = $(basename $(DIA_PLT)).dialyzer_analysis + + debug opt: $(TARGET_FILES) clean: @@ -89,7 +93,6 @@ copy: $(APP_TARGET): $(APP_SRC) $(ERL_TOP)/erts/vsn.mk $(vsn_verbose)sed -e 's;%VSN%;$(VSN);' $< > $@ - include $(ERL_TOP)/make/otp_release_targets.mk release_spec: $(APP_TARGET) @@ -104,6 +107,30 @@ release_docs_spec: list_preloaded: @echo $(PRE_LOADED_MODULES) +dclean: + rm -f $(DIA_PLT) + rm -f $(DIA_ANALYSIS) + +dialyzer_plt: $(DIA_PLT) + +$(DIA_PLT): $(ERL_FILES) + @echo "Building ($(basename $(DIA_PLT))) plt file" + @dialyzer --build_plt \ + --output_plt $@ \ + -r ../ebin \ + ../../../lib/kernel/ebin \ + ../../../lib/stdlib/ebin \ + ../../../lib/crypto/ebin \ + ../../../lib/compiler/ebin \ + --output $(DIA_ANALYSIS) \ + --verbose + +dialyzer: $(DIA_PLT) + @echo "Running dialyzer on $(basename $(DIA_PLT))" + @dialyzer --plt $< \ + ../ebin \ + --verbose + # # Combine a BEAM assembly script file a stub Erlang file into a BEAM file. # See add_abstract_chunk script. diff --git a/erts/preloaded/src/net.erl b/erts/preloaded/src/net.erl index 01b12696e9..0183a69ddb 100644 --- a/erts/preloaded/src/net.erl +++ b/erts/preloaded/src/net.erl @@ -20,9 +20,11 @@ -module(net). +-compile(no_native). + %% Administrative and "global" utility functions -export([ - on_load/0, on_load/1, on_load/2, + on_load/0, on_load/1, info/0, command/1 ]). @@ -105,23 +107,10 @@ on_load() -> on_load(#{}). -spec on_load(Extra) -> ok when - Extra :: maps:map(). - -on_load(Extra) when is_map(Extra) -> - on_load(atom_to_list(?MODULE), Extra). - --spec on_load(Path, Extra) -> ok when - Path :: string(), - Extra :: maps:map(). - -on_load(Path, Extra) when is_list(Path) andalso is_map(Extra) -> - on_load(nif_is_loaded(), Path, Extra). - -on_load(true, _Path, _Extra) -> - ok; -on_load(false, Path, Extra) -> - ok = erlang:load_nif(Path, Extra). + Extra :: map(). +on_load(Extra) -> + ok = erlang:load_nif(atom_to_list(?MODULE), Extra). -spec info() -> list(). @@ -315,29 +304,26 @@ if_names() -> %% %% =========================================================================== -nif_is_loaded() -> - false. - nif_info() -> - erlang:error(badarg). + erlang:nif_error(undef). nif_command(_Cmd) -> - erlang:error(badarg). + erlang:nif_error(undef). nif_gethostname() -> - erlang:error(badarg). + erlang:nif_error(undef). nif_getnameinfo(_Addr, _Flags) -> - erlang:error(badarg). + erlang:nif_error(undef). nif_getaddrinfo(_Host, _Service, _Hints) -> - erlang:error(badarg). + erlang:nif_error(undef). nif_if_name2index(_Name) -> - erlang:error(badarg). + erlang:nif_error(undef). nif_if_index2name(_Id) -> - erlang:error(badarg). + erlang:nif_error(undef). nif_if_names() -> - erlang:error(badarg). + erlang:nif_error(undef). diff --git a/erts/preloaded/src/socket.erl b/erts/preloaded/src/socket.erl index 8af052e149..d1053d88f9 100644 --- a/erts/preloaded/src/socket.erl +++ b/erts/preloaded/src/socket.erl @@ -20,11 +20,12 @@ -module(socket). +-compile(no_native). -compile({no_auto_import,[error/1]}). %% Administrative and "global" utility functions -export([ - on_load/0, on_load/1, on_load/2, + on_load/0, on_load/1, info/0, ensure_sockaddr/1 ]). @@ -112,8 +113,8 @@ msghdr/0, cmsghdr_level/0, cmsghdr_type/0, - cmsghdr_data/0, - cmsghdr/0, + %% cmsghdr_data/0, + cmsghdr_recv/0, cmsghdr_send/0, uint8/0, uint16/0, @@ -311,7 +312,7 @@ %% Int - Raw level, sent down and used "as is". -type sockopt_level() :: otp | socket | - ip | ipv6 | tcp | udp | sctp | raw | + ip | ipv6 | tcp | udp | sctp | non_neg_integer(). %% There are some options that are 'read-only'. @@ -546,7 +547,7 @@ %% The maximum size of the control buffer is platform %% specific. It is the users responsibility to ensure %% that its not exceeded. - ctrl := [cmsghdr()], + ctrl := [cmsghdr_recv()] | [cmsghdr_send()], %% Only valid with recvmsg flags := msghdr_flags() @@ -555,7 +556,7 @@ %% Even if we are able to decode both level and type, we may not be %% able to decode the data, in which case it will be a binary. --type cmsghdr_level() :: socket | protocol() | integer(). +-type cmsghdr_level() :: socket | ip | ipv6 | integer(). -type cmsghdr_type() :: timestamp | pktinfo | tos | @@ -564,18 +565,44 @@ credentials | origdstaddr | integer(). --type cmsghdr_data() :: timeval() | % if level = socket and type = timstamp - ip_pktinfo() | % if level = ip and type = pktinfo - ipv6_pktinfo() | % if level = ipv6 and type = pktinfo - ip_tos() | % if level = ip and type = tos - integer() | % if level = ip and type = ttl - sockaddr_in4() | % if level = ip and type = origdstaddr - binary(). --type cmsghdr() :: #{ - level := cmsghdr_level(), - type := cmsghdr_type(), - data := cmsghdr_data() - }. +%% Do we need this? See cmsghdr() +%% -type cmsghdr_data() :: timeval() | % if level = socket and type = timstamp +%% ip_pktinfo() | % if level = ip and type = pktinfo +%% ipv6_pktinfo() | % if level = ipv6 and type = pktinfo +%% ip_tos() | % if level = ip and type = tos +%% integer() | % if level = ip and type = ttl +%% sockaddr_in4() | % if level = ip and type = origdstaddr +%% binary(). +%% -type cmsghdr() :: #{ +%% level := cmsghdr_level(), +%% type := cmsghdr_type(), +%% data := cmsghdr_data() +%% }. + +-type cmsghdr_recv() :: + #{level := socket, type := timestamp, data := timeval()} | + #{level := socket, type := rights, data := binary()} | + #{level := socket, type := credentials, data := binary()} | + #{level := socket, type := integer(), data := binary()} | + #{level := ip, type := tos, data := ip_tos()} | + #{level := ip, type := ttl, data := integer()} | + #{level := ip, type := pktinfo, data := ip_pktinfo()} | + #{level := ip, type := origdstaddr, data := sockaddr_in4()} | + #{level := ip, type := integer(), data := binary()} | + #{level := ipv6, type := pktinfo, data := ipv6_pktinfo()} | + #{level := ipv6, type := integer(), data := binary()} | + #{level := integer(), type := integer(), data := binary()}. + + +-type cmsghdr_send() :: + #{level := socket, type := integer(), data := binary()} | + #{level := ip, type := tos, data := ip_tos() | binary()} | + #{level := ip, type := ttl, data := integer() | binary()} | + #{level := ip, type := integer(), data := binary()} | + #{level := ipv6, type := integer(), data := binary()} | + #{level := udp, type := integer(), data := binary()} | + #{level := integer(), type := integer(), data := binary()}. + -define(SOCKET_DOMAIN_LOCAL, 1). -define(SOCKET_DOMAIN_UNIX, ?SOCKET_DOMAIN_LOCAL). @@ -630,10 +657,12 @@ -define(SOCKET_OPT_LEVEL_UDP, 5). -define(SOCKET_OPT_LEVEL_SCTP, 6). +%% *** OTP (socket) options -define(SOCKET_OPT_OTP_DEBUG, 1). -define(SOCKET_OPT_OTP_IOW, 2). -define(SOCKET_OPT_OTP_CTRL_PROC, 3). +%% *** SOCKET (socket) options -define(SOCKET_OPT_SOCK_ACCEPTCONN, 1). %% -define(SOCKET_OPT_SOCK_ACCEPTFILTER, 2). % FreeBSD -define(SOCKET_OPT_SOCK_BINDTODEVICE, 3). @@ -667,6 +696,7 @@ -define(SOCKET_OPT_SOCK_TIMESTAMP, 31). -define(SOCKET_OPT_SOCK_TYPE, 32). +%% *** IP (socket) options -define(SOCKET_OPT_IP_ADD_MEMBERSHIP, 1). -define(SOCKET_OPT_IP_ADD_SOURCE_MEMBERSHIP, 2). -define(SOCKET_OPT_IP_BLOCK_SOURCE, 3). @@ -701,6 +731,7 @@ -define(SOCKET_OPT_IP_TTL, 32). -define(SOCKET_OPT_IP_UNBLOCK_SOURCE, 33). +%% *** IPv6 (socket) options -define(SOCKET_OPT_IPV6_ADDRFORM, 1). -define(SOCKET_OPT_IPV6_ADD_MEMBERSHIP, 2). -define(SOCKET_OPT_IPV6_AUTHHDR, 3). % Obsolete? @@ -734,6 +765,7 @@ %% -define(SOCKET_OPT_IPV6_USE_MIN_MTU, 31). % FreeBSD -define(SOCKET_OPT_IPV6_V6ONLY, 32). +%% *** TCP (socket) options -define(SOCKET_OPT_TCP_CONGESTION, 1). -define(SOCKET_OPT_TCP_CORK, 2). %% -define(SOCKET_OPT_TCP_INFO, 3). @@ -748,8 +780,10 @@ %% -define(SOCKET_OPT_TCP_SYNCNT, 12). %% -define(SOCKET_OPT_TCP_USER_TIMEOUT, 13). +%% *** UDP (socket) options -define(SOCKET_OPT_UDP_CORK, 1). +%% *** SCTP (socket) options %% -define(SOCKET_OPT_SCTP_ADAPTION_LAYER, 1). -define(SOCKET_OPT_SCTP_ASSOCINFO, 2). %% -define(SOCKET_OPT_SCTP_AUTH_ACTIVE_KEY, 3). @@ -802,23 +836,10 @@ on_load() -> on_load(#{}). -spec on_load(Extra) -> ok when - Extra :: maps:map(). - -on_load(Extra) when is_map(Extra) -> - on_load(atom_to_list(?MODULE), Extra). - --spec on_load(Path, Extra) -> ok when - Path :: string(), - Extra :: maps:map(). + Extra :: map(). -on_load(Path, Extra) when is_list(Path) andalso is_map(Extra) -> - on_load(nif_is_loaded(), Path, Extra). - -on_load(true, _Path, _Extra) -> - ok; -on_load(false, Path, Extra) -> - %% ok = erlang:load_nif(Path, maps:put(timestamp, formated_timestamp(), Extra)). - ok = erlang:load_nif(Path, Extra). +on_load(Extra) -> + ok = erlang:load_nif(atom_to_list(?MODULE), Extra). @@ -1022,8 +1043,8 @@ bind(#socket{ref = SockRef}, Addrs, Action) when is_list(Addrs) andalso ((Action =:= add) orelse (Action =:= remove)) -> try begin - ensure_type(seqpacket, which_type(SockRef)), - ensure_proto(sctp, which_protocol(SockRef)), + ensure_type(SockRef, seqpacket), + ensure_proto(SockRef, sctp), validate_addrs(which_domain(SockRef), Addrs), nif_bind(SockRef, Addrs, Action) end @@ -1508,38 +1529,6 @@ ensure_msghdr(_) -> einval(). - -%% send(Sock, #{ctrl = Ctrl} = MsgHdr, Flags) when is_list(Ctrl) -> -%% case encode_cmsghdrs(Ctrl) of -%% undefined -> -%% send(Sock, maps:remove(ctrl, MsgHdr), Flags); -%% Ctrl2 -> -%% send(Sock, MsgHdr#{ctrl = Ctrl2}, Flags) -%% end. - -%% encode_cmsghdrs([]) -> -%% undefined; -%% encode_cmsghdrs(Hdrs) -> -%% encode_cmsghdrs(Hdrs, []). - -%% encode_cmsghdrs([], Acc) -> -%% list_to_binary(lists:reverse(Acc)); -%% encode_cmsghdrs([H|T], Acc) when is_binary(H) -> -%% encode_cmsghdrs(T, [H|Acc]); -%% encode_cmsghdrs([#{level := Level, -%% type := Type, -%% data := Data} | T], Acc) -> -%% case nif_encode_cmsghdr(Level, Type, Data) of -%% {ok, Bin} when is_binary(Bin) -> -%% encode_cmsghdrs(T, [Bin | Acc]); -%% {error, _} = ERROR -> -%% ERROR -%% end. - - - - - %% =========================================================================== %% %% writev - write data into multiple buffers @@ -1924,7 +1913,8 @@ do_recvfrom(SockRef, BufSz, EFlags, Timeout) -> Reason :: term(). recvmsg(Socket) -> - recvmsg(Socket, 0, 0, ?SOCKET_RECV_FLAGS_DEFAULT, ?SOCKET_RECV_TIMEOUT_DEFAULT). + recvmsg(Socket, 0, 0, + ?SOCKET_RECV_FLAGS_DEFAULT, ?SOCKET_RECV_TIMEOUT_DEFAULT). -spec recvmsg(Socket, Flags) -> {ok, MsgHdr} | {error, Reason} when Socket :: socket(), @@ -2118,6 +2108,12 @@ shutdown(#socket{ref = SockRef}, How) -> ; (Socket, sctp, sctp_socket_option(), Value) -> ok | {error, Reason} when Socket :: socket(), Value :: term(), + Reason :: term() + ; (Socket, Level, Key, Value) -> ok | {error, Reason} when + Socket :: socket(), + Level :: non_neg_integer(), + Key :: non_neg_integer(), + Value :: binary(), Reason :: term(). setopt(#socket{ref = SockRef}, Level, Key, Value) -> @@ -2223,6 +2219,11 @@ getopt(#socket{ref = SockRef}, Level, Key) -> %% These are internal "shortcut" functions for the options %% domain, type and protocol. + +-spec which_domain(SockRef) -> Domain when + SockRef :: reference(), + Domain :: domain(). + which_domain(SockRef) -> case nif_getopt(SockRef, true, ?SOCKET_OPT_LEVEL_SOCKET, ?SOCKET_OPT_SOCK_DOMAIN) of @@ -2233,6 +2234,10 @@ which_domain(SockRef) -> end. +-spec which_type(SockRef) -> Type when + SockRef :: reference(), + Type :: type(). + which_type(SockRef) -> case nif_getopt(SockRef, true, ?SOCKET_OPT_LEVEL_SOCKET, ?SOCKET_OPT_SOCK_TYPE) of @@ -2242,6 +2247,10 @@ which_type(SockRef) -> throw(ERROR) end. +-spec which_protocol(SockRef) -> Protocol when + SockRef :: reference(), + Protocol :: protocol(). + which_protocol(SockRef) -> case nif_getopt(SockRef, true, ?SOCKET_OPT_LEVEL_SOCKET, ?SOCKET_OPT_SOCK_PROTOCOL) of @@ -2409,6 +2418,57 @@ enc_setopt_key(Level, Opt, Domain, Type, Protocol) -> %% encode the value into an more "manageable" type. %% It also handles "aliases" (see linger). +-spec enc_setopt_value(otp, otp_socket_option(), + Value, Domain, Type, Protocol) -> term() when + Value :: term(), + Domain :: domain(), + Type :: type(), + Protocol :: protocol() + ; (socket, socket_option(), + Value, Domain, Type, Protocol) -> term() when + Value :: term(), + Domain :: domain(), + Type :: type(), + Protocol :: protocol() + ; (ip, ip_socket_option(), + Value, Domain, Type, Protocol) -> term() when + Value :: term(), + Domain :: domain(), + Type :: type(), + Protocol :: protocol() + ; (ipv6, ipv6_socket_option(), + Value, Domain, Type, Protocol) -> term() when + Value :: term(), + Domain :: domain(), + Type :: type(), + Protocol :: protocol() + ; (tcp, tcp_socket_option(), + Value, Domain, Type, Protocol) -> term() when + Value :: term(), + Domain :: domain(), + Type :: type(), + Protocol :: protocol() + ; (udp, udp_socket_option(), + Value, Domain, Type, Protocol) -> term() when + Value :: term(), + Domain :: domain(), + Type :: type(), + Protocol :: protocol() + ; (sctp, sctp_socket_option(), + Value, Domain, Type, Protocol) -> term() when + Value :: term(), + Domain :: domain(), + Type :: type(), + Protocol :: protocol() + ; (Level, Opt, + Value, Domain, Type, Protocol) -> term() when + Level :: integer(), + Opt :: integer(), + Value :: binary(), + Domain :: domain(), + Type :: type(), + Protocol :: protocol(). + enc_setopt_value(otp, debug, V, _, _, _) when is_boolean(V) -> V; enc_setopt_value(otp, iow, V, _, _, _) when is_boolean(V) -> @@ -2762,8 +2822,8 @@ enc_setopt_value(sctp, rtoinfo, #{assoc_id := AssocId, enc_setopt_value(sctp = L, Opt, V, _D, _T, _P) -> not_supported({L, Opt, V}); -enc_setopt_value(raw = L, Opt, _V, _D, _T, _P) -> - not_supported({L, Opt}); +%% enc_setopt_value(raw = L, Opt, _V, _D, _T, _P) -> +%% not_supported({L, Opt}); %% Is this correct? What about getopt? enc_setopt_value(L, Opt, V, _, _, _) @@ -2788,10 +2848,10 @@ enc_getopt_key(Level, Opt, Domain, Type, Protocol) -> %% +++ Decode getopt value +++ %% %% For the most part, we simply let the value pass through, but for some -%% values we do an actual decode. +%% values we may need to do an actual decode. %% -%% Let the user deal with this... +%% Let the user deal with this for now... dec_getopt_value(_L, _Opt, V, _D, _T, _P) -> V. @@ -3361,15 +3421,26 @@ tdiff(T1, T2) -> %% %% =========================================================================== +-spec not_supported(What) -> no_return() when + What :: term(). + not_supported(What) -> error({not_supported, What}). +-spec unknown(What) -> no_return() when + What :: term(). + unknown(What) -> error({unknown, What}). +-spec einval() -> no_return(). + einval() -> error(einval). +-spec error(Reason) -> no_return() when + Reason :: term(). + error(Reason) -> throw({error, Reason}). @@ -3380,71 +3451,69 @@ error(Reason) -> %% %% =========================================================================== -nif_is_loaded() -> false. - nif_info() -> - erlang:error(badarg). + erlang:nif_error(undef). nif_open(_Domain, _Type, _Protocol, _Extra) -> - erlang:error(badarg). + erlang:nif_error(undef). nif_bind(_SRef, _SockAddr) -> - erlang:error(badarg). + erlang:nif_error(undef). nif_bind(_SRef, _SockAddrs, _Action) -> - erlang:error(badarg). + erlang:nif_error(undef). nif_connect(_SRef, _SockAddr) -> - erlang:error(badarg). + erlang:nif_error(undef). nif_finalize_connection(_SRef) -> - erlang:error(badarg). + erlang:nif_error(undef). nif_listen(_SRef, _Backlog) -> - erlang:error(badarg). + erlang:nif_error(undef). nif_accept(_SRef, _Ref) -> - erlang:error(badarg). + erlang:nif_error(undef). nif_send(_SockRef, _SendRef, _Data, _Flags) -> - erlang:error(badarg). + erlang:nif_error(undef). nif_sendto(_SRef, _SendRef, _Data, _Dest, _Flags) -> - erlang:error(badarg). + erlang:nif_error(undef). nif_sendmsg(_SRef, _SendRef, _MsgHdr, _Flags) -> - erlang:error(badarg). + erlang:nif_error(undef). nif_recv(_SRef, _RecvRef, _Length, _Flags) -> - erlang:error(badarg). + erlang:nif_error(undef). nif_recvfrom(_SRef, _RecvRef, _Length, _Flags) -> - erlang:error(badarg). + erlang:nif_error(undef). nif_recvmsg(_SRef, _RecvRef, _BufSz, _CtrlSz, _Flags) -> - erlang:error(badarg). + erlang:nif_error(undef). nif_cancel(_SRef, _Op, _Ref) -> - erlang:error(badarg). + erlang:nif_error(undef). nif_close(_SRef) -> - erlang:error(badarg). + erlang:nif_error(undef). nif_shutdown(_SRef, _How) -> - erlang:error(badarg). + erlang:nif_error(undef). nif_finalize_close(_SRef) -> - erlang:error(badarg). + erlang:nif_error(undef). nif_setopt(_Ref, _IsEnc, _Lev, _Key, _Val) -> - erlang:error(badarg). + erlang:nif_error(undef). nif_getopt(_Ref, _IsEnc, _Lev, _Key) -> - erlang:error(badarg). + erlang:nif_error(undef). nif_sockname(_Ref) -> - erlang:error(badarg). + erlang:nif_error(undef). nif_peername(_Ref) -> - erlang:error(badarg). + erlang:nif_error(undef). -- cgit v1.2.3 From 93ed18d0b5e46c9637ff50052e1d12a66d5d40e1 Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Tue, 18 Sep 2018 12:38:08 +0200 Subject: [socket-nif] Encoding of cmsg headers for sendmsg Fixed various issues with regard to encode of CMsgHdrs during sendmsg. OTP-14831 --- erts/emulator/nifs/common/socket_nif.c | 222 +++++++++++++++++++++++--------- erts/emulator/nifs/common/socket_util.h | 11 +- 2 files changed, 167 insertions(+), 66 deletions(-) diff --git a/erts/emulator/nifs/common/socket_nif.c b/erts/emulator/nifs/common/socket_nif.c index 68a9730d0c..5fca0eb58b 100644 --- a/erts/emulator/nifs/common/socket_nif.c +++ b/erts/emulator/nifs/common/socket_nif.c @@ -1914,13 +1914,15 @@ extern char* encode_cmsghdrs(ErlNifEnv* env, extern char* decode_cmsghdrs(ErlNifEnv* env, SocketDescriptor* descP, ERL_NIF_TERM eCMsgHdr, - void* cmsgHdrBufP, - size_t cmsgHdrBufLen); + char* cmsgHdrBufP, + size_t cmsgHdrBufLen, + size_t* cmsgHdrBufUsed); extern char* decode_cmsghdr(ErlNifEnv* env, SocketDescriptor* descP, ERL_NIF_TERM eCMsgHdr, - void** bufP, - size_t* rem); + char* bufP, + size_t rem, + size_t* used); static char* encode_cmsghdr_level(ErlNifEnv* env, int level, ERL_NIF_TERM* eLevel); @@ -1970,18 +1972,22 @@ extern char* encode_msghdr_flags(ErlNifEnv* env, SocketDescriptor* descP, int msgFlags, ERL_NIF_TERM* flags); -static char* decode_cmsghdr_data(ErlNifEnv* env, - void** bufP, - size_t* rem, - int level, - int type, - ERL_NIF_TERM eData); -static char* decode_cmsghdr_final(void** bufP, - size_t* rem, - int level, - int type, - char* data, - int sz); +static char* decode_cmsghdr_data(ErlNifEnv* env, + SocketDescriptor* descP, + char* bufP, + size_t rem, + int level, + int type, + ERL_NIF_TERM eData, + size_t* used); +static char* decode_cmsghdr_final(SocketDescriptor* descP, + char* bufP, + size_t rem, + int level, + int type, + char* data, + int sz, + size_t* used); static BOOLEAN_T decode_sock_linger(ErlNifEnv* env, ERL_NIF_TERM eVal, struct linger* valP); @@ -3787,8 +3793,8 @@ ERL_NIF_TERM nsendmsg(ErlNifEnv* env, ErlNifBinary* iovBins; struct iovec* iov; unsigned int iovLen; - void* ctrlBuf; - size_t ctrlBufLen; + char* ctrlBuf; + size_t ctrlBufLen, ctrlBufUsed; int save_errno; ssize_t written, dataSize; char* xres; @@ -3820,7 +3826,7 @@ ERL_NIF_TERM nsendmsg(ErlNifEnv* env, return esock_make_error(env, esock_atom_einval); SSDBG( descP, ("SOCKET", "nsendmsg -> not connected: " - "\r\n %T" + "\r\n address: %T" "\r\n", eAddr) ); if ((xres = esock_decode_sockaddr(env, eAddr, @@ -3850,15 +3856,18 @@ ERL_NIF_TERM nsendmsg(ErlNifEnv* env, /* The *opional* ctrl */ if (GET_MAP_VAL(env, eMsgHdr, esock_atom_ctrl, &eCtrl)) { ctrlBufLen = descP->wCtrlSz; - ctrlBuf = MALLOC(ctrlBufLen); + ctrlBuf = (char*) MALLOC(ctrlBufLen); ESOCK_ASSERT( (ctrlBuf != NULL) ); } else { eCtrl = esock_atom_undefined; ctrlBufLen = 0; ctrlBuf = NULL; } - - + SSDBG( descP, ("SOCKET", "nsendmsg -> optional ctrl: " + "\r\n ctrlBuf: 0x%lX" + "\r\n ctrlBufLen: %d" + "\r\n eCtrl: %T\r\n", ctrlBuf, ctrlBufLen, eCtrl) ); + /* Decode the iov and initiate that part of the msghdr */ if ((xres = esock_decode_iov(env, eIOV, iovBins, iov, iovLen, &dataSize)) != NULL) { @@ -3875,10 +3884,12 @@ ERL_NIF_TERM nsendmsg(ErlNifEnv* env, "nsendmsg -> total (iov) data size: %d\r\n", dataSize) ); - /* Decode the ctrl and initiate that part of the msghdr */ + /* Decode the ctrl and initiate that part of the msghdr. + */ if (ctrlBuf != NULL) { if ((xres = decode_cmsghdrs(env, descP, - eCtrl, ctrlBuf, ctrlBufLen)) != NULL) { + eCtrl, + ctrlBuf, ctrlBufLen, &ctrlBufUsed)) != NULL) { FREE(iovBins); FREE(iov); if (ctrlBuf != NULL) FREE(ctrlBuf); @@ -3886,7 +3897,7 @@ ERL_NIF_TERM nsendmsg(ErlNifEnv* env, } } msgHdr.msg_control = ctrlBuf; - msgHdr.msg_controllen = ctrlBufLen; + msgHdr.msg_controllen = ctrlBufUsed; /* The msg-flags field is not used when sending, but zero it just in case */ @@ -11169,35 +11180,64 @@ extern char* decode_cmsghdrs(ErlNifEnv* env, SocketDescriptor* descP, ERL_NIF_TERM eCMsgHdr, - void* cmsgHdrBufP, - size_t cmsgHdrBufLen) + char* cmsgHdrBufP, + size_t cmsgHdrBufLen, + size_t* cmsgHdrBufUsed) { ERL_NIF_TERM elem, tail, list; - void* bufP; - size_t rem; + char* bufP; + size_t rem, used, totUsed = 0; unsigned int len; int i; char* xres; + SSDBG( descP, ("SOCKET", "decode_cmsghdrs -> entry with" + "\r\n cmsgHdrBufP: 0x%lX" + "\r\n cmsgHdrBufLen: %d" + "\r\n", cmsgHdrBufP, cmsgHdrBufLen) ); + if (IS_LIST(env, eCMsgHdr) && GET_LIST_LEN(env, eCMsgHdr, &len)) { - for (i = 0, list = eCMsgHdr, rem = cmsgHdrBufLen, bufP = cmsgHdrBufP; + + SSDBG( descP, ("SOCKET", "decode_cmsghdrs -> list length: %d\r\n", len) ); + + for (i = 0, list = eCMsgHdr, rem = cmsgHdrBufLen, bufP = cmsgHdrBufP; i < len; i++) { + SSDBG( descP, ("SOCKET", "decode_cmsghdrs -> process elem %d:" + "\r\n (buffer) rem: %u" + "\r\n (buffer) totUsed: %u" + "\r\n", i, rem, totUsed) ); + /* Extract the (current) head of the (cmsg hdr) list */ if (!GET_LIST_ELEM(env, list, &elem, &tail)) return ESOCK_STR_EINVAL; - if ((xres = decode_cmsghdr(env, descP, elem, &bufP, &rem)) != NULL) + used = 0; // Just in case... + if ((xres = decode_cmsghdr(env, descP, elem, bufP, rem, &used)) != NULL) return xres; - - list = tail; + + bufP = CHARP( ULONG(bufP) + used ); + rem = SZT( rem - used ); + list = tail; + totUsed += used; + } + SSDBG( descP, ("SOCKET", + "decode_cmsghdrs -> all %d ctrl headers processed\r\n", + len) ); + xres = NULL; } else { xres = ESOCK_STR_EINVAL; } + *cmsgHdrBufUsed = totUsed; + + SSDBG( descP, ("SOCKET", "decode_cmsghdrs -> done with %s when" + "\r\n totUsed = %u\r\n", + ((xres != NULL) ? xres : "NULL"), totUsed) ); + return xres; } @@ -11217,15 +11257,20 @@ char* decode_cmsghdrs(ErlNifEnv* env, * data :: cmsghdr_data() (term() | binary()) * The type of the data depends on * level and type, but can be a binary, - * which means that the data already coded. + * which means that the data is already coded. */ extern char* decode_cmsghdr(ErlNifEnv* env, SocketDescriptor* descP, ERL_NIF_TERM eCMsgHdr, - void** bufP, - size_t* rem) + char* bufP, + size_t rem, + size_t* used) { + SSDBG( descP, ("SOCKET", "decode_cmsghdr -> entry with" + "\r\n eCMsgHdr: %T" + "\r\n", eCMsgHdr) ); + if (IS_MAP(env, eCMsgHdr)) { ERL_NIF_TERM eLevel, eType, eData; int level, type; @@ -11236,28 +11281,42 @@ char* decode_cmsghdr(ErlNifEnv* env, if (!GET_MAP_VAL(env, eCMsgHdr, esock_atom_level, &eLevel)) return ESOCK_STR_EINVAL; + SSDBG( descP, ("SOCKET", "decode_cmsghdr -> eLevel: %T" + "\r\n", eLevel) ); + if (!GET_MAP_VAL(env, eCMsgHdr, esock_atom_type, &eType)) return ESOCK_STR_EINVAL; + SSDBG( descP, ("SOCKET", "decode_cmsghdr -> eType: %T" + "\r\n", eType) ); + if (!GET_MAP_VAL(env, eCMsgHdr, esock_atom_data, &eData)) return ESOCK_STR_EINVAL; + SSDBG( descP, ("SOCKET", "decode_cmsghdr -> eData: %T" + "\r\n", eData) ); + /* Second, decode level */ if ((xres = decode_cmsghdr_level(env, eLevel, &level)) != NULL) return xres; + SSDBG( descP, ("SOCKET", "decode_cmsghdr -> level: %d\r\n", level) ); + /* third, decode type */ if ((xres = decode_cmsghdr_type(env, level, eType, &type)) != NULL) return xres; + SSDBG( descP, ("SOCKET", "decode_cmsghdr -> type: %d\r\n", type) ); + /* And finally data * If its a binary, we are done. Otherwise, we need to check * level and type to know what kind of data to expect. */ - return decode_cmsghdr_data(env, bufP, rem, level, type, eData); + return decode_cmsghdr_data(env, descP, bufP, rem, level, type, eData, used); } else { + *used = 0; return ESOCK_STR_EINVAL; } @@ -11270,24 +11329,36 @@ char* decode_cmsghdr(ErlNifEnv* env, * For all combinations of level and type we accept a binary as data, * so we begin by testing for that. If its not a binary, then we check * level (ip) and type (tos or ttl), in which case the data *must* be - * an integer (we have already taken care of the binary). + * an integer and ip_tos() respectively. */ static -char* decode_cmsghdr_data(ErlNifEnv* env, - void** bufP, - size_t* rem, - int level, - int type, - ERL_NIF_TERM eData) +char* decode_cmsghdr_data(ErlNifEnv* env, + SocketDescriptor* descP, + char* bufP, + size_t rem, + int level, + int type, + ERL_NIF_TERM eData, + size_t* used) { - char* xres = ESOCK_STR_EINVAL; + char* xres; + + SSDBG( descP, ("SOCKET", "decode_cmsghdr_data -> entry with" + "\r\n eData: %T" + "\r\n", eData) ); if (IS_BIN(env, eData)) { ErlNifBinary bin; if (GET_BIN(env, eData, &bin)) { - return decode_cmsghdr_final(bufP, rem, level, type, - (char*) bin.data, bin.size); + SSDBG( descP, ("SOCKET", "decode_cmsghdr_data -> " + "do final decode with binary\r\n") ); + return decode_cmsghdr_final(descP, bufP, rem, level, type, + (char*) bin.data, bin.size, + used); + } else { + *used = 0; + xres = ESOCK_STR_EINVAL; } } else { @@ -11307,9 +11378,15 @@ char* decode_cmsghdr_data(ErlNifEnv* env, { int data; if (decode_ip_tos(env, eData, &data)) { - return decode_cmsghdr_final(bufP, rem, level, type, + SSDBG( descP, ("SOCKET", "decode_cmsghdr_data -> " + "do final decode with tos\r\n") ); + return decode_cmsghdr_final(descP, bufP, rem, level, type, (char*) &data, - sizeof(data)); + sizeof(data), + used); + } else { + *used = 0; + xres = ESOCK_STR_EINVAL; } } break; @@ -11320,9 +11397,15 @@ char* decode_cmsghdr_data(ErlNifEnv* env, { int data; if (GET_INT(env, eData, &data)) { - return decode_cmsghdr_final(bufP, rem, level, type, + SSDBG( descP, ("SOCKET", "decode_cmsghdr_data -> " + "do final decode with ttl\r\n") ); + return decode_cmsghdr_final(descP, bufP, rem, level, type, (char*) &data, - sizeof(data)); + sizeof(data), + used); + } else { + *used = 0; + xres = ESOCK_STR_EINVAL; } } break; @@ -11332,6 +11415,8 @@ char* decode_cmsghdr_data(ErlNifEnv* env, break; default: + *used = 0; + xres = ESOCK_STR_EINVAL; break; } @@ -11346,18 +11431,25 @@ char* decode_cmsghdr_data(ErlNifEnv* env, * This does the final create of the cmsghdr (including the data copy). */ static -char* decode_cmsghdr_final(void** bufP, - size_t* rem, - int level, - int type, - char* data, - int sz) +char* decode_cmsghdr_final(SocketDescriptor* descP, + char* bufP, + size_t rem, + int level, + int type, + char* data, + int sz, + size_t* used) { - int currentRem = *rem; - int len = CMSG_LEN(sz); - int space = CMSG_SPACE(sz); + int len = CMSG_LEN(sz); + int space = CMSG_SPACE(sz); - if (currentRem >= space) { + SSDBG( descP, ("SOCKET", "decode_cmsghdr_data -> entry when" + "\r\n level: %d" + "\r\n type: %d" + "\r\n sz: %d => %d, %d" + "\r\n", level, type, sz, len, space) ); + + if (rem >= space) { struct cmsghdr* cmsgP = (struct cmsghdr*) bufP; /* The header */ @@ -11365,13 +11457,15 @@ char* decode_cmsghdr_final(void** bufP, cmsgP->cmsg_level = level; cmsgP->cmsg_type = type; - sys_memcpy(CMSG_DATA(cmsgP), &data, sz); - *bufP += space; - *rem -= space; + sys_memcpy(CMSG_DATA(cmsgP), data, sz); + *used = space; } else { + *used = 0; return ESOCK_STR_EINVAL; } + SSDBG( descP, ("SOCKET", "decode_cmsghdr_final -> done\r\n") ); + return NULL; } diff --git a/erts/emulator/nifs/common/socket_util.h b/erts/emulator/nifs/common/socket_util.h index d0b3076df1..a38453e238 100644 --- a/erts/emulator/nifs/common/socket_util.h +++ b/erts/emulator/nifs/common/socket_util.h @@ -29,8 +29,15 @@ #include #include "socket_int.h" -#define VOIDP(P) ((void*)P) -#define CHARP(P) ((char*)P) +#define CHAR(C) ((char) (C)) +#define UCHAR(C) ((unsigned char) (C)) +#define INT(I) ((int) (I)) +#define UINT(U) ((unsigned int) (U)) +#define LONG(L) ((long) (L)) +#define ULONG(L) ((unsigned long) (L)) +#define SZT(I) ((size_t) (I)) +#define VOIDP(P) ((void*) (P)) +#define CHARP(P) ((char*) (P)) #define ESOCK_ABORT(E) esock_abort(E, __func__, __FILE__, __LINE__) #define ESOCK_ASSERT(e) ((void) ((e) ? 1 : (ESOCK_ABORT(#e), 0))) -- cgit v1.2.3 From d72a3c72dc6e74fb06e4e488db32fc819ce0c088 Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Tue, 18 Sep 2018 12:40:43 +0200 Subject: [socket-nif] Cleanup and guards Some cleanup and also added a guard to sendmsg to ensure that only ctrl (cmsg hdr) with actual content will be "sent down". OTP-14831 --- erts/preloaded/ebin/socket.beam | Bin 66000 -> 66132 bytes erts/preloaded/src/socket.erl | 19 ++----------------- 2 files changed, 2 insertions(+), 17 deletions(-) diff --git a/erts/preloaded/ebin/socket.beam b/erts/preloaded/ebin/socket.beam index b3f3e385d2..1100065e2a 100644 Binary files a/erts/preloaded/ebin/socket.beam and b/erts/preloaded/ebin/socket.beam differ diff --git a/erts/preloaded/src/socket.erl b/erts/preloaded/src/socket.erl index d1053d88f9..ad7a35694b 100644 --- a/erts/preloaded/src/socket.erl +++ b/erts/preloaded/src/socket.erl @@ -565,20 +565,6 @@ credentials | origdstaddr | integer(). -%% Do we need this? See cmsghdr() -%% -type cmsghdr_data() :: timeval() | % if level = socket and type = timstamp -%% ip_pktinfo() | % if level = ip and type = pktinfo -%% ipv6_pktinfo() | % if level = ipv6 and type = pktinfo -%% ip_tos() | % if level = ip and type = tos -%% integer() | % if level = ip and type = ttl -%% sockaddr_in4() | % if level = ip and type = origdstaddr -%% binary(). -%% -type cmsghdr() :: #{ -%% level := cmsghdr_level(), -%% type := cmsghdr_type(), -%% data := cmsghdr_data() -%% }. - -type cmsghdr_recv() :: #{level := socket, type := timestamp, data := timeval()} | #{level := socket, type := rights, data := binary()} | @@ -592,8 +578,6 @@ #{level := ipv6, type := pktinfo, data := ipv6_pktinfo()} | #{level := ipv6, type := integer(), data := binary()} | #{level := integer(), type := integer(), data := binary()}. - - -type cmsghdr_send() :: #{level := socket, type := integer(), data := binary()} | #{level := ip, type := tos, data := ip_tos() | binary()} | @@ -1522,9 +1506,10 @@ do_sendmsg(SockRef, MsgHdr, EFlags, Timeout) -> ERROR end. +ensure_msghdr(#{ctrl := []} = M) -> + ensure_msghdr(maps:remove(ctrl, M)); ensure_msghdr(#{iov := IOV} = M) when is_list(IOV) andalso (IOV =/= []) -> M#{iov := erlang:iolist_to_iovec(IOV)}; - %% M; ensure_msghdr(_) -> einval(). -- cgit v1.2.3 From c14eca1d556926677dc03357c248dc4cf3dc38ed Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Tue, 18 Sep 2018 12:43:02 +0200 Subject: [socket-nif] Added (more) tests for [recv|send]msg Added some more tests for sendmsg (with cmsghdr). OTP-14831 --- lib/kernel/test/socket_client.erl | 44 ++++++++++++++++++++++++++++++++++----- lib/kernel/test/socket_server.erl | 16 ++++++++++---- 2 files changed, 51 insertions(+), 9 deletions(-) diff --git a/lib/kernel/test/socket_client.erl b/lib/kernel/test/socket_client.erl index 56424457e0..58d70b6181 100644 --- a/lib/kernel/test/socket_client.erl +++ b/lib/kernel/test/socket_client.erl @@ -28,7 +28,7 @@ -define(LIB, socket_lib). --record(client, {socket, type, dest, msg_id = 1}). +-record(client, {socket, msg = true, type, dest, msg_id = 1}). start(Port) -> start_tcp(Port). @@ -190,7 +190,9 @@ do_init(Domain, stream = Type, Proto) -> i("try (socket) bind"), case socket:bind(Sock, any) of {ok, _P} -> - ok = socket:setopt(Sock, ip, tos, mincost), + ok = socket:setopt(Sock, socket, timestamp, true), + ok = socket:setopt(Sock, ip, tos, mincost), + ok = socket:setopt(Sock, ip, recvtos, true), Sock; {error, BReason} -> throw({bind, BReason}) @@ -205,6 +207,10 @@ do_init(Domain, dgram = Type, Proto) -> end, case socket:bind(Sock, any) of {ok, _} -> + ok = socket:setopt(Sock, socket, timestamp, true), + ok = socket:setopt(Sock, ip, tos, mincost), + ok = socket:setopt(Sock, ip, recvtos, true), + ok = socket:setopt(Sock, ip, recvttl, true), Sock; {error, BReason} -> throw({bind, BReason}) @@ -286,15 +292,43 @@ send(#client{socket = Sock, type = dgram, dest = Dest}, Msg) -> ERROR end. -recv(#client{socket = Sock, type = stream}) -> +recv(#client{socket = Sock, type = stream, msg = false}) -> case socket:recv(Sock) of {ok, Msg} -> {ok, {undefined, Msg}}; {error, _} = ERROR -> ERROR end; -recv(#client{socket = Sock, type = dgram}) -> - socket:recvfrom(Sock). +recv(#client{socket = Sock, type = stream, msg = true}) -> + case socket:recvmsg(Sock) of + %% An iov of length 1 is an simplification... + {ok, #{addr := undefined = Source, + iov := [Msg], + ctrl := CMsgHdrs, + flags := Flags}} -> + i("received message: " + "~n CMsgHdr: ~p" + "~n Flags: ~p", [CMsgHdrs, Flags]), + {ok, {Source, Msg}}; + {error, _} = ERROR -> + ERROR + end; +recv(#client{socket = Sock, type = dgram, msg = false}) -> + socket:recvfrom(Sock); +recv(#client{socket = Sock, type = dgram, msg = true}) -> + case socket:recvmsg(Sock) of + {ok, #{addr := Source, + iov := [Msg], + ctrl := CMsgHdrs, + flags := Flags}} -> + i("received message: " + "~n CMsgHdr: ~p" + "~n Flags: ~p", [CMsgHdrs, Flags]), + {ok, {Source, Msg}}; + {error, _} = ERROR -> + ERROR + end. + which_addr(_Domain, []) -> diff --git a/lib/kernel/test/socket_server.erl b/lib/kernel/test/socket_server.erl index b43642131b..ea2bdc8e0d 100644 --- a/lib/kernel/test/socket_server.erl +++ b/lib/kernel/test/socket_server.erl @@ -901,13 +901,21 @@ peek_recvfrom(Sock, BufSz) -> send(#handler{socket = Sock, msg = true, type = stream}, Msg, _) -> - MsgHdr = #{iov => [Msg]}, - socket:sendmsg(Sock, MsgHdr); + CMsgHdr = #{level => ip, type => tos, data => reliability}, + CMsgHdrs = [CMsgHdr], + MsgHdr = #{iov => [Msg], ctrl => CMsgHdrs}, + %% socket:setopt(Sock, otp, debug, true), + Res = socket:sendmsg(Sock, MsgHdr), + %% socket:setopt(Sock, otp, debug, false), + Res; send(#handler{socket = Sock, type = stream}, Msg, _) -> socket:send(Sock, Msg); send(#handler{socket = Sock, msg = true, type = dgram}, Msg, Dest) -> - MsgHdr = #{addr => Dest, - iov => [Msg]}, + CMsgHdr = #{level => ip, type => tos, data => reliability}, + CMsgHdrs = [CMsgHdr], + MsgHdr = #{addr => Dest, + iov => [Msg], + ctrl => CMsgHdrs}, %% ok = socket:setopt(Sock, otp, debug, true), Res = socket:sendmsg(Sock, MsgHdr), %% ok = socket:setopt(Sock, otp, debug, false), -- cgit v1.2.3 From a866bd04c5ce5f418f0e11685713af2992ef0ce8 Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Tue, 18 Sep 2018 15:31:14 +0200 Subject: [socket-nif] Post rebase update --- erts/preloaded/ebin/socket.beam | Bin 66132 -> 66152 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/erts/preloaded/ebin/socket.beam b/erts/preloaded/ebin/socket.beam index 1100065e2a..e6a33337ba 100644 Binary files a/erts/preloaded/ebin/socket.beam and b/erts/preloaded/ebin/socket.beam differ -- cgit v1.2.3 From e01a856c993b55c3fbc76fd429783d4aad5bfc80 Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Wed, 19 Sep 2018 18:21:47 +0200 Subject: [socket-nif] Add proper connect and accept timeout handling Added proper connect and accept timeout handling. Made use of the enif_select(mode = cancel) feature. Each time a timeout expires, the previous operation (connect or accept) has to be cancelled (actually its the select operation that has to be cancelled). Only partial implementation of cancel for now (connect and accept). More to follow... OTP-14831 --- erts/emulator/nifs/common/socket_nif.c | 400 +++++++++++++++++++++++++++++++-- erts/preloaded/ebin/socket.beam | Bin 66152 -> 65868 bytes erts/preloaded/src/socket.erl | 111 +++------ lib/kernel/test/socket_client.erl | 21 +- lib/kernel/test/socket_server.erl | 10 +- 5 files changed, 434 insertions(+), 108 deletions(-) diff --git a/erts/emulator/nifs/common/socket_nif.c b/erts/emulator/nifs/common/socket_nif.c index 5fca0eb58b..fde2349234 100644 --- a/erts/emulator/nifs/common/socket_nif.c +++ b/erts/emulator/nifs/common/socket_nif.c @@ -737,6 +737,7 @@ typedef struct { SocketAddress remote; unsigned int addrLen; + ErlNifEnv* env; /* +++ Controller (owner) process +++ */ ErlNifPid ctrlPid; @@ -777,7 +778,7 @@ typedef struct { size_t rBufSz; // Read buffer size (when data length = 0 is specified) size_t rCtrlSz; // Read control buffer size size_t wCtrlSz; // Write control buffer size - BOOLEAN_T iow; // Inform On Wrap + BOOLEAN_T iow; // Inform On (counter) Wrap BOOLEAN_T dbg; /* +++ Close stuff +++ */ @@ -915,11 +916,9 @@ static ERL_NIF_TERM nif_finalize_connection(ErlNifEnv* env, static ERL_NIF_TERM nif_finalize_close(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); -/* static ERL_NIF_TERM nif_cancel(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); -*/ static ERL_NIF_TERM nopen(ErlNifEnv* env, @@ -983,7 +982,6 @@ static ERL_NIF_TERM nclose(ErlNifEnv* env, static ERL_NIF_TERM nshutdown(ErlNifEnv* env, SocketDescriptor* descP, int how); - static ERL_NIF_TERM nsetopt(ErlNifEnv* env, SocketDescriptor* descP, BOOLEAN_T isEncoded, @@ -1823,6 +1821,38 @@ static ERL_NIF_TERM nsockname(ErlNifEnv* env, SocketDescriptor* descP); static ERL_NIF_TERM npeername(ErlNifEnv* env, SocketDescriptor* descP); +static ERL_NIF_TERM ncancel(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM op, + ERL_NIF_TERM opRef); +static ERL_NIF_TERM ncancel_connect(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM opRef); +static ERL_NIF_TERM ncancel_accept(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM opRef); +static ERL_NIF_TERM ncancel_accept_current(ErlNifEnv* env, + SocketDescriptor* descP); +static ERL_NIF_TERM ncancel_accept_waiting(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM opRef); +static ERL_NIF_TERM ncancel_send(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM opRef); +static ERL_NIF_TERM ncancel_recv(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM opRef); +static ERL_NIF_TERM ncancel_read_select(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM opRef); +static ERL_NIF_TERM ncancel_write_select(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM opRef); +static ERL_NIF_TERM ncancel_mode_select(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM opRef, + int smode, + int rmode); static ERL_NIF_TERM nsetopt_str_opt(ErlNifEnv* env, SocketDescriptor* descP, @@ -2089,6 +2119,9 @@ static BOOLEAN_T acceptor_pop(ErlNifEnv* env, ErlNifPid* pid, ErlNifMonitor* mon, ERL_NIF_TERM* ref); +static BOOLEAN_T acceptor_unqueue(ErlNifEnv* env, + SocketDescriptor* descP, + const ErlNifPid* pid); static BOOLEAN_T qsearch4pid(ErlNifEnv* env, SocketRequestQueue* q, @@ -2241,8 +2274,10 @@ static char str_exsend[] = "exsend"; // failed send /* *** "Global" Atoms *** */ +ERL_NIF_TERM esock_atom_accept; ERL_NIF_TERM esock_atom_addr; ERL_NIF_TERM esock_atom_any; +ERL_NIF_TERM esock_atom_connect; ERL_NIF_TERM esock_atom_credentials; ERL_NIF_TERM esock_atom_ctrl; ERL_NIF_TERM esock_atom_ctrunc; @@ -2267,6 +2302,7 @@ ERL_NIF_TERM esock_atom_local; ERL_NIF_TERM esock_atom_loopback; ERL_NIF_TERM esock_atom_lowdelay; ERL_NIF_TERM esock_atom_mincost; +ERL_NIF_TERM esock_atom_not_found; ERL_NIF_TERM esock_atom_ok; ERL_NIF_TERM esock_atom_oob; ERL_NIF_TERM esock_atom_origdstaddr; @@ -2276,11 +2312,18 @@ ERL_NIF_TERM esock_atom_port; ERL_NIF_TERM esock_atom_protocol; ERL_NIF_TERM esock_atom_raw; ERL_NIF_TERM esock_atom_rdm; -ERL_NIF_TERM esock_atom_rights; +ERL_NIF_TERM esock_atom_recv; +ERL_NIF_TERM esock_atom_recvfrom; +ERL_NIF_TERM esock_atom_recvmsg; ERL_NIF_TERM esock_atom_reliability; +ERL_NIF_TERM esock_atom_rights; ERL_NIF_TERM esock_atom_scope_id; ERL_NIF_TERM esock_atom_sctp; ERL_NIF_TERM esock_atom_sec; +ERL_NIF_TERM esock_atom_select_sent; +ERL_NIF_TERM esock_atom_send; +ERL_NIF_TERM esock_atom_sendmsg; +ERL_NIF_TERM esock_atom_sendto; ERL_NIF_TERM esock_atom_seqpacket; ERL_NIF_TERM esock_atom_socket; ERL_NIF_TERM esock_atom_spec_dst; @@ -3228,7 +3271,8 @@ ERL_NIF_TERM naccept_listening(ErlNifEnv* env, &descP->currentAcceptor.mon) > 0) return esock_make_error(env, atom_exmon); - descP->currentAcceptor.ref = ref; + descP->currentAcceptor.ref = enif_make_copy(descP->env, ref); + descP->currentAcceptorP = &descP->currentAcceptor; SELECT(env, descP->sock, @@ -3326,7 +3370,8 @@ ERL_NIF_TERM naccept_listening(ErlNifEnv* env, /* *** naccept_accepting *** * We have an active acceptor and possibly acceptors waiting in queue. - * At the moment the queue is *not* implemented. + * If the pid of the calling process is not the pid of the "current process", + * push the requester onto the queue. */ static ERL_NIF_TERM naccept_accepting(ErlNifEnv* env, @@ -3353,11 +3398,8 @@ ERL_NIF_TERM naccept_accepting(ErlNifEnv* env, "\r\n", caller, descP->currentAcceptor.pid) ); if (!compare_pids(env, &descP->currentAcceptor.pid, &caller)) { - /* This will have to do until we implement the queue. - * When we have the queue, we should simply push this request, - * and instead return with eagain (the caller will then wait - * for the select message). - */ + + /* Not the "current caller", so (maybe) push onto queue */ SSDBG( descP, ("SOCKET", "naccept_accepting -> not (active) acceptor\r\n") ); @@ -10419,6 +10461,304 @@ ERL_NIF_TERM npeername(ErlNifEnv* env, +/* ---------------------------------------------------------------------- + * nif_cancel + * + * Description: + * Cancel a previous select! + * + * Arguments: + * Socket (ref) - Points to the socket descriptor. + * Operation (atom) - What kind of operation (accept, send, ...) is to be cancelled + * Ref (ref) - Unique id for the operation + */ +static +ERL_NIF_TERM nif_cancel(ErlNifEnv* env, + int argc, + const ERL_NIF_TERM argv[]) +{ + SocketDescriptor* descP; + ERL_NIF_TERM op, opRef, result; + + SGDBG( ("SOCKET", "nif_cancel -> entry with argc: %d\r\n", argc) ); + + /* Extract arguments and perform preliminary validation */ + + if ((argc != 3) || + !enif_get_resource(env, argv[0], sockets, (void**) &descP)) { + return enif_make_badarg(env); + } + op = argv[1]; + opRef = argv[2]; + + SSDBG( descP, + ("SOCKET", "nif_cancel -> args when sock = %d:" + "\r\n op: %T" + "\r\n opRef: %T" + "\r\n", descP->sock, op, opRef) ); + + result = ncancel(env, descP, op, opRef); + + SSDBG( descP, + ("SOCKET", "nif_cancel -> done with result: " + "\r\n %T" + "\r\n", result) ); + + return result; + +} + + +static +ERL_NIF_TERM ncancel(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM op, + ERL_NIF_TERM opRef) +{ + /* + * + * Do we really need all these variants? Should it not be enough with: + * + * connect | accept | send | recv + * + * + */ + if (COMPARE(op, esock_atom_connect) == 0) { + return ncancel_connect(env, descP, opRef); + } else if (COMPARE(op, esock_atom_accept) == 0) { + return ncancel_accept(env, descP, opRef); + } else if (COMPARE(op, esock_atom_send) == 0) { + return ncancel_send(env, descP, opRef); + } else if (COMPARE(op, esock_atom_sendto) == 0) { + return ncancel_send(env, descP, opRef); + } else if (COMPARE(op, esock_atom_sendmsg) == 0) { + return ncancel_send(env, descP, opRef); + } else if (COMPARE(op, esock_atom_recv) == 0) { + return ncancel_recv(env, descP, opRef); + } else if (COMPARE(op, esock_atom_recvfrom) == 0) { + return ncancel_recv(env, descP, opRef); + } else if (COMPARE(op, esock_atom_recvmsg) == 0) { + return ncancel_recv(env, descP, opRef); + } else { + return esock_make_error(env, esock_atom_einval); + } +} + + + +/* *** ncancel_connect *** + * + * + */ +static +ERL_NIF_TERM ncancel_connect(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM opRef) +{ + return ncancel_write_select(env, descP, opRef); +} + + +/* *** ncancel_accept *** + * + * We have two different cases: + * *) Its the current acceptor + * Cancel the select! + * We need to activate one of the waiting acceptors. + * *) Its one of the acceptors ("waiting") in the queue + * Simply remove the acceptor from the queue. + * + */ +static +ERL_NIF_TERM ncancel_accept(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM opRef) +{ + ERL_NIF_TERM res; + + SSDBG( descP, + ("SOCKET", "ncancel_accept -> entry with" + "\r\n opRef: %T" + "\r\n %s" + "\r\n", opRef, + ((descP->currentAcceptorP == NULL) ? "without acceptor" : "with acceptor")) ); + + MLOCK(descP->accMtx); + + if (descP->currentAcceptorP != NULL) { + if (COMPARE(opRef, descP->currentAcceptor.ref) == 0) { + res = ncancel_accept_current(env, descP); + } else { + res = ncancel_accept_waiting(env, descP, opRef); + } + } else { + /* Or badarg? */ + res = esock_make_error(env, esock_atom_einval); + } + + MUNLOCK(descP->accMtx); + + SSDBG( descP, + ("SOCKET", "ncancel_accept -> done with result:" + "\r\n %T" + "\r\n", res) ); + + return res; +} + + +/* The current process has an ongoing select we first must + * cancel. Then we must re-activate the "first" (the first + * in the acceptor queue). + */ +static +ERL_NIF_TERM ncancel_accept_current(ErlNifEnv* env, + SocketDescriptor* descP) +{ + ERL_NIF_TERM res; + + SSDBG( descP, ("SOCKET", "ncancel_accept_current -> entry\r\n") ); + + res = ncancel_read_select(env, descP, descP->currentAcceptor.ref); + + SSDBG( descP, ("SOCKET", "ncancel_accept_current -> cancel res: %T\r\n", res) ); + + if (acceptor_pop(env, descP, + &descP->currentAcceptor.pid, + &descP->currentAcceptor.mon, + &descP->currentAcceptor.ref)) { + + /* There was another one */ + + SSDBG( descP, ("SOCKET", "ncancel_accept_current -> new (active) acceptor: " + "\r\n pid: %T" + "\r\n ref: %T" + "\r\n", + descP->currentAcceptor.pid, + descP->currentAcceptor.ref) ); + + SELECT(env, + descP->sock, + (ERL_NIF_SELECT_READ), + descP, &descP->currentAcceptor.pid, descP->currentAcceptor.ref); + + } else { + SSDBG( descP, ("SOCKET", "ncancel_accept_current -> no more acceptors\r\n") ); + descP->currentAcceptorP = NULL; + descP->state = SOCKET_STATE_LISTENING; + } + + SSDBG( descP, ("SOCKET", "ncancel_accept_current -> done with result:" + "\r\n %T" + "\r\n", res) ); + + return res; +} + + +/* These processes have not performed a select, so we can simply + * remove them from the acceptor queue. + */ +static +ERL_NIF_TERM ncancel_accept_waiting(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM opRef) +{ + ErlNifPid caller; + + if (enif_self(env, &caller) == NULL) + return esock_make_error(env, atom_exself); + + /* unqueue request from (acceptor) queue */ + + if (acceptor_unqueue(env, descP, &caller)) { + return esock_atom_ok; + } else { + /* Race? */ + return esock_make_error(env, esock_atom_not_found); + } +} + + + +/* *** ncancel_send *** + * + * + */ +static +ERL_NIF_TERM ncancel_send(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM opRef) +{ + return esock_make_error(env, esock_atom_einval); +} + + + +/* *** ncancel_recv *** + * + * + */ +static +ERL_NIF_TERM ncancel_recv(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM opRef) +{ + return esock_make_error(env, esock_atom_einval); +} + + + +static +ERL_NIF_TERM ncancel_read_select(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM opRef) +{ + return ncancel_mode_select(env, descP, opRef, + ERL_NIF_SELECT_READ, + ERL_NIF_SELECT_READ_CANCELLED); +} + + +static +ERL_NIF_TERM ncancel_write_select(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM opRef) +{ + return ncancel_mode_select(env, descP, opRef, + ERL_NIF_SELECT_WRITE, + ERL_NIF_SELECT_WRITE_CANCELLED); +} + + +static +ERL_NIF_TERM ncancel_mode_select(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM opRef, + int smode, + int rmode) +{ + int selectRes = enif_select(env, descP->sock, + (ERL_NIF_SELECT_CANCEL | smode), + descP, NULL, opRef); + + if (selectRes & rmode) { + /* Was cancelled */ + return esock_atom_ok; + } else if (selectRes > 0) { + /* Has already sent the message */ + return esock_make_error(env, esock_atom_select_sent); + } else { + /* Stopped? */ + SSDBG( descP, ("SOCKET", "ncancel_mode_select -> failed: %d (0x%lX)" + "\r\n", selectRes, selectRes) ); + return esock_make_error(env, esock_atom_einval); + } + +} + + + /* ---------------------------------------------------------------------- * U t i l i t y F u n c t i o n s * ---------------------------------------------------------------------- @@ -10537,8 +10877,9 @@ ERL_NIF_TERM recv_check_result(ErlNifEnv* env, } /* There is a special case: If the provided 'to read' value is - * zero (0). That means that we reads as much as we can, using - * the default read buffer size. + * zero (0) (only for type =/= stream). + * That means that we reads as much as we can, using the default + * read buffer size. */ if (bufP->size == read) { @@ -12537,6 +12878,8 @@ SocketDescriptor* alloc_descriptor(SOCKET sock, HANDLE event) if ((descP = enif_alloc_resource(sockets, sizeof(SocketDescriptor))) != NULL) { char buf[64]; /* Buffer used for building the mutex name */ + descP->env = enif_alloc_env(); + sprintf(buf, "socket[w,%d]", sock); descP->writeMtx = MCREATE(buf); descP->currentWriterP = NULL; // currentWriter not used @@ -13161,7 +13504,7 @@ ERL_NIF_TERM acceptor_push(ErlNifEnv* env, SocketRequestor* reqP = &e->data; reqP->pid = pid; - reqP->ref = ref; + reqP->ref = enif_make_copy(descP->env, ref); if (MONP(env, descP, &pid, &reqP->mon) > 0) { FREE(reqP); @@ -13205,6 +13548,19 @@ BOOLEAN_T acceptor_pop(ErlNifEnv* env, } +/* *** acceptor unqueue *** + * + * Remove an acceptor from the acceptor queue. + */ +static +BOOLEAN_T acceptor_unqueue(ErlNifEnv* env, + SocketDescriptor* descP, + const ErlNifPid* pid) +{ + return qunqueue(env, &descP->acceptorsQ, pid); +} + + static BOOLEAN_T qsearch4pid(ErlNifEnv* env, SocketRequestQueue* q, @@ -13647,7 +14003,7 @@ void socket_down(ErlNifEnv* env, "socket_down -> " "not current acceptor - maybe a waiting acceptor\r\n") ); - qunqueue(env, &descP->acceptorsQ, pid); + acceptor_unqueue(env, descP, pid); } } @@ -13697,7 +14053,7 @@ ErlNifFunc socket_funcs[] = * is called after the connect *select* has "completed". */ {"nif_finalize_connection", 1, nif_finalize_connection, 0}, - // {"nif_cancel", 2, nif_cancel, 0}, + {"nif_cancel", 3, nif_cancel, 0}, {"nif_finalize_close", 1, nif_finalize_close, ERL_NIF_DIRTY_JOB_IO_BOUND} }; @@ -13817,8 +14173,10 @@ int on_load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info) atom_want = MKA(env, str_want); /* Global atom(s) */ + esock_atom_accept = MKA(env, "accept"); esock_atom_addr = MKA(env, "addr"); esock_atom_any = MKA(env, "any"); + esock_atom_connect = MKA(env, "connect"); esock_atom_credentials = MKA(env, "credentials"); esock_atom_ctrl = MKA(env, "ctrl"); esock_atom_ctrunc = MKA(env, "ctrunc"); @@ -13843,6 +14201,7 @@ int on_load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info) esock_atom_loopback = MKA(env, "loopback"); esock_atom_lowdelay = MKA(env, "lowdelay"); esock_atom_mincost = MKA(env, "mincost"); + esock_atom_not_found = MKA(env, "not_found"); esock_atom_ok = MKA(env, "ok"); esock_atom_oob = MKA(env, "oob"); esock_atom_origdstaddr = MKA(env, "origdstaddr"); @@ -13852,11 +14211,18 @@ int on_load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info) esock_atom_protocol = MKA(env, "protocol"); esock_atom_raw = MKA(env, "raw"); esock_atom_rdm = MKA(env, "rdm"); + esock_atom_recv = MKA(env, "recv"); + esock_atom_recvfrom = MKA(env, "recvfrom"); + esock_atom_recvmsg = MKA(env, "recvmsg"); esock_atom_reliability = MKA(env, "reliability"); esock_atom_rights = MKA(env, "rights"); esock_atom_scope_id = MKA(env, "scope_id"); esock_atom_sctp = MKA(env, "sctp"); esock_atom_sec = MKA(env, "sec"); + esock_atom_select_sent = MKA(env, "select_sent"); + esock_atom_send = MKA(env, "send"); + esock_atom_sendmsg = MKA(env, "sendmsg"); + esock_atom_sendto = MKA(env, "sendto"); esock_atom_seqpacket = MKA(env, "seqpacket"); esock_atom_socket = MKA(env, "socket"); esock_atom_spec_dst = MKA(env, "spec_dst"); diff --git a/erts/preloaded/ebin/socket.beam b/erts/preloaded/ebin/socket.beam index e6a33337ba..2475dce37b 100644 Binary files a/erts/preloaded/ebin/socket.beam and b/erts/preloaded/ebin/socket.beam differ diff --git a/erts/preloaded/src/socket.erl b/erts/preloaded/src/socket.erl index ad7a35694b..1c16c94711 100644 --- a/erts/preloaded/src/socket.erl +++ b/erts/preloaded/src/socket.erl @@ -1137,7 +1137,7 @@ connect(#socket{ref = SockRef}, #{family := Fam} = SockAddr, Timeout) %% nif_finalize_connection(SockRef) after NewTimeout -> - nif_cancel(SockRef, connect, Ref), + cancel(SockRef, connect, Ref), {error, timeout} end; {error, _} = ERROR -> @@ -1145,7 +1145,6 @@ connect(#socket{ref = SockRef}, #{family := Fam} = SockAddr, Timeout) end. - %% =========================================================================== %% %% listen - listen for connections on a socket @@ -1227,13 +1226,12 @@ do_accept(LSockRef, Timeout) -> {error, Reason} after NewTimeout -> - nif_cancel(LSockRef, accept, AccRef), - flush_select_msgs(LSockRef, AccRef), + cancel(LSockRef, accept, AccRef), {error, timeout} end; {error, _} = ERROR -> - nif_cancel(LSockRef, accept, AccRef), % Just to be on the safe side... + cancel(LSockRef, accept, AccRef), % Just to be on the safe side... ERROR end. @@ -1305,8 +1303,7 @@ do_send(SockRef, Data, EFlags, Timeout) -> {error, Reason} after NewTimeout -> - nif_cancel(SockRef, send, SendRef), - flush_select_msgs(SockRef, SendRef), + cancel(SockRef, send, SendRef), {error, {timeout, size(Data)}} end; {error, eagain} -> @@ -1319,8 +1316,7 @@ do_send(SockRef, Data, EFlags, Timeout) -> {error, Reason} after Timeout -> - nif_cancel(SockRef, send, SendRef), - flush_select_msgs(SockRef, SendRef), + cancel(SockRef, send, SendRef), {error, {timeout, size(Data)}} end; @@ -1403,8 +1399,7 @@ do_sendto(SockRef, Data, Dest, EFlags, Timeout) -> {error, Reason} after Timeout -> - nif_cancel(SockRef, sendto, SendRef), - flush_select_msgs(SockRef, SendRef), + cancel(SockRef, sendto, SendRef), {error, timeout} end; @@ -1414,8 +1409,7 @@ do_sendto(SockRef, Data, Dest, EFlags, Timeout) -> do_sendto(SockRef, Data, Dest, EFlags, next_timeout(TS, Timeout)) after Timeout -> - nif_cancel(SockRef, sendto, SendRef), - flush_select_msgs(SockRef, SendRef), + cancel(SockRef, sendto, SendRef), {error, timeout} end; @@ -1497,8 +1491,7 @@ do_sendmsg(SockRef, MsgHdr, EFlags, Timeout) -> do_sendmsg(SockRef, MsgHdr, EFlags, next_timeout(TS, Timeout)) after Timeout -> - nif_cancel(SockRef, sendmsg, SendRef), - flush_select_msgs(SockRef, SendRef), + cancel(SockRef, sendmsg, SendRef), {error, timeout} end; @@ -1519,62 +1512,6 @@ ensure_msghdr(_) -> %% writev - write data into multiple buffers %% -%% send(Socket, Data, Flags, Timeout) -%% when (is_list(Data) orelse is_binary(Data)) andalso is_list(Flags) -> -%% IOVec = erlang:iolist_to_iovec(Data), -%% EFlags = enc_send_flags(Flags), -%% send_iovec(Socket, IOVec, EFlags, Timeout). - - -%% %% Iterate over the IO-vector (list of binaries). - -%% send_iovec(_Socket, [] = _IOVec, _EFlags, _Timeout) -> -%% ok; -%% send_iovec({socket, _, SockRef} = Socket, [Bin|IOVec], EFlags, Timeout) -> -%% case do_send(SockRef, make_ref(), Bin, EFlags, Timeout) of -%% {ok, NewTimeout} -> -%% send_iovec(Socket, IOVec, EFlags, NewTimeout); -%% {error, _} = ERROR -> -%% ERROR -%% end. - - -%% do_send(SockRef, SendRef, Data, _EFlags, Timeout) -%% when (Timeout < 0) -> -%% nif_cancel(SockRef, SendRef), -%% flush_select_msgs(SockRef, SendRef), -%% {error, {timeout, size(Data)}}; -%% do_send(SockRef, SendRef, Data, EFlags, Timeout) -> -%% TS = timestamp(Timeout), -%% case nif_send(SockRef, SendRef, Data, EFlags) of -%% ok -> -%% {ok, next_timeout(TS, Timeout)}; -%% {ok, Written} -> -%% %% We are partially done, wait for continuation -%% receive -%% {select, SockRef, SendRef, ready_output} -> -%% <<_:Written/binary, Rest/binary>> = Data, -%% do_send(SockRef, make_ref(), Rest, EFlags, -%% next_timeout(TS, Timeout)) -%% after Timeout -> -%% nif_cancel(SockRef, SendRef), -%% flush_select_msgs(SockRef, SendRef), -%% {error, timeout} -%% end; -%% {error, eagain} -> -%% receive -%% {select, SockRef, SendRef, ready_output} -> -%% do_send(SockRef, SendRef, Data, EFlags, -%% next_timeout(TS, Timeout)) -%% after Timeout -> -%% nif_cancel(SockRef, SendRef), -%% flush_select_msgs(SockRef, SendRef), -%% {error, timeout} -%% end; - -%% {error, _} = ERROR -> -%% ERROR -%% end. %% =========================================================================== @@ -1695,8 +1632,7 @@ do_recv(SockRef, _OldRef, Length, EFlags, Acc, Timeout) after NewTimeout -> - nif_cancel(SockRef, recv, RecvRef), - flush_select_msgs(SockRef, RecvRef), + cancel(SockRef, recv, RecvRef), {error, {timeout, Acc}} end; @@ -1715,8 +1651,7 @@ do_recv(SockRef, _OldRef, Length, EFlags, Acc, Timeout) after NewTimeout -> - nif_cancel(SockRef, recv, RecvRef), - flush_select_msgs(SockRef, RecvRef), + cancel(SockRef, recv, RecvRef), {error, {timeout, Acc}} end; @@ -1739,8 +1674,7 @@ do_recv(SockRef, _OldRef, Length, EFlags, Acc, Timeout) {error, Reason} after NewTimeout -> - nif_cancel(SockRef, recv, RecvRef), - flush_select_msgs(SockRef, RecvRef), + cancel(SockRef, recv, RecvRef), {error, timeout} end; @@ -1765,7 +1699,7 @@ do_recv(SockRef, RecvRef, 0 = _Length, _Eflags, Acc, _Timeout) -> %% The current recv operation is to be cancelled, so no need for a ref... %% The cancel will end our 'read everything you have' and "activate" %% any waiting reader. - nif_cancel(SockRef, recv, RecvRef), + cancel(SockRef, recv, RecvRef), {ok, Acc}; do_recv(_SockRef, _RecvRef, _Length, _EFlags, Acc, _Timeout) when (size(Acc) > 0) -> {error, {timeout, Acc}}; @@ -1878,8 +1812,7 @@ do_recvfrom(SockRef, BufSz, EFlags, Timeout) -> {error, Reason} after NewTimeout -> - nif_cancel(SockRef, recvfrom, RecvRef), - flush_select_msgs(SockRef, RecvRef), + cancel(SockRef, recvfrom, RecvRef), {error, timeout} end; @@ -1966,8 +1899,7 @@ do_recvmsg(SockRef, BufSz, CtrlSz, EFlags, Timeout) -> {error, Reason} after NewTimeout -> - nif_cancel(SockRef, recvmsg, RecvRef), - flush_select_msgs(SockRef, RecvRef), + cancel(SockRef, recvmsg, RecvRef), {error, timeout} end; @@ -3325,10 +3257,19 @@ ensure_sockaddr(_SockAddr) -> -flush_select_msgs(LSRef, Ref) -> +cancel(SockRef, Op, OpRef) -> + case nif_cancel(SockRef, Op, OpRef) of + %% The select has already completed + {error, select_sent} -> + flush_select_msgs(SockRef, OpRef); + Other -> + Other + end. + +flush_select_msgs(SockRef, Ref) -> receive - {select, LSRef, Ref, _} -> - flush_select_msgs(LSRef, Ref) + {select, SockRef, Ref, _} -> + flush_select_msgs(SockRef, Ref) after 0 -> ok end. diff --git a/lib/kernel/test/socket_client.erl b/lib/kernel/test/socket_client.erl index 58d70b6181..6cd353fd07 100644 --- a/lib/kernel/test/socket_client.erl +++ b/lib/kernel/test/socket_client.erl @@ -74,7 +74,7 @@ start(Domain, Type, Proto, Addr, Port) -> %% The way we use tos only works because we %% send so few messages (a new value for every %% message). - put(tos, 1), + tos_init(), do_start(Domain, Type, Proto, SA). do_start(Domain, stream = Type, Proto, SA) -> @@ -282,11 +282,10 @@ send(#client{socket = Sock, type = dgram, dest = Dest}, Msg) -> %% i("try send to: " %% "~n ~p", [Dest]), %% ok = socket:setopt(Sock, otp, debug, true), - TOS = get(tos), + TOS = tos_next(), ok = socket:setopt(Sock, ip, tos, TOS), case socket:sendto(Sock, Msg, Dest) of ok = OK -> - put(tos, TOS+1), OK; {error, _} = ERROR -> ERROR @@ -400,6 +399,22 @@ which_addr2(Domain, [_|IFO]) -> %% lists:flatten(FormatDate). +%% --- + +tos_init() -> + put(tos, 1). + +tos_next() -> + case get(tos) of + TOS when (TOS < 100) -> + put(tos, TOS + 1), + TOS; + _ -> + put(tos, 1), + 1 + end. + + %% --- e(F, A) -> diff --git a/lib/kernel/test/socket_server.erl b/lib/kernel/test/socket_server.erl index ea2bdc8e0d..3e5c4e5d95 100644 --- a/lib/kernel/test/socket_server.erl +++ b/lib/kernel/test/socket_server.erl @@ -34,7 +34,7 @@ -define(LIB, socket_lib). -record(manager, {socket, msg, peek, acceptors, handler_id, handlers}). --record(acceptor, {id, socket, manager}). +-record(acceptor, {id, socket, manager, atimeout = 5000}). -record(handler, {socket, peek, msg, type, manager}). -define(NUM_ACCEPTORS, 5). @@ -521,13 +521,14 @@ acceptor_stop(Pid, _Reason) -> acceptor_init(Manager, Sock, ID) -> put(sname, f("acceptor[~w]", [ID])), Manager ! {acceptor, self(), ok}, + %% ok = socket:setopt(Sock, otp, debug, true), acceptor_loop(#acceptor{id = ID, manager = Manager, socket = Sock}). -acceptor_loop(#acceptor{socket = LSock} = A) -> +acceptor_loop(#acceptor{socket = LSock, atimeout = Timeout} = A) -> i("try accept"), - case socket:accept(LSock, infinity) of + case socket:accept(LSock, Timeout) of {ok, Sock} -> i("accepted: " "~n ~p" @@ -542,6 +543,9 @@ acceptor_loop(#acceptor{socket = LSock} = A) -> socket:close(Sock), exit({failed_starting_handler, Reason}) end; + {error, timeout} -> + i("timeout"), + acceptor_loop(A); {error, Reason} -> e("accept failure: " "~n ~p", [Reason]), -- cgit v1.2.3 From 13d10bc60a41f98647d802524ea8ef8fa9af6b39 Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Thu, 20 Sep 2018 15:37:37 +0200 Subject: [socket-nif] Add proper send timeout handling Added proper send timeout handling. Made use of the enif_select(mode = cancel) feature. Each time a timeout expires, the "active" send (the surrent write select) has to be cancelled. OTP-14831 --- erts/emulator/nifs/common/socket_nif.c | 384 +++++++++++++++++++++++++++++++-- erts/preloaded/ebin/socket.beam | Bin 65868 -> 66040 bytes erts/preloaded/src/socket.erl | 15 +- lib/kernel/test/socket_client.erl | 344 +++++++++++++++++++---------- lib/kernel/test/socket_server.erl | 24 ++- 5 files changed, 616 insertions(+), 151 deletions(-) diff --git a/erts/emulator/nifs/common/socket_nif.c b/erts/emulator/nifs/common/socket_nif.c index fde2349234..04c3609b32 100644 --- a/erts/emulator/nifs/common/socket_nif.c +++ b/erts/emulator/nifs/common/socket_nif.c @@ -1839,6 +1839,11 @@ static ERL_NIF_TERM ncancel_accept_waiting(ErlNifEnv* env, static ERL_NIF_TERM ncancel_send(ErlNifEnv* env, SocketDescriptor* descP, ERL_NIF_TERM opRef); +static ERL_NIF_TERM ncancel_send_current(ErlNifEnv* env, + SocketDescriptor* descP); +static ERL_NIF_TERM ncancel_send_waiting(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM opRef); static ERL_NIF_TERM ncancel_recv(ErlNifEnv* env, SocketDescriptor* descP, ERL_NIF_TERM opRef); @@ -1894,6 +1899,10 @@ static ERL_NIF_TERM ngetopt_timeval_opt(ErlNifEnv* env, int level, int opt); +static BOOLEAN_T send_check_writer(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM ref, + ERL_NIF_TERM* checkResult); static ERL_NIF_TERM send_check_result(ErlNifEnv* env, SocketDescriptor* descP, ssize_t written, @@ -2123,6 +2132,22 @@ static BOOLEAN_T acceptor_unqueue(ErlNifEnv* env, SocketDescriptor* descP, const ErlNifPid* pid); +static BOOLEAN_T writer_search4pid(ErlNifEnv* env, + SocketDescriptor* descP, + ErlNifPid* pid); +static ERL_NIF_TERM writer_push(ErlNifEnv* env, + SocketDescriptor* descP, + ErlNifPid pid, + ERL_NIF_TERM ref); +static BOOLEAN_T writer_pop(ErlNifEnv* env, + SocketDescriptor* descP, + ErlNifPid* pid, + ErlNifMonitor* mon, + ERL_NIF_TERM* ref); +static BOOLEAN_T writer_unqueue(ErlNifEnv* env, + SocketDescriptor* descP, + const ErlNifPid* pid); + static BOOLEAN_T qsearch4pid(ErlNifEnv* env, SocketRequestQueue* q, ErlNifPid* pid); @@ -3399,7 +3424,7 @@ ERL_NIF_TERM naccept_accepting(ErlNifEnv* env, if (!compare_pids(env, &descP->currentAcceptor.pid, &caller)) { - /* Not the "current caller", so (maybe) push onto queue */ + /* Not the "current acceptor", so (maybe) push onto queue */ SSDBG( descP, ("SOCKET", "naccept_accepting -> not (active) acceptor\r\n") ); @@ -3459,6 +3484,8 @@ ERL_NIF_TERM naccept_accepting(ErlNifEnv* env, SSDBG( descP, ("SOCKET", "naccept_accepting -> accept success\r\n") ); + DEMONP(env, descP, &descP->currentAcceptor.mon); + if ((accEvent = sock_create_event(accSock)) == INVALID_EVENT) { save_errno = sock_errno(); while ((sock_close(accSock) == INVALID_SOCKET) && @@ -3499,10 +3526,7 @@ ERL_NIF_TERM naccept_accepting(ErlNifEnv* env, accDescP->state = SOCKET_STATE_CONNECTED; - /* Here we should have the test if we have something in the queue. - * And if so, pop it and copy the (waiting) acceptor, and then - * make a new select with that info). - */ + /* Check if there are waiting acceptors (popping the acceptor queue) */ if (acceptor_pop(env, descP, &descP->currentAcceptor.pid, @@ -3607,12 +3631,12 @@ ERL_NIF_TERM nif_send(ErlNifEnv* env, } -/* What do we do when another process tries to write - * when the current writer has a select already waiting? - * Queue it? And what about simultaneous read and write? - * Queue up all operations towards the socket? +/* *** nsend *** * - * We (may) need a currentOp field and an ops queue field. + * Do the actual send. + * Do some initial writer checks, do the actual send and then + * analyze the result. If we are done, another writer may be + * scheduled (if there is one in the writer queue). */ static ERL_NIF_TERM nsend(ErlNifEnv* env, @@ -3621,12 +3645,17 @@ ERL_NIF_TERM nsend(ErlNifEnv* env, ErlNifBinary* sndDataP, int flags) { - int save_errno; - ssize_t written; + int save_errno; + ssize_t written; + ERL_NIF_TERM writerCheck; if (!descP->isWritable) return enif_make_badarg(env); + /* Check if there is already a current writer and if its us */ + if (!send_check_writer(env, descP, sendRef, &writerCheck)) + return writerCheck; + /* We ignore the wrap for the moment. * Maybe we should issue a wrap-message to controlling process... */ @@ -3645,6 +3674,7 @@ ERL_NIF_TERM nsend(ErlNifEnv* env, } + /* ---------------------------------------------------------------------- * nif_sendto * @@ -3710,9 +3740,13 @@ ERL_NIF_TERM nif_sendto(ErlNifEnv* env, &remoteAddrLen)) != NULL) return esock_make_error_str(env, xres); + MLOCK(descP->writeMtx); + res = nsendto(env, descP, sendRef, &sndData, flags, &remoteAddr, remoteAddrLen); + MUNLOCK(descP->writeMtx); + SGDBG( ("SOCKET", "nif_sendto -> done with result: " "\r\n %T" "\r\n", res) ); @@ -3730,12 +3764,17 @@ ERL_NIF_TERM nsendto(ErlNifEnv* env, SocketAddress* toAddrP, unsigned int toAddrLen) { - int save_errno; - ssize_t written; + int save_errno; + ssize_t written; + ERL_NIF_TERM writerCheck; if (!descP->isWritable) return enif_make_badarg(env); + /* Check if there is already a current writer and if its us */ + if (!send_check_writer(env, descP, sendRef, &writerCheck)) + return writerCheck; + /* We ignore the wrap for the moment. * Maybe we should issue a wrap-message to controlling process... */ @@ -3811,8 +3850,12 @@ ERL_NIF_TERM nif_sendmsg(ErlNifEnv* env, if (!esendflags2sendflags(eflags, &flags)) return esock_make_error(env, esock_atom_einval); + MLOCK(descP->writeMtx); + res = nsendmsg(env, descP, sendRef, eMsgHdr, flags); + MUNLOCK(descP->writeMtx); + SSDBG( descP, ("SOCKET", "nif_sendmsg -> done with result: " "\r\n %T" @@ -3839,12 +3882,16 @@ ERL_NIF_TERM nsendmsg(ErlNifEnv* env, size_t ctrlBufLen, ctrlBufUsed; int save_errno; ssize_t written, dataSize; + ERL_NIF_TERM writerCheck; char* xres; if (!descP->isWritable) return enif_make_badarg(env); - + /* Check if there is already a current writer and if its us */ + if (!send_check_writer(env, descP, sendRef, &writerCheck)) + return writerCheck; + /* Depending on if we are *connected* or not, we require * different things in the msghdr map. */ @@ -10607,7 +10654,7 @@ ERL_NIF_TERM ncancel_accept(ErlNifEnv* env, } -/* The current process has an ongoing select we first must +/* The current acceptor process has an ongoing select we first must * cancel. Then we must re-activate the "first" (the first * in the acceptor queue). */ @@ -10664,7 +10711,7 @@ ERL_NIF_TERM ncancel_accept_waiting(ErlNifEnv* env, SocketDescriptor* descP, ERL_NIF_TERM opRef) { - ErlNifPid caller; + ErlNifPid caller; if (enif_self(env, &caller) == NULL) return esock_make_error(env, atom_exself); @@ -10683,14 +10730,117 @@ ERL_NIF_TERM ncancel_accept_waiting(ErlNifEnv* env, /* *** ncancel_send *** * - * + * Cancel a send operation. + * Its either the current writer or one of the waiting writers. */ static ERL_NIF_TERM ncancel_send(ErlNifEnv* env, SocketDescriptor* descP, ERL_NIF_TERM opRef) { - return esock_make_error(env, esock_atom_einval); + ERL_NIF_TERM res; + + SSDBG( descP, + ("SOCKET", "ncancel_send -> entry with" + "\r\n opRef: %T" + "\r\n %s" + "\r\n", opRef, + ((descP->currentWriterP == NULL) ? "without writer" : "with writer")) ); + + MLOCK(descP->writeMtx); + + if (descP->currentWriterP != NULL) { + if (COMPARE(opRef, descP->currentWriter.ref) == 0) { + res = ncancel_send_current(env, descP); + } else { + res = ncancel_send_waiting(env, descP, opRef); + } + } else { + /* Or badarg? */ + res = esock_make_error(env, esock_atom_einval); + } + + MUNLOCK(descP->writeMtx); + + SSDBG( descP, + ("SOCKET", "ncancel_send -> done with result:" + "\r\n %T" + "\r\n", res) ); + + return res; +} + + + +/* The current writer process has an ongoing select we first must + * cancel. Then we must re-activate the "first" (the first + * in the writer queue). + */ +static +ERL_NIF_TERM ncancel_send_current(ErlNifEnv* env, + SocketDescriptor* descP) +{ + ERL_NIF_TERM res; + + SSDBG( descP, ("SOCKET", "ncancel_send_current -> entry\r\n") ); + + res = ncancel_write_select(env, descP, descP->currentWriter.ref); + + SSDBG( descP, ("SOCKET", "ncancel_send_current -> cancel res: %T\r\n", res) ); + + if (writer_pop(env, descP, + &descP->currentWriter.pid, + &descP->currentWriter.mon, + &descP->currentWriter.ref)) { + + /* There was another one */ + + SSDBG( descP, ("SOCKET", "ncancel_send_current -> new (active) writer: " + "\r\n pid: %T" + "\r\n ref: %T" + "\r\n", + descP->currentWriter.pid, + descP->currentWriter.ref) ); + + SELECT(env, + descP->sock, + (ERL_NIF_SELECT_WRITE), + descP, &descP->currentWriter.pid, descP->currentWriter.ref); + + } else { + SSDBG( descP, ("SOCKET", "ncancel_send_current -> no more writers\r\n") ); + descP->currentWriterP = NULL; + } + + SSDBG( descP, ("SOCKET", "ncancel_send_current -> done with result:" + "\r\n %T" + "\r\n", res) ); + + return res; +} + + +/* These processes have not performed a select, so we can simply + * remove them from the writer queue. + */ +static +ERL_NIF_TERM ncancel_send_waiting(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM opRef) +{ + ErlNifPid caller; + + if (enif_self(env, &caller) == NULL) + return esock_make_error(env, atom_exself); + + /* unqueue request from (writer) queue */ + + if (writer_unqueue(env, descP, &caller)) { + return esock_atom_ok; + } else { + /* Race? */ + return esock_make_error(env, esock_atom_not_found); + } } @@ -10764,6 +10914,66 @@ ERL_NIF_TERM ncancel_mode_select(ErlNifEnv* env, * ---------------------------------------------------------------------- */ +/* *** send_check_writer *** + * + * Checks if we have a current writer and if that is us. If not, then we must + * be made to wait for our turn. This is done by pushing us unto the writer queue. + */ +static +BOOLEAN_T send_check_writer(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM ref, + ERL_NIF_TERM* checkResult) +{ + if (descP->currentWriterP != NULL) { + ErlNifPid caller; + + if (enif_self(env, &caller) == NULL) { + *checkResult = esock_make_error(env, atom_exself); + return FALSE; + } + + if (!compare_pids(env, &descP->currentWriter.pid, &caller)) { + /* Not the "current writer", so (maybe) push onto queue */ + + SSDBG( descP, + ("SOCKET", "send_check_writer -> not (current) writer\r\n") ); + + if (!writer_search4pid(env, descP, &caller)) + *checkResult = writer_push(env, descP, caller, ref); + else + *checkResult = esock_make_error(env, esock_atom_eagain); + + SSDBG( descP, + ("SOCKET", + "nsend -> queue (push) result: %T\r\n", checkResult) ); + + return FALSE; + + } + + } + + *checkResult = esock_atom_ok; // Does not actually matter in this case, but ... + + return TRUE; +} + + + +/* *** send_check_result *** + * + * Check the result of a socket send (send, sendto and sendmsg) call. + * If a "complete" send has been made, the next (waiting) writer will be + * scheduled (if there is one). + * If we did not manage to send the entire package, make another select, + * so that we can be informed when we can make another try (to send the rest), + * and return with the amount we actually managed to send (its up to the caller + * (that is the erlang code) to figure out hust much is left to send). + * If the write fail, we give up and return with the appropriate error code. + * + * What about the remaining writers!! + */ static ERL_NIF_TERM send_check_result(ErlNifEnv* env, SocketDescriptor* descP, @@ -10783,24 +10993,67 @@ ERL_NIF_TERM send_check_result(ErlNifEnv* env, cnt_inc(&descP->writePkgCnt, 1); cnt_inc(&descP->writeByteCnt, written); + DEMONP(env, descP, &descP->currentWriter.mon); SSDBG( descP, ("SOCKET", "send_check_result -> " "everything written (%d,%d) - done\r\n", dataSize, written) ); + /* Ok, this write is done maybe activate the next (if any) */ + + if (writer_pop(env, descP, + &descP->currentWriter.pid, + &descP->currentWriter.mon, + &descP->currentWriter.ref)) { + + /* There was another one */ + + SSDBG( descP, ("SOCKET", "send_check_result -> new (active) writer: " + "\r\n pid: %T" + "\r\n ref: %T" + "\r\n", + descP->currentWriter.pid, + descP->currentWriter.ref) ); + + SELECT(env, + descP->sock, + (ERL_NIF_SELECT_WRITE), + descP, &descP->currentWriter.pid, descP->currentWriter.ref); + + } else { + descP->currentWriterP = NULL; + } + return esock_atom_ok; } else if (written < 0) { - /* Ouch, check what kind of failure */ + /* Some kind of send failure - check what kind */ + if ((saveErrno != EAGAIN) && (saveErrno != EINTR)) { + ErlNifPid pid; + ErlNifMonitor mon; + ERL_NIF_TERM ref, res; + + /* + * An actual failure - we (and everyone waiting) give up + */ cnt_inc(&descP->writeFails, 1); SSDBG( descP, ("SOCKET", "send_check_result -> error: %d\r\n", saveErrno) ); - return esock_make_error_errno(env, saveErrno); + res = esock_make_error_errno(env, saveErrno); + + while (writer_pop(env, descP, &pid, &mon, &ref)) { + SSDBG( descP, + ("SOCKET", "send_check_result -> abort %T\r\n", pid) ); + send_msg_nif_abort(env, ref, res, &pid); + DEMONP(env, descP, &mon); + } + + return res; } else { @@ -13561,6 +13814,95 @@ BOOLEAN_T acceptor_unqueue(ErlNifEnv* env, } + +/* *** writer search for pid *** + * + * Search for a pid in the writer queue. + */ +static +BOOLEAN_T writer_search4pid(ErlNifEnv* env, + SocketDescriptor* descP, + ErlNifPid* pid) +{ + return qsearch4pid(env, &descP->writersQ, pid); +} + + +/* *** writer push *** + * + * Push an writer onto the writer queue. + * This happens when we already have atleast one current writer. + */ +static +ERL_NIF_TERM writer_push(ErlNifEnv* env, + SocketDescriptor* descP, + ErlNifPid pid, + ERL_NIF_TERM ref) +{ + SocketRequestQueueElement* e = MALLOC(sizeof(SocketRequestQueueElement)); + SocketRequestor* reqP = &e->data; + + reqP->pid = pid; + reqP->ref = enif_make_copy(descP->env, ref); + + if (MONP(env, descP, &pid, &reqP->mon) > 0) { + FREE(reqP); + return esock_make_error(env, atom_exmon); + } + + qpush(&descP->writersQ, e); + + // THIS IS OK => MAKES THE CALLER WAIT FOR ITS TURN + return esock_make_error(env, esock_atom_eagain); +} + + +/* *** writer pop *** + * + * Pop an writer from the writer queue. + */ +static +BOOLEAN_T writer_pop(ErlNifEnv* env, + SocketDescriptor* descP, + ErlNifPid* pid, + ErlNifMonitor* mon, + ERL_NIF_TERM* ref) +{ + SocketRequestQueueElement* e = qpop(&descP->writersQ); + + if (e != NULL) { + *pid = e->data.pid; + *mon = e->data.mon; + *ref = e->data.ref; // At this point the ref has already been copied (env) + FREE(e); + return TRUE; + } else { + /* (acceptors) Queue was empty */ + // *pid = NULL; we have no null value for pids + // *mon = NULL; we have no null value for monitors + *ref = esock_atom_undefined; // Just in case + return FALSE; + } + +} + + +/* *** writer unqueue *** + * + * Remove an writer from the writer queue. + */ +static +BOOLEAN_T writer_unqueue(ErlNifEnv* env, + SocketDescriptor* descP, + const ErlNifPid* pid) +{ + return qunqueue(env, &descP->writersQ, pid); +} + + + + + static BOOLEAN_T qsearch4pid(ErlNifEnv* env, SocketRequestQueue* q, diff --git a/erts/preloaded/ebin/socket.beam b/erts/preloaded/ebin/socket.beam index 2475dce37b..9e6d9f4709 100644 Binary files a/erts/preloaded/ebin/socket.beam and b/erts/preloaded/ebin/socket.beam differ diff --git a/erts/preloaded/src/socket.erl b/erts/preloaded/src/socket.erl index 1c16c94711..652054457f 100644 --- a/erts/preloaded/src/socket.erl +++ b/erts/preloaded/src/socket.erl @@ -1345,10 +1345,19 @@ sendto(Socket, Data, Dest) -> Data :: binary(), Dest :: null | sockaddr(), Flags :: send_flags(), - Reason :: term(). + Reason :: term() + ; (Socket, Data, Dest, Timeout) -> ok | {error, Reason} when + Socket :: socket(), + Data :: iodata(), + Dest :: null | sockaddr(), + Timeout :: timeout(), + Reason :: term(). + +sendto(Socket, Data, Dest, Flags) when is_list(Flags) -> + sendto(Socket, Data, Dest, Flags, ?SOCKET_SENDTO_TIMEOUT_DEFAULT); +sendto(Socket, Data, Dest, Timeout) -> + sendto(Socket, Data, Dest, ?SOCKET_SENDTO_FLAGS_DEFAULT, Timeout). -sendto(Socket, Data, Dest, Flags) -> - sendto(Socket, Data, Dest, Flags, ?SOCKET_SENDTO_TIMEOUT_DEFAULT). -spec sendto(Socket, Data, Dest, Flags, Timeout) -> ok | {error, Reason} when Socket :: socket(), diff --git a/lib/kernel/test/socket_client.erl b/lib/kernel/test/socket_client.erl index 6cd353fd07..1c07e799b8 100644 --- a/lib/kernel/test/socket_client.erl +++ b/lib/kernel/test/socket_client.erl @@ -21,53 +21,126 @@ -module(socket_client). -export([ - start/1, start/5, - start_tcp/1, start_tcp/2, start_tcp4/1, start_tcp6/1, - start_udp/1, start_udp/2, start_udp4/1, start_udp6/1 + start/1, start/2, start/5, start/6, + start_tcp/1, start_tcp/2, start_tcp/3, + start_tcp4/1, start_tcp4/2, start_tcp6/1, start_tcp6/2, + start_udp/1, start_udp/2, start_udp/3, + start_udp4/1, start_udp4/2, start_udp6/1, start_udp6/2 ]). -define(LIB, socket_lib). --record(client, {socket, msg = true, type, dest, msg_id = 1}). +-record(client, {socket, verbose = true, msg = true, type, dest, msg_id = 1}). start(Port) -> - start_tcp(Port). + start(Port, 1). + +start(Port, Num) -> + start_tcp(Port, Num). start_tcp(Port) -> - start_tcp4(Port). + start_tcp(Port, 1). + +start_tcp(Port, Num) -> + start_tcp4(Port, Num). start_tcp4(Port) -> - start(inet, stream, tcp, Port). + start_tcp4(Port, 1). + +start_tcp4(Port, Num) -> + start(inet, stream, tcp, Port, Num). start_tcp6(Port) -> - start(inet6, stream, tcp, Port). + start_tcp6(Port, 1). + +start_tcp6(Port, Num) -> + start(inet6, stream, tcp, Port, Num). -start_tcp(Addr, Port) when (size(Addr) =:= 4) -> - start(inet, stream, tcp, Addr, Port); -start_tcp(Addr, Port) when (size(Addr) =:= 8) -> - start(inet6, stream, tcp, Addr, Port). +start_tcp(Addr, Port, Num) when (size(Addr) =:= 4) andalso + is_integer(Num) andalso + (Num > 0) -> + start(inet, stream, tcp, Addr, Port, Num); +start_tcp(Addr, Port, Num) when (size(Addr) =:= 8) andalso + is_integer(Num) andalso + (Num > 0) -> + start(inet6, stream, tcp, Addr, Port, Num). start_udp(Port) -> - start_udp4(Port). + start_udp(Port, 1). + +start_udp(Port, Num) -> + start_udp4(Port, Num). start_udp4(Port) -> - start(inet, dgram, udp, Port). + start_udp4(Port, 1). + +start_udp4(Port, Num) -> + start(inet, dgram, udp, Port, Num). start_udp6(Port) -> - start(inet6, dgram, udp, Port). + start_udp6(Port, 1). -start_udp(Addr, Port) when (size(Addr) =:= 4) -> - start(inet, dgram, udp, Addr, Port); -start_udp(Addr, Port) when (size(Addr) =:= 8) -> - start(inet6, dgram, udp, Addr, Port). +start_udp6(Port, Num) -> + start(inet6, dgram, udp, Port, Num). +start_udp(Addr, Port, Num) when (size(Addr) =:= 4) -> + start(inet, dgram, udp, Addr, Port, Num); +start_udp(Addr, Port, Num) when (size(Addr) =:= 8) -> + start(inet6, dgram, udp, Addr, Port, Num). -start(Domain, Type, Proto, Port) -> - start(Domain, Type, Proto, which_addr(Domain), Port). + +start(Domain, Type, Proto, Port, Num) + when is_integer(Port) andalso is_integer(Num) -> + start(Domain, Type, Proto, which_addr(Domain), Port, Num); start(Domain, Type, Proto, Addr, Port) -> + start(Domain, Type, Proto, Addr, Port, 1). + +start(Domain, Type, Proto, Addr, Port, 1 = Num) -> + start(Domain, Type, Proto, Addr, Port, Num, true); +start(Domain, Type, Proto, Addr, Port, Num) + when is_integer(Num) andalso (Num > 1) -> + start(Domain, Type, Proto, Addr, Port, Num, false). + +start(Domain, Type, Proto, Addr, Port, Num, Verbose) -> put(sname, "starter"), + Clients = start_clients(Num, Domain, Type, Proto, Addr, Port, Verbose), + await_clients(Clients). + +start_clients(Num, Domain, Type, Proto, Addr, Port, Verbose) -> + start_clients(Num, 1, Domain, Type, Proto, Addr, Port, Verbose, []). + +start_clients(Num, ID, Domain, Type, Proto, Addr, Port, Verbose, Acc) + when (Num > 0) -> + StartClient = fun() -> + start_client(ID, Domain, Type, Proto, Addr, Port, Verbose) + end, + {Pid, _} = spawn_monitor(StartClient), + ?LIB:sleep(500), + i("start client ~w", [ID]), + start_clients(Num-1, ID+1, Domain, Type, Proto, Addr, Port, Verbose, [Pid|Acc]); +start_clients(_, _, _, _, _, _, _, _, Acc) -> + i("all client(s) started"), + lists:reverse(Acc). + +await_clients([]) -> + i("all clients done"); +await_clients(Clients) -> + receive + {'DOWN', _MRef, process, Pid, _Reason} -> + case lists:delete(Pid, Clients) of + Clients2 when (Clients2 =/= Clients) -> + i("client ~p done", [Pid]), + await_clients(Clients2); + _ -> + await_clients(Clients) + end + end. + + +start_client(ID, Domain, Type, Proto, Addr, Port, Verbose) -> + put(sname, ?LIB:f("client[~w]", [ID])), SA = #{family => Domain, addr => Addr, port => Port}, @@ -75,110 +148,119 @@ start(Domain, Type, Proto, Addr, Port) -> %% send so few messages (a new value for every %% message). tos_init(), - do_start(Domain, Type, Proto, SA). + do_start(Domain, Type, Proto, SA, Verbose). -do_start(Domain, stream = Type, Proto, SA) -> +do_start(Domain, stream = Type, Proto, SA, Verbose) -> try do_init(Domain, Type, Proto) of Sock -> connect(Sock, SA), - {ok, Name} = socket:sockname(Sock), - {ok, Peer} = socket:peername(Sock), - {ok, Domain} = socket:getopt(Sock, socket, domain), - {ok, Type} = socket:getopt(Sock, socket, type), - {ok, Proto} = socket:getopt(Sock, socket, protocol), - {ok, OOBI} = socket:getopt(Sock, socket, oobinline), - {ok, SndBuf} = socket:getopt(Sock, socket, sndbuf), - {ok, RcvBuf} = socket:getopt(Sock, socket, rcvbuf), - {ok, Linger} = socket:getopt(Sock, socket, linger), - {ok, MTU} = socket:getopt(Sock, ip, mtu), - {ok, MTUDisc} = socket:getopt(Sock, ip, mtu_discover), - {ok, MALL} = socket:getopt(Sock, ip, multicast_all), - {ok, MIF} = socket:getopt(Sock, ip, multicast_if), - {ok, MLoop} = socket:getopt(Sock, ip, multicast_loop), - {ok, MTTL} = socket:getopt(Sock, ip, multicast_ttl), - {ok, RecvTOS} = socket:getopt(Sock, ip, recvtos), - i("connected: " - "~n From: ~p" - "~n To: ~p" - "~nwhen" - "~n (socket) Domain: ~p" - "~n (socket) Type: ~p" - "~n (socket) Protocol: ~p" - "~n (socket) OOBInline: ~p" - "~n (socket) SndBuf: ~p" - "~n (socket) RcvBuf: ~p" - "~n (socket) Linger: ~p" - "~n (ip) MTU: ~p" - "~n (ip) MTU Discovery: ~p" - "~n (ip) Multicast ALL: ~p" - "~n (ip) Multicast IF: ~p" - "~n (ip) Multicast Loop: ~p" - "~n (ip) Multicast TTL: ~p" - "~n (ip) RecvTOS: ~p" - "~n => wait some", - [Name, Peer, - Domain, Type, Proto, - OOBI, SndBuf, RcvBuf, Linger, - MTU, MTUDisc, MALL, MIF, MLoop, MTTL, - RecvTOS]), + maybe_print_start_info(Verbose, Sock, Type), %% Give the server some time... ?LIB:sleep(5000), %% ok = socket:close(Sock), - send_loop(#client{socket = Sock, - type = Type}) + send_loop(#client{socket = Sock, + type = Type, + verbose = Verbose}) catch throw:E -> e("Failed initiate: " "~n Error: ~p", [E]) end; -do_start(Domain, dgram = Type, Proto, SA) -> +do_start(Domain, dgram = Type, Proto, SA, Verbose) -> try do_init(Domain, Type, Proto) of Sock -> + maybe_print_start_info(Verbose, Sock, Type), %% Give the server some time... - {ok, Domain} = socket:getopt(Sock, socket, domain), - {ok, Type} = socket:getopt(Sock, socket, type), - {ok, Proto} = socket:getopt(Sock, socket, protocol), - {ok, OOBI} = socket:getopt(Sock, socket, oobinline), - {ok, SndBuf} = socket:getopt(Sock, socket, sndbuf), - {ok, RcvBuf} = socket:getopt(Sock, socket, rcvbuf), - {ok, Linger} = socket:getopt(Sock, socket, linger), - {ok, MALL} = socket:getopt(Sock, ip, multicast_all), - {ok, MIF} = socket:getopt(Sock, ip, multicast_if), - {ok, MLoop} = socket:getopt(Sock, ip, multicast_loop), - {ok, MTTL} = socket:getopt(Sock, ip, multicast_ttl), - {ok, RecvTOS} = socket:getopt(Sock, ip, recvtos), - {ok, RecvTTL} = socket:getopt(Sock, ip, recvttl), - i("initiated when: " - "~n (socket) Domain: ~p" - "~n (socket) Type: ~p" - "~n (socket) Protocol: ~p" - "~n (socket) OOBInline: ~p" - "~n (socket) SndBuf: ~p" - "~n (socket) RcvBuf: ~p" - "~n (socket) Linger: ~p" - "~n (ip) Multicast ALL: ~p" - "~n (ip) Multicast IF: ~p" - "~n (ip) Multicast Loop: ~p" - "~n (ip) Multicast TTL: ~p" - "~n (ip) RecvTOS: ~p" - "~n (ip) RecvTTL: ~p" - "~n => wait some", - [Domain, Type, Proto, - OOBI, SndBuf, RcvBuf, Linger, - MALL, MIF, MLoop, MTTL, - RecvTOS, RecvTTL]), ?LIB:sleep(5000), %% ok = socket:close(Sock), - send_loop(#client{socket = Sock, - type = Type, - dest = SA}) + send_loop(#client{socket = Sock, + type = Type, + dest = SA, + verbose = Verbose}) catch throw:E -> e("Failed initiate: " "~n Error: ~p", [E]) end. +maybe_print_start_info(true = _Verbose, Sock, stream = _Type) -> + {ok, Name} = socket:sockname(Sock), + {ok, Peer} = socket:peername(Sock), + {ok, Domain} = socket:getopt(Sock, socket, domain), + {ok, Type} = socket:getopt(Sock, socket, type), + {ok, Proto} = socket:getopt(Sock, socket, protocol), + {ok, OOBI} = socket:getopt(Sock, socket, oobinline), + {ok, SndBuf} = socket:getopt(Sock, socket, sndbuf), + {ok, RcvBuf} = socket:getopt(Sock, socket, rcvbuf), + {ok, Linger} = socket:getopt(Sock, socket, linger), + {ok, MTU} = socket:getopt(Sock, ip, mtu), + {ok, MTUDisc} = socket:getopt(Sock, ip, mtu_discover), + {ok, MALL} = socket:getopt(Sock, ip, multicast_all), + {ok, MIF} = socket:getopt(Sock, ip, multicast_if), + {ok, MLoop} = socket:getopt(Sock, ip, multicast_loop), + {ok, MTTL} = socket:getopt(Sock, ip, multicast_ttl), + {ok, RecvTOS} = socket:getopt(Sock, ip, recvtos), + i("connected: " + "~n From: ~p" + "~n To: ~p" + "~nwhen" + "~n (socket) Domain: ~p" + "~n (socket) Type: ~p" + "~n (socket) Protocol: ~p" + "~n (socket) OOBInline: ~p" + "~n (socket) SndBuf: ~p" + "~n (socket) RcvBuf: ~p" + "~n (socket) Linger: ~p" + "~n (ip) MTU: ~p" + "~n (ip) MTU Discovery: ~p" + "~n (ip) Multicast ALL: ~p" + "~n (ip) Multicast IF: ~p" + "~n (ip) Multicast Loop: ~p" + "~n (ip) Multicast TTL: ~p" + "~n (ip) RecvTOS: ~p" + "~n => wait some", + [Name, Peer, + Domain, Type, Proto, + OOBI, SndBuf, RcvBuf, Linger, + MTU, MTUDisc, MALL, MIF, MLoop, MTTL, + RecvTOS]); +maybe_print_start_info(true = _Verbose, Sock, dgram = _Type) -> + {ok, Domain} = socket:getopt(Sock, socket, domain), + {ok, Type} = socket:getopt(Sock, socket, type), + {ok, Proto} = socket:getopt(Sock, socket, protocol), + {ok, OOBI} = socket:getopt(Sock, socket, oobinline), + {ok, SndBuf} = socket:getopt(Sock, socket, sndbuf), + {ok, RcvBuf} = socket:getopt(Sock, socket, rcvbuf), + {ok, Linger} = socket:getopt(Sock, socket, linger), + {ok, MALL} = socket:getopt(Sock, ip, multicast_all), + {ok, MIF} = socket:getopt(Sock, ip, multicast_if), + {ok, MLoop} = socket:getopt(Sock, ip, multicast_loop), + {ok, MTTL} = socket:getopt(Sock, ip, multicast_ttl), + {ok, RecvTOS} = socket:getopt(Sock, ip, recvtos), + {ok, RecvTTL} = socket:getopt(Sock, ip, recvttl), + i("initiated when: " + "~n (socket) Domain: ~p" + "~n (socket) Type: ~p" + "~n (socket) Protocol: ~p" + "~n (socket) OOBInline: ~p" + "~n (socket) SndBuf: ~p" + "~n (socket) RcvBuf: ~p" + "~n (socket) Linger: ~p" + "~n (ip) Multicast ALL: ~p" + "~n (ip) Multicast IF: ~p" + "~n (ip) Multicast Loop: ~p" + "~n (ip) Multicast TTL: ~p" + "~n (ip) RecvTOS: ~p" + "~n (ip) RecvTTL: ~p" + "~n => wait some", + [Domain, Type, Proto, + OOBI, SndBuf, RcvBuf, Linger, + MALL, MIF, MLoop, MTTL, + RecvTOS, RecvTTL]); +maybe_print_start_info(_Verbose, _Sock, _Type) -> + ok. + do_init(Domain, stream = Type, Proto) -> i("try (socket) open"), Sock = case socket:open(Domain, Type, Proto) of @@ -248,14 +330,25 @@ send_loop(#client{msg_id = N} = C) when (N =< 10) -> i("request ~w sent - now try read answer", [N]), case recv(C) of {ok, {Source, Msg}} -> - i("received ~w bytes of data~s", - [size(Msg), case Source of - undefined -> ""; - _ -> ?LIB:f(" from:~n ~p", [Source]) - end]), + if + (C#client.verbose =:= true) -> + i("received ~w bytes of data~s", + [size(Msg), case Source of + undefined -> ""; + _ -> ?LIB:f(" from:~n ~p", [Source]) + end]); + true -> + i("received ~w bytes", [size(Msg)]) + end, case ?LIB:dec_msg(Msg) of {reply, N, Reply} -> - i("received reply ~w: ~p", [N, Reply]), + if + (C#client.verbose =:= true) -> + i("received reply ~w: ~p", [N, Reply]); + true -> + i("received reply ~w", [N]) + end, + ?LIB:sleep(500), % Just to spread it out a bit send_loop(C#client{msg_id = N+1}) end; {error, RReason} -> @@ -268,13 +361,20 @@ send_loop(#client{msg_id = N} = C) when (N =< 10) -> "~n ~p", [N, SReason]), exit({failed_send, SReason}) end; -send_loop(#client{socket = Sock}) -> +send_loop(Client) -> + sock_close(Client). + +sock_close(#client{socket = Sock, verbose = true}) -> i("we are done - close the socket when: " "~n ~p", [socket:info()]), ok = socket:close(Sock), i("we are done - socket closed when: " - "~n ~p", [socket:info()]). + "~n ~p", [socket:info()]); +sock_close(#client{socket = Sock}) -> + i("we are done"), + ok = socket:close(Sock). + send(#client{socket = Sock, type = stream}, Msg) -> socket:send(Sock, Msg); @@ -298,31 +398,41 @@ recv(#client{socket = Sock, type = stream, msg = false}) -> {error, _} = ERROR -> ERROR end; -recv(#client{socket = Sock, type = stream, msg = true}) -> +recv(#client{socket = Sock, verbose = Verbose, type = stream, msg = true}) -> case socket:recvmsg(Sock) of %% An iov of length 1 is an simplification... {ok, #{addr := undefined = Source, iov := [Msg], ctrl := CMsgHdrs, flags := Flags}} -> - i("received message: " - "~n CMsgHdr: ~p" - "~n Flags: ~p", [CMsgHdrs, Flags]), + if + (Verbose =:= true) -> + i("received message: " + "~n CMsgHdr: ~p" + "~n Flags: ~p", [CMsgHdrs, Flags]); + true -> + ok + end, {ok, {Source, Msg}}; {error, _} = ERROR -> ERROR end; recv(#client{socket = Sock, type = dgram, msg = false}) -> socket:recvfrom(Sock); -recv(#client{socket = Sock, type = dgram, msg = true}) -> +recv(#client{socket = Sock, verbose = Verbose, type = dgram, msg = true}) -> case socket:recvmsg(Sock) of {ok, #{addr := Source, iov := [Msg], ctrl := CMsgHdrs, flags := Flags}} -> - i("received message: " - "~n CMsgHdr: ~p" - "~n Flags: ~p", [CMsgHdrs, Flags]), + if + (Verbose =:= true) -> + i("received message: " + "~n CMsgHdr: ~p" + "~n Flags: ~p", [CMsgHdrs, Flags]); + true -> + ok + end, {ok, {Source, Msg}}; {error, _} = ERROR -> ERROR diff --git a/lib/kernel/test/socket_server.erl b/lib/kernel/test/socket_server.erl index 3e5c4e5d95..9142942428 100644 --- a/lib/kernel/test/socket_server.erl +++ b/lib/kernel/test/socket_server.erl @@ -34,8 +34,10 @@ -define(LIB, socket_lib). -record(manager, {socket, msg, peek, acceptors, handler_id, handlers}). --record(acceptor, {id, socket, manager, atimeout = 5000}). --record(handler, {socket, peek, msg, type, manager}). +-record(acceptor, {id, socket, manager, + atimeout = 5000}). +-record(handler, {socket, peek, msg, type, manager, + stimeout = 5000, rtimeout = 5000}). -define(NUM_ACCEPTORS, 5). @@ -904,28 +906,30 @@ peek_recvfrom(Sock, BufSz) -> end. -send(#handler{socket = Sock, msg = true, type = stream}, Msg, _) -> +send(#handler{socket = Sock, msg = true, type = stream, stimeout = Timeout}, + Msg, _) -> CMsgHdr = #{level => ip, type => tos, data => reliability}, CMsgHdrs = [CMsgHdr], MsgHdr = #{iov => [Msg], ctrl => CMsgHdrs}, %% socket:setopt(Sock, otp, debug, true), - Res = socket:sendmsg(Sock, MsgHdr), + Res = socket:sendmsg(Sock, MsgHdr, Timeout), %% socket:setopt(Sock, otp, debug, false), Res; -send(#handler{socket = Sock, type = stream}, Msg, _) -> - socket:send(Sock, Msg); -send(#handler{socket = Sock, msg = true, type = dgram}, Msg, Dest) -> +send(#handler{socket = Sock, type = stream, stimeout = Timeout}, Msg, _) -> + socket:send(Sock, Msg, Timeout); +send(#handler{socket = Sock, msg = true, type = dgram, stimeout = Timeout}, + Msg, Dest) -> CMsgHdr = #{level => ip, type => tos, data => reliability}, CMsgHdrs = [CMsgHdr], MsgHdr = #{addr => Dest, iov => [Msg], ctrl => CMsgHdrs}, %% ok = socket:setopt(Sock, otp, debug, true), - Res = socket:sendmsg(Sock, MsgHdr), + Res = socket:sendmsg(Sock, MsgHdr, Timeout), %% ok = socket:setopt(Sock, otp, debug, false), Res; -send(#handler{socket = Sock, type = dgram}, Msg, Dest) -> - socket:sendto(Sock, Msg, Dest). +send(#handler{socket = Sock, type = dgram, stimeout = Timeout}, Msg, Dest) -> + socket:sendto(Sock, Msg, Dest, Timeout). %% filler() -> %% list_to_binary(lists:duplicate(2048, " FILLER ")). -- cgit v1.2.3 From e3ace52f8e7d893d170ff5eb60d22c27ab9ec1e5 Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Thu, 20 Sep 2018 17:42:03 +0200 Subject: [socket-nif] Various fixes related to FreeBSD environment --- erts/emulator/nifs/common/socket_nif.c | 61 +++++++++++++++++++++++++++------ erts/emulator/nifs/common/socket_util.c | 4 +++ 2 files changed, 55 insertions(+), 10 deletions(-) diff --git a/erts/emulator/nifs/common/socket_nif.c b/erts/emulator/nifs/common/socket_nif.c index 5fca0eb58b..c98ab1e514 100644 --- a/erts/emulator/nifs/common/socket_nif.c +++ b/erts/emulator/nifs/common/socket_nif.c @@ -362,6 +362,15 @@ static void (*esock_sctp_freepaddrs)(struct sockaddr *addrs) = NULL; /* *** Misc macros and defines *** */ +/* This macro exist on some (linux) platforms */ +#if !defined(IPTOS_TOS_MASK) +#define IPTOS_TOS_MASK 0x1E +#endif +#if !defined(IPTOS_TOS) +#define IPTOS_TOS(tos) ((tos)&IPTOS_TOS_MASK) +#endif + + #if defined(TCP_CA_NAME_MAX) #define SOCKET_OPT_TCP_CONGESTION_NAME_MAX TCP_CA_NAME_MAX #else @@ -1158,7 +1167,7 @@ static ERL_NIF_TERM nsetopt_lvl_ip_minttl(ErlNifEnv* env, SocketDescriptor* descP, ERL_NIF_TERM eVal); #endif -#if defined(IP_MSFILTER) +#if defined(IP_MSFILTER) && defined(IP_MSFILTER_SIZE) static ERL_NIF_TERM nsetopt_lvl_ip_msfilter(ErlNifEnv* env, SocketDescriptor* descP, ERL_NIF_TERM eVal); @@ -1641,7 +1650,7 @@ static ERL_NIF_TERM ngetopt_lvl_ip_nodefrag(ErlNifEnv* env, static ERL_NIF_TERM ngetopt_lvl_ip_pktinfo(ErlNifEnv* env, SocketDescriptor* descP); #endif -#if defined(IP_RECVDSTADDRS) +#if defined(IP_RECVDSTADDR) static ERL_NIF_TERM ngetopt_lvl_ip_recvdstaddr(ErlNifEnv* env, SocketDescriptor* descP); #endif @@ -5461,7 +5470,7 @@ ERL_NIF_TERM nsetopt_lvl_ip(ErlNifEnv* env, break; #endif -#if defined(IP_MSFILTER) +#if defined(IP_MSFILTER) && defined(IP_MSFILTER_SIZE) case SOCKET_OPT_IP_MSFILTER: result = nsetopt_lvl_ip_msfilter(env, descP, eVal); break; @@ -5777,7 +5786,7 @@ ERL_NIF_TERM nsetopt_lvl_ip_minttl(ErlNifEnv* env, * * The value can be *either* the atom 'null' or a map of type ip_msfilter(). */ -#if defined(IP_MSFILTER) +#if defined(IP_MSFILTER) && defined(IP_MSFILTER_SIZE) static ERL_NIF_TERM nsetopt_lvl_ip_msfilter(ErlNifEnv* env, SocketDescriptor* descP, @@ -7219,7 +7228,7 @@ ERL_NIF_TERM nsetopt_lvl_sctp_associnfo(ErlNifEnv* env, SSDBG( descP, ("SOCKET", "nsetopt_lvl_sctp_associnfo -> decode attributes\r\n") ); - if (!GET_INT(env, eAssocId, &assocParams.sasoc_assoc_id)) + if (!GET_UINT(env, eAssocId, &assocParams.sasoc_assoc_id)) return esock_make_error(env, esock_atom_einval); /* @@ -7547,7 +7556,7 @@ ERL_NIF_TERM nsetopt_lvl_sctp_rtoinfo(ErlNifEnv* env, SSDBG( descP, ("SOCKET", "nsetopt_lvl_sctp_rtoinfo -> decode attributes\r\n") ); - if (!GET_INT(env, eAssocId, &rtoInfo.srto_assoc_id)) + if (!GET_UINT(env, eAssocId, &rtoInfo.srto_assoc_id)) return esock_make_error(env, esock_atom_einval); if (!GET_UINT(env, eInitial, &rtoInfo.srto_initial)) @@ -7744,7 +7753,7 @@ BOOLEAN_T elevel2level(BOOLEAN_T isEncoded, #if defined(SOL_IP) *level = SOL_IP; #else - *level = IPROTO_IP; + *level = IPPROTO_IP; #endif result = TRUE; break; @@ -11927,7 +11936,7 @@ char* encode_cmsghdr_data_ip(ErlNifEnv* env, size_t dataLen, ERL_NIF_TERM* eCMsgHdrData) { - char* xres; + char* xres = NULL; switch (type) { #if defined(IP_TOS) @@ -12022,7 +12031,7 @@ char* encode_cmsghdr_data_ip(ErlNifEnv* env, break; } - return NULL; + return xres; } @@ -12111,20 +12120,30 @@ char* encode_msghdr_flags(ErlNifEnv* env, } else { SocketTArray ta = TARRAY_CREATE(10); // Just to be on the safe side +#if defined(MSG_EOR) if ((msgFlags & MSG_EOR) == MSG_EOR) TARRAY_ADD(ta, esock_atom_eor); - +#endif + +#if defined(MSG_TRUNC) if ((msgFlags & MSG_TRUNC) == MSG_TRUNC) TARRAY_ADD(ta, esock_atom_trunc); +#endif +#if defined(MSG_CTRUNC) if ((msgFlags & MSG_CTRUNC) == MSG_CTRUNC) TARRAY_ADD(ta, esock_atom_ctrunc); +#endif +#if defined(MSG_OOB) if ((msgFlags & MSG_OOB) == MSG_OOB) TARRAY_ADD(ta, esock_atom_oob); +#endif +#if defined(MSG_ERRQUEUE) if ((msgFlags & MSG_ERRQUEUE) == MSG_ERRQUEUE) TARRAY_ADD(ta, esock_atom_errqueue); +#endif SSDBG( descP, ("SOCKET", "esock_encode_cmsghdrs -> flags processed when" @@ -12905,35 +12924,47 @@ BOOLEAN_T esendflags2sendflags(unsigned int eflags, int* flags) for (ef = SOCKET_SEND_FLAG_LOW; ef <= SOCKET_SEND_FLAG_HIGH; ef++) { switch (ef) { +#if defined(MSG_CONFIRM) case SOCKET_SEND_FLAG_CONFIRM: if ((1 << SOCKET_SEND_FLAG_CONFIRM) & eflags) tmp |= MSG_CONFIRM; break; +#endif +#if defined(MSG_DONTROUTE) case SOCKET_SEND_FLAG_DONTROUTE: if ((1 << SOCKET_SEND_FLAG_DONTROUTE) & eflags) tmp |= MSG_DONTROUTE; break; +#endif +#if defined(MSG_EOR) case SOCKET_SEND_FLAG_EOR: if ((1 << SOCKET_SEND_FLAG_EOR) & eflags) tmp |= MSG_EOR; break; +#endif +#if defined(MSG_MORE) case SOCKET_SEND_FLAG_MORE: if ((1 << SOCKET_SEND_FLAG_MORE) & eflags) tmp |= MSG_MORE; break; +#endif +#if defined(MSG_NOSIGNAL) case SOCKET_SEND_FLAG_NOSIGNAL: if ((1 << SOCKET_SEND_FLAG_NOSIGNAL) & eflags) tmp |= MSG_NOSIGNAL; break; +#endif +#if defined(MSG_OOB) case SOCKET_SEND_FLAG_OOB: if ((1 << SOCKET_SEND_FLAG_OOB) & eflags) tmp |= MSG_OOB; break; +#endif default: return FALSE; @@ -12969,20 +13000,26 @@ BOOLEAN_T erecvflags2recvflags(unsigned int eflags, int* flags) "\r\n", ef, tmp) ); switch (ef) { +#if defined(MSG_CMSG_CLOEXEC) case SOCKET_RECV_FLAG_CMSG_CLOEXEC: if ((1 << SOCKET_RECV_FLAG_CMSG_CLOEXEC) & eflags) tmp |= MSG_CMSG_CLOEXEC; break; +#endif +#if defined(MSG_ERRQUEUE) case SOCKET_RECV_FLAG_ERRQUEUE: if ((1 << SOCKET_RECV_FLAG_ERRQUEUE) & eflags) tmp |= MSG_ERRQUEUE; break; +#endif +#if defined(MSG_OOB) case SOCKET_RECV_FLAG_OOB: if ((1 << SOCKET_RECV_FLAG_OOB) & eflags) tmp |= MSG_OOB; break; +#endif /* * @@ -12991,15 +13028,19 @@ BOOLEAN_T erecvflags2recvflags(unsigned int eflags, int* flags) * * */ +#if defined(MSG_PEEK) case SOCKET_RECV_FLAG_PEEK: if ((1 << SOCKET_RECV_FLAG_PEEK) & eflags) tmp |= MSG_PEEK; break; +#endif +#if defined(MSG_TRUNC) case SOCKET_RECV_FLAG_TRUNC: if ((1 << SOCKET_RECV_FLAG_TRUNC) & eflags) tmp |= MSG_TRUNC; break; +#endif default: return FALSE; diff --git a/erts/emulator/nifs/common/socket_util.c b/erts/emulator/nifs/common/socket_util.c index a73b40cd29..8bb725fb5b 100644 --- a/erts/emulator/nifs/common/socket_util.c +++ b/erts/emulator/nifs/common/socket_util.c @@ -1238,7 +1238,11 @@ char* esock_decode_protocol(ErlNifEnv* env, *proto = IPPROTO_IP; #endif } else if (COMPARE(esock_atom_ipv6, eProto) == 0) { +#if defined(SOL_IPV6) *proto = SOL_IPV6; +#else + *proto = IPPROTO_IPV6; +#endif } else if (COMPARE(esock_atom_tcp, eProto) == 0) { *proto = IPPROTO_TCP; } else if (COMPARE(esock_atom_udp, eProto) == 0) { -- cgit v1.2.3 From 37704e1eccc60e2077be13ec0a4c3344d9c04b30 Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Fri, 21 Sep 2018 12:00:05 +0200 Subject: [socket-nif] More constant if-def and header include moving --- erts/emulator/nifs/common/net_nif.c | 40 +++++++++++++++++++++++++++++++++- erts/emulator/nifs/common/socket_nif.c | 17 +-------------- 2 files changed, 40 insertions(+), 17 deletions(-) diff --git a/erts/emulator/nifs/common/net_nif.c b/erts/emulator/nifs/common/net_nif.c index 9905d99a04..f7eeee45ac 100644 --- a/erts/emulator/nifs/common/net_nif.c +++ b/erts/emulator/nifs/common/net_nif.c @@ -47,7 +47,6 @@ #include #include #include -#include #include #ifdef HAVE_UNISTD_H @@ -121,6 +120,7 @@ #include #endif +#include #include #include #include @@ -698,37 +698,53 @@ ERL_NIF_TERM ngetnameinfo(ErlNifEnv* env, } break; +#if defined(EAI_AGAIN) case EAI_AGAIN: result = esock_make_error(env, esock_atom_eagain); break; +#endif +#if defined(EAI_BADFLAGS) case EAI_BADFLAGS: result = esock_make_error(env, atom_ebadflags); break; +#endif +#if defined(EAI_FAIL) case EAI_FAIL: result = esock_make_error(env, atom_efail); break; +#endif +#if defined(EAI_FAMILY) case EAI_FAMILY: result = esock_make_error(env, atom_efamily); break; +#endif +#if defined(EAI_MEMORY) case EAI_MEMORY: result = esock_make_error(env, atom_emem); break; +#endif +#if defined(EAI_NONAME) case EAI_NONAME: result = esock_make_error(env, atom_enoname); break; +#endif +#if defined(EAI_OVERFLOW) case EAI_OVERFLOW: result = esock_make_error(env, atom_eoverflow); break; +#endif +#if defined(EAI_SYSTEM) case EAI_SYSTEM: result = esock_make_error_errno(env, get_errno()); break; +#endif default: result = esock_make_error(env, esock_atom_einval); @@ -842,49 +858,71 @@ ERL_NIF_TERM ngetaddrinfo(ErlNifEnv* env, } break; +#if defined(EAI_ADDRFAMILY) case EAI_ADDRFAMILY: result = esock_make_error(env, atom_eaddrfamily); break; +#endif +#if defined(EAI_AGAIN) case EAI_AGAIN: result = esock_make_error(env, esock_atom_eagain); break; +#endif +#if defined(EAI_BADFLAGS) case EAI_BADFLAGS: result = esock_make_error(env, atom_ebadflags); break; +#endif +#if defined(EAI_FAIL) case EAI_FAIL: result = esock_make_error(env, atom_efail); break; +#endif +#if defined(EAI_FAMILY) case EAI_FAMILY: result = esock_make_error(env, atom_efamily); break; +#endif +#if defined(EAI_MEMORY) case EAI_MEMORY: result = esock_make_error(env, atom_emem); break; +#endif +#if defined(EAI_NODATA) case EAI_NODATA: result = esock_make_error(env, atom_enodata); break; +#endif +#if defined(EAI_NONAME) case EAI_NONAME: result = esock_make_error(env, atom_enoname); break; +#endif +#if defined(EAI_SERVICE) case EAI_SERVICE: result = esock_make_error(env, atom_eservice); break; +#endif +#if defined(EAI_SOCKTYPE) case EAI_SOCKTYPE: result = esock_make_error(env, atom_esocktype); break; +#endif +#if defined(EAI_SYSTEM) case EAI_SYSTEM: result = esock_make_error(env, atom_esystem); break; +#endif default: result = esock_make_error(env, esock_atom_einval); diff --git a/erts/emulator/nifs/common/socket_nif.c b/erts/emulator/nifs/common/socket_nif.c index c98ab1e514..6564c3c82f 100644 --- a/erts/emulator/nifs/common/socket_nif.c +++ b/erts/emulator/nifs/common/socket_nif.c @@ -25,21 +25,6 @@ #define STATIC_ERLANG_NIF 1 -/* #include */ -/* #include */ -/* #include */ -/* #include */ -/* #include */ -/* #include */ -/* #include */ -/* #include */ -/* #include */ -/* #include */ -/* #include */ -/* #include */ -/* #include */ -/* #include */ - #ifdef HAVE_CONFIG_H #include "config.h" @@ -61,7 +46,6 @@ #include #include #include -#include #include #ifdef HAVE_UNISTD_H @@ -135,6 +119,7 @@ #include #endif +#include #include #include #include -- cgit v1.2.3 From 0474c879d44f3fe5696f8c867ffb283ccab43949 Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Fri, 21 Sep 2018 12:00:59 +0200 Subject: [socket-nif] Add x86_64 FreeBSD --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index fee8cba0c7..4bd743f83a 100644 --- a/.gitignore +++ b/.gitignore @@ -37,6 +37,7 @@ x86_64-apple-darwin[0-9]*.[0-9]*.[0-9]* sparc-sun-solaris[0-9]*.[0-9]* i386-pc-solaris[0-9]*.[0-9]* i386-unknown-freebsd[0-9]*.[0-9]* +x86_64-unknown-freebsd[0-9]*.[0-9]* tile-tilera-linux-gnu powerpc-unknown-linux-gnu aarch64-unknown-linux-gnu -- cgit v1.2.3 From ceb5ac40d519cc475c6c3252a2e4f87cdcd5b6f0 Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Fri, 21 Sep 2018 12:11:21 +0200 Subject: [socket-nif] Moved include file (FreeBSD) --- erts/emulator/nifs/common/socket_nif.c | 28 ++++++++++------------------ 1 file changed, 10 insertions(+), 18 deletions(-) diff --git a/erts/emulator/nifs/common/socket_nif.c b/erts/emulator/nifs/common/socket_nif.c index 04c3609b32..a3281f040d 100644 --- a/erts/emulator/nifs/common/socket_nif.c +++ b/erts/emulator/nifs/common/socket_nif.c @@ -25,22 +25,6 @@ #define STATIC_ERLANG_NIF 1 -/* #include */ -/* #include */ -/* #include */ -/* #include */ -/* #include */ -/* #include */ -/* #include */ -/* #include */ -/* #include */ -/* #include */ -/* #include */ -/* #include */ -/* #include */ -/* #include */ - - #ifdef HAVE_CONFIG_H #include "config.h" #endif @@ -61,7 +45,6 @@ #include #include #include -#include #include #ifdef HAVE_UNISTD_H @@ -120,6 +103,14 @@ #undef WANT_NONBLOCKING #include "sys.h" + + + +/* AND HERE WE MAY HAVE A BUNCH OF DEFINES....SEE INET DRIVER.... */ + + + + #else /* !__WIN32__ */ #include @@ -135,6 +126,7 @@ #include #endif +#include #include #include #include @@ -312,7 +304,7 @@ static void (*esock_sctp_freepaddrs)(struct sockaddr *addrs) = NULL; #endif #include "sys.h" -#endif +#endif /* !__WIN32__ */ #include -- cgit v1.2.3 From 91ffcbf2bee63cd5314132edee56825973a62f72 Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Tue, 25 Sep 2018 12:49:57 +0200 Subject: [socket-nif|test] Add first preliminary socket test suite Add a proper but very basic test suite. The only (6) test cases now are very basic (open-close and send-receive). OTP-14831 --- lib/kernel/test/Makefile | 4 + lib/kernel/test/socket_SUITE.erl | 426 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 430 insertions(+) create mode 100644 lib/kernel/test/socket_SUITE.erl diff --git a/lib/kernel/test/Makefile b/lib/kernel/test/Makefile index 051fac25af..28edf4889b 100644 --- a/lib/kernel/test/Makefile +++ b/lib/kernel/test/Makefile @@ -96,6 +96,7 @@ MODULES= \ standard_error_SUITE \ multi_load_SUITE \ zzz_SUITE \ + socket_SUITE \ $(SOCKET_MODULES) APP_FILES = \ @@ -134,6 +135,7 @@ ERL_COMPILE_FLAGS += EBIN = . SOCKET_TARGETS = $(SOCKET_MODULES:%=$(EBIN)/%.$(EMULATOR)) +TARGETS = $(MODULES:%=$(EBIN)/%.$(EMULATOR)) # ---------------------------------------------------- @@ -156,6 +158,8 @@ clean: docs: +targets: $(TARGETS) + socket: $(SOCKET_TARGETS) diff --git a/lib/kernel/test/socket_SUITE.erl b/lib/kernel/test/socket_SUITE.erl new file mode 100644 index 0000000000..1bf12693fe --- /dev/null +++ b/lib/kernel/test/socket_SUITE.erl @@ -0,0 +1,426 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2018-2018. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% + +-module(socket_SUITE). + +-include_lib("common_test/include/ct.hrl"). +-include_lib("common_test/include/ct_event.hrl"). + +%% Suite exports +-export([suite/0, all/0, groups/0]). + +%% Test cases +-export([ + %% Basic + api_b_open_and_close_udp4/1, + api_b_open_and_close_tcp4/1, + api_b_sendto_and_recvfrom_udp4/1, + api_b_sendmsg_and_recvmsg_udp4/1, + api_b_send_and_recv_tcp4/1, + api_b_sendmsg_and_recvmsg_tcp4/1 + + %% Tickets + ]). + +%% Internal exports +%% -export([]). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +-define(BASIC_REQ, <<"hejsan">>). +-define(BASIC_REP, <<"hoppsan">>). + +-define(FAIL(R), exit(R)). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +suite() -> + [{ct_hooks,[ts_install_cth]}, + {timetrap,{minutes,1}}]. + +all() -> + [ + {group, api} + %% {group, tickets} + ]. + +groups() -> + [{api, [], api_cases()}, + {api_basic, [], api_basic_cases()} + %% {tickets, [], ticket_cases()} + ]. + +api_cases() -> + [ + {group, api_basic} + ]. + +api_basic_cases() -> + [api_b_open_and_close_udp4, + api_b_open_and_close_tcp4, + api_b_sendto_and_recvfrom_udp4, + api_b_sendmsg_and_recvmsg_udp4, + api_b_send_and_recv_tcp4, + api_b_sendmsg_and_recvmsg_tcp4 + ]. + +%% ticket_cases() -> +%% []. + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Basically open (create) and close an IPv4 UDP (dgram) socket. +%% With some extra checks... +api_b_open_and_close_udp4(suite) -> + []; +api_b_open_and_close_udp4(doc) -> + []; +api_b_open_and_close_udp4(_Config) when is_list(_Config) -> + tc_begin(api_b_open_and_close_udp4), + ok = api_b_open_and_close(inet, dgram, udp), + tc_end(). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Basically open (create) and close an IPv4 TCP (stream) socket. +%% With some extra checks... +api_b_open_and_close_tcp4(suite) -> + []; +api_b_open_and_close_tcp4(doc) -> + []; +api_b_open_and_close_tcp4(_Config) when is_list(_Config) -> + tc_begin(api_b_open_and_close_tcp4), + ok = api_b_open_and_close(inet, stream, tcp), + tc_end(). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +api_b_open_and_close(Domain, Type, Proto) -> + Socket = case socket:open(Domain, Type, Proto) of + {ok, S} -> + S; + {error, Reason} -> + ?FAIL({open, Reason}) + end, + {ok, Domain} = socket:getopt(Socket, socket, domain), + {ok, Type} = socket:getopt(Socket, socket, type), + {ok, Proto} = socket:getopt(Socket, socket, protocol), + Self = self(), + {ok, Self} = socket:getopt(Socket, otp, controlling_process), + ok = socket:close(Socket), + ok. + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Basically send and receive on an IPv4 UDP (dgram) socket using +%% sendto and recvfrom.. +api_b_sendto_and_recvfrom_udp4(suite) -> + []; +api_b_sendto_and_recvfrom_udp4(doc) -> + []; +api_b_sendto_and_recvfrom_udp4(_Config) when is_list(_Config) -> + tc_begin(api_b_sendto_and_recvfrom_udp4), + Send = fun(Sock, Data, Dest) -> + socket:sendto(Sock, Data, Dest) + end, + Recv = fun(Sock) -> + socket:recvfrom(Sock) + end, + ok = api_b_send_and_recv_udp(inet, Send, Recv), + tc_end(). + + +%% Basically send and receive on an IPv4 UDP (dgram) socket +%% using sendmsg and recvmsg. +api_b_sendmsg_and_recvmsg_udp4(suite) -> + []; +api_b_sendmsg_and_recvmsg_udp4(doc) -> + []; +api_b_sendmsg_and_recvmsg_udp4(_Config) when is_list(_Config) -> + tc_begin(api_b_sendmsg_and_recvmsg_udp4), + Send = fun(Sock, Data, Dest) -> + %% CMsgHdr = #{level => ip, type => tos, data => reliability}, + %% CMsgHdrs = [CMsgHdr], + MsgHdr = #{addr => Dest, + %% ctrl => CMsgHdrs, + iov => [Data]}, + socket:sendmsg(Sock, MsgHdr) + end, + Recv = fun(Sock) -> + case socket:recvmsg(Sock) of + {ok, #{addr := Source, + iov := [Data]}} -> + {ok, {Source, Data}}; + {error, _} = ERROR -> + ERROR + end + end, + ok = api_b_send_and_recv_udp(inet, Send, Recv), + tc_end(). + + +api_b_send_and_recv_udp(Domain, Send, Recv) -> + SockSrc = case socket:open(Domain, dgram, udp) of + {ok, S1} -> + S1; + {error, OR1} -> + ?FAIL({open, src, OR1}) + end, + LAddr = which_local_addr(Domain), + LSA = #{family => Domain, addr => LAddr}, + case socket:bind(SockSrc, LSA) of + {ok, _} -> + ok; + {error, BR1} -> + ?FAIL({bind, src, BR1}) + end, + SockDst = case socket:open(Domain, dgram, udp) of + {ok, S2} -> + S2; + {error, OR2} -> + ?FAIL({open, dst, OR2}) + end, + case socket:bind(SockDst, LSA) of + {ok, _} -> + ok; + {error, BR2} -> + ?FAIL({bind, dst, BR2}) + end, + Dst = case socket:sockname(SockDst) of + {ok, SA} -> + SA; + {ok, SNR} -> + ?FAIL({sockname, dst, SNR}) + end, + ok = Send(SockSrc, ?BASIC_REQ, Dst), + {ok, {Src, ?BASIC_REQ}} = Recv(SockDst), + ok = Send(SockDst, ?BASIC_REP, Src), + {ok, {Dst, ?BASIC_REP}} = Recv(SockSrc), + socket:close(SockSrc), + socket:close(SockDst), + ok. + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Basically send and receive using the "common" functions (send and recv) +%% on an IPv4 TCP (stream) socket. +api_b_send_and_recv_tcp4(suite) -> + []; +api_b_send_and_recv_tcp4(doc) -> + []; +api_b_send_and_recv_tcp4(_Config) when is_list(_Config) -> + tc_begin(api_b_send_and_recv_tcp4), + Send = fun(Sock, Data) -> + socket:send(Sock, Data) + end, + Recv = fun(Sock) -> + socket:recv(Sock) + end, + ok = api_b_send_and_recv_tcp(inet, Send, Recv), + tc_end(). + + +%% Basically send and receive using the msg functions (sendmsg and recvmsg) +%% on an IPv4 TCP (stream) socket. +api_b_sendmsg_and_recvmsg_tcp4(suite) -> + []; +api_b_sendmsg_and_recvmsg_tcp4(doc) -> + []; +api_b_sendmsg_and_recvmsg_tcp4(_Config) when is_list(_Config) -> + tc_begin(api_b_sendmsg_and_recvmsg_tcp4), + Send = fun(Sock, Data) -> + MsgHdr = #{iov => [Data]}, + socket:sendmsg(Sock, MsgHdr) + end, + Recv = fun(Sock) -> + case socket:recvmsg(Sock) of + {ok, #{addr := undefined, + iov := [Data]}} -> + {ok, Data}; + {error, _} = ERROR -> + ERROR + end + end, + ok = api_b_send_and_recv_tcp(inet, Send, Recv), + tc_end(). + + +api_b_send_and_recv_tcp(Domain, Send, Recv) -> + process_flag(trap_exit, true), + LAddr = which_local_addr(Domain), + LSA = #{family => Domain, addr => LAddr}, + Starter = self(), + ServerFun = fun() -> + %% Create the listen socket + ServerLSock = + case socket:open(Domain, stream, tcp) of + {ok, S1} -> + S1; + {error, ServerOR} -> + ?FAIL({server, open, ServerOR}) + end, + %% And bind it to the local address + SP = + case socket:bind(ServerLSock, LSA) of + {ok, P} -> + P; + {error, ServerBR} -> + ?FAIL({server, bind, ServerBR}) + end, + %% Listen for connecting clients + case socket:listen(ServerLSock) of + ok -> + ok; + {error, ServerLR} -> + ?FAIL({server, listen, ServerLR}) + end, + %% We are ready + Starter ! {self(), {ok, SP}}, + %% Accept connections + ServerSock = + case socket:accept(ServerLSock) of + {ok, Sock} -> + Sock; + {error, ServerAR} -> + ?FAIL({server, accept, ServerAR}) + end, + %% Wait for a message + case Recv(ServerSock) of + {ok, ?BASIC_REQ} -> + ok; + {error, ServerRR} -> + ?FAIL({server, recv, ServerRR}) + end, + %% Send the reply + case Send(ServerSock, ?BASIC_REP) of + ok -> + ok; + {error, ServerSR} -> + ?FAIL({server, send, ServerSR}) + end, + %% Close the sockets + socket:close(ServerSock), + socket:close(ServerLSock), + %% We are done + exit(normal) + end, + Server = spawn_link(ServerFun), + ServerPort = + receive + {Server, {ok, P}} -> + P; + {'EXIT', Server, ServerStartReason} -> + ?FAIL({server, start, ServerStartReason}) + end, + ClientSock = + case socket:open(Domain, stream, tcp) of + {ok, S2} -> + S2; + {error, ClientOR} -> + ?FAIL({client, open, ClientOR}) + end, + case socket:bind(ClientSock, LSA) of + {ok, _} -> + ok; + {error, ClientBR} -> + ?FAIL({client, bind, ClientBR}) + end, + case socket:connect(ClientSock, LSA#{port => ServerPort}) of + ok -> + ok; + {error, ClientCR} -> + ?FAIL({client, connect, ClientCR}) + end, + case Send(ClientSock, ?BASIC_REQ) of + ok -> + ok; + {error, ClientSR} -> + ?FAIL({client, send, ClientSR}) + end, + case Recv(ClientSock) of + {ok, ?BASIC_REP} -> + ok; + {ok, Msg} -> + ?FAIL({client, recv, {unexpected, Msg}}) + end, + receive + {'EXIT', Server, normal} -> + ok; + {'EXIT', Server, ServerStopReason} -> + ?FAIL({server, stop, ServerStopReason}) + end, + socket:close(ClientSock), + ok. + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% This gets the local address (not 127.0...) +%% We should really implement this using the (new) net module, +%% but until that gets the necessary functionality... +which_local_addr(Domain) -> + case inet:getifaddrs() of + {ok, IFL} -> + which_addr(Domain, IFL); + {error, Reason} -> + ?FAIL({inet, getifaddrs, Reason}) + end. + +which_addr(_Domain, []) -> + ?FAIL(no_address); +which_addr(Domain, [{Name, IFO}|_IFL]) when (Name =/= "lo") -> + which_addr2(Domain, IFO); +which_addr(Domain, [_|IFL]) -> + which_addr(Domain, IFL). + +which_addr2(_Domain, []) -> + ?FAIL(no_address); +which_addr2(inet = _Domain, [{addr, Addr}|_IFO]) when (size(Addr) =:= 4) -> + Addr; +which_addr2(inet6 = _Domain, [{addr, Addr}|_IFO]) when (size(Addr) =:= 8) -> + Addr; +which_addr2(Domain, [_|IFO]) -> + which_addr2(Domain, IFO). + + +tc_begin(TC) -> + put(tc_name, TC), + p("begin"). + +tc_end() -> + ok. + +p(F) -> + p(F, []). + +p(F, A) -> + io:format(user, "*** ~w " ++ F ++ "~n", [get(tc_name)|A]). + -- cgit v1.2.3 From 742a210ace9625646386eb71b8d33e9938eeaec1 Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Tue, 25 Sep 2018 16:34:55 +0200 Subject: [socket-nif] Add proper recv timeout handling Added proper recv timeout handling. Made use of the enif_select(mode = cancel) feature. Each time a timeout expires, the "active" recv (the surrent reader select) has to be cancelled. Not yet tested...something for the new test suite... Also, added support for getopt(controlling_pprocess) that, for some reason, was not yet implemented. OTP-14831 --- erts/emulator/nifs/common/socket_int.h | 1 + erts/emulator/nifs/common/socket_nif.c | 803 +++++++++++++++++++++++++++++---- lib/kernel/test/socket_server.erl | 4 +- 3 files changed, 727 insertions(+), 81 deletions(-) diff --git a/erts/emulator/nifs/common/socket_int.h b/erts/emulator/nifs/common/socket_int.h index 3595c483d7..c3595e495d 100644 --- a/erts/emulator/nifs/common/socket_int.h +++ b/erts/emulator/nifs/common/socket_int.h @@ -184,6 +184,7 @@ extern ERL_NIF_TERM esock_atom_einval; #define MKLA(E,A,L) enif_make_list_from_array((E), (A), (L)) #define MKEL(E) enif_make_list((E), 0) #define MKMA(E,KA,VA,L,M) enif_make_map_from_arrays((E), (KA), (VA), (L), (M)) +#define MKPID(E, P) enif_make_pid((E), (P)) #define MKREF(E) enif_make_ref((E)) #define MKS(E,S) enif_make_string((E), (S), ERL_NIF_LATIN1) #define MKSL(E,S,L) enif_make_string_len((E), (S), (L), ERL_NIF_LATIN1) diff --git a/erts/emulator/nifs/common/socket_nif.c b/erts/emulator/nifs/common/socket_nif.c index c48d6eab00..a6940f788c 100644 --- a/erts/emulator/nifs/common/socket_nif.c +++ b/erts/emulator/nifs/common/socket_nif.c @@ -1490,6 +1490,8 @@ static ERL_NIF_TERM ngetopt_otp_debug(ErlNifEnv* env, SocketDescriptor* descP); static ERL_NIF_TERM ngetopt_otp_iow(ErlNifEnv* env, SocketDescriptor* descP); +static ERL_NIF_TERM ngetopt_otp_ctrl_proc(ErlNifEnv* env, + SocketDescriptor* descP); static ERL_NIF_TERM ngetopt_native(ErlNifEnv* env, SocketDescriptor* descP, int level, @@ -1849,6 +1851,11 @@ static ERL_NIF_TERM ncancel_send_waiting(ErlNifEnv* env, static ERL_NIF_TERM ncancel_recv(ErlNifEnv* env, SocketDescriptor* descP, ERL_NIF_TERM opRef); +static ERL_NIF_TERM ncancel_recv_current(ErlNifEnv* env, + SocketDescriptor* descP); +static ERL_NIF_TERM ncancel_recv_waiting(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM opRef); static ERL_NIF_TERM ncancel_read_select(ErlNifEnv* env, SocketDescriptor* descP, ERL_NIF_TERM opRef); @@ -1911,6 +1918,18 @@ static ERL_NIF_TERM send_check_result(ErlNifEnv* env, ssize_t dataSize, int saveErrno, ERL_NIF_TERM sendRef); +static BOOLEAN_T recv_check_reader(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM ref, + ERL_NIF_TERM* checkResult); +static char* recv_init_current_reader(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM ref); +static ERL_NIF_TERM recv_update_current_reader(ErlNifEnv* env, + SocketDescriptor* descP); +static void recv_error_current_reader(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM reason); static ERL_NIF_TERM recv_check_result(ErlNifEnv* env, SocketDescriptor* descP, int read, @@ -2150,6 +2169,22 @@ static BOOLEAN_T writer_unqueue(ErlNifEnv* env, SocketDescriptor* descP, const ErlNifPid* pid); +static BOOLEAN_T reader_search4pid(ErlNifEnv* env, + SocketDescriptor* descP, + ErlNifPid* pid); +static ERL_NIF_TERM reader_push(ErlNifEnv* env, + SocketDescriptor* descP, + ErlNifPid pid, + ERL_NIF_TERM ref); +static BOOLEAN_T reader_pop(ErlNifEnv* env, + SocketDescriptor* descP, + ErlNifPid* pid, + ErlNifMonitor* mon, + ERL_NIF_TERM* ref); +static BOOLEAN_T reader_unqueue(ErlNifEnv* env, + SocketDescriptor* descP, + const ErlNifPid* pid); + static BOOLEAN_T qsearch4pid(ErlNifEnv* env, SocketRequestQueue* q, ErlNifPid* pid); @@ -2159,7 +2194,6 @@ static SocketRequestQueueElement* qpop(SocketRequestQueue* q); static BOOLEAN_T qunqueue(ErlNifEnv* env, SocketRequestQueue* q, const ErlNifPid* pid); - /* #if defined(HAVE_SYS_UN_H) || defined(SO_BINDTODEVICE) static size_t my_strnlen(const char *s, size_t maxlen); @@ -2175,6 +2209,15 @@ static void socket_down(ErlNifEnv* env, void* obj, const ErlNifPid* pid, const ErlNifMonitor* mon); +static void socket_down_acceptor(ErlNifEnv* env, + SocketDescriptor* descP, + const ErlNifPid* pid); +static void socket_down_writer(ErlNifEnv* env, + SocketDescriptor* descP, + const ErlNifPid* pid); +static void socket_down_reader(ErlNifEnv* env, + SocketDescriptor* descP, + const ErlNifPid* pid); /* static char* send_msg_error_closed(ErlNifEnv* env, @@ -3986,6 +4029,8 @@ ERL_NIF_TERM nsendmsg(ErlNifEnv* env, if (ctrlBuf != NULL) FREE(ctrlBuf); return esock_make_error_str(env, xres); } + } else { + ctrlBufUsed = 0; } msgHdr.msg_control = ctrlBuf; msgHdr.msg_controllen = ctrlBufUsed; @@ -4168,6 +4213,7 @@ ERL_NIF_TERM nrecv(ErlNifEnv* env, { ssize_t read; ErlNifBinary buf; + ERL_NIF_TERM readerCheck; int save_errno; int bufSz = (len ? len : descP->rBufSz); @@ -4179,6 +4225,10 @@ ERL_NIF_TERM nrecv(ErlNifEnv* env, if (!descP->isReadable) return enif_make_badarg(env); + /* Check if there is already a current reader and if its us */ + if (!recv_check_reader(env, descP, recvRef, &readerCheck)) + return readerCheck; + /* Allocate a buffer: * Either as much as we want to read or (if zero (0)) use the "default" * size (what has been configured). @@ -4315,6 +4365,7 @@ ERL_NIF_TERM nrecvfrom(ErlNifEnv* env, ssize_t read; int save_errno; ErlNifBinary buf; + ERL_NIF_TERM readerCheck; int bufSz = (len ? len : descP->rBufSz); SSDBG( descP, ("SOCKET", "nrecvfrom -> entry with" @@ -4325,6 +4376,10 @@ ERL_NIF_TERM nrecvfrom(ErlNifEnv* env, if (!descP->isReadable) return enif_make_badarg(env); + /* Check if there is already a current reader and if its us */ + if (!recv_check_reader(env, descP, recvRef, &readerCheck)) + return readerCheck; + /* Allocate a buffer: * Either as much as we want to read or (if zero (0)) use the "default" * size (what has been configured). @@ -4476,6 +4531,7 @@ ERL_NIF_TERM nrecvmsg(ErlNifEnv* env, struct iovec iov[1]; // Shall we always use 1? ErlNifBinary data[1]; // Shall we always use 1? ErlNifBinary ctrl; + ERL_NIF_TERM readerCheck; SocketAddress addr; SSDBG( descP, ("SOCKET", "nrecvmsg -> entry with" @@ -4487,6 +4543,10 @@ ERL_NIF_TERM nrecvmsg(ErlNifEnv* env, if (!descP->isReadable) return enif_make_badarg(env); + /* Check if there is already a current reader and if its us */ + if (!recv_check_reader(env, descP, recvRef, &readerCheck)) + return readerCheck; + /* for (i = 0; i < sizeof(buf); i++) { if (!ALLOC_BIN(bifSz, &buf[i])) @@ -8093,7 +8153,7 @@ ERL_NIF_TERM ngetopt_otp(ErlNifEnv* env, ERL_NIF_TERM result; SSDBG( descP, - ("SOCKET", "ngetopt_opt -> entry with" + ("SOCKET", "ngetopt_otp -> entry with" "\r\n eOpt: %d" "\r\n", eOpt) ); @@ -8106,13 +8166,17 @@ ERL_NIF_TERM ngetopt_otp(ErlNifEnv* env, result = ngetopt_otp_iow(env, descP); break; + case SOCKET_OPT_OTP_CTRL_PROC: + result = ngetopt_otp_ctrl_proc(env, descP); + break; + default: result = esock_make_error(env, esock_atom_einval); break; } SSDBG( descP, - ("SOCKET", "ngetopt_opt -> done when" + ("SOCKET", "ngetopt_otp -> done when" "\r\n result: %T" "\r\n", result) ); @@ -8144,6 +8208,18 @@ ERL_NIF_TERM ngetopt_otp_iow(ErlNifEnv* env, } +/* ngetopt_otp_ctrl_proc - Handle the OTP (level) controlling_process options + */ +static +ERL_NIF_TERM ngetopt_otp_ctrl_proc(ErlNifEnv* env, + SocketDescriptor* descP) +{ + ERL_NIF_TERM eVal = MKPID(env, &descP->ctrlPid); + + return esock_make_ok2(env, eVal); +} + + /* The option has *not* been encoded. Instead it has been provided * in "native mode" (option is provided as is). In this case it will have the @@ -10752,6 +10828,8 @@ ERL_NIF_TERM ncancel_send(ErlNifEnv* env, { ERL_NIF_TERM res; + MLOCK(descP->writeMtx); + SSDBG( descP, ("SOCKET", "ncancel_send -> entry with" "\r\n opRef: %T" @@ -10759,8 +10837,6 @@ ERL_NIF_TERM ncancel_send(ErlNifEnv* env, "\r\n", opRef, ((descP->currentWriterP == NULL) ? "without writer" : "with writer")) ); - MLOCK(descP->writeMtx); - if (descP->currentWriterP != NULL) { if (COMPARE(opRef, descP->currentWriter.ref) == 0) { res = ncancel_send_current(env, descP); @@ -10859,14 +10935,116 @@ ERL_NIF_TERM ncancel_send_waiting(ErlNifEnv* env, /* *** ncancel_recv *** * - * + * Cancel a read operation. + * Its either the current reader or one of the waiting readers. */ static ERL_NIF_TERM ncancel_recv(ErlNifEnv* env, SocketDescriptor* descP, ERL_NIF_TERM opRef) { - return esock_make_error(env, esock_atom_einval); + ERL_NIF_TERM res; + + MLOCK(descP->readMtx); + + SSDBG( descP, + ("SOCKET", "ncancel_recv -> entry with" + "\r\n opRef: %T" + "\r\n %s" + "\r\n", opRef, + ((descP->currentReaderP == NULL) ? "without reader" : "with reader")) ); + + if (descP->currentReaderP != NULL) { + if (COMPARE(opRef, descP->currentReader.ref) == 0) { + res = ncancel_recv_current(env, descP); + } else { + res = ncancel_recv_waiting(env, descP, opRef); + } + } else { + /* Or badarg? */ + res = esock_make_error(env, esock_atom_einval); + } + + MUNLOCK(descP->readMtx); + + SSDBG( descP, + ("SOCKET", "ncancel_recv -> done with result:" + "\r\n %T" + "\r\n", res) ); + + return res; +} + + +/* The current reader process has an ongoing select we first must + * cancel. Then we must re-activate the "first" (the first + * in the reader queue). + */ +static +ERL_NIF_TERM ncancel_recv_current(ErlNifEnv* env, + SocketDescriptor* descP) +{ + ERL_NIF_TERM res; + + SSDBG( descP, ("SOCKET", "ncancel_recv_current -> entry\r\n") ); + + res = ncancel_read_select(env, descP, descP->currentReader.ref); + + SSDBG( descP, ("SOCKET", "ncancel_recv_current -> cancel res: %T\r\n", res) ); + + if (reader_pop(env, descP, + &descP->currentReader.pid, + &descP->currentReader.mon, + &descP->currentReader.ref)) { + + /* There was another one */ + + SSDBG( descP, ("SOCKET", "ncancel_recv_current -> new (active) reader: " + "\r\n pid: %T" + "\r\n ref: %T" + "\r\n", + descP->currentReader.pid, + descP->currentReader.ref) ); + + SELECT(env, + descP->sock, + (ERL_NIF_SELECT_READ), + descP, &descP->currentReader.pid, descP->currentReader.ref); + + } else { + SSDBG( descP, ("SOCKET", "ncancel_recv_current -> no more readers\r\n") ); + descP->currentReaderP = NULL; + } + + SSDBG( descP, ("SOCKET", "ncancel_recv_current -> done with result:" + "\r\n %T" + "\r\n", res) ); + + return res; +} + + +/* These processes have not performed a select, so we can simply + * remove them from the reader queue. + */ +static +ERL_NIF_TERM ncancel_recv_waiting(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM opRef) +{ + ErlNifPid caller; + + if (enif_self(env, &caller) == NULL) + return esock_make_error(env, atom_exself); + + /* unqueue request from (reader) queue */ + + if (reader_unqueue(env, descP, &caller)) { + return esock_atom_ok; + } else { + /* Race? */ + return esock_make_error(env, esock_atom_not_found); + } } @@ -10958,7 +11136,8 @@ BOOLEAN_T send_check_writer(ErlNifEnv* env, SSDBG( descP, ("SOCKET", - "nsend -> queue (push) result: %T\r\n", checkResult) ); + "send_check_writer -> queue (push) result: %T\r\n", + checkResult) ); return FALSE; @@ -11005,7 +11184,8 @@ ERL_NIF_TERM send_check_result(ErlNifEnv* env, cnt_inc(&descP->writePkgCnt, 1); cnt_inc(&descP->writeByteCnt, written); - DEMONP(env, descP, &descP->currentWriter.mon); + if (descP->currentWriterP != NULL) + DEMONP(env, descP, &descP->currentWriter.mon); SSDBG( descP, ("SOCKET", "send_check_result -> " @@ -11058,11 +11238,15 @@ ERL_NIF_TERM send_check_result(ErlNifEnv* env, res = esock_make_error_errno(env, saveErrno); - while (writer_pop(env, descP, &pid, &mon, &ref)) { - SSDBG( descP, - ("SOCKET", "send_check_result -> abort %T\r\n", pid) ); - send_msg_nif_abort(env, ref, res, &pid); - DEMONP(env, descP, &mon); + if (descP->currentWriterP != NULL) { + DEMONP(env, descP, &descP->currentWriter.mon); + + while (writer_pop(env, descP, &pid, &mon, &ref)) { + SSDBG( descP, + ("SOCKET", "send_check_result -> abort %T\r\n", pid) ); + send_msg_nif_abort(env, ref, res, &pid); + DEMONP(env, descP, &mon); + } } return res; @@ -11087,6 +11271,20 @@ ERL_NIF_TERM send_check_result(ErlNifEnv* env, * so schedule the rest for later. */ + if (descP->currentWriterP == NULL) { + ErlNifPid caller; + + if (enif_self(env, &caller) == NULL) + return esock_make_error(env, atom_exself); + descP->currentWriter.pid = caller; + if (MONP(env, descP, + &descP->currentWriter.pid, + &descP->currentWriter.mon) > 0) + return esock_make_error(env, atom_exmon); + descP->currentWriter.ref = enif_make_copy(descP->env, sendRef); + descP->currentWriterP = &descP->currentWriter; + } + cnt_inc(&descP->writeWaits, 1); SELECT(env, descP->sock, (ERL_NIF_SELECT_WRITE), @@ -11100,6 +11298,167 @@ ERL_NIF_TERM send_check_result(ErlNifEnv* env, } + +/* *** recv_check_reader *** + * + * Checks if we have a current reader and if that is us. If not, then we must + * be made to wait for our turn. This is done by pushing us unto the reader queue. + */ +static +BOOLEAN_T recv_check_reader(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM ref, + ERL_NIF_TERM* checkResult) +{ + if (descP->currentReaderP != NULL) { + ErlNifPid caller; + + if (enif_self(env, &caller) == NULL) { + *checkResult = esock_make_error(env, atom_exself); + return FALSE; + } + + if (!compare_pids(env, &descP->currentReader.pid, &caller)) { + /* Not the "current reader", so (maybe) push onto queue */ + + SSDBG( descP, + ("SOCKET", "recv_check_reader -> not (current) reader\r\n") ); + + if (!reader_search4pid(env, descP, &caller)) + *checkResult = reader_push(env, descP, caller, ref); + else + *checkResult = esock_make_error(env, esock_atom_eagain); + + SSDBG( descP, + ("SOCKET", + "recv_check_reader -> queue (push) result: %T\r\n", + checkResult) ); + + return FALSE; + + } + + } + + *checkResult = esock_atom_ok; // Does not actually matter in this case, but ... + + return TRUE; +} + + + +/* *** recv_init_current_reader *** + * + * Initiate (maybe) the currentReader structure of the descriptor. + * Including monitoring the calling process. + */ +static +char* recv_init_current_reader(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM recvRef) +{ + if (descP->currentReaderP == NULL) { + ErlNifPid caller; + + if (enif_self(env, &caller) == NULL) + return str_exself; + + descP->currentReader.pid = caller; + if (MONP(env, descP, + &descP->currentReader.pid, + &descP->currentReader.mon) > 0) { + return str_exmon; + } + descP->currentReader.ref = enif_make_copy(descP->env, recvRef); + descP->currentReaderP = &descP->currentReader; + } + + return NULL; +} + + + +/* *** recv_update_current_reader *** + * + * Demonitors the current reader process and pop's the reader queue. + * If there is a waiting (reader) process, then it will be assigned + * as the new current reader and a new (read) select will be done. + */ + +static +ERL_NIF_TERM recv_update_current_reader(ErlNifEnv* env, + SocketDescriptor* descP) +{ + if (descP->currentReaderP != NULL) { + + DEMONP(env, descP, &descP->currentReader.mon); + + if (reader_pop(env, descP, + &descP->currentReader.pid, + &descP->currentReader.mon, + &descP->currentReader.ref)) { + + /* There was another one */ + + SSDBG( descP, + ("SOCKET", "recv_update_current_reader -> new (active) reader: " + "\r\n pid: %T" + "\r\n ref: %T" + "\r\n", + descP->currentReader.pid, + descP->currentReader.ref) ); + + SELECT(env, + descP->sock, + (ERL_NIF_SELECT_READ), + descP, + &descP->currentReader.pid, + descP->currentReader.ref); + + } else { + descP->currentWriterP = NULL; + } + } + + return esock_atom_ok; +} + + + +/* *** recv_error_current_reader *** + * + * Process the current reader and any waiting readers + * when a read (fatal) error has occured. + * All waiting readers will be "aborted", that is a + * nif_abort message will be sent (with reaf and reason). + */ +static +void recv_error_current_reader(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM reason) +{ + if (descP->currentReaderP != NULL) { + ErlNifPid pid; + ErlNifMonitor mon; + ERL_NIF_TERM ref; + + DEMONP(env, descP, &descP->currentReader.mon); + + while (reader_pop(env, descP, &pid, &mon, &ref)) { + SSDBG( descP, + ("SOCKET", "recv_error_current_reader -> abort %T\r\n", pid) ); + send_msg_nif_abort(env, ref, reason, &pid); + DEMONP(env, descP, &mon); + } + } +} + + + +/* *** recv_check_result *** + * + * Process the result of a call to recv. + */ static ERL_NIF_TERM recv_check_result(ErlNifEnv* env, SocketDescriptor* descP, @@ -11109,6 +11468,7 @@ ERL_NIF_TERM recv_check_result(ErlNifEnv* env, ErlNifBinary* bufP, ERL_NIF_TERM recvRef) { + char* xres; ERL_NIF_TERM data; SSDBG( descP, @@ -11129,15 +11489,20 @@ ERL_NIF_TERM recv_check_result(ErlNifEnv* env, */ if ((read == 0) && (descP->type == SOCK_STREAM)) { + ERL_NIF_TERM res = esock_make_error(env, atom_closed); /* * When a stream socket peer has performed an orderly shutdown, the return * value will be 0 (the traditional "end-of-file" return). * * *We* do never actually try to read 0 bytes from a stream socket! + * + * We must also notify any waiting readers! */ - return esock_make_error(env, atom_closed); + recv_error_current_reader(env, descP, res); + + return res; } @@ -11173,6 +11538,11 @@ ERL_NIF_TERM recv_check_result(ErlNifEnv* env, * => We choose alt 1 for now. */ + cnt_inc(&descP->readByteCnt, read); + + if ((xres = recv_init_current_reader(env, descP, recvRef)) != NULL) + return esock_make_error_str(env, xres); + data = MKBIN(env, bufP); SSDBG( descP, @@ -11188,16 +11558,24 @@ ERL_NIF_TERM recv_check_result(ErlNifEnv* env, /* * WE NEED TO INFORM ANY WAITING READERS + * + * DEMONP of the current reader! + * * */ - data = MKBIN(env, bufP); + cnt_inc(&descP->readPkgCnt, 1); + cnt_inc(&descP->readByteCnt, read); SSDBG( descP, ("SOCKET", "recv_check_result -> [%d] " "we got exactly what we could fit\r\n", toRead) ); + recv_update_current_reader(env, descP); + + data = MKBIN(env, bufP); + return esock_make_ok3(env, atom_true, data); } @@ -11207,6 +11585,7 @@ ERL_NIF_TERM recv_check_result(ErlNifEnv* env, /* +++ Error handling +++ */ if (saveErrno == ECONNRESET) { + ERL_NIF_TERM res = esock_make_error(env, atom_closed); /* +++ Oups - closed +++ */ @@ -11231,12 +11610,14 @@ ERL_NIF_TERM recv_check_result(ErlNifEnv* env, descP->closeLocal = FALSE; descP->state = SOCKET_STATE_CLOSING; + recv_error_current_reader(env, descP, res); + SELECT(env, descP->sock, (ERL_NIF_SELECT_STOP), descP, NULL, recvRef); - return esock_make_error(env, atom_closed); + return res; } else if ((saveErrno == ERRNO_BLOCK) || (saveErrno == EAGAIN)) { @@ -11248,9 +11629,14 @@ ERL_NIF_TERM recv_check_result(ErlNifEnv* env, return esock_make_error(env, esock_atom_eagain); } else { + ERL_NIF_TERM res = esock_make_error_errno(env, saveErrno); + SSDBG( descP, ("SOCKET", "recv_check_result -> [%d] errno: %d\r\n", toRead, saveErrno) ); - return esock_make_error_errno(env, saveErrno); + + recv_error_current_reader(env, descP, res); + + return res; } } else { @@ -11265,19 +11651,24 @@ ERL_NIF_TERM recv_check_result(ErlNifEnv* env, if (toRead == 0) { - /* +++ We got a chunk of data but +++ - * +++ since we did not fill the +++ - * +++ buffer, we must split it +++ - * +++ into a sub-binary. +++ + /* +++ We got it all, but since we +++ + * +++ did not fill the buffer, we +++ + * +++ must split it into a sub-binary. +++ */ SSDBG( descP, ("SOCKET", "recv_check_result -> [%d] split buffer\r\n", toRead) ); + cnt_inc(&descP->readPkgCnt, 1); + cnt_inc(&descP->readByteCnt, read); + + recv_update_current_reader(env, descP); + data = MKBIN(env, bufP); data = MKSBIN(env, data, 0, read); - SSDBG( descP, ("SOCKET", "recv_check_result -> [%d] done\r\n", toRead) ); + SSDBG( descP, + ("SOCKET", "recv_check_result -> [%d] done\r\n", toRead) ); return esock_make_ok3(env, atom_true, data); @@ -11289,6 +11680,8 @@ ERL_NIF_TERM recv_check_result(ErlNifEnv* env, SSDBG( descP, ("SOCKET", "recv_check_result -> [%d] " "only part of message - expect more\r\n", toRead) ); + cnt_inc(&descP->readByteCnt, read); + return esock_make_ok3(env, atom_false, MKBIN(env, bufP)); } } @@ -13907,10 +14300,10 @@ ERL_NIF_TERM writer_push(ErlNifEnv* env, */ static BOOLEAN_T writer_pop(ErlNifEnv* env, - SocketDescriptor* descP, - ErlNifPid* pid, - ErlNifMonitor* mon, - ERL_NIF_TERM* ref) + SocketDescriptor* descP, + ErlNifPid* pid, + ErlNifMonitor* mon, + ERL_NIF_TERM* ref) { SocketRequestQueueElement* e = qpop(&descP->writersQ); @@ -13921,7 +14314,7 @@ BOOLEAN_T writer_pop(ErlNifEnv* env, FREE(e); return TRUE; } else { - /* (acceptors) Queue was empty */ + /* (writers) Queue was empty */ // *pid = NULL; we have no null value for pids // *mon = NULL; we have no null value for monitors *ref = esock_atom_undefined; // Just in case @@ -13945,6 +14338,92 @@ BOOLEAN_T writer_unqueue(ErlNifEnv* env, +/* *** reader search for pid *** + * + * Search for a pid in the reader queue. + */ +static +BOOLEAN_T reader_search4pid(ErlNifEnv* env, + SocketDescriptor* descP, + ErlNifPid* pid) +{ + return qsearch4pid(env, &descP->readersQ, pid); +} + + +/* *** reader push *** + * + * Push an reader onto the raeder queue. + * This happens when we already have atleast one current reader. + */ +static +ERL_NIF_TERM reader_push(ErlNifEnv* env, + SocketDescriptor* descP, + ErlNifPid pid, + ERL_NIF_TERM ref) +{ + SocketRequestQueueElement* e = MALLOC(sizeof(SocketRequestQueueElement)); + SocketRequestor* reqP = &e->data; + + reqP->pid = pid; + reqP->ref = enif_make_copy(descP->env, ref); + + if (MONP(env, descP, &pid, &reqP->mon) > 0) { + FREE(reqP); + return esock_make_error(env, atom_exmon); + } + + qpush(&descP->readersQ, e); + + // THIS IS OK => MAKES THE CALLER WAIT FOR ITS TURN + return esock_make_error(env, esock_atom_eagain); +} + + +/* *** reader pop *** + * + * Pop an writer from the reader queue. + */ +static +BOOLEAN_T reader_pop(ErlNifEnv* env, + SocketDescriptor* descP, + ErlNifPid* pid, + ErlNifMonitor* mon, + ERL_NIF_TERM* ref) +{ + SocketRequestQueueElement* e = qpop(&descP->readersQ); + + if (e != NULL) { + *pid = e->data.pid; + *mon = e->data.mon; + *ref = e->data.ref; // At this point the ref has already been copied (env) + FREE(e); + return TRUE; + } else { + /* (readers) Queue was empty */ + // *pid = NULL; we have no null value for pids + // *mon = NULL; we have no null value for monitors + *ref = esock_atom_undefined; // Just in case + return FALSE; + } + +} + + +/* *** reader unqueue *** + * + * Remove an reader from the reader queue. + */ +static +BOOLEAN_T reader_unqueue(ErlNifEnv* env, + SocketDescriptor* descP, + const ErlNifPid* pid) +{ + return qunqueue(env, &descP->readersQ, pid); +} + + + static @@ -14324,77 +14803,243 @@ void socket_down(ErlNifEnv* env, "\r\n pid: %T" "\r\n", descP->sock, *pid) ); - /* Eventually we should go through the other queues also, - * the process can be one of them... - * - * Currently only the accteptors actuallu use the queues. - */ + + if (compare_pids(env, &descP->ctrlPid, pid)) { + /* We don't bother with the queue cleanup here - + * we leave it to the stop callback function. + */ - if (descP->currentAcceptorP != NULL) { + descP->state = SOCKET_STATE_CLOSING; + descP->closeLocal = TRUE; + descP->closeRef = MKREF(env); + enif_select(env, descP->sock, (ERL_NIF_SELECT_STOP), + descP, NULL, descP->closeRef); - /* - * We have acceptor(s) (atleast one) - * - * Check first if its the current acceptor, - * and if not check the queue. - */ + } else { + + /* check all operation queue(s): acceptor, writer and reader. */ + + MLOCK(descP->accMtx); + if (descP->currentAcceptorP != NULL) + socket_down_acceptor(env, descP, pid); + MUNLOCK(descP->accMtx); + + MLOCK(descP->writeMtx); + if (descP->currentWriterP != NULL) + socket_down_writer(env, descP, pid); + MUNLOCK(descP->writeMtx); + + MLOCK(descP->readMtx); + if (descP->currentReaderP != NULL) + socket_down_reader(env, descP, pid); + MUNLOCK(descP->readMtx); + + } + + SSDBG( descP, ("SOCKET", "socket_down -> done\r\n") ); + +} - if (compare_pids(env, &descP->currentAcceptor.pid, pid)) { + +/* *** socket_down_acceptor *** + * + * Check and then handle a downed acceptor process. + * + */ +static +void socket_down_acceptor(ErlNifEnv* env, + SocketDescriptor* descP, + const ErlNifPid* pid) +{ + if (compare_pids(env, &descP->currentAcceptor.pid, pid)) { + + SSDBG( descP, ("SOCKET", + "socket_down_acceptor -> " + "current acceptor - try pop the queue\r\n") ); + + if (acceptor_pop(env, descP, + &descP->currentAcceptor.pid, + &descP->currentAcceptor.mon, + &descP->currentAcceptor.ref)) { + int res; + + /* There was another one, so we will still be in accepting state */ + SSDBG( descP, ("SOCKET", - "socket_down -> " - "current acceptor - try pop the queue\r\n") ); + "socket_down_acceptor -> new (active) acceptor: " + "\r\n pid: %T" + "\r\n ref: %T" + "\r\n", + descP->currentAcceptor.pid, + descP->currentAcceptor.ref) ); - if (acceptor_pop(env, descP, - &descP->currentAcceptor.pid, - &descP->currentAcceptor.mon, - &descP->currentAcceptor.ref)) { - int res; + if ((res = enif_select(env, + descP->sock, + (ERL_NIF_SELECT_READ), + descP, + &descP->currentAcceptor.pid, + descP->currentAcceptor.ref) < 0)) { - /* There was another one, so we will still be in accepting state */ + esock_warning_msg("Failed select (%d) for new acceptor " + "after current (%T) died\r\n", + res, *pid); - SSDBG( descP, ("SOCKET", "socket_down -> new (active) acceptor: " - "\r\n pid: %T" - "\r\n ref: %T" - "\r\n", - descP->currentAcceptor.pid, - descP->currentAcceptor.ref) ); + } + + } else { + + SSDBG( descP, ("SOCKET", + "socket_down_acceptor -> no active acceptor\r\n") ); + + descP->currentAcceptorP = NULL; + descP->state = SOCKET_STATE_LISTENING; + } + + } else { + + /* Maybe unqueue one of the waiting acceptors */ + + SSDBG( descP, ("SOCKET", + "socket_down_acceptor -> " + "not current acceptor - maybe a waiting acceptor\r\n") ); + + acceptor_unqueue(env, descP, pid); + } +} + + + + +/* *** socket_down_writer *** + * + * Check and then handle a downed writer process. + * + */ +static +void socket_down_writer(ErlNifEnv* env, + SocketDescriptor* descP, + const ErlNifPid* pid) +{ + if (compare_pids(env, &descP->currentWriter.pid, pid)) { + + SSDBG( descP, ("SOCKET", + "socket_down_writer -> " + "current writer - try pop the queue\r\n") ); + + if (writer_pop(env, descP, + &descP->currentWriter.pid, + &descP->currentWriter.mon, + &descP->currentWriter.ref)) { + int res; + + /* There was another one */ + + SSDBG( descP, ("SOCKET", "socket_down_writer -> new (current) writer: " + "\r\n pid: %T" + "\r\n ref: %T" + "\r\n", + descP->currentWriter.pid, + descP->currentWriter.ref) ); + + if ((res = enif_select(env, + descP->sock, + (ERL_NIF_SELECT_WRITE), + descP, + &descP->currentWriter.pid, + descP->currentWriter.ref) < 0)) { - if ((res = enif_select(env, - descP->sock, - (ERL_NIF_SELECT_READ), - descP, - &descP->currentAcceptor.pid, - descP->currentAcceptor.ref) < 0)) { - - esock_warning_msg("Failed select (%d) for new acceptor " - "after current (%T) died\r\n", - res, *pid); - - } + esock_warning_msg("Failed select (%d) for new writer " + "after current (%T) died\r\n", + res, *pid); - } else { + } + + } else { + + SSDBG( descP, ("SOCKET", + "socket_down_writer -> no active writer\r\n") ); + + descP->currentWriterP = NULL; + } + + } else { + + /* Maybe unqueue one of the waiting writer(s) */ + + SSDBG( descP, ("SOCKET", + "socket_down_writer -> " + "not current writer - maybe a waiting writer\r\n") ); + + writer_unqueue(env, descP, pid); + } +} + + + + +/* *** socket_down_reader *** + * + * Check and then handle a downed reader process. + * + */ +static +void socket_down_reader(ErlNifEnv* env, + SocketDescriptor* descP, + const ErlNifPid* pid) +{ + if (compare_pids(env, &descP->currentReader.pid, pid)) { + + SSDBG( descP, ("SOCKET", + "socket_down_reader -> " + "current reader - try pop the queue\r\n") ); + + if (reader_pop(env, descP, + &descP->currentReader.pid, + &descP->currentReader.mon, + &descP->currentReader.ref)) { + int res; + + /* There was another one */ + + SSDBG( descP, ("SOCKET", "socket_down_reader -> new (current) reader: " + "\r\n pid: %T" + "\r\n ref: %T" + "\r\n", + descP->currentReader.pid, + descP->currentReader.ref) ); + + if ((res = enif_select(env, + descP->sock, + (ERL_NIF_SELECT_READ), + descP, + &descP->currentReader.pid, + descP->currentReader.ref) < 0)) { - SSDBG( descP, ("SOCKET", "socket_down -> no active acceptor\r\n") ); + esock_warning_msg("Failed select (%d) for new reader " + "after current (%T) died\r\n", + res, *pid); - descP->currentAcceptorP = NULL; - descP->state = SOCKET_STATE_LISTENING; } } else { - /* Maybe unqueue one of the waiting acceptors */ - SSDBG( descP, ("SOCKET", - "socket_down -> " - "not current acceptor - maybe a waiting acceptor\r\n") ); + "socket_down_reader -> no active reader\r\n") ); - acceptor_unqueue(env, descP, pid); + descP->currentReaderP = NULL; } + + } else { + + /* Maybe unqueue one of the waiting reader(s) */ + + SSDBG( descP, ("SOCKET", + "socket_down_reader -> " + "not current reader - maybe a waiting reader\r\n") ); + + reader_unqueue(env, descP, pid); } - - SSDBG( descP, ("SOCKET", "socket_down -> done\r\n") ); - } diff --git a/lib/kernel/test/socket_server.erl b/lib/kernel/test/socket_server.erl index 9142942428..45adffc5e6 100644 --- a/lib/kernel/test/socket_server.erl +++ b/lib/kernel/test/socket_server.erl @@ -922,8 +922,8 @@ send(#handler{socket = Sock, msg = true, type = dgram, stimeout = Timeout}, CMsgHdr = #{level => ip, type => tos, data => reliability}, CMsgHdrs = [CMsgHdr], MsgHdr = #{addr => Dest, - iov => [Msg], - ctrl => CMsgHdrs}, + ctrl => CMsgHdrs, + iov => [Msg]}, %% ok = socket:setopt(Sock, otp, debug, true), Res = socket:sendmsg(Sock, MsgHdr, Timeout), %% ok = socket:setopt(Sock, otp, debug, false), -- cgit v1.2.3 From cd6afd8df61eb33aa2b51d4a0315fa8903135465 Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Wed, 26 Sep 2018 12:11:50 +0200 Subject: [socket-nif] Improve socket stop Make sure the current whatever (writer, reader or writer) is not the closer pid before *attempting* to sending the abort message. And if that fails, issue a warning message (not aborting). OTP-14831 --- erts/emulator/nifs/common/socket_nif.c | 66 +++++++++++++++++++++++++--------- 1 file changed, 50 insertions(+), 16 deletions(-) diff --git a/erts/emulator/nifs/common/socket_nif.c b/erts/emulator/nifs/common/socket_nif.c index a6940f788c..ccbcf63ece 100644 --- a/erts/emulator/nifs/common/socket_nif.c +++ b/erts/emulator/nifs/common/socket_nif.c @@ -2918,15 +2918,14 @@ ERL_NIF_TERM nif_bind(ErlNifEnv* env, eSockAddr = argv[1]; SSDBG( descP, - ("SOCKET", "nif_bind -> args when sock = %d:" + ("SOCKET", "nif_bind -> args when sock = %d (0x%lX)" "\r\n Socket: %T" "\r\n SockAddr: %T" - "\r\n", descP->sock, argv[0], eSockAddr) ); + "\r\n", descP->sock, descP->state, argv[0], eSockAddr) ); /* Make sure we are ready * Not sure how this would even happen, but... */ - /* WHY NOT !IS_OPEN(...) */ if (descP->state != SOCKET_STATE_OPEN) return esock_make_error(env, atom_exbadstate); @@ -14646,11 +14645,22 @@ void socket_stop(ErlNifEnv* env, void* obj, int fd, int is_direct_call) /* We have a (current) writer and *may* therefor also have * writers waiting. */ - - ESOCK_ASSERT( (NULL == send_msg_nif_abort(env, - descP->currentWriter.ref, - atom_closed, - &descP->currentWriter.pid)) ); + + if (!compare_pids(env, + &descP->closerPid, + &descP->currentWriter.pid) && + send_msg_nif_abort(env, + descP->currentWriter.ref, + atom_closed, + &descP->currentWriter.pid) != NULL) { + /* Shall we really do this? + * This happens if the controlling process has been killed! + */ + esock_warning_msg("Failed sending abort (%T) message to " + "current writer %T\r\n", + descP->currentWriter.ref, + descP->currentWriter.pid); + } /* And also deal with the waiting writers (in the same way) */ inform_waiting_procs(env, descP, &descP->writersQ, TRUE, atom_closed); @@ -14662,10 +14672,21 @@ void socket_stop(ErlNifEnv* env, void* obj, int fd, int is_direct_call) * readers waiting. */ - ESOCK_ASSERT( (NULL == send_msg_nif_abort(env, - descP->currentReader.ref, - atom_closed, - &descP->currentReader.pid)) ); + if (!compare_pids(env, + &descP->closerPid, + &descP->currentReader.pid) && + send_msg_nif_abort(env, + descP->currentReader.ref, + atom_closed, + &descP->currentReader.pid) != NULL) { + /* Shall we really do this? + * This happens if the controlling process has been killed! + */ + esock_warning_msg("Failed sending abort (%T) message to " + "current reader %T\r\n", + descP->currentReader.ref, + descP->currentReader.pid); + } /* And also deal with the waiting readers (in the same way) */ inform_waiting_procs(env, descP, &descP->readersQ, TRUE, atom_closed); @@ -14676,10 +14697,21 @@ void socket_stop(ErlNifEnv* env, void* obj, int fd, int is_direct_call) * acceptors waiting. */ - ESOCK_ASSERT( (NULL == send_msg_nif_abort(env, - descP->currentAcceptor.ref, - atom_closed, - &descP->currentAcceptor.pid)) ); + if (!compare_pids(env, + &descP->closerPid, + &descP->currentAcceptor.pid) && + send_msg_nif_abort(env, + descP->currentAcceptor.ref, + atom_closed, + &descP->currentAcceptor.pid) != NULL) { + /* Shall we really do this? + * This happens if the controlling process has been killed! + */ + esock_warning_msg("Failed sending abort (%T) message to " + "current acceptor %T\r\n", + descP->currentAcceptor.ref, + descP->currentAcceptor.pid); + } /* And also deal with the waiting acceptors (in the same way) */ inform_waiting_procs(env, descP, &descP->acceptorsQ, TRUE, atom_closed); @@ -14811,6 +14843,8 @@ void socket_down(ErlNifEnv* env, descP->state = SOCKET_STATE_CLOSING; descP->closeLocal = TRUE; + descP->closerPid = *pid; + descP->closerMon = *mon; descP->closeRef = MKREF(env); enif_select(env, descP->sock, (ERL_NIF_SELECT_STOP), descP, NULL, descP->closeRef); -- cgit v1.2.3 From 18305496c064c8d9d32448a6425bdd39eaa20c7f Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Wed, 26 Sep 2018 15:02:53 +0200 Subject: [socket-nif|test] Add operation timeout test cases Add a number of *basic* operation (connect, accept, send and recv). All of which are not yet implemented (skipped with 'not-yet-implemented'). All of these are basic and only local (both sides on the same host). OTP-14831 --- lib/kernel/test/socket_SUITE.erl | 777 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 735 insertions(+), 42 deletions(-) diff --git a/lib/kernel/test/socket_SUITE.erl b/lib/kernel/test/socket_SUITE.erl index 1bf12693fe..e4f9d2c8b2 100644 --- a/lib/kernel/test/socket_SUITE.erl +++ b/lib/kernel/test/socket_SUITE.erl @@ -25,6 +25,8 @@ %% Suite exports -export([suite/0, all/0, groups/0]). +-export([init_per_suite/1, end_per_suite/1, + init_per_testcase/2, end_per_testcase/2]). %% Test cases -export([ @@ -34,7 +36,29 @@ api_b_sendto_and_recvfrom_udp4/1, api_b_sendmsg_and_recvmsg_udp4/1, api_b_send_and_recv_tcp4/1, - api_b_sendmsg_and_recvmsg_tcp4/1 + api_b_sendmsg_and_recvmsg_tcp4/1, + + %% Operation Timeout + to_connect_tcp4/1, + to_connect_tcp6/1, + to_accept_tcp4/1, + to_accept_tcp6/1, + to_send_tcp4/1, + to_send_tcp6/1, + to_sendto_udp4/1, + to_sendto_udp6/1, + to_sendmsg_tcp4/1, + to_sendmsg_tcp6/1, + to_recv_udp4/1, + to_recv_udp6/1, + to_recv_tcp4/1, + to_recv_tcp6/1, + to_recvfrom_udp4/1, + to_recvfrom_udp6/1, + to_recvmsg_udp4/1, + to_recvmsg_udp6/1, + to_recvmsg_tcp4/1, + to_recvmsg_tcp6/1 %% Tickets ]). @@ -64,18 +88,21 @@ all() -> ]. groups() -> - [{api, [], api_cases()}, - {api_basic, [], api_basic_cases()} + [{api, [], api_cases()}, + {api_basic, [], api_basic_cases()}, + {op_with_timeout, [], op_with_timeout_cases()} %% {tickets, [], ticket_cases()} ]. api_cases() -> [ - {group, api_basic} + {group, api_basic}, + {group, op_with_timeout} ]. api_basic_cases() -> - [api_b_open_and_close_udp4, + [ + api_b_open_and_close_udp4, api_b_open_and_close_tcp4, api_b_sendto_and_recvfrom_udp4, api_b_sendmsg_and_recvmsg_udp4, @@ -83,11 +110,52 @@ api_basic_cases() -> api_b_sendmsg_and_recvmsg_tcp4 ]. +op_with_timeout_cases() -> + [ + to_connect_tcp4, + to_connect_tcp6, + to_accept_tcp4, + to_accept_tcp6, + to_send_tcp4, + to_send_tcp6, + to_sendto_udp4, + to_sendto_udp6, + to_sendmsg_tcp4, + to_sendmsg_tcp6, + to_recv_udp4, + to_recv_udp6, + to_recv_tcp4, + to_recv_tcp6, + to_recvfrom_udp4, + to_recvfrom_udp6, + to_recvmsg_udp4, + to_recvmsg_udp6, + to_recvmsg_tcp4, + to_recvmsg_tcp6 + ]. + + %% ticket_cases() -> %% []. +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +init_per_suite(Config) -> + Config. + +end_per_suite(_) -> + ok. + +init_per_testcase(_TC, Config) -> + Config. + +end_per_testcase(_TC, Config) -> + Config. + + + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% Basically open (create) and close an IPv4 UDP (dgram) socket. @@ -154,6 +222,8 @@ api_b_sendto_and_recvfrom_udp4(_Config) when is_list(_Config) -> tc_end(). +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + %% Basically send and receive on an IPv4 UDP (dgram) socket %% using sendmsg and recvmsg. api_b_sendmsg_and_recvmsg_udp4(suite) -> @@ -183,42 +253,19 @@ api_b_sendmsg_and_recvmsg_udp4(_Config) when is_list(_Config) -> tc_end(). +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + api_b_send_and_recv_udp(Domain, Send, Recv) -> - SockSrc = case socket:open(Domain, dgram, udp) of - {ok, S1} -> - S1; - {error, OR1} -> - ?FAIL({open, src, OR1}) - end, - LAddr = which_local_addr(Domain), - LSA = #{family => Domain, addr => LAddr}, - case socket:bind(SockSrc, LSA) of - {ok, _} -> - ok; - {error, BR1} -> - ?FAIL({bind, src, BR1}) - end, - SockDst = case socket:open(Domain, dgram, udp) of - {ok, S2} -> - S2; - {error, OR2} -> - ?FAIL({open, dst, OR2}) - end, - case socket:bind(SockDst, LSA) of - {ok, _} -> - ok; - {error, BR2} -> - ?FAIL({bind, dst, BR2}) - end, - Dst = case socket:sockname(SockDst) of - {ok, SA} -> - SA; - {ok, SNR} -> - ?FAIL({sockname, dst, SNR}) - end, - ok = Send(SockSrc, ?BASIC_REQ, Dst), + SockSrc = sock_open(Domain, dgram, udp), + LAddr = which_local_addr(Domain), + LSA = #{family => Domain, addr => LAddr}, + sock_bind(SockSrc, LSA), + SockDst = sock_open(Domain, dgram, udp), + sock_bind(SockDst, LSA), + Dst = sock_sockname(SockDst), + ok = Send(SockSrc, ?BASIC_REQ, Dst), {ok, {Src, ?BASIC_REQ}} = Recv(SockDst), - ok = Send(SockDst, ?BASIC_REP, Src), + ok = Send(SockDst, ?BASIC_REP, Src), {ok, {Dst, ?BASIC_REP}} = Recv(SockSrc), socket:close(SockSrc), socket:close(SockDst), @@ -246,6 +293,8 @@ api_b_send_and_recv_tcp4(_Config) when is_list(_Config) -> tc_end(). +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + %% Basically send and receive using the msg functions (sendmsg and recvmsg) %% on an IPv4 TCP (stream) socket. api_b_sendmsg_and_recvmsg_tcp4(suite) -> @@ -271,6 +320,8 @@ api_b_sendmsg_and_recvmsg_tcp4(_Config) when is_list(_Config) -> tc_end(). +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + api_b_send_and_recv_tcp(Domain, Send, Recv) -> process_flag(trap_exit, true), LAddr = which_local_addr(Domain), @@ -380,6 +431,528 @@ api_b_send_and_recv_tcp(Domain, Send, Recv) -> +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% This test case is intended to test the connect timeout option +%% on an IPv4 TCP (stream) socket. +to_connect_tcp4(suite) -> + []; +to_connect_tcp4(doc) -> + []; +to_connect_tcp4(_Config) when is_list(_Config) -> + tc_begin(to_connect_tcp4), + ok = to_connect_tcp(inet), + tc_end(). + %% not_yet_implemented(). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% This test case is intended to test the connect timeout option +%% on an IPv6 TCP (stream) socket. +to_connect_tcp6(suite) -> + []; +to_connect_tcp6(doc) -> + []; +to_connect_tcp6(_Config) when is_list(_Config) -> + %% tc_begin(to_connect_tcp6), + %% ok = to_connect_tcp(inet6), + %% tc_end(). + not_yet_implemented(). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +to_connect_tcp(Domain) -> + process_flag(trap_exit, true), + p("init"), + Client = self(), + LocalAddr = which_local_addr(Domain), + LocalSA = #{family => Domain, addr => LocalAddr}, + ServerName = f("~s:server", [get_tc_name()]), + Server = spawn_link(fun() -> + set_tc_name(ServerName), + p("open"), + LSock = sock_open(Domain, stream, tcp), + p("bind"), + ServerLPort = sock_bind(LSock, LocalSA), + p("listen on ~w", [ServerLPort]), + sock_listen(LSock, 1), + p("inform client"), + Client ! {self(), ServerLPort}, + p("await termination command"), + receive + die -> + p("terminating"), + exit(normal) + end + end), + + p("await server port"), + ServerLPort = + receive + {Server, Port} -> + Port + end, + p("open(s)"), + CSock1 = sock_open(Domain, stream, tcp), + CSock2 = sock_open(Domain, stream, tcp), + CSock3 = sock_open(Domain, stream, tcp), + p("bind(s)"), + _ClientPort1 = sock_bind(CSock1, LocalSA), + _ClientPort2 = sock_bind(CSock2, LocalSA), + _ClientPort3 = sock_bind(CSock3, LocalSA), + ServerSA = LocalSA#{port => ServerLPort}, + to_connect_tcp_await_timeout([CSock1, CSock2, CSock3], ServerSA), + p("terminate server"), + Server ! die, + receive + {'EXIT', Server, _} -> + p("server terminated"), + ok + end, + ok. + + +to_connect_tcp_await_timeout(Socks, ServerSA) -> + to_connect_tcp_await_timeout(Socks, ServerSA, 1). + +to_connect_tcp_await_timeout([], _ServerSA, _ID) -> + ?FAIL(unexpected_success); +to_connect_tcp_await_timeout([Sock|Socks], ServerSA, ID) -> + p("~w: try connect", [ID]), + case socket:connect(Sock, ServerSA, 5000) of + {error, timeout} -> + p("expected timeout (~w)", [ID]), + ok; + {error, Reason} -> + p("failed connecting: ~p", [Reason]), + ?FAIL({recv, Reason}); + ok -> + p("unexpected success (~w) - try next", [ID]), + to_connect_tcp_await_timeout(Socks, ServerSA, ID+1) + end. + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% This test case is intended to test the accept timeout option +%% on an IPv4 TCP (stream) socket. +to_accept_tcp4(suite) -> + []; +to_accept_tcp4(doc) -> + []; +to_accept_tcp4(_Config) when is_list(_Config) -> + %% tc_begin(to_accept_tcp4), + %% ok = to_accept_tcp(inet), + %% tc_end(). + not_yet_implemented(). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% This test case is intended to test the accept timeout option +%% on an IPv6 TCP (stream) socket. +to_accept_tcp6(suite) -> + []; +to_accept_tcp6(doc) -> + []; +to_accept_tcp6(_Config) when is_list(_Config) -> + %% tc_begin(to_accept_tcp6), + %% ok = to_accept_tcp(inet6), + %% tc_end(). + not_yet_implemented(). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% This test case is intended to test the send timeout option +%% on an IPv4 TCP (stream) socket. +to_send_tcp4(suite) -> + []; +to_send_tcp4(doc) -> + []; +to_send_tcp4(_Config) when is_list(_Config) -> + %% tc_begin(to_send_tcp4), + %% ok = to_send_tcp(inet), + %% tc_end(). + not_yet_implemented(). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% This test case is intended to test the send timeout option +%% on an IPv6 TCP (stream) socket. +to_send_tcp6(suite) -> + []; +to_send_tcp6(doc) -> + []; +to_send_tcp6(_Config) when is_list(_Config) -> + %% tc_begin(to_send_tcp6), + %% ok = to_send_tcp(inet6), + %% tc_end(). + not_yet_implemented(). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% This test case is intended to test the sendto timeout option +%% on an IPv4 UDP (dgram) socket. +to_sendto_udp4(suite) -> + []; +to_sendto_udp4(doc) -> + []; +to_sendto_udp4(_Config) when is_list(_Config) -> + %% tc_begin(to_sendto_udp4), + %% ok = to_sendto_udp(inet), + %% tc_end(). + not_yet_implemented(). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% This test case is intended to test the sendto timeout option +%% on an IPv6 UDP (dgram) socket. +to_sendto_udp6(suite) -> + []; +to_sendto_udp6(doc) -> + []; +to_sendto_udp6(_Config) when is_list(_Config) -> + %% tc_begin(to_sendto_udp6), + %% ok = to_sendto_udp(inet6), + %% tc_end(). + not_yet_implemented(). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% This test case is intended to test the sendmsg timeout option +%% on an IPv4 TCP (stream) socket. +to_sendmsg_tcp4(suite) -> + []; +to_sendmsg_tcp4(doc) -> + []; +to_sendmsg_tcp4(_Config) when is_list(_Config) -> + %% tc_begin(to_sendmsg_tcp4), + %% ok = to_sendmsg_tcp(inet), + %% tc_end(). + not_yet_implemented(). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% This test case is intended to test the sendmsg timeout option +%% on an IPv6 TCP (stream) socket. +to_sendmsg_tcp6(suite) -> + []; +to_sendmsg_tcp6(doc) -> + []; +to_sendmsg_tcp6(_Config) when is_list(_Config) -> + %% tc_begin(to_sendmsg_tcp6), + %% ok = to_sendmsg_tcp(inet6), + %% tc_end(). + not_yet_implemented(). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% This test case is intended to test the recv timeout option +%% on an IPv4 UDP (dgram) socket. To test this we must connect +%% the socket. +to_recv_udp4(suite) -> + []; +to_recv_udp4(doc) -> + []; +to_recv_udp4(_Config) when is_list(_Config) -> + %% tc_begin(to_recv_udp4), + %% ok = to_recv_udp(inet), + %% tc_end(). + not_yet_implemented(). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% This test case is intended to test the recv timeout option +%% on an IPv6 UDP (dgram) socket. To test this we must connect +%% the socket. +to_recv_udp6(suite) -> + []; +to_recv_udp6(doc) -> + []; +to_recv_udp6(_Config) when is_list(_Config) -> + %% tc_begin(to_recv_udp6), + %% ok = to_recv_udp(inet6), + %% tc_end(). + not_yet_implemented(). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% This test case is intended to test the recv timeout option +%% on an IPv4 TCP (stream) socket. +to_recv_tcp4(suite) -> + []; +to_recv_tcp4(doc) -> + []; +to_recv_tcp4(_Config) when is_list(_Config) -> + tc_begin(to_recv_tcp4), + ok = to_recv_tcp(inet), + tc_end(). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% This test case is intended to test the recv timeout option +%% on an IPv6 TCP (stream) socket. +to_recv_tcp6(suite) -> + []; +to_recv_tcp6(doc) -> + []; +to_recv_tcp6(_Config) when is_list(_Config) -> + %% tc_begin(to_recv_tcp6), + %% ok = to_recv_tcp(inet6), + %% tc_end(). + not_yet_implemented(). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +to_recv_tcp(Domain) -> + process_flag(trap_exit, true), + p("server -> open"), + LSock = sock_open(Domain, stream, tcp), + LocalAddr = which_local_addr(Domain), + LocalSA = #{family => Domain, addr => LocalAddr}, + p("server -> bind"), + ServerLPort = sock_bind(LSock, LocalSA), + p("server(~w) -> listen", [ServerLPort]), + sock_listen(LSock), + ClientName = f("~s:client", [get_tc_name()]), + Client = spawn_link(fun() -> + set_tc_name(ClientName), + p("open"), + CSock = sock_open(Domain, stream, tcp), + p("bind"), + ClientPort = sock_bind(CSock, LocalSA), + p("[~w] connect to ~w", + [ClientPort, ServerLPort]), + sock_connect(CSock, LocalSA#{port => ServerLPort}), + p("await termination command"), + receive + die -> + p("terminating"), + exit(normal) + end + end), + p("server -> accept on ~w", [ServerLPort]), + Sock = sock_accept(LSock), + p("server -> recv"), + %% The zero (0) represents "give me everything you have" + case socket:recv(Sock, 0, 5000) of + {error, timeout} -> + p("server -> expected timeout"), + ok; + {ok, _Data} -> + ?FAIL(unexpected_success); + {error, Reason} -> + ?FAIL({recv, Reason}) + end, + Client ! die, + receive + {'EXIT', Client, _} -> + ok + end, + ok. + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% This test case is intended to test the recvfrom timeout option +%% on an IPv4 UDP (dgram) socket. +to_recvfrom_udp4(suite) -> + []; +to_recvfrom_udp4(doc) -> + []; +to_recvfrom_udp4(_Config) when is_list(_Config) -> + tc_begin(to_recvfrom_udp4), + ok = to_recvfrom_udp(inet), + tc_end(). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% This test case is intended to test the recvfrom timeout option +%% on an IPv6 UDP (dgram) socket. +to_recvfrom_udp6(suite) -> + []; +to_recvfrom_udp6(doc) -> + []; +to_recvfrom_udp6(_Config) when is_list(_Config) -> + %% tc_begin(to_recvfrom_udp6), + %% ok = to_recvfrom_udp(inet6), + %% tc_end(). + not_yet_implemented(). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +to_recvfrom_udp(Domain) -> + process_flag(trap_exit, true), + p("init"), + LocalAddr = which_local_addr(Domain), + LocalSA = #{family => Domain, addr => LocalAddr}, + p("open"), + Sock = sock_open(Domain, dgram, udp), + p("bind"), + _Port = sock_bind(Sock, LocalSA), + p("recv"), + case socket:recvfrom(Sock, 0, 5000) of + {error, timeout} -> + p("expected timeout"), + ok; + {ok, _SrcData} -> + ?FAIL(unexpected_success); + {error, Reason} -> + ?FAIL({recv, Reason}) + end, + ok. + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% This test case is intended to test the recvmsg timeout option +%% on an IPv4 UDP (dgram) socket. +to_recvmsg_udp4(suite) -> + []; +to_recvmsg_udp4(doc) -> + []; +to_recvmsg_udp4(_Config) when is_list(_Config) -> + %% not_yet_implemented(). + tc_begin(to_recvmsg_udp4), + ok = to_recvmsg_udp(inet), + tc_end(). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% This test case is intended to test the recvmsg timeout option +%% on an IPv6 UDP (dgram) socket. +to_recvmsg_udp6(suite) -> + []; +to_recvmsg_udp6(doc) -> + []; +to_recvmsg_udp6(_Config) when is_list(_Config) -> + %% tc_begin(to_recvmsg_udp6), + %% ok = to_recvmsg_udp(inet6), + %% tc_end(). + not_yet_implemented(). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +to_recvmsg_udp(Domain) -> + process_flag(trap_exit, true), + p("init"), + LocalAddr = which_local_addr(Domain), + LocalSA = #{family => Domain, addr => LocalAddr}, + p("open"), + Sock = sock_open(Domain, dgram, udp), + p("bind"), + _Port = sock_bind(Sock, LocalSA), + p("recv"), + case socket:recvmsg(Sock, 5000) of + {error, timeout} -> + p("expected timeout"), + ok; + {ok, _MsgHdr} -> + ?FAIL(unexpected_success); + {error, Reason} -> + ?FAIL({recv, Reason}) + end, + ok. + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% This test case is intended to test the recvmsg timeout option +%% on an IPv4 TCP (stream) socket. +to_recvmsg_tcp4(suite) -> + []; +to_recvmsg_tcp4(doc) -> + []; +to_recvmsg_tcp4(_Config) when is_list(_Config) -> + tc_begin(to_recvmsg_tcp4), + ok = to_recvmsg_tcp(inet), + tc_end(). + %% not_yet_implemented(). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% This test case is intended to test the recvmsg timeout option +%% on an IPv6 TCP (stream) socket. +to_recvmsg_tcp6(suite) -> + []; +to_recvmsg_tcp6(doc) -> + []; +to_recvmsg_tcp6(_Config) when is_list(_Config) -> + %% tc_begin(to_recvmsg_tcp6), + %% ok = to_recvmsg_tcp(inet6), + %% tc_end(). + not_yet_implemented(). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +to_recvmsg_tcp(Domain) -> + process_flag(trap_exit, true), + p("server -> open"), + LSock = sock_open(Domain, stream, tcp), + LocalAddr = which_local_addr(Domain), + LocalSA = #{family => Domain, addr => LocalAddr}, + p("server -> bind"), + ServerLPort = sock_bind(LSock, LocalSA), + p("server(~w) -> listen", [ServerLPort]), + sock_listen(LSock), + ClientName = f("~s:client", [get_tc_name()]), + Client = spawn_link(fun() -> + set_tc_name(ClientName), + p("open"), + CSock = sock_open(Domain, stream, tcp), + p("bind"), + ClientPort = sock_bind(CSock, LocalSA), + p("[~w] connect to ~w", + [ClientPort, ServerLPort]), + sock_connect(CSock, LocalSA#{port => ServerLPort}), + p("await termination command"), + receive + die -> + p("terminating"), + exit(normal) + end + end), + p("server -> accept on ~w", [ServerLPort]), + Sock = sock_accept(LSock), + p("server -> recv"), + %% The zero (0) represents "give me everything you have" + case socket:recvmsg(Sock, 5000) of + {error, timeout} -> + p("server -> expected timeout"), + ok; + {ok, _Data} -> + ?FAIL(unexpected_success); + {error, Reason} -> + ?FAIL({recv, Reason}) + end, + Client ! die, + receive + {'EXIT', Client, _} -> + ok + end, + ok. + + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -411,16 +984,136 @@ which_addr2(Domain, [_|IFO]) -> which_addr2(Domain, IFO). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +sock_open(Domain, Type, Proto) -> + try socket:open(Domain, Type, Proto) of + {ok, Socket} -> + Socket; + {error, Reason} -> + ?FAIL({open, Reason}) + catch + C:E:S -> + ?FAIL({open, C, E, S}) + end. + + +sock_bind(Sock, SockAddr) -> + try socket:bind(Sock, SockAddr) of + {ok, Port} -> + Port; + {error, Reason} -> + p("sock_bind -> error: ~p", [Reason]), + ?FAIL({bind, Reason}) + catch + C:E:S -> + p("sock_bind -> failed: ~p, ~p, ~p", [C, E, S]), + ?FAIL({bind, C, E, S}) + end. + +sock_connect(Sock, SockAddr) -> + try socket:connect(Sock, SockAddr) of + ok -> + ok; + {error, Reason} -> + ?FAIL({connect, Reason}) + catch + C:E:S -> + ?FAIL({connect, C, E, S}) + end. + +sock_sockname(Sock) -> + try socket:sockname(Sock) of + {ok, SockAddr} -> + SockAddr; + {error, Reason} -> + ?FAIL({sockname, Reason}) + catch + C:E:S -> + ?FAIL({sockname, C, E, S}) + end. + + +sock_listen(Sock) -> + sock_listen2(fun() -> socket:listen(Sock) end). + +sock_listen(Sock, BackLog) -> + sock_listen2(fun() -> socket:listen(Sock, BackLog) end). + +sock_listen2(Listen) -> + try Listen() of + ok -> + ok; + {error, Reason} -> + ?FAIL({listen, Reason}) + catch + C:E:S -> + ?FAIL({listen, C, E, S}) + end. + + +sock_accept(LSock) -> + try socket:accept(LSock) of + {ok, Sock} -> + Sock; + {error, Reason} -> + p("sock_accept -> error: ~p", [Reason]), + ?FAIL({accept, Reason}) + catch + C:E:S -> + p("sock_accept -> failed: ~p, ~p, ~p", [C, E, S]), + ?FAIL({accept, C, E, S}) + end. + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +not_yet_implemented() -> + {skip, "not yet implemented"}. + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +set_tc_name(N) when is_atom(N) -> + set_tc_name(atom_to_list(N)); +set_tc_name(N) when is_list(N) -> + put(tc_name, N). + +get_tc_name() -> + get(tc_name). + tc_begin(TC) -> - put(tc_name, TC), - p("begin"). + set_tc_name(TC), + p("begin ***"). tc_end() -> + p("done ***"), ok. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +f(F, A) -> + lists:flatten(io_lib:format(F, A)). + p(F) -> p(F, []). p(F, A) -> - io:format(user, "*** ~w " ++ F ++ "~n", [get(tc_name)|A]). + TcName = + case get(tc_name) of + undefined -> + ""; + Name when is_list(Name) -> + Name + end, + i("*** ~s[~p] " ++ F, [TcName,self()|A]). + + +%% i(F) -> +%% i(F, []). +i(F, A) -> + io:format(user, F ++ "~n", A). -- cgit v1.2.3 From 4d8504a7c33133ea9f7ade7b9eb763406e0d60fa Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Thu, 27 Sep 2018 12:38:23 +0200 Subject: [socket-nif|test] Test case grouping The test cases has been slighly regrouped. The to-group has moved into the api-group. OTP-14831 --- lib/kernel/test/socket_SUITE.erl | 318 +++++++++++++++++++-------------------- 1 file changed, 159 insertions(+), 159 deletions(-) diff --git a/lib/kernel/test/socket_SUITE.erl b/lib/kernel/test/socket_SUITE.erl index e4f9d2c8b2..0255c8ef75 100644 --- a/lib/kernel/test/socket_SUITE.erl +++ b/lib/kernel/test/socket_SUITE.erl @@ -30,7 +30,7 @@ %% Test cases -export([ - %% Basic + %% API Basic api_b_open_and_close_udp4/1, api_b_open_and_close_tcp4/1, api_b_sendto_and_recvfrom_udp4/1, @@ -38,27 +38,27 @@ api_b_send_and_recv_tcp4/1, api_b_sendmsg_and_recvmsg_tcp4/1, - %% Operation Timeout - to_connect_tcp4/1, - to_connect_tcp6/1, - to_accept_tcp4/1, - to_accept_tcp6/1, - to_send_tcp4/1, - to_send_tcp6/1, - to_sendto_udp4/1, - to_sendto_udp6/1, - to_sendmsg_tcp4/1, - to_sendmsg_tcp6/1, - to_recv_udp4/1, - to_recv_udp6/1, - to_recv_tcp4/1, - to_recv_tcp6/1, - to_recvfrom_udp4/1, - to_recvfrom_udp6/1, - to_recvmsg_udp4/1, - to_recvmsg_udp6/1, - to_recvmsg_tcp4/1, - to_recvmsg_tcp6/1 + %% API Operation Timeout + api_to_connect_tcp4/1, + api_to_connect_tcp6/1, + api_to_accept_tcp4/1, + api_to_accept_tcp6/1, + api_to_send_tcp4/1, + api_to_send_tcp6/1, + api_to_sendapi_to_udp4/1, + api_to_sendapi_to_udp6/1, + api_to_sendmsg_tcp4/1, + api_to_sendmsg_tcp6/1, + api_to_recv_udp4/1, + api_to_recv_udp6/1, + api_to_recv_tcp4/1, + api_to_recv_tcp6/1, + api_to_recvfrom_udp4/1, + api_to_recvfrom_udp6/1, + api_to_recvmsg_udp4/1, + api_to_recvmsg_udp6/1, + api_to_recvmsg_tcp4/1, + api_to_recvmsg_tcp6/1 %% Tickets ]). @@ -88,16 +88,16 @@ all() -> ]. groups() -> - [{api, [], api_cases()}, - {api_basic, [], api_basic_cases()}, - {op_with_timeout, [], op_with_timeout_cases()} - %% {tickets, [], ticket_cases()} + [{api, [], api_cases()}, + {api_basic, [], api_basic_cases()}, + {api_op_with_timeout, [], api_op_with_timeout_cases()} + %% {tickets, [], ticket_cases()} ]. api_cases() -> [ {group, api_basic}, - {group, op_with_timeout} + {group, api_op_with_timeout} ]. api_basic_cases() -> @@ -110,28 +110,28 @@ api_basic_cases() -> api_b_sendmsg_and_recvmsg_tcp4 ]. -op_with_timeout_cases() -> +api_op_with_timeout_cases() -> [ - to_connect_tcp4, - to_connect_tcp6, - to_accept_tcp4, - to_accept_tcp6, - to_send_tcp4, - to_send_tcp6, - to_sendto_udp4, - to_sendto_udp6, - to_sendmsg_tcp4, - to_sendmsg_tcp6, - to_recv_udp4, - to_recv_udp6, - to_recv_tcp4, - to_recv_tcp6, - to_recvfrom_udp4, - to_recvfrom_udp6, - to_recvmsg_udp4, - to_recvmsg_udp6, - to_recvmsg_tcp4, - to_recvmsg_tcp6 + api_to_connect_tcp4, + api_to_connect_tcp6, + api_to_accept_tcp4, + api_to_accept_tcp6, + api_to_send_tcp4, + api_to_send_tcp6, + api_to_sendapi_to_udp4, + api_to_sendapi_to_udp6, + api_to_sendmsg_tcp4, + api_to_sendmsg_tcp6, + api_to_recv_udp4, + api_to_recv_udp6, + api_to_recv_tcp4, + api_to_recv_tcp6, + api_to_recvfrom_udp4, + api_to_recvfrom_udp6, + api_to_recvmsg_udp4, + api_to_recvmsg_udp6, + api_to_recvmsg_tcp4, + api_to_recvmsg_tcp6 ]. @@ -435,13 +435,13 @@ api_b_send_and_recv_tcp(Domain, Send, Recv) -> %% This test case is intended to test the connect timeout option %% on an IPv4 TCP (stream) socket. -to_connect_tcp4(suite) -> +api_to_connect_tcp4(suite) -> []; -to_connect_tcp4(doc) -> +api_to_connect_tcp4(doc) -> []; -to_connect_tcp4(_Config) when is_list(_Config) -> - tc_begin(to_connect_tcp4), - ok = to_connect_tcp(inet), +api_to_connect_tcp4(_Config) when is_list(_Config) -> + tc_begin(api_to_connect_tcp4), + ok = api_to_connect_tcp(inet), tc_end(). %% not_yet_implemented(). @@ -450,20 +450,20 @@ to_connect_tcp4(_Config) when is_list(_Config) -> %% This test case is intended to test the connect timeout option %% on an IPv6 TCP (stream) socket. -to_connect_tcp6(suite) -> +api_to_connect_tcp6(suite) -> []; -to_connect_tcp6(doc) -> +api_to_connect_tcp6(doc) -> []; -to_connect_tcp6(_Config) when is_list(_Config) -> - %% tc_begin(to_connect_tcp6), - %% ok = to_connect_tcp(inet6), +api_to_connect_tcp6(_Config) when is_list(_Config) -> + %% tc_begin(api_to_connect_tcp6), + %% ok = api_to_connect_tcp(inet6), %% tc_end(). not_yet_implemented(). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -to_connect_tcp(Domain) -> +api_to_connect_tcp(Domain) -> process_flag(trap_exit, true), p("init"), Client = self(), @@ -503,7 +503,7 @@ to_connect_tcp(Domain) -> _ClientPort2 = sock_bind(CSock2, LocalSA), _ClientPort3 = sock_bind(CSock3, LocalSA), ServerSA = LocalSA#{port => ServerLPort}, - to_connect_tcp_await_timeout([CSock1, CSock2, CSock3], ServerSA), + api_to_connect_tcp_await_timeout([CSock1, CSock2, CSock3], ServerSA), p("terminate server"), Server ! die, receive @@ -514,12 +514,12 @@ to_connect_tcp(Domain) -> ok. -to_connect_tcp_await_timeout(Socks, ServerSA) -> - to_connect_tcp_await_timeout(Socks, ServerSA, 1). +api_to_connect_tcp_await_timeout(Socks, ServerSA) -> + api_to_connect_tcp_await_timeout(Socks, ServerSA, 1). -to_connect_tcp_await_timeout([], _ServerSA, _ID) -> +api_to_connect_tcp_await_timeout([], _ServerSA, _ID) -> ?FAIL(unexpected_success); -to_connect_tcp_await_timeout([Sock|Socks], ServerSA, ID) -> +api_to_connect_tcp_await_timeout([Sock|Socks], ServerSA, ID) -> p("~w: try connect", [ID]), case socket:connect(Sock, ServerSA, 5000) of {error, timeout} -> @@ -530,7 +530,7 @@ to_connect_tcp_await_timeout([Sock|Socks], ServerSA, ID) -> ?FAIL({recv, Reason}); ok -> p("unexpected success (~w) - try next", [ID]), - to_connect_tcp_await_timeout(Socks, ServerSA, ID+1) + api_to_connect_tcp_await_timeout(Socks, ServerSA, ID+1) end. @@ -539,13 +539,13 @@ to_connect_tcp_await_timeout([Sock|Socks], ServerSA, ID) -> %% This test case is intended to test the accept timeout option %% on an IPv4 TCP (stream) socket. -to_accept_tcp4(suite) -> +api_to_accept_tcp4(suite) -> []; -to_accept_tcp4(doc) -> +api_to_accept_tcp4(doc) -> []; -to_accept_tcp4(_Config) when is_list(_Config) -> - %% tc_begin(to_accept_tcp4), - %% ok = to_accept_tcp(inet), +api_to_accept_tcp4(_Config) when is_list(_Config) -> + %% tc_begin(api_to_accept_tcp4), + %% ok = api_to_accept_tcp(inet), %% tc_end(). not_yet_implemented(). @@ -554,13 +554,13 @@ to_accept_tcp4(_Config) when is_list(_Config) -> %% This test case is intended to test the accept timeout option %% on an IPv6 TCP (stream) socket. -to_accept_tcp6(suite) -> +api_to_accept_tcp6(suite) -> []; -to_accept_tcp6(doc) -> +api_to_accept_tcp6(doc) -> []; -to_accept_tcp6(_Config) when is_list(_Config) -> - %% tc_begin(to_accept_tcp6), - %% ok = to_accept_tcp(inet6), +api_to_accept_tcp6(_Config) when is_list(_Config) -> + %% tc_begin(api_to_accept_tcp6), + %% ok = api_to_accept_tcp(inet6), %% tc_end(). not_yet_implemented(). @@ -569,13 +569,13 @@ to_accept_tcp6(_Config) when is_list(_Config) -> %% This test case is intended to test the send timeout option %% on an IPv4 TCP (stream) socket. -to_send_tcp4(suite) -> +api_to_send_tcp4(suite) -> []; -to_send_tcp4(doc) -> +api_to_send_tcp4(doc) -> []; -to_send_tcp4(_Config) when is_list(_Config) -> - %% tc_begin(to_send_tcp4), - %% ok = to_send_tcp(inet), +api_to_send_tcp4(_Config) when is_list(_Config) -> + %% tc_begin(api_to_send_tcp4), + %% ok = api_to_send_tcp(inet), %% tc_end(). not_yet_implemented(). @@ -584,13 +584,13 @@ to_send_tcp4(_Config) when is_list(_Config) -> %% This test case is intended to test the send timeout option %% on an IPv6 TCP (stream) socket. -to_send_tcp6(suite) -> +api_to_send_tcp6(suite) -> []; -to_send_tcp6(doc) -> +api_to_send_tcp6(doc) -> []; -to_send_tcp6(_Config) when is_list(_Config) -> - %% tc_begin(to_send_tcp6), - %% ok = to_send_tcp(inet6), +api_to_send_tcp6(_Config) when is_list(_Config) -> + %% tc_begin(api_to_send_tcp6), + %% ok = api_to_send_tcp(inet6), %% tc_end(). not_yet_implemented(). @@ -599,13 +599,13 @@ to_send_tcp6(_Config) when is_list(_Config) -> %% This test case is intended to test the sendto timeout option %% on an IPv4 UDP (dgram) socket. -to_sendto_udp4(suite) -> +api_to_sendapi_to_udp4(suite) -> []; -to_sendto_udp4(doc) -> +api_to_sendapi_to_udp4(doc) -> []; -to_sendto_udp4(_Config) when is_list(_Config) -> - %% tc_begin(to_sendto_udp4), - %% ok = to_sendto_udp(inet), +api_to_sendapi_to_udp4(_Config) when is_list(_Config) -> + %% tc_begin(api_to_sendapi_to_udp4), + %% ok = api_to_sendapi_to_udp(inet), %% tc_end(). not_yet_implemented(). @@ -614,13 +614,13 @@ to_sendto_udp4(_Config) when is_list(_Config) -> %% This test case is intended to test the sendto timeout option %% on an IPv6 UDP (dgram) socket. -to_sendto_udp6(suite) -> +api_to_sendapi_to_udp6(suite) -> []; -to_sendto_udp6(doc) -> +api_to_sendapi_to_udp6(doc) -> []; -to_sendto_udp6(_Config) when is_list(_Config) -> - %% tc_begin(to_sendto_udp6), - %% ok = to_sendto_udp(inet6), +api_to_sendapi_to_udp6(_Config) when is_list(_Config) -> + %% tc_begin(api_to_sendapi_to_udp6), + %% ok = api_to_sendapi_to_udp(inet6), %% tc_end(). not_yet_implemented(). @@ -629,13 +629,13 @@ to_sendto_udp6(_Config) when is_list(_Config) -> %% This test case is intended to test the sendmsg timeout option %% on an IPv4 TCP (stream) socket. -to_sendmsg_tcp4(suite) -> +api_to_sendmsg_tcp4(suite) -> []; -to_sendmsg_tcp4(doc) -> +api_to_sendmsg_tcp4(doc) -> []; -to_sendmsg_tcp4(_Config) when is_list(_Config) -> - %% tc_begin(to_sendmsg_tcp4), - %% ok = to_sendmsg_tcp(inet), +api_to_sendmsg_tcp4(_Config) when is_list(_Config) -> + %% tc_begin(api_to_sendmsg_tcp4), + %% ok = api_to_sendmsg_tcp(inet), %% tc_end(). not_yet_implemented(). @@ -644,13 +644,13 @@ to_sendmsg_tcp4(_Config) when is_list(_Config) -> %% This test case is intended to test the sendmsg timeout option %% on an IPv6 TCP (stream) socket. -to_sendmsg_tcp6(suite) -> +api_to_sendmsg_tcp6(suite) -> []; -to_sendmsg_tcp6(doc) -> +api_to_sendmsg_tcp6(doc) -> []; -to_sendmsg_tcp6(_Config) when is_list(_Config) -> - %% tc_begin(to_sendmsg_tcp6), - %% ok = to_sendmsg_tcp(inet6), +api_to_sendmsg_tcp6(_Config) when is_list(_Config) -> + %% tc_begin(api_to_sendmsg_tcp6), + %% ok = api_to_sendmsg_tcp(inet6), %% tc_end(). not_yet_implemented(). @@ -660,13 +660,13 @@ to_sendmsg_tcp6(_Config) when is_list(_Config) -> %% This test case is intended to test the recv timeout option %% on an IPv4 UDP (dgram) socket. To test this we must connect %% the socket. -to_recv_udp4(suite) -> +api_to_recv_udp4(suite) -> []; -to_recv_udp4(doc) -> +api_to_recv_udp4(doc) -> []; -to_recv_udp4(_Config) when is_list(_Config) -> - %% tc_begin(to_recv_udp4), - %% ok = to_recv_udp(inet), +api_to_recv_udp4(_Config) when is_list(_Config) -> + %% tc_begin(api_to_recv_udp4), + %% ok = api_to_recv_udp(inet), %% tc_end(). not_yet_implemented(). @@ -676,13 +676,13 @@ to_recv_udp4(_Config) when is_list(_Config) -> %% This test case is intended to test the recv timeout option %% on an IPv6 UDP (dgram) socket. To test this we must connect %% the socket. -to_recv_udp6(suite) -> +api_to_recv_udp6(suite) -> []; -to_recv_udp6(doc) -> +api_to_recv_udp6(doc) -> []; -to_recv_udp6(_Config) when is_list(_Config) -> - %% tc_begin(to_recv_udp6), - %% ok = to_recv_udp(inet6), +api_to_recv_udp6(_Config) when is_list(_Config) -> + %% tc_begin(api_to_recv_udp6), + %% ok = api_to_recv_udp(inet6), %% tc_end(). not_yet_implemented(). @@ -691,13 +691,13 @@ to_recv_udp6(_Config) when is_list(_Config) -> %% This test case is intended to test the recv timeout option %% on an IPv4 TCP (stream) socket. -to_recv_tcp4(suite) -> +api_to_recv_tcp4(suite) -> []; -to_recv_tcp4(doc) -> +api_to_recv_tcp4(doc) -> []; -to_recv_tcp4(_Config) when is_list(_Config) -> - tc_begin(to_recv_tcp4), - ok = to_recv_tcp(inet), +api_to_recv_tcp4(_Config) when is_list(_Config) -> + tc_begin(api_to_recv_tcp4), + ok = api_to_recv_tcp(inet), tc_end(). @@ -705,20 +705,20 @@ to_recv_tcp4(_Config) when is_list(_Config) -> %% This test case is intended to test the recv timeout option %% on an IPv6 TCP (stream) socket. -to_recv_tcp6(suite) -> +api_to_recv_tcp6(suite) -> []; -to_recv_tcp6(doc) -> +api_to_recv_tcp6(doc) -> []; -to_recv_tcp6(_Config) when is_list(_Config) -> - %% tc_begin(to_recv_tcp6), - %% ok = to_recv_tcp(inet6), +api_to_recv_tcp6(_Config) when is_list(_Config) -> + %% tc_begin(api_to_recv_tcp6), + %% ok = api_to_recv_tcp(inet6), %% tc_end(). not_yet_implemented(). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -to_recv_tcp(Domain) -> +api_to_recv_tcp(Domain) -> process_flag(trap_exit, true), p("server -> open"), LSock = sock_open(Domain, stream, tcp), @@ -770,13 +770,13 @@ to_recv_tcp(Domain) -> %% This test case is intended to test the recvfrom timeout option %% on an IPv4 UDP (dgram) socket. -to_recvfrom_udp4(suite) -> +api_to_recvfrom_udp4(suite) -> []; -to_recvfrom_udp4(doc) -> +api_to_recvfrom_udp4(doc) -> []; -to_recvfrom_udp4(_Config) when is_list(_Config) -> - tc_begin(to_recvfrom_udp4), - ok = to_recvfrom_udp(inet), +api_to_recvfrom_udp4(_Config) when is_list(_Config) -> + tc_begin(api_to_recvfrom_udp4), + ok = api_to_recvfrom_udp(inet), tc_end(). @@ -784,20 +784,20 @@ to_recvfrom_udp4(_Config) when is_list(_Config) -> %% This test case is intended to test the recvfrom timeout option %% on an IPv6 UDP (dgram) socket. -to_recvfrom_udp6(suite) -> +api_to_recvfrom_udp6(suite) -> []; -to_recvfrom_udp6(doc) -> +api_to_recvfrom_udp6(doc) -> []; -to_recvfrom_udp6(_Config) when is_list(_Config) -> - %% tc_begin(to_recvfrom_udp6), - %% ok = to_recvfrom_udp(inet6), +api_to_recvfrom_udp6(_Config) when is_list(_Config) -> + %% tc_begin(api_to_recvfrom_udp6), + %% ok = api_to_recvfrom_udp(inet6), %% tc_end(). not_yet_implemented(). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -to_recvfrom_udp(Domain) -> +api_to_recvfrom_udp(Domain) -> process_flag(trap_exit, true), p("init"), LocalAddr = which_local_addr(Domain), @@ -823,14 +823,14 @@ to_recvfrom_udp(Domain) -> %% This test case is intended to test the recvmsg timeout option %% on an IPv4 UDP (dgram) socket. -to_recvmsg_udp4(suite) -> +api_to_recvmsg_udp4(suite) -> []; -to_recvmsg_udp4(doc) -> +api_to_recvmsg_udp4(doc) -> []; -to_recvmsg_udp4(_Config) when is_list(_Config) -> +api_to_recvmsg_udp4(_Config) when is_list(_Config) -> %% not_yet_implemented(). - tc_begin(to_recvmsg_udp4), - ok = to_recvmsg_udp(inet), + tc_begin(api_to_recvmsg_udp4), + ok = api_to_recvmsg_udp(inet), tc_end(). @@ -838,20 +838,20 @@ to_recvmsg_udp4(_Config) when is_list(_Config) -> %% This test case is intended to test the recvmsg timeout option %% on an IPv6 UDP (dgram) socket. -to_recvmsg_udp6(suite) -> +api_to_recvmsg_udp6(suite) -> []; -to_recvmsg_udp6(doc) -> +api_to_recvmsg_udp6(doc) -> []; -to_recvmsg_udp6(_Config) when is_list(_Config) -> - %% tc_begin(to_recvmsg_udp6), - %% ok = to_recvmsg_udp(inet6), +api_to_recvmsg_udp6(_Config) when is_list(_Config) -> + %% tc_begin(api_to_recvmsg_udp6), + %% ok = api_to_recvmsg_udp(inet6), %% tc_end(). not_yet_implemented(). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -to_recvmsg_udp(Domain) -> +api_to_recvmsg_udp(Domain) -> process_flag(trap_exit, true), p("init"), LocalAddr = which_local_addr(Domain), @@ -877,13 +877,13 @@ to_recvmsg_udp(Domain) -> %% This test case is intended to test the recvmsg timeout option %% on an IPv4 TCP (stream) socket. -to_recvmsg_tcp4(suite) -> +api_to_recvmsg_tcp4(suite) -> []; -to_recvmsg_tcp4(doc) -> +api_to_recvmsg_tcp4(doc) -> []; -to_recvmsg_tcp4(_Config) when is_list(_Config) -> - tc_begin(to_recvmsg_tcp4), - ok = to_recvmsg_tcp(inet), +api_to_recvmsg_tcp4(_Config) when is_list(_Config) -> + tc_begin(api_to_recvmsg_tcp4), + ok = api_to_recvmsg_tcp(inet), tc_end(). %% not_yet_implemented(). @@ -892,20 +892,20 @@ to_recvmsg_tcp4(_Config) when is_list(_Config) -> %% This test case is intended to test the recvmsg timeout option %% on an IPv6 TCP (stream) socket. -to_recvmsg_tcp6(suite) -> +api_to_recvmsg_tcp6(suite) -> []; -to_recvmsg_tcp6(doc) -> +api_to_recvmsg_tcp6(doc) -> []; -to_recvmsg_tcp6(_Config) when is_list(_Config) -> - %% tc_begin(to_recvmsg_tcp6), - %% ok = to_recvmsg_tcp(inet6), +api_to_recvmsg_tcp6(_Config) when is_list(_Config) -> + %% tc_begin(api_to_recvmsg_tcp6), + %% ok = api_to_recvmsg_tcp(inet6), %% tc_end(). not_yet_implemented(). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -to_recvmsg_tcp(Domain) -> +api_to_recvmsg_tcp(Domain) -> process_flag(trap_exit, true), p("server -> open"), LSock = sock_open(Domain, stream, tcp), -- cgit v1.2.3 From 4341dcd7ab14ae8af3d12a30563b5229015e320c Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Thu, 27 Sep 2018 12:40:35 +0200 Subject: [socket-nif|doc] SCTP type and sendto Added missing SCTP type (for assoc id) and updated sento doc (missing clauses). OTP-14831 --- erts/doc/src/socket.xml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/erts/doc/src/socket.xml b/erts/doc/src/socket.xml index 35f7e8502d..e2fd0adc8f 100644 --- a/erts/doc/src/socket.xml +++ b/erts/doc/src/socket.xml @@ -146,6 +146,9 @@ + + + @@ -481,7 +484,8 @@ - + + Send a message on a socket. -- cgit v1.2.3 From 0b56a98366fc152c0fa5d5398220ac31866114d5 Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Fri, 28 Sep 2018 18:34:45 +0200 Subject: [socket-nif|doc] Cleanup and fixed setopt doc Removed some cruft. Also tried, without success, to fix the build issue ("Error file: cannot find module exporting type"). It may be because of a spec-file issue. Finally fixed the setopt doc. It did not have a seperate clause for clause 8 (the fallback clause that takes level and key as integer). --- erts/doc/src/net.xml | 4 +- erts/doc/src/socket.xml | 272 ++++-------------------------------------------- 2 files changed, 23 insertions(+), 253 deletions(-) diff --git a/erts/doc/src/net.xml b/erts/doc/src/net.xml index c022fee4f7..e0c3b4ec73 100644 --- a/erts/doc/src/net.xml +++ b/erts/doc/src/net.xml @@ -75,7 +75,7 @@

Address-to-name translation in a protocol-independant manner.

This function is the inverse of - getaddrinfo. + getaddrinfo. It converts a socket address to a corresponding host and service.

@@ -89,7 +89,7 @@

Network address and service translation.

This function is the inverse of - getnameinfo. + getnameinfo. It converts host and service to a corresponding socket address.

One of the Host and Service may be undefined but not both.

diff --git a/erts/doc/src/socket.xml b/erts/doc/src/socket.xml index e2fd0adc8f..af7e0ca9c1 100644 --- a/erts/doc/src/socket.xml +++ b/erts/doc/src/socket.xml @@ -245,7 +245,8 @@ doing a close does not guarantee that any data sent is delivered to the recipient before the close is detected at the remote side.

One way to handle this is to use the - shutdown function + shutdown + function (socket:shutdown(Socket, write)) to signal that no more data is to be sent and then wait for the read side of the socket to be closed.

@@ -281,49 +282,6 @@ even if "we" support an option, that does not mean that the underlying OS does.

-
@@ -501,6 +459,24 @@ + Set options on a socket. + +

Set options on a socket.

+

What properties are valid depend both on Level and on + what kind of socket it is (domain, type and + protocol).

+ +

Not all options are valid on all platforms. That is, + even if "we" support an option, that does not mean that the + underlying OS does.

+ +

Sockets are set 'non-blocking' when created, so this option + is *not* available (as it would adversely effect the Erlang VM + to set a socket 'blocking').

+
+ + + Set options on a socket. @@ -539,213 +515,7 @@
Examples -

TBD: Need to implement a receiver process in order to be able to - implement active!

-

x_tcp.erl:

- -listen(Addr, Port) -> - try - begin - Socket = case socket:open(inet, stream, tcp) of - {ok, Socket} -> - Socket; - {error, _} = OERROR -> - throw(OERROR) - end, - SockAddr = #in4_sockaddr{port = Port, - addr = Addr}, - ok = case socket:bind(Socket, SockAddr) of - ok -> - ok; - {error, _} = BERROR -> - throw(BERROR) - end, - case socket:listen(Socket, 10) of - ok -> - {ok, Socket}; - {error, _} = LERROR -> - throw(LERROR) - end - end - catch - throw:ERROR -> - ERROR -end. - - - -connect(Addr, Port) -> - try - begin - Socket = case socket:open(inet, stream, tcp) of - {ok, Socket} -> - Socket; - {error, _} = OERROR -> - throw(OERROR) - end, - BSockAddr = #in4_sockaddr{port = 0, - addr = any}, - ok = case socket:bind(Socket, BSockAddr) of - ok -> - ok; - {error, _} = BERROR -> - throw(BERROR) - end, - CSockAddr = #in4_sockaddr{port = Port, - addr = Addr}, - Socket = case socket:connect(Socket, CSockAddr) of - {ok, Sock} -> - Sock; - {error, _} = CERROR -> - throw(CERROR) - end, - case start_receiver(Socket) of - {ok, Pid} -> - ok = socket:ts_add(Socket, receiver, Pid), - {ok, Socket}; - {error, _} = RERROR -> - socket:close(Socket), - throw(RERROR) - end - end - catch - throw:ERROR -> - ERROR -end. - - - -accept(LSocket) -> - case socket:accept(LSocket) of - {ok, Socket} -> - case start_receiver(Socket) of - {ok, Pid} -> - ok = socket:ts_add(Socket, receiver, Pid), - {ok, Socket}; - {error, _} = RERROR -> - socket:close(Socket), - throw(RERROR) - end, - {error, _} = AERROR -> - AERROR - end. - - - -send(Socket, Data) -> - socket:send(Socket, Data). - - - -setopt(Socket, controlling_process = Item, Pid) when is_pid(Pid) -> - case socket:setopt(Socket, otp, Item, Pid) of - ok -> - {ok, Receiver} = socket:ts_get(Socket, receiver), - update_receiver(Receiver, {controlling_process, Pid), - ok; - {error, _} = ERROR -> - ERROR - end; -setopt(Socket, active, Active) -> - {ok, Receiver} = socket:ts_get(Socket, receiver), - receiver_active(Receiver, Active), - ok; -%% This is just to indicate that there will be more options -%% that needs to be handled -setopt(Socket, Item, Pid) when is_pid(Pid) -> - socket:setopt(Socket, which_level(Item), Item, Pid). - - -

The receiver process:

- -start_receiver(Socket) -> - CtrlProc = self(), - Ref = make_ref(), - Receiver = proc_lib:spawn_link(fun() -> receiver_init(CtrlProc, Ref) end), - receive - {?MODULE, started, Ref} -> - Receiver ! {?MODULE, receiver, Ref, Sock, true}, - unlink(Receiver), - {ok, Receiver}; - {'EXIT', Receiver, Reason} -> - {error, Reason} - end. - -receiver_active(Receiver, Active) - when (Active =:= false) orelse - (Active =:= true) orelse - (Active =:= once) orelse - is_integer(Active) -> - Receiver ! {?MODULE, active, What}. - -receiver_update(Receiver, What) -> - Ref = make_ref(), - Receiver ! {?MODULE, receiver_update, Ref, What}, - receive - {?MODULE, receiver_upadate, Ref, Result} -> - Result - end. - --record(receiver, {sock :: socket:socket(), - ctrl :: pid(), - num_packages :: infinity | non_neg_ineteger()}). - -receiver_init(Pid, Ref) -> - receive - {?MODULE, receiver, Ref, stop} -> - i("received stop"), - exit(normal); - - {?MODULE, receiver, Ref, Sock, InitialMode} -> - i("received socket: ~p", [Sock]), - NumPackages = mode2pkgcount(InitialMode), - receiver(#receiver{sock = Sock, ctrl = Pid}) - end. - -mode2pkgcount(true) -> - infinity; -mode2pkgcount(false) -> - 0; -mode2pkgcount(N) when is_integer(N) andalso (N >= 0) -> - N. - -receiver(#receiver{num_packages = 0} = State) -> - receive - {?MODULE, active, false} -> - receiver(State); - {?MODULE, active, true} -> - receiver(State#receiver{num_packages = infinity}); - {?MODULE, active, once} -> - receiver(State#receiver{num_packages = 1}); - {?MODULE, active, N} when (N > 0) -> - receiver(State#receiver{num_packages = N}) - end; -receiver(#receiver{num_packages = N, sock = Sock, ctrl = Pid} = State) -> - case socket:recv(Sock, 0) of - {ok, Package} when size(Package) > 0 -> - Pid ! {tcp, Sock, Packege}, - case next_active(N) of - 0 -> - Pid ! {tcp_passive, Sock}, - receiver(State#{num_packages = 0}); - NextN -> - receiver(State#{num_packages = NextN}) - end; - {ok, Package} when size(Package) =:= 0 -> - receiver(State); - - {error, closed} -> - i("closed"), - Pid ! {tcp_closed, Sock}, - exit(normal); - - {error, Reason} -> - i("error: ~p", [Reason]), - Pid ! {tcp_error, Sock, Reason}, - exit(normal) - end. - - +

TBD

-- cgit v1.2.3 From 1c412c62ba3be17b7a818f264049a7ee7942351e Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Fri, 28 Sep 2018 18:40:08 +0200 Subject: [socket-nif] Add support for socket (level otp) buffer options Add support for otp level socket options rcvbuf, rcvctrlbuf and sndctrlbuf. These options define default sizes for these buffers. The 'rcvbuf' is used when receiving messages when calling the recv, recvfrom and recvmsg functions. The 'rcvctrlbuf' is used for the control message header info when calling the recvmsg function. The 'sndctrlbuf' is used for the control message header info when calling the sendmsg function. OTP-14831 --- erts/doc/src/socket_usage.xml | 21 +++++ erts/emulator/nifs/common/socket_nif.c | 146 ++++++++++++++++++++++++++++++++ erts/emulator/nifs/common/socket_util.c | 29 +++++++ erts/emulator/nifs/common/socket_util.h | 6 ++ erts/preloaded/ebin/socket.beam | Bin 66040 -> 66584 bytes erts/preloaded/src/socket.erl | 33 +++++++- lib/kernel/test/socket_SUITE.erl | 112 +++++++++++++++++++++++- 7 files changed, 342 insertions(+), 5 deletions(-) diff --git a/erts/doc/src/socket_usage.xml b/erts/doc/src/socket_usage.xml index 9785830637..23d0f319f1 100644 --- a/erts/doc/src/socket_usage.xml +++ b/erts/doc/src/socket_usage.xml @@ -81,6 +81,27 @@ yes none
+ + rcvbuf + default | pos_integer() + yes + yes + default only valid for set + + + rcvctrlbuf + default | pos_integer() + yes + yes + default only valid for set + + + sndctrlbuf + default | pos_integer() + yes + yes + default only valid for set +

Options for level socket:

diff --git a/erts/emulator/nifs/common/socket_nif.c b/erts/emulator/nifs/common/socket_nif.c index ccbcf63ece..876bed3135 100644 --- a/erts/emulator/nifs/common/socket_nif.c +++ b/erts/emulator/nifs/common/socket_nif.c @@ -501,6 +501,9 @@ typedef union { #define SOCKET_OPT_OTP_DEBUG 1 #define SOCKET_OPT_OTP_IOW 2 #define SOCKET_OPT_OTP_CTRL_PROC 3 +#define SOCKET_OPT_OTP_RCVBUF 4 +#define SOCKET_OPT_OTP_RCVCTRLBUF 6 +#define SOCKET_OPT_OTP_SNDCTRLBUF 7 #define SOCKET_OPT_SOCK_ACCEPTCONN 1 #define SOCKET_OPT_SOCK_BINDTODEVICE 3 @@ -1004,6 +1007,15 @@ static ERL_NIF_TERM nsetopt_otp_iow(ErlNifEnv* env, static ERL_NIF_TERM nsetopt_otp_ctrl_proc(ErlNifEnv* env, SocketDescriptor* descP, ERL_NIF_TERM eVal); +static ERL_NIF_TERM nsetopt_otp_rcvbuf(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); +static ERL_NIF_TERM nsetopt_otp_rcvctrlbuf(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); +static ERL_NIF_TERM nsetopt_otp_sndctrlbuf(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); static ERL_NIF_TERM nsetopt_native(ErlNifEnv* env, SocketDescriptor* descP, int level, @@ -1492,6 +1504,12 @@ static ERL_NIF_TERM ngetopt_otp_iow(ErlNifEnv* env, SocketDescriptor* descP); static ERL_NIF_TERM ngetopt_otp_ctrl_proc(ErlNifEnv* env, SocketDescriptor* descP); +static ERL_NIF_TERM ngetopt_otp_rcvbuf(ErlNifEnv* env, + SocketDescriptor* descP); +static ERL_NIF_TERM ngetopt_otp_rcvctrlbuf(ErlNifEnv* env, + SocketDescriptor* descP); +static ERL_NIF_TERM ngetopt_otp_sndctrlbuf(ErlNifEnv* env, + SocketDescriptor* descP); static ERL_NIF_TERM ngetopt_native(ErlNifEnv* env, SocketDescriptor* descP, int level, @@ -5001,6 +5019,18 @@ ERL_NIF_TERM nsetopt_otp(ErlNifEnv* env, result = nsetopt_otp_ctrl_proc(env, descP, eVal); break; + case SOCKET_OPT_OTP_RCVBUF: + result = nsetopt_otp_rcvbuf(env, descP, eVal); + break; + + case SOCKET_OPT_OTP_RCVCTRLBUF: + result = nsetopt_otp_rcvctrlbuf(env, descP, eVal); + break; + + case SOCKET_OPT_OTP_SNDCTRLBUF: + result = nsetopt_otp_sndctrlbuf(env, descP, eVal); + break; + default: result = esock_make_error(env, esock_atom_einval); break; @@ -5079,6 +5109,74 @@ ERL_NIF_TERM nsetopt_otp_ctrl_proc(ErlNifEnv* env, +/* nsetopt_otp_rcvbuf - Handle the OTP (level) rcvbuf option + */ +static +ERL_NIF_TERM nsetopt_otp_rcvbuf(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) +{ + size_t val; + char* xres; + + if ((xres = esock_decode_bufsz(env, + eVal, + SOCKET_RECV_BUFFER_SIZE_DEFAULT, &val)) != NULL) + return esock_make_error_str(env, xres); + + descP->rBufSz = val; + + return esock_atom_ok; +} + + + +/* nsetopt_otp_rcvctrlbuf - Handle the OTP (level) rcvctrlbuf option + */ +static +ERL_NIF_TERM nsetopt_otp_rcvctrlbuf(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) +{ + size_t val; + char* xres; + + if ((xres = esock_decode_bufsz(env, + eVal, + SOCKET_RECV_CTRL_BUFFER_SIZE_DEFAULT, + &val)) != NULL) + return esock_make_error_str(env, xres); + + descP->rCtrlSz = val; + + return esock_atom_ok; +} + + + +/* nsetopt_otp_sndctrlbuf - Handle the OTP (level) sndctrlbuf option + */ +static +ERL_NIF_TERM nsetopt_otp_sndctrlbuf(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) +{ + size_t val; + char* xres; + + if ((xres = esock_decode_bufsz(env, + eVal, + SOCKET_SEND_CTRL_BUFFER_SIZE_DEFAULT, + &val)) != NULL) + return esock_make_error_str(env, xres); + + descP->wCtrlSz = val; + + return esock_atom_ok; +} + + + /* The option has *not* been encoded. Instead it has been provided * in "native mode" (option is provided as is and value as a binary). */ @@ -8169,6 +8267,18 @@ ERL_NIF_TERM ngetopt_otp(ErlNifEnv* env, result = ngetopt_otp_ctrl_proc(env, descP); break; + case SOCKET_OPT_OTP_RCVBUF: + result = ngetopt_otp_rcvbuf(env, descP); + break; + + case SOCKET_OPT_OTP_RCVCTRLBUF: + result = ngetopt_otp_rcvctrlbuf(env, descP); + break; + + case SOCKET_OPT_OTP_SNDCTRLBUF: + result = ngetopt_otp_sndctrlbuf(env, descP); + break; + default: result = esock_make_error(env, esock_atom_einval); break; @@ -8220,6 +8330,42 @@ ERL_NIF_TERM ngetopt_otp_ctrl_proc(ErlNifEnv* env, +/* ngetopt_otp_rcvbuf - Handle the OTP (level) rcvbuf options + */ +static +ERL_NIF_TERM ngetopt_otp_rcvbuf(ErlNifEnv* env, + SocketDescriptor* descP) +{ + ERL_NIF_TERM eVal = MKI(env, descP->rBufSz); + + return esock_make_ok2(env, eVal); +} + + +/* ngetopt_otp_rcvctrlbuf - Handle the OTP (level) rcvctrlbuf options + */ +static +ERL_NIF_TERM ngetopt_otp_rcvctrlbuf(ErlNifEnv* env, + SocketDescriptor* descP) +{ + ERL_NIF_TERM eVal = MKI(env, descP->rCtrlSz); + + return esock_make_ok2(env, eVal); +} + + +/* ngetopt_otp_sndctrlbuf - Handle the OTP (level) sndctrlbuf options + */ +static +ERL_NIF_TERM ngetopt_otp_sndctrlbuf(ErlNifEnv* env, + SocketDescriptor* descP) +{ + ERL_NIF_TERM eVal = MKI(env, descP->wCtrlSz); + + return esock_make_ok2(env, eVal); +} + + /* The option has *not* been encoded. Instead it has been provided * in "native mode" (option is provided as is). In this case it will have the * format: {NativeOpt :: integer(), ValueSize :: non_neg_integer()} diff --git a/erts/emulator/nifs/common/socket_util.c b/erts/emulator/nifs/common/socket_util.c index 8bb725fb5b..ff50fd2384 100644 --- a/erts/emulator/nifs/common/socket_util.c +++ b/erts/emulator/nifs/common/socket_util.c @@ -1261,6 +1261,35 @@ char* esock_decode_protocol(ErlNifEnv* env, +/* +++ esock_decode_bufsz +++ + * + * Decode an buffer size. The size of a buffer is: + * + * Sz > 0 => Use provided value + * Sz => Use provided default + * + */ +extern +char* esock_decode_bufsz(ErlNifEnv* env, + ERL_NIF_TERM eVal, + size_t defSz, + size_t* sz) +{ + int val; + + if (!GET_INT(env, eVal, &val)) + return ESOCK_STR_EINVAL; + + if (val > 0) + *sz = (size_t) val; + else + *sz = defSz; + + return NULL; +} + + + /* *** esock_decode_string *** * * Decode a string value. A successful decode results in an diff --git a/erts/emulator/nifs/common/socket_util.h b/erts/emulator/nifs/common/socket_util.h index a38453e238..1b5d003155 100644 --- a/erts/emulator/nifs/common/socket_util.h +++ b/erts/emulator/nifs/common/socket_util.h @@ -157,6 +157,12 @@ char* esock_encode_protocol(ErlNifEnv* env, int type, ERL_NIF_TERM* eProtocol); +extern +char* esock_decode_bufsz(ErlNifEnv* env, + ERL_NIF_TERM eVal, + size_t defSz, + size_t* sz); + extern BOOLEAN_T esock_decode_string(ErlNifEnv* env, const ERL_NIF_TERM eString, diff --git a/erts/preloaded/ebin/socket.beam b/erts/preloaded/ebin/socket.beam index 9e6d9f4709..a0550990e3 100644 Binary files a/erts/preloaded/ebin/socket.beam and b/erts/preloaded/ebin/socket.beam differ diff --git a/erts/preloaded/src/socket.erl b/erts/preloaded/src/socket.erl index 652054457f..8093bad885 100644 --- a/erts/preloaded/src/socket.erl +++ b/erts/preloaded/src/socket.erl @@ -320,12 +320,15 @@ %% Should we just document it and leave it to the user? %% Or catch it in the encode functions? %% A setopt for a readonly option leads to einval? +%% Do we really need a sndbuf? -type otp_socket_option() :: debug | iow | controlling_process | rcvbuf | - sndbuf. + sndbuf | + rcvctrlbuf | + sndctrlbuf. %% Shall we have special treatment of linger?? %% read-only options: %% domain | protocol | type. @@ -645,6 +648,10 @@ -define(SOCKET_OPT_OTP_DEBUG, 1). -define(SOCKET_OPT_OTP_IOW, 2). -define(SOCKET_OPT_OTP_CTRL_PROC, 3). +-define(SOCKET_OPT_OTP_RCVBUF, 4). +%%-define(SOCKET_OPT_OTP_SNDBUF, 5). +-define(SOCKET_OPT_OTP_RCVCTRLBUF, 6). +-define(SOCKET_OPT_OTP_SNDCTRLBUF, 7). %% *** SOCKET (socket) options -define(SOCKET_OPT_SOCK_ACCEPTCONN, 1). @@ -2136,9 +2143,9 @@ getopt(#socket{ref = SockRef}, Level, Key) -> end end catch - throw:T -> - T; - error:Reason -> + throw:E:_S -> + E; + error:Reason:_Stack -> {error, Reason} % Process more? end. @@ -2401,6 +2408,18 @@ enc_setopt_value(otp, iow, V, _, _, _) when is_boolean(V) -> V; enc_setopt_value(otp, controlling_process, V, _, _, _) when is_pid(V) -> V; +enc_setopt_value(otp, rcvbuf, V, _, _, _) when (V =:= default) -> + 0; +enc_setopt_value(otp, rcvbuf, V, _, _, _) when is_integer(V) andalso (V > 0) -> + V; +enc_setopt_value(otp, rcvctrlbuf, V, _, _, _) when (V =:= default) -> + 0; +enc_setopt_value(otp, rcvctrlbuf, V, _, _, _) when is_integer(V) andalso (V > 0) -> + V; +enc_setopt_value(otp, sndctrlbuf, V, _, _, _) when (V =:= default) -> + 0; +enc_setopt_value(otp, sndctrlbuf, V, _, _, _) when is_integer(V) andalso (V > 0) -> + V; enc_setopt_value(otp = L, Opt, V, _D, _T, _P) -> not_supported({L, Opt, V}); @@ -2871,6 +2890,12 @@ enc_sockopt_key(otp, iow, _, _, _, _) -> ?SOCKET_OPT_OTP_IOW; enc_sockopt_key(otp, controlling_process, _, _, _, _) -> ?SOCKET_OPT_OTP_CTRL_PROC; +enc_sockopt_key(otp, rcvbuf, _, _, _, _) -> + ?SOCKET_OPT_OTP_RCVBUF; +enc_sockopt_key(otp, rcvctrlbuf, _, _, _, _) -> + ?SOCKET_OPT_OTP_RCVCTRLBUF; +enc_sockopt_key(otp, sndctrlbuf, _, _, _, _) -> + ?SOCKET_OPT_OTP_SNDCTRLBUF; enc_sockopt_key(otp = L, Opt, _, _, _, _) -> not_supported({L, Opt}); diff --git a/lib/kernel/test/socket_SUITE.erl b/lib/kernel/test/socket_SUITE.erl index 0255c8ef75..d4cd144168 100644 --- a/lib/kernel/test/socket_SUITE.erl +++ b/lib/kernel/test/socket_SUITE.erl @@ -38,6 +38,9 @@ api_b_send_and_recv_tcp4/1, api_b_sendmsg_and_recvmsg_tcp4/1, + %% API Options + api_opt_simple_otp_options/1, + %% API Operation Timeout api_to_connect_tcp4/1, api_to_connect_tcp6/1, @@ -90,13 +93,15 @@ all() -> groups() -> [{api, [], api_cases()}, {api_basic, [], api_basic_cases()}, - {api_op_with_timeout, [], api_op_with_timeout_cases()} + {api_op_with_timeout, [], api_op_with_timeout_cases()}, + {api_options, [], api_options_cases()} %% {tickets, [], ticket_cases()} ]. api_cases() -> [ {group, api_basic}, + {group, api_options}, {group, api_op_with_timeout} ]. @@ -110,6 +115,11 @@ api_basic_cases() -> api_b_sendmsg_and_recvmsg_tcp4 ]. +api_options_cases() -> + [ + api_opt_simple_otp_options + ]. + api_op_with_timeout_cases() -> [ api_to_connect_tcp4, @@ -431,6 +441,92 @@ api_b_send_and_recv_tcp(Domain, Send, Recv) -> +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Perform some simple getopt and setopt with the level = otp options +api_opt_simple_otp_options(suite) -> + []; +api_opt_simple_otp_options(doc) -> + []; +api_opt_simple_otp_options(_Config) when is_list(_Config) -> + tc_begin(api_opt_simple_otp_options), + + p("Create sockets"), + S1 = sock_open(inet, stream, tcp), + S2 = sock_open(inet, dgram, udp), + + Get = fun(S, Key) -> + socket:getopt(S, otp, Key) + end, + Set = fun(S, Key, Val) -> + socket:setopt(S, otp, Key, Val) + end, + + p("Create dummy process"), + Pid = spawn_link(fun() -> + receive + die -> + exit(normal) + end + end), + + F = fun(Sock) -> + p("Test IOW"), + {ok, IOW} = Get(Sock, iow), + NotIOW = not IOW, + ok = Set(Sock, iow, NotIOW), + {ok, NotIOW} = Get(Sock, iow), + + p("Test rcvbuf"), + {ok, RcvBuf} = Get(Sock, rcvbuf), + RcvBuf2 = RcvBuf*2, + ok = Set(Sock, rcvbuf, RcvBuf2), + {ok, RcvBuf2} = Get(Sock, rcvbuf), + ok = Set(Sock, rcvbuf, default), + {ok, RcvBuf} = Get(Sock, rcvbuf), + + p("Test rcvctrlbuf"), + {ok, RcvCtrlBuf} = Get(Sock, rcvctrlbuf), + RcvCtrlBuf2 = RcvCtrlBuf*2, + ok = Set(Sock, rcvctrlbuf, RcvCtrlBuf2), + {ok, RcvCtrlBuf2} = Get(Sock, rcvctrlbuf), + ok = Set(Sock, rcvctrlbuf, default), + {ok, RcvCtrlBuf} = Get(Sock, rcvctrlbuf), + + p("Test sndctrlbuf"), + {ok, SndCtrlBuf} = Get(Sock, sndctrlbuf), + SndCtrlBuf2 = SndCtrlBuf*2, + ok = Set(Sock, sndctrlbuf, SndCtrlBuf2), + {ok, SndCtrlBuf2} = Get(Sock, sndctrlbuf), + ok = Set(Sock, sndctrlbuf, default), + {ok, RcvCtrlBuf} = Get(Sock, sndctrlbuf), + + p("Test controlling-process"), + Self = self(), + {ok, Self} = Get(Sock, controlling_process), + ok = Set(Sock, controlling_process, Pid), + {ok, Pid} = Get(Sock, controlling_process) + + end, + + p("Test stream/tcp "), + F(S1), + + p("Test dgram/udp "), + F(S2), + + p("kill dummy process"), + %% This will also close its sockets (S1 and S2), + %% This should really be tested explicitly... + Pid ! die, + + %% p("close sockets"), + %% sock_close(S1), + %% sock_close(S2), + + tc_end(). + + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% This test case is intended to test the connect timeout option @@ -1067,6 +1163,20 @@ sock_accept(LSock) -> end. +sock_close(Sock) -> + try socket:close(Sock) of + ok -> + ok; + {error, Reason} -> + p("sock_close -> error: ~p", [Reason]), + ?FAIL({close, Reason}) + catch + C:E:S -> + p("sock_close -> failed: ~p, ~p, ~p", [C, E, S]), + ?FAIL({close, C, E, S}) + end. + + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -- cgit v1.2.3 From 4b1e1e148a5cc9c19127b61aaa3e15eeeaea6cb2 Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Thu, 4 Oct 2018 10:06:16 +0200 Subject: [socket-nif] Socket option 'SO_DOMAIN' not avalable on all platforms Internally in the socket module we accessed domain, type and protocol for an open socket. But as it turns out, domain (family) is not actually available as a socket option on all platforms. FreeBSD in this case. So, since we store these values in the socket descriptor anyway, we switch to use these values for our internal use instead. OTP-14831 --- erts/doc/src/socket_usage.xml | 2 +- erts/emulator/nifs/common/socket_nif.c | 146 ++++++++++++++++++++++++++++++++- erts/preloaded/ebin/socket.beam | Bin 66584 -> 66596 bytes erts/preloaded/src/socket.erl | 13 +-- 4 files changed, 152 insertions(+), 9 deletions(-) diff --git a/erts/doc/src/socket_usage.xml b/erts/doc/src/socket_usage.xml index 23d0f319f1..1ea29097c3 100644 --- a/erts/doc/src/socket_usage.xml +++ b/erts/doc/src/socket_usage.xml @@ -146,7 +146,7 @@ domain() no yes - none + Not on FreeBSD (for instance) dontroute diff --git a/erts/emulator/nifs/common/socket_nif.c b/erts/emulator/nifs/common/socket_nif.c index 876bed3135..b821fd6575 100644 --- a/erts/emulator/nifs/common/socket_nif.c +++ b/erts/emulator/nifs/common/socket_nif.c @@ -319,8 +319,8 @@ static void (*esock_sctp_freepaddrs)(struct sockaddr *addrs) = NULL; /* Debug stuff... */ -#define SOCKET_NIF_DEBUG_DEFAULT FALSE -#define SOCKET_DEBUG_DEFAULT FALSE +#define SOCKET_GLOBAL_DEBUG_DEFAULT FALSE +#define SOCKET_DEBUG_DEFAULT FALSE /* Counters and stuff (Don't know where to sent this stuff anyway) */ #define SOCKET_NIF_IOW_DEFAULT FALSE @@ -504,6 +504,9 @@ typedef union { #define SOCKET_OPT_OTP_RCVBUF 4 #define SOCKET_OPT_OTP_RCVCTRLBUF 6 #define SOCKET_OPT_OTP_SNDCTRLBUF 7 +#define SOCKET_OPT_OTP_DOMAIN 0xFF01 // INTERNAL AND ONLY GET +#define SOCKET_OPT_OTP_TYPE 0xFF02 // INTERNAL AND ONLY GET +#define SOCKET_OPT_OTP_PROTOCOL 0xFF03 // INTERNAL AND ONLY GET #define SOCKET_OPT_SOCK_ACCEPTCONN 1 #define SOCKET_OPT_SOCK_BINDTODEVICE 3 @@ -1510,6 +1513,12 @@ static ERL_NIF_TERM ngetopt_otp_rcvctrlbuf(ErlNifEnv* env, SocketDescriptor* descP); static ERL_NIF_TERM ngetopt_otp_sndctrlbuf(ErlNifEnv* env, SocketDescriptor* descP); +static ERL_NIF_TERM ngetopt_otp_domain(ErlNifEnv* env, + SocketDescriptor* descP); +static ERL_NIF_TERM ngetopt_otp_type(ErlNifEnv* env, + SocketDescriptor* descP); +static ERL_NIF_TERM ngetopt_otp_protocol(ErlNifEnv* env, + SocketDescriptor* descP); static ERL_NIF_TERM ngetopt_native(ErlNifEnv* env, SocketDescriptor* descP, int level, @@ -8279,6 +8288,19 @@ ERL_NIF_TERM ngetopt_otp(ErlNifEnv* env, result = ngetopt_otp_sndctrlbuf(env, descP); break; + /* *** INTERNAL *** */ + case SOCKET_OPT_OTP_DOMAIN: + result = ngetopt_otp_domain(env, descP); + break; + + case SOCKET_OPT_OTP_TYPE: + result = ngetopt_otp_type(env, descP); + break; + + case SOCKET_OPT_OTP_PROTOCOL: + result = ngetopt_otp_protocol(env, descP); + break; + default: result = esock_make_error(env, esock_atom_einval); break; @@ -8366,6 +8388,124 @@ ERL_NIF_TERM ngetopt_otp_sndctrlbuf(ErlNifEnv* env, } +/* ngetopt_otp_domain - Handle the OTP (level) domain options. + */ +static +ERL_NIF_TERM ngetopt_otp_domain(ErlNifEnv* env, + SocketDescriptor* descP) +{ + ERL_NIF_TERM result; + int val = descP->domain; + + switch (val) { + case AF_INET: + result = esock_make_ok2(env, esock_atom_inet); + break; + +#if defined(HAVE_IN6) && defined(AF_INET6) + case AF_INET6: + result = esock_make_ok2(env, esock_atom_inet6); + break; +#endif + +#if defined(HAVE_SYS_UN_H) + case AF_UNIX: + result = esock_make_ok2(env, esock_atom_local); + break; +#endif + + default: + result = esock_make_error(env, + MKT2(env, + esock_atom_unknown, + MKI(env, val))); + break; + } + + return result; +} + + +/* ngetopt_otp_type - Handle the OTP (level) type options. + */ +static +ERL_NIF_TERM ngetopt_otp_type(ErlNifEnv* env, + SocketDescriptor* descP) +{ + ERL_NIF_TERM result; + int val = descP->type; + + switch (val) { + case SOCK_STREAM: + result = esock_make_ok2(env, esock_atom_stream); + break; + + case SOCK_DGRAM: + result = esock_make_ok2(env, esock_atom_dgram); + break; + +#ifdef HAVE_SCTP + case SOCK_SEQPACKET: + result = esock_make_ok2(env, esock_atom_seqpacket); + break; +#endif + case SOCK_RAW: + result = esock_make_ok2(env, esock_atom_raw); + break; + + case SOCK_RDM: + result = esock_make_ok2(env, esock_atom_rdm); + break; + + default: + result = esock_make_error(env, + MKT2(env, esock_atom_unknown, MKI(env, val))); + break; + } + + return result; +} + + +/* ngetopt_otp_protocol - Handle the OTP (level) protocol options. + */ +static +ERL_NIF_TERM ngetopt_otp_protocol(ErlNifEnv* env, + SocketDescriptor* descP) +{ + ERL_NIF_TERM result; + int val = descP->protocol; + + switch (val) { + case IPPROTO_IP: + result = esock_make_ok2(env, esock_atom_ip); + break; + + case IPPROTO_TCP: + result = esock_make_ok2(env, esock_atom_tcp); + break; + + case IPPROTO_UDP: + result = esock_make_ok2(env, esock_atom_udp); + break; + +#if defined(HAVE_SCTP) + case IPPROTO_SCTP: + result = esock_make_ok2(env, esock_atom_sctp); + break; +#endif + + default: + result = esock_make_error(env, + MKT2(env, esock_atom_unknown, MKI(env, val))); + break; + } + + return result; +} + + + /* The option has *not* been encoded. Instead it has been provided * in "native mode" (option is provided as is). In this case it will have the * format: {NativeOpt :: integer(), ValueSize :: non_neg_integer()} @@ -15279,7 +15419,7 @@ BOOLEAN_T extract_debug(ErlNifEnv* env, */ ERL_NIF_TERM debug = MKA(env, "debug"); - return esock_extract_bool_from_map(env, map, debug, SOCKET_NIF_DEBUG_DEFAULT); + return esock_extract_bool_from_map(env, map, debug, SOCKET_GLOBAL_DEBUG_DEFAULT); } static diff --git a/erts/preloaded/ebin/socket.beam b/erts/preloaded/ebin/socket.beam index a0550990e3..9c7bcf89b5 100644 Binary files a/erts/preloaded/ebin/socket.beam and b/erts/preloaded/ebin/socket.beam differ diff --git a/erts/preloaded/src/socket.erl b/erts/preloaded/src/socket.erl index 8093bad885..c388fc2849 100644 --- a/erts/preloaded/src/socket.erl +++ b/erts/preloaded/src/socket.erl @@ -652,6 +652,9 @@ %%-define(SOCKET_OPT_OTP_SNDBUF, 5). -define(SOCKET_OPT_OTP_RCVCTRLBUF, 6). -define(SOCKET_OPT_OTP_SNDCTRLBUF, 7). +-define(SOCKET_OPT_OTP_DOMAIN, 16#FF01). % INTERNAL +-define(SOCKET_OPT_OTP_TYPE, 16#FF02). % INTERNAL +-define(SOCKET_OPT_OTP_PROTOCOL, 16#FF03). % INTERNAL %% *** SOCKET (socket) options -define(SOCKET_OPT_SOCK_ACCEPTCONN, 1). @@ -2159,7 +2162,7 @@ getopt(#socket{ref = SockRef}, Level, Key) -> which_domain(SockRef) -> case nif_getopt(SockRef, true, - ?SOCKET_OPT_LEVEL_SOCKET, ?SOCKET_OPT_SOCK_DOMAIN) of + ?SOCKET_OPT_LEVEL_OTP, ?SOCKET_OPT_OTP_DOMAIN) of {ok, Domain} -> Domain; {error, _} = ERROR -> @@ -2173,7 +2176,7 @@ which_domain(SockRef) -> which_type(SockRef) -> case nif_getopt(SockRef, true, - ?SOCKET_OPT_LEVEL_SOCKET, ?SOCKET_OPT_SOCK_TYPE) of + ?SOCKET_OPT_LEVEL_OTP, ?SOCKET_OPT_OTP_TYPE) of {ok, Type} -> Type; {error, _} = ERROR -> @@ -2186,9 +2189,9 @@ which_type(SockRef) -> which_protocol(SockRef) -> case nif_getopt(SockRef, true, - ?SOCKET_OPT_LEVEL_SOCKET, ?SOCKET_OPT_SOCK_PROTOCOL) of - {ok, Type} -> - Type; + ?SOCKET_OPT_LEVEL_OTP, ?SOCKET_OPT_OTP_PROTOCOL) of + {ok, Proto} -> + Proto; {error, _} = ERROR -> throw(ERROR) end. -- cgit v1.2.3 From 6b43b24610329f9b1201c36cc4dfcb346ed0317c Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Thu, 4 Oct 2018 12:01:35 +0200 Subject: [socket-nif] Fixed decode of send and recv flags Add explicitly handling the default case, when not flags are provided (value of zero). OTP-14831 --- erts/emulator/nifs/common/socket_nif.c | 53 ++++++++++++++++++++++++++-------- 1 file changed, 41 insertions(+), 12 deletions(-) diff --git a/erts/emulator/nifs/common/socket_nif.c b/erts/emulator/nifs/common/socket_nif.c index b821fd6575..4fd10e1ae6 100644 --- a/erts/emulator/nifs/common/socket_nif.c +++ b/erts/emulator/nifs/common/socket_nif.c @@ -3052,26 +3052,36 @@ static ERL_NIF_TERM nconnect(ErlNifEnv* env, SocketDescriptor* descP) { - int code; + int code, save_errno = 0; /* Verify that we are where in the proper state */ - if (!IS_OPEN(descP)) + if (!IS_OPEN(descP)) { + SSDBG( descP, ("SOCKET", "nif_sendto -> not open\r\n") ); return esock_make_error(env, atom_exbadstate); + } - if (IS_CONNECTED(descP)) + if (IS_CONNECTED(descP)) { + SSDBG( descP, ("SOCKET", "nif_sendto -> already connected\r\n") ); return esock_make_error(env, atom_eisconn); + } - if (IS_CONNECTING(descP)) + if (IS_CONNECTING(descP)) { + SSDBG( descP, ("SOCKET", "nif_sendto -> already connecting\r\n") ); return esock_make_error(env, esock_atom_einval); - + } + code = sock_connect(descP->sock, (struct sockaddr*) &descP->remote, descP->addrLen); + save_errno = sock_errno(); + + SSDBG( descP, ("SOCKET", "nif_sendto -> connect result: %d, %d\r\n", + code, save_errno) ); if (IS_SOCKET_ERROR(code) && - ((sock_errno() == ERRNO_BLOCK) || /* Winsock2 */ - (sock_errno() == EINPROGRESS))) { /* Unix & OSE!! */ + ((save_errno == ERRNO_BLOCK) || /* Winsock2 */ + (save_errno == EINPROGRESS))) { /* Unix & OSE!! */ ERL_NIF_TERM ref = MKREF(env); descP->state = SOCKET_STATE_CONNECTING; SELECT(env, @@ -3086,7 +3096,7 @@ ERL_NIF_TERM nconnect(ErlNifEnv* env, */ return esock_atom_ok; } else { - return esock_make_error_errno(env, sock_errno()); + return esock_make_error_errno(env, save_errno); } } @@ -3800,16 +3810,22 @@ ERL_NIF_TERM nif_sendto(ErlNifEnv* env, descP->sock, argv[0], sendRef, sndData.size, eSockAddr, eflags) ); /* THIS TEST IS NOT CORRECT!!! */ - if (!IS_OPEN(descP)) + if (!IS_OPEN(descP)) { + SSDBG( descP, ("SOCKET", "nif_sendto -> not open (%u)\r\n", descP->state) ); return esock_make_error(env, esock_atom_einval); + } - if (!esendflags2sendflags(eflags, &flags)) + if (!esendflags2sendflags(eflags, &flags)) { + SSDBG( descP, ("SOCKET", "nif_sendto -> sendflags decode failed\r\n") ); return esock_make_error(env, esock_atom_einval); + } if ((xres = esock_decode_sockaddr(env, eSockAddr, &remoteAddr, - &remoteAddrLen)) != NULL) + &remoteAddrLen)) != NULL) { + SSDBG( descP, ("SOCKET", "nif_sendto -> sockaddr decode: %s\r\n", xres) ); return esock_make_error_str(env, xres); + } MLOCK(descP->writeMtx); @@ -4345,8 +4361,10 @@ ERL_NIF_TERM nif_recvfrom(ErlNifEnv* env, /* if (IS_OPEN(descP)) */ /* return esock_make_error(env, atom_enotconn); */ - if (!erecvflags2recvflags(eflags, &flags)) + if (!erecvflags2recvflags(eflags, &flags)) { + SSDBG( descP, ("SOCKET", "nif_recvfrom -> recvflags decode failed\r\n") ); return enif_make_badarg(env); + } MLOCK(descP->readMtx); @@ -14198,6 +14216,12 @@ BOOLEAN_T esendflags2sendflags(unsigned int eflags, int* flags) unsigned int ef; int tmp = 0; + /* First, check if we have any flags at all */ + if (eflags == 0) { + *flags = 0; + return TRUE; + } + for (ef = SOCKET_SEND_FLAG_LOW; ef <= SOCKET_SEND_FLAG_HIGH; ef++) { switch (ef) { @@ -14269,6 +14293,11 @@ BOOLEAN_T erecvflags2recvflags(unsigned int eflags, int* flags) "\r\n eflags: %d" "\r\n", eflags) ); + if (eflags == 0) { + *flags = 0; + return TRUE; + } + for (ef = SOCKET_RECV_FLAG_LOW; ef <= SOCKET_RECV_FLAG_HIGH; ef++) { SGDBG( ("SOCKET", "erecvflags2recvflags -> iteration" -- cgit v1.2.3 From e698436942c7aaf4f2872c19df2555275be169d1 Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Thu, 4 Oct 2018 12:03:35 +0200 Subject: [socket-nif|test] Updated test case for connect timeout The connect timeout test case uses the listen backlog argument to test connect timeout (its set to 1). The idea is that when the server backlog is full, it should not reply. The client attempting to connect will then get a timeout. But as it turns out, this behaviour is not "set in stone". On FreeBSD 11.2, the result is instead a 'econnreset'. It should also be noted that this local connections. That is, both end of the connection is on the same machine. So, test case has been updated to also accept a econnreset (even though that is not what it is testing). OTP-14831 --- lib/kernel/test/socket_SUITE.erl | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/lib/kernel/test/socket_SUITE.erl b/lib/kernel/test/socket_SUITE.erl index d4cd144168..f3a7d84e81 100644 --- a/lib/kernel/test/socket_SUITE.erl +++ b/lib/kernel/test/socket_SUITE.erl @@ -203,7 +203,15 @@ api_b_open_and_close(Domain, Type, Proto) -> {error, Reason} -> ?FAIL({open, Reason}) end, - {ok, Domain} = socket:getopt(Socket, socket, domain), + %% Domain is not available on all platforms: + case socket:getopt(Socket, socket, domain) of + {ok, Domain} -> + ok; + {error, einval} -> + ok; + Else -> + ?FAIL({getopt, domain, Else}) + end, {ok, Type} = socket:getopt(Socket, socket, type), {ok, Proto} = socket:getopt(Socket, socket, protocol), Self = self(), @@ -558,6 +566,11 @@ api_to_connect_tcp6(_Config) when is_list(_Config) -> %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% We use the backlog (listen) argument to test this. +%% Note that the behaviour of the TCP "server side" can vary when +%% a client connect to a "busy" server (full backlog). +%% For instance, on FreeBSD (11.2) the reponse when the backlog is full +%% is a econreset. api_to_connect_tcp(Domain) -> process_flag(trap_exit, true), @@ -621,6 +634,9 @@ api_to_connect_tcp_await_timeout([Sock|Socks], ServerSA, ID) -> {error, timeout} -> p("expected timeout (~w)", [ID]), ok; + {error, econnreset = Reason} -> + p("failed connecting: ~p - giving up", [Reason]), + ok; {error, Reason} -> p("failed connecting: ~p", [Reason]), ?FAIL({recv, Reason}); -- cgit v1.2.3 From 352b28117de7e523ea09fd2b6a5066d8ffd1156b Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Thu, 4 Oct 2018 15:24:13 +0200 Subject: [socket-nif] Add owner validation when setopt controlling_process Before allowing setopt(Sock, opt, controlling_process, NewPid), verify that the caller is actually the current controlling_process. OTP-14831 --- erts/emulator/nifs/common/socket_int.h | 11 +++++++++++ erts/emulator/nifs/common/socket_nif.c | 14 +++++++++++++- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/erts/emulator/nifs/common/socket_int.h b/erts/emulator/nifs/common/socket_int.h index c3595e495d..f9246856fa 100644 --- a/erts/emulator/nifs/common/socket_int.h +++ b/erts/emulator/nifs/common/socket_int.h @@ -103,8 +103,10 @@ typedef unsigned int BOOLEAN_T; /* ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ * "Global" atoms */ +extern ERL_NIF_TERM esock_atom_accept; extern ERL_NIF_TERM esock_atom_addr; extern ERL_NIF_TERM esock_atom_any; +extern ERL_NIF_TERM esock_atom_connect; extern ERL_NIF_TERM esock_atom_credentials; extern ERL_NIF_TERM esock_atom_ctrl; extern ERL_NIF_TERM esock_atom_ctrunc; @@ -129,6 +131,8 @@ extern ERL_NIF_TERM esock_atom_local; extern ERL_NIF_TERM esock_atom_loopback; extern ERL_NIF_TERM esock_atom_lowdelay; extern ERL_NIF_TERM esock_atom_mincost; +extern ERL_NIF_TERM esock_atom_not_found; +extern ERL_NIF_TERM esock_atom_not_owner; extern ERL_NIF_TERM esock_atom_ok; extern ERL_NIF_TERM esock_atom_oob; extern ERL_NIF_TERM esock_atom_origdstaddr; @@ -138,11 +142,18 @@ extern ERL_NIF_TERM esock_atom_port; extern ERL_NIF_TERM esock_atom_protocol; extern ERL_NIF_TERM esock_atom_raw; extern ERL_NIF_TERM esock_atom_rdm; +extern ERL_NIF_TERM esock_atom_recv; +extern ERL_NIF_TERM esock_atom_recvfrom; +extern ERL_NIF_TERM esock_atom_recvmsg; extern ERL_NIF_TERM esock_atom_reliability; extern ERL_NIF_TERM esock_atom_rights; extern ERL_NIF_TERM esock_atom_scope_id; extern ERL_NIF_TERM esock_atom_sctp; extern ERL_NIF_TERM esock_atom_sec; +extern ERL_NIF_TERM esock_atom_select_sent; +extern ERL_NIF_TERM esock_atom_send; +extern ERL_NIF_TERM esock_atom_sendmsg; +extern ERL_NIF_TERM esock_atom_sendto; extern ERL_NIF_TERM esock_atom_seqpacket; extern ERL_NIF_TERM esock_atom_socket; extern ERL_NIF_TERM esock_atom_spec_dst; diff --git a/erts/emulator/nifs/common/socket_nif.c b/erts/emulator/nifs/common/socket_nif.c index 876bed3135..a762742cd7 100644 --- a/erts/emulator/nifs/common/socket_nif.c +++ b/erts/emulator/nifs/common/socket_nif.c @@ -2391,6 +2391,7 @@ ERL_NIF_TERM esock_atom_loopback; ERL_NIF_TERM esock_atom_lowdelay; ERL_NIF_TERM esock_atom_mincost; ERL_NIF_TERM esock_atom_not_found; +ERL_NIF_TERM esock_atom_not_owner; ERL_NIF_TERM esock_atom_ok; ERL_NIF_TERM esock_atom_oob; ERL_NIF_TERM esock_atom_origdstaddr; @@ -5074,7 +5075,7 @@ ERL_NIF_TERM nsetopt_otp_ctrl_proc(ErlNifEnv* env, SocketDescriptor* descP, ERL_NIF_TERM eVal) { - ErlNifPid newCtrlPid; + ErlNifPid caller, newCtrlPid; ErlNifMonitor newCtrlMon; int xres; @@ -5083,6 +5084,16 @@ ERL_NIF_TERM nsetopt_otp_ctrl_proc(ErlNifEnv* env, "\r\n eVal: %T" "\r\n", eVal) ); + /* Before we begin, ensure that caller is (current) controlling-process */ + if (enif_self(env, &caller) == NULL) + return esock_make_error(env, atom_exself); + + if (!compare_pids(env, &descP->ctrlPid, &caller)) { + SSDBG( descP, ("SOCKET", "nsetopt_otp_ctrl_proc -> not owner (%T)\r\n", + descP->ctrlPid) ); + return esock_make_error(env, esock_atom_not_owner); + } + if (!GET_LPID(env, eVal, &newCtrlPid)) { esock_warning_msg("Failed get pid of new controlling process\r\n"); return esock_make_error(env, esock_atom_einval); @@ -15413,6 +15424,7 @@ int on_load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info) esock_atom_lowdelay = MKA(env, "lowdelay"); esock_atom_mincost = MKA(env, "mincost"); esock_atom_not_found = MKA(env, "not_found"); + esock_atom_not_owner = MKA(env, "not_owner"); esock_atom_ok = MKA(env, "ok"); esock_atom_oob = MKA(env, "oob"); esock_atom_origdstaddr = MKA(env, "origdstaddr"); -- cgit v1.2.3 From 8daa980d92a17e7bb948971b2048c7758e5dd2e6 Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Thu, 4 Oct 2018 14:54:07 +0200 Subject: [socket-nif|test] Add controlling_process test case Added a test case that specifically tested setting the controlling_process. Also, ensure that invalid updates of controlling-process (not owner) actually fails. OTP-14831 --- lib/kernel/test/socket_SUITE.erl | 150 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 144 insertions(+), 6 deletions(-) diff --git a/lib/kernel/test/socket_SUITE.erl b/lib/kernel/test/socket_SUITE.erl index d4cd144168..95b58892c1 100644 --- a/lib/kernel/test/socket_SUITE.erl +++ b/lib/kernel/test/socket_SUITE.erl @@ -40,6 +40,7 @@ %% API Options api_opt_simple_otp_options/1, + api_opt_simple_otp_controlling_process/1, %% API Operation Timeout api_to_connect_tcp4/1, @@ -117,7 +118,8 @@ api_basic_cases() -> api_options_cases() -> [ - api_opt_simple_otp_options + api_opt_simple_otp_options, + api_opt_simple_otp_controlling_process ]. api_op_with_timeout_cases() -> @@ -338,6 +340,7 @@ api_b_send_and_recv_tcp(Domain, Send, Recv) -> LSA = #{family => Domain, addr => LAddr}, Starter = self(), ServerFun = fun() -> + put(sname, "server"), %% Create the listen socket ServerLSock = case socket:open(Domain, stream, tcp) of @@ -454,7 +457,7 @@ api_opt_simple_otp_options(_Config) when is_list(_Config) -> p("Create sockets"), S1 = sock_open(inet, stream, tcp), S2 = sock_open(inet, dgram, udp), - + Get = fun(S, Key) -> socket:getopt(S, otp, Key) end, @@ -464,6 +467,7 @@ api_opt_simple_otp_options(_Config) when is_list(_Config) -> p("Create dummy process"), Pid = spawn_link(fun() -> + put(sname, "dummy"), receive die -> exit(normal) @@ -527,6 +531,135 @@ api_opt_simple_otp_options(_Config) when is_list(_Config) -> tc_end(). +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Perform some simple getopt and setopt with the level = otp options +api_opt_simple_otp_controlling_process(suite) -> + []; +api_opt_simple_otp_controlling_process(doc) -> + []; +api_opt_simple_otp_controlling_process(_Config) when is_list(_Config) -> + tc_begin(api_opt_simple_otp_controlling_process), + + p("Create sockets"), + S1 = sock_open(inet, stream, tcp), + S2 = sock_open(inet, dgram, udp), + + Get = fun(S, Key) -> + socket:getopt(S, otp, Key) + end, + Set = fun(S, Key, Val) -> + socket:setopt(S, otp, Key, Val) + end, + + AwaitStart = + fun() -> + p("await start command"), + receive + {start, P, S} -> + {P, S} + end + end, + AwaitContinue = + fun(Pid) -> + p("await continue command"), + receive + {continue, Pid} -> + ok + end + end, + AwaitReady = + fun(Pid) -> + p("await ready confirmation from ~p", [Pid]), + receive + {ready, Pid} -> + ok + end + end, + AwaitDie = + fun(Pid) -> + p("await die command"), + receive + {die, Pid} -> + ok + end + end, + ClientStarter = + fun() -> + put(sname, "client"), + Self = self(), + {Parent, Sock} = AwaitStart(), + p("verify parent ~p controlling", [Parent]), + {ok, Parent} = Get(Sock, controlling_process), + p("attempt invalid control transfer (to self)"), + {error, not_owner} = Set(Sock, controlling_process, self()), + p("verify parent ~p (still) controlling", [Parent]), + {ok, Parent} = Get(Sock, controlling_process), + p("announce ready"), + Parent ! {ready, self()}, + + AwaitContinue(Parent), + p("verify self controlling"), + {ok, Self} = Get(Sock, controlling_process), + p("transfer control to parent ~p", [Parent]), + ok = Set(Sock, controlling_process, Parent), + p("attempt invalid control transfer (to self)"), + {error, not_owner} = Set(Sock, controlling_process, self()), + p("verify parent ~p controlling", [Parent]), + {ok, Parent} = Get(Sock, controlling_process), + p("announce ready"), + Parent ! {ready, self()}, + + AwaitDie(Parent), + p("done"), + exit(normal) + end, + + Tester = + fun(Sock, Client) -> + p("start"), + Self = self(), + p("verify self controlling"), + {ok, Self} = Get(Sock, controlling_process), + p("announce start"), + Client ! {start, Self, Sock}, + AwaitReady(Client), + + p("transfer control to client ~p", [Client]), + ok = Set(Sock, controlling_process, Client), + p("verify client ~p controlling", [Client]), + {ok, Client} = Get(Sock, controlling_process), + p("attempt invalid control transfer (to self)"), + {error, not_owner} = Set(Sock, controlling_process, self()), + p("announce continue"), + Client ! {continue, Self}, + AwaitReady(Client), + + p("verify self controlling"), + {ok, Self} = Get(Sock, controlling_process), + p("announce die"), + Client ! {die, Self}, + p("done"), + ok + end, + + p("Create Worker Process(s)"), + Pid1 = spawn_link(ClientStarter), + Pid2 = spawn_link(ClientStarter), + + p("Test stream/tcp "), + Tester(S1, Pid1), + + p("Test dgram/udp "), + Tester(S2, Pid2), + + p("close sockets"), + sock_close(S1), + sock_close(S2), + + tc_end(). + + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% This test case is intended to test the connect timeout option @@ -567,7 +700,7 @@ api_to_connect_tcp(Domain) -> LocalSA = #{family => Domain, addr => LocalAddr}, ServerName = f("~s:server", [get_tc_name()]), Server = spawn_link(fun() -> - set_tc_name(ServerName), + put(sname, ServerName), p("open"), LSock = sock_open(Domain, stream, tcp), p("bind"), @@ -826,7 +959,7 @@ api_to_recv_tcp(Domain) -> sock_listen(LSock), ClientName = f("~s:client", [get_tc_name()]), Client = spawn_link(fun() -> - set_tc_name(ClientName), + put(sname, ClientName), p("open"), CSock = sock_open(Domain, stream, tcp), p("bind"), @@ -1013,7 +1146,7 @@ api_to_recvmsg_tcp(Domain) -> sock_listen(LSock), ClientName = f("~s:client", [get_tc_name()]), Client = spawn_link(fun() -> - set_tc_name(ClientName), + put(sname, ClientName), p("open"), CSock = sock_open(Domain, stream, tcp), p("bind"), @@ -1215,7 +1348,12 @@ p(F, A) -> TcName = case get(tc_name) of undefined -> - ""; + case get(sname) of + undefined -> + ""; + SName when is_list(SName) -> + SName + end; Name when is_list(Name) -> Name end, -- cgit v1.2.3 From b98a1e312db1393aa5dd9cdd2e1a5f3daaf954bf Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Thu, 4 Oct 2018 18:16:38 +0200 Subject: [socket-nif|test] begin rework socket suite Begin rework socket test suite using a command evaluator. OTP-14831 --- lib/kernel/test/socket_SUITE.erl | 222 ++++++++++++++++++++++++++++++++++----- 1 file changed, 198 insertions(+), 24 deletions(-) diff --git a/lib/kernel/test/socket_SUITE.erl b/lib/kernel/test/socket_SUITE.erl index 1fff17cf8c..70810f5f3d 100644 --- a/lib/kernel/test/socket_SUITE.erl +++ b/lib/kernel/test/socket_SUITE.erl @@ -71,6 +71,17 @@ %% -export([]). +-type evaluator_state() :: term(). +-type command_fun() :: + fun((State :: evaluator_state()) -> ok) | + fun((State :: evaluator_state()) -> {ok, evaluator_state()}) | + fun((State :: evaluator_state()) -> {error, term()}). + +-type command() :: #{desc := string(), + cmd := command_fun() + }. + + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -define(BASIC_REQ, <<"hejsan">>). @@ -178,7 +189,10 @@ api_b_open_and_close_udp4(doc) -> []; api_b_open_and_close_udp4(_Config) when is_list(_Config) -> tc_begin(api_b_open_and_close_udp4), - ok = api_b_open_and_close(inet, dgram, udp), + State = #{domain => inet, + type => dgram, + protocol => udp}, + ok = api_b_open_and_close(State), tc_end(). @@ -192,34 +206,103 @@ api_b_open_and_close_tcp4(doc) -> []; api_b_open_and_close_tcp4(_Config) when is_list(_Config) -> tc_begin(api_b_open_and_close_tcp4), - ok = api_b_open_and_close(inet, stream, tcp), + State = #{domain => inet, + type => stream, + protocol => tcp}, + ok = api_b_open_and_close(State), tc_end(). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -api_b_open_and_close(Domain, Type, Proto) -> - Socket = case socket:open(Domain, Type, Proto) of - {ok, S} -> - S; - {error, Reason} -> - ?FAIL({open, Reason}) - end, - %% Domain is not available on all platforms: - case socket:getopt(Socket, socket, domain) of - {ok, Domain} -> - ok; - {error, einval} -> - ok; - Else -> - ?FAIL({getopt, domain, Else}) - end, - {ok, Type} = socket:getopt(Socket, socket, type), - {ok, Proto} = socket:getopt(Socket, socket, protocol), - Self = self(), - {ok, Self} = socket:getopt(Socket, otp, controlling_process), - ok = socket:close(Socket), - ok. +api_b_open_and_close(InitState) -> + Seq = + [ + #{desc => "open", + cmd => fun(#{domain := Domain, + type := Type, + protocol := Protocol} = S) -> + Res = socket:open(Domain, Type, Protocol), + {ok, {S, Res}} + end}, + #{desc => "validate open", + cmd => fun({S, {ok, Sock}}) -> + NewS = S#{socket => Sock}, + {ok, NewS}; + ({_, {error, _} = ERROR}) -> + ERROR + end}, + #{desc => "get domain (maybe)", + cmd => fun(#{socket := Sock} = S) -> + Res = socket:getopt(Sock, socket, domain), + {ok, {S, Res}} + end}, + #{desc => "validate domain (maybe)", + cmd => fun({#{domain := Domain} = S, {ok, Domain}}) -> + {ok, S}; + ({#{domain := ExpDomain}, {ok, Domain}}) -> + {error, {unexpected_domain, ExpDomain, Domain}}; + %% Some platforms do not support this option + ({S, {error, einval}}) -> + {ok, S}; + ({_, {error, _} = ERROR}) -> + ERROR + end}, + #{desc => "get type", + cmd => fun(#{socket := Sock} = State) -> + Res = socket:getopt(Sock, socket, type), + {ok, {State, Res}} + end}, + #{desc => "validate type", + cmd => fun({#{type := Type} = State, {ok, Type}}) -> + {ok, State}; + ({#{type := ExpType}, {ok, Type}}) -> + {error, {unexpected_type, ExpType, Type}}; + ({_, {error, _} = ERROR}) -> + ERROR + end}, + #{desc => "get protocol", + cmd => fun(#{socket := Sock} = State) -> + Res = socket:getopt(Sock, socket, protocol), + {ok, {State, Res}} + end}, + #{desc => "validate protocol", + cmd => fun({#{protocol := Protocol} = State, {ok, Protocol}}) -> + {ok, State}; + ({#{protocol := ExpProtocol}, {ok, Protocol}}) -> + {error, {unexpected_type, ExpProtocol, Protocol}}; + ({_, {error, _} = ERROR}) -> + ERROR + end}, + #{desc => "get controlling-process", + cmd => fun(#{socket := Sock} = State) -> + Res = socket:getopt(Sock, otp, controlling_process), + {ok, {State, Res}} + end}, + #{desc => "validate controlling-process", + cmd => fun({State, {ok, Pid}}) -> + case self() of + Pid -> + {ok, State}; + _ -> + {error, {unexpected_owner, Pid}} + end; + ({_, {error, _} = ERROR}) -> + ERROR + end}, + #{desc => "close socket", + cmd => fun(#{socket := Sock} = State) -> + Res = socket:close(Sock), + {ok, {State, Res}} + end}, + #{desc => "validate socket close", + cmd => fun({_, ok}) -> + {ok, normal}; + ({_, {error, _} = ERROR}) -> + ERROR + end}], + Evaluator = evaluator_start("tester", Seq, InitState), + ok = await_evaluator_finish([Evaluator]). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -1230,6 +1313,97 @@ which_addr2(Domain, [_|IFO]) -> +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% An evaluator is a process that executes a command sequence. +%% A test case will consist of atleast one evaluator (one for +%% each actor). +%% The evaluator process *always* run locally. Which means that +%% it will act as a "proxy" for remote nodes in necessary. +%% When the command sequence has been processed, the final state +%% will be used as exit reason. +%% A successful command shall evaluate to ok | {ok, NewState} + +-spec evaluator_start(Name, Seq, Init) -> {Pid, MRef} when + Name :: string(), + Seq :: [command()], + Init :: evaluator_state(), + Pid :: pid(), + MRef :: reference(). + +evaluator_start(Name, Seq, Init) + when is_list(Name) andalso is_list(Seq) andalso (Seq =/= []) -> + erlang:spawn_monitor(fun() -> evaluator_init(Name, Seq, Init) end). + +evaluator_init(Name, Seq, Init) -> + put(sname, Name), + evaluator_loop(1, Seq, Init). + +evaluator_loop(_ID, [], FinalState) -> + exit(FinalState); +evaluator_loop(ID, [#{desc := Desc, + cmd := Cmd}|Cmds], State) when is_function(Cmd, 1) -> + ei("evaluate command ~2w: ~s", [ID, Desc]), + try Cmd(State) of + ok -> + evaluator_loop(ID + 1, Cmds, State); + {ok, NewState} -> + evaluator_loop(ID + 1, Cmds, NewState); + {error, Reason} -> + ee("command ~w failed: " + "~n Reason: ~p", [ID, Reason]), + exit({command_failed, ID, Reason, State}) + catch + C:E:S -> + ee("command ~w crashed: " + "~n Class: ~p" + "~n Error: ~p" + "~n Call Stack: ~p", [ID, C, E, S]), + exit({command_crashed, ID, {C,E,S}, State}) + end. + +await_evaluator_finish(Evs) -> + await_evaluator_finish(Evs, []). + +await_evaluator_finish([], []) -> + ok; +await_evaluator_finish([], Fails) -> + Fails; +await_evaluator_finish(Evs, Fails) -> + receive + {'DOWN', _MRef, process, Pid, normal} -> + case lists:keydelete(Pid, 1, Evs) of + Evs -> + p("unknown process ~p died (normal)", [Pid]), + await_evaluator_finish(Evs, Fails); + NewEvs -> + p("evaluator ~p success", [Pid]), + await_evaluator_finish(NewEvs, Fails) + end; + {'DOWN', _MRef, process, Pid, Reason} -> + case lists:keydelete(Pid, 1, Evs) of + Evs -> + p("unknown process ~p died: " + "~n ~p", [Pid, Reason]), + await_evaluator_finish(Evs, Fails); + NewEvs -> + p("Evaluator ~p failed", [Pid]), + await_evaluator_finish(NewEvs, [{Pid, Reason}|Fails]) + end + end. + + +ei(F, A) -> + eprint("", F, A). + +ee(F, A) -> + eprint(" ", F, A). + +eprint(Prefix, F, A) -> + io:format(user, "[~s][~p] ~s" ++ F ++ "~n", [get(sname), self(), Prefix | A]). + + + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% sock_open(Domain, Type, Proto) -> -- cgit v1.2.3 From 8210ffc460a0bfab8b3007afe3e1141c1bb9dec6 Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Fri, 5 Oct 2018 11:07:55 +0200 Subject: [socket-nif|test] Two more test cases (udp) reworked Two test cases where reworked using the command evaluator. OTP-14831 --- lib/kernel/test/socket_SUITE.erl | 111 +++++++++++++++++++++++++++++++++------ 1 file changed, 94 insertions(+), 17 deletions(-) diff --git a/lib/kernel/test/socket_SUITE.erl b/lib/kernel/test/socket_SUITE.erl index 70810f5f3d..ed5722f008 100644 --- a/lib/kernel/test/socket_SUITE.erl +++ b/lib/kernel/test/socket_SUITE.erl @@ -305,6 +305,7 @@ api_b_open_and_close(InitState) -> ok = await_evaluator_finish([Evaluator]). + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% Basically send and receive on an IPv4 UDP (dgram) socket using @@ -321,7 +322,10 @@ api_b_sendto_and_recvfrom_udp4(_Config) when is_list(_Config) -> Recv = fun(Sock) -> socket:recvfrom(Sock) end, - ok = api_b_send_and_recv_udp(inet, Send, Recv), + InitState = #{domain => inet, + send => Send, + recv => Recv}, + ok = api_b_send_and_recv_udp(InitState), tc_end(). @@ -352,27 +356,100 @@ api_b_sendmsg_and_recvmsg_udp4(_Config) when is_list(_Config) -> ERROR end end, - ok = api_b_send_and_recv_udp(inet, Send, Recv), + InitState = #{domain => inet, + send => Send, + recv => Recv}, + ok = api_b_send_and_recv_udp(InitState), tc_end(). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -api_b_send_and_recv_udp(Domain, Send, Recv) -> - SockSrc = sock_open(Domain, dgram, udp), - LAddr = which_local_addr(Domain), - LSA = #{family => Domain, addr => LAddr}, - sock_bind(SockSrc, LSA), - SockDst = sock_open(Domain, dgram, udp), - sock_bind(SockDst, LSA), - Dst = sock_sockname(SockDst), - ok = Send(SockSrc, ?BASIC_REQ, Dst), - {ok, {Src, ?BASIC_REQ}} = Recv(SockDst), - ok = Send(SockDst, ?BASIC_REP, Src), - {ok, {Dst, ?BASIC_REP}} = Recv(SockSrc), - socket:close(SockSrc), - socket:close(SockDst), - ok. +api_b_send_and_recv_udp(InitState) -> + %% SockSrc = sock_open(Domain, dgram, udp), + %% LAddr = which_local_addr(Domain), + %% LSA = #{family => Domain, addr => LAddr}, + %% sock_bind(SockSrc, LSA), + %% SockDst = sock_open(Domain, dgram, udp), + %% sock_bind(SockDst, LSA), + %% Dst = sock_sockname(SockDst), + %% ok = Send(SockSrc, ?BASIC_REQ, Dst), + %% {ok, {Src, ?BASIC_REQ}} = Recv(SockDst), + %% ok = Send(SockDst, ?BASIC_REP, Src), + %% {ok, {Dst, ?BASIC_REP}} = Recv(SockSrc), + %% socket:close(SockSrc), + %% socket:close(SockDst), + Seq = + [ + #{desc => "local address", + cmd => fun(#{domain := Domain} = State) -> + LAddr = which_local_addr(Domain), + LSA = #{family => Domain, addr => LAddr}, + {ok, State#{lsa => LSA}} + end}, + #{desc => "open src socket", + cmd => fun(#{domain := Domain} = State) -> + Sock = sock_open(Domain, dgram, udp), + SASrc = sock_sockname(Sock), + {ok, State#{sock_src => Sock, sa_src => SASrc}} + end}, + #{desc => "bind src", + cmd => fun(#{sock_src := Sock, lsa := LSA}) -> + sock_bind(Sock, LSA), + ok + end}, + #{desc => "sockname src socket", + cmd => fun(#{sock_src := Sock} = State) -> + SASrc = sock_sockname(Sock), + %% ei("src sockaddr: ~p", [SASrc]), + {ok, State#{sa_src => SASrc}} + end}, + #{desc => "open dst socket", + cmd => fun(#{domain := Domain} = State) -> + Sock = sock_open(Domain, dgram, udp), + {ok, State#{sock_dst => Sock}} + end}, + #{desc => "bind dst", + cmd => fun(#{sock_dst := Sock, lsa := LSA}) -> + sock_bind(Sock, LSA), + ok + end}, + #{desc => "sockname dst socket", + cmd => fun(#{sock_dst := Sock} = State) -> + SADst = sock_sockname(Sock), + %% ei("dst sockaddr: ~p", [SADst]), + {ok, State#{sa_dst => SADst}} + end}, + #{desc => "send req (to dst)", + cmd => fun(#{sock_src := Sock, sa_dst := Dst, send := Send}) -> + ok = Send(Sock, ?BASIC_REQ, Dst) + end}, + #{desc => "recv req (from src)", + cmd => fun(#{sock_dst := Sock, sa_src := Src, recv := Recv} = State) -> + {ok, {Src, ?BASIC_REQ}} = Recv(Sock), + ok + end}, + #{desc => "send rep (to src)", + cmd => fun(#{sock_dst := Sock, sa_src := Src, send := Send}) -> + ok = Send(Sock, ?BASIC_REP, Src) + end}, + #{desc => "recv rep (from dst)", + cmd => fun(#{sock_src := Sock, sa_dst := Dst, recv := Recv}) -> + {ok, {Dst, ?BASIC_REP}} = Recv(Sock), + ok + end}, + #{desc => "close src socket", + cmd => fun(#{sock_src := Sock}) -> + ok = socket:close(Sock) + end}, + #{desc => "close dst socket", + cmd => fun(#{sock_dst := Sock}) -> + ok = socket:close(Sock), + {ok, normal} + end} + ], + Evaluator = evaluator_start("tester", Seq, InitState), + ok = await_evaluator_finish([Evaluator]). -- cgit v1.2.3 From 6e931258872c15404aa1dfd5198f2b490452b30b Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Fri, 5 Oct 2018 14:52:47 +0200 Subject: [socket-nif] Add *preliminary* new function supports/0,1 --- erts/emulator/nifs/common/socket_nif.c | 37 +++++++++++++++++++++++++++++++--- erts/preloaded/src/socket.erl | 14 +++++++++++++ 2 files changed, 48 insertions(+), 3 deletions(-) diff --git a/erts/emulator/nifs/common/socket_nif.c b/erts/emulator/nifs/common/socket_nif.c index 0ef88162bc..4def45f869 100644 --- a/erts/emulator/nifs/common/socket_nif.c +++ b/erts/emulator/nifs/common/socket_nif.c @@ -860,6 +860,9 @@ typedef struct { static ERL_NIF_TERM nif_info(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); +static ERL_NIF_TERM nif_supports(ErlNifEnv* env, + int argc, + const ERL_NIF_TERM argv[]); /* This is a *global* debug function (enable or disable for all operations and all sockets. @@ -2536,6 +2539,7 @@ static SocketData data; * Utility and admin functions: * ---------------------------- * nif_info/0 + * nif_supports/1 * (nif_debug/1) * * The "proper" socket functions: @@ -2619,6 +2623,31 @@ ERL_NIF_TERM nif_info(ErlNifEnv* env, } + +/* ---------------------------------------------------------------------- + * nif_supports + * + * Description: + * This function is intended to answer the question: "Is X supported?" + * Currently only one key is "supported": options + * That results in a list of all *known options* (known by us) and if + * the platform supports (OS) it or not. + */ + +static +ERL_NIF_TERM nif_support(ErlNifEnv* env, + int argc, + const ERL_NIF_TERM argv[]) +{ + if (argc != 1) { + return enif_make_badarg(env); + } else { + return MKEL(env); // PLACEHOLDER + } +} + + + /* ---------------------------------------------------------------------- * nif_open * @@ -15412,9 +15441,11 @@ void socket_down_reader(ErlNifEnv* env, static ErlNifFunc socket_funcs[] = { - // Some utility functions - {"nif_info", 0, nif_info, 0}, - // {"nif_debug", 1, nif_debug_, 0}, + // Some utility and support functions + {"nif_info", 0, nif_info, 0}, + {"nif_supports", 1, nif_supports, 0}, + // {"nif_debug", 1, nif_debug, 0}, + // {"nif_command", 1, nif_command, 0}, // The proper "socket" interface // nif_open/1 is used when we already have a file descriptor diff --git a/erts/preloaded/src/socket.erl b/erts/preloaded/src/socket.erl index c388fc2849..147d074310 100644 --- a/erts/preloaded/src/socket.erl +++ b/erts/preloaded/src/socket.erl @@ -27,6 +27,7 @@ -export([ on_load/0, on_load/1, info/0, + supports/0, supports/1, ensure_sockaddr/1 ]). @@ -843,6 +844,16 @@ info() -> nif_info(). +-spec supports() -> list(). + +supports() -> + [{options, supports(options)}]. + +supports(options) -> + nif_supports(?SOCKET_SUPPORTS_OPTIONS); +supports(_) -> + false. + %% =========================================================================== %% @@ -3417,6 +3428,9 @@ error(Reason) -> nif_info() -> erlang:nif_error(undef). +nif_supports(_Key) -> + erlang:nif_error(undef). + nif_open(_Domain, _Type, _Protocol, _Extra) -> erlang:nif_error(undef). -- cgit v1.2.3 From d4cedb9fa86db720cc247b9201d5c7e1e8aab461 Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Fri, 5 Oct 2018 14:23:47 +0200 Subject: [socket-nif|test] Two more test cases (tcp) reworked Two (tcp) test cases where reworked using the command evaluator. OTP-14831 --- lib/kernel/test/socket_SUITE.erl | 288 ++++++++++++++++++++++----------------- 1 file changed, 160 insertions(+), 128 deletions(-) diff --git a/lib/kernel/test/socket_SUITE.erl b/lib/kernel/test/socket_SUITE.erl index ed5722f008..e50daaf367 100644 --- a/lib/kernel/test/socket_SUITE.erl +++ b/lib/kernel/test/socket_SUITE.erl @@ -71,6 +71,7 @@ %% -export([]). +-type initial_evaluator_state() :: map(). -type evaluator_state() :: term(). -type command_fun() :: fun((State :: evaluator_state()) -> ok) | @@ -78,8 +79,7 @@ fun((State :: evaluator_state()) -> {error, term()}). -type command() :: #{desc := string(), - cmd := command_fun() - }. + cmd := command_fun()}. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -89,6 +89,8 @@ -define(FAIL(R), exit(R)). +-define(SLEEP(T), receive after T -> ok end). + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -366,19 +368,6 @@ api_b_sendmsg_and_recvmsg_udp4(_Config) when is_list(_Config) -> %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% api_b_send_and_recv_udp(InitState) -> - %% SockSrc = sock_open(Domain, dgram, udp), - %% LAddr = which_local_addr(Domain), - %% LSA = #{family => Domain, addr => LAddr}, - %% sock_bind(SockSrc, LSA), - %% SockDst = sock_open(Domain, dgram, udp), - %% sock_bind(SockDst, LSA), - %% Dst = sock_sockname(SockDst), - %% ok = Send(SockSrc, ?BASIC_REQ, Dst), - %% {ok, {Src, ?BASIC_REQ}} = Recv(SockDst), - %% ok = Send(SockDst, ?BASIC_REP, Src), - %% {ok, {Dst, ?BASIC_REP}} = Recv(SockSrc), - %% socket:close(SockSrc), - %% socket:close(SockDst), Seq = [ #{desc => "local address", @@ -425,7 +414,7 @@ api_b_send_and_recv_udp(InitState) -> ok = Send(Sock, ?BASIC_REQ, Dst) end}, #{desc => "recv req (from src)", - cmd => fun(#{sock_dst := Sock, sa_src := Src, recv := Recv} = State) -> + cmd => fun(#{sock_dst := Sock, sa_src := Src, recv := Recv}) -> {ok, {Src, ?BASIC_REQ}} = Recv(Sock), ok end}, @@ -469,7 +458,10 @@ api_b_send_and_recv_tcp4(_Config) when is_list(_Config) -> Recv = fun(Sock) -> socket:recv(Sock) end, - ok = api_b_send_and_recv_tcp(inet, Send, Recv), + InitState = #{domain => inet, + send => Send, + recv => Recv}, + ok = api_b_send_and_recv_tcp(InitState), tc_end(). @@ -496,119 +488,157 @@ api_b_sendmsg_and_recvmsg_tcp4(_Config) when is_list(_Config) -> ERROR end end, - ok = api_b_send_and_recv_tcp(inet, Send, Recv), + InitState = #{domain => inet, + send => Send, + recv => Recv}, + ok = api_b_send_and_recv_tcp(InitState), tc_end(). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -api_b_send_and_recv_tcp(Domain, Send, Recv) -> +api_b_send_and_recv_tcp(InitState) -> process_flag(trap_exit, true), - LAddr = which_local_addr(Domain), - LSA = #{family => Domain, addr => LAddr}, - Starter = self(), - ServerFun = fun() -> - put(sname, "server"), - %% Create the listen socket - ServerLSock = - case socket:open(Domain, stream, tcp) of - {ok, S1} -> - S1; - {error, ServerOR} -> - ?FAIL({server, open, ServerOR}) - end, - %% And bind it to the local address - SP = - case socket:bind(ServerLSock, LSA) of - {ok, P} -> - P; - {error, ServerBR} -> - ?FAIL({server, bind, ServerBR}) - end, - %% Listen for connecting clients - case socket:listen(ServerLSock) of - ok -> - ok; - {error, ServerLR} -> - ?FAIL({server, listen, ServerLR}) - end, - %% We are ready - Starter ! {self(), {ok, SP}}, - %% Accept connections - ServerSock = - case socket:accept(ServerLSock) of - {ok, Sock} -> - Sock; - {error, ServerAR} -> - ?FAIL({server, accept, ServerAR}) - end, - %% Wait for a message - case Recv(ServerSock) of - {ok, ?BASIC_REQ} -> - ok; - {error, ServerRR} -> - ?FAIL({server, recv, ServerRR}) - end, - %% Send the reply - case Send(ServerSock, ?BASIC_REP) of - ok -> - ok; - {error, ServerSR} -> - ?FAIL({server, send, ServerSR}) - end, - %% Close the sockets - socket:close(ServerSock), - socket:close(ServerLSock), - %% We are done - exit(normal) - end, - Server = spawn_link(ServerFun), - ServerPort = - receive - {Server, {ok, P}} -> - P; - {'EXIT', Server, ServerStartReason} -> - ?FAIL({server, start, ServerStartReason}) - end, - ClientSock = - case socket:open(Domain, stream, tcp) of - {ok, S2} -> - S2; - {error, ClientOR} -> - ?FAIL({client, open, ClientOR}) - end, - case socket:bind(ClientSock, LSA) of - {ok, _} -> - ok; - {error, ClientBR} -> - ?FAIL({client, bind, ClientBR}) - end, - case socket:connect(ClientSock, LSA#{port => ServerPort}) of - ok -> - ok; - {error, ClientCR} -> - ?FAIL({client, connect, ClientCR}) - end, - case Send(ClientSock, ?BASIC_REQ) of - ok -> - ok; - {error, ClientSR} -> - ?FAIL({client, send, ClientSR}) - end, - case Recv(ClientSock) of - {ok, ?BASIC_REP} -> - ok; - {ok, Msg} -> - ?FAIL({client, recv, {unexpected, Msg}}) - end, - receive - {'EXIT', Server, normal} -> - ok; - {'EXIT', Server, ServerStopReason} -> - ?FAIL({server, stop, ServerStopReason}) - end, - socket:close(ClientSock), - ok. + ServerSeq = + [ + #{desc => "which local address", + cmd => fun(#{domain := Domain} = State) -> + LAddr = which_local_addr(Domain), + LSA = #{family => Domain, addr => LAddr}, + {ok, State#{lsa => LSA}} + end}, + #{desc => "create listen socket", + cmd => fun(#{domain := Domain} = State) -> + case socket:open(Domain, stream, tcp) of + {ok, Sock} -> + {ok, State#{lsock => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "bind to local address", + cmd => fun(#{lsock := LSock, lsa := LSA} = State) -> + case socket:bind(LSock, LSA) of + {ok, Port} -> + {ok, State#{lport => Port}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "make listen socket", + cmd => fun(#{lsock := LSock}) -> + socket:listen(LSock) + end}, + #{desc => "announce server port", + cmd => fun(#{parent := Parent, lport := Port}) -> + ei("announcing port to parent (~p)", [Parent]), + Parent ! {server_port, self(), Port}, + ok + end}, + #{desc => "await connection", + cmd => fun(#{lsock := LSock} = State) -> + case socket:accept(LSock) of + {ok, Sock} -> + ei("accepted: ~p", [Sock]), + {ok, State#{tsock => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "await request", + cmd => fun(#{tsock := Sock, recv := Recv}) -> + case Recv(Sock) of + {ok, ?BASIC_REQ} -> + ok; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "send reply", + cmd => fun(#{tsock := Sock, send := Send}) -> + Send(Sock, ?BASIC_REP) + end}, + #{desc => "sleep some", + cmd => fun(_) -> + ?SLEEP(1000), + ok + end}, + #{desc => "close traffic socket", + cmd => fun(#{tsock := Sock}) -> + socket:close(Sock) + end}, + #{desc => "close listen socket", + cmd => fun(#{lsock := Sock}) -> + socket:close(Sock) + end}, + #{desc => "finish", + cmd => fun(_) -> + {ok, normal} + end} + ], + + ClientSeq = + [ + #{desc => "which server (local) address", + cmd => fun(#{domain := Domain, server_port := Port} = State) -> + LAddr = which_local_addr(Domain), + LSA = #{family => Domain, + addr => LAddr}, + SSA = LSA#{port => Port}, + {ok, State#{lsa => LSA, ssa => SSA}} + end}, + #{desc => "create socket", + cmd => fun(#{domain := Domain} = State) -> + case socket:open(Domain, stream, tcp) of + {ok, Sock} -> + {ok, State#{sock => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "bind to local address", + cmd => fun(#{sock := Sock, lsa := LSA} = State) -> + case socket:bind(Sock, LSA) of + {ok, Port} -> + {ok, State#{port => Port}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "connect to server", + cmd => fun(#{sock := Sock, ssa := SSA}) -> + socket:connect(Sock, SSA) + end}, + #{desc => "send request (to server)", + cmd => fun(#{sock := Sock, send := Send}) -> + Send(Sock, ?BASIC_REQ) + end}, + #{desc => "recv reply (from server)", + cmd => fun(#{sock := Sock, recv := Recv}) -> + {ok, ?BASIC_REP} = Recv(Sock), + ok + end}, + #{desc => "close socket", + cmd => fun(#{sock := Sock}) -> + socket:close(Sock) + end}, + #{desc => "finish", + cmd => fun(_) -> + {ok, normal} + end} + ], + + p("start server evaluator"), + Server = evaluator_start("server", ServerSeq, InitState), + p("await server (~p) port", [Server]), + SPort = receive + {server_port, Server, Port} -> + Port + end, + p("start client evaluator"), + Client = evaluator_start("client", ClientSeq, InitState#{server_port => SPort}), + p("await evaluator(s)"), + ok = await_evaluator_finish([Server, Client]). @@ -1404,13 +1434,15 @@ which_addr2(Domain, [_|IFO]) -> -spec evaluator_start(Name, Seq, Init) -> {Pid, MRef} when Name :: string(), Seq :: [command()], - Init :: evaluator_state(), + Init :: initial_evaluator_state(), Pid :: pid(), MRef :: reference(). evaluator_start(Name, Seq, Init) when is_list(Name) andalso is_list(Seq) andalso (Seq =/= []) -> - erlang:spawn_monitor(fun() -> evaluator_init(Name, Seq, Init) end). + Init2 = Init#{parent => self()}, + {Pid, _} = erlang:spawn_monitor(fun() -> evaluator_init(Name, Seq, Init2) end), + Pid. evaluator_init(Name, Seq, Init) -> put(sname, Name), @@ -1449,7 +1481,7 @@ await_evaluator_finish([], Fails) -> await_evaluator_finish(Evs, Fails) -> receive {'DOWN', _MRef, process, Pid, normal} -> - case lists:keydelete(Pid, 1, Evs) of + case lists:delete(Pid, Evs) of Evs -> p("unknown process ~p died (normal)", [Pid]), await_evaluator_finish(Evs, Fails); @@ -1458,7 +1490,7 @@ await_evaluator_finish(Evs, Fails) -> await_evaluator_finish(NewEvs, Fails) end; {'DOWN', _MRef, process, Pid, Reason} -> - case lists:keydelete(Pid, 1, Evs) of + case lists:delete(Pid, Evs) of Evs -> p("unknown process ~p died: " "~n ~p", [Pid, Reason]), -- cgit v1.2.3 From 09ada8ef363de23dff9af58b2284ea8aaf399071 Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Fri, 5 Oct 2018 18:46:13 +0200 Subject: [socket-nif] Add first three socket (socket) options to supports Added the first three socket (level socket) options to the supports function(s). OTP-14831 --- erts/emulator/nifs/common/socket_int.h | 3 + erts/emulator/nifs/common/socket_nif.c | 335 ++++++++++++++++++++++++++------- erts/preloaded/ebin/socket.beam | Bin 66596 -> 66960 bytes erts/preloaded/src/socket.erl | 3 + 4 files changed, 268 insertions(+), 73 deletions(-) diff --git a/erts/emulator/nifs/common/socket_int.h b/erts/emulator/nifs/common/socket_int.h index f9246856fa..69bd6aba96 100644 --- a/erts/emulator/nifs/common/socket_int.h +++ b/erts/emulator/nifs/common/socket_int.h @@ -104,8 +104,11 @@ typedef unsigned int BOOLEAN_T; * "Global" atoms */ extern ERL_NIF_TERM esock_atom_accept; +extern ERL_NIF_TERM esock_atom_acceptconn; extern ERL_NIF_TERM esock_atom_addr; extern ERL_NIF_TERM esock_atom_any; +extern ERL_NIF_TERM esock_atom_bindtodevice; +extern ERL_NIF_TERM esock_atom_broadcast; extern ERL_NIF_TERM esock_atom_connect; extern ERL_NIF_TERM esock_atom_credentials; extern ERL_NIF_TERM esock_atom_ctrl; diff --git a/erts/emulator/nifs/common/socket_nif.c b/erts/emulator/nifs/common/socket_nif.c index 4def45f869..964b9ccd73 100644 --- a/erts/emulator/nifs/common/socket_nif.c +++ b/erts/emulator/nifs/common/socket_nif.c @@ -603,6 +603,9 @@ typedef union { #define ESOCK_RECVMSG_IOVEC_SZ 1 +#define SOCKET_SUPPORTS_OPTIONS 0x0001 + + /* =================================================================== * * * @@ -931,6 +934,14 @@ static ERL_NIF_TERM nif_cancel(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); +static ERL_NIF_TERM nsupports(ErlNifEnv* env, int key); +static ERL_NIF_TERM nsupports_options(ErlNifEnv* env); +static ERL_NIF_TERM nsupports_options_socket(ErlNifEnv* env); +static ERL_NIF_TERM nsupports_options_ip(ErlNifEnv* env); +static ERL_NIF_TERM nsupports_options_ipv6(ErlNifEnv* env); +static ERL_NIF_TERM nsupports_options_tcp(ErlNifEnv* env); +static ERL_NIF_TERM nsupports_options_udp(ErlNifEnv* env); +static ERL_NIF_TERM nsupports_options_sctp(ErlNifEnv* env); static ERL_NIF_TERM nopen(ErlNifEnv* env, int domain, @@ -2375,8 +2386,11 @@ static char str_exsend[] = "exsend"; // failed send /* *** "Global" Atoms *** */ ERL_NIF_TERM esock_atom_accept; +ERL_NIF_TERM esock_atom_acceptconn; ERL_NIF_TERM esock_atom_addr; ERL_NIF_TERM esock_atom_any; +ERL_NIF_TERM esock_atom_bindtodevice; +ERL_NIF_TERM esock_atom_broadcast; ERL_NIF_TERM esock_atom_connect; ERL_NIF_TERM esock_atom_credentials; ERL_NIF_TERM esock_atom_ctrl; @@ -2632,18 +2646,190 @@ ERL_NIF_TERM nif_info(ErlNifEnv* env, * Currently only one key is "supported": options * That results in a list of all *known options* (known by us) and if * the platform supports (OS) it or not. + * + * Key + * --- + * options [{socket, [{Opt, boolean()}]}, + * {ip, [{Opt, boolean()}]}, + * {ipv6, [{Opt, boolean()}]}, + * {tcp, [{Opt, boolean()}]}, + * {udp, [{Opt, boolean()}]}, + * {sctp, [{Opt, boolean()}]}] */ static -ERL_NIF_TERM nif_support(ErlNifEnv* env, - int argc, - const ERL_NIF_TERM argv[]) +ERL_NIF_TERM nif_supports(ErlNifEnv* env, + int argc, + const ERL_NIF_TERM argv[]) { - if (argc != 1) { + int key; + + SGDBG( ("SOCKET", "nif_supports -> entry with %d args\r\n", argc) ); + + /* Extract arguments and perform preliminary validation */ + + if ((argc != 1) || + !GET_INT(env, argv[0], &key)) { return enif_make_badarg(env); - } else { - return MKEL(env); // PLACEHOLDER } + + return nsupports(env, key); +} + + + +/* nopen - create an endpoint for communication + * + * Assumes the input has been validated. + * + * Normally we want debugging on (individual) sockets to be controlled + * by the sockets own debug flag. But since we don't even have a socket + * yet, we must use the global debug flag. + */ +static +ERL_NIF_TERM nsupports(ErlNifEnv* env, int key) +{ + ERL_NIF_TERM result; + + SGDBG( ("SOCKET", "nsupports -> entry with 0x%lX\r\n", key) ); + + switch (key) { + case SOCKET_SUPPORTS_OPTIONS: + result = nsupports_options(env); + break; + + default: + result = esock_atom_false; + break; + } + + return result; +} + + +static +ERL_NIF_TERM nsupports_options(ErlNifEnv* env) +{ + ERL_NIF_TERM sockOpts = nsupports_options_socket(env); + ERL_NIF_TERM sockOptsT = MKT2(env, esock_atom_socket, sockOpts); + ERL_NIF_TERM ipOpts = nsupports_options_ip(env); + ERL_NIF_TERM ipOptsT = MKT2(env, esock_atom_ip, ipOpts); + ERL_NIF_TERM ipv6Opts = nsupports_options_ipv6(env); + ERL_NIF_TERM ipv6OptsT = MKT2(env, esock_atom_ipv6, ipv6Opts); + ERL_NIF_TERM tcpOpts = nsupports_options_tcp(env); + ERL_NIF_TERM tcpOptsT = MKT2(env, esock_atom_tcp, tcpOpts); + ERL_NIF_TERM udpOpts = nsupports_options_udp(env); + ERL_NIF_TERM udpOptsT = MKT2(env, esock_atom_udp, udpOpts); + ERL_NIF_TERM sctpOpts = nsupports_options_sctp(env); + ERL_NIF_TERM sctpOptsT = MKT2(env, esock_atom_sctp, sctpOpts); + ERL_NIF_TERM optsA[] = {sockOptsT, + ipOptsT, ipv6OptsT, + tcpOptsT, udpOptsT, sctpOptsT}; + unsigned int lenOptsA = sizeof(optsA) / sizeof(ERL_NIF_TERM); + ERL_NIF_TERM optsL = MKLA(env, optsA, lenOptsA); + + return optsL; +} + + + +static +ERL_NIF_TERM nsupports_options_socket(ErlNifEnv* env) +{ + SocketTArray opts = TARRAY_CREATE(128); + ERL_NIF_TERM tmp, optsL; + + + /* *** SOCKET_OPT_SOCK_ACCEPTCONN => SO_ACCEPTCONN *** */ +#if defined(SO_ACCEPTCONN) + tmp = MKT2(env, esock_atom_acceptconn, esock_atom_true); +#else + tmp = MKT2(env, esock_atom_acceptconn, esock_atom_false); +#endif + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_SOCK_BINDTODEVICE => SO_BINDTODEVICE *** */ +#if defined(SO_BINDTODEVICE) + tmp = MKT2(env, esock_atom_bindtodevice, esock_atom_true); +#else + tmp = MKT2(env, esock_atom_bindtodevice, esock_atom_false); +#endif + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_SOCK_BROADCAST => SO_BROADCAST *** */ +#if defined(SO_BROADCAST) + tmp = MKT2(env, esock_atom_broadcast, esock_atom_true); +#else + tmp = MKT2(env, esock_atom_broadcast, esock_atom_false); +#endif + TARRAY_ADD(opts, tmp); + + + TARRAY_TOLIST(opts, env, &optsL); + + return optsL; +} + + + +static +ERL_NIF_TERM nsupports_options_ip(ErlNifEnv* env) +{ + ERL_NIF_TERM result; + + result = MKEL(env); + + return result; +} + + + +static +ERL_NIF_TERM nsupports_options_ipv6(ErlNifEnv* env) +{ + ERL_NIF_TERM result; + + result = MKEL(env); + + return result; +} + + + +static +ERL_NIF_TERM nsupports_options_tcp(ErlNifEnv* env) +{ + ERL_NIF_TERM result; + + result = MKEL(env); + + return result; +} + + + +static +ERL_NIF_TERM nsupports_options_udp(ErlNifEnv* env) +{ + ERL_NIF_TERM result; + + result = MKEL(env); + + return result; +} + + + +static +ERL_NIF_TERM nsupports_options_sctp(ErlNifEnv* env) +{ + ERL_NIF_TERM result; + + result = MKEL(env); + + return result; } @@ -15595,73 +15781,76 @@ int on_load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info) atom_want = MKA(env, str_want); /* Global atom(s) */ - esock_atom_accept = MKA(env, "accept"); - esock_atom_addr = MKA(env, "addr"); - esock_atom_any = MKA(env, "any"); - esock_atom_connect = MKA(env, "connect"); - esock_atom_credentials = MKA(env, "credentials"); - esock_atom_ctrl = MKA(env, "ctrl"); - esock_atom_ctrunc = MKA(env, "ctrunc"); - esock_atom_data = MKA(env, "data"); - esock_atom_debug = MKA(env, "debug"); - esock_atom_dgram = MKA(env, "dgram"); - esock_atom_eor = MKA(env, "eor"); - esock_atom_error = MKA(env, "error"); - esock_atom_errqueue = MKA(env, "errqueue"); - esock_atom_false = MKA(env, "false"); - esock_atom_family = MKA(env, "family"); - esock_atom_flags = MKA(env, "flags"); - esock_atom_flowinfo = MKA(env, "flowinfo"); - esock_atom_ifindex = MKA(env, "ifindex"); - esock_atom_inet = MKA(env, "inet"); - esock_atom_inet6 = MKA(env, "inet6"); - esock_atom_iov = MKA(env, "iov"); - esock_atom_ip = MKA(env, "ip"); - esock_atom_ipv6 = MKA(env, "ipv6"); - esock_atom_level = MKA(env, "level"); - esock_atom_local = MKA(env, "local"); - esock_atom_loopback = MKA(env, "loopback"); - esock_atom_lowdelay = MKA(env, "lowdelay"); - esock_atom_mincost = MKA(env, "mincost"); - esock_atom_not_found = MKA(env, "not_found"); - esock_atom_not_owner = MKA(env, "not_owner"); - esock_atom_ok = MKA(env, "ok"); - esock_atom_oob = MKA(env, "oob"); - esock_atom_origdstaddr = MKA(env, "origdstaddr"); - esock_atom_path = MKA(env, "path"); - esock_atom_pktinfo = MKA(env, "pktinfo"); - esock_atom_port = MKA(env, "port"); - esock_atom_protocol = MKA(env, "protocol"); - esock_atom_raw = MKA(env, "raw"); - esock_atom_rdm = MKA(env, "rdm"); - esock_atom_recv = MKA(env, "recv"); - esock_atom_recvfrom = MKA(env, "recvfrom"); - esock_atom_recvmsg = MKA(env, "recvmsg"); - esock_atom_reliability = MKA(env, "reliability"); - esock_atom_rights = MKA(env, "rights"); - esock_atom_scope_id = MKA(env, "scope_id"); - esock_atom_sctp = MKA(env, "sctp"); - esock_atom_sec = MKA(env, "sec"); - esock_atom_select_sent = MKA(env, "select_sent"); - esock_atom_send = MKA(env, "send"); - esock_atom_sendmsg = MKA(env, "sendmsg"); - esock_atom_sendto = MKA(env, "sendto"); - esock_atom_seqpacket = MKA(env, "seqpacket"); - esock_atom_socket = MKA(env, "socket"); - esock_atom_spec_dst = MKA(env, "spec_dst"); - esock_atom_stream = MKA(env, "stream"); - esock_atom_tcp = MKA(env, "tcp"); - esock_atom_throughput = MKA(env, "throughput"); - esock_atom_timestamp = MKA(env, "timestamp"); - esock_atom_tos = MKA(env, "tos"); - esock_atom_true = MKA(env, "true"); - esock_atom_trunc = MKA(env, "trunc"); - esock_atom_ttl = MKA(env, "ttl"); - esock_atom_type = MKA(env, "type"); - esock_atom_udp = MKA(env, "udp"); - esock_atom_undefined = MKA(env, "undefined"); - esock_atom_unknown = MKA(env, "unknown"); - esock_atom_usec = MKA(env, "usec"); + esock_atom_accept = MKA(env, "accept"); + esock_atom_acceptconn = MKA(env, "acceptconn"); + esock_atom_addr = MKA(env, "addr"); + esock_atom_any = MKA(env, "any"); + esock_atom_bindtodevice = MKA(env, "bindtodevice"); + esock_atom_broadcast = MKA(env, "broadcast"); + esock_atom_connect = MKA(env, "connect"); + esock_atom_credentials = MKA(env, "credentials"); + esock_atom_ctrl = MKA(env, "ctrl"); + esock_atom_ctrunc = MKA(env, "ctrunc"); + esock_atom_data = MKA(env, "data"); + esock_atom_debug = MKA(env, "debug"); + esock_atom_dgram = MKA(env, "dgram"); + esock_atom_eor = MKA(env, "eor"); + esock_atom_error = MKA(env, "error"); + esock_atom_errqueue = MKA(env, "errqueue"); + esock_atom_false = MKA(env, "false"); + esock_atom_family = MKA(env, "family"); + esock_atom_flags = MKA(env, "flags"); + esock_atom_flowinfo = MKA(env, "flowinfo"); + esock_atom_ifindex = MKA(env, "ifindex"); + esock_atom_inet = MKA(env, "inet"); + esock_atom_inet6 = MKA(env, "inet6"); + esock_atom_iov = MKA(env, "iov"); + esock_atom_ip = MKA(env, "ip"); + esock_atom_ipv6 = MKA(env, "ipv6"); + esock_atom_level = MKA(env, "level"); + esock_atom_local = MKA(env, "local"); + esock_atom_loopback = MKA(env, "loopback"); + esock_atom_lowdelay = MKA(env, "lowdelay"); + esock_atom_mincost = MKA(env, "mincost"); + esock_atom_not_found = MKA(env, "not_found"); + esock_atom_not_owner = MKA(env, "not_owner"); + esock_atom_ok = MKA(env, "ok"); + esock_atom_oob = MKA(env, "oob"); + esock_atom_origdstaddr = MKA(env, "origdstaddr"); + esock_atom_path = MKA(env, "path"); + esock_atom_pktinfo = MKA(env, "pktinfo"); + esock_atom_port = MKA(env, "port"); + esock_atom_protocol = MKA(env, "protocol"); + esock_atom_raw = MKA(env, "raw"); + esock_atom_rdm = MKA(env, "rdm"); + esock_atom_recv = MKA(env, "recv"); + esock_atom_recvfrom = MKA(env, "recvfrom"); + esock_atom_recvmsg = MKA(env, "recvmsg"); + esock_atom_reliability = MKA(env, "reliability"); + esock_atom_rights = MKA(env, "rights"); + esock_atom_scope_id = MKA(env, "scope_id"); + esock_atom_sctp = MKA(env, "sctp"); + esock_atom_sec = MKA(env, "sec"); + esock_atom_select_sent = MKA(env, "select_sent"); + esock_atom_send = MKA(env, "send"); + esock_atom_sendmsg = MKA(env, "sendmsg"); + esock_atom_sendto = MKA(env, "sendto"); + esock_atom_seqpacket = MKA(env, "seqpacket"); + esock_atom_socket = MKA(env, "socket"); + esock_atom_spec_dst = MKA(env, "spec_dst"); + esock_atom_stream = MKA(env, "stream"); + esock_atom_tcp = MKA(env, "tcp"); + esock_atom_throughput = MKA(env, "throughput"); + esock_atom_timestamp = MKA(env, "timestamp"); + esock_atom_tos = MKA(env, "tos"); + esock_atom_true = MKA(env, "true"); + esock_atom_trunc = MKA(env, "trunc"); + esock_atom_ttl = MKA(env, "ttl"); + esock_atom_type = MKA(env, "type"); + esock_atom_udp = MKA(env, "udp"); + esock_atom_undefined = MKA(env, "undefined"); + esock_atom_unknown = MKA(env, "unknown"); + esock_atom_usec = MKA(env, "usec"); /* Global error codes */ esock_atom_eafnosupport = MKA(env, ESOCK_STR_EAFNOSUPPORT); diff --git a/erts/preloaded/ebin/socket.beam b/erts/preloaded/ebin/socket.beam index 9c7bcf89b5..0387e534d8 100644 Binary files a/erts/preloaded/ebin/socket.beam and b/erts/preloaded/ebin/socket.beam differ diff --git a/erts/preloaded/src/socket.erl b/erts/preloaded/src/socket.erl index 147d074310..5f23bcce84 100644 --- a/erts/preloaded/src/socket.erl +++ b/erts/preloaded/src/socket.erl @@ -817,6 +817,8 @@ -define(SOCKET_SHUTDOWN_HOW_READ_WRITE, 2). +-define(SOCKET_SUPPORTS_OPTIONS, 16#0001). + %% =========================================================================== %% @@ -855,6 +857,7 @@ supports(_) -> false. + %% =========================================================================== %% %% The proper socket API -- cgit v1.2.3 From f1c6c983a7f21b55c597279e806b5df8f38d4947 Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Mon, 8 Oct 2018 15:32:37 +0200 Subject: [socket-nif] Add the options for socket, ip and ipv6 for supports Add the options for level socket (full), ip and ipv6 to the supports function. OTP-14831 --- erts/emulator/nifs/common/socket_int.h | 77 +++ erts/emulator/nifs/common/socket_nif.c | 914 ++++++++++++++++++++++++++++++++- erts/preloaded/src/socket.erl | 4 +- 3 files changed, 986 insertions(+), 9 deletions(-) diff --git a/erts/emulator/nifs/common/socket_int.h b/erts/emulator/nifs/common/socket_int.h index 69bd6aba96..93722d2e98 100644 --- a/erts/emulator/nifs/common/socket_int.h +++ b/erts/emulator/nifs/common/socket_int.h @@ -105,10 +105,19 @@ typedef unsigned int BOOLEAN_T; */ extern ERL_NIF_TERM esock_atom_accept; extern ERL_NIF_TERM esock_atom_acceptconn; +extern ERL_NIF_TERM esock_atom_acceptfilter; extern ERL_NIF_TERM esock_atom_addr; +extern ERL_NIF_TERM esock_atom_addrform; +extern ERL_NIF_TERM esock_atom_add_membership; +extern ERL_NIF_TERM esock_atom_add_source_membership; extern ERL_NIF_TERM esock_atom_any; +extern ERL_NIF_TERM esock_atom_authhdr; +extern ERL_NIF_TERM esock_atom_auth_level; extern ERL_NIF_TERM esock_atom_bindtodevice; +extern ERL_NIF_TERM esock_atom_block_source; extern ERL_NIF_TERM esock_atom_broadcast; +extern ERL_NIF_TERM esock_atom_busy_poll; +extern ERL_NIF_TERM esock_atom_checksum; extern ERL_NIF_TERM esock_atom_connect; extern ERL_NIF_TERM esock_atom_credentials; extern ERL_NIF_TERM esock_atom_ctrl; @@ -116,63 +125,131 @@ extern ERL_NIF_TERM esock_atom_ctrunc; extern ERL_NIF_TERM esock_atom_data; extern ERL_NIF_TERM esock_atom_debug; extern ERL_NIF_TERM esock_atom_dgram; +extern ERL_NIF_TERM esock_atom_domain; +extern ERL_NIF_TERM esock_atom_dontfrag; +extern ERL_NIF_TERM esock_atom_dontroute; +extern ERL_NIF_TERM esock_atom_drop_membership; +extern ERL_NIF_TERM esock_atom_drop_source_membership; +extern ERL_NIF_TERM esock_atom_dstopts; extern ERL_NIF_TERM esock_atom_eor; extern ERL_NIF_TERM esock_atom_error; extern ERL_NIF_TERM esock_atom_errqueue; +extern ERL_NIF_TERM esock_atom_esp_network_level; +extern ERL_NIF_TERM esock_atom_esp_trans_level; +extern ERL_NIF_TERM esock_atom_faith; extern ERL_NIF_TERM esock_atom_false; extern ERL_NIF_TERM esock_atom_family; extern ERL_NIF_TERM esock_atom_flags; extern ERL_NIF_TERM esock_atom_flowinfo; +extern ERL_NIF_TERM esock_atom_freebind; +extern ERL_NIF_TERM esock_atom_hdrincl; +extern ERL_NIF_TERM esock_atom_hoplimit; +extern ERL_NIF_TERM esock_atom_hopopts; extern ERL_NIF_TERM esock_atom_ifindex; extern ERL_NIF_TERM esock_atom_inet; extern ERL_NIF_TERM esock_atom_inet6; extern ERL_NIF_TERM esock_atom_iov; extern ERL_NIF_TERM esock_atom_ip; +extern ERL_NIF_TERM esock_atom_ipcomp_level; extern ERL_NIF_TERM esock_atom_ipv6; +extern ERL_NIF_TERM esock_atom_join_group; +extern ERL_NIF_TERM esock_atom_keepalive; +extern ERL_NIF_TERM esock_atom_leave_group; extern ERL_NIF_TERM esock_atom_level; +extern ERL_NIF_TERM esock_atom_linger; extern ERL_NIF_TERM esock_atom_local; extern ERL_NIF_TERM esock_atom_loopback; extern ERL_NIF_TERM esock_atom_lowdelay; +extern ERL_NIF_TERM esock_atom_mark; extern ERL_NIF_TERM esock_atom_mincost; +extern ERL_NIF_TERM esock_atom_minttl; +extern ERL_NIF_TERM esock_atom_msfilter; +extern ERL_NIF_TERM esock_atom_mtu; +extern ERL_NIF_TERM esock_atom_mtu_discover; +extern ERL_NIF_TERM esock_atom_multicast_all; +extern ERL_NIF_TERM esock_atom_multicast_hops; +extern ERL_NIF_TERM esock_atom_multicast_if; +extern ERL_NIF_TERM esock_atom_multicast_loop; +extern ERL_NIF_TERM esock_atom_multicast_ttl; +extern ERL_NIF_TERM esock_atom_nodefrag; extern ERL_NIF_TERM esock_atom_not_found; extern ERL_NIF_TERM esock_atom_not_owner; extern ERL_NIF_TERM esock_atom_ok; extern ERL_NIF_TERM esock_atom_oob; +extern ERL_NIF_TERM esock_atom_oobinline; +extern ERL_NIF_TERM esock_atom_options; extern ERL_NIF_TERM esock_atom_origdstaddr; +extern ERL_NIF_TERM esock_atom_passcred; extern ERL_NIF_TERM esock_atom_path; +extern ERL_NIF_TERM esock_atom_peekcred; +extern ERL_NIF_TERM esock_atom_peek_off; extern ERL_NIF_TERM esock_atom_pktinfo; +extern ERL_NIF_TERM esock_atom_pktoptions; extern ERL_NIF_TERM esock_atom_port; +extern ERL_NIF_TERM esock_atom_portrange; +extern ERL_NIF_TERM esock_atom_priority; extern ERL_NIF_TERM esock_atom_protocol; extern ERL_NIF_TERM esock_atom_raw; +extern ERL_NIF_TERM esock_atom_rcvbuf; +extern ERL_NIF_TERM esock_atom_rcvbufforce; +extern ERL_NIF_TERM esock_atom_rcvlowat; +extern ERL_NIF_TERM esock_atom_rcvtimeo; extern ERL_NIF_TERM esock_atom_rdm; extern ERL_NIF_TERM esock_atom_recv; +extern ERL_NIF_TERM esock_atom_recvdstaddr; +extern ERL_NIF_TERM esock_atom_recverr; extern ERL_NIF_TERM esock_atom_recvfrom; +extern ERL_NIF_TERM esock_atom_recvif; extern ERL_NIF_TERM esock_atom_recvmsg; +extern ERL_NIF_TERM esock_atom_recvopts; +extern ERL_NIF_TERM esock_atom_recvorigdstaddr; +extern ERL_NIF_TERM esock_atom_recvpktinfo; +extern ERL_NIF_TERM esock_atom_recvtclass; +extern ERL_NIF_TERM esock_atom_recvtos; +extern ERL_NIF_TERM esock_atom_recvttl; extern ERL_NIF_TERM esock_atom_reliability; +extern ERL_NIF_TERM esock_atom_retopts; +extern ERL_NIF_TERM esock_atom_reuseaddr; +extern ERL_NIF_TERM esock_atom_reuseport; extern ERL_NIF_TERM esock_atom_rights; +extern ERL_NIF_TERM esock_atom_router_alert; +extern ERL_NIF_TERM esock_atom_rthdr; +extern ERL_NIF_TERM esock_atom_rxq_ovfl; extern ERL_NIF_TERM esock_atom_scope_id; extern ERL_NIF_TERM esock_atom_sctp; extern ERL_NIF_TERM esock_atom_sec; extern ERL_NIF_TERM esock_atom_select_sent; extern ERL_NIF_TERM esock_atom_send; extern ERL_NIF_TERM esock_atom_sendmsg; +extern ERL_NIF_TERM esock_atom_sendsrcaddr; extern ERL_NIF_TERM esock_atom_sendto; extern ERL_NIF_TERM esock_atom_seqpacket; +extern ERL_NIF_TERM esock_atom_setfib; +extern ERL_NIF_TERM esock_atom_sndbuf; +extern ERL_NIF_TERM esock_atom_sndbufforce; +extern ERL_NIF_TERM esock_atom_sndlowat; +extern ERL_NIF_TERM esock_atom_sndtimeo; extern ERL_NIF_TERM esock_atom_socket; extern ERL_NIF_TERM esock_atom_spec_dst; extern ERL_NIF_TERM esock_atom_stream; +extern ERL_NIF_TERM esock_atom_tclass; extern ERL_NIF_TERM esock_atom_tcp; extern ERL_NIF_TERM esock_atom_throughput; extern ERL_NIF_TERM esock_atom_timestamp; extern ERL_NIF_TERM esock_atom_tos; +extern ERL_NIF_TERM esock_atom_transparent; extern ERL_NIF_TERM esock_atom_true; extern ERL_NIF_TERM esock_atom_trunc; extern ERL_NIF_TERM esock_atom_ttl; extern ERL_NIF_TERM esock_atom_type; extern ERL_NIF_TERM esock_atom_udp; +extern ERL_NIF_TERM esock_atom_unblock_source; extern ERL_NIF_TERM esock_atom_undefined; +extern ERL_NIF_TERM esock_atom_unicast_hops; extern ERL_NIF_TERM esock_atom_unknown; extern ERL_NIF_TERM esock_atom_usec; +extern ERL_NIF_TERM esock_atom_use_min_mtu; +extern ERL_NIF_TERM esock_atom_v6only; /* ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ diff --git a/erts/emulator/nifs/common/socket_nif.c b/erts/emulator/nifs/common/socket_nif.c index 964b9ccd73..359cc7db62 100644 --- a/erts/emulator/nifs/common/socket_nif.c +++ b/erts/emulator/nifs/common/socket_nif.c @@ -2387,74 +2387,151 @@ static char str_exsend[] = "exsend"; // failed send /* *** "Global" Atoms *** */ ERL_NIF_TERM esock_atom_accept; ERL_NIF_TERM esock_atom_acceptconn; +ERL_NIF_TERM esock_atom_acceptfilter; ERL_NIF_TERM esock_atom_addr; +ERL_NIF_TERM esock_atom_addrform; +ERL_NIF_TERM esock_atom_add_membership; +ERL_NIF_TERM esock_atom_add_source_membership; ERL_NIF_TERM esock_atom_any; +ERL_NIF_TERM esock_atom_authhdr; +ERL_NIF_TERM esock_atom_auth_level; ERL_NIF_TERM esock_atom_bindtodevice; +ERL_NIF_TERM esock_atom_block_source; ERL_NIF_TERM esock_atom_broadcast; +ERL_NIF_TERM esock_atom_busy_poll; +ERL_NIF_TERM esock_atom_checksum; ERL_NIF_TERM esock_atom_connect; ERL_NIF_TERM esock_atom_credentials; ERL_NIF_TERM esock_atom_ctrl; ERL_NIF_TERM esock_atom_ctrunc; ERL_NIF_TERM esock_atom_data; -ERL_NIF_TERM esock_atom_dgram; ERL_NIF_TERM esock_atom_debug; +ERL_NIF_TERM esock_atom_dgram; +ERL_NIF_TERM esock_atom_domain; +ERL_NIF_TERM esock_atom_dontfrag; +ERL_NIF_TERM esock_atom_dontroute; +ERL_NIF_TERM esock_atom_drop_membership; +ERL_NIF_TERM esock_atom_drop_source_membership; +ERL_NIF_TERM esock_atom_dstopts; ERL_NIF_TERM esock_atom_eor; ERL_NIF_TERM esock_atom_error; ERL_NIF_TERM esock_atom_errqueue; +ERL_NIF_TERM esock_atom_esp_network_level; +ERL_NIF_TERM esock_atom_esp_trans_level; +ERL_NIF_TERM esock_atom_faith; ERL_NIF_TERM esock_atom_false; ERL_NIF_TERM esock_atom_family; ERL_NIF_TERM esock_atom_flags; ERL_NIF_TERM esock_atom_flowinfo; +ERL_NIF_TERM esock_atom_freebind; +ERL_NIF_TERM esock_atom_hdrincl; +ERL_NIF_TERM esock_atom_hoplimit; +ERL_NIF_TERM esock_atom_hopopts; ERL_NIF_TERM esock_atom_ifindex; ERL_NIF_TERM esock_atom_inet; ERL_NIF_TERM esock_atom_inet6; ERL_NIF_TERM esock_atom_iov; ERL_NIF_TERM esock_atom_ip; +ERL_NIF_TERM esock_atom_ipcomp_level; ERL_NIF_TERM esock_atom_ipv6; +ERL_NIF_TERM esock_atom_join_group; +ERL_NIF_TERM esock_atom_keepalive; +ERL_NIF_TERM esock_atom_leave_group; ERL_NIF_TERM esock_atom_level; +ERL_NIF_TERM esock_atom_linger; ERL_NIF_TERM esock_atom_local; ERL_NIF_TERM esock_atom_loopback; ERL_NIF_TERM esock_atom_lowdelay; +ERL_NIF_TERM esock_atom_mark; ERL_NIF_TERM esock_atom_mincost; +ERL_NIF_TERM esock_atom_minttl; +ERL_NIF_TERM esock_atom_msfilter; +ERL_NIF_TERM esock_atom_mtu; +ERL_NIF_TERM esock_atom_mtu_discover; +ERL_NIF_TERM esock_atom_multicast_all; +ERL_NIF_TERM esock_atom_multicast_hops; +ERL_NIF_TERM esock_atom_multicast_if; +ERL_NIF_TERM esock_atom_multicast_loop; +ERL_NIF_TERM esock_atom_multicast_ttl; +ERL_NIF_TERM esock_atom_nodefrag; ERL_NIF_TERM esock_atom_not_found; ERL_NIF_TERM esock_atom_not_owner; ERL_NIF_TERM esock_atom_ok; ERL_NIF_TERM esock_atom_oob; +ERL_NIF_TERM esock_atom_oobinline; +ERL_NIF_TERM esock_atom_options; ERL_NIF_TERM esock_atom_origdstaddr; +ERL_NIF_TERM esock_atom_passcred; ERL_NIF_TERM esock_atom_path; +ERL_NIF_TERM esock_atom_peekcred; +ERL_NIF_TERM esock_atom_peek_off; ERL_NIF_TERM esock_atom_pktinfo; +ERL_NIF_TERM esock_atom_pktoptions; ERL_NIF_TERM esock_atom_port; +ERL_NIF_TERM esock_atom_portrange; +ERL_NIF_TERM esock_atom_priority; ERL_NIF_TERM esock_atom_protocol; ERL_NIF_TERM esock_atom_raw; +ERL_NIF_TERM esock_atom_rcvbuf; +ERL_NIF_TERM esock_atom_rcvbufforce; +ERL_NIF_TERM esock_atom_rcvlowat; +ERL_NIF_TERM esock_atom_rcvtimeo; ERL_NIF_TERM esock_atom_rdm; ERL_NIF_TERM esock_atom_recv; +ERL_NIF_TERM esock_atom_recvdstaddr; +ERL_NIF_TERM esock_atom_recverr; ERL_NIF_TERM esock_atom_recvfrom; +ERL_NIF_TERM esock_atom_recvif; ERL_NIF_TERM esock_atom_recvmsg; +ERL_NIF_TERM esock_atom_recvopts; +ERL_NIF_TERM esock_atom_recvorigdstaddr; +ERL_NIF_TERM esock_atom_recvpktinfo; +ERL_NIF_TERM esock_atom_recvtclass; +ERL_NIF_TERM esock_atom_recvtos; +ERL_NIF_TERM esock_atom_recvttl; ERL_NIF_TERM esock_atom_reliability; +ERL_NIF_TERM esock_atom_retopts; +ERL_NIF_TERM esock_atom_reuseaddr; +ERL_NIF_TERM esock_atom_reuseport; ERL_NIF_TERM esock_atom_rights; +ERL_NIF_TERM esock_atom_router_alert; +ERL_NIF_TERM esock_atom_rthdr; +ERL_NIF_TERM esock_atom_rxq_ovfl; ERL_NIF_TERM esock_atom_scope_id; ERL_NIF_TERM esock_atom_sctp; ERL_NIF_TERM esock_atom_sec; ERL_NIF_TERM esock_atom_select_sent; ERL_NIF_TERM esock_atom_send; ERL_NIF_TERM esock_atom_sendmsg; +ERL_NIF_TERM esock_atom_sendsrcaddr; ERL_NIF_TERM esock_atom_sendto; ERL_NIF_TERM esock_atom_seqpacket; +ERL_NIF_TERM esock_atom_setfib; ERL_NIF_TERM esock_atom_socket; +ERL_NIF_TERM esock_atom_sndbuf; +ERL_NIF_TERM esock_atom_sndbufforce; +ERL_NIF_TERM esock_atom_sndlowat; +ERL_NIF_TERM esock_atom_sndtimeo; ERL_NIF_TERM esock_atom_spec_dst; ERL_NIF_TERM esock_atom_stream; +ERL_NIF_TERM esock_atom_tclass; ERL_NIF_TERM esock_atom_tcp; ERL_NIF_TERM esock_atom_throughput; ERL_NIF_TERM esock_atom_timestamp; ERL_NIF_TERM esock_atom_tos; +ERL_NIF_TERM esock_atom_transparent; ERL_NIF_TERM esock_atom_true; ERL_NIF_TERM esock_atom_trunc; ERL_NIF_TERM esock_atom_ttl; ERL_NIF_TERM esock_atom_type; ERL_NIF_TERM esock_atom_udp; +ERL_NIF_TERM esock_atom_unblock_source; ERL_NIF_TERM esock_atom_undefined; +ERL_NIF_TERM esock_atom_unicast_hops; ERL_NIF_TERM esock_atom_unknown; ERL_NIF_TERM esock_atom_usec; +ERL_NIF_TERM esock_atom_use_min_mtu; +ERL_NIF_TERM esock_atom_v6only; /* *** "Global" error (=reason) atoms *** */ ERL_NIF_TERM esock_atom_eagain; @@ -2749,6 +2826,11 @@ ERL_NIF_TERM nsupports_options_socket(ErlNifEnv* env) TARRAY_ADD(opts, tmp); + /* *** SOCKET_OPT_SOCK_ACCEPTFILTER => SO_ACCEPTFILTER *** */ + tmp = MKT2(env, esock_atom_acceptfilter, esock_atom_false); + TARRAY_ADD(opts, tmp); + + /* *** SOCKET_OPT_SOCK_BINDTODEVICE => SO_BINDTODEVICE *** */ #if defined(SO_BINDTODEVICE) tmp = MKT2(env, esock_atom_bindtodevice, esock_atom_true); @@ -2767,6 +2849,222 @@ ERL_NIF_TERM nsupports_options_socket(ErlNifEnv* env) TARRAY_ADD(opts, tmp); + /* *** SOCKET_OPT_SOCK_BUSY_POLL => SO_BUSY_POLL *** */ + tmp = MKT2(env, esock_atom_busy_poll, esock_atom_false); + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_SOCK_DEBUG => SO_DEBUG *** */ +#if defined(SO_DEBUG) + tmp = MKT2(env, esock_atom_debug, esock_atom_true); +#else + tmp = MKT2(env, esock_atom_debug, esock_atom_false); +#endif + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_SOCK_DOMAIN => SO_DOMAIN *** */ +#if defined(SO_DOMAIN) + tmp = MKT2(env, esock_atom_domain, esock_atom_true); +#else + tmp = MKT2(env, esock_atom_domain, esock_atom_false); +#endif + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_SOCK_DONTROUTE => SO_DONTROUTE *** */ +#if defined(SO_DONTROUTE) + tmp = MKT2(env, esock_atom_dontroute, esock_atom_true); +#else + tmp = MKT2(env, esock_atom_dontroute, esock_atom_false); +#endif + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_SOCK_ERROR => SO_ERROR *** */ + tmp = MKT2(env, esock_atom_error, esock_atom_false); + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_SOCK_KEEPALIVE => SO_KEEPALIVE *** */ +#if defined(SO_KEEPALIVE) + tmp = MKT2(env, esock_atom_keepalive, esock_atom_true); +#else + tmp = MKT2(env, esock_atom_keepalive, esock_atom_false); +#endif + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_SOCK_LINGER => SO_LINGER *** */ +#if defined(SO_LINGER) + tmp = MKT2(env, esock_atom_linger, esock_atom_true); +#else + tmp = MKT2(env, esock_atom_linger, esock_atom_false); +#endif + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_SOCK_MARK => SO_MARK *** */ + tmp = MKT2(env, esock_atom_mark, esock_atom_false); + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_SOCK_OOBINLINE => SO_OOBINLINE *** */ +#if defined(SO_OOBINLINE) + tmp = MKT2(env, esock_atom_oobinline, esock_atom_true); +#else + tmp = MKT2(env, esock_atom_oobinline, esock_atom_false); +#endif + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_PASSCRED => SO_PASSCRED *** */ + tmp = MKT2(env, esock_atom_passcred, esock_atom_false); + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_SOCK_PEEK_OFF => SO_PEEK_OFF *** */ +#if defined(SO_PEEK_OFF) + tmp = MKT2(env, esock_atom_peek_off, esock_atom_true); +#else + tmp = MKT2(env, esock_atom_peek_off, esock_atom_false); +#endif + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_SOCK_PEEKCRED => SO_PEEKCRED *** */ + tmp = MKT2(env, esock_atom_peekcred, esock_atom_false); + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_SOCK_PRIORITY => SO_PRIORITY *** */ +#if defined(SO_PRIORITY) + tmp = MKT2(env, esock_atom_priority, esock_atom_true); +#else + tmp = MKT2(env, esock_atom_priority, esock_atom_false); +#endif + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_SOCK_PROTOCOL => SO_PROTOCOL *** */ +#if defined(SO_PROTOCOL) + tmp = MKT2(env, esock_atom_protocol, esock_atom_true); +#else + tmp = MKT2(env, esock_atom_protocol, esock_atom_false); +#endif + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_SOCK_RCVBUF => SO_RCVBUF *** */ +#if defined(SO_RCVBUF) + tmp = MKT2(env, esock_atom_rcvbuf, esock_atom_true); +#else + tmp = MKT2(env, esock_atom_rcvbuf, esock_atom_false); +#endif + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_SOCK_RCVBUFFORCE => SO_RCVBUFFORCE *** */ + tmp = MKT2(env, esock_atom_rcvbufforce, esock_atom_false); + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_SOCK_RCVLOWAT => SO_RCVLOWAT *** */ +#if defined(SO_RCVLOWAT) + tmp = MKT2(env, esock_atom_rcvlowat, esock_atom_true); +#else + tmp = MKT2(env, esock_atom_rcvlowat, esock_atom_false); +#endif + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_SOCK_RCVTIMEO => SO_RCVTIMEO *** */ +#if defined(SO_RCVTIMEO) + tmp = MKT2(env, esock_atom_rcvtimeo, esock_atom_true); +#else + tmp = MKT2(env, esock_atom_rcvtimeo, esock_atom_false); +#endif + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_SOCK_REUSEADDR => SO_REUSEADDR *** */ +#if defined(SO_REUSEADDR) + tmp = MKT2(env, esock_atom_reuseaddr, esock_atom_true); +#else + tmp = MKT2(env, esock_atom_reuseaddr, esock_atom_false); +#endif + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_SOCK_REUSEPORT => SO_REUSEPORT *** */ +#if defined(SO_REUSEPORT) + tmp = MKT2(env, esock_atom_reuseport, esock_atom_true); +#else + tmp = MKT2(env, esock_atom_reuseport, esock_atom_false); +#endif + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_SOCK_RXQ_OVFL => SO_RXQ_OVFL *** */ + tmp = MKT2(env, esock_atom_rxq_ovfl, esock_atom_false); + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_SOCK_SETFIB => SO_SETFIB *** */ + tmp = MKT2(env, esock_atom_setfib, esock_atom_false); + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_SOCK_SNDBUF => SO_SNDBUF *** */ +#if defined(SO_SNDBUF) + tmp = MKT2(env, esock_atom_sndbuf, esock_atom_true); +#else + tmp = MKT2(env, esock_atom_sndbuf, esock_atom_false); +#endif + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_SOCK_SNDBUFFORCE => SO_SNDBUFFORCE *** */ + tmp = MKT2(env, esock_atom_sndbufforce, esock_atom_false); + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_SOCK_SNDLOWAT => SO_SNDLOWAT *** */ +#if defined(SO_SNDLOWAT) + tmp = MKT2(env, esock_atom_sndlowat, esock_atom_true); +#else + tmp = MKT2(env, esock_atom_sndlowat, esock_atom_false); +#endif + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_SOCK_SNDTIMEO => SO_SNDTIMEO *** */ +#if defined(SO_SNDTIMEO) + tmp = MKT2(env, esock_atom_sndtimeo, esock_atom_true); +#else + tmp = MKT2(env, esock_atom_sndtimeo, esock_atom_false); +#endif + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_SOCK_TIMESTAMP => SO_TIMESTAMP *** */ +#if defined(SO_TIMESTAMP) + tmp = MKT2(env, esock_atom_timestamp, esock_atom_true); +#else + tmp = MKT2(env, esock_atom_timestamp, esock_atom_false); +#endif + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_SOCK_TYPE => SO_TYPE *** */ +#if defined(SO_TYPE) + tmp = MKT2(env, esock_atom_type, esock_atom_true); +#else + tmp = MKT2(env, esock_atom_type, esock_atom_false); +#endif + TARRAY_ADD(opts, tmp); + + TARRAY_TOLIST(opts, env, &optsL); return optsL; @@ -2777,11 +3075,298 @@ ERL_NIF_TERM nsupports_options_socket(ErlNifEnv* env) static ERL_NIF_TERM nsupports_options_ip(ErlNifEnv* env) { - ERL_NIF_TERM result; + SocketTArray opts = TARRAY_CREATE(128); + ERL_NIF_TERM tmp, optsL; - result = MKEL(env); - return result; + /* *** SOCKET_OPT_IP_ADD_MEMBERSHIP => IP_ADD_MEMBERSHIP *** */ +#if defined(IP_ADD_MEMBERSHIP) + tmp = MKT2(env, esock_atom_add_membership, esock_atom_true); +#else + tmp = MKT2(env, esock_atom_add_membership, esock_atom_false); +#endif + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_IP_ADD_SOURCE_MEMBERSHIP => IP_ADD_SOURCE_MEMBERSHIP *** */ +#if defined(IP_ADD_SOURCE_MEMBERSHIP) + tmp = MKT2(env, esock_atom_add_source_membership, esock_atom_true); +#else + tmp = MKT2(env, esock_atom_add_source_membership, esock_atom_false); +#endif + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_IP_BLOCK_SOURCE => IP_BLOCK_SOURCE *** */ +#if defined(IP_BLOCK_SOURCE) + tmp = MKT2(env, esock_atom_block_source, esock_atom_true); +#else + tmp = MKT2(env, esock_atom_block_source, esock_atom_false); +#endif + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_IP_DONTFRAG => IP_DONTFRAG *** */ + tmp = MKT2(env, esock_atom_dontfrag, esock_atom_false); + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_IP_DROP_MEMBERSHIP => IP_DROP_MEMBERSHIP *** */ +#if defined(IP_DROP_MEMBERSHIP) + tmp = MKT2(env, esock_atom_drop_membership, esock_atom_true); +#else + tmp = MKT2(env, esock_atom_drop_membership, esock_atom_false); +#endif + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_IP_DROP_SOURCE_MEMBERSHIP => IP_DROP_SOURCE_MEMBERSHIP *** */ +#if defined(IP_DROP_SOURCE_MEMBERSHIP) + tmp = MKT2(env, esock_atom_drop_source_membership, esock_atom_true); +#else + tmp = MKT2(env, esock_atom_drop_source_membership, esock_atom_false); +#endif + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_IP_FREEBIND => IP_FREEBIND *** */ +#if defined(IP_FREEBIND) + tmp = MKT2(env, esock_atom_freebind, esock_atom_true); +#else + tmp = MKT2(env, esock_atom_freebind, esock_atom_false); +#endif + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_IP_HDRINCL => IP_HDRINCL *** */ +#if defined(IP_HDRINCL) + tmp = MKT2(env, esock_atom_hdrincl, esock_atom_true); +#else + tmp = MKT2(env, esock_atom_hdrincl, esock_atom_false); +#endif + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_IP_MINTTL => IP_MINTTL *** */ +#if defined(IP_MINTTL) + tmp = MKT2(env, esock_atom_minttl, esock_atom_true); +#else + tmp = MKT2(env, esock_atom_minttl, esock_atom_false); +#endif + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_IP_MSFILTER => IP_MSFILTER / IP_MSFILTER_SIZE *** */ +#if defined(IP_MSFILTER) && defined(IP_MSFILTER_SIZE) + tmp = MKT2(env, esock_atom_msfilter, esock_atom_true); +#else + tmp = MKT2(env, esock_atom_msfilter, esock_atom_false); +#endif + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_IP_MTU => IP_MTU *** */ + tmp = MKT2(env, esock_atom_mtu, esock_atom_false); + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_IP_MTU_DISCOVER => IP_MTU_DISCOVER *** */ +#if defined(IP_MTU_DISCOVER) + tmp = MKT2(env, esock_atom_mtu_discover, esock_atom_true); +#else + tmp = MKT2(env, esock_atom_mtu_discover, esock_atom_false); +#endif + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_IP_MULTICAST_ALL => IP_MULTICAST_ALL *** */ +#if defined(IP_MULTICAST_ALL) + tmp = MKT2(env, esock_atom_multicast_all, esock_atom_true); +#else + tmp = MKT2(env, esock_atom_multicast_all, esock_atom_false); +#endif + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_IP_MULTICAST_IF => IP_MULTICAST_IF *** */ +#if defined(IP_MULTICAST_IF) + tmp = MKT2(env, esock_atom_multicast_if, esock_atom_true); +#else + tmp = MKT2(env, esock_atom_multicast_if, esock_atom_false); +#endif + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_IP_MULTICAST_LOOP => IP_MULTICAST_LOOP *** */ +#if defined(IP_MULTICAST_LOOP) + tmp = MKT2(env, esock_atom_multicast_loop, esock_atom_true); +#else + tmp = MKT2(env, esock_atom_multicast_loop, esock_atom_false); +#endif + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_IP_MULTICAST_TTL => IP_MULTICAST_TTL *** */ +#if defined(IP_MULTICAST_TTL) + tmp = MKT2(env, esock_atom_multicast_ttl, esock_atom_true); +#else + tmp = MKT2(env, esock_atom_multicast_ttl, esock_atom_false); +#endif + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_IP_NODEFRAG => IP_NODEFRAG *** */ +#if defined(IP_NODEFRAG) + tmp = MKT2(env, esock_atom_nodefrag, esock_atom_true); +#else + tmp = MKT2(env, esock_atom_nodefrag, esock_atom_false); +#endif + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_IP_OPTIONS => IP_OPTIONS *** */ + tmp = MKT2(env, esock_atom_options, esock_atom_false); + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_IP_PKTINFO => IP_PKTINFO *** */ +#if defined(IP_PKTINFO) + tmp = MKT2(env, esock_atom_pktinfo, esock_atom_true); +#else + tmp = MKT2(env, esock_atom_pktinfo, esock_atom_false); +#endif + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_IP_RECVDSTADDR => IP_RECVDSTADDR *** */ +#if defined(IP_RECVDSTADDR) + tmp = MKT2(env, esock_atom_recvdstaddr, esock_atom_true); +#else + tmp = MKT2(env, esock_atom_recvdstaddr, esock_atom_false); +#endif + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_IP_RECVERR => IP_RECVERR *** */ +#if defined(IP_RECVERR) + tmp = MKT2(env, esock_atom_recverr, esock_atom_true); +#else + tmp = MKT2(env, esock_atom_recverr, esock_atom_false); +#endif + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_IP_RECVIF => IP_RECVIF *** */ +#if defined(IP_RECVIF) + tmp = MKT2(env, esock_atom_recvif, esock_atom_true); +#else + tmp = MKT2(env, esock_atom_recvif, esock_atom_false); +#endif + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_IP_RECVOPTS => IP_RECVOPTS *** */ +#if defined(IP_RECVOPTS) + tmp = MKT2(env, esock_atom_recvopts, esock_atom_true); +#else + tmp = MKT2(env, esock_atom_recvopts, esock_atom_false); +#endif + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_IP_RECVORIGDSTADDR => IP_RECVORIGDSTADDR *** */ +#if defined(IP_RECVORIGDSTADDR) + tmp = MKT2(env, esock_atom_recvorigdstaddr, esock_atom_true); +#else + tmp = MKT2(env, esock_atom_recvorigdstaddr, esock_atom_false); +#endif + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_IP_RECVTOS => IP_RECVTOS *** */ +#if defined(IP_RECVTOS) + tmp = MKT2(env, esock_atom_recvtos, esock_atom_true); +#else + tmp = MKT2(env, esock_atom_recvtos, esock_atom_false); +#endif + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_IP_RECVTTL => IP_RECVTTL *** */ +#if defined(IP_RECVTTL) + tmp = MKT2(env, esock_atom_recvttl, esock_atom_true); +#else + tmp = MKT2(env, esock_atom_recvttl, esock_atom_false); +#endif + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_IP_RETOPTS => IP_RETOPTS *** */ +#if defined(IP_RETOPTS) + tmp = MKT2(env, esock_atom_retopts, esock_atom_true); +#else + tmp = MKT2(env, esock_atom_retopts, esock_atom_false); +#endif + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_IP_ROUTER_ALERT => IP_ROUTER_ALERT *** */ +#if defined(IP_ROUTER_ALERT) + tmp = MKT2(env, esock_atom_router_alert, esock_atom_true); +#else + tmp = MKT2(env, esock_atom_router_alert, esock_atom_false); +#endif + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_IP_SENDSRCADDR => IP_SENDSRCADDR *** */ +#if defined(IP_SENDSRCADDR) + tmp = MKT2(env, esock_atom_sendsrcaddr, esock_atom_true); +#else + tmp = MKT2(env, esock_atom_sendsrcaddr, esock_atom_false); +#endif + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_IP_TOS => IP_TOS *** */ +#if defined(IP_TOS) + tmp = MKT2(env, esock_atom_tos, esock_atom_true); +#else + tmp = MKT2(env, esock_atom_tos, esock_atom_false); +#endif + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_IP_TRANSPARENT => IP_TRANSPARENT *** */ +#if defined(IP_TRANSPARENT) + tmp = MKT2(env, esock_atom_transparent, esock_atom_true); +#else + tmp = MKT2(env, esock_atom_transparent, esock_atom_false); +#endif + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_IP_TTL => IP_TTL *** */ +#if defined(IP_TTL) + tmp = MKT2(env, esock_atom_ttl, esock_atom_true); +#else + tmp = MKT2(env, esock_atom_ttl, esock_atom_false); +#endif + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_IP_UNBLOCK_SOURCE => IP_UNBLOCK_SOURCE *** */ +#if defined(IP_UNBLOCK_SOURCE) + tmp = MKT2(env, esock_atom_unblock_source, esock_atom_true); +#else + tmp = MKT2(env, esock_atom_unblock_source, esock_atom_false); +#endif + TARRAY_ADD(opts, tmp); + + + TARRAY_TOLIST(opts, env, &optsL); + + return optsL; } @@ -2789,11 +3374,249 @@ ERL_NIF_TERM nsupports_options_ip(ErlNifEnv* env) static ERL_NIF_TERM nsupports_options_ipv6(ErlNifEnv* env) { - ERL_NIF_TERM result; + SocketTArray opts = TARRAY_CREATE(128); + ERL_NIF_TERM tmp, optsL; - result = MKEL(env); - return result; + /* *** SOCKET_OPT_IPV6_ADDRFORM => IPV6_ADDRFORM *** */ +#if defined(IPV6_ADDRFORM) + tmp = MKT2(env, esock_atom_addrform, esock_atom_true); +#else + tmp = MKT2(env, esock_atom_addrform, esock_atom_false); +#endif + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_IPV6_ADD_MEMBERSHIP => IPV6_ADD_MEMBERSHIP *** */ +#if defined(IPV6_ADD_MEMBERSHIP) + tmp = MKT2(env, esock_atom_add_membership, esock_atom_true); +#else + tmp = MKT2(env, esock_atom_add_membership, esock_atom_false); +#endif + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_IPV6_AUTHHDR => IPV6_AUTHHDR *** */ +#if defined(IPV6_AUTHHDR) + tmp = MKT2(env, esock_atom_authhdr, esock_atom_true); +#else + tmp = MKT2(env, esock_atom_authhdr, esock_atom_false); +#endif + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_IPV6_AUTH_LEVEL => IPV6_AUTH_LEVEL *** */ + tmp = MKT2(env, esock_atom_auth_level, esock_atom_false); + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_IPV6_CHECKSUM => IPV6_CHECKSUM *** */ + tmp = MKT2(env, esock_atom_checksum, esock_atom_false); + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_IPV6_DROP_MEMBERSHIP => IPV6_DROP_MEMBERSHIP *** */ +#if defined(IPV6_DROP_MEMBERSHIP) + tmp = MKT2(env, esock_atom_drop_membership, esock_atom_true); +#else + tmp = MKT2(env, esock_atom_drop_membership, esock_atom_false); +#endif + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_IPV6_DSTOPTS => IPV6_DSTOPTS *** */ +#if defined(IPV6_DSTOPTS) + tmp = MKT2(env, esock_atom_dstopts, esock_atom_true); +#else + tmp = MKT2(env, esock_atom_dstopts, esock_atom_false); +#endif + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_IPV6_ESP_NETWORK_LEVEL => IPV6_ESP_NETWORK_LEVEL *** */ + tmp = MKT2(env, esock_atom_esp_network_level, esock_atom_false); + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_IPV6_ESP_TRANS_LEVEL => IPV6_ESP_TRANS_LEVEL *** */ + tmp = MKT2(env, esock_atom_esp_trans_level, esock_atom_false); + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_IPV6_FAITH => IPV6_FAITH *** */ + tmp = MKT2(env, esock_atom_faith, esock_atom_false); + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_IPV6_FLOWINFO => IPV6_FLOWINFO *** */ +#if defined(IPV6_FLOWINFO) + tmp = MKT2(env, esock_atom_flowinfo, esock_atom_true); +#else + tmp = MKT2(env, esock_atom_flowinfo, esock_atom_false); +#endif + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_IPV6_HOPLIMIT => IPV6_HOPLIMIT *** */ +#if defined(IPV6_HOPLIMIT) + tmp = MKT2(env, esock_atom_hoplimit, esock_atom_true); +#else + tmp = MKT2(env, esock_atom_hoplimit, esock_atom_false); +#endif + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_IPV6_HOPOPTS => IPV6_HOPOPTS *** */ +#if defined(IPV6_HOPOPTS) + tmp = MKT2(env, esock_atom_hopopts, esock_atom_true); +#else + tmp = MKT2(env, esock_atom_hopopts, esock_atom_false); +#endif + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_IPV6_IPCOMP_LEVEL => IPV6_IPCOMP_LEVEL *** */ + tmp = MKT2(env, esock_atom_ipcomp_level, esock_atom_false); + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_IPV6_JOIN_GROUP => IPV6_JOIN_GROUP *** */ + tmp = MKT2(env, esock_atom_join_group, esock_atom_false); + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_IPV6_LEAVE_GROUP => IPV6_LEAVE_GROUP *** */ + tmp = MKT2(env, esock_atom_leave_group, esock_atom_false); + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_IPV6_MTU => IPV6_MTU *** */ +#if defined(IPV6_MTU) + tmp = MKT2(env, esock_atom_mtu, esock_atom_true); +#else + tmp = MKT2(env, esock_atom_mtu, esock_atom_false); +#endif + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_IPV6_MTU_DISCOVER => IPV6_MTU_DISCOVER *** */ +#if defined(IPV6_MTU_DISCOVER) + tmp = MKT2(env, esock_atom_mtu_discover, esock_atom_true); +#else + tmp = MKT2(env, esock_atom_mtu_discover, esock_atom_false); +#endif + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_IPV6_MULTICAST_HOPS => IPV6_MULTICAST_HOPS *** */ +#if defined(IPV6_MULTICAST_HOPS) + tmp = MKT2(env, esock_atom_multicast_hops, esock_atom_true); +#else + tmp = MKT2(env, esock_atom_multicast_hops, esock_atom_false); +#endif + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_IPV6_MULTICAST_IF => IPV6_MULTICAST_IF *** */ +#if defined(IPV6_MULTICAST_IF) + tmp = MKT2(env, esock_atom_multicast_if, esock_atom_true); +#else + tmp = MKT2(env, esock_atom_multicast_if, esock_atom_false); +#endif + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_IPV6_MULTICAST_LOOP => IPV6_MULTICAST_LOOP *** */ +#if defined(IPV6_MULTICAST_LOOP) + tmp = MKT2(env, esock_atom_multicast_loop, esock_atom_true); +#else + tmp = MKT2(env, esock_atom_multicast_loop, esock_atom_false); +#endif + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_IPV6_PORTRANGE => IPV6_PORTRANGE *** */ + tmp = MKT2(env, esock_atom_portrange, esock_atom_false); + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_IPV6_PKTOPTIONS => IPV6_PKTOPTIONS *** */ + tmp = MKT2(env, esock_atom_pktoptions, esock_atom_false); + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_IPV6_RECVERR => IPV6_RECVERR *** */ +#if defined(IPV6_RECVERR) + tmp = MKT2(env, esock_atom_recverr, esock_atom_true); +#else + tmp = MKT2(env, esock_atom_recverr, esock_atom_false); +#endif + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_IPV6_RECVPKTINFO => IPV6_RECVPKTINFO *** */ +#if defined(IPV6_RECVPKTINFO) + tmp = MKT2(env, esock_atom_recvpktinfo, esock_atom_true); +#else + tmp = MKT2(env, esock_atom_recvpktinfo, esock_atom_false); +#endif + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_IPV6_RECVTCLASS => IPV6_RECVTCLASS *** */ + tmp = MKT2(env, esock_atom_recvtclass, esock_atom_false); + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_IPV6_ROUTER_ALERT => IPV6_ROUTER_ALERT *** */ +#if defined(IPV6_ROUTER_ALERT) + tmp = MKT2(env, esock_atom_router_alert, esock_atom_true); +#else + tmp = MKT2(env, esock_atom_router_alert, esock_atom_false); +#endif + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_IPV6_RTHDR => IPV6_RTHDR *** */ +#if defined(IPV6_RTHDR) + tmp = MKT2(env, esock_atom_rthdr, esock_atom_true); +#else + tmp = MKT2(env, esock_atom_rthdr, esock_atom_false); +#endif + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_IPV6_TCLASS => IPV6_TCLASS *** */ + tmp = MKT2(env, esock_atom_tclass, esock_atom_false); + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_IPV6_UNICAST_HOPS => IPV6_UNICAST_HOPS *** */ +#if defined(IPV6_UNICAST_HOPS) + tmp = MKT2(env, esock_atom_unicast_hops, esock_atom_true); +#else + tmp = MKT2(env, esock_atom_unicast_hops, esock_atom_false); +#endif + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_IPV6_USE_MIN_MTU => IPV6_USE_MIN_MTU *** */ + tmp = MKT2(env, esock_atom_use_min_mtu, esock_atom_false); + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_IPV6_V6ONLY => IPV6_V6ONLY *** */ +#if defined(IPV6_V6ONLY) + tmp = MKT2(env, esock_atom_v6only, esock_atom_true); +#else + tmp = MKT2(env, esock_atom_v6only, esock_atom_false); +#endif + TARRAY_ADD(opts, tmp); + + + TARRAY_TOLIST(opts, env, &optsL); + + return optsL; } @@ -15783,10 +16606,19 @@ int on_load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info) /* Global atom(s) */ esock_atom_accept = MKA(env, "accept"); esock_atom_acceptconn = MKA(env, "acceptconn"); + esock_atom_acceptfilter = MKA(env, "acceptfilter"); esock_atom_addr = MKA(env, "addr"); + esock_atom_addrform = MKA(env, "addrform"); + esock_atom_add_membership = MKA(env, "add_membership"); + esock_atom_add_source_membership = MKA(env, "add_source_membership"); esock_atom_any = MKA(env, "any"); + esock_atom_authhdr = MKA(env, "authhdr"); + esock_atom_auth_level = MKA(env, "auth_level"); esock_atom_bindtodevice = MKA(env, "bindtodevice"); + esock_atom_block_source = MKA(env, "block_source"); esock_atom_broadcast = MKA(env, "broadcast"); + esock_atom_busy_poll = MKA(env, "busy_poll"); + esock_atom_checksum = MKA(env, "checksum"); esock_atom_connect = MKA(env, "connect"); esock_atom_credentials = MKA(env, "credentials"); esock_atom_ctrl = MKA(env, "ctrl"); @@ -15794,63 +16626,131 @@ int on_load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info) esock_atom_data = MKA(env, "data"); esock_atom_debug = MKA(env, "debug"); esock_atom_dgram = MKA(env, "dgram"); + esock_atom_domain = MKA(env, "domain"); + esock_atom_dontfrag = MKA(env, "dontfrag"); + esock_atom_dontroute = MKA(env, "dontroute"); + esock_atom_drop_membership = MKA(env, "drop_membership"); + esock_atom_drop_source_membership = MKA(env, "drop_source_membership"); + esock_atom_dstopts = MKA(env, "dstpopts"); esock_atom_eor = MKA(env, "eor"); esock_atom_error = MKA(env, "error"); esock_atom_errqueue = MKA(env, "errqueue"); + esock_atom_esp_network_level = MKA(env, "esp_network_level"); + esock_atom_esp_trans_level = MKA(env, "esp_trans_level"); + esock_atom_faith = MKA(env, "faith"); esock_atom_false = MKA(env, "false"); esock_atom_family = MKA(env, "family"); esock_atom_flags = MKA(env, "flags"); esock_atom_flowinfo = MKA(env, "flowinfo"); + esock_atom_freebind = MKA(env, "freebind"); + esock_atom_hdrincl = MKA(env, "hdrincl"); + esock_atom_hoplimit = MKA(env, "hoplimit"); + esock_atom_hopopts = MKA(env, "hopopts"); esock_atom_ifindex = MKA(env, "ifindex"); esock_atom_inet = MKA(env, "inet"); esock_atom_inet6 = MKA(env, "inet6"); esock_atom_iov = MKA(env, "iov"); esock_atom_ip = MKA(env, "ip"); + esock_atom_ipcomp_level = MKA(env, "ipcomp_level"); esock_atom_ipv6 = MKA(env, "ipv6"); + esock_atom_join_group = MKA(env, "join_group"); + esock_atom_keepalive = MKA(env, "keepalive"); + esock_atom_leave_group = MKA(env, "leave_group"); esock_atom_level = MKA(env, "level"); + esock_atom_linger = MKA(env, "linger"); esock_atom_local = MKA(env, "local"); esock_atom_loopback = MKA(env, "loopback"); esock_atom_lowdelay = MKA(env, "lowdelay"); + esock_atom_mark = MKA(env, "mark"); esock_atom_mincost = MKA(env, "mincost"); + esock_atom_minttl = MKA(env, "minttl"); + esock_atom_msfilter = MKA(env, "msfilter"); + esock_atom_mtu = MKA(env, "mtu"); + esock_atom_mtu_discover = MKA(env, "mtu_discover"); + esock_atom_multicast_all = MKA(env, "multicast_all"); + esock_atom_multicast_hops = MKA(env, "multicast_hops"); + esock_atom_multicast_if = MKA(env, "multicast_if"); + esock_atom_multicast_loop = MKA(env, "multicast_loop"); + esock_atom_multicast_ttl = MKA(env, "multicast_ttl"); + esock_atom_nodefrag = MKA(env, "nodefrag"); esock_atom_not_found = MKA(env, "not_found"); esock_atom_not_owner = MKA(env, "not_owner"); esock_atom_ok = MKA(env, "ok"); esock_atom_oob = MKA(env, "oob"); + esock_atom_oobinline = MKA(env, "oobinline"); + esock_atom_options = MKA(env, "options"); esock_atom_origdstaddr = MKA(env, "origdstaddr"); + esock_atom_passcred = MKA(env, "passcred"); esock_atom_path = MKA(env, "path"); + esock_atom_peekcred = MKA(env, "peekcred"); + esock_atom_peek_off = MKA(env, "peek_off"); esock_atom_pktinfo = MKA(env, "pktinfo"); + esock_atom_pktoptions = MKA(env, "pktoptions"); esock_atom_port = MKA(env, "port"); + esock_atom_portrange = MKA(env, "portrange"); + esock_atom_priority = MKA(env, "priority"); esock_atom_protocol = MKA(env, "protocol"); esock_atom_raw = MKA(env, "raw"); + esock_atom_rcvbuf = MKA(env, "rcvbuf"); + esock_atom_rcvbufforce = MKA(env, "rcvbufforce"); + esock_atom_rcvlowat = MKA(env, "rcvlowat"); + esock_atom_rcvtimeo = MKA(env, "rcvtimeo"); esock_atom_rdm = MKA(env, "rdm"); esock_atom_recv = MKA(env, "recv"); + esock_atom_recvdstaddr = MKA(env, "recvdstaddr"); + esock_atom_recverr = MKA(env, "recverr"); esock_atom_recvfrom = MKA(env, "recvfrom"); + esock_atom_recvif = MKA(env, "recvif"); esock_atom_recvmsg = MKA(env, "recvmsg"); + esock_atom_recvopts = MKA(env, "recvopts"); + esock_atom_recvorigdstaddr = MKA(env, "recvorigdstaddr"); + esock_atom_recvpktinfo = MKA(env, "recvpktinfo"); + esock_atom_recvtclass = MKA(env, "recvtclass"); + esock_atom_recvtos = MKA(env, "recvtos"); + esock_atom_recvttl = MKA(env, "recvttl"); esock_atom_reliability = MKA(env, "reliability"); + esock_atom_retopts = MKA(env, "retopts"); + esock_atom_reuseaddr = MKA(env, "reuseaddr"); + esock_atom_reuseport = MKA(env, "reuseport"); esock_atom_rights = MKA(env, "rights"); + esock_atom_router_alert = MKA(env, "router_alert"); + esock_atom_rthdr = MKA(env, "rthdr"); + esock_atom_rxq_ovfl = MKA(env, "rxq_ovfl"); esock_atom_scope_id = MKA(env, "scope_id"); esock_atom_sctp = MKA(env, "sctp"); esock_atom_sec = MKA(env, "sec"); esock_atom_select_sent = MKA(env, "select_sent"); esock_atom_send = MKA(env, "send"); esock_atom_sendmsg = MKA(env, "sendmsg"); + esock_atom_sendsrcaddr = MKA(env, "sendsrcaddr"); esock_atom_sendto = MKA(env, "sendto"); esock_atom_seqpacket = MKA(env, "seqpacket"); + esock_atom_setfib = MKA(env, "setfib"); + esock_atom_sndbuf = MKA(env, "sndbuf"); + esock_atom_sndbufforce = MKA(env, "sndbufforce"); + esock_atom_sndlowat = MKA(env, "sndlowat"); + esock_atom_sndtimeo = MKA(env, "sndtimeo"); esock_atom_socket = MKA(env, "socket"); esock_atom_spec_dst = MKA(env, "spec_dst"); esock_atom_stream = MKA(env, "stream"); + esock_atom_tclass = MKA(env, "tclass"); esock_atom_tcp = MKA(env, "tcp"); esock_atom_throughput = MKA(env, "throughput"); esock_atom_timestamp = MKA(env, "timestamp"); esock_atom_tos = MKA(env, "tos"); + esock_atom_transparent = MKA(env, "transparent"); esock_atom_true = MKA(env, "true"); esock_atom_trunc = MKA(env, "trunc"); esock_atom_ttl = MKA(env, "ttl"); esock_atom_type = MKA(env, "type"); esock_atom_udp = MKA(env, "udp"); + esock_atom_unblock_source = MKA(env, "unblock_source"); esock_atom_undefined = MKA(env, "undefined"); + esock_atom_unicast_hops = MKA(env, "unicast_hops"); esock_atom_unknown = MKA(env, "unknown"); esock_atom_usec = MKA(env, "usec"); + esock_atom_use_min_mtu = MKA(env, "use_min_mtu"); + esock_atom_v6only = MKA(env, "v6only"); /* Global error codes */ esock_atom_eafnosupport = MKA(env, ESOCK_STR_EAFNOSUPPORT); diff --git a/erts/preloaded/src/socket.erl b/erts/preloaded/src/socket.erl index 5f23bcce84..882e305b26 100644 --- a/erts/preloaded/src/socket.erl +++ b/erts/preloaded/src/socket.erl @@ -734,8 +734,8 @@ %% -define(SOCKET_OPT_IPV6_CHECKSUM, 5). % FreeBSD -define(SOCKET_OPT_IPV6_DROP_MEMBERSHIP, 6). -define(SOCKET_OPT_IPV6_DSTOPTS, 7). -%% -define(SOCKET_OPT_IPV6_ESP_TRANS_LEVEL, 8). % FreeBSD -%% -define(SOCKET_OPT_IPV6_ESP_NETWORK_LEVEL, 9). % FreeBSD +%% -define(SOCKET_OPT_IPV6_ESP_NETWORK_LEVEL, 8). % FreeBSD +%% -define(SOCKET_OPT_IPV6_ESP_TRANS_LEVEL, 9). % FreeBSD %% -define(SOCKET_OPT_IPV6_FAITH, 10). % FreeBSD -define(SOCKET_OPT_IPV6_FLOWINFO, 11). -define(SOCKET_OPT_IPV6_HOPLIMIT, 12). -- cgit v1.2.3 From 46c3090432a391f27b9af8f6b19ee27310257c76 Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Tue, 9 Oct 2018 11:35:38 +0200 Subject: [socket-nif] Completed (ahum) the socket option(s) part of supports "Completed" the socket options part of the supports function. OTP-14831 --- erts/emulator/nifs/common/socket_int.h | 43 ++++ erts/emulator/nifs/common/socket_nif.c | 394 ++++++++++++++++++++++++++++++++- 2 files changed, 427 insertions(+), 10 deletions(-) diff --git a/erts/emulator/nifs/common/socket_int.h b/erts/emulator/nifs/common/socket_int.h index 93722d2e98..06f677482c 100644 --- a/erts/emulator/nifs/common/socket_int.h +++ b/erts/emulator/nifs/common/socket_int.h @@ -106,25 +106,39 @@ typedef unsigned int BOOLEAN_T; extern ERL_NIF_TERM esock_atom_accept; extern ERL_NIF_TERM esock_atom_acceptconn; extern ERL_NIF_TERM esock_atom_acceptfilter; +extern ERL_NIF_TERM esock_atom_adaption_layer; extern ERL_NIF_TERM esock_atom_addr; extern ERL_NIF_TERM esock_atom_addrform; extern ERL_NIF_TERM esock_atom_add_membership; extern ERL_NIF_TERM esock_atom_add_source_membership; extern ERL_NIF_TERM esock_atom_any; +extern ERL_NIF_TERM esock_atom_associnfo; extern ERL_NIF_TERM esock_atom_authhdr; +extern ERL_NIF_TERM esock_atom_auth_active_key; +extern ERL_NIF_TERM esock_atom_auth_asconf; +extern ERL_NIF_TERM esock_atom_auth_chunk; +extern ERL_NIF_TERM esock_atom_auth_delete_key; +extern ERL_NIF_TERM esock_atom_auth_key; extern ERL_NIF_TERM esock_atom_auth_level; +extern ERL_NIF_TERM esock_atom_autoclose; extern ERL_NIF_TERM esock_atom_bindtodevice; extern ERL_NIF_TERM esock_atom_block_source; extern ERL_NIF_TERM esock_atom_broadcast; extern ERL_NIF_TERM esock_atom_busy_poll; extern ERL_NIF_TERM esock_atom_checksum; extern ERL_NIF_TERM esock_atom_connect; +extern ERL_NIF_TERM esock_atom_congestion; +extern ERL_NIF_TERM esock_atom_context; +extern ERL_NIF_TERM esock_atom_cork; extern ERL_NIF_TERM esock_atom_credentials; extern ERL_NIF_TERM esock_atom_ctrl; extern ERL_NIF_TERM esock_atom_ctrunc; extern ERL_NIF_TERM esock_atom_data; extern ERL_NIF_TERM esock_atom_debug; +extern ERL_NIF_TERM esock_atom_default_send_params; +extern ERL_NIF_TERM esock_atom_delayed_ack_time; extern ERL_NIF_TERM esock_atom_dgram; +extern ERL_NIF_TERM esock_atom_disable_fragments; extern ERL_NIF_TERM esock_atom_domain; extern ERL_NIF_TERM esock_atom_dontfrag; extern ERL_NIF_TERM esock_atom_dontroute; @@ -136,31 +150,46 @@ extern ERL_NIF_TERM esock_atom_error; extern ERL_NIF_TERM esock_atom_errqueue; extern ERL_NIF_TERM esock_atom_esp_network_level; extern ERL_NIF_TERM esock_atom_esp_trans_level; +extern ERL_NIF_TERM esock_atom_events; +extern ERL_NIF_TERM esock_atom_explicit_eor; extern ERL_NIF_TERM esock_atom_faith; extern ERL_NIF_TERM esock_atom_false; extern ERL_NIF_TERM esock_atom_family; extern ERL_NIF_TERM esock_atom_flags; extern ERL_NIF_TERM esock_atom_flowinfo; +extern ERL_NIF_TERM esock_atom_fragment_interleave; extern ERL_NIF_TERM esock_atom_freebind; +extern ERL_NIF_TERM esock_atom_get_peer_addr_info; extern ERL_NIF_TERM esock_atom_hdrincl; +extern ERL_NIF_TERM esock_atom_hmac_ident; extern ERL_NIF_TERM esock_atom_hoplimit; extern ERL_NIF_TERM esock_atom_hopopts; extern ERL_NIF_TERM esock_atom_ifindex; extern ERL_NIF_TERM esock_atom_inet; extern ERL_NIF_TERM esock_atom_inet6; +extern ERL_NIF_TERM esock_atom_info; +extern ERL_NIF_TERM esock_atom_initmsg; extern ERL_NIF_TERM esock_atom_iov; extern ERL_NIF_TERM esock_atom_ip; extern ERL_NIF_TERM esock_atom_ipcomp_level; extern ERL_NIF_TERM esock_atom_ipv6; +extern ERL_NIF_TERM esock_atom_i_want_mapped_v4_addr; extern ERL_NIF_TERM esock_atom_join_group; extern ERL_NIF_TERM esock_atom_keepalive; +extern ERL_NIF_TERM esock_atom_keepcnt; +extern ERL_NIF_TERM esock_atom_keepidle; +extern ERL_NIF_TERM esock_atom_keepintvl; extern ERL_NIF_TERM esock_atom_leave_group; extern ERL_NIF_TERM esock_atom_level; extern ERL_NIF_TERM esock_atom_linger; extern ERL_NIF_TERM esock_atom_local; +extern ERL_NIF_TERM esock_atom_local_auth_chunks; extern ERL_NIF_TERM esock_atom_loopback; extern ERL_NIF_TERM esock_atom_lowdelay; extern ERL_NIF_TERM esock_atom_mark; +extern ERL_NIF_TERM esock_atom_maxburst; +extern ERL_NIF_TERM esock_atom_maxseg; +extern ERL_NIF_TERM esock_atom_md5sig; extern ERL_NIF_TERM esock_atom_mincost; extern ERL_NIF_TERM esock_atom_minttl; extern ERL_NIF_TERM esock_atom_msfilter; @@ -171,7 +200,10 @@ extern ERL_NIF_TERM esock_atom_multicast_hops; extern ERL_NIF_TERM esock_atom_multicast_if; extern ERL_NIF_TERM esock_atom_multicast_loop; extern ERL_NIF_TERM esock_atom_multicast_ttl; +extern ERL_NIF_TERM esock_atom_nodelay; extern ERL_NIF_TERM esock_atom_nodefrag; +extern ERL_NIF_TERM esock_atom_noopt; +extern ERL_NIF_TERM esock_atom_nopush; extern ERL_NIF_TERM esock_atom_not_found; extern ERL_NIF_TERM esock_atom_not_owner; extern ERL_NIF_TERM esock_atom_ok; @@ -179,14 +211,18 @@ extern ERL_NIF_TERM esock_atom_oob; extern ERL_NIF_TERM esock_atom_oobinline; extern ERL_NIF_TERM esock_atom_options; extern ERL_NIF_TERM esock_atom_origdstaddr; +extern ERL_NIF_TERM esock_atom_partial_delivery_point; extern ERL_NIF_TERM esock_atom_passcred; extern ERL_NIF_TERM esock_atom_path; extern ERL_NIF_TERM esock_atom_peekcred; extern ERL_NIF_TERM esock_atom_peek_off; +extern ERL_NIF_TERM esock_atom_peer_addr_params; +extern ERL_NIF_TERM esock_atom_peer_auth_chunks; extern ERL_NIF_TERM esock_atom_pktinfo; extern ERL_NIF_TERM esock_atom_pktoptions; extern ERL_NIF_TERM esock_atom_port; extern ERL_NIF_TERM esock_atom_portrange; +extern ERL_NIF_TERM esock_atom_primary_addr; extern ERL_NIF_TERM esock_atom_priority; extern ERL_NIF_TERM esock_atom_protocol; extern ERL_NIF_TERM esock_atom_raw; @@ -208,12 +244,14 @@ extern ERL_NIF_TERM esock_atom_recvtclass; extern ERL_NIF_TERM esock_atom_recvtos; extern ERL_NIF_TERM esock_atom_recvttl; extern ERL_NIF_TERM esock_atom_reliability; +extern ERL_NIF_TERM esock_atom_reset_streams; extern ERL_NIF_TERM esock_atom_retopts; extern ERL_NIF_TERM esock_atom_reuseaddr; extern ERL_NIF_TERM esock_atom_reuseport; extern ERL_NIF_TERM esock_atom_rights; extern ERL_NIF_TERM esock_atom_router_alert; extern ERL_NIF_TERM esock_atom_rthdr; +extern ERL_NIF_TERM esock_atom_rtoinfo; extern ERL_NIF_TERM esock_atom_rxq_ovfl; extern ERL_NIF_TERM esock_atom_scope_id; extern ERL_NIF_TERM esock_atom_sctp; @@ -225,13 +263,16 @@ extern ERL_NIF_TERM esock_atom_sendsrcaddr; extern ERL_NIF_TERM esock_atom_sendto; extern ERL_NIF_TERM esock_atom_seqpacket; extern ERL_NIF_TERM esock_atom_setfib; +extern ERL_NIF_TERM esock_atom_set_peer_primary_addr; extern ERL_NIF_TERM esock_atom_sndbuf; extern ERL_NIF_TERM esock_atom_sndbufforce; extern ERL_NIF_TERM esock_atom_sndlowat; extern ERL_NIF_TERM esock_atom_sndtimeo; extern ERL_NIF_TERM esock_atom_socket; extern ERL_NIF_TERM esock_atom_spec_dst; +extern ERL_NIF_TERM esock_atom_status; extern ERL_NIF_TERM esock_atom_stream; +extern ERL_NIF_TERM esock_atom_syncnt; extern ERL_NIF_TERM esock_atom_tclass; extern ERL_NIF_TERM esock_atom_tcp; extern ERL_NIF_TERM esock_atom_throughput; @@ -248,6 +289,8 @@ extern ERL_NIF_TERM esock_atom_undefined; extern ERL_NIF_TERM esock_atom_unicast_hops; extern ERL_NIF_TERM esock_atom_unknown; extern ERL_NIF_TERM esock_atom_usec; +extern ERL_NIF_TERM esock_atom_user_timeout; +extern ERL_NIF_TERM esock_atom_use_ext_recvinfo; extern ERL_NIF_TERM esock_atom_use_min_mtu; extern ERL_NIF_TERM esock_atom_v6only; diff --git a/erts/emulator/nifs/common/socket_nif.c b/erts/emulator/nifs/common/socket_nif.c index 359cc7db62..a59b9ec772 100644 --- a/erts/emulator/nifs/common/socket_nif.c +++ b/erts/emulator/nifs/common/socket_nif.c @@ -2388,25 +2388,39 @@ static char str_exsend[] = "exsend"; // failed send ERL_NIF_TERM esock_atom_accept; ERL_NIF_TERM esock_atom_acceptconn; ERL_NIF_TERM esock_atom_acceptfilter; +ERL_NIF_TERM esock_atom_adaption_layer; ERL_NIF_TERM esock_atom_addr; ERL_NIF_TERM esock_atom_addrform; ERL_NIF_TERM esock_atom_add_membership; ERL_NIF_TERM esock_atom_add_source_membership; ERL_NIF_TERM esock_atom_any; +ERL_NIF_TERM esock_atom_associnfo; ERL_NIF_TERM esock_atom_authhdr; +ERL_NIF_TERM esock_atom_auth_active_key; +ERL_NIF_TERM esock_atom_auth_asconf; +ERL_NIF_TERM esock_atom_auth_chunk; +ERL_NIF_TERM esock_atom_auth_delete_key; +ERL_NIF_TERM esock_atom_auth_key; ERL_NIF_TERM esock_atom_auth_level; +ERL_NIF_TERM esock_atom_autoclose; ERL_NIF_TERM esock_atom_bindtodevice; ERL_NIF_TERM esock_atom_block_source; ERL_NIF_TERM esock_atom_broadcast; ERL_NIF_TERM esock_atom_busy_poll; ERL_NIF_TERM esock_atom_checksum; ERL_NIF_TERM esock_atom_connect; +ERL_NIF_TERM esock_atom_congestion; +ERL_NIF_TERM esock_atom_context; +ERL_NIF_TERM esock_atom_cork; ERL_NIF_TERM esock_atom_credentials; ERL_NIF_TERM esock_atom_ctrl; ERL_NIF_TERM esock_atom_ctrunc; ERL_NIF_TERM esock_atom_data; ERL_NIF_TERM esock_atom_debug; +ERL_NIF_TERM esock_atom_default_send_params; +ERL_NIF_TERM esock_atom_delayed_ack_time; ERL_NIF_TERM esock_atom_dgram; +ERL_NIF_TERM esock_atom_disable_fragments; ERL_NIF_TERM esock_atom_domain; ERL_NIF_TERM esock_atom_dontfrag; ERL_NIF_TERM esock_atom_dontroute; @@ -2418,31 +2432,46 @@ ERL_NIF_TERM esock_atom_error; ERL_NIF_TERM esock_atom_errqueue; ERL_NIF_TERM esock_atom_esp_network_level; ERL_NIF_TERM esock_atom_esp_trans_level; +ERL_NIF_TERM esock_atom_events; +ERL_NIF_TERM esock_atom_explicit_eor; ERL_NIF_TERM esock_atom_faith; ERL_NIF_TERM esock_atom_false; ERL_NIF_TERM esock_atom_family; ERL_NIF_TERM esock_atom_flags; ERL_NIF_TERM esock_atom_flowinfo; +ERL_NIF_TERM esock_atom_fragment_interleave; ERL_NIF_TERM esock_atom_freebind; +ERL_NIF_TERM esock_atom_get_peer_addr_info; ERL_NIF_TERM esock_atom_hdrincl; +ERL_NIF_TERM esock_atom_hmac_ident; ERL_NIF_TERM esock_atom_hoplimit; ERL_NIF_TERM esock_atom_hopopts; ERL_NIF_TERM esock_atom_ifindex; ERL_NIF_TERM esock_atom_inet; ERL_NIF_TERM esock_atom_inet6; +ERL_NIF_TERM esock_atom_info; +ERL_NIF_TERM esock_atom_initmsg; ERL_NIF_TERM esock_atom_iov; ERL_NIF_TERM esock_atom_ip; ERL_NIF_TERM esock_atom_ipcomp_level; ERL_NIF_TERM esock_atom_ipv6; +ERL_NIF_TERM esock_atom_i_want_mapped_v4_addr; ERL_NIF_TERM esock_atom_join_group; ERL_NIF_TERM esock_atom_keepalive; +ERL_NIF_TERM esock_atom_keepcnt; +ERL_NIF_TERM esock_atom_keepidle; +ERL_NIF_TERM esock_atom_keepintvl; ERL_NIF_TERM esock_atom_leave_group; ERL_NIF_TERM esock_atom_level; ERL_NIF_TERM esock_atom_linger; ERL_NIF_TERM esock_atom_local; +ERL_NIF_TERM esock_atom_local_auth_chunks; ERL_NIF_TERM esock_atom_loopback; ERL_NIF_TERM esock_atom_lowdelay; ERL_NIF_TERM esock_atom_mark; +ERL_NIF_TERM esock_atom_maxburst; +ERL_NIF_TERM esock_atom_maxseg; +ERL_NIF_TERM esock_atom_md5sig; ERL_NIF_TERM esock_atom_mincost; ERL_NIF_TERM esock_atom_minttl; ERL_NIF_TERM esock_atom_msfilter; @@ -2453,7 +2482,10 @@ ERL_NIF_TERM esock_atom_multicast_hops; ERL_NIF_TERM esock_atom_multicast_if; ERL_NIF_TERM esock_atom_multicast_loop; ERL_NIF_TERM esock_atom_multicast_ttl; +ERL_NIF_TERM esock_atom_nodelay; ERL_NIF_TERM esock_atom_nodefrag; +ERL_NIF_TERM esock_atom_noopt; +ERL_NIF_TERM esock_atom_nopush; ERL_NIF_TERM esock_atom_not_found; ERL_NIF_TERM esock_atom_not_owner; ERL_NIF_TERM esock_atom_ok; @@ -2461,14 +2493,18 @@ ERL_NIF_TERM esock_atom_oob; ERL_NIF_TERM esock_atom_oobinline; ERL_NIF_TERM esock_atom_options; ERL_NIF_TERM esock_atom_origdstaddr; +ERL_NIF_TERM esock_atom_partial_delivery_point; ERL_NIF_TERM esock_atom_passcred; ERL_NIF_TERM esock_atom_path; ERL_NIF_TERM esock_atom_peekcred; ERL_NIF_TERM esock_atom_peek_off; +ERL_NIF_TERM esock_atom_peer_addr_params; +ERL_NIF_TERM esock_atom_peer_auth_chunks; ERL_NIF_TERM esock_atom_pktinfo; ERL_NIF_TERM esock_atom_pktoptions; ERL_NIF_TERM esock_atom_port; ERL_NIF_TERM esock_atom_portrange; +ERL_NIF_TERM esock_atom_primary_addr; ERL_NIF_TERM esock_atom_priority; ERL_NIF_TERM esock_atom_protocol; ERL_NIF_TERM esock_atom_raw; @@ -2490,12 +2526,14 @@ ERL_NIF_TERM esock_atom_recvtclass; ERL_NIF_TERM esock_atom_recvtos; ERL_NIF_TERM esock_atom_recvttl; ERL_NIF_TERM esock_atom_reliability; +ERL_NIF_TERM esock_atom_reset_streams; ERL_NIF_TERM esock_atom_retopts; ERL_NIF_TERM esock_atom_reuseaddr; ERL_NIF_TERM esock_atom_reuseport; ERL_NIF_TERM esock_atom_rights; ERL_NIF_TERM esock_atom_router_alert; ERL_NIF_TERM esock_atom_rthdr; +ERL_NIF_TERM esock_atom_rtoinfo; ERL_NIF_TERM esock_atom_rxq_ovfl; ERL_NIF_TERM esock_atom_scope_id; ERL_NIF_TERM esock_atom_sctp; @@ -2507,13 +2545,16 @@ ERL_NIF_TERM esock_atom_sendsrcaddr; ERL_NIF_TERM esock_atom_sendto; ERL_NIF_TERM esock_atom_seqpacket; ERL_NIF_TERM esock_atom_setfib; +ERL_NIF_TERM esock_atom_set_peer_primary_addr; ERL_NIF_TERM esock_atom_socket; ERL_NIF_TERM esock_atom_sndbuf; ERL_NIF_TERM esock_atom_sndbufforce; ERL_NIF_TERM esock_atom_sndlowat; ERL_NIF_TERM esock_atom_sndtimeo; ERL_NIF_TERM esock_atom_spec_dst; +ERL_NIF_TERM esock_atom_status; ERL_NIF_TERM esock_atom_stream; +ERL_NIF_TERM esock_atom_syncnt; ERL_NIF_TERM esock_atom_tclass; ERL_NIF_TERM esock_atom_tcp; ERL_NIF_TERM esock_atom_throughput; @@ -2530,6 +2571,8 @@ ERL_NIF_TERM esock_atom_undefined; ERL_NIF_TERM esock_atom_unicast_hops; ERL_NIF_TERM esock_atom_unknown; ERL_NIF_TERM esock_atom_usec; +ERL_NIF_TERM esock_atom_user_timeout; +ERL_NIF_TERM esock_atom_use_ext_recvinfo; ERL_NIF_TERM esock_atom_use_min_mtu; ERL_NIF_TERM esock_atom_v6only; @@ -3624,11 +3667,94 @@ ERL_NIF_TERM nsupports_options_ipv6(ErlNifEnv* env) static ERL_NIF_TERM nsupports_options_tcp(ErlNifEnv* env) { - ERL_NIF_TERM result; + SocketTArray opts = TARRAY_CREATE(32); + ERL_NIF_TERM tmp, optsL; - result = MKEL(env); - return result; + /* *** SOCKET_OPT_TCP_CONGESTION => TCP_CONGESTION *** */ +#if defined(TCP_CONGESTION) + tmp = MKT2(env, esock_atom_congestion, esock_atom_true); +#else + tmp = MKT2(env, esock_atom_congestion, esock_atom_false); +#endif + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_TCP_CORK => TCP_CORK *** */ +#if defined(TCP_CORK) + tmp = MKT2(env, esock_atom_cork, esock_atom_true); +#else + tmp = MKT2(env, esock_atom_cork, esock_atom_false); +#endif + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_TCP_INFO => TCP_INFO *** */ + tmp = MKT2(env, esock_atom_info, esock_atom_false); + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_TCP_KEEPCNT => TCP_KEEPCNT *** */ + tmp = MKT2(env, esock_atom_keepcnt, esock_atom_false); + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_TCP_KEEPIDLE => TCP_KEEPIDLE *** */ + tmp = MKT2(env, esock_atom_keepidle, esock_atom_false); + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_TCP_KEEPINTVL => TCP_KEEPINTVL *** */ + tmp = MKT2(env, esock_atom_keepintvl, esock_atom_false); + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_TCP_MAXSEG => TCP_MAXSEG *** */ +#if defined(TCP_) + tmp = MKT2(env, esock_atom_maxseg, esock_atom_true); +#else + tmp = MKT2(env, esock_atom_maxseg, esock_atom_false); +#endif + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_TCP_MD5SIG => TCP_MD5SIG *** */ + tmp = MKT2(env, esock_atom_md5sig, esock_atom_false); + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_TCP_NODELAY => TCP_NODELAY *** */ +#if defined(TCP_) + tmp = MKT2(env, esock_atom_nodelay, esock_atom_true); +#else + tmp = MKT2(env, esock_atom_nodelay, esock_atom_false); +#endif + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_TCP_NOOPT => TCP_NOOPT *** */ + tmp = MKT2(env, esock_atom_noopt, esock_atom_false); + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_TCP_NOPUSH => TCP_NOPUSH *** */ + tmp = MKT2(env, esock_atom_nopush, esock_atom_false); + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_TCP_SYNCNT => TCP_SYNCNT *** */ + tmp = MKT2(env, esock_atom_syncnt, esock_atom_false); + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_TCP_USER_TIMEOUT => TCP_USER_TIMEOUT *** */ + tmp = MKT2(env, esock_atom_user_timeout, esock_atom_false); + TARRAY_ADD(opts, tmp); + + + TARRAY_TOLIST(opts, env, &optsL); + + return optsL; } @@ -3636,11 +3762,22 @@ ERL_NIF_TERM nsupports_options_tcp(ErlNifEnv* env) static ERL_NIF_TERM nsupports_options_udp(ErlNifEnv* env) { - ERL_NIF_TERM result; + SocketTArray opts = TARRAY_CREATE(8); + ERL_NIF_TERM tmp, optsL; - result = MKEL(env); - return result; + /* *** SOCKET_OPT_UDP_CORK => UDP_CORK *** */ +#if defined(UDP_CORK) + tmp = MKT2(env, esock_atom_cork, esock_atom_true); +#else + tmp = MKT2(env, esock_atom_cork, esock_atom_false); +#endif + TARRAY_ADD(opts, tmp); + + + TARRAY_TOLIST(opts, env, &optsL); + + return optsL; } @@ -3648,11 +3785,205 @@ ERL_NIF_TERM nsupports_options_udp(ErlNifEnv* env) static ERL_NIF_TERM nsupports_options_sctp(ErlNifEnv* env) { - ERL_NIF_TERM result; + SocketTArray opts = TARRAY_CREATE(64); + ERL_NIF_TERM tmp, optsL; - result = MKEL(env); - return result; + /* *** SOCKET_OPT_SCTP_ADAPTION_LAYER => SCTP_ADAPTION_LAYER *** */ + tmp = MKT2(env, esock_atom_adaption_layer, esock_atom_false); + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_SCTP_ASSOCINFO => SCTP_ASSOCINFO *** */ +#if defined(SCTP_ASSOCINFO) + tmp = MKT2(env, esock_atom_associnfo, esock_atom_true); +#else + tmp = MKT2(env, esock_atom_associnfo, esock_atom_false); +#endif + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_SCTP_AUTH_ACTIVE_KEY => SCTP_AUTH_ACTIVE_KEY *** */ + tmp = MKT2(env, esock_atom_auth_active_key, esock_atom_false); + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_SCTP_AUTH_ASCONF => SCTP_AUTH_ASCONF *** */ + tmp = MKT2(env, esock_atom_auth_asconf, esock_atom_false); + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_SCTP_AUTH_CHUNK => SCTP_AUTH_CHUNK *** */ + tmp = MKT2(env, esock_atom_auth_chunk, esock_atom_false); + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_SCTP_AUTH_DELETE_KEY => SCTP_AUTH_DELETE_KEY *** */ + tmp = MKT2(env, esock_atom_auth_delete_key, esock_atom_false); + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_SCTP_AUTH_KEY => SCTP_AUTH_KEY *** */ + tmp = MKT2(env, esock_atom_auth_key, esock_atom_false); + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_SCTP_AUTOCLOSE => SCTP_AUTOCLOSE *** */ +#if defined(SCTP_AUTOCLOSE) + tmp = MKT2(env, esock_atom_autoclose, esock_atom_true); +#else + tmp = MKT2(env, esock_atom_autoclose, esock_atom_false); +#endif + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_SCTP_CONTEXT => SCTP_CONTEXT *** */ + tmp = MKT2(env, esock_atom_context, esock_atom_false); + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_SCTP_DEFAULT_SEND_PARAMS => SCTP_DEFAULT_SEND_PARAMS *** */ + tmp = MKT2(env, esock_atom_default_send_params, esock_atom_false); + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_SCTP_DELAYED_ACK_TIME => SCTP_DELAYED_ACK_TIME *** */ + tmp = MKT2(env, esock_atom_delayed_ack_time, esock_atom_false); + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_SCTP_DISABLE_FRAGMENTS => SCTP_DISABLE_FRAGMENTS *** */ +#if defined(SCTP_DISABLE_FRAGMENTS) + tmp = MKT2(env, esock_atom_disable_fragments, esock_atom_true); +#else + tmp = MKT2(env, esock_atom_disable_fragments, esock_atom_false); +#endif + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_SCTP_HMAC_IDENT => SCTP_HMAC_IDENT *** */ + tmp = MKT2(env, esock_atom_hmac_ident, esock_atom_false); + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_SCTP_EVENTS => SCTP_EVENTS *** */ +#if defined(SCTP_EVENTS) + tmp = MKT2(env, esock_atom_events, esock_atom_true); +#else + tmp = MKT2(env, esock_atom_events, esock_atom_false); +#endif + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_SCTP_EXPLICIT_EOR => SCTP_EXPLICIT_EOR *** */ + tmp = MKT2(env, esock_atom_explicit_eor, esock_atom_false); + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_SCTP_FRAGMENT_INTERLEAVE => SCTP_FRAGMENT_INTERLEAVE *** */ + tmp = MKT2(env, esock_atom_fragment_interleave, esock_atom_false); + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_SCTP_GET_PEER_ADDR_INFO => SCTP_GET_PEER_ADDR_INFO *** */ + tmp = MKT2(env, esock_atom_get_peer_addr_info, esock_atom_false); + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_SCTP_INITMSG => SCTP_INITMSG *** */ +#if defined(SCTP_INITMSG) + tmp = MKT2(env, esock_atom_initmsg, esock_atom_true); +#else + tmp = MKT2(env, esock_atom_initmsg, esock_atom_false); +#endif + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_SCTP_I_WANT_MAPPED_V4_ADDR => SCTP_I_WANT_MAPPED_V4_ADDR *** */ + tmp = MKT2(env, esock_atom_i_want_mapped_v4_addr, esock_atom_false); + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_SCTP_LOCAL_AUTH_CHUNKS => SCTP_LOCAL_AUTH_CHUNKS *** */ + tmp = MKT2(env, esock_atom_local_auth_chunks, esock_atom_false); + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_SCTP_MAXSEG => SCTP_MAXSEG *** */ +#if defined(SCTP_MAXSEG) + tmp = MKT2(env, esock_atom_maxseg, esock_atom_true); +#else + tmp = MKT2(env, esock_atom_maxseg, esock_atom_false); +#endif + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_SCTP_MAXBURST => SCTP_MAXBURST *** */ + tmp = MKT2(env, esock_atom_maxburst, esock_atom_false); + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_SCTP_NODELAY => SCTP_NODELAY *** */ +#if defined(SCTP_NODELAY) + tmp = MKT2(env, esock_atom_nodelay, esock_atom_true); +#else + tmp = MKT2(env, esock_atom_nodelay, esock_atom_false); +#endif + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_SCTP_PARTIAL_DELIVERY_POINT => SCTP_PARTIAL_DELIVERY_POINT *** */ + tmp = MKT2(env, esock_atom_partial_delivery_point, esock_atom_false); + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_SCTP_PEER_ADDR_PARAMS => SCTP_PEER_ADDR_PARAMS *** */ + tmp = MKT2(env, esock_atom_peer_addr_params, esock_atom_false); + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_SCTP_PEER_AUTH_CHUNKS => SCTP_PEER_AUTH_CHUNKS *** */ + tmp = MKT2(env, esock_atom_peer_auth_chunks, esock_atom_false); + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_SCTP_PRIMARY_ADDR => SCTP_PRIMARY_ADDR *** */ + tmp = MKT2(env, esock_atom_primary_addr, esock_atom_false); + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_SCTP_RESET_STREAMS => SCTP_RESET_STREAMS *** */ + tmp = MKT2(env, esock_atom_reset_streams, esock_atom_false); + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_SCTP_RTOINFO => SCTP_RTOINFO *** */ +#if defined(SCTP_RTOINFO) + tmp = MKT2(env, esock_atom_rtoinfo, esock_atom_true); +#else + tmp = MKT2(env, esock_atom_rtoinfo, esock_atom_false); +#endif + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_SCTP_SET_PEER_PRIMARY_ADDR => SCTP_SET_PEER_PRIMARY_ADDR *** */ + tmp = MKT2(env, esock_atom_set_peer_primary_addr, esock_atom_false); + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_SCTP_STATUS => SCTP_STATUS *** */ + tmp = MKT2(env, esock_atom_status, esock_atom_false); + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_SCTP_USE_EXT_RECVINFO => SCTP_USE_EXT_RECVINFO *** */ + tmp = MKT2(env, esock_atom_use_ext_recvinfo, esock_atom_false); + TARRAY_ADD(opts, tmp); + + + TARRAY_TOLIST(opts, env, &optsL); + + return optsL; } @@ -16607,25 +16938,39 @@ int on_load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info) esock_atom_accept = MKA(env, "accept"); esock_atom_acceptconn = MKA(env, "acceptconn"); esock_atom_acceptfilter = MKA(env, "acceptfilter"); + esock_atom_adaption_layer = MKA(env, "adaption_layer"); esock_atom_addr = MKA(env, "addr"); esock_atom_addrform = MKA(env, "addrform"); esock_atom_add_membership = MKA(env, "add_membership"); esock_atom_add_source_membership = MKA(env, "add_source_membership"); esock_atom_any = MKA(env, "any"); + esock_atom_associnfo = MKA(env, "associnfo"); esock_atom_authhdr = MKA(env, "authhdr"); + esock_atom_auth_active_key = MKA(env, "auth_active_key"); + esock_atom_auth_asconf = MKA(env, "auth_asconf"); + esock_atom_auth_chunk = MKA(env, "auth_chunk"); + esock_atom_auth_delete_key = MKA(env, "auth_delete_key"); + esock_atom_auth_key = MKA(env, "auth_key"); esock_atom_auth_level = MKA(env, "auth_level"); + esock_atom_autoclose = MKA(env, "autoclose"); esock_atom_bindtodevice = MKA(env, "bindtodevice"); esock_atom_block_source = MKA(env, "block_source"); esock_atom_broadcast = MKA(env, "broadcast"); esock_atom_busy_poll = MKA(env, "busy_poll"); esock_atom_checksum = MKA(env, "checksum"); esock_atom_connect = MKA(env, "connect"); + esock_atom_congestion = MKA(env, "congestion"); + esock_atom_context = MKA(env, "context"); + esock_atom_cork = MKA(env, "cork"); esock_atom_credentials = MKA(env, "credentials"); esock_atom_ctrl = MKA(env, "ctrl"); esock_atom_ctrunc = MKA(env, "ctrunc"); esock_atom_data = MKA(env, "data"); esock_atom_debug = MKA(env, "debug"); + esock_atom_default_send_params = MKA(env, "default_send_params"); + esock_atom_delayed_ack_time = MKA(env, "delayed_ack_time"); esock_atom_dgram = MKA(env, "dgram"); + esock_atom_disable_fragments = MKA(env, "disable_fragments"); esock_atom_domain = MKA(env, "domain"); esock_atom_dontfrag = MKA(env, "dontfrag"); esock_atom_dontroute = MKA(env, "dontroute"); @@ -16637,31 +16982,46 @@ int on_load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info) esock_atom_errqueue = MKA(env, "errqueue"); esock_atom_esp_network_level = MKA(env, "esp_network_level"); esock_atom_esp_trans_level = MKA(env, "esp_trans_level"); + esock_atom_events = MKA(env, "events"); + esock_atom_explicit_eor = MKA(env, "explicit_eor"); esock_atom_faith = MKA(env, "faith"); esock_atom_false = MKA(env, "false"); esock_atom_family = MKA(env, "family"); esock_atom_flags = MKA(env, "flags"); esock_atom_flowinfo = MKA(env, "flowinfo"); + esock_atom_fragment_interleave = MKA(env, "fragment_interleave"); esock_atom_freebind = MKA(env, "freebind"); + esock_atom_get_peer_addr_info = MKA(env, "get_peer_addr_info"); esock_atom_hdrincl = MKA(env, "hdrincl"); + esock_atom_hmac_ident = MKA(env, "hmac_ident"); esock_atom_hoplimit = MKA(env, "hoplimit"); esock_atom_hopopts = MKA(env, "hopopts"); esock_atom_ifindex = MKA(env, "ifindex"); esock_atom_inet = MKA(env, "inet"); esock_atom_inet6 = MKA(env, "inet6"); + esock_atom_info = MKA(env, "info"); + esock_atom_initmsg = MKA(env, "initmsg"); esock_atom_iov = MKA(env, "iov"); esock_atom_ip = MKA(env, "ip"); esock_atom_ipcomp_level = MKA(env, "ipcomp_level"); esock_atom_ipv6 = MKA(env, "ipv6"); + esock_atom_i_want_mapped_v4_addr = MKA(env, "i_want_mapped_v4_addr"); esock_atom_join_group = MKA(env, "join_group"); esock_atom_keepalive = MKA(env, "keepalive"); + esock_atom_keepcnt = MKA(env, "keepcnt"); + esock_atom_keepidle = MKA(env, "keepidle"); + esock_atom_keepintvl = MKA(env, "keepintvl"); esock_atom_leave_group = MKA(env, "leave_group"); esock_atom_level = MKA(env, "level"); esock_atom_linger = MKA(env, "linger"); esock_atom_local = MKA(env, "local"); + esock_atom_local_auth_chunks = MKA(env, "local_auth_chunks"); esock_atom_loopback = MKA(env, "loopback"); esock_atom_lowdelay = MKA(env, "lowdelay"); esock_atom_mark = MKA(env, "mark"); + esock_atom_maxburst = MKA(env, "maxburst"); + esock_atom_maxseg = MKA(env, "maxseg"); + esock_atom_md5sig = MKA(env, "md5sig"); esock_atom_mincost = MKA(env, "mincost"); esock_atom_minttl = MKA(env, "minttl"); esock_atom_msfilter = MKA(env, "msfilter"); @@ -16673,6 +17033,9 @@ int on_load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info) esock_atom_multicast_loop = MKA(env, "multicast_loop"); esock_atom_multicast_ttl = MKA(env, "multicast_ttl"); esock_atom_nodefrag = MKA(env, "nodefrag"); + esock_atom_nodelay = MKA(env, "nodelay"); + esock_atom_noopt = MKA(env, "noopt"); + esock_atom_nopush = MKA(env, "nopush"); esock_atom_not_found = MKA(env, "not_found"); esock_atom_not_owner = MKA(env, "not_owner"); esock_atom_ok = MKA(env, "ok"); @@ -16680,14 +17043,18 @@ int on_load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info) esock_atom_oobinline = MKA(env, "oobinline"); esock_atom_options = MKA(env, "options"); esock_atom_origdstaddr = MKA(env, "origdstaddr"); + esock_atom_partial_delivery_point = MKA(env, "partial_delivery_point"); esock_atom_passcred = MKA(env, "passcred"); esock_atom_path = MKA(env, "path"); esock_atom_peekcred = MKA(env, "peekcred"); esock_atom_peek_off = MKA(env, "peek_off"); + esock_atom_peer_addr_params = MKA(env, "peer_addr_params"); + esock_atom_peer_auth_chunks = MKA(env, "peer_auth_chunks"); esock_atom_pktinfo = MKA(env, "pktinfo"); esock_atom_pktoptions = MKA(env, "pktoptions"); esock_atom_port = MKA(env, "port"); esock_atom_portrange = MKA(env, "portrange"); + esock_atom_primary_addr = MKA(env, "primary_addr"); esock_atom_priority = MKA(env, "priority"); esock_atom_protocol = MKA(env, "protocol"); esock_atom_raw = MKA(env, "raw"); @@ -16709,12 +17076,14 @@ int on_load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info) esock_atom_recvtos = MKA(env, "recvtos"); esock_atom_recvttl = MKA(env, "recvttl"); esock_atom_reliability = MKA(env, "reliability"); - esock_atom_retopts = MKA(env, "retopts"); + esock_atom_reset_streams = MKA(env, "reset_streams"); + esock_atom_retopts = MKA(env, "retopts"); esock_atom_reuseaddr = MKA(env, "reuseaddr"); esock_atom_reuseport = MKA(env, "reuseport"); esock_atom_rights = MKA(env, "rights"); esock_atom_router_alert = MKA(env, "router_alert"); esock_atom_rthdr = MKA(env, "rthdr"); + esock_atom_rtoinfo = MKA(env, "rtoinfo"); esock_atom_rxq_ovfl = MKA(env, "rxq_ovfl"); esock_atom_scope_id = MKA(env, "scope_id"); esock_atom_sctp = MKA(env, "sctp"); @@ -16726,13 +17095,16 @@ int on_load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info) esock_atom_sendto = MKA(env, "sendto"); esock_atom_seqpacket = MKA(env, "seqpacket"); esock_atom_setfib = MKA(env, "setfib"); + esock_atom_set_peer_primary_addr = MKA(env, "set_peer_primary_addr"); esock_atom_sndbuf = MKA(env, "sndbuf"); esock_atom_sndbufforce = MKA(env, "sndbufforce"); esock_atom_sndlowat = MKA(env, "sndlowat"); esock_atom_sndtimeo = MKA(env, "sndtimeo"); esock_atom_socket = MKA(env, "socket"); esock_atom_spec_dst = MKA(env, "spec_dst"); + esock_atom_status = MKA(env, "status"); esock_atom_stream = MKA(env, "stream"); + esock_atom_syncnt = MKA(env, "syncnt"); esock_atom_tclass = MKA(env, "tclass"); esock_atom_tcp = MKA(env, "tcp"); esock_atom_throughput = MKA(env, "throughput"); @@ -16749,6 +17121,8 @@ int on_load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info) esock_atom_unicast_hops = MKA(env, "unicast_hops"); esock_atom_unknown = MKA(env, "unknown"); esock_atom_usec = MKA(env, "usec"); + esock_atom_user_timeout = MKA(env, "user_timeout"); + esock_atom_use_ext_recvinfo = MKA(env, "use_ext_recvinfo"); esock_atom_use_min_mtu = MKA(env, "use_min_mtu"); esock_atom_v6only = MKA(env, "v6only"); -- cgit v1.2.3 From 789d5cefd0f710fd83dec27ad2239ea299737a49 Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Tue, 9 Oct 2018 12:23:59 +0200 Subject: [socket-nif] The supports function now also handles sctp and ipv6 The supports function now also handles testing for SCTP and IPv6 support. Basic test at the moment, but better then nothing. OTP-14831 --- erts/emulator/nifs/common/socket_nif.c | 475 +++++++++++++++++++-------------- erts/preloaded/ebin/socket.beam | Bin 66960 -> 67144 bytes erts/preloaded/src/socket.erl | 10 +- 3 files changed, 287 insertions(+), 198 deletions(-) diff --git a/erts/emulator/nifs/common/socket_nif.c b/erts/emulator/nifs/common/socket_nif.c index a59b9ec772..4544604f28 100644 --- a/erts/emulator/nifs/common/socket_nif.c +++ b/erts/emulator/nifs/common/socket_nif.c @@ -314,6 +314,10 @@ static void (*esock_sctp_freepaddrs)(struct sockaddr *addrs) = NULL; #include "socket_int.h" #include "socket_util.h" +#if defined(SOL_IPV6) || defined(IPPROTO_IPV6) +#define HAVE_IPV6 +#endif + /* All platforms fail on malloc errors. */ #define FATAL_MALLOC @@ -604,6 +608,8 @@ typedef union { #define SOCKET_SUPPORTS_OPTIONS 0x0001 +#define SOCKET_SUPPORTS_SCTP 0x0002 +#define SOCKET_SUPPORTS_IPV6 0x0003 @@ -942,6 +948,8 @@ static ERL_NIF_TERM nsupports_options_ipv6(ErlNifEnv* env); static ERL_NIF_TERM nsupports_options_tcp(ErlNifEnv* env); static ERL_NIF_TERM nsupports_options_udp(ErlNifEnv* env); static ERL_NIF_TERM nsupports_options_sctp(ErlNifEnv* env); +static ERL_NIF_TERM nsupports_sctp(ErlNifEnv* env); +static ERL_NIF_TERM nsupports_ipv6(ErlNifEnv* env); static ERL_NIF_TERM nopen(ErlNifEnv* env, int domain, @@ -1322,7 +1330,7 @@ ERL_NIF_TERM nsetopt_lvl_ip_update_source(ErlNifEnv* env, /* *** Handling set of socket options for level = ipv6 *** */ -#if defined(SOL_IPV6) +#if defined(HAVE_IPV6) static ERL_NIF_TERM nsetopt_lvl_ipv6(ErlNifEnv* env, SocketDescriptor* descP, int eOpt, @@ -1430,7 +1438,7 @@ static ERL_NIF_TERM nsetopt_lvl_ipv6_update_membership(ErlNifEnv* env, int opt); #endif -#endif // defined(SOL_IPV6) +#endif // defined(HAVE_IPV6) static ERL_NIF_TERM nsetopt_lvl_tcp(ErlNifEnv* env, SocketDescriptor* descP, int eOpt, @@ -1736,7 +1744,7 @@ static ERL_NIF_TERM ngetopt_lvl_ip_transparent(ErlNifEnv* env, static ERL_NIF_TERM ngetopt_lvl_ip_ttl(ErlNifEnv* env, SocketDescriptor* descP); #endif -#if defined(SOL_IPV6) +#if defined(HAVE_IPV6) static ERL_NIF_TERM ngetopt_lvl_ipv6(ErlNifEnv* env, SocketDescriptor* descP, int eOpt); @@ -1805,7 +1813,7 @@ static ERL_NIF_TERM ngetopt_lvl_ipv6_v6only(ErlNifEnv* env, SocketDescriptor* descP); #endif -#endif // defined(SOL_IPV6) +#endif // defined(HAVE_IPV6) static ERL_NIF_TERM ngetopt_lvl_tcp(ErlNifEnv* env, SocketDescriptor* descP, @@ -2060,7 +2068,7 @@ static char* encode_cmsghdr_data_ip(ErlNifEnv* env, size_t dataPos, size_t dataLen, ERL_NIF_TERM* eCMsgHdrData); -#if defined(SOL_IPV6) +#if defined(HAVE_IPV6) static char* encode_cmsghdr_data_ipv6(ErlNifEnv* env, ERL_NIF_TERM ctrlBuf, int type, @@ -2818,6 +2826,14 @@ ERL_NIF_TERM nsupports(ErlNifEnv* env, int key) result = nsupports_options(env); break; + case SOCKET_SUPPORTS_SCTP: + result = nsupports_sctp(env); + break; + + case SOCKET_SUPPORTS_IPV6: + result = nsupports_ipv6(env); + break; + default: result = esock_atom_false; break; @@ -3988,6 +4004,39 @@ ERL_NIF_TERM nsupports_options_sctp(ErlNifEnv* env) +static +ERL_NIF_TERM nsupports_sctp(ErlNifEnv* env) +{ + ERL_NIF_TERM supports; + +#if defined(HAVE_SCTP) + supports = esock_atom_true; +#else + supports = esock_atom_false; +#endif + + return supports; +} + + + +static +ERL_NIF_TERM nsupports_ipv6(ErlNifEnv* env) +{ + ERL_NIF_TERM supports; + + /* Is this (test) really sufficient for testing if we support IPv6? */ +#if defined(HAVE_IPV6) + supports = esock_atom_true; +#else + supports = esock_atom_false; +#endif + + return supports; +} + + + /* ---------------------------------------------------------------------- * nif_open * @@ -6654,8 +6703,12 @@ ERL_NIF_TERM nsetopt_level(ErlNifEnv* env, result = nsetopt_lvl_ip(env, descP, eOpt, eVal); break; +#if defined(HAVE_IPV6) #if defined(SOL_IPV6) case SOL_IPV6: +#else + case IPPROTO_IPV6: +#endif result = nsetopt_lvl_ipv6(env, descP, eOpt, eVal); break; #endif @@ -8141,7 +8194,7 @@ ERL_NIF_TERM nsetopt_lvl_ip_update_source(ErlNifEnv* env, /* nsetopt_lvl_ipv6 - Level *IPv6* option(s) */ -#if defined(SOL_IPV6) +#if defined(HAVE_IPV6) static ERL_NIF_TERM nsetopt_lvl_ipv6(ErlNifEnv* env, SocketDescriptor* descP, @@ -8612,7 +8665,7 @@ ERL_NIF_TERM nsetopt_lvl_ipv6_update_membership(ErlNifEnv* env, -#endif // defined(SOL_IPV6) +#endif // defined(HAVE_IPV6) @@ -9414,10 +9467,14 @@ BOOLEAN_T elevel2level(BOOLEAN_T isEncoded, result = TRUE; break; -#if defined(SOL_IPV6) +#if defined(HAVE_IPV6) case SOCKET_OPT_LEVEL_IPV6: *isOTP = FALSE; +#if defined(SOL_IPV6) *level = SOL_IPV6; +#else + *level = IPPROTO_IPV6; +#endif result = TRUE; break; #endif @@ -10063,8 +10120,12 @@ ERL_NIF_TERM ngetopt_level(ErlNifEnv* env, result = ngetopt_lvl_ip(env, descP, eOpt); break; +#if defined(HAVE_IPV6) #if defined(SOL_IPV6) case SOL_IPV6: +#else + case IPPROTO_IPV6: +#endif result = ngetopt_lvl_ipv6(env, descP, eOpt); break; #endif @@ -11267,7 +11328,7 @@ ERL_NIF_TERM ngetopt_lvl_ip_ttl(ErlNifEnv* env, /* ngetopt_lvl_ipv6 - Level *IPv6* option(s) */ -#if defined(SOL_IPV6) +#if defined(HAVE_IPV6) static ERL_NIF_TERM ngetopt_lvl_ipv6(ErlNifEnv* env, SocketDescriptor* descP, @@ -11576,7 +11637,7 @@ ERL_NIF_TERM ngetopt_lvl_ipv6_v6only(ErlNifEnv* env, #endif -#endif // defined(SOL_IPV6) +#endif // defined(HAVE_IPV6) @@ -14180,8 +14241,12 @@ char* encode_cmsghdr_level(ErlNifEnv* env, xres = NULL; break; +#if defined(HAVE_IPV6) #if defined(SOL_IPV6) case SOL_IPV6: +#else + case IPPROTO_IPV6: +#endif *eLevel = esock_atom_ip; xres = NULL; break; @@ -14228,9 +14293,13 @@ char* decode_cmsghdr_level(ErlNifEnv* env, *level = IPPROTO_IP; #endif xres = NULL; -#if defined(SOL_IPV6) +#if defined(HAVE_IPV6) } else if (COMPARE(eLevel, esock_atom_ipv6) == 0) { +#if defined(SOL_IPV6) *level = SOL_IPV6; +#else + *level = IPPROTO_IPV6; +#endif xres = NULL; #endif } else if (COMPARE(eLevel, esock_atom_udp) == 0) { @@ -14330,8 +14399,12 @@ char* encode_cmsghdr_type(ErlNifEnv* env, } break; +#if defined(HAVE_IPV6) #if defined(SOL_IPV6) - case SOL_IPV6: + case SOL_IPV6: +#else + case IPPROTO_IPV6: +#endif switch (type) { #if defined(IPV6_PKTINFO) case IPV6_PKTINFO: @@ -14442,8 +14515,12 @@ char* decode_cmsghdr_type(ErlNifEnv* env, } break; +#if defined(HAVE_IPV6) #if defined(SOL_IPV6) case SOL_IPV6: +#else + case IPPROTO_IPV6: +#endif if (IS_NUM(env, eType)) { if (!GET_INT(env, eType, type)) { *type = -1; @@ -14516,8 +14593,12 @@ char* encode_cmsghdr_data(ErlNifEnv* env, eCMsgHdrData); break; +#if defined(HAVE_IPV6) #if defined(SOL_IPV6) case SOL_IPV6: +#else + case IPPROTO_IPV6: +#endif xres = encode_cmsghdr_data_ipv6(env, ctrlBuf, type, dataP, dataPos, dataLen, eCMsgHdrData); @@ -14714,7 +14795,7 @@ char* encode_cmsghdr_data_ip(ErlNifEnv* env, * Encode the data part when protocol = IPv6 of the cmsghdr(). * */ -#if defined(SOL_IPV6) +#if defined(HAVE_IPV6) static char* encode_cmsghdr_data_ipv6(ErlNifEnv* env, ERL_NIF_TERM ctrlBuf, @@ -16935,196 +17016,196 @@ int on_load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info) atom_want = MKA(env, str_want); /* Global atom(s) */ - esock_atom_accept = MKA(env, "accept"); - esock_atom_acceptconn = MKA(env, "acceptconn"); - esock_atom_acceptfilter = MKA(env, "acceptfilter"); - esock_atom_adaption_layer = MKA(env, "adaption_layer"); - esock_atom_addr = MKA(env, "addr"); - esock_atom_addrform = MKA(env, "addrform"); - esock_atom_add_membership = MKA(env, "add_membership"); - esock_atom_add_source_membership = MKA(env, "add_source_membership"); - esock_atom_any = MKA(env, "any"); - esock_atom_associnfo = MKA(env, "associnfo"); - esock_atom_authhdr = MKA(env, "authhdr"); - esock_atom_auth_active_key = MKA(env, "auth_active_key"); - esock_atom_auth_asconf = MKA(env, "auth_asconf"); - esock_atom_auth_chunk = MKA(env, "auth_chunk"); - esock_atom_auth_delete_key = MKA(env, "auth_delete_key"); - esock_atom_auth_key = MKA(env, "auth_key"); - esock_atom_auth_level = MKA(env, "auth_level"); - esock_atom_autoclose = MKA(env, "autoclose"); - esock_atom_bindtodevice = MKA(env, "bindtodevice"); - esock_atom_block_source = MKA(env, "block_source"); - esock_atom_broadcast = MKA(env, "broadcast"); - esock_atom_busy_poll = MKA(env, "busy_poll"); - esock_atom_checksum = MKA(env, "checksum"); - esock_atom_connect = MKA(env, "connect"); - esock_atom_congestion = MKA(env, "congestion"); - esock_atom_context = MKA(env, "context"); - esock_atom_cork = MKA(env, "cork"); - esock_atom_credentials = MKA(env, "credentials"); - esock_atom_ctrl = MKA(env, "ctrl"); - esock_atom_ctrunc = MKA(env, "ctrunc"); - esock_atom_data = MKA(env, "data"); - esock_atom_debug = MKA(env, "debug"); + esock_atom_accept = MKA(env, "accept"); + esock_atom_acceptconn = MKA(env, "acceptconn"); + esock_atom_acceptfilter = MKA(env, "acceptfilter"); + esock_atom_adaption_layer = MKA(env, "adaption_layer"); + esock_atom_addr = MKA(env, "addr"); + esock_atom_addrform = MKA(env, "addrform"); + esock_atom_add_membership = MKA(env, "add_membership"); + esock_atom_add_source_membership = MKA(env, "add_source_membership"); + esock_atom_any = MKA(env, "any"); + esock_atom_associnfo = MKA(env, "associnfo"); + esock_atom_authhdr = MKA(env, "authhdr"); + esock_atom_auth_active_key = MKA(env, "auth_active_key"); + esock_atom_auth_asconf = MKA(env, "auth_asconf"); + esock_atom_auth_chunk = MKA(env, "auth_chunk"); + esock_atom_auth_delete_key = MKA(env, "auth_delete_key"); + esock_atom_auth_key = MKA(env, "auth_key"); + esock_atom_auth_level = MKA(env, "auth_level"); + esock_atom_autoclose = MKA(env, "autoclose"); + esock_atom_bindtodevice = MKA(env, "bindtodevice"); + esock_atom_block_source = MKA(env, "block_source"); + esock_atom_broadcast = MKA(env, "broadcast"); + esock_atom_busy_poll = MKA(env, "busy_poll"); + esock_atom_checksum = MKA(env, "checksum"); + esock_atom_connect = MKA(env, "connect"); + esock_atom_congestion = MKA(env, "congestion"); + esock_atom_context = MKA(env, "context"); + esock_atom_cork = MKA(env, "cork"); + esock_atom_credentials = MKA(env, "credentials"); + esock_atom_ctrl = MKA(env, "ctrl"); + esock_atom_ctrunc = MKA(env, "ctrunc"); + esock_atom_data = MKA(env, "data"); + esock_atom_debug = MKA(env, "debug"); esock_atom_default_send_params = MKA(env, "default_send_params"); esock_atom_delayed_ack_time = MKA(env, "delayed_ack_time"); - esock_atom_dgram = MKA(env, "dgram"); + esock_atom_dgram = MKA(env, "dgram"); esock_atom_disable_fragments = MKA(env, "disable_fragments"); - esock_atom_domain = MKA(env, "domain"); - esock_atom_dontfrag = MKA(env, "dontfrag"); - esock_atom_dontroute = MKA(env, "dontroute"); + esock_atom_domain = MKA(env, "domain"); + esock_atom_dontfrag = MKA(env, "dontfrag"); + esock_atom_dontroute = MKA(env, "dontroute"); esock_atom_drop_membership = MKA(env, "drop_membership"); esock_atom_drop_source_membership = MKA(env, "drop_source_membership"); - esock_atom_dstopts = MKA(env, "dstpopts"); - esock_atom_eor = MKA(env, "eor"); - esock_atom_error = MKA(env, "error"); - esock_atom_errqueue = MKA(env, "errqueue"); - esock_atom_esp_network_level = MKA(env, "esp_network_level"); - esock_atom_esp_trans_level = MKA(env, "esp_trans_level"); - esock_atom_events = MKA(env, "events"); - esock_atom_explicit_eor = MKA(env, "explicit_eor"); - esock_atom_faith = MKA(env, "faith"); - esock_atom_false = MKA(env, "false"); - esock_atom_family = MKA(env, "family"); - esock_atom_flags = MKA(env, "flags"); - esock_atom_flowinfo = MKA(env, "flowinfo"); - esock_atom_fragment_interleave = MKA(env, "fragment_interleave"); - esock_atom_freebind = MKA(env, "freebind"); - esock_atom_get_peer_addr_info = MKA(env, "get_peer_addr_info"); - esock_atom_hdrincl = MKA(env, "hdrincl"); - esock_atom_hmac_ident = MKA(env, "hmac_ident"); - esock_atom_hoplimit = MKA(env, "hoplimit"); - esock_atom_hopopts = MKA(env, "hopopts"); - esock_atom_ifindex = MKA(env, "ifindex"); - esock_atom_inet = MKA(env, "inet"); - esock_atom_inet6 = MKA(env, "inet6"); - esock_atom_info = MKA(env, "info"); - esock_atom_initmsg = MKA(env, "initmsg"); - esock_atom_iov = MKA(env, "iov"); - esock_atom_ip = MKA(env, "ip"); - esock_atom_ipcomp_level = MKA(env, "ipcomp_level"); - esock_atom_ipv6 = MKA(env, "ipv6"); - esock_atom_i_want_mapped_v4_addr = MKA(env, "i_want_mapped_v4_addr"); - esock_atom_join_group = MKA(env, "join_group"); - esock_atom_keepalive = MKA(env, "keepalive"); - esock_atom_keepcnt = MKA(env, "keepcnt"); - esock_atom_keepidle = MKA(env, "keepidle"); - esock_atom_keepintvl = MKA(env, "keepintvl"); - esock_atom_leave_group = MKA(env, "leave_group"); - esock_atom_level = MKA(env, "level"); - esock_atom_linger = MKA(env, "linger"); - esock_atom_local = MKA(env, "local"); - esock_atom_local_auth_chunks = MKA(env, "local_auth_chunks"); - esock_atom_loopback = MKA(env, "loopback"); - esock_atom_lowdelay = MKA(env, "lowdelay"); - esock_atom_mark = MKA(env, "mark"); - esock_atom_maxburst = MKA(env, "maxburst"); - esock_atom_maxseg = MKA(env, "maxseg"); - esock_atom_md5sig = MKA(env, "md5sig"); - esock_atom_mincost = MKA(env, "mincost"); - esock_atom_minttl = MKA(env, "minttl"); - esock_atom_msfilter = MKA(env, "msfilter"); - esock_atom_mtu = MKA(env, "mtu"); - esock_atom_mtu_discover = MKA(env, "mtu_discover"); - esock_atom_multicast_all = MKA(env, "multicast_all"); - esock_atom_multicast_hops = MKA(env, "multicast_hops"); - esock_atom_multicast_if = MKA(env, "multicast_if"); - esock_atom_multicast_loop = MKA(env, "multicast_loop"); - esock_atom_multicast_ttl = MKA(env, "multicast_ttl"); - esock_atom_nodefrag = MKA(env, "nodefrag"); - esock_atom_nodelay = MKA(env, "nodelay"); - esock_atom_noopt = MKA(env, "noopt"); - esock_atom_nopush = MKA(env, "nopush"); - esock_atom_not_found = MKA(env, "not_found"); - esock_atom_not_owner = MKA(env, "not_owner"); - esock_atom_ok = MKA(env, "ok"); - esock_atom_oob = MKA(env, "oob"); - esock_atom_oobinline = MKA(env, "oobinline"); - esock_atom_options = MKA(env, "options"); - esock_atom_origdstaddr = MKA(env, "origdstaddr"); + esock_atom_dstopts = MKA(env, "dstpopts"); + esock_atom_eor = MKA(env, "eor"); + esock_atom_error = MKA(env, "error"); + esock_atom_errqueue = MKA(env, "errqueue"); + esock_atom_esp_network_level = MKA(env, "esp_network_level"); + esock_atom_esp_trans_level = MKA(env, "esp_trans_level"); + esock_atom_events = MKA(env, "events"); + esock_atom_explicit_eor = MKA(env, "explicit_eor"); + esock_atom_faith = MKA(env, "faith"); + esock_atom_false = MKA(env, "false"); + esock_atom_family = MKA(env, "family"); + esock_atom_flags = MKA(env, "flags"); + esock_atom_flowinfo = MKA(env, "flowinfo"); + esock_atom_fragment_interleave = MKA(env, "fragment_interleave"); + esock_atom_freebind = MKA(env, "freebind"); + esock_atom_get_peer_addr_info = MKA(env, "get_peer_addr_info"); + esock_atom_hdrincl = MKA(env, "hdrincl"); + esock_atom_hmac_ident = MKA(env, "hmac_ident"); + esock_atom_hoplimit = MKA(env, "hoplimit"); + esock_atom_hopopts = MKA(env, "hopopts"); + esock_atom_ifindex = MKA(env, "ifindex"); + esock_atom_inet = MKA(env, "inet"); + esock_atom_inet6 = MKA(env, "inet6"); + esock_atom_info = MKA(env, "info"); + esock_atom_initmsg = MKA(env, "initmsg"); + esock_atom_iov = MKA(env, "iov"); + esock_atom_ip = MKA(env, "ip"); + esock_atom_ipcomp_level = MKA(env, "ipcomp_level"); + esock_atom_ipv6 = MKA(env, "ipv6"); + esock_atom_i_want_mapped_v4_addr = MKA(env, "i_want_mapped_v4_addr"); + esock_atom_join_group = MKA(env, "join_group"); + esock_atom_keepalive = MKA(env, "keepalive"); + esock_atom_keepcnt = MKA(env, "keepcnt"); + esock_atom_keepidle = MKA(env, "keepidle"); + esock_atom_keepintvl = MKA(env, "keepintvl"); + esock_atom_leave_group = MKA(env, "leave_group"); + esock_atom_level = MKA(env, "level"); + esock_atom_linger = MKA(env, "linger"); + esock_atom_local = MKA(env, "local"); + esock_atom_local_auth_chunks = MKA(env, "local_auth_chunks"); + esock_atom_loopback = MKA(env, "loopback"); + esock_atom_lowdelay = MKA(env, "lowdelay"); + esock_atom_mark = MKA(env, "mark"); + esock_atom_maxburst = MKA(env, "maxburst"); + esock_atom_maxseg = MKA(env, "maxseg"); + esock_atom_md5sig = MKA(env, "md5sig"); + esock_atom_mincost = MKA(env, "mincost"); + esock_atom_minttl = MKA(env, "minttl"); + esock_atom_msfilter = MKA(env, "msfilter"); + esock_atom_mtu = MKA(env, "mtu"); + esock_atom_mtu_discover = MKA(env, "mtu_discover"); + esock_atom_multicast_all = MKA(env, "multicast_all"); + esock_atom_multicast_hops = MKA(env, "multicast_hops"); + esock_atom_multicast_if = MKA(env, "multicast_if"); + esock_atom_multicast_loop = MKA(env, "multicast_loop"); + esock_atom_multicast_ttl = MKA(env, "multicast_ttl"); + esock_atom_nodefrag = MKA(env, "nodefrag"); + esock_atom_nodelay = MKA(env, "nodelay"); + esock_atom_noopt = MKA(env, "noopt"); + esock_atom_nopush = MKA(env, "nopush"); + esock_atom_not_found = MKA(env, "not_found"); + esock_atom_not_owner = MKA(env, "not_owner"); + esock_atom_ok = MKA(env, "ok"); + esock_atom_oob = MKA(env, "oob"); + esock_atom_oobinline = MKA(env, "oobinline"); + esock_atom_options = MKA(env, "options"); + esock_atom_origdstaddr = MKA(env, "origdstaddr"); esock_atom_partial_delivery_point = MKA(env, "partial_delivery_point"); - esock_atom_passcred = MKA(env, "passcred"); - esock_atom_path = MKA(env, "path"); - esock_atom_peekcred = MKA(env, "peekcred"); - esock_atom_peek_off = MKA(env, "peek_off"); - esock_atom_peer_addr_params = MKA(env, "peer_addr_params"); - esock_atom_peer_auth_chunks = MKA(env, "peer_auth_chunks"); - esock_atom_pktinfo = MKA(env, "pktinfo"); - esock_atom_pktoptions = MKA(env, "pktoptions"); - esock_atom_port = MKA(env, "port"); - esock_atom_portrange = MKA(env, "portrange"); - esock_atom_primary_addr = MKA(env, "primary_addr"); - esock_atom_priority = MKA(env, "priority"); - esock_atom_protocol = MKA(env, "protocol"); - esock_atom_raw = MKA(env, "raw"); - esock_atom_rcvbuf = MKA(env, "rcvbuf"); - esock_atom_rcvbufforce = MKA(env, "rcvbufforce"); - esock_atom_rcvlowat = MKA(env, "rcvlowat"); - esock_atom_rcvtimeo = MKA(env, "rcvtimeo"); - esock_atom_rdm = MKA(env, "rdm"); - esock_atom_recv = MKA(env, "recv"); - esock_atom_recvdstaddr = MKA(env, "recvdstaddr"); - esock_atom_recverr = MKA(env, "recverr"); - esock_atom_recvfrom = MKA(env, "recvfrom"); - esock_atom_recvif = MKA(env, "recvif"); - esock_atom_recvmsg = MKA(env, "recvmsg"); - esock_atom_recvopts = MKA(env, "recvopts"); - esock_atom_recvorigdstaddr = MKA(env, "recvorigdstaddr"); - esock_atom_recvpktinfo = MKA(env, "recvpktinfo"); - esock_atom_recvtclass = MKA(env, "recvtclass"); - esock_atom_recvtos = MKA(env, "recvtos"); - esock_atom_recvttl = MKA(env, "recvttl"); - esock_atom_reliability = MKA(env, "reliability"); - esock_atom_reset_streams = MKA(env, "reset_streams"); - esock_atom_retopts = MKA(env, "retopts"); - esock_atom_reuseaddr = MKA(env, "reuseaddr"); - esock_atom_reuseport = MKA(env, "reuseport"); - esock_atom_rights = MKA(env, "rights"); - esock_atom_router_alert = MKA(env, "router_alert"); - esock_atom_rthdr = MKA(env, "rthdr"); - esock_atom_rtoinfo = MKA(env, "rtoinfo"); - esock_atom_rxq_ovfl = MKA(env, "rxq_ovfl"); - esock_atom_scope_id = MKA(env, "scope_id"); - esock_atom_sctp = MKA(env, "sctp"); - esock_atom_sec = MKA(env, "sec"); - esock_atom_select_sent = MKA(env, "select_sent"); - esock_atom_send = MKA(env, "send"); - esock_atom_sendmsg = MKA(env, "sendmsg"); - esock_atom_sendsrcaddr = MKA(env, "sendsrcaddr"); - esock_atom_sendto = MKA(env, "sendto"); - esock_atom_seqpacket = MKA(env, "seqpacket"); - esock_atom_setfib = MKA(env, "setfib"); - esock_atom_set_peer_primary_addr = MKA(env, "set_peer_primary_addr"); - esock_atom_sndbuf = MKA(env, "sndbuf"); - esock_atom_sndbufforce = MKA(env, "sndbufforce"); - esock_atom_sndlowat = MKA(env, "sndlowat"); - esock_atom_sndtimeo = MKA(env, "sndtimeo"); - esock_atom_socket = MKA(env, "socket"); - esock_atom_spec_dst = MKA(env, "spec_dst"); - esock_atom_status = MKA(env, "status"); - esock_atom_stream = MKA(env, "stream"); - esock_atom_syncnt = MKA(env, "syncnt"); - esock_atom_tclass = MKA(env, "tclass"); - esock_atom_tcp = MKA(env, "tcp"); - esock_atom_throughput = MKA(env, "throughput"); - esock_atom_timestamp = MKA(env, "timestamp"); - esock_atom_tos = MKA(env, "tos"); - esock_atom_transparent = MKA(env, "transparent"); - esock_atom_true = MKA(env, "true"); - esock_atom_trunc = MKA(env, "trunc"); - esock_atom_ttl = MKA(env, "ttl"); - esock_atom_type = MKA(env, "type"); - esock_atom_udp = MKA(env, "udp"); - esock_atom_unblock_source = MKA(env, "unblock_source"); - esock_atom_undefined = MKA(env, "undefined"); - esock_atom_unicast_hops = MKA(env, "unicast_hops"); - esock_atom_unknown = MKA(env, "unknown"); - esock_atom_usec = MKA(env, "usec"); - esock_atom_user_timeout = MKA(env, "user_timeout"); - esock_atom_use_ext_recvinfo = MKA(env, "use_ext_recvinfo"); - esock_atom_use_min_mtu = MKA(env, "use_min_mtu"); - esock_atom_v6only = MKA(env, "v6only"); + esock_atom_passcred = MKA(env, "passcred"); + esock_atom_path = MKA(env, "path"); + esock_atom_peekcred = MKA(env, "peekcred"); + esock_atom_peek_off = MKA(env, "peek_off"); + esock_atom_peer_addr_params = MKA(env, "peer_addr_params"); + esock_atom_peer_auth_chunks = MKA(env, "peer_auth_chunks"); + esock_atom_pktinfo = MKA(env, "pktinfo"); + esock_atom_pktoptions = MKA(env, "pktoptions"); + esock_atom_port = MKA(env, "port"); + esock_atom_portrange = MKA(env, "portrange"); + esock_atom_primary_addr = MKA(env, "primary_addr"); + esock_atom_priority = MKA(env, "priority"); + esock_atom_protocol = MKA(env, "protocol"); + esock_atom_raw = MKA(env, "raw"); + esock_atom_rcvbuf = MKA(env, "rcvbuf"); + esock_atom_rcvbufforce = MKA(env, "rcvbufforce"); + esock_atom_rcvlowat = MKA(env, "rcvlowat"); + esock_atom_rcvtimeo = MKA(env, "rcvtimeo"); + esock_atom_rdm = MKA(env, "rdm"); + esock_atom_recv = MKA(env, "recv"); + esock_atom_recvdstaddr = MKA(env, "recvdstaddr"); + esock_atom_recverr = MKA(env, "recverr"); + esock_atom_recvfrom = MKA(env, "recvfrom"); + esock_atom_recvif = MKA(env, "recvif"); + esock_atom_recvmsg = MKA(env, "recvmsg"); + esock_atom_recvopts = MKA(env, "recvopts"); + esock_atom_recvorigdstaddr = MKA(env, "recvorigdstaddr"); + esock_atom_recvpktinfo = MKA(env, "recvpktinfo"); + esock_atom_recvtclass = MKA(env, "recvtclass"); + esock_atom_recvtos = MKA(env, "recvtos"); + esock_atom_recvttl = MKA(env, "recvttl"); + esock_atom_reliability = MKA(env, "reliability"); + esock_atom_reset_streams = MKA(env, "reset_streams"); + esock_atom_retopts = MKA(env, "retopts"); + esock_atom_reuseaddr = MKA(env, "reuseaddr"); + esock_atom_reuseport = MKA(env, "reuseport"); + esock_atom_rights = MKA(env, "rights"); + esock_atom_router_alert = MKA(env, "router_alert"); + esock_atom_rthdr = MKA(env, "rthdr"); + esock_atom_rtoinfo = MKA(env, "rtoinfo"); + esock_atom_rxq_ovfl = MKA(env, "rxq_ovfl"); + esock_atom_scope_id = MKA(env, "scope_id"); + esock_atom_sctp = MKA(env, "sctp"); + esock_atom_sec = MKA(env, "sec"); + esock_atom_select_sent = MKA(env, "select_sent"); + esock_atom_send = MKA(env, "send"); + esock_atom_sendmsg = MKA(env, "sendmsg"); + esock_atom_sendsrcaddr = MKA(env, "sendsrcaddr"); + esock_atom_sendto = MKA(env, "sendto"); + esock_atom_seqpacket = MKA(env, "seqpacket"); + esock_atom_setfib = MKA(env, "setfib"); + esock_atom_set_peer_primary_addr = MKA(env, "set_peer_primary_addr"); + esock_atom_sndbuf = MKA(env, "sndbuf"); + esock_atom_sndbufforce = MKA(env, "sndbufforce"); + esock_atom_sndlowat = MKA(env, "sndlowat"); + esock_atom_sndtimeo = MKA(env, "sndtimeo"); + esock_atom_socket = MKA(env, "socket"); + esock_atom_spec_dst = MKA(env, "spec_dst"); + esock_atom_status = MKA(env, "status"); + esock_atom_stream = MKA(env, "stream"); + esock_atom_syncnt = MKA(env, "syncnt"); + esock_atom_tclass = MKA(env, "tclass"); + esock_atom_tcp = MKA(env, "tcp"); + esock_atom_throughput = MKA(env, "throughput"); + esock_atom_timestamp = MKA(env, "timestamp"); + esock_atom_tos = MKA(env, "tos"); + esock_atom_transparent = MKA(env, "transparent"); + esock_atom_true = MKA(env, "true"); + esock_atom_trunc = MKA(env, "trunc"); + esock_atom_ttl = MKA(env, "ttl"); + esock_atom_type = MKA(env, "type"); + esock_atom_udp = MKA(env, "udp"); + esock_atom_unblock_source = MKA(env, "unblock_source"); + esock_atom_undefined = MKA(env, "undefined"); + esock_atom_unicast_hops = MKA(env, "unicast_hops"); + esock_atom_unknown = MKA(env, "unknown"); + esock_atom_usec = MKA(env, "usec"); + esock_atom_user_timeout = MKA(env, "user_timeout"); + esock_atom_use_ext_recvinfo = MKA(env, "use_ext_recvinfo"); + esock_atom_use_min_mtu = MKA(env, "use_min_mtu"); + esock_atom_v6only = MKA(env, "v6only"); /* Global error codes */ esock_atom_eafnosupport = MKA(env, ESOCK_STR_EAFNOSUPPORT); diff --git a/erts/preloaded/ebin/socket.beam b/erts/preloaded/ebin/socket.beam index 0387e534d8..6b3d488086 100644 Binary files a/erts/preloaded/ebin/socket.beam and b/erts/preloaded/ebin/socket.beam differ diff --git a/erts/preloaded/src/socket.erl b/erts/preloaded/src/socket.erl index 882e305b26..d0e074c817 100644 --- a/erts/preloaded/src/socket.erl +++ b/erts/preloaded/src/socket.erl @@ -818,6 +818,8 @@ -define(SOCKET_SUPPORTS_OPTIONS, 16#0001). +-define(SOCKET_SUPPORTS_SCTP, 16#0002). +-define(SOCKET_SUPPORTS_IPV6, 16#0003). %% =========================================================================== @@ -849,10 +851,16 @@ info() -> -spec supports() -> list(). supports() -> - [{options, supports(options)}]. + [{options, supports(options)}, + {sctp, supports(sctp)}, + {ipv6, supports(ipv6)}]. supports(options) -> nif_supports(?SOCKET_SUPPORTS_OPTIONS); +supports(sctp) -> + nif_supports(?SOCKET_SUPPORTS_SCTP); +supports(ipv6) -> + nif_supports(?SOCKET_SUPPORTS_IPV6); supports(_) -> false. -- cgit v1.2.3 From ce28d70c686f342fb04fc05e1d00501e7cfbf213 Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Tue, 9 Oct 2018 16:32:31 +0200 Subject: [socket-nif|doc] Add preliminary doc for the function supports Added preliminary documentation for the function socket:supports/0,1,2,3. It still does not generate proper doc for supports/3 (the last arg, Opt, don't get a type). OTP-14831 --- erts/doc/src/socket.xml | 49 ++++++++++++++++++++++++ erts/preloaded/ebin/socket.beam | Bin 67144 -> 68544 bytes erts/preloaded/src/socket.erl | 83 ++++++++++++++++++++++++++++++++++++++-- 3 files changed, 129 insertions(+), 3 deletions(-) diff --git a/erts/doc/src/socket.xml b/erts/doc/src/socket.xml index af7e0ca9c1..49c14869bf 100644 --- a/erts/doc/src/socket.xml +++ b/erts/doc/src/socket.xml @@ -205,6 +205,27 @@ + + + + + + + + + + + + + + + + + + + + + @@ -511,6 +532,34 @@ + + + + + + + + + + + + + + + + + + + + + Report info about what the platform supports. + +

This function intends to retreive information about what the + platform supports. Such as if SCTP is supported. Or which socket + options are supported.

+
+
+
Examples diff --git a/erts/preloaded/ebin/socket.beam b/erts/preloaded/ebin/socket.beam index 6b3d488086..a0bf156263 100644 Binary files a/erts/preloaded/ebin/socket.beam and b/erts/preloaded/ebin/socket.beam differ diff --git a/erts/preloaded/src/socket.erl b/erts/preloaded/src/socket.erl index d0e074c817..acf5e18cec 100644 --- a/erts/preloaded/src/socket.erl +++ b/erts/preloaded/src/socket.erl @@ -27,7 +27,7 @@ -export([ on_load/0, on_load/1, info/0, - supports/0, supports/1, + supports/0, supports/1, supports/2, supports/3, ensure_sockaddr/1 ]). @@ -848,20 +848,97 @@ info() -> nif_info(). --spec supports() -> list(). + +%% =========================================================================== +%% +%% supports - get information about what the platform "supports". +%% +%% Generates a list of various info about what the plaform can support. +%% The most obvious case is 'options'. +%% +%% Each item in a 'supports'-list will appear only *one* time. +%% +%% =========================================================================== + +-type supports_options_socket() :: [{socket_option(), boolean()}]. +-type supports_options_ip() :: [{ip_socket_option(), boolean()}]. +-type supports_options_ipv6() :: [{ipv6_socket_option(), boolean()}]. +-type supports_options_tcp() :: [{tcp_socket_option(), boolean()}]. +-type supports_options_udp() :: [{udp_socket_option(), boolean()}]. +-type supports_options_sctp() :: [{sctp_socket_option(), boolean()}]. +-type supports_options() :: [{socket, supports_options_socket()} | + {ip, supports_options_ip()} | + {ipv6, supports_options_ipv6()} | + {tcp, supports_options_tcp()} | + {udp, supports_options_udp()} | + {sctp, supports_options_sctp()}]. + +-spec supports() -> [{options, supports_options()} | + {sctp, boolean()} | + {ipv6, boolean()}]. supports() -> [{options, supports(options)}, {sctp, supports(sctp)}, {ipv6, supports(ipv6)}]. + +-spec supports(options) -> supports_options(); + (sctp) -> boolean(); + (ipv6) -> boolean(); + (Key1) -> false when + Key1 :: term(). + supports(options) -> nif_supports(?SOCKET_SUPPORTS_OPTIONS); supports(sctp) -> nif_supports(?SOCKET_SUPPORTS_SCTP); supports(ipv6) -> nif_supports(?SOCKET_SUPPORTS_IPV6); -supports(_) -> +supports(_Key1) -> + false. + +-spec supports(options, socket) -> supports_options_socket(); + (options, ip) -> supports_options_ip(); + (options, ipv6) -> supports_options_ipv6(); + (options, tcp) -> supports_options_tcp(); + (options, udp) -> supports_options_udp(); + (options, sctp) -> supports_options_sctp(); + (Key1, Key2) -> false when + Key1 :: term(), + Key2 :: term(). + +supports(options, Level) -> + proplists:get_value(Level, supports(options), false); +supports(_Key1, _Level) -> + false. + + +-spec supports(options, socket, Opt) -> boolean() when + Opt :: socket_option(); + (options, ip, Opt) -> boolean() when + Opt :: ip_socket_option(); + (options, ipv6, Opt) -> boolean() when + Opt :: ipv6_socket_option(); + (options, tcp, Opt) -> boolean() when + Opt :: tcp_socket_option(); + (options, udp, Opt) -> boolean() when + Opt :: udp_socket_option(); + (options, sctp, Opt) -> boolean() when + Opt :: sctp_socket_option(); + (Key1, Key2, Key3) -> false when + Key1 :: term(), + Key2 :: term(), + Key3 :: term(). + +supports(options, Level, Opt) -> + case supports(options, Level) of + S when is_list(S) -> + proplists:get_value(Opt, S, false); + _ -> + false + end; +supports(_Key1, _Key2, _Key3) -> false. -- cgit v1.2.3 From 2a10a95dc380b41deffa060e30c0c461dfc3df8d Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Wed, 10 Oct 2018 17:59:55 +0200 Subject: [socket-nif|test] Reworked simple otp options test case --- lib/kernel/test/socket_SUITE.erl | 295 +++++++++++++++++++++++++++++++-------- 1 file changed, 234 insertions(+), 61 deletions(-) diff --git a/lib/kernel/test/socket_SUITE.erl b/lib/kernel/test/socket_SUITE.erl index e50daaf367..a9f51cd11f 100644 --- a/lib/kernel/test/socket_SUITE.erl +++ b/lib/kernel/test/socket_SUITE.erl @@ -652,10 +652,6 @@ api_opt_simple_otp_options(doc) -> api_opt_simple_otp_options(_Config) when is_list(_Config) -> tc_begin(api_opt_simple_otp_options), - p("Create sockets"), - S1 = sock_open(inet, stream, tcp), - S2 = sock_open(inet, dgram, udp), - Get = fun(S, Key) -> socket:getopt(S, otp, Key) end, @@ -663,68 +659,245 @@ api_opt_simple_otp_options(_Config) when is_list(_Config) -> socket:setopt(S, otp, Key, Val) end, - p("Create dummy process"), - Pid = spawn_link(fun() -> - put(sname, "dummy"), - receive - die -> - exit(normal) - end - end), - - F = fun(Sock) -> - p("Test IOW"), - {ok, IOW} = Get(Sock, iow), - NotIOW = not IOW, - ok = Set(Sock, iow, NotIOW), - {ok, NotIOW} = Get(Sock, iow), - - p("Test rcvbuf"), - {ok, RcvBuf} = Get(Sock, rcvbuf), - RcvBuf2 = RcvBuf*2, - ok = Set(Sock, rcvbuf, RcvBuf2), - {ok, RcvBuf2} = Get(Sock, rcvbuf), - ok = Set(Sock, rcvbuf, default), - {ok, RcvBuf} = Get(Sock, rcvbuf), - - p("Test rcvctrlbuf"), - {ok, RcvCtrlBuf} = Get(Sock, rcvctrlbuf), - RcvCtrlBuf2 = RcvCtrlBuf*2, - ok = Set(Sock, rcvctrlbuf, RcvCtrlBuf2), - {ok, RcvCtrlBuf2} = Get(Sock, rcvctrlbuf), - ok = Set(Sock, rcvctrlbuf, default), - {ok, RcvCtrlBuf} = Get(Sock, rcvctrlbuf), - - p("Test sndctrlbuf"), - {ok, SndCtrlBuf} = Get(Sock, sndctrlbuf), - SndCtrlBuf2 = SndCtrlBuf*2, - ok = Set(Sock, sndctrlbuf, SndCtrlBuf2), - {ok, SndCtrlBuf2} = Get(Sock, sndctrlbuf), - ok = Set(Sock, sndctrlbuf, default), - {ok, RcvCtrlBuf} = Get(Sock, sndctrlbuf), - - p("Test controlling-process"), - Self = self(), - {ok, Self} = Get(Sock, controlling_process), - ok = Set(Sock, controlling_process, Pid), - {ok, Pid} = Get(Sock, controlling_process) + Seq = + [ + %% *** Init part *** + #{desc => "create socket", + cmd => fun(#{domain := Domain, + type := Type, + protocol := Protocol} = State) -> + Sock = sock_open(Domain, Type, Protocol), + {ok, State#{sock => Sock}} + end}, + #{desc => "create dummy process", + cmd => fun(State) -> + Pid = spawn_link(fun() -> + put(sname, "dummy"), + receive + die -> + exit(normal) + end + end), + {ok, State#{dummy => Pid}} + end}, - end, + %% *** Check iow part *** + #{desc => "get iow", + cmd => fun(#{sock := Sock} = State) -> + case Get(Sock, iow) of + {ok, IOW} when is_boolean(IOW) -> + {ok, State#{iow => IOW}}; + {ok, InvalidIOW} -> + {error, {invalid, InvalidIOW}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "set (new) iow", + cmd => fun(#{sock := Sock, iow := OldIOW} = State) -> + NewIOW = not OldIOW, + case Set(Sock, iow, NewIOW) of + ok -> + {ok, State#{iow => NewIOW}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "get (new) iow", + cmd => fun(#{sock := Sock, iow := IOW}) -> + case Get(Sock, iow) of + {ok, IOW} -> + ok; + {ok, InvalidIOW} -> + {error, {invalid, InvalidIOW}}; + {error, _} = ERROR -> + ERROR + end + end}, - p("Test stream/tcp "), - F(S1), + %% *** Check rcvbuf part *** + #{desc => "get rcvbuf", + cmd => fun(#{sock := Sock} = State) -> + case Get(Sock, rcvbuf) of + {ok, RcvBuf} when is_integer(RcvBuf) -> + {ok, State#{rcvbuf => RcvBuf}}; + {ok, InvalidRcvBuf} -> + {error, {invalid, InvalidRcvBuf}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "set (new) rcvbuf", + cmd => fun(#{sock := Sock, rcvbuf := OldRcvBuf} = State) -> + NewRcvBuf = 2 * OldRcvBuf, + case Set(Sock, rcvbuf, NewRcvBuf) of + ok -> + {ok, State#{rcvbuf => NewRcvBuf}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "get (new) rcvbuf", + cmd => fun(#{sock := Sock, rcvbuf := RcvBuf}) -> + case Get(Sock, rcvbuf) of + {ok, RcvBuf} -> + ok; + {ok, InvalidRcvBuf} -> + {error, {invalid, InvalidRcvBuf}}; + {error, _} = ERROR -> + ERROR + end + end}, - p("Test dgram/udp "), - F(S2), + %% *** Check rcvctrlbuf part *** + #{desc => "get rcvctrlbuf", + cmd => fun(#{sock := Sock} = State) -> + case Get(Sock, rcvctrlbuf) of + {ok, RcvCtrlBuf} when is_integer(RcvCtrlBuf) -> + {ok, State#{rcvctrlbuf => RcvCtrlBuf}}; + {ok, InvalidRcvCtrlBuf} -> + {error, {invalid, InvalidRcvCtrlBuf}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "set (new) rcvctrlbuf", + cmd => fun(#{sock := Sock, rcvctrlbuf := OldRcvCtrlBuf} = State) -> + NewRcvCtrlBuf = 2 * OldRcvCtrlBuf, + case Set(Sock, rcvctrlbuf, NewRcvCtrlBuf) of + ok -> + {ok, State#{rcvctrlbuf => NewRcvCtrlBuf}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "get (new) rcvctrlbuf", + cmd => fun(#{sock := Sock, rcvctrlbuf := RcvCtrlBuf}) -> + case Get(Sock, rcvctrlbuf) of + {ok, RcvCtrlBuf} -> + ok; + {ok, InvalidRcvCtrlBuf} -> + {error, {invalid, InvalidRcvCtrlBuf}}; + {error, _} = ERROR -> + ERROR + end + end}, + %% *** Check rcvctrlbuf part *** + #{desc => "get rcvctrlbuf", + cmd => fun(#{sock := Sock} = State) -> + case Get(Sock, rcvctrlbuf) of + {ok, RcvCtrlBuf} when is_integer(RcvCtrlBuf) -> + {ok, State#{rcvctrlbuf => RcvCtrlBuf}}; + {ok, InvalidRcvCtrlBuf} -> + {error, {invalid, InvalidRcvCtrlBuf}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "set (new) rcvctrlbuf", + cmd => fun(#{sock := Sock, rcvctrlbuf := OldRcvCtrlBuf} = State) -> + NewRcvCtrlBuf = 2 * OldRcvCtrlBuf, + case Set(Sock, rcvctrlbuf, NewRcvCtrlBuf) of + ok -> + {ok, State#{rcvctrlbuf => NewRcvCtrlBuf}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "get (new) rcvctrlbuf", + cmd => fun(#{sock := Sock, rcvctrlbuf := RcvCtrlBuf}) -> + case Get(Sock, rcvctrlbuf) of + {ok, RcvCtrlBuf} -> + ok; + {ok, InvalidRcvCtrlBuf} -> + {error, {invalid, InvalidRcvCtrlBuf}}; + {error, _} = ERROR -> + ERROR + end + end}, - p("kill dummy process"), - %% This will also close its sockets (S1 and S2), - %% This should really be tested explicitly... - Pid ! die, - %% p("close sockets"), - %% sock_close(S1), - %% sock_close(S2), + %% *** Check sndctrlbuf part *** + #{desc => "get sndctrlbuf", + cmd => fun(#{sock := Sock} = State) -> + case Get(Sock, sndctrlbuf) of + {ok, SndCtrlBuf} when is_integer(SndCtrlBuf) -> + {ok, State#{sndctrlbuf => SndCtrlBuf}}; + {ok, InvalidSndCtrlBuf} -> + {error, {invalid, InvalidSndCtrlBuf}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "set (new) sndctrlbuf", + cmd => fun(#{sock := Sock, sndctrlbuf := OldSndCtrlBuf} = State) -> + NewSndCtrlBuf = 2 * OldSndCtrlBuf, + case Set(Sock, sndctrlbuf, NewSndCtrlBuf) of + ok -> + {ok, State#{sndctrlbuf => NewSndCtrlBuf}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "get (new) sndctrlbuf", + cmd => fun(#{sock := Sock, sndctrlbuf := SndCtrlBuf}) -> + case Get(Sock, sndctrlbuf) of + {ok, SndCtrlBuf} -> + ok; + {ok, InvalidSndCtrlBuf} -> + {error, {invalid, InvalidSndCtrlBuf}}; + {error, _} = ERROR -> + ERROR + end + end}, + + %% *** Check controlling-process part *** + #{desc => "verify self as controlling-process", + cmd => fun(#{sock := Sock}) -> + Self = self(), + case Get(Sock, controlling_process) of + {ok, Self} -> + ok; + {ok, InvalidPid} -> + {error, {invalid, InvalidPid}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "set dummy as controlling-process", + cmd => fun(#{sock := Sock, dummy := Dummy}) -> + Set(Sock, controlling_process, Dummy) + end}, + #{desc => "verify dummy as controlling-process", + cmd => fun(#{sock := Sock, dummy := Dummy}) -> + case Get(Sock, controlling_process) of + {ok, Dummy} -> + ok; + {ok, InvalidPid} -> + {error, {invalid, InvalidPid}}; + {error, _} = ERROR -> + ERROR + end + end}, + + %% *** We are done *** + #{desc => "finish", + cmd => fun(_) -> + {ok, normal} + end} + ], + + p("Run test for stream/tcp socket"), + InitState1 = #{domain => inet, type => stream, protocol => tcp}, + Tester1 = evaluator_start("tcp-tester", Seq, InitState1), + p("await evaluator 1"), + ok = await_evaluator_finish([Tester1]), + + p("Run test for dgram/udp socket"), + InitState2 = #{domain => inet, type => dgram, protocol => udp}, + Tester2 = evaluator_start("udp-tester", Seq, InitState2), + p("await evaluator 2"), + ok = await_evaluator_finish([Tester2]), tc_end(). -- cgit v1.2.3 From 8092cb3bb4e3a90be52df03fff211a2e29dc8784 Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Wed, 10 Oct 2018 18:55:33 +0200 Subject: [socket-nif|test] Reworked otp controlling process option test case --- lib/kernel/test/socket_SUITE.erl | 327 +++++++++++++++++++++++++++------------ 1 file changed, 224 insertions(+), 103 deletions(-) diff --git a/lib/kernel/test/socket_SUITE.erl b/lib/kernel/test/socket_SUITE.erl index a9f51cd11f..23a77f428c 100644 --- a/lib/kernel/test/socket_SUITE.erl +++ b/lib/kernel/test/socket_SUITE.erl @@ -912,10 +912,6 @@ api_opt_simple_otp_controlling_process(doc) -> api_opt_simple_otp_controlling_process(_Config) when is_list(_Config) -> tc_begin(api_opt_simple_otp_controlling_process), - p("Create sockets"), - S1 = sock_open(inet, stream, tcp), - S2 = sock_open(inet, dgram, udp), - Get = fun(S, Key) -> socket:getopt(S, otp, Key) end, @@ -923,110 +919,235 @@ api_opt_simple_otp_controlling_process(_Config) when is_list(_Config) -> socket:setopt(S, otp, Key, Val) end, - AwaitStart = - fun() -> - p("await start command"), - receive - {start, P, S} -> - {P, S} - end - end, - AwaitContinue = - fun(Pid) -> - p("await continue command"), - receive - {continue, Pid} -> - ok - end - end, - AwaitReady = - fun(Pid) -> - p("await ready confirmation from ~p", [Pid]), - receive - {ready, Pid} -> - ok - end - end, - AwaitDie = - fun(Pid) -> - p("await die command"), - receive - {die, Pid} -> - ok - end - end, - ClientStarter = - fun() -> - put(sname, "client"), - Self = self(), - {Parent, Sock} = AwaitStart(), - p("verify parent ~p controlling", [Parent]), - {ok, Parent} = Get(Sock, controlling_process), - p("attempt invalid control transfer (to self)"), - {error, not_owner} = Set(Sock, controlling_process, self()), - p("verify parent ~p (still) controlling", [Parent]), - {ok, Parent} = Get(Sock, controlling_process), - p("announce ready"), - Parent ! {ready, self()}, - - AwaitContinue(Parent), - p("verify self controlling"), - {ok, Self} = Get(Sock, controlling_process), - p("transfer control to parent ~p", [Parent]), - ok = Set(Sock, controlling_process, Parent), - p("attempt invalid control transfer (to self)"), - {error, not_owner} = Set(Sock, controlling_process, self()), - p("verify parent ~p controlling", [Parent]), - {ok, Parent} = Get(Sock, controlling_process), - p("announce ready"), - Parent ! {ready, self()}, - - AwaitDie(Parent), - p("done"), - exit(normal) - end, + ClientSeq = + [ + %% *** Init part *** + #{desc => "await start", + cmd => fun(State) -> + receive + {start, Tester, Socket} -> + {ok, State#{tester => Tester, + sock => Socket}} + end + end}, + #{desc => "verify tester as controlling-process", + cmd => fun(#{tester := Tester, sock := Sock} = _State) -> + case Get(Sock, controlling_process) of + {ok, Tester} -> + ok; + {ok, InvalidPid} -> + {error, {invalid, InvalidPid}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "attempt invalid controlling-process transfer (to self)", + cmd => fun(#{sock := Sock} = _State) -> + case Set(Sock, controlling_process, self()) of + {error, not_owner} -> + ok; + ok -> + {error, unexpected_success}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (1)", + cmd => fun(#{tester := Tester} = _State) -> + Tester ! {ready, self()}, + ok + end}, + #{desc => "await continue", + cmd => fun(#{tester := Tester} = _State) -> + receive + {continue, Tester} -> + ok + end + end}, + #{desc => "verify self as controlling-process", + cmd => fun(#{sock := Sock} = _State) -> + Self = self(), + case Get(Sock, controlling_process) of + {ok, Self} -> + ok; + {ok, InvalidPid} -> + {error, {invalid, InvalidPid}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "attempt controlling-process transfer to tester", + cmd => fun(#{tester := Tester, sock := Sock} = _State) -> + Set(Sock, controlling_process, Tester) + end}, + #{desc => "attempt invalid controlling-process transfer (to self)", + cmd => fun(#{sock := Sock} = _State) -> + case Set(Sock, controlling_process, self()) of + {error, not_owner} -> + ok; + ok -> + {error, unexpected_success}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (2)", + cmd => fun(#{tester := Tester} = _State) -> + Tester ! {ready, self()}, + ok + end}, + #{desc => "await termination", + cmd => fun(#{tester := Tester} = State) -> + receive + {terminate, Tester} -> + State1 = maps:remove(tester, State), + State2 = maps:remove(sock, State1), + {ok, State2} + end + end}, - Tester = - fun(Sock, Client) -> - p("start"), - Self = self(), - p("verify self controlling"), - {ok, Self} = Get(Sock, controlling_process), - p("announce start"), - Client ! {start, Self, Sock}, - AwaitReady(Client), - - p("transfer control to client ~p", [Client]), - ok = Set(Sock, controlling_process, Client), - p("verify client ~p controlling", [Client]), - {ok, Client} = Get(Sock, controlling_process), - p("attempt invalid control transfer (to self)"), - {error, not_owner} = Set(Sock, controlling_process, self()), - p("announce continue"), - Client ! {continue, Self}, - AwaitReady(Client), - - p("verify self controlling"), - {ok, Self} = Get(Sock, controlling_process), - p("announce die"), - Client ! {die, Self}, - p("done"), - ok - end, + %% *** We are done *** + #{desc => "finish", + cmd => fun(_) -> + {ok, normal} + end} + ], - p("Create Worker Process(s)"), - Pid1 = spawn_link(ClientStarter), - Pid2 = spawn_link(ClientStarter), + TesterSeq = + [ + %% *** Init part *** + #{desc => "create socket", + cmd => fun(#{domain := Domain, + type := Type, + protocol := Protocol} = State) -> + Sock = sock_open(Domain, Type, Protocol), + {ok, State#{sock => Sock}} + end}, + #{desc => "verify self as controlling-process", + cmd => fun(#{sock := Sock} = _State) -> + Self = self(), + case Get(Sock, controlling_process) of + {ok, Self} -> + ok; + {ok, InvalidPid} -> + {error, {invalid, InvalidPid}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "order (client) start", + cmd => fun(#{client := Client, sock := Sock} = _State) -> + Client ! {start, self(), Sock}, + ok + end}, + #{desc => "await (client) ready (1)", + cmd => fun(#{client := Client} = _State) -> + receive + {ready, Client} -> + ok + end + end}, + #{desc => "attempt controlling-process transfer to client", + cmd => fun(#{client := Client, sock := Sock} = _State) -> + Set(Sock, controlling_process, Client) + end}, + #{desc => "verify client as controlling-process", + cmd => fun(#{client := Client, sock := Sock} = _State) -> + case Get(Sock, controlling_process) of + {ok, Client} -> + ok; + {ok, InvalidPid} -> + {error, {invalid, InvalidPid}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "attempt invalid controlling-process transfer (to self)", + cmd => fun(#{sock := Sock} = _State) -> + case Set(Sock, controlling_process, self()) of + {error, not_owner} -> + ok; + ok -> + {error, unexpected_success}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "order (client) continue", + cmd => fun(#{client := Client} = _State) -> + Client ! {continue, self()}, + ok + end}, + #{desc => "await (client) ready (2)", + cmd => fun(#{client := Client} = _State) -> + receive + {ready, Client} -> + ok + end + end}, + #{desc => "verify self as controlling-process", + cmd => fun(#{sock := Sock} = _State) -> + Self = self(), + case Get(Sock, controlling_process) of + {ok, Self} -> + ok; + {ok, InvalidPid} -> + {error, {invalid, InvalidPid}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "monitor client", + cmd => fun(#{client := Client} = State) -> + MRef = erlang:monitor(process, Client), + {ok, State#{client_mref => MRef}} + end}, + #{desc => "order (client) terminate", + cmd => fun(#{client := Client} = _State) -> + Client ! {terminate, self()}, + ok + end}, + #{desc => "await (client) down", + cmd => fun(#{client := Client} = State) -> + receive + {'DOWN', _, process, Client, _} -> + {ok, maps:remove(client, State)} + end + end}, + #{desc => "close socket", + cmd => fun(#{sock := Sock} = State) -> + sock_close(Sock), + {ok, maps:remove(sock, State)} + end}, - p("Test stream/tcp "), - Tester(S1, Pid1), + %% *** We are done *** + #{desc => "finish", + cmd => fun(_) -> + {ok, normal} + end} + ], - p("Test dgram/udp "), - Tester(S2, Pid2), + p("Run test for stream/tcp socket"), + ClientInitState1 = #{}, + Client1 = evaluator_start("tcp-client", ClientSeq, ClientInitState1), + TesterInitState1 = #{domain => inet, + type => stream, + protocol => tcp, + client => Client1}, + Tester1 = evaluator_start("tcp-tester", TesterSeq, TesterInitState1), + p("await stream/tcp evaluator"), + ok = await_evaluator_finish([Tester1, Client1]), - p("close sockets"), - sock_close(S1), - sock_close(S2), + p("Run test for dgram/udp socket"), + ClientInitState2 = #{}, + Client2 = evaluator_start("udp-client", ClientSeq, ClientInitState2), + TesterInitState2 = #{domain => inet, + type => dgram, + protocol => udp, + client => Client2}, + Tester2 = evaluator_start("udp-tester", TesterSeq, TesterInitState2), + p("await dgram/udp evaluator"), + ok = await_evaluator_finish([Tester2, Client2]), tc_end(). -- cgit v1.2.3 From 3ccba221981a6ae61cb2b7222fe7ea56aa56a923 Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Thu, 11 Oct 2018 12:06:41 +0200 Subject: [socket-nif|test] Reworked the connect timeout test case --- lib/kernel/test/socket_SUITE.erl | 281 +++++++++++++++++++++++++++++++-------- 1 file changed, 226 insertions(+), 55 deletions(-) diff --git a/lib/kernel/test/socket_SUITE.erl b/lib/kernel/test/socket_SUITE.erl index 23a77f428c..2d406458f0 100644 --- a/lib/kernel/test/socket_SUITE.erl +++ b/lib/kernel/test/socket_SUITE.erl @@ -1162,7 +1162,8 @@ api_to_connect_tcp4(doc) -> []; api_to_connect_tcp4(_Config) when is_list(_Config) -> tc_begin(api_to_connect_tcp4), - ok = api_to_connect_tcp(inet), + InitState = #{domain => inet}, + ok = api_to_connect_tcp(InitState), tc_end(). %% not_yet_implemented(). @@ -1177,7 +1178,8 @@ api_to_connect_tcp6(doc) -> []; api_to_connect_tcp6(_Config) when is_list(_Config) -> %% tc_begin(api_to_connect_tcp6), - %% ok = api_to_connect_tcp(inet6), + %% InitState = #{domain => inet6}, + %% ok = api_to_connect_tcp(InitState), %% tc_end(). not_yet_implemented(). @@ -1189,54 +1191,223 @@ api_to_connect_tcp6(_Config) when is_list(_Config) -> %% For instance, on FreeBSD (11.2) the reponse when the backlog is full %% is a econreset. -api_to_connect_tcp(Domain) -> +api_to_connect_tcp(InitState) -> process_flag(trap_exit, true), - p("init"), - Client = self(), - LocalAddr = which_local_addr(Domain), - LocalSA = #{family => Domain, addr => LocalAddr}, - ServerName = f("~s:server", [get_tc_name()]), - Server = spawn_link(fun() -> - put(sname, ServerName), - p("open"), - LSock = sock_open(Domain, stream, tcp), - p("bind"), - ServerLPort = sock_bind(LSock, LocalSA), - p("listen on ~w", [ServerLPort]), - sock_listen(LSock, 1), - p("inform client"), - Client ! {self(), ServerLPort}, - p("await termination command"), - receive - die -> - p("terminating"), - exit(normal) - end - end), - - p("await server port"), - ServerLPort = - receive - {Server, Port} -> - Port - end, - p("open(s)"), - CSock1 = sock_open(Domain, stream, tcp), - CSock2 = sock_open(Domain, stream, tcp), - CSock3 = sock_open(Domain, stream, tcp), - p("bind(s)"), - _ClientPort1 = sock_bind(CSock1, LocalSA), - _ClientPort2 = sock_bind(CSock2, LocalSA), - _ClientPort3 = sock_bind(CSock3, LocalSA), - ServerSA = LocalSA#{port => ServerLPort}, - api_to_connect_tcp_await_timeout([CSock1, CSock2, CSock3], ServerSA), - p("terminate server"), - Server ! die, - receive - {'EXIT', Server, _} -> - p("server terminated"), - ok - end, + + ServerSeq = + [ + %% *** Wait for start order part *** + #{desc => "await start (from tester)", + cmd => fun(State) -> + receive + {start, Tester} when is_pid(Tester) -> + {ok, State#{tester => Tester}} + end + end}, + + %% *** Init part *** + #{desc => "which local address", + cmd => fun(#{domain := Domain} = State) -> + LAddr = which_local_addr(Domain), + LSA = #{family => Domain, addr => LAddr}, + {ok, State#{lsa => LSA}} + end}, + #{desc => "create listen socket", + cmd => fun(#{domain := Domain} = State) -> + case socket:open(Domain, stream, tcp) of + {ok, Sock} -> + {ok, State#{lsock => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "bind to local address", + cmd => fun(#{lsock := LSock, lsa := LSA} = State) -> + case socket:bind(LSock, LSA) of + {ok, Port} -> + {ok, State#{lport => Port}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "make listen socket (with backlog = 1)", + cmd => fun(#{lsock := LSock}) -> + socket:listen(LSock, 1) + end}, + #{desc => "monitor server", + cmd => fun(#{tester := Tester} = State) -> + MRef = erlang:monitor(process, Tester), + {ok, State#{tester_mref => MRef}} + end}, + #{desc => "announce ready", + cmd => fun(#{tester := Tester, lport := Port}) -> + ei("announcing ready to tester (~p)", [Tester]), + Tester ! {ready, self(), Port}, + ok + end}, + #{desc => "await terminate (from tester)", + cmd => fun(#{tester := Tester}) -> + receive + {'DOWN', _, process, Tester, Reason} -> + {error, {unexpected_tester_exit, Reason}}; + {terminate, Tester} -> + ok + end + end}, + + %% *** We are done *** + #{desc => "finish", + cmd => fun(_) -> + {ok, normal} + end} + ], + + TesterSeq = + [ + %% *** Init part *** + #{desc => "which local address", + cmd => fun(#{domain := Domain} = State) -> + LAddr = which_local_addr(Domain), + LSA = #{family => Domain, addr => LAddr}, + {ok, State#{lsa => LSA}} + end}, + #{desc => "create socket 1", + cmd => fun(#{domain := Domain} = State) -> + case socket:open(Domain, stream, tcp) of + {ok, Sock} -> + {ok, State#{sock1 => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "create socket 2", + cmd => fun(#{domain := Domain} = State) -> + case socket:open(Domain, stream, tcp) of + {ok, Sock} -> + {ok, State#{sock2 => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "create socket 3", + cmd => fun(#{domain := Domain} = State) -> + case socket:open(Domain, stream, tcp) of + {ok, Sock} -> + {ok, State#{sock3 => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "bind socket 1 to local address", + cmd => fun(#{sock1 := Sock, lsa := LSA} = _State) -> + case socket:bind(Sock, LSA) of + {ok, _} -> + ok; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "bind socket 2 to local address", + cmd => fun(#{sock2 := Sock, lsa := LSA} = _State) -> + case socket:bind(Sock, LSA) of + {ok, _} -> + ok; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "bind socket 3 to local address", + cmd => fun(#{sock3 := Sock, lsa := LSA} = _State) -> + case socket:bind(Sock, LSA) of + {ok, _} -> + ok; + {error, _} = ERROR -> + ERROR + end + end}, + + %% *** Synchronize with the server *** + #{desc => "order (server) start", + cmd => fun(#{server := Server}) -> + Server ! {start, self()}, + ok + end}, + #{desc => "await ready (from server)", + cmd => fun(#{server := Server, lsa := LSA} = State) -> + receive + {ready, Server, Port} -> + {ok, State#{ssa => LSA#{port => Port}}} + end + end}, + + %% *** Connect sequence *** + #{desc => "order (server) start", + cmd => fun(#{sock1 := Sock1, + sock2 := Sock2, + sock3 := Sock3, + ssa := SSA}) -> + Socks = [Sock1, Sock2, Sock3], + api_to_connect_tcp_await_timeout(Socks, SSA) + end}, + + %% *** Terminate server *** + #{desc => "monitor server", + cmd => fun(#{server := Server} = State) -> + MRef = erlang:monitor(process, Server), + {ok, State#{server_mref => MRef}} + end}, + #{desc => "order (server) terminate", + cmd => fun(#{server := Server} = _State) -> + Server ! {terminate, self()}, + ok + end}, + #{desc => "await (server) down", + cmd => fun(#{server := Server} = State) -> + receive + {'DOWN', _, process, Server, _} -> + State1 = maps:remove(server, State), + State2 = maps:remove(ssa, State1), + {ok, State2} + end + end}, + + %% *** Close our sockets *** + #{desc => "close socket 3", + cmd => fun(#{sock3 := Sock} = State) -> + sock_close(Sock), + {ok, maps:remove(sock3, State)} + + end}, + #{desc => "close socket 2", + cmd => fun(#{sock2 := Sock} = State) -> + sock_close(Sock), + {ok, maps:remove(sock2, State)} + + end}, + #{desc => "close socket 1", + cmd => fun(#{sock1 := Sock} = State) -> + sock_close(Sock), + {ok, maps:remove(sock1, State)} + + end}, + + %% *** We are done *** + #{desc => "finish", + cmd => fun(_) -> + {ok, normal} + end} + ], + + p("create server evaluator"), + ServerInitState = InitState, + Server = evaluator_start("server", ServerSeq, ServerInitState), + + p("create tester evaluator"), + TesterInitState = InitState#{server => Server}, + Tester = evaluator_start("tester", TesterSeq, TesterInitState), + + p("await evaluator(s)"), + ok = await_evaluator_finish([Server, Tester]), ok. @@ -1246,19 +1417,19 @@ api_to_connect_tcp_await_timeout(Socks, ServerSA) -> api_to_connect_tcp_await_timeout([], _ServerSA, _ID) -> ?FAIL(unexpected_success); api_to_connect_tcp_await_timeout([Sock|Socks], ServerSA, ID) -> - p("~w: try connect", [ID]), + ei("~w: try connect", [ID]), case socket:connect(Sock, ServerSA, 5000) of {error, timeout} -> - p("expected timeout (~w)", [ID]), + ei("expected timeout (~w)", [ID]), ok; {error, econnreset = Reason} -> - p("failed connecting: ~p - giving up", [Reason]), + ei("failed connecting: ~p - giving up", [Reason]), ok; {error, Reason} -> - p("failed connecting: ~p", [Reason]), - ?FAIL({recv, Reason}); + ee("failed connecting: ~p", [Reason]), + ?FAIL({connect, Reason}); ok -> - p("unexpected success (~w) - try next", [ID]), + ei("unexpected success (~w) - try next", [ID]), api_to_connect_tcp_await_timeout(Socks, ServerSA, ID+1) end. -- cgit v1.2.3 From 51d0d80c96d29445713a026870e3e0124f3380a4 Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Thu, 11 Oct 2018 15:06:54 +0200 Subject: [socket-nif|test] Reworked tcp (IPv4) recv timeout test case --- lib/kernel/test/socket_SUITE.erl | 384 ++++++++++++++++++++++++++++++++++----- 1 file changed, 334 insertions(+), 50 deletions(-) diff --git a/lib/kernel/test/socket_SUITE.erl b/lib/kernel/test/socket_SUITE.erl index 2d406458f0..9a4b263ea0 100644 --- a/lib/kernel/test/socket_SUITE.erl +++ b/lib/kernel/test/socket_SUITE.erl @@ -1246,12 +1246,12 @@ api_to_connect_tcp(InitState) -> ok end}, #{desc => "await terminate (from tester)", - cmd => fun(#{tester := Tester}) -> + cmd => fun(#{tester := Tester} = State) -> receive {'DOWN', _, process, Tester, Reason} -> - {error, {unexpected_tester_exit, Reason}}; + {error, {unexpected_exit, tester, Reason}}; {terminate, Tester} -> - ok + {ok, maps:remove(tester, State)} end end}, @@ -1597,7 +1597,8 @@ api_to_recv_tcp4(doc) -> []; api_to_recv_tcp4(_Config) when is_list(_Config) -> tc_begin(api_to_recv_tcp4), - ok = api_to_recv_tcp(inet), + InitState = #{domain => inet}, + ok = api_to_recv_tcp(InitState), tc_end(). @@ -1611,59 +1612,342 @@ api_to_recv_tcp6(doc) -> []; api_to_recv_tcp6(_Config) when is_list(_Config) -> %% tc_begin(api_to_recv_tcp6), - %% ok = api_to_recv_tcp(inet6), + %% InitState = #{domain => inet6}, + %% ok = api_to_recv_tcp(InitState), %% tc_end(). not_yet_implemented(). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -api_to_recv_tcp(Domain) -> +api_to_recv_tcp(InitState) -> process_flag(trap_exit, true), - p("server -> open"), - LSock = sock_open(Domain, stream, tcp), - LocalAddr = which_local_addr(Domain), - LocalSA = #{family => Domain, addr => LocalAddr}, - p("server -> bind"), - ServerLPort = sock_bind(LSock, LocalSA), - p("server(~w) -> listen", [ServerLPort]), - sock_listen(LSock), - ClientName = f("~s:client", [get_tc_name()]), - Client = spawn_link(fun() -> - put(sname, ClientName), - p("open"), - CSock = sock_open(Domain, stream, tcp), - p("bind"), - ClientPort = sock_bind(CSock, LocalSA), - p("[~w] connect to ~w", - [ClientPort, ServerLPort]), - sock_connect(CSock, LocalSA#{port => ServerLPort}), - p("await termination command"), - receive - die -> - p("terminating"), - exit(normal) - end - end), - p("server -> accept on ~w", [ServerLPort]), - Sock = sock_accept(LSock), - p("server -> recv"), - %% The zero (0) represents "give me everything you have" - case socket:recv(Sock, 0, 5000) of - {error, timeout} -> - p("server -> expected timeout"), - ok; - {ok, _Data} -> - ?FAIL(unexpected_success); - {error, Reason} -> - ?FAIL({recv, Reason}) - end, - Client ! die, - receive - {'EXIT', Client, _} -> - ok - end, - ok. + + ServerSeq = + [ + %% *** Wait for start order *** + #{desc => "await start (from tester)", + cmd => fun(State) -> + receive + {start, Tester} when is_pid(Tester) -> + {ok, State#{tester => Tester}} + end + end}, + + %% *** Init part *** + #{desc => "which local address", + cmd => fun(#{domain := Domain} = State) -> + LAddr = which_local_addr(Domain), + LSA = #{family => Domain, addr => LAddr}, + {ok, State#{lsa => LSA}} + end}, + #{desc => "create listen socket", + cmd => fun(#{domain := Domain} = State) -> + case socket:open(Domain, stream, tcp) of + {ok, Sock} -> + {ok, State#{lsock => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "bind to local address", + cmd => fun(#{lsock := LSock, lsa := LSA} = State) -> + case socket:bind(LSock, LSA) of + {ok, Port} -> + {ok, State#{lport => Port}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "make listen socket (with backlog = 1)", + cmd => fun(#{lsock := LSock}) -> + socket:listen(LSock, 1) + end}, + #{desc => "monitor tester", + cmd => fun(#{tester := Tester} = State) -> + MRef = erlang:monitor(process, Tester), + {ok, State#{tester_mref => MRef}} + end}, + #{desc => "announce ready", + cmd => fun(#{tester := Tester, lport := Port}) -> + Tester ! {ready, self(), Port}, + ok + end}, + #{desc => "await continue", + cmd => fun(#{tester := Tester}) -> + receive + {'DOWN', _, process, Tester, Reason} -> + {error, {unexpected_exit, tester, Reason}}; + {continue, Tester} -> + ok + end + end}, + + %% *** The actual test *** + #{desc => "await accept", + cmd => fun(#{lsock := LSock} = State) -> + case socket:accept(LSock) of + {ok, Sock} -> + {ok, State#{sock => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "attempt to recv (without success)", + cmd => fun(#{sock := Sock} = _State) -> + case socket:recv(Sock, 0, 5000) of + {error, timeout} -> + ok; + {ok, _Data} -> + {error, unexpected_success}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (recv timeout success)", + cmd => fun(#{tester := Tester} = _State) -> + Tester ! {ready, self()}, + ok + end}, + + %% *** Termination *** + #{desc => "await terminate", + cmd => fun(#{tester := Tester} = State) -> + receive + {'DOWN', _, process, Tester, Reason} -> + {error, {unexpected_exit, tester, Reason}}; + {terminate, Tester} -> + {ok, maps:remove(tester, State)} + end + end}, + #{desc => "close (traffic) socket", + cmd => fun(#{sock := Sock} = State) -> + sock_close(Sock), + {ok, maps:remove(sock, State)} + end}, + #{desc => "close (listen) socket", + cmd => fun(#{lsock := LSock} = State) -> + sock_close(LSock), + {ok, maps:remove(lsock, State)} + end}, + + %% *** We are done *** + #{desc => "finish", + cmd => fun(_) -> + {ok, normal} + end} + ], + + ClientSeq = + [ + %% *** Wait for start order part *** + #{desc => "await start (from tester)", + cmd => fun(State) -> + receive + {start, Tester, Port} when is_pid(Tester) -> + {ok, State#{tester => Tester, + server_port => Port}} + end + end}, + + %% *** Init part *** + #{desc => "which local address", + cmd => fun(#{domain := Domain, server_port := Port} = State) -> + LAddr = which_local_addr(Domain), + LSA = #{family => Domain, + addr => LAddr}, + SSA = LSA#{port => Port}, + {ok, State#{lsa => LSA, ssa => SSA}} + end}, + #{desc => "create socket", + cmd => fun(#{domain := Domain} = State) -> + case socket:open(Domain, stream, tcp) of + {ok, Sock} -> + {ok, State#{sock => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "bind to local address", + cmd => fun(#{sock := Sock, lsa := LSA} = _State) -> + case socket:bind(Sock, LSA) of + {ok, _} -> + ok; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "monitor tester", + cmd => fun(#{tester := Tester} = State) -> + MRef = erlang:monitor(process, Tester), + {ok, State#{tester_mref => MRef}} + end}, + #{desc => "announce ready", + cmd => fun(#{tester := Tester} = _State) -> + Tester ! {ready, self()}, + ok + end}, + + %% *** The actual test *** + #{desc => "await continue (with connect)", + cmd => fun(#{tester := Tester} = _State) -> + receive + {'DOWN', _, process, Tester, Reason} -> + {error, {unexpected_exit, tester, Reason}}; + {continue, Tester} -> + ok + end + end}, + #{desc => "connect", + cmd => fun(#{sock := Sock, ssa := SSA}) -> + sock_connect(Sock, SSA), + ok + end}, + + %% *** Termination *** + #{desc => "await terminate", + cmd => fun(#{tester := Tester} = State) -> + receive + {'DOWN', _, process, Tester, Reason} -> + {error, {unexpected_exit, tester, Reason}}; + {terminate, Tester} -> + {ok, maps:remove(tester, State)} + end + end}, + #{desc => "close socket", + cmd => fun(#{sock := Sock} = State) -> + sock_close(Sock), + {ok, maps:remove(sock, State)} + end}, + + %% *** We are done *** + #{desc => "finish", + cmd => fun(_) -> + {ok, normal} + end} + ], + + TesterSeq = + [ + %% *** Init part *** + #{desc => "monitor server", + cmd => fun(#{server := Server} = State) -> + MRef = erlang:monitor(process, Server), + {ok, State#{server_mref => MRef}} + end}, + #{desc => "monitor client", + cmd => fun(#{client := Client} = State) -> + MRef = erlang:monitor(process, Client), + {ok, State#{client_mref => MRef}} + end}, + + %% *** Activate server *** + #{desc => "start server", + cmd => fun(#{server := Server} = _State) -> + Server ! {start, self()}, + ok + end}, + #{desc => "await server ready (init)", + cmd => fun(#{server := Server} = State) -> + receive + {'DOWN', _, process, Server, Reason} -> + {error, {unexpected_exit, server, Reason}}; + {ready, Server, Port} -> + {ok, State#{server_port => Port}} + end + end}, + #{desc => "order server to continue (with accept)", + cmd => fun(#{server := Server} = _State) -> + Server ! {continue, self()}, + ok + end}, + + %% *** Activate client *** + #{desc => "start client", + cmd => fun(#{client := Client, server_port := Port} = _State) -> + Client ! {start, self(), Port}, + ok + end}, + #{desc => "await client ready", + cmd => fun(#{client := Client} = _State) -> + receive + {'DOWN', _, process, Client, Reason} -> + {error, {unexpected_exit, client, Reason}}; + {ready, Client} -> + ok + end + end}, + + %% *** The actual test *** + #{desc => "order client to continue (with connect)", + cmd => fun(#{client := Client} = _State) -> + Client ! {continue, self()}, + ok + end}, + #{desc => "await server ready (accept/recv)", + cmd => fun(#{server := Server} = _State) -> + receive + {'DOWN', _, process, Server, Reason} -> + {error, {unexpected_exit, server, Reason}}; + {ready, Server} -> + ok + end + end}, + + %% *** Termination *** + #{desc => "order client to terminate", + cmd => fun(#{client := Client} = _State) -> + Client ! {terminate, self()}, + ok + end}, + #{desc => "await client termination", + cmd => fun(#{client := Client} = State) -> + receive + {'DOWN', _, process, Client, _Reason} -> + State1 = maps:remove(client, State), + State2 = maps:remove(client_mref, State1), + {ok, State2} + end + end}, + #{desc => "order server to terminate", + cmd => fun(#{server := Server} = State) -> + Server ! {terminate, self()}, + ok + end}, + #{desc => "await server termination", + cmd => fun(#{server := Server} = State) -> + receive + {'DOWN', _, process, Server, _Reason} -> + State1 = maps:remove(server, State), + State2 = maps:remove(server_mref, State1), + State3 = maps:remove(server_port, State2), + {ok, State3} + end + end}, + + %% *** We are done *** + #{desc => "finish", + cmd => fun(_) -> + {ok, normal} + end} + ], + + + p("start server evaluator"), + ServerInitState = InitState, + Server = evaluator_start("server", ServerSeq, ServerInitState), + + p("start client evaluator"), + ClientInitState = InitState, + Client = evaluator_start("client", ClientSeq, ClientInitState), + + p("start tester evaluator"), + TesterInitState = #{server => Server, client => Client}, + Tester = evaluator_start("tester", TesterSeq, TesterInitState), + + p("await evaluator(s)"), + ok = await_evaluator_finish([Server, Client, Tester]). + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -- cgit v1.2.3 From d66f69c98404b8c299e98237839a35b351d454ca Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Thu, 11 Oct 2018 15:27:31 +0200 Subject: [socket-nif|test] Reworked (IPv4) tcp recvmsg timeout test case --- lib/kernel/test/socket_SUITE.erl | 152 +++++++++++++++------------------------ 1 file changed, 57 insertions(+), 95 deletions(-) diff --git a/lib/kernel/test/socket_SUITE.erl b/lib/kernel/test/socket_SUITE.erl index 9a4b263ea0..a432473f01 100644 --- a/lib/kernel/test/socket_SUITE.erl +++ b/lib/kernel/test/socket_SUITE.erl @@ -1597,8 +1597,9 @@ api_to_recv_tcp4(doc) -> []; api_to_recv_tcp4(_Config) when is_list(_Config) -> tc_begin(api_to_recv_tcp4), - InitState = #{domain => inet}, - ok = api_to_recv_tcp(InitState), + Recv = fun(Sock) -> socket:recv(Sock, 0, 5000) end, + InitState = #{domain => inet, recv => Recv}, + ok = api_to_receive_tcp(InitState), tc_end(). @@ -1612,15 +1613,22 @@ api_to_recv_tcp6(doc) -> []; api_to_recv_tcp6(_Config) when is_list(_Config) -> %% tc_begin(api_to_recv_tcp6), - %% InitState = #{domain => inet6}, - %% ok = api_to_recv_tcp(InitState), - %% tc_end(). + %% Res = case socket:supports(ipv6) of + %% true -> + %% Recv = fun(Sock) -> socket:recv(Sock, 0, 5000) end, + %% InitState = #{domain => inet6, recv => Recv}, + %% ok = api_to_receive_tcp(InitState); + %% false -> + %% {skip, ipv6_not_supported} + %% end, + %% tc_end(), + %% Res. not_yet_implemented(). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -api_to_recv_tcp(InitState) -> +api_to_receive_tcp(InitState) -> process_flag(trap_exit, true), ServerSeq = @@ -1694,8 +1702,8 @@ api_to_recv_tcp(InitState) -> end end}, #{desc => "attempt to recv (without success)", - cmd => fun(#{sock := Sock} = _State) -> - case socket:recv(Sock, 0, 5000) of + cmd => fun(#{sock := Sock, recv := Recv} = _State) -> + case Recv(Sock) of {error, timeout} -> ok; {ok, _Data} -> @@ -1910,7 +1918,7 @@ api_to_recv_tcp(InitState) -> end end}, #{desc => "order server to terminate", - cmd => fun(#{server := Server} = State) -> + cmd => fun(#{server := Server} = _State) -> Server ! {terminate, self()}, ok end}, @@ -2067,9 +2075,10 @@ api_to_recvmsg_tcp4(doc) -> []; api_to_recvmsg_tcp4(_Config) when is_list(_Config) -> tc_begin(api_to_recvmsg_tcp4), - ok = api_to_recvmsg_tcp(inet), + Recv = fun(Sock) -> socket:recvmsg(Sock, 5000) end, + InitState = #{domain => inet, recv => Recv}, + ok = api_to_receive_tcp(InitState), tc_end(). - %% not_yet_implemented(). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -2082,60 +2091,13 @@ api_to_recvmsg_tcp6(doc) -> []; api_to_recvmsg_tcp6(_Config) when is_list(_Config) -> %% tc_begin(api_to_recvmsg_tcp6), - %% ok = api_to_recvmsg_tcp(inet6), + %% Recv = fun(Sock) -> socket:recvmsg(Sock, 5000) end, + %% InitState = #{domain => inet6, recv => Recv}, + %% ok = api_to_receive_tcp(InitState), %% tc_end(). not_yet_implemented(). -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -api_to_recvmsg_tcp(Domain) -> - process_flag(trap_exit, true), - p("server -> open"), - LSock = sock_open(Domain, stream, tcp), - LocalAddr = which_local_addr(Domain), - LocalSA = #{family => Domain, addr => LocalAddr}, - p("server -> bind"), - ServerLPort = sock_bind(LSock, LocalSA), - p("server(~w) -> listen", [ServerLPort]), - sock_listen(LSock), - ClientName = f("~s:client", [get_tc_name()]), - Client = spawn_link(fun() -> - put(sname, ClientName), - p("open"), - CSock = sock_open(Domain, stream, tcp), - p("bind"), - ClientPort = sock_bind(CSock, LocalSA), - p("[~w] connect to ~w", - [ClientPort, ServerLPort]), - sock_connect(CSock, LocalSA#{port => ServerLPort}), - p("await termination command"), - receive - die -> - p("terminating"), - exit(normal) - end - end), - p("server -> accept on ~w", [ServerLPort]), - Sock = sock_accept(LSock), - p("server -> recv"), - %% The zero (0) represents "give me everything you have" - case socket:recvmsg(Sock, 5000) of - {error, timeout} -> - p("server -> expected timeout"), - ok; - {ok, _Data} -> - ?FAIL(unexpected_success); - {error, Reason} -> - ?FAIL({recv, Reason}) - end, - Client ! die, - receive - {'EXIT', Client, _} -> - ok - end, - ok. - %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -2312,36 +2274,36 @@ sock_sockname(Sock) -> end. -sock_listen(Sock) -> - sock_listen2(fun() -> socket:listen(Sock) end). - -sock_listen(Sock, BackLog) -> - sock_listen2(fun() -> socket:listen(Sock, BackLog) end). - -sock_listen2(Listen) -> - try Listen() of - ok -> - ok; - {error, Reason} -> - ?FAIL({listen, Reason}) - catch - C:E:S -> - ?FAIL({listen, C, E, S}) - end. - - -sock_accept(LSock) -> - try socket:accept(LSock) of - {ok, Sock} -> - Sock; - {error, Reason} -> - p("sock_accept -> error: ~p", [Reason]), - ?FAIL({accept, Reason}) - catch - C:E:S -> - p("sock_accept -> failed: ~p, ~p, ~p", [C, E, S]), - ?FAIL({accept, C, E, S}) - end. +%% sock_listen(Sock) -> +%% sock_listen2(fun() -> socket:listen(Sock) end). + +%% sock_listen(Sock, BackLog) -> +%% sock_listen2(fun() -> socket:listen(Sock, BackLog) end). + +%% sock_listen2(Listen) -> +%% try Listen() of +%% ok -> +%% ok; +%% {error, Reason} -> +%% ?FAIL({listen, Reason}) +%% catch +%% C:E:S -> +%% ?FAIL({listen, C, E, S}) +%% end. + + +%% sock_accept(LSock) -> +%% try socket:accept(LSock) of +%% {ok, Sock} -> +%% Sock; +%% {error, Reason} -> +%% p("sock_accept -> error: ~p", [Reason]), +%% ?FAIL({accept, Reason}) +%% catch +%% C:E:S -> +%% p("sock_accept -> failed: ~p, ~p, ~p", [C, E, S]), +%% ?FAIL({accept, C, E, S}) +%% end. sock_close(Sock) -> @@ -2372,8 +2334,8 @@ set_tc_name(N) when is_atom(N) -> set_tc_name(N) when is_list(N) -> put(tc_name, N). -get_tc_name() -> - get(tc_name). +%% get_tc_name() -> +%% get(tc_name). tc_begin(TC) -> set_tc_name(TC), @@ -2386,8 +2348,8 @@ tc_end() -> %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -f(F, A) -> - lists:flatten(io_lib:format(F, A)). +%% f(F, A) -> +%% lists:flatten(io_lib:format(F, A)). p(F) -> p(F, []). -- cgit v1.2.3 From c4c1f1d2bd307a88c5668a489d131dc6b74bf3af Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Thu, 11 Oct 2018 19:13:31 +0200 Subject: [socket-nif|test] Wrapped each test case in try Each test case is wrapped in a try catch in the ttc_try function. It handles entry, exit and skip. --- lib/kernel/test/socket_SUITE.erl | 428 ++++++++++++++++++++++----------------- 1 file changed, 238 insertions(+), 190 deletions(-) diff --git a/lib/kernel/test/socket_SUITE.erl b/lib/kernel/test/socket_SUITE.erl index a432473f01..dc9d5240ca 100644 --- a/lib/kernel/test/socket_SUITE.erl +++ b/lib/kernel/test/socket_SUITE.erl @@ -49,8 +49,8 @@ api_to_accept_tcp6/1, api_to_send_tcp4/1, api_to_send_tcp6/1, - api_to_sendapi_to_udp4/1, - api_to_sendapi_to_udp6/1, + api_to_sendto_udp4/1, + api_to_sendto_udp6/1, api_to_sendmsg_tcp4/1, api_to_sendmsg_tcp6/1, api_to_recv_udp4/1, @@ -143,8 +143,8 @@ api_op_with_timeout_cases() -> api_to_accept_tcp6, api_to_send_tcp4, api_to_send_tcp6, - api_to_sendapi_to_udp4, - api_to_sendapi_to_udp6, + api_to_sendto_udp4, + api_to_sendto_udp6, api_to_sendmsg_tcp4, api_to_sendmsg_tcp6, api_to_recv_udp4, @@ -190,12 +190,13 @@ api_b_open_and_close_udp4(suite) -> api_b_open_and_close_udp4(doc) -> []; api_b_open_and_close_udp4(_Config) when is_list(_Config) -> - tc_begin(api_b_open_and_close_udp4), - State = #{domain => inet, - type => dgram, - protocol => udp}, - ok = api_b_open_and_close(State), - tc_end(). + tc_try(api_b_open_and_close_udp4, + fun() -> + InitState = #{domain => inet, + type => dgram, + protocol => udp}, + ok = api_b_open_and_close(InitState) + end). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -207,12 +208,13 @@ api_b_open_and_close_tcp4(suite) -> api_b_open_and_close_tcp4(doc) -> []; api_b_open_and_close_tcp4(_Config) when is_list(_Config) -> - tc_begin(api_b_open_and_close_tcp4), - State = #{domain => inet, - type => stream, - protocol => tcp}, - ok = api_b_open_and_close(State), - tc_end(). + tc_try(api_b_open_and_close_tcp4, + fun() -> + InitState = #{domain => inet, + type => stream, + protocol => tcp}, + ok = api_b_open_and_close(InitState) + end). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -317,18 +319,19 @@ api_b_sendto_and_recvfrom_udp4(suite) -> api_b_sendto_and_recvfrom_udp4(doc) -> []; api_b_sendto_and_recvfrom_udp4(_Config) when is_list(_Config) -> - tc_begin(api_b_sendto_and_recvfrom_udp4), - Send = fun(Sock, Data, Dest) -> - socket:sendto(Sock, Data, Dest) - end, - Recv = fun(Sock) -> - socket:recvfrom(Sock) - end, - InitState = #{domain => inet, - send => Send, - recv => Recv}, - ok = api_b_send_and_recv_udp(InitState), - tc_end(). + tc_try(api_b_sendto_and_recvfrom_udp4, + fun() -> + Send = fun(Sock, Data, Dest) -> + socket:sendto(Sock, Data, Dest) + end, + Recv = fun(Sock) -> + socket:recvfrom(Sock) + end, + InitState = #{domain => inet, + send => Send, + recv => Recv}, + ok = api_b_send_and_recv_udp(InitState) + end). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -340,29 +343,32 @@ api_b_sendmsg_and_recvmsg_udp4(suite) -> api_b_sendmsg_and_recvmsg_udp4(doc) -> []; api_b_sendmsg_and_recvmsg_udp4(_Config) when is_list(_Config) -> - tc_begin(api_b_sendmsg_and_recvmsg_udp4), - Send = fun(Sock, Data, Dest) -> - %% CMsgHdr = #{level => ip, type => tos, data => reliability}, - %% CMsgHdrs = [CMsgHdr], + tc_try(api_b_sendmsg_and_recvmsg_udp4, + fun() -> + Send = fun(Sock, Data, Dest) -> + %% CMsgHdr = #{level => ip, + %% type => tos, + %% data => reliability}, + %% CMsgHdrs = [CMsgHdr], MsgHdr = #{addr => Dest, %% ctrl => CMsgHdrs, iov => [Data]}, - socket:sendmsg(Sock, MsgHdr) - end, - Recv = fun(Sock) -> - case socket:recvmsg(Sock) of - {ok, #{addr := Source, - iov := [Data]}} -> - {ok, {Source, Data}}; - {error, _} = ERROR -> - ERROR - end - end, - InitState = #{domain => inet, - send => Send, - recv => Recv}, - ok = api_b_send_and_recv_udp(InitState), - tc_end(). + socket:sendmsg(Sock, MsgHdr) + end, + Recv = fun(Sock) -> + case socket:recvmsg(Sock) of + {ok, #{addr := Source, + iov := [Data]}} -> + {ok, {Source, Data}}; + {error, _} = ERROR -> + ERROR + end + end, + InitState = #{domain => inet, + send => Send, + recv => Recv}, + ok = api_b_send_and_recv_udp(InitState) + end). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -451,18 +457,19 @@ api_b_send_and_recv_tcp4(suite) -> api_b_send_and_recv_tcp4(doc) -> []; api_b_send_and_recv_tcp4(_Config) when is_list(_Config) -> - tc_begin(api_b_send_and_recv_tcp4), - Send = fun(Sock, Data) -> - socket:send(Sock, Data) - end, - Recv = fun(Sock) -> - socket:recv(Sock) - end, - InitState = #{domain => inet, - send => Send, - recv => Recv}, - ok = api_b_send_and_recv_tcp(InitState), - tc_end(). + tc_try(api_b_send_and_recv_tcp4, + fun() -> + Send = fun(Sock, Data) -> + socket:send(Sock, Data) + end, + Recv = fun(Sock) -> + socket:recv(Sock) + end, + InitState = #{domain => inet, + send => Send, + recv => Recv}, + ok = api_b_send_and_recv_tcp(InitState) + end). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -474,25 +481,26 @@ api_b_sendmsg_and_recvmsg_tcp4(suite) -> api_b_sendmsg_and_recvmsg_tcp4(doc) -> []; api_b_sendmsg_and_recvmsg_tcp4(_Config) when is_list(_Config) -> - tc_begin(api_b_sendmsg_and_recvmsg_tcp4), - Send = fun(Sock, Data) -> - MsgHdr = #{iov => [Data]}, - socket:sendmsg(Sock, MsgHdr) - end, - Recv = fun(Sock) -> - case socket:recvmsg(Sock) of - {ok, #{addr := undefined, - iov := [Data]}} -> - {ok, Data}; - {error, _} = ERROR -> - ERROR - end - end, - InitState = #{domain => inet, - send => Send, - recv => Recv}, - ok = api_b_send_and_recv_tcp(InitState), - tc_end(). + tc_try(api_b_sendmsg_and_recvmsg_tcp4, + fun() -> + Send = fun(Sock, Data) -> + MsgHdr = #{iov => [Data]}, + socket:sendmsg(Sock, MsgHdr) + end, + Recv = fun(Sock) -> + case socket:recvmsg(Sock) of + {ok, #{addr := undefined, + iov := [Data]}} -> + {ok, Data}; + {error, _} = ERROR -> + ERROR + end + end, + InitState = #{domain => inet, + send => Send, + recv => Recv}, + ok = api_b_send_and_recv_tcp(InitState) + end). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -650,8 +658,10 @@ api_opt_simple_otp_options(suite) -> api_opt_simple_otp_options(doc) -> []; api_opt_simple_otp_options(_Config) when is_list(_Config) -> - tc_begin(api_opt_simple_otp_options), + tc_try(api_opt_simple_otp_options, + fun() -> api_opt_simple_otp_options() end). +api_opt_simple_otp_options() -> Get = fun(S, Key) -> socket:getopt(S, otp, Key) end, @@ -897,9 +907,7 @@ api_opt_simple_otp_options(_Config) when is_list(_Config) -> InitState2 = #{domain => inet, type => dgram, protocol => udp}, Tester2 = evaluator_start("udp-tester", Seq, InitState2), p("await evaluator 2"), - ok = await_evaluator_finish([Tester2]), - - tc_end(). + ok = await_evaluator_finish([Tester2]). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -910,8 +918,10 @@ api_opt_simple_otp_controlling_process(suite) -> api_opt_simple_otp_controlling_process(doc) -> []; api_opt_simple_otp_controlling_process(_Config) when is_list(_Config) -> - tc_begin(api_opt_simple_otp_controlling_process), + tc_try(api_opt_simple_otp_controlling_process, + fun() -> api_opt_simple_otp_controlling_process() end). +api_opt_simple_otp_controlling_process() -> Get = fun(S, Key) -> socket:getopt(S, otp, Key) end, @@ -1147,9 +1157,8 @@ api_opt_simple_otp_controlling_process(_Config) when is_list(_Config) -> client => Client2}, Tester2 = evaluator_start("udp-tester", TesterSeq, TesterInitState2), p("await dgram/udp evaluator"), - ok = await_evaluator_finish([Tester2, Client2]), + ok = await_evaluator_finish([Tester2, Client2]). - tc_end(). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -1161,11 +1170,11 @@ api_to_connect_tcp4(suite) -> api_to_connect_tcp4(doc) -> []; api_to_connect_tcp4(_Config) when is_list(_Config) -> - tc_begin(api_to_connect_tcp4), - InitState = #{domain => inet}, - ok = api_to_connect_tcp(InitState), - tc_end(). - %% not_yet_implemented(). + tc_try(api_to_connect_tcp4, + fun() -> + InitState = #{domain => inet}, + ok = api_to_connect_tcp(InitState) + end). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -1177,11 +1186,12 @@ api_to_connect_tcp6(suite) -> api_to_connect_tcp6(doc) -> []; api_to_connect_tcp6(_Config) when is_list(_Config) -> - %% tc_begin(api_to_connect_tcp6), - %% InitState = #{domain => inet6}, - %% ok = api_to_connect_tcp(InitState), - %% tc_end(). - not_yet_implemented(). + tc_try(api_to_connect_tcp6, + fun() -> + not_yet_implemented(), + InitState = #{domain => inet6}, + ok = api_to_connect_tcp(InitState) + end). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -1429,7 +1439,7 @@ api_to_connect_tcp_await_timeout([Sock|Socks], ServerSA, ID) -> ee("failed connecting: ~p", [Reason]), ?FAIL({connect, Reason}); ok -> - ei("unexpected success (~w) - try next", [ID]), + ei("unexpected success (~w) - try next", [ID]), api_to_connect_tcp_await_timeout(Socks, ServerSA, ID+1) end. @@ -1444,10 +1454,11 @@ api_to_accept_tcp4(suite) -> api_to_accept_tcp4(doc) -> []; api_to_accept_tcp4(_Config) when is_list(_Config) -> - %% tc_begin(api_to_accept_tcp4), - %% ok = api_to_accept_tcp(inet), - %% tc_end(). - not_yet_implemented(). + tc_try(api_to_accept_tcp4, + fun() -> + not_yet_implemented()%% , + %% ok = api_to_accept_tcp(inet) + end). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -1459,10 +1470,11 @@ api_to_accept_tcp6(suite) -> api_to_accept_tcp6(doc) -> []; api_to_accept_tcp6(_Config) when is_list(_Config) -> - %% tc_begin(api_to_accept_tcp6), - %% ok = api_to_accept_tcp(inet6), - %% tc_end(). - not_yet_implemented(). + tc_try(api_to_accept_tcp4, + fun() -> + not_yet_implemented()%% , + %% ok = api_to_accept_tcp(inet6) + end). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -1474,10 +1486,11 @@ api_to_send_tcp4(suite) -> api_to_send_tcp4(doc) -> []; api_to_send_tcp4(_Config) when is_list(_Config) -> - %% tc_begin(api_to_send_tcp4), - %% ok = api_to_send_tcp(inet), - %% tc_end(). - not_yet_implemented(). + tc_try(api_to_send_tcp4, + fun() -> + not_yet_implemented()%% , + %% ok = api_to_send_tcp(inet) + end). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -1489,40 +1502,43 @@ api_to_send_tcp6(suite) -> api_to_send_tcp6(doc) -> []; api_to_send_tcp6(_Config) when is_list(_Config) -> - %% tc_begin(api_to_send_tcp6), - %% ok = api_to_send_tcp(inet6), - %% tc_end(). - not_yet_implemented(). + tc_try(api_to_send_tcp6, + fun() -> + not_yet_implemented()%% , + %% ok = api_to_send_tcp(inet6) + end). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% This test case is intended to test the sendto timeout option %% on an IPv4 UDP (dgram) socket. -api_to_sendapi_to_udp4(suite) -> +api_to_sendto_udp4(suite) -> []; -api_to_sendapi_to_udp4(doc) -> +api_to_sendto_udp4(doc) -> []; -api_to_sendapi_to_udp4(_Config) when is_list(_Config) -> - %% tc_begin(api_to_sendapi_to_udp4), - %% ok = api_to_sendapi_to_udp(inet), - %% tc_end(). - not_yet_implemented(). +api_to_sendto_udp4(_Config) when is_list(_Config) -> + tc_try(api_to_sendto_udp4, + fun() -> + not_yet_implemented()%% , + %% ok = api_to_sendto_to_udp(inet) + end). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% This test case is intended to test the sendto timeout option %% on an IPv6 UDP (dgram) socket. -api_to_sendapi_to_udp6(suite) -> +api_to_sendto_udp6(suite) -> []; -api_to_sendapi_to_udp6(doc) -> +api_to_sendto_udp6(doc) -> []; -api_to_sendapi_to_udp6(_Config) when is_list(_Config) -> - %% tc_begin(api_to_sendapi_to_udp6), - %% ok = api_to_sendapi_to_udp(inet6), - %% tc_end(). - not_yet_implemented(). +api_to_sendto_udp6(_Config) when is_list(_Config) -> + tc_try(api_to_sendto_udp6, + fun() -> + not_yet_implemented()%% , + %% ok = api_to_sendto_to_udp(inet6) + end). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -1534,10 +1550,11 @@ api_to_sendmsg_tcp4(suite) -> api_to_sendmsg_tcp4(doc) -> []; api_to_sendmsg_tcp4(_Config) when is_list(_Config) -> - %% tc_begin(api_to_sendmsg_tcp4), - %% ok = api_to_sendmsg_tcp(inet), - %% tc_end(). - not_yet_implemented(). + tc_try(api_to_sendmsg_tcp4, + fun() -> + not_yet_implemented()%% , + %% ok = api_to_sendmsg_tcp(inet) + end). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -1549,10 +1566,11 @@ api_to_sendmsg_tcp6(suite) -> api_to_sendmsg_tcp6(doc) -> []; api_to_sendmsg_tcp6(_Config) when is_list(_Config) -> - %% tc_begin(api_to_sendmsg_tcp6), - %% ok = api_to_sendmsg_tcp(inet6), - %% tc_end(). - not_yet_implemented(). + tc_try(api_to_sendmsg_tcp6, + fun() -> + not_yet_implemented()%% , + %% ok = api_to_sendmsg_tcp(inet6) + end). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -1565,10 +1583,11 @@ api_to_recv_udp4(suite) -> api_to_recv_udp4(doc) -> []; api_to_recv_udp4(_Config) when is_list(_Config) -> - %% tc_begin(api_to_recv_udp4), - %% ok = api_to_recv_udp(inet), - %% tc_end(). - not_yet_implemented(). + tc_try(api_to_recv_udp4, + fun() -> + not_yet_implemented()%%, + %%ok = api_to_recv_udp(inet) + end). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -1581,10 +1600,11 @@ api_to_recv_udp6(suite) -> api_to_recv_udp6(doc) -> []; api_to_recv_udp6(_Config) when is_list(_Config) -> - %% tc_begin(api_to_recv_udp6), - %% ok = api_to_recv_udp(inet6), - %% tc_end(). - not_yet_implemented(). + tc_try(api_to_recv_udp6, + fun() -> + not_yet_implemented()%% , + %% ok = api_to_recv_udp(inet6) + end). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -1596,11 +1616,12 @@ api_to_recv_tcp4(suite) -> api_to_recv_tcp4(doc) -> []; api_to_recv_tcp4(_Config) when is_list(_Config) -> - tc_begin(api_to_recv_tcp4), - Recv = fun(Sock) -> socket:recv(Sock, 0, 5000) end, - InitState = #{domain => inet, recv => Recv}, - ok = api_to_receive_tcp(InitState), - tc_end(). + tc_try(api_to_recv_tcp4, + fun() -> + Recv = fun(Sock) -> socket:recv(Sock, 0, 5000) end, + InitState = #{domain => inet, recv => Recv}, + ok = api_to_receive_tcp(InitState) + end). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -1612,18 +1633,20 @@ api_to_recv_tcp6(suite) -> api_to_recv_tcp6(doc) -> []; api_to_recv_tcp6(_Config) when is_list(_Config) -> - %% tc_begin(api_to_recv_tcp6), - %% Res = case socket:supports(ipv6) of - %% true -> - %% Recv = fun(Sock) -> socket:recv(Sock, 0, 5000) end, - %% InitState = #{domain => inet6, recv => Recv}, - %% ok = api_to_receive_tcp(InitState); - %% false -> - %% {skip, ipv6_not_supported} - %% end, - %% tc_end(), - %% Res. - not_yet_implemented(). + tc_try(api_to_recv_tcp6, + fun() -> + not_yet_implemented(), + case socket:supports(ipv6) of + true -> + Recv = fun(Sock) -> + socket:recv(Sock, 0, 5000) + end, + InitState = #{domain => inet6, recv => Recv}, + ok = api_to_receive_tcp(InitState); + false -> + skip("ipv6 not supported") + end + end). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -1967,9 +1990,10 @@ api_to_recvfrom_udp4(suite) -> api_to_recvfrom_udp4(doc) -> []; api_to_recvfrom_udp4(_Config) when is_list(_Config) -> - tc_begin(api_to_recvfrom_udp4), - ok = api_to_recvfrom_udp(inet), - tc_end(). + tc_try(api_to_recvfrom_udp4, + fun() -> + ok = api_to_recvfrom_udp(inet) + end). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -1981,10 +2005,11 @@ api_to_recvfrom_udp6(suite) -> api_to_recvfrom_udp6(doc) -> []; api_to_recvfrom_udp6(_Config) when is_list(_Config) -> - %% tc_begin(api_to_recvfrom_udp6), - %% ok = api_to_recvfrom_udp(inet6), - %% tc_end(). - not_yet_implemented(). + tc_try(api_to_recvfrom_udp6, + fun() -> + not_yet_implemented(), + ok = api_to_recvfrom_udp(inet6) + end). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -2020,10 +2045,10 @@ api_to_recvmsg_udp4(suite) -> api_to_recvmsg_udp4(doc) -> []; api_to_recvmsg_udp4(_Config) when is_list(_Config) -> - %% not_yet_implemented(). - tc_begin(api_to_recvmsg_udp4), - ok = api_to_recvmsg_udp(inet), - tc_end(). + tc_try(api_to_recvmsg_udp4, + fun() -> + ok = api_to_recvmsg_udp(inet) + end). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -2035,10 +2060,11 @@ api_to_recvmsg_udp6(suite) -> api_to_recvmsg_udp6(doc) -> []; api_to_recvmsg_udp6(_Config) when is_list(_Config) -> - %% tc_begin(api_to_recvmsg_udp6), - %% ok = api_to_recvmsg_udp(inet6), - %% tc_end(). - not_yet_implemented(). + tc_try(api_to_recvmsg_udp6, + fun() -> + not_yet_implemented(), + ok = api_to_recvmsg_udp(inet6) + end). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -2074,11 +2100,12 @@ api_to_recvmsg_tcp4(suite) -> api_to_recvmsg_tcp4(doc) -> []; api_to_recvmsg_tcp4(_Config) when is_list(_Config) -> - tc_begin(api_to_recvmsg_tcp4), - Recv = fun(Sock) -> socket:recvmsg(Sock, 5000) end, - InitState = #{domain => inet, recv => Recv}, - ok = api_to_receive_tcp(InitState), - tc_end(). + tc_try(api_to_recvmsg_tcp4, + fun() -> + Recv = fun(Sock) -> socket:recvmsg(Sock, 5000) end, + InitState = #{domain => inet, recv => Recv}, + ok = api_to_receive_tcp(InitState) + end). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -2090,13 +2117,13 @@ api_to_recvmsg_tcp6(suite) -> api_to_recvmsg_tcp6(doc) -> []; api_to_recvmsg_tcp6(_Config) when is_list(_Config) -> - %% tc_begin(api_to_recvmsg_tcp6), - %% Recv = fun(Sock) -> socket:recvmsg(Sock, 5000) end, - %% InitState = #{domain => inet6, recv => Recv}, - %% ok = api_to_receive_tcp(InitState), - %% tc_end(). - not_yet_implemented(). - + tc_try(api_to_recvmsg_tcp6, + fun() -> + not_yet_implemented(), + Recv = fun(Sock) -> socket:recvmsg(Sock, 5000) end, + InitState = #{domain => inet6, recv => Recv}, + ok = api_to_receive_tcp(InitState) + end). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -2324,7 +2351,10 @@ sock_close(Sock) -> %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% not_yet_implemented() -> - {skip, "not yet implemented"}. + skip("not yet implemented"). + +skip(Reason) -> + throw({skip, Reason}). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -2341,11 +2371,29 @@ tc_begin(TC) -> set_tc_name(TC), p("begin ***"). -tc_end() -> - p("done ***"), +tc_end(Result) when is_list(Result) -> + p("done: ~s", [Result]), ok. +tc_try(Case, Fun) when is_atom(Case) andalso is_function(Fun, 0) -> + tc_begin(Case), + try + begin + Fun(), + tc_end("ok") + end + catch + throw:{skip, _} = SKIP -> + tc_end("skipping"), + SKIP; + Class:Error:Stack -> + tc_end("failed"), + erlang:raise(Class, Error, Stack) + end. + + + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% f(F, A) -> -- cgit v1.2.3 From cb2d61a99ff3613f9b47598844a0274049859b7e Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Fri, 12 Oct 2018 10:48:00 +0200 Subject: [socket-nif|test] Reworked (IPv4) udp recvfrom timeout test case --- lib/kernel/test/socket_SUITE.erl | 88 ++++++++++++++++++++++++++++++---------- 1 file changed, 66 insertions(+), 22 deletions(-) diff --git a/lib/kernel/test/socket_SUITE.erl b/lib/kernel/test/socket_SUITE.erl index dc9d5240ca..724bbc9539 100644 --- a/lib/kernel/test/socket_SUITE.erl +++ b/lib/kernel/test/socket_SUITE.erl @@ -1992,7 +1992,8 @@ api_to_recvfrom_udp4(doc) -> api_to_recvfrom_udp4(_Config) when is_list(_Config) -> tc_try(api_to_recvfrom_udp4, fun() -> - ok = api_to_recvfrom_udp(inet) + InitState = #{domain => inet}, + ok = api_to_recvfrom_udp(InitState) end). @@ -2008,32 +2009,75 @@ api_to_recvfrom_udp6(_Config) when is_list(_Config) -> tc_try(api_to_recvfrom_udp6, fun() -> not_yet_implemented(), - ok = api_to_recvfrom_udp(inet6) + InitState = #{domain => inet6}, + ok = api_to_recvfrom_udp(InitState) end). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -api_to_recvfrom_udp(Domain) -> - process_flag(trap_exit, true), - p("init"), - LocalAddr = which_local_addr(Domain), - LocalSA = #{family => Domain, addr => LocalAddr}, - p("open"), - Sock = sock_open(Domain, dgram, udp), - p("bind"), - _Port = sock_bind(Sock, LocalSA), - p("recv"), - case socket:recvfrom(Sock, 0, 5000) of - {error, timeout} -> - p("expected timeout"), - ok; - {ok, _SrcData} -> - ?FAIL(unexpected_success); - {error, Reason} -> - ?FAIL({recv, Reason}) - end, - ok. +api_to_recvfrom_udp(InitState) -> + TesterSeq = + [ + %% *** Init part *** + #{desc => "which local address", + cmd => fun(#{domain := Domain} = State) -> + LAddr = which_local_addr(Domain), + LSA = #{family => Domain, addr => LAddr}, + {ok, State#{lsa => LSA}} + end}, + #{desc => "create socket", + cmd => fun(#{domain := Domain} = State) -> + case socket:open(Domain, dgram, udp) of + {ok, Sock} -> + {ok, State#{sock => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "bind to local address", + cmd => fun(#{sock := Sock, lsa := LSA} = _State) -> + case socket:bind(Sock, LSA) of + {ok, _Port} -> + ok; + {error, _} = ERROR -> + ERROR + end + end}, + + %% *** The actual test *** + #{desc => "attempt to read (without success)", + cmd => fun(#{sock := Sock} = _State) -> + case socket:recvfrom(Sock, 0, 5000) of + {error, timeout} -> + ok; + {ok, _SrcData} -> + {error, unexpected_sucsess}; + {error, _} = ERROR -> + ERROR + end + end}, + + %% *** Termination *** + #{desc => "close socket", + cmd => fun(#{sock := Sock} = _State) -> + sock_close(Sock), + ok + end}, + + %% *** We are done *** + #{desc => "finish", + cmd => fun(_) -> + {ok, normal} + end} + ], + + p("start tester evaluator"), + Tester = evaluator_start("tester", TesterSeq, InitState), + + p("await evaluator"), + ok = await_evaluator_finish([Tester]). + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -- cgit v1.2.3 From 227eb3efe1cd42324c315dc14d9945ffcd935069 Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Fri, 12 Oct 2018 11:05:26 +0200 Subject: [socket-nif|test] Reworked (IPv4) udp recvmsg timeout test case --- lib/kernel/test/socket_SUITE.erl | 63 ++++++++++++++++++---------------------- 1 file changed, 29 insertions(+), 34 deletions(-) diff --git a/lib/kernel/test/socket_SUITE.erl b/lib/kernel/test/socket_SUITE.erl index 724bbc9539..82d79ef32b 100644 --- a/lib/kernel/test/socket_SUITE.erl +++ b/lib/kernel/test/socket_SUITE.erl @@ -1992,8 +1992,9 @@ api_to_recvfrom_udp4(doc) -> api_to_recvfrom_udp4(_Config) when is_list(_Config) -> tc_try(api_to_recvfrom_udp4, fun() -> - InitState = #{domain => inet}, - ok = api_to_recvfrom_udp(InitState) + Recv = fun(Sock) -> socket:recvfrom(Sock, 0, 5000) end, + InitState = #{domain => inet, recv => Recv}, + ok = api_to_receive_udp(InitState) end). @@ -2009,14 +2010,15 @@ api_to_recvfrom_udp6(_Config) when is_list(_Config) -> tc_try(api_to_recvfrom_udp6, fun() -> not_yet_implemented(), - InitState = #{domain => inet6}, - ok = api_to_recvfrom_udp(InitState) + Recv = fun(Sock) -> socket:recvfrom(Sock, 0, 5000) end, + InitState = #{domain => inet6, recv => Recv}, + ok = api_to_receive_udp(InitState) end). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -api_to_recvfrom_udp(InitState) -> +api_to_receive_udp(InitState) -> TesterSeq = [ %% *** Init part *** @@ -2047,11 +2049,11 @@ api_to_recvfrom_udp(InitState) -> %% *** The actual test *** #{desc => "attempt to read (without success)", - cmd => fun(#{sock := Sock} = _State) -> - case socket:recvfrom(Sock, 0, 5000) of + cmd => fun(#{sock := Sock, recv := Recv} = _State) -> + case Recv(Sock) of {error, timeout} -> ok; - {ok, _SrcData} -> + {ok, _} -> {error, unexpected_sucsess}; {error, _} = ERROR -> ERROR @@ -2091,7 +2093,9 @@ api_to_recvmsg_udp4(doc) -> api_to_recvmsg_udp4(_Config) when is_list(_Config) -> tc_try(api_to_recvmsg_udp4, fun() -> - ok = api_to_recvmsg_udp(inet) + Recv = fun(Sock) -> socket:recvmsg(Sock, 5000) end, + InitState = #{domain => inet, recv => Recv}, + ok = api_to_receive_udp(InitState) end). @@ -2107,34 +2111,12 @@ api_to_recvmsg_udp6(_Config) when is_list(_Config) -> tc_try(api_to_recvmsg_udp6, fun() -> not_yet_implemented(), - ok = api_to_recvmsg_udp(inet6) + Recv = fun(Sock) -> socket:recvmsg(Sock, 5000) end, + InitState = #{domain => inet6, recv => Recv}, + ok = api_to_receive_udp(InitState) end). -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -api_to_recvmsg_udp(Domain) -> - process_flag(trap_exit, true), - p("init"), - LocalAddr = which_local_addr(Domain), - LocalSA = #{family => Domain, addr => LocalAddr}, - p("open"), - Sock = sock_open(Domain, dgram, udp), - p("bind"), - _Port = sock_bind(Sock, LocalSA), - p("recv"), - case socket:recvmsg(Sock, 5000) of - {error, timeout} -> - p("expected timeout"), - ok; - {ok, _MsgHdr} -> - ?FAIL(unexpected_success); - {error, Reason} -> - ?FAIL({recv, Reason}) - end, - ok. - - %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% This test case is intended to test the recvmsg timeout option @@ -2401,6 +2383,19 @@ skip(Reason) -> throw({skip, Reason}). +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% t() -> +%% os:timestamp(). + + +%% tdiff({A1, B1, C1} = _T1x, {A2, B2, C2} = _T2x) -> +%% T1 = A1*1000000000+B1*1000+(C1 div 1000), +%% T2 = A2*1000000000+B2*1000+(C2 div 1000), +%% T2 - T1. + + + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% set_tc_name(N) when is_atom(N) -> -- cgit v1.2.3 From 22336422d7a014db53dcba7ff1b6e71a1729d89a Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Fri, 12 Oct 2018 11:59:16 +0200 Subject: [socket-nif|test] Add accept timeout test case and update other timeout test cases(s) Added simple accept timeout testcase. Updated timeout test cases(s) with a timeout validation. OTP-14831 --- lib/kernel/test/socket_SUITE.erl | 249 ++++++++++++++++++++++++++++++--------- 1 file changed, 196 insertions(+), 53 deletions(-) diff --git a/lib/kernel/test/socket_SUITE.erl b/lib/kernel/test/socket_SUITE.erl index 82d79ef32b..de6a20e60b 100644 --- a/lib/kernel/test/socket_SUITE.erl +++ b/lib/kernel/test/socket_SUITE.erl @@ -1172,7 +1172,7 @@ api_to_connect_tcp4(doc) -> api_to_connect_tcp4(_Config) when is_list(_Config) -> tc_try(api_to_connect_tcp4, fun() -> - InitState = #{domain => inet}, + InitState = #{domain => inet, timeout => 5000}, ok = api_to_connect_tcp(InitState) end). @@ -1189,7 +1189,7 @@ api_to_connect_tcp6(_Config) when is_list(_Config) -> tc_try(api_to_connect_tcp6, fun() -> not_yet_implemented(), - InitState = #{domain => inet6}, + InitState = #{domain => inet6, timeout => 5000}, ok = api_to_connect_tcp(InitState) end). @@ -1352,12 +1352,13 @@ api_to_connect_tcp(InitState) -> %% *** Connect sequence *** #{desc => "order (server) start", - cmd => fun(#{sock1 := Sock1, - sock2 := Sock2, - sock3 := Sock3, - ssa := SSA}) -> + cmd => fun(#{sock1 := Sock1, + sock2 := Sock2, + sock3 := Sock3, + ssa := SSA, + timeout := To}) -> Socks = [Sock1, Sock2, Sock3], - api_to_connect_tcp_await_timeout(Socks, SSA) + api_to_connect_tcp_await_timeout(Socks, To, SSA) end}, %% *** Terminate server *** @@ -1417,21 +1418,28 @@ api_to_connect_tcp(InitState) -> Tester = evaluator_start("tester", TesterSeq, TesterInitState), p("await evaluator(s)"), - ok = await_evaluator_finish([Server, Tester]), - ok. + ok = await_evaluator_finish([Server, Tester]). -api_to_connect_tcp_await_timeout(Socks, ServerSA) -> - api_to_connect_tcp_await_timeout(Socks, ServerSA, 1). +api_to_connect_tcp_await_timeout(Socks, To, ServerSA) -> + api_to_connect_tcp_await_timeout(Socks, To, ServerSA, 1). -api_to_connect_tcp_await_timeout([], _ServerSA, _ID) -> +api_to_connect_tcp_await_timeout([], _To, _ServerSA, _ID) -> ?FAIL(unexpected_success); -api_to_connect_tcp_await_timeout([Sock|Socks], ServerSA, ID) -> +api_to_connect_tcp_await_timeout([Sock|Socks], To, ServerSA, ID) -> ei("~w: try connect", [ID]), - case socket:connect(Sock, ServerSA, 5000) of + Start = t(), + case socket:connect(Sock, ServerSA, To) of {error, timeout} -> ei("expected timeout (~w)", [ID]), - ok; + Stop = t(), + TDiff = tdiff(Start, Stop), + if + (TDiff >= To) -> + ok; + true -> + {error, {unexpected_timeout, TDiff, To}} + end; {error, econnreset = Reason} -> ei("failed connecting: ~p - giving up", [Reason]), ok; @@ -1440,7 +1448,7 @@ api_to_connect_tcp_await_timeout([Sock|Socks], ServerSA, ID) -> ?FAIL({connect, Reason}); ok -> ei("unexpected success (~w) - try next", [ID]), - api_to_connect_tcp_await_timeout(Socks, ServerSA, ID+1) + api_to_connect_tcp_await_timeout(Socks, To, ServerSA, ID+1) end. @@ -1456,8 +1464,8 @@ api_to_accept_tcp4(doc) -> api_to_accept_tcp4(_Config) when is_list(_Config) -> tc_try(api_to_accept_tcp4, fun() -> - not_yet_implemented()%% , - %% ok = api_to_accept_tcp(inet) + InitState = #{domain => inet, timeout => 5000}, + ok = api_to_accept_tcp(InitState) end). @@ -1472,11 +1480,94 @@ api_to_accept_tcp6(doc) -> api_to_accept_tcp6(_Config) when is_list(_Config) -> tc_try(api_to_accept_tcp4, fun() -> - not_yet_implemented()%% , - %% ok = api_to_accept_tcp(inet6) + not_yet_implemented(), + InitState = #{domain => inet6, timeout => 5000}, + ok = api_to_accept_tcp(InitState) end). +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +api_to_accept_tcp(InitState) -> + TesterSeq = + [ + %% *** Init part *** + #{desc => "which local address", + cmd => fun(#{domain := Domain} = State) -> + LAddr = which_local_addr(Domain), + LSA = #{family => Domain, addr => LAddr}, + {ok, State#{lsa => LSA}} + end}, + #{desc => "create (listen) socket", + cmd => fun(#{domain := Domain} = State) -> + case socket:open(Domain, stream, tcp) of + {ok, Sock} -> + {ok, State#{lsock => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "bind to local address", + cmd => fun(#{lsock := LSock, lsa := LSA} = _State) -> + case socket:bind(LSock, LSA) of + {ok, _} -> + ok; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "make listen socket", + cmd => fun(#{lsock := LSock}) -> + socket:listen(LSock) + end}, + + %% *** The actual test part *** + #{desc => "attempt to accept (without success)", + cmd => fun(#{lsock := LSock, timeout := To} = State) -> + Start = t(), + case socket:accept(LSock, To) of + {error, timeout} -> + {ok, State#{start => Start, stop => t()}}; + {ok, Sock} -> + (catch socket:close(Sock)), + {error, unexpected_success}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "validate timeout time", + cmd => fun(#{start := Start, stop := Stop, timeout := To} = _State) -> + TDiff = tdiff(Start, Stop), + if + (TDiff >= To) -> + ok; + true -> + {error, {unexpected_timeout, TDiff, To}} + end + end}, + + %% *** Close (listen) socket *** + #{desc => "close (listen) socket", + cmd => fun(#{lsock := LSock} = State) -> + sock_close(LSock), + {ok, maps:remove(sock3, State)} + end}, + + %% *** We are done *** + #{desc => "finish", + cmd => fun(_) -> + {ok, normal} + end} + ], + + p("create tester evaluator"), + Tester = evaluator_start("tester", TesterSeq, InitState), + + p("await evaluator"), + ok = await_evaluator_finish([Tester]). + + + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% This test case is intended to test the send timeout option @@ -1618,8 +1709,10 @@ api_to_recv_tcp4(doc) -> api_to_recv_tcp4(_Config) when is_list(_Config) -> tc_try(api_to_recv_tcp4, fun() -> - Recv = fun(Sock) -> socket:recv(Sock, 0, 5000) end, - InitState = #{domain => inet, recv => Recv}, + Recv = fun(Sock, To) -> socket:recv(Sock, 0, To) end, + InitState = #{domain => inet, + recv => Recv, + timeout => 5000}, ok = api_to_receive_tcp(InitState) end). @@ -1638,10 +1731,12 @@ api_to_recv_tcp6(_Config) when is_list(_Config) -> not_yet_implemented(), case socket:supports(ipv6) of true -> - Recv = fun(Sock) -> - socket:recv(Sock, 0, 5000) + Recv = fun(Sock, To) -> + socket:recv(Sock, 0, To) end, - InitState = #{domain => inet6, recv => Recv}, + InitState = #{domain => inet6, + recv => Recv, + timeout => 5000}, ok = api_to_receive_tcp(InitState); false -> skip("ipv6 not supported") @@ -1725,16 +1820,27 @@ api_to_receive_tcp(InitState) -> end end}, #{desc => "attempt to recv (without success)", - cmd => fun(#{sock := Sock, recv := Recv} = _State) -> - case Recv(Sock) of + cmd => fun(#{sock := Sock, recv := Recv, timeout := To} = State) -> + Start = t(), + case Recv(Sock, To) of {error, timeout} -> - ok; + {ok, State#{start => Start, stop => t()}}; {ok, _Data} -> {error, unexpected_success}; {error, _} = ERROR -> ERROR end end}, + #{desc => "validate timeout time", + cmd => fun(#{start := Start, stop := Stop, timeout := To} = _State) -> + TDiff = tdiff(Start, Stop), + if + (TDiff >= To) -> + ok; + true -> + {error, {unexpected_timeout, TDiff, To}} + end + end}, #{desc => "announce ready (recv timeout success)", cmd => fun(#{tester := Tester} = _State) -> Tester ! {ready, self()}, @@ -1992,8 +2098,10 @@ api_to_recvfrom_udp4(doc) -> api_to_recvfrom_udp4(_Config) when is_list(_Config) -> tc_try(api_to_recvfrom_udp4, fun() -> - Recv = fun(Sock) -> socket:recvfrom(Sock, 0, 5000) end, - InitState = #{domain => inet, recv => Recv}, + Recv = fun(Sock, To) -> socket:recvfrom(Sock, 0, To) end, + InitState = #{domain => inet, + recv => Recv, + timeout => 5000}, ok = api_to_receive_udp(InitState) end). @@ -2010,8 +2118,10 @@ api_to_recvfrom_udp6(_Config) when is_list(_Config) -> tc_try(api_to_recvfrom_udp6, fun() -> not_yet_implemented(), - Recv = fun(Sock) -> socket:recvfrom(Sock, 0, 5000) end, - InitState = #{domain => inet6, recv => Recv}, + Recv = fun(Sock, To) -> socket:recvfrom(Sock, 0, To) end, + InitState = #{domain => inet6, + recv => Recv, + timeout => 5000}, ok = api_to_receive_udp(InitState) end). @@ -2049,17 +2159,28 @@ api_to_receive_udp(InitState) -> %% *** The actual test *** #{desc => "attempt to read (without success)", - cmd => fun(#{sock := Sock, recv := Recv} = _State) -> - case Recv(Sock) of + cmd => fun(#{sock := Sock, recv := Recv, timeout := To} = State) -> + Start = t(), + case Recv(Sock, To) of {error, timeout} -> - ok; + {ok, State#{start => Start, stop => t()}}; {ok, _} -> {error, unexpected_sucsess}; {error, _} = ERROR -> ERROR end end}, - + #{desc => "validate timeout time", + cmd => fun(#{start := Start, stop := Stop, timeout := To} = _State) -> + TDiff = tdiff(Start, Stop), + if + (TDiff >= To) -> + ok; + true -> + {error, {unexpected_timeout, TDiff, To}} + end + end}, + %% *** Termination *** #{desc => "close socket", cmd => fun(#{sock := Sock} = _State) -> @@ -2093,8 +2214,10 @@ api_to_recvmsg_udp4(doc) -> api_to_recvmsg_udp4(_Config) when is_list(_Config) -> tc_try(api_to_recvmsg_udp4, fun() -> - Recv = fun(Sock) -> socket:recvmsg(Sock, 5000) end, - InitState = #{domain => inet, recv => Recv}, + Recv = fun(Sock, To) -> socket:recvmsg(Sock, To) end, + InitState = #{domain => inet, + recv => Recv, + timeout => 5000}, ok = api_to_receive_udp(InitState) end). @@ -2111,8 +2234,10 @@ api_to_recvmsg_udp6(_Config) when is_list(_Config) -> tc_try(api_to_recvmsg_udp6, fun() -> not_yet_implemented(), - Recv = fun(Sock) -> socket:recvmsg(Sock, 5000) end, - InitState = #{domain => inet6, recv => Recv}, + Recv = fun(Sock, To) -> socket:recvmsg(Sock, To) end, + InitState = #{domain => inet6, + recv => Recv, + timeout => 5000}, ok = api_to_receive_udp(InitState) end). @@ -2128,8 +2253,10 @@ api_to_recvmsg_tcp4(doc) -> api_to_recvmsg_tcp4(_Config) when is_list(_Config) -> tc_try(api_to_recvmsg_tcp4, fun() -> - Recv = fun(Sock) -> socket:recvmsg(Sock, 5000) end, - InitState = #{domain => inet, recv => Recv}, + Recv = fun(Sock, To) -> socket:recvmsg(Sock, To) end, + InitState = #{domain => inet, + recv => Recv, + timeout => 5000}, ok = api_to_receive_tcp(InitState) end). @@ -2146,8 +2273,10 @@ api_to_recvmsg_tcp6(_Config) when is_list(_Config) -> tc_try(api_to_recvmsg_tcp6, fun() -> not_yet_implemented(), - Recv = fun(Sock) -> socket:recvmsg(Sock, 5000) end, - InitState = #{domain => inet6, recv => Recv}, + Recv = fun(Sock, To) -> socket:recvmsg(Sock, To) end, + InitState = #{domain => inet6, + recv => Recv, + timeout => 5000}, ok = api_to_receive_tcp(InitState) end). @@ -2273,7 +2402,8 @@ ee(F, A) -> eprint(" ", F, A). eprint(Prefix, F, A) -> - io:format(user, "[~s][~p] ~s" ++ F ++ "~n", [get(sname), self(), Prefix | A]). + io:format(user, "[~s][~s][~p] ~s" ++ F ++ "~n", + [formated_timestamp(), get(sname), self(), Prefix | A]). @@ -2385,17 +2515,30 @@ skip(Reason) -> %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% t() -> -%% os:timestamp(). +t() -> + os:timestamp(). -%% tdiff({A1, B1, C1} = _T1x, {A2, B2, C2} = _T2x) -> -%% T1 = A1*1000000000+B1*1000+(C1 div 1000), -%% T2 = A2*1000000000+B2*1000+(C2 div 1000), -%% T2 - T1. +tdiff({A1, B1, C1} = _T1x, {A2, B2, C2} = _T2x) -> + T1 = A1*1000000000+B1*1000+(C1 div 1000), + T2 = A2*1000000000+B2*1000+(C2 div 1000), + T2 - T1. - +formated_timestamp() -> + format_timestamp(os:timestamp()). + +format_timestamp({_N1, _N2, _N3} = TS) -> + {_Date, Time} = calendar:now_to_local_time(TS), + %% {YYYY,MM,DD} = Date, + {Hour,Min,Sec} = Time, + %% FormatTS = + %% io_lib:format("~.4w-~.2.0w-~.2.0w ~.2.0w:~.2.0w:~.2.0w.~w", + %% [YYYY, MM, DD, Hour, Min, Sec, N3]), + FormatTS = io_lib:format("~.2.0w:~.2.0w:~.2.0w", [Hour, Min, Sec]), + lists:flatten(FormatTS). + + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% set_tc_name(N) when is_atom(N) -> @@ -2454,7 +2597,7 @@ p(F, A) -> Name when is_list(Name) -> Name end, - i("*** ~s[~p] " ++ F, [TcName,self()|A]). + i("*** [~s][~s][~p] " ++ F, [formated_timestamp(),TcName,self()|A]). %% i(F) -> -- cgit v1.2.3 From 09168a9d0a78319639d62254da717090e21c47ab Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Fri, 12 Oct 2018 15:54:46 +0200 Subject: [socket-nif|test] Add multi accept timeout test case Added simple multi-accept (multiple acceptors) timeout testcase. OTP-14831 --- lib/kernel/test/socket_SUITE.erl | 436 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 436 insertions(+) diff --git a/lib/kernel/test/socket_SUITE.erl b/lib/kernel/test/socket_SUITE.erl index de6a20e60b..3f4347c52f 100644 --- a/lib/kernel/test/socket_SUITE.erl +++ b/lib/kernel/test/socket_SUITE.erl @@ -47,6 +47,8 @@ api_to_connect_tcp6/1, api_to_accept_tcp4/1, api_to_accept_tcp6/1, + api_to_maccept_tcp4/1, + api_to_maccept_tcp6/1, api_to_send_tcp4/1, api_to_send_tcp6/1, api_to_sendto_udp4/1, @@ -1568,6 +1570,440 @@ api_to_accept_tcp(InitState) -> +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% This test case is intended to test the multi accept timeout option +%% on an IPv4 TCP (stream) socket with multiple acceptor processes +%% (three in this case). +api_to_maccept_tcp4(suite) -> + []; +api_to_maccept_tcp4(doc) -> + []; +api_to_maccept_tcp4(_Config) when is_list(_Config) -> + tc_try(api_to_maccept_tcp4, + fun() -> + InitState = #{domain => inet, timeout => 5000}, + ok = api_to_maccept_tcp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% This test case is intended to test the accept timeout option +%% on an IPv6 TCP (stream) socket. +api_to_maccept_tcp6(suite) -> + []; +api_to_maccept_tcp6(doc) -> + []; +api_to_maccept_tcp6(_Config) when is_list(_Config) -> + tc_try(api_to_maccept_tcp4, + fun() -> + not_yet_implemented(), + InitState = #{domain => inet6, timeout => 5000}, + ok = api_to_maccept_tcp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +api_to_maccept_tcp(InitState) -> + PrimAcceptorSeq = + [ + %% *** Init part *** + #{desc => "await start", + cmd => fun(State) -> + receive + {start, Tester} -> + MRef = erlang:monitor(process, Tester), + {ok, State#{tester => Tester, + tester_mref => MRef}} + end + end}, + #{desc => "which local address", + cmd => fun(#{domain := Domain} = State) -> + LAddr = which_local_addr(Domain), + LSA = #{family => Domain, addr => LAddr}, + {ok, State#{lsa => LSA}} + end}, + #{desc => "create (listen) socket", + cmd => fun(#{domain := Domain} = State) -> + case socket:open(Domain, stream, tcp) of + {ok, Sock} -> + {ok, State#{lsock => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "bind to local address", + cmd => fun(#{lsock := LSock, lsa := LSA} = _State) -> + case socket:bind(LSock, LSA) of + {ok, _} -> + ok; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "make listen socket", + cmd => fun(#{lsock := LSock}) -> + socket:listen(LSock) + end}, + + #{desc => "announce ready", + cmd => fun(#{lsock := LSock, tester := Tester}) -> + ei("announcing port to tester (~p)", [Tester]), + Tester ! {ready, self(), LSock}, + ok + end}, + #{desc => "await continue", + cmd => fun(#{tester := Tester} = _State) -> + receive + {'DOWN', _, process, Tester, Reason} -> + {error, {unexpected_exit, tester, Reason}}; + {continue, Tester} -> + ok + end + end}, + + %% *** The actual test part *** + #{desc => "attempt to accept (without success)", + cmd => fun(#{lsock := LSock, timeout := To} = State) -> + Start = t(), + case socket:accept(LSock, To) of + {error, timeout} -> + {ok, State#{start => Start, stop => t()}}; + {ok, Sock} -> + (catch socket:close(Sock)), + {error, unexpected_success}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "validate timeout time", + cmd => fun(#{start := Start, stop := Stop, timeout := To} = _State) -> + TDiff = tdiff(Start, Stop), + if + (TDiff >= To) -> + ok; + true -> + {error, {unexpected_timeout, TDiff, To}} + end + end}, + #{desc => "announce ready", + cmd => fun(#{tester := Tester}) -> + ei("announcing port to tester (~p)", [Tester]), + Tester ! {ready, self()}, + ok + end}, + #{desc => "await terminate", + cmd => fun(#{tester := Tester} = _State) -> + receive + {'DOWN', _, process, Tester, Reason} -> + {error, {unexpected_exit, tester, Reason}}; + {terminate, Tester} -> + ok + end + end}, + + %% *** Close (listen) socket *** + #{desc => "close (listen) socket", + cmd => fun(#{lsock := LSock} = State) -> + sock_close(LSock), + {ok, maps:remove(sock3, State)} + end}, + + %% *** We are done *** + #{desc => "finish", + cmd => fun(_) -> + {ok, normal} + end} + ], + + + SecAcceptorSeq = + [ + %% *** Init part *** + #{desc => "await start", + cmd => fun(State) -> + receive + {start, Tester, LSock} -> + MRef = erlang:monitor(process, Tester), + {ok, State#{tester => Tester, + lsock => LSock, + tester_mref => MRef}} + end + end}, + #{desc => "announce ready (1)", + cmd => fun(#{tester := Tester} = _State) -> + Tester ! {ready, self()}, + ok + end}, + #{desc => "await continue", + cmd => fun(#{tester := Tester} = _State) -> + receive + {'DOWN', _, process, Tester, Reason} -> + ee("Unexpected DOWN regarding tester ~p: " + "~n ~p", [Reason]), + {error, {unexpected_exit, tester, Reason}}; + {continue, Tester} -> + ok + end + end}, + + %% *** The actual test part *** + #{desc => "attempt to accept (without success)", + cmd => fun(#{lsock := LSock, timeout := To} = State) -> + Start = t(), + case socket:accept(LSock, To) of + {error, timeout} -> + {ok, State#{start => Start, stop => t()}}; + {ok, Sock} -> + (catch socket:close(Sock)), + {error, unexpected_success}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "validate timeout time", + cmd => fun(#{start := Start, stop := Stop, timeout := To} = _State) -> + TDiff = tdiff(Start, Stop), + if + (TDiff >= To) -> + ok; + true -> + {error, {unexpected_timeout, TDiff, To}} + end + end}, + #{desc => "announce ready (2)", + cmd => fun(#{tester := Tester} = _State) -> + Tester ! {ready, self()}, + ok + end}, + #{desc => "await terminate", + cmd => fun(#{tester := Tester} = _State) -> + receive + {'DOWN', _, process, Tester, Reason} -> + ee("Unexpected DOWN regarding tester ~p: " + "~n ~p", [Reason]), + {error, {unexpected_exit, tester, Reason}}; + {terminate, Tester} -> + ok + end + end}, + + %% *** We are done *** + #{desc => "finish", + cmd => fun(_) -> + {ok, normal} + end} + ], + + + TesterSeq = + [ + %% Init part + #{desc => "monitor prim-acceptor", + cmd => fun(#{prim_acceptor := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + #{desc => "monitor sec-acceptor 1", + cmd => fun(#{sec_acceptor1 := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + #{desc => "monitor sec-acceptor 2", + cmd => fun(#{sec_acceptor2 := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + + + %% Start the prim-acceptor + #{desc => "start prim-acceptor", + cmd => fun(#{prim_acceptor := Pid} = _State) -> + Pid ! {start, self()}, + ok + end}, + #{desc => "await prim-acceptor ready (1)", + cmd => fun(#{prim_acceptor := Pid} = State) -> + receive + {'DOWN', _, process, Pid, Reason} -> + ee("Unexpected DOWN regarding prim-acceptor ~p:" + "~n ~p", [Reason]), + {error, {unexpected_exit, prim_acceptor}}; + {ready, Pid, LSock} -> + {ok, State#{lsock => LSock}} + end + end}, + + %% Start sec-acceptor-1 + #{desc => "start sec-acceptor 1", + cmd => fun(#{sec_acceptor1 := Pid, lsock := LSock} = _State) -> + Pid ! {start, self(), LSock}, + ok + end}, + #{desc => "await sec-acceptor 1 ready (1)", + cmd => fun(#{sec_acceptor1 := Pid} = _State) -> + receive + {'DOWN', _, process, Pid, Reason} -> + ee("Unexpected DOWN regarding sec-acceptor 1 ~p:" + "~n ~p", [Reason]), + {error, {unexpected_exit, sec_acceptor_1}}; + {ready, Pid} -> + ok + end + end}, + + %% Start sec-acceptor-2 + #{desc => "start sec-acceptor 2", + cmd => fun(#{sec_acceptor2 := Pid, lsock := LSock} = _State) -> + Pid ! {start, self(), LSock}, + ok + end}, + #{desc => "await sec-acceptor 2 ready (1)", + cmd => fun(#{sec_acceptor2 := Pid} = _State) -> + receive + {'DOWN', _, process, Pid, Reason} -> + ee("Unexpected DOWN regarding sec-acceptor 2 ~p:" + "~n ~p", [Reason]), + {error, {unexpected_exit, sec_acceptor_2}}; + {ready, Pid} -> + ok + end + end}, + + %% Activate the acceptor(s) + #{desc => "active prim-acceptor", + cmd => fun(#{prim_acceptor := Pid} = _State) -> + Pid ! {continue, self()}, + ok + end}, + #{desc => "active sec-acceptor 1", + cmd => fun(#{sec_acceptor1 := Pid} = _State) -> + Pid ! {continue, self()}, + ok + end}, + #{desc => "active sec-acceptor 2", + cmd => fun(#{sec_acceptor2 := Pid} = _State) -> + Pid ! {continue, self()}, + ok + end}, + + %% Await acceptor(s) completions + #{desc => "await prim-acceptor ready (2)", + cmd => fun(#{prim_acceptor := Pid} = _State) -> + receive + {'DOWN', _, process, Pid, Reason} -> + ee("Unexpected DOWN regarding prim-acceptor ~p:" + "~n ~p", [Reason]), + {error, {unexpected_exit, prim_acceptor}}; + {ready, Pid} -> + ok + end + end}, + #{desc => "await sec-acceptor 1 ready (2)", + cmd => fun(#{sec_acceptor1 := Pid} = _State) -> + receive + {'DOWN', _, process, Pid, Reason} -> + ee("Unexpected DOWN regarding sec-acceptor 1 ~p:" + "~n ~p", [Reason]), + {error, {unexpected_exit, sec_acceptor_1}}; + {ready, Pid} -> + ok + end + end}, + #{desc => "await sec-acceptor 2 ready (2)", + cmd => fun(#{sec_acceptor2 := Pid} = _State) -> + receive + {'DOWN', _, process, Pid, Reason} -> + ee("Unexpected DOWN regarding sec-acceptor 2 ~p:" + "~n ~p", [Reason]), + {error, {unexpected_exit, sec_acceptor_2}}; + {ready, Pid} -> + ok + end + end}, + + + %% Terminate the acceptor(s) + #{desc => "order prim-acceptor to terminate", + cmd => fun(#{prim_acceptor := Pid} = _State) -> + ei("send terminate command to prim-acceptor (~p)", [Pid]), + Pid ! {terminate, self()}, + ok + end}, + #{desc => "order sec-acceptor 1 to terminate", + cmd => fun(#{sec_acceptor1 := Pid} = _State) -> + ei("send terminate command to sec-acceptor-1 (~p)", [Pid]), + Pid ! {terminate, self()}, + ok + end}, + #{desc => "order sec-acceptor 2 to terminate", + cmd => fun(#{sec_acceptor2 := Pid} = _State) -> + ei("send terminate command to sec-acceptor-2 (~p)", [Pid]), + Pid ! {terminate, self()}, + ok + end}, + + %% Await acceptor(s) termination + #{desc => "await prim-acceptor termination", + cmd => fun(#{prim_acceptor := Pid} = State) -> + receive + {'DOWN', _, process, Pid, _} -> + State1 = maps:remove(prim_acceptor, State), + {ok, State1} + end + end}, + #{desc => "await sec-acceptor 1 termination", + cmd => fun(#{sec_acceptor1 := Pid} = State) -> + receive + {'DOWN', _, process, Pid, _} -> + State1 = maps:remove(sec_acceptor1, State), + {ok, State1} + end + end}, + #{desc => "await sec-acceptor 2 termination", + cmd => fun(#{sec_acceptor2 := Pid} = State) -> + receive + {'DOWN', _, process, Pid, _} -> + State1 = maps:remove(sec_acceptor2, State), + {ok, State1} + end + end}, + + %% *** We are done *** + #{desc => "finish", + cmd => fun(_) -> + {ok, normal} + end} + ], + + p("create prim-acceptor evaluator"), + PrimAInitState = InitState, + PrimAcceptor = evaluator_start("prim-acceptor", + PrimAcceptorSeq, PrimAInitState), + + p("create prim-acceptor 1 evaluator"), + SecAInitState1 = maps:remove(domain, InitState), + SecAcceptor1 = evaluator_start("sec-acceptor-1", + SecAcceptorSeq, SecAInitState1), + + p("create prim-acceptor 2 evaluator"), + SecAInitState2 = SecAInitState1, + SecAcceptor2 = evaluator_start("sec-acceptor-2", + SecAcceptorSeq, SecAInitState2), + + p("create tester evaluator"), + TesterInitState = #{prim_acceptor => PrimAcceptor, + sec_acceptor1 => SecAcceptor1, + sec_acceptor2 => SecAcceptor2}, + Tester = evaluator_start("tester", TesterSeq, TesterInitState), + + p("await evaluator(s)"), + ok = await_evaluator_finish([PrimAcceptor, SecAcceptor1, SecAcceptor2, Tester]). + + + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% This test case is intended to test the send timeout option -- cgit v1.2.3 From ac1e27ce82fde99519a9cf271144179e4a2ef373 Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Fri, 12 Oct 2018 16:59:28 +0200 Subject: [socket-nif|test] Add skeletons for controlling-process test cases --- lib/kernel/test/socket_SUITE.erl | 560 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 556 insertions(+), 4 deletions(-) diff --git a/lib/kernel/test/socket_SUITE.erl b/lib/kernel/test/socket_SUITE.erl index 3f4347c52f..71e6dff174 100644 --- a/lib/kernel/test/socket_SUITE.erl +++ b/lib/kernel/test/socket_SUITE.erl @@ -64,7 +64,29 @@ api_to_recvmsg_udp4/1, api_to_recvmsg_udp6/1, api_to_recvmsg_tcp4/1, - api_to_recvmsg_tcp6/1 + api_to_recvmsg_tcp6/1, + + %% Controlling Process + socket_cleanup_tcp4/1, + socket_cleanup_tcp6/1, + socket_cleanup_udp4/1, + socket_cleanup_udp6/1, + socket_close_tcp4/1, + socket_close_tcp6/1, + socket_close_udp4/1, + socket_close_udp6/1, + recv_response_local_close_tcp4/1, + recv_response_local_close_tcp6/1, + recv_response_remote_close_tcp4/1, + recv_response_remote_close_tcp6/1, + recvmsg_response_local_close_tcp4/1, + recvmsg_response_local_close_tcp6/1, + recvmsg_response_remote_close_tcp4/1, + recvmsg_response_remote_close_tcp6/1, + acceptor_response_local_close_tcp4/1, + acceptor_response_local_close_tcp6/1, + acceptor_response_remote_close_tcp4/1, + acceptor_response_remote_close_tcp6/1 %% Tickets ]). @@ -102,15 +124,17 @@ suite() -> all() -> [ - {group, api} + {group, api}, + {group, controlling_process} %% {group, tickets} ]. groups() -> [{api, [], api_cases()}, {api_basic, [], api_basic_cases()}, + {api_options, [], api_options_cases()}, {api_op_with_timeout, [], api_op_with_timeout_cases()}, - {api_options, [], api_options_cases()} + {controlling_process, [], controlling_process_cases()} %% {tickets, [], ticket_cases()} ]. @@ -161,6 +185,34 @@ api_op_with_timeout_cases() -> api_to_recvmsg_tcp6 ]. +%% These cases tests what happens when the controlling process dies +controlling_process_cases() -> + [ + socket_cleanup_tcp4, + socket_cleanup_tcp6, + socket_cleanup_udp4, + socket_cleanup_udp6, + + socket_close_tcp4, + socket_close_tcp6, + socket_close_udp4, + socket_close_udp6, + + recv_response_local_close_tcp4, + recv_response_local_close_tcp6, + recv_response_remote_close_tcp4, + recv_response_remote_close_tcp6, + + recvmsg_response_local_close_tcp4, + recvmsg_response_local_close_tcp6, + recvmsg_response_remote_close_tcp4, + recvmsg_response_remote_close_tcp6, + + acceptor_response_local_close_tcp4, + acceptor_response_local_close_tcp6, + acceptor_response_remote_close_tcp4, + acceptor_response_remote_close_tcp6 + ]. %% ticket_cases() -> %% []. @@ -183,6 +235,14 @@ end_per_testcase(_TC, Config) -> +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% %% +%% API BASIC %% +%% %% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% Basically open (create) and close an IPv4 UDP (dgram) socket. @@ -549,7 +609,7 @@ api_b_send_and_recv_tcp(InitState) -> cmd => fun(#{lsock := LSock} = State) -> case socket:accept(LSock) of {ok, Sock} -> - ei("accepted: ~p", [Sock]), + ei("accepted: ~n ~p", [Sock]), {ok, State#{tsock => Sock}}; {error, _} = ERROR -> ERROR @@ -652,6 +712,14 @@ api_b_send_and_recv_tcp(InitState) -> +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% %% +%% API OPTIONS %% +%% %% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% Perform some simple getopt and setopt with the level = otp options @@ -1163,6 +1231,14 @@ api_opt_simple_otp_controlling_process() -> +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% %% +%% API OPERATIONS WITH TIMEOUT %% +%% %% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% This test case is intended to test the connect timeout option @@ -2717,6 +2793,482 @@ api_to_recvmsg_tcp6(_Config) when is_list(_Config) -> end). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% %% +%% CONTROLLING PROCESS %% +%% %% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test that the sockets are cleaned up +%% (removed) when the controlling process terminates (without explicitly +%% calling the close function). For a IPv4 TCP (stream) socket. + +socket_cleanup_tcp4(suite) -> + []; +socket_cleanup_tcp4(doc) -> + []; +socket_cleanup_tcp4(_Config) when is_list(_Config) -> + tc_try(socket_cleanup_tcp4, + fun() -> + not_yet_implemented(), + InitState = #{domain => inet, + type => stream, + protocol => tcp}, + ok = socket_cleanup(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test that the sockets are cleaned up +%% (removed) when the controlling process terminates (without explicitly +%% calling the close function). For a IPv6 TCP (stream) socket. + +socket_cleanup_tcp6(suite) -> + []; +socket_cleanup_tcp6(doc) -> + []; +socket_cleanup_tcp6(_Config) when is_list(_Config) -> + tc_try(socket_cleanup_tcp6, + fun() -> + not_yet_implemented(), + InitState = #{domain => inet6, + type => stream, + protocol => tcp}, + ok = socket_cleanup(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test that the sockets are cleaned up +%% (removed) when the controlling process terminates (without explicitly +%% calling the close function). For a IPv4 UDP (dgram) socket. + +socket_cleanup_udp4(suite) -> + []; +socket_cleanup_udp4(doc) -> + []; +socket_cleanup_udp4(_Config) when is_list(_Config) -> + tc_try(socket_cleanup_udp4, + fun() -> + not_yet_implemented(), + InitState = #{domain => inet, + type => dgram, + protocol => udp}, + ok = socket_cleanup(InitState) + end). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test that the sockets are cleaned up +%% (removed) when the controlling process terminates (without explicitly +%% calling the close function). For a IPv6 UDP (dgram) socket. + +socket_cleanup_udp6(suite) -> + []; +socket_cleanup_udp6(doc) -> + []; +socket_cleanup_udp6(_Config) when is_list(_Config) -> + tc_try(socket_cleanup_udp6, + fun() -> + not_yet_implemented(), + InitState = #{domain => inet6, + type => dgram, + protocol => udp}, + ok = socket_cleanup(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +socket_cleanup(_InitState) -> + ok. + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test what happens when a process other +%% than the controlling process closes a socket. +%% For a IPv4 TCP (stream) socket. + +socket_close_tcp4(suite) -> + []; +socket_close_tcp4(doc) -> + []; +socket_close_tcp4(_Config) when is_list(_Config) -> + tc_try(socket_close_tcp4, + fun() -> + not_yet_implemented(), + InitState = #{domain => inet, + type => stream, + protocol => tcp}, + ok = socket_close(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test what happens when a process other +%% than the controlling process closes a socket. +%% For a IPv6 TCP (stream) socket. + +socket_close_tcp6(suite) -> + []; +socket_close_tcp6(doc) -> + []; +socket_close_tcp6(_Config) when is_list(_Config) -> + tc_try(socket_close_tcp6, + fun() -> + not_yet_implemented(), + InitState = #{domain => inet6, + type => stream, + protocol => tcp}, + ok = socket_close(InitState) + end). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test what happens when a process other +%% than the controlling process closes a socket. +%% For a IPv4 UDP (dgram) socket. + +socket_close_udp4(suite) -> + []; +socket_close_udp4(doc) -> + []; +socket_close_udp4(_Config) when is_list(_Config) -> + tc_try(socket_close_udp4, + fun() -> + not_yet_implemented(), + InitState = #{domain => inet, + type => dgram, + protocol => udp}, + ok = socket_close(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test what happens when a process other +%% than the controlling process closes a socket. +%% For a IPv6 UDP (dgram) socket. + +socket_close_udp6(suite) -> + []; +socket_close_udp6(doc) -> + []; +socket_close_udp6(_Config) when is_list(_Config) -> + tc_try(socket_close_udp6, + fun() -> + not_yet_implemented(), + InitState = #{domain => inet6, + type => dgram, + protocol => udp}, + ok = socket_close(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +socket_close(_InitState) -> + ok. + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test what happens when a socket is +%% locally closed while the process is calling the recv function. +%% Socket is IPv4. + +recv_response_local_close_tcp4(suite) -> + []; +recv_response_local_close_tcp4(doc) -> + []; +recv_response_local_close_tcp4(_Config) when is_list(_Config) -> + tc_try(recv_response_local_close_tcp4, + fun() -> + not_yet_implemented(), + Recv = fun(Sock) -> socket:recv(Sock) end, + InitState = #{domain => inet, + type => stream, + protocol => tcp, + recv => Recv}, + ok = receive_response_local_close_tcp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test what happens when a socket is +%% locally closed while the process is calling the recv function. +%% Socket is IPv6. + +recv_response_local_close_tcp6(suite) -> + []; +recv_response_local_close_tcp6(doc) -> + []; +recv_response_local_close_tcp6(_Config) when is_list(_Config) -> + tc_try(recv_response_local_close_tcp6, + fun() -> + not_yet_implemented(), + Recv = fun(Sock) -> socket:recv(Sock) end, + InitState = #{domain => inet6, + type => stream, + protocol => tcp, + recv => Recv}, + ok = receive_response_local_close_tcp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +receive_response_local_close_tcp(_InitState) -> + ok. + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test what happens when a socket is +%% remotely closed while the process is calling the recv function. +%% Socket is IPv4. + +recv_response_remote_close_tcp4(suite) -> + []; +recv_response_remote_close_tcp4(doc) -> + []; +recv_response_remote_close_tcp4(_Config) when is_list(_Config) -> + tc_try(recv_response_remote_close_tcp4, + fun() -> + not_yet_implemented(), + Recv = fun(Sock) -> socket:recv(Sock) end, + InitState = #{domain => inet, + type => stream, + protocol => tcp, + recv => Recv}, + ok = receive_response_remote_close_tcp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test what happens when a socket is +%% remotely closed while the process is calling the recv function. +%% Socket is IPv6. + +recv_response_remote_close_tcp6(suite) -> + []; +recv_response_remote_close_tcp6(doc) -> + []; +recv_response_remote_close_tcp6(_Config) when is_list(_Config) -> + tc_try(recv_response_remote_close_tcp6, + fun() -> + not_yet_implemented(), + Recv = fun(Sock) -> socket:recv(Sock) end, + InitState = #{domain => inet6, + type => stream, + protocol => tcp, + recv => Recv}, + ok = receive_response_remote_close_tcp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +receive_response_remote_close_tcp(_InitState) -> + ok. + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test what happens when a socket is +%% locally closed while the process is calling the recvmsg function. +%% Socket is IPv4. + +recvmsg_response_local_close_tcp4(suite) -> + []; +recvmsg_response_local_close_tcp4(doc) -> + []; +recvmsg_response_local_close_tcp4(_Config) when is_list(_Config) -> + tc_try(recvmsg_response_local_close_tcp4, + fun() -> + not_yet_implemented(), + Recv = fun(Sock) -> socket:recvmsg(Sock) end, + InitState = #{domain => inet, + type => stream, + protocol => tcp, + recv => Recv}, + ok = receive_response_local_close_tcp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test what happens when a socket is +%% locally closed while the process is calling the recvmsg function. +%% Socket is IPv6. + +recvmsg_response_local_close_tcp6(suite) -> + []; +recvmsg_response_local_close_tcp6(doc) -> + []; +recvmsg_response_local_close_tcp6(_Config) when is_list(_Config) -> + tc_try(recvmsg_response_local_close_tcp6, + fun() -> + not_yet_implemented(), + Recv = fun(Sock) -> socket:recvmsg(Sock) end, + InitState = #{domain => inet6, + type => stream, + protocol => tcp, + recv => Recv}, + ok = receive_response_local_close_tcp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test what happens when a socket is +%% remotely closed while the process is calling the recvmsg function. +%% Socket is IPv4. + +recvmsg_response_remote_close_tcp4(suite) -> + []; +recvmsg_response_remote_close_tcp4(doc) -> + []; +recvmsg_response_remote_close_tcp4(_Config) when is_list(_Config) -> + tc_try(recvmsg_response_remote_close_tcp4, + fun() -> + not_yet_implemented(), + Recv = fun(Sock) -> socket:recvmsg(Sock) end, + InitState = #{domain => inet, + type => stream, + protocol => tcp, + recv => Recv}, + ok = receive_response_remote_close_tcp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test what happens when a socket is +%% remotely closed while the process is calling the recvmsg function. +%% Socket is IPv6. + +recvmsg_response_remote_close_tcp6(suite) -> + []; +recvmsg_response_remote_close_tcp6(doc) -> + []; +recvmsg_response_remote_close_tcp6(_Config) when is_list(_Config) -> + tc_try(recvmsg_response_remote_close_tcp6, + fun() -> + not_yet_implemented(), + Recv = fun(Sock) -> socket:recvmsg(Sock) end, + InitState = #{domain => inet6, + type => stream, + protocol => tcp, + recv => Recv}, + ok = receive_response_remote_close_tcp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test what happens when a socket is +%% locally closed while the process is calling the accept function. +%% We test what happens with a non-controlling_process also, since we +%% git the setup anyway. +%% Socket is IPv4. + +acceptor_response_local_close_tcp4(suite) -> + []; +acceptor_response_local_close_tcp4(doc) -> + []; +acceptor_response_local_close_tcp4(_Config) when is_list(_Config) -> + tc_try(acceptor_response_local_close_tcp4, + fun() -> + not_yet_implemented(), + InitState = #{domain => inet, + type => stream, + protocol => tcp}, + ok = acceptor_response_local_close_tcp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test what happens when a socket is +%% locally closed while the process is calling the accept function. +%% We test what happens with a non-controlling_process also, since we +%% git the setup anyway. +%% Socket is IPv6. + +acceptor_response_local_close_tcp6(suite) -> + []; +acceptor_response_local_close_tcp6(doc) -> + []; +acceptor_response_local_close_tcp6(_Config) when is_list(_Config) -> + tc_try(acceptor_response_local_close_tcp6, + fun() -> + not_yet_implemented(), + InitState = #{domain => inet, + type => stream, + protocol => tcp}, + ok = acceptor_response_local_close_tcp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +acceptor_response_local_close_tcp(_InitState) -> + ok. + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test what happens when a socket is +%% remotely closed while the process is calling the accept function. +%% We test what happens with a non-controlling_process also, since we +%% git the setup anyway. +%% Socket is IPv4. + +acceptor_response_remote_close_tcp4(suite) -> + []; +acceptor_response_remote_close_tcp4(doc) -> + []; +acceptor_response_remote_close_tcp4(_Config) when is_list(_Config) -> + tc_try(acceptor_response_remote_close_tcp4, + fun() -> + not_yet_implemented(), + InitState = #{domain => inet, + type => stream, + protocol => tcp}, + ok = acceptor_response_remote_close_tcp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test what happens when a socket is +%% remotely closed while the process is calling the accept function. +%% We test what happens with a non-controlling_process also, since we +%% git the setup anyway. +%% Socket is IPv6. + +acceptor_response_remote_close_tcp6(suite) -> + []; +acceptor_response_remote_close_tcp6(doc) -> + []; +acceptor_response_remote_close_tcp6(_Config) when is_list(_Config) -> + tc_try(acceptor_response_remote_close_tcp6, + fun() -> + not_yet_implemented(), + InitState = #{domain => inet, + type => stream, + protocol => tcp}, + ok = acceptor_response_remote_close_tcp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +acceptor_response_remote_close_tcp(_InitState) -> + ok. + + + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -- cgit v1.2.3 From 468c633a6e96a689532957e299ce7fb7c67a5d58 Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Mon, 15 Oct 2018 18:29:03 +0200 Subject: [socket-nif] Fix socket close when owner process exits The handling of terminating controlling-process was incomplete. Also added state check (CLOSED and CLOSING) in all nif-functions to prevent access for a closed (or closing) socket. OTP-14831 --- erts/emulator/nifs/common/socket_nif.c | 180 +++++++++++++++++++++++++++++++-- 1 file changed, 173 insertions(+), 7 deletions(-) diff --git a/erts/emulator/nifs/common/socket_nif.c b/erts/emulator/nifs/common/socket_nif.c index 0ef88162bc..f09f618653 100644 --- a/erts/emulator/nifs/common/socket_nif.c +++ b/erts/emulator/nifs/common/socket_nif.c @@ -390,7 +390,18 @@ static void (*esock_sctp_freepaddrs)(struct sockaddr *addrs) = NULL; #define SOCKET_STATE_ACCEPTING (SOCKET_STATE_LISTENING | SOCKET_FLAG_ACC) #define SOCKET_STATE_CLOSING (SOCKET_FLAG_CLOSE) -#define IS_OPEN(d) \ +#define IS_CLOSED(d) \ + ((d)->state == SOCKET_STATE_CLOSED) + +/* +#define IS_STATE(d, f) \ + (((d)->state & (f)) == (f)) +*/ + +#define IS_CLOSING(d) \ + (((d)->state & SOCKET_STATE_CLOSING) == SOCKET_STATE_CLOSING) + +#define IS_OPEN(d) \ (((d)->state & SOCKET_FLAG_OPEN) == SOCKET_FLAG_OPEN) #define IS_CONNECTED(d) \ @@ -2945,6 +2956,9 @@ ERL_NIF_TERM nif_bind(ErlNifEnv* env, } eSockAddr = argv[1]; + if (IS_CLOSED(descP) || IS_CLOSING(descP)) + return esock_make_error(env, atom_closed); + SSDBG( descP, ("SOCKET", "nif_bind -> args when sock = %d (0x%lX)" "\r\n Socket: %T" @@ -3034,6 +3048,9 @@ ERL_NIF_TERM nif_connect(ErlNifEnv* env, } eSockAddr = argv[1]; + if (IS_CLOSED(descP) || IS_CLOSING(descP)) + return esock_make_error(env, atom_closed); + SSDBG( descP, ("SOCKET", "nif_connect -> args when sock = %d:" "\r\n Socket: %T" @@ -3235,6 +3252,9 @@ ERL_NIF_TERM nif_listen(ErlNifEnv* env, return enif_make_badarg(env); } + if (IS_CLOSED(descP) || IS_CLOSING(descP)) + return esock_make_error(env, atom_closed); + SSDBG( descP, ("SOCKET", "nif_listen -> args when sock = %d:" "\r\n Socket: %T" @@ -3296,6 +3316,9 @@ ERL_NIF_TERM nif_accept(ErlNifEnv* env, } ref = argv[1]; + if (IS_CLOSED(descP) || IS_CLOSING(descP)) + return esock_make_error(env, atom_closed); + SSDBG( descP, ("SOCKET", "nif_accept -> args when sock = %d:" "\r\n Socket: %T" @@ -3676,6 +3699,9 @@ ERL_NIF_TERM nif_send(ErlNifEnv* env, } sendRef = argv[1]; + if (IS_CLOSED(descP) || IS_CLOSING(descP)) + return esock_make_error(env, atom_closed); + SSDBG( descP, ("SOCKET", "nif_send -> args when sock = %d:" "\r\n Socket: %T" @@ -3800,6 +3826,9 @@ ERL_NIF_TERM nif_sendto(ErlNifEnv* env, sendRef = argv[1]; eSockAddr = argv[3]; + if (IS_CLOSED(descP) || IS_CLOSING(descP)) + return esock_make_error(env, atom_closed); + SSDBG( descP, ("SOCKET", "nif_sendto -> args when sock = %d:" "\r\n Socket: %T" @@ -3923,6 +3952,9 @@ ERL_NIF_TERM nif_sendmsg(ErlNifEnv* env, sendRef = argv[1]; eMsgHdr = argv[2]; + if (IS_CLOSED(descP) || IS_CLOSING(descP)) + return esock_make_error(env, atom_closed); + SSDBG( descP, ("SOCKET", "nif_sendmsg -> args when sock = %d:" "\r\n Socket: %T" @@ -4212,6 +4244,9 @@ ERL_NIF_TERM nif_recv(ErlNifEnv* env, } recvRef = argv[1]; + if (IS_CLOSED(descP) || IS_CLOSING(descP)) + return esock_make_error(env, atom_closed); + if (!IS_CONNECTED(descP)) return esock_make_error(env, atom_enotconn); @@ -4351,6 +4386,9 @@ ERL_NIF_TERM nif_recvfrom(ErlNifEnv* env, } recvRef = argv[1]; + if (IS_CLOSED(descP) || IS_CLOSING(descP)) + return esock_make_error(env, atom_closed); + SSDBG( descP, ("SOCKET", "nif_recvfrom -> args when sock = %d:" "\r\n Socket: %T" @@ -4511,6 +4549,9 @@ ERL_NIF_TERM nif_recvmsg(ErlNifEnv* env, } recvRef = argv[1]; + if (IS_CLOSED(descP) || IS_CLOSING(descP)) + return esock_make_error(env, atom_closed); + SSDBG( descP, ("SOCKET", "nif_recvmsg -> args when sock = %d:" "\r\n Socket: %T" @@ -4669,6 +4710,9 @@ ERL_NIF_TERM nif_close(ErlNifEnv* env, return enif_make_badarg(env); } + if (IS_CLOSED(descP) || IS_CLOSING(descP)) + return esock_make_error(env, atom_closed); + return nclose(env, descP); } @@ -4807,10 +4851,10 @@ ERL_NIF_TERM nfinalize_close(ErlNifEnv* env, { ERL_NIF_TERM reply; - if (descP->state == SOCKET_STATE_CLOSED) + if (IS_CLOSED(descP)) return esock_atom_ok; - if (descP->state != SOCKET_STATE_CLOSING) + if (!IS_CLOSING(descP)) return esock_make_error(env, atom_enotclosing); /* This nif is executed in a dirty scheduler just so that @@ -4873,6 +4917,9 @@ ERL_NIF_TERM nif_shutdown(ErlNifEnv* env, return enif_make_badarg(env); } + if (IS_CLOSED(descP) || IS_CLOSING(descP)) + return esock_make_error(env, atom_closed); + if (!ehow2how(ehow, &how)) return enif_make_badarg(env); @@ -4957,6 +5004,9 @@ ERL_NIF_TERM nif_setopt(ErlNifEnv* env, eIsEncoded = argv[1]; eVal = argv[4]; + if (IS_CLOSED(descP) || IS_CLOSING(descP)) + return esock_make_error(env, atom_closed); + isEncoded = esock_decode_bool(eIsEncoded); /* SGDBG( ("SOCKET", "nif_setopt -> eIsDecoded (%T) decoded: %d\r\n", */ @@ -8214,6 +8264,9 @@ ERL_NIF_TERM nif_getopt(ErlNifEnv* env, eIsEncoded = argv[1]; eOpt = argv[3]; // Is "normally" an int, but if raw mode: {Int, ValueSz} + if (IS_CLOSED(descP) || IS_CLOSING(descP)) + return esock_make_error(env, atom_closed); + SSDBG( descP, ("SOCKET", "nif_getopt -> args when sock = %d:" "\r\n Socket: %T" @@ -10807,6 +10860,9 @@ ERL_NIF_TERM nif_sockname(ErlNifEnv* env, return enif_make_badarg(env); } + if (IS_CLOSED(descP) || IS_CLOSING(descP)) + return esock_make_error(env, atom_closed); + SSDBG( descP, ("SOCKET", "nif_sockname -> args when sock = %d:" "\r\n Socket: %T" @@ -10872,6 +10928,9 @@ ERL_NIF_TERM nif_peername(ErlNifEnv* env, return enif_make_badarg(env); } + if (IS_CLOSED(descP) || IS_CLOSING(descP)) + return esock_make_error(env, atom_closed); + SSDBG( descP, ("SOCKET", "nif_peername -> args when sock = %d:" "\r\n Socket: %T" @@ -10940,6 +10999,9 @@ ERL_NIF_TERM nif_cancel(ErlNifEnv* env, op = argv[1]; opRef = argv[2]; + if (IS_CLOSED(descP) || IS_CLOSING(descP)) + return esock_make_error(env, atom_closed); + SSDBG( descP, ("SOCKET", "nif_cancel -> args when sock = %d:" "\r\n op: %T" @@ -14955,6 +15017,7 @@ void socket_stop(ErlNifEnv* env, void* obj, int fd, int is_direct_call) MLOCK(descP->accMtx); MLOCK(descP->closeMtx); + SSDBG( descP, ("SOCKET", "socket_stop -> all mutex(s) locked\r\n") ); descP->state = SOCKET_STATE_CLOSING; // Just in case...??? descP->isReadable = FALSE; @@ -14964,7 +15027,13 @@ void socket_stop(ErlNifEnv* env, void* obj, int fd, int is_direct_call) /* We should check that we actually have a monitor. * This *should* be done with a "NULL" monitor value, * which there currently is none... + * If we got here because the controlling process died, + * its no point in demonitor. Also, we not actually have + * a monitor in that case... */ + SSDBG( descP, + ("SOCKET", + "socket_stop -> demonitor (maybe) controlling process\r\n") ); DEMONP(env, descP, &descP->ctrlMon); if (descP->currentWriterP != NULL) { @@ -14972,6 +15041,7 @@ void socket_stop(ErlNifEnv* env, void* obj, int fd, int is_direct_call) * writers waiting. */ + SSDBG( descP, ("SOCKET", "socket_stop -> handle writer(s)\r\n") ); if (!compare_pids(env, &descP->closerPid, &descP->currentWriter.pid) && @@ -14998,6 +15068,7 @@ void socket_stop(ErlNifEnv* env, void* obj, int fd, int is_direct_call) * readers waiting. */ + SSDBG( descP, ("SOCKET", "socket_stop -> handle reader(s)\r\n") ); if (!compare_pids(env, &descP->closerPid, &descP->currentReader.pid) && @@ -15023,6 +15094,7 @@ void socket_stop(ErlNifEnv* env, void* obj, int fd, int is_direct_call) * acceptors waiting. */ + SSDBG( descP, ("SOCKET", "socket_stop -> handle acceptor(s)\r\n") ); if (!compare_pids(env, &descP->closerPid, &descP->currentAcceptor.pid) && @@ -15085,7 +15157,15 @@ void socket_stop(ErlNifEnv* env, void* obj, int fd, int is_direct_call) } } - + + if (!is_direct_call) { + descP->sock = INVALID_SOCKET; + descP->event = INVALID_EVENT; + descP->state = SOCKET_STATE_CLOSED; + } + + SSDBG( descP, ("SOCKET", "socket_stop -> unlock all mutex(s)\r\n") ); + MUNLOCK(descP->closeMtx); MUNLOCK(descP->accMtx); MUNLOCK(descP->readMtx); @@ -15163,22 +15243,108 @@ void socket_down(ErlNifEnv* env, if (compare_pids(env, &descP->ctrlPid, pid)) { + int selectRes; + /* We don't bother with the queue cleanup here - * we leave it to the stop callback function. */ + SSDBG( descP, ("SOCKET", "socket_down -> controlling process term\r\n") ); + descP->state = SOCKET_STATE_CLOSING; descP->closeLocal = TRUE; descP->closerPid = *pid; descP->closerMon = *mon; - descP->closeRef = MKREF(env); - enif_select(env, descP->sock, (ERL_NIF_SELECT_STOP), - descP, NULL, descP->closeRef); + descP->closeRef = MKREF(env); // Do we really need this in this case? + + selectRes = enif_select(env, descP->sock, (ERL_NIF_SELECT_STOP), + descP, NULL, descP->closeRef); + + if (selectRes & ERL_NIF_SELECT_STOP_CALLED) { + /* We are done - wwe can finalize (socket close) directly */ + SSDBG( descP, + ("SOCKET", "socket_down -> [%d] stop called\r\n", descP->sock) ); + dec_socket(descP->domain, descP->type, descP->protocol); + descP->state = SOCKET_STATE_CLOSED; + + /* And finally close the socket. + * Since we close the socket because of an exiting owner, + * we do not need to wait for buffers to sync (linger). + * If the owner wish to ensure the buffer are written, + * it should have closed teh socket explicitly... + */ + if (sock_close(descP->sock) != 0) { + int save_errno = sock_errno(); + + esock_warning_msg("Failed closing socket for terminating " + "controlling process: " + "\r\n Controlling Process: %T" + "\r\n Descriptor: %d" + "\r\n Errno: %d" + "\r\n", pid, descP->sock, save_errno); + } + sock_close_event(descP->event); + + descP->sock = INVALID_SOCKET; + descP->event = INVALID_EVENT; + + descP->state = SOCKET_STATE_CLOSED; + + } else if (selectRes & ERL_NIF_SELECT_STOP_SCHEDULED) { + /* The stop callback function has been *scheduled* which means that + * "should" wait for it to complete. But since we are in a callback + * (down) function, we cannot... + * So, we must close the socket + */ + SSDBG( descP, + ("SOCKET", + "socket_down -> [%d] stop scheduled\r\n", descP->sock) ); + dec_socket(descP->domain, descP->type, descP->protocol); + + /* And now what? We can't wait for the stop function here... + * So, we simply close it here and leave the rest of the "close" + * for later (when the stop function actually gets called... + */ + + if (sock_close(descP->sock) != 0) { + int save_errno = sock_errno(); + + esock_warning_msg("Failed closing socket for terminating " + "controlling process: " + "\r\n Controlling Process: %T" + "\r\n Descriptor: %d" + "\r\n Errno: %d" + "\r\n", pid, descP->sock, save_errno); + } + sock_close_event(descP->event); + + } else { + + /* + * + * + * WE SHOULD REALLY HAVE A WAY TO CLOBBER THE SOCKET, + * SO WE DON'T LET STUFF LEAK. + * NOW, BECAUSE WE FAILED TO SELECT, WE CANNOT FINISH + * THE CLOSE, WHAT TO DO? ABORT? + * + * + */ + esock_warning_msg("Failed selecting stop when handling down " + "of controlling process: " + "\r\n Select Res: %d" + "\r\n Controlling Process: %T" + "\r\n Descriptor: %d" + "\r\n", selectRes, pid, descP->sock); + } + } else { /* check all operation queue(s): acceptor, writer and reader. */ + SSDBG( descP, ("SOCKET", "socket_down -> other process term\r\n") ); + MLOCK(descP->accMtx); if (descP->currentAcceptorP != NULL) socket_down_acceptor(env, descP, pid); -- cgit v1.2.3 From f2481d58a2fb64b8f3c7cbb26e09cb17c04726e0 Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Mon, 15 Oct 2018 18:31:43 +0200 Subject: [socket-nif|test] Added two test cases regarding exiting owner Added two (working) test cases for testing "socket cleanup" when the controlling-process exits. OTP-14831 --- lib/kernel/test/socket_SUITE.erl | 487 +++++++++++++++++++++------------------ 1 file changed, 265 insertions(+), 222 deletions(-) diff --git a/lib/kernel/test/socket_SUITE.erl b/lib/kernel/test/socket_SUITE.erl index 71e6dff174..42420a37dc 100644 --- a/lib/kernel/test/socket_SUITE.erl +++ b/lib/kernel/test/socket_SUITE.erl @@ -67,26 +67,22 @@ api_to_recvmsg_tcp6/1, %% Controlling Process - socket_cleanup_tcp4/1, - socket_cleanup_tcp6/1, - socket_cleanup_udp4/1, - socket_cleanup_udp6/1, - socket_close_tcp4/1, - socket_close_tcp6/1, - socket_close_udp4/1, - socket_close_udp6/1, - recv_response_local_close_tcp4/1, - recv_response_local_close_tcp6/1, - recv_response_remote_close_tcp4/1, - recv_response_remote_close_tcp6/1, - recvmsg_response_local_close_tcp4/1, - recvmsg_response_local_close_tcp6/1, - recvmsg_response_remote_close_tcp4/1, - recvmsg_response_remote_close_tcp6/1, - acceptor_response_local_close_tcp4/1, - acceptor_response_local_close_tcp6/1, - acceptor_response_remote_close_tcp4/1, - acceptor_response_remote_close_tcp6/1 + sc_socket_cleanup_tcp4/1, + sc_socket_cleanup_tcp6/1, + sc_socket_cleanup_udp4/1, + sc_socket_cleanup_udp6/1, + sc_recv_response_local_close_tcp4/1, + sc_recv_response_local_close_tcp6/1, + sc_recv_response_remote_close_tcp4/1, + sc_recv_response_remote_close_tcp6/1, + sc_recvmsg_response_local_close_tcp4/1, + sc_recvmsg_response_local_close_tcp6/1, + sc_recvmsg_response_remote_close_tcp4/1, + sc_recvmsg_response_remote_close_tcp6/1, + sc_acceptor_response_local_close_tcp4/1, + sc_acceptor_response_local_close_tcp6/1, + sc_acceptor_response_remote_close_tcp4/1, + sc_acceptor_response_remote_close_tcp6/1 %% Tickets ]). @@ -125,7 +121,7 @@ suite() -> all() -> [ {group, api}, - {group, controlling_process} + {group, socket_closure} %% {group, tickets} ]. @@ -134,7 +130,7 @@ groups() -> {api_basic, [], api_basic_cases()}, {api_options, [], api_options_cases()}, {api_op_with_timeout, [], api_op_with_timeout_cases()}, - {controlling_process, [], controlling_process_cases()} + {socket_closure, [], socket_closure_cases()} %% {tickets, [], ticket_cases()} ]. @@ -185,33 +181,29 @@ api_op_with_timeout_cases() -> api_to_recvmsg_tcp6 ]. -%% These cases tests what happens when the controlling process dies -controlling_process_cases() -> +%% These cases tests what happens when the socket is closed, locally or +%% remotely. +socket_closure_cases() -> [ - socket_cleanup_tcp4, - socket_cleanup_tcp6, - socket_cleanup_udp4, - socket_cleanup_udp6, - - socket_close_tcp4, - socket_close_tcp6, - socket_close_udp4, - socket_close_udp6, - - recv_response_local_close_tcp4, - recv_response_local_close_tcp6, - recv_response_remote_close_tcp4, - recv_response_remote_close_tcp6, - - recvmsg_response_local_close_tcp4, - recvmsg_response_local_close_tcp6, - recvmsg_response_remote_close_tcp4, - recvmsg_response_remote_close_tcp6, - - acceptor_response_local_close_tcp4, - acceptor_response_local_close_tcp6, - acceptor_response_remote_close_tcp4, - acceptor_response_remote_close_tcp6 + sc_socket_cleanup_tcp4, + sc_socket_cleanup_tcp6, + sc_socket_cleanup_udp4, + sc_socket_cleanup_udp6, + + sc_recv_response_local_close_tcp4, + sc_recv_response_local_close_tcp6, + sc_recv_response_remote_close_tcp4, + sc_recv_response_remote_close_tcp6, + + sc_recvmsg_response_local_close_tcp4, + sc_recvmsg_response_local_close_tcp6, + sc_recvmsg_response_remote_close_tcp4, + sc_recvmsg_response_remote_close_tcp6, + + sc_acceptor_response_local_close_tcp4, + sc_acceptor_response_local_close_tcp6, + sc_acceptor_response_remote_close_tcp4, + sc_acceptor_response_remote_close_tcp6 ]. %% ticket_cases() -> @@ -1337,7 +1329,9 @@ api_to_connect_tcp(InitState) -> cmd => fun(#{tester := Tester} = State) -> receive {'DOWN', _, process, Tester, Reason} -> - {error, {unexpected_exit, tester, Reason}}; + ee("Unexpected DOWN regarding tester ~p: " + "~n ~p", [Reason]), + {error, {unexpected_exit, tester}}; {terminate, Tester} -> {ok, maps:remove(tester, State)} end @@ -1734,7 +1728,9 @@ api_to_maccept_tcp(InitState) -> cmd => fun(#{tester := Tester} = _State) -> receive {'DOWN', _, process, Tester, Reason} -> - {error, {unexpected_exit, tester, Reason}}; + ee("Unexpected DOWN regarding tester ~p: " + "~n ~p", [Reason]), + {error, {unexpected_exit, tester}}; {continue, Tester} -> ok end @@ -1774,7 +1770,9 @@ api_to_maccept_tcp(InitState) -> cmd => fun(#{tester := Tester} = _State) -> receive {'DOWN', _, process, Tester, Reason} -> - {error, {unexpected_exit, tester, Reason}}; + ee("Unexpected DOWN regarding tester ~p: " + "~n ~p", [Reason]), + {error, {unexpected_exit, tester}}; {terminate, Tester} -> ok end @@ -2797,68 +2795,67 @@ api_to_recvmsg_tcp6(_Config) when is_list(_Config) -> %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% %% -%% CONTROLLING PROCESS %% +%% SOCKET CLOSURE %% %% %% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% This test case is intended to test that the sockets are cleaned up -%% (removed) when the controlling process terminates (without explicitly +%% ("removed") when the controlling process terminates (without explicitly %% calling the close function). For a IPv4 TCP (stream) socket. -socket_cleanup_tcp4(suite) -> +sc_socket_cleanup_tcp4(suite) -> []; -socket_cleanup_tcp4(doc) -> +sc_socket_cleanup_tcp4(doc) -> []; -socket_cleanup_tcp4(_Config) when is_list(_Config) -> - tc_try(socket_cleanup_tcp4, +sc_socket_cleanup_tcp4(_Config) when is_list(_Config) -> + tc_try(sc_socket_cleanup_tcp4, fun() -> - not_yet_implemented(), + %% not_yet_implemented(), InitState = #{domain => inet, type => stream, protocol => tcp}, - ok = socket_cleanup(InitState) + ok = sc_socket_cleanup(InitState) end). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% This test case is intended to test that the sockets are cleaned up -%% (removed) when the controlling process terminates (without explicitly +%% ("removed") when the controlling process terminates (without explicitly %% calling the close function). For a IPv6 TCP (stream) socket. -socket_cleanup_tcp6(suite) -> +sc_socket_cleanup_tcp6(suite) -> []; -socket_cleanup_tcp6(doc) -> +sc_socket_cleanup_tcp6(doc) -> []; -socket_cleanup_tcp6(_Config) when is_list(_Config) -> - tc_try(socket_cleanup_tcp6, +sc_socket_cleanup_tcp6(_Config) when is_list(_Config) -> + tc_try(sc_socket_cleanup_tcp6, fun() -> not_yet_implemented(), InitState = #{domain => inet6, type => stream, protocol => tcp}, - ok = socket_cleanup(InitState) + ok = sc_socket_cleanup(InitState) end). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% This test case is intended to test that the sockets are cleaned up -%% (removed) when the controlling process terminates (without explicitly +%% ("removed") when the controlling process terminates (without explicitly %% calling the close function). For a IPv4 UDP (dgram) socket. -socket_cleanup_udp4(suite) -> +sc_socket_cleanup_udp4(suite) -> []; -socket_cleanup_udp4(doc) -> +sc_socket_cleanup_udp4(doc) -> []; -socket_cleanup_udp4(_Config) when is_list(_Config) -> - tc_try(socket_cleanup_udp4, +sc_socket_cleanup_udp4(_Config) when is_list(_Config) -> + tc_try(sc_socket_cleanup_udp4, fun() -> - not_yet_implemented(), InitState = #{domain => inet, type => dgram, protocol => udp}, - ok = socket_cleanup(InitState) + ok = sc_socket_cleanup(InitState) end). @@ -2868,113 +2865,159 @@ socket_cleanup_udp4(_Config) when is_list(_Config) -> %% (removed) when the controlling process terminates (without explicitly %% calling the close function). For a IPv6 UDP (dgram) socket. -socket_cleanup_udp6(suite) -> +sc_socket_cleanup_udp6(suite) -> []; -socket_cleanup_udp6(doc) -> +sc_socket_cleanup_udp6(doc) -> []; -socket_cleanup_udp6(_Config) when is_list(_Config) -> - tc_try(socket_cleanup_udp6, +sc_socket_cleanup_udp6(_Config) when is_list(_Config) -> + tc_try(sc_socket_cleanup_udp6, fun() -> not_yet_implemented(), InitState = #{domain => inet6, type => dgram, protocol => udp}, - ok = socket_cleanup(InitState) - end). - - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -socket_cleanup(_InitState) -> - ok. - - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% This test case is intended to test what happens when a process other -%% than the controlling process closes a socket. -%% For a IPv4 TCP (stream) socket. - -socket_close_tcp4(suite) -> - []; -socket_close_tcp4(doc) -> - []; -socket_close_tcp4(_Config) when is_list(_Config) -> - tc_try(socket_close_tcp4, - fun() -> - not_yet_implemented(), - InitState = #{domain => inet, - type => stream, - protocol => tcp}, - ok = socket_close(InitState) + ok = sc_socket_cleanup(InitState) end). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% This test case is intended to test what happens when a process other -%% than the controlling process closes a socket. -%% For a IPv6 TCP (stream) socket. - -socket_close_tcp6(suite) -> - []; -socket_close_tcp6(doc) -> - []; -socket_close_tcp6(_Config) when is_list(_Config) -> - tc_try(socket_close_tcp6, - fun() -> - not_yet_implemented(), - InitState = #{domain => inet6, - type => stream, - protocol => tcp}, - ok = socket_close(InitState) - end). +sc_socket_cleanup(InitState) -> + OwnerSeq = + [ + %% *** Wait for start order part *** + #{desc => "await start (from tester)", + cmd => fun(State) -> + receive + {start, Tester} when is_pid(Tester) -> + {ok, State#{tester => Tester}} + end + end}, + %% *** Init part *** + #{desc => "monitor tester", + cmd => fun(#{tester := Tester} = _State) -> + _MRef = erlang:monitor(process, Tester), + ok + end}, + #{desc => "create socket", + cmd => fun(#{domain := Domain, + type := Type, + protocol := Proto} = State) -> + case socket:open(Domain, Type, Proto) of + {ok, Sock} -> + {ok, State#{sock => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready", + cmd => fun(#{tester := Tester, sock := Sock} = _State) -> + Tester ! {ready, self(), Sock}, + ok + end}, -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% This test case is intended to test what happens when a process other -%% than the controlling process closes a socket. -%% For a IPv4 UDP (dgram) socket. - -socket_close_udp4(suite) -> - []; -socket_close_udp4(doc) -> - []; -socket_close_udp4(_Config) when is_list(_Config) -> - tc_try(socket_close_udp4, - fun() -> - not_yet_implemented(), - InitState = #{domain => inet, - type => dgram, - protocol => udp}, - ok = socket_close(InitState) - end). - + %% *** The actual test *** + %% We intentially leave the socket "as is", no explicit close + #{desc => "await terminate (from tester)", + cmd => fun(#{tester := Tester} = State) -> + receive + {'DOWN', _, process, Tester, Reason} -> + ee("Unexpected DOWN regarding tester ~p: " + "~n ~p", [Reason]), + {error, {unexpected_exit, tester}}; + {terminate, Tester} -> + {ok, maps:remove(tester, State)} + end + end}, + #{desc => "enable (otp) debug", + cmd => fun(#{sock := Sock} = _State) -> + ok = socket:setopt(Sock, otp, debug, true) + end}, -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% This test case is intended to test what happens when a process other -%% than the controlling process closes a socket. -%% For a IPv6 UDP (dgram) socket. + %% *** We are done *** + #{desc => "finish", + cmd => fun(_) -> + {ok, normal} + end} + ], -socket_close_udp6(suite) -> - []; -socket_close_udp6(doc) -> - []; -socket_close_udp6(_Config) when is_list(_Config) -> - tc_try(socket_close_udp6, - fun() -> - not_yet_implemented(), - InitState = #{domain => inet6, - type => dgram, - protocol => udp}, - ok = socket_close(InitState) - end). + TesterSeq = + [ + %% *** Init part *** + #{desc => "monitor owner", + cmd => fun(#{owner := Owner} = _State) -> + _MRef = erlang:monitor(process, Owner), + ok + end}, + #{desc => "order (owner) start", + cmd => fun(#{owner := Pid} = _State) -> + Pid ! {start, self()}, + ok + end}, + #{desc => "await (owner) ready", + cmd => fun(#{owner := Owner} = State) -> + receive + {'DOWN', _, process, Owner, Reason} -> + ee("Unexpected DOWN regarding owner ~p: " + "~n ~p", [Reason]), + {error, {unexpected_exit, owner}}; + {ready, Owner, Sock} -> + {ok, State#{sock => Sock}} + end + end}, + #{desc => "verify owner as controlling-process", + cmd => fun(#{owner := Owner, sock := Sock} = _State) -> + case socket:getopt(Sock, otp, controlling_process) of + {ok, Owner} -> + ok; + {ok, Other} -> + {error, {unexpected_owner, Other}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "order (owner) terminate", + cmd => fun(#{owner := Pid} = _State) -> + Pid ! {terminate, self()}, + ok + end}, + #{desc => "await (owner) termination", + cmd => fun(#{owner := Owner} = _State) -> + receive + {'DOWN', _, process, Owner, _} -> + ok + end + end}, + #{desc => "verify no socket (closed)", + cmd => fun(#{owner := Owner, sock := Sock} = _State) -> + case socket:getopt(Sock, otp, controlling_process) of + {ok, Pid} -> + {error, {unexpected_success, Owner, Pid}}; + {error, closed} -> + ok; + {error, Reason} -> + ei("expected failure: ~p", [Reason]), + {error, {unexpected_failure, Reason}} + end + end}, + %% *** We are done *** + #{desc => "finish", + cmd => fun(_) -> + {ok, normal} + end} + ], -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + p("start (socket) owner evaluator"), + Owner = evaluator_start("owner", OwnerSeq, InitState), -socket_close(_InitState) -> - ok. + p("start tester evaluator"), + TesterInitState = #{owner => Owner}, + Tester = evaluator_start("tester", TesterSeq, TesterInitState), + p("await evaluator"), + ok = await_evaluator_finish([Owner, Tester]). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -2982,12 +3025,12 @@ socket_close(_InitState) -> %% locally closed while the process is calling the recv function. %% Socket is IPv4. -recv_response_local_close_tcp4(suite) -> +sc_recv_response_local_close_tcp4(suite) -> []; -recv_response_local_close_tcp4(doc) -> +sc_recv_response_local_close_tcp4(doc) -> []; -recv_response_local_close_tcp4(_Config) when is_list(_Config) -> - tc_try(recv_response_local_close_tcp4, +sc_recv_response_local_close_tcp4(_Config) when is_list(_Config) -> + tc_try(sc_recv_response_local_close_tcp4, fun() -> not_yet_implemented(), Recv = fun(Sock) -> socket:recv(Sock) end, @@ -2995,7 +3038,7 @@ recv_response_local_close_tcp4(_Config) when is_list(_Config) -> type => stream, protocol => tcp, recv => Recv}, - ok = receive_response_local_close_tcp(InitState) + ok = sc_receive_response_local_close_tcp(InitState) end). @@ -3004,12 +3047,12 @@ recv_response_local_close_tcp4(_Config) when is_list(_Config) -> %% locally closed while the process is calling the recv function. %% Socket is IPv6. -recv_response_local_close_tcp6(suite) -> +sc_recv_response_local_close_tcp6(suite) -> []; -recv_response_local_close_tcp6(doc) -> +sc_recv_response_local_close_tcp6(doc) -> []; -recv_response_local_close_tcp6(_Config) when is_list(_Config) -> - tc_try(recv_response_local_close_tcp6, +sc_recv_response_local_close_tcp6(_Config) when is_list(_Config) -> + tc_try(sc_recv_response_local_close_tcp6, fun() -> not_yet_implemented(), Recv = fun(Sock) -> socket:recv(Sock) end, @@ -3017,13 +3060,13 @@ recv_response_local_close_tcp6(_Config) when is_list(_Config) -> type => stream, protocol => tcp, recv => Recv}, - ok = receive_response_local_close_tcp(InitState) + ok = sc_receive_response_local_close_tcp(InitState) end). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -receive_response_local_close_tcp(_InitState) -> +sc_receive_response_local_close_tcp(_InitState) -> ok. @@ -3033,12 +3076,12 @@ receive_response_local_close_tcp(_InitState) -> %% remotely closed while the process is calling the recv function. %% Socket is IPv4. -recv_response_remote_close_tcp4(suite) -> +sc_recv_response_remote_close_tcp4(suite) -> []; -recv_response_remote_close_tcp4(doc) -> +sc_recv_response_remote_close_tcp4(doc) -> []; -recv_response_remote_close_tcp4(_Config) when is_list(_Config) -> - tc_try(recv_response_remote_close_tcp4, +sc_recv_response_remote_close_tcp4(_Config) when is_list(_Config) -> + tc_try(sc_recv_response_remote_close_tcp4, fun() -> not_yet_implemented(), Recv = fun(Sock) -> socket:recv(Sock) end, @@ -3046,7 +3089,7 @@ recv_response_remote_close_tcp4(_Config) when is_list(_Config) -> type => stream, protocol => tcp, recv => Recv}, - ok = receive_response_remote_close_tcp(InitState) + ok = sc_receive_response_remote_close_tcp(InitState) end). @@ -3055,12 +3098,12 @@ recv_response_remote_close_tcp4(_Config) when is_list(_Config) -> %% remotely closed while the process is calling the recv function. %% Socket is IPv6. -recv_response_remote_close_tcp6(suite) -> +sc_recv_response_remote_close_tcp6(suite) -> []; -recv_response_remote_close_tcp6(doc) -> +sc_recv_response_remote_close_tcp6(doc) -> []; -recv_response_remote_close_tcp6(_Config) when is_list(_Config) -> - tc_try(recv_response_remote_close_tcp6, +sc_recv_response_remote_close_tcp6(_Config) when is_list(_Config) -> + tc_try(sc_recv_response_remote_close_tcp6, fun() -> not_yet_implemented(), Recv = fun(Sock) -> socket:recv(Sock) end, @@ -3068,13 +3111,13 @@ recv_response_remote_close_tcp6(_Config) when is_list(_Config) -> type => stream, protocol => tcp, recv => Recv}, - ok = receive_response_remote_close_tcp(InitState) + ok = sc_receive_response_remote_close_tcp(InitState) end). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -receive_response_remote_close_tcp(_InitState) -> +sc_receive_response_remote_close_tcp(_InitState) -> ok. @@ -3084,12 +3127,12 @@ receive_response_remote_close_tcp(_InitState) -> %% locally closed while the process is calling the recvmsg function. %% Socket is IPv4. -recvmsg_response_local_close_tcp4(suite) -> +sc_recvmsg_response_local_close_tcp4(suite) -> []; -recvmsg_response_local_close_tcp4(doc) -> +sc_recvmsg_response_local_close_tcp4(doc) -> []; -recvmsg_response_local_close_tcp4(_Config) when is_list(_Config) -> - tc_try(recvmsg_response_local_close_tcp4, +sc_recvmsg_response_local_close_tcp4(_Config) when is_list(_Config) -> + tc_try(sc_recvmsg_response_local_close_tcp4, fun() -> not_yet_implemented(), Recv = fun(Sock) -> socket:recvmsg(Sock) end, @@ -3097,7 +3140,7 @@ recvmsg_response_local_close_tcp4(_Config) when is_list(_Config) -> type => stream, protocol => tcp, recv => Recv}, - ok = receive_response_local_close_tcp(InitState) + ok = sc_receive_response_local_close_tcp(InitState) end). @@ -3106,12 +3149,12 @@ recvmsg_response_local_close_tcp4(_Config) when is_list(_Config) -> %% locally closed while the process is calling the recvmsg function. %% Socket is IPv6. -recvmsg_response_local_close_tcp6(suite) -> +sc_recvmsg_response_local_close_tcp6(suite) -> []; -recvmsg_response_local_close_tcp6(doc) -> +sc_recvmsg_response_local_close_tcp6(doc) -> []; -recvmsg_response_local_close_tcp6(_Config) when is_list(_Config) -> - tc_try(recvmsg_response_local_close_tcp6, +sc_recvmsg_response_local_close_tcp6(_Config) when is_list(_Config) -> + tc_try(sc_recvmsg_response_local_close_tcp6, fun() -> not_yet_implemented(), Recv = fun(Sock) -> socket:recvmsg(Sock) end, @@ -3119,7 +3162,7 @@ recvmsg_response_local_close_tcp6(_Config) when is_list(_Config) -> type => stream, protocol => tcp, recv => Recv}, - ok = receive_response_local_close_tcp(InitState) + ok = sc_receive_response_local_close_tcp(InitState) end). @@ -3128,12 +3171,12 @@ recvmsg_response_local_close_tcp6(_Config) when is_list(_Config) -> %% remotely closed while the process is calling the recvmsg function. %% Socket is IPv4. -recvmsg_response_remote_close_tcp4(suite) -> +sc_recvmsg_response_remote_close_tcp4(suite) -> []; -recvmsg_response_remote_close_tcp4(doc) -> +sc_recvmsg_response_remote_close_tcp4(doc) -> []; -recvmsg_response_remote_close_tcp4(_Config) when is_list(_Config) -> - tc_try(recvmsg_response_remote_close_tcp4, +sc_recvmsg_response_remote_close_tcp4(_Config) when is_list(_Config) -> + tc_try(sc_recvmsg_response_remote_close_tcp4, fun() -> not_yet_implemented(), Recv = fun(Sock) -> socket:recvmsg(Sock) end, @@ -3141,7 +3184,7 @@ recvmsg_response_remote_close_tcp4(_Config) when is_list(_Config) -> type => stream, protocol => tcp, recv => Recv}, - ok = receive_response_remote_close_tcp(InitState) + ok = sc_receive_response_remote_close_tcp(InitState) end). @@ -3150,12 +3193,12 @@ recvmsg_response_remote_close_tcp4(_Config) when is_list(_Config) -> %% remotely closed while the process is calling the recvmsg function. %% Socket is IPv6. -recvmsg_response_remote_close_tcp6(suite) -> +sc_recvmsg_response_remote_close_tcp6(suite) -> []; -recvmsg_response_remote_close_tcp6(doc) -> +sc_recvmsg_response_remote_close_tcp6(doc) -> []; -recvmsg_response_remote_close_tcp6(_Config) when is_list(_Config) -> - tc_try(recvmsg_response_remote_close_tcp6, +sc_recvmsg_response_remote_close_tcp6(_Config) when is_list(_Config) -> + tc_try(sc_recvmsg_response_remote_close_tcp6, fun() -> not_yet_implemented(), Recv = fun(Sock) -> socket:recvmsg(Sock) end, @@ -3163,7 +3206,7 @@ recvmsg_response_remote_close_tcp6(_Config) when is_list(_Config) -> type => stream, protocol => tcp, recv => Recv}, - ok = receive_response_remote_close_tcp(InitState) + ok = sc_receive_response_remote_close_tcp(InitState) end). @@ -3174,18 +3217,18 @@ recvmsg_response_remote_close_tcp6(_Config) when is_list(_Config) -> %% git the setup anyway. %% Socket is IPv4. -acceptor_response_local_close_tcp4(suite) -> +sc_acceptor_response_local_close_tcp4(suite) -> []; -acceptor_response_local_close_tcp4(doc) -> +sc_acceptor_response_local_close_tcp4(doc) -> []; -acceptor_response_local_close_tcp4(_Config) when is_list(_Config) -> - tc_try(acceptor_response_local_close_tcp4, +sc_acceptor_response_local_close_tcp4(_Config) when is_list(_Config) -> + tc_try(sc_acceptor_response_local_close_tcp4, fun() -> not_yet_implemented(), InitState = #{domain => inet, type => stream, protocol => tcp}, - ok = acceptor_response_local_close_tcp(InitState) + ok = sc_acceptor_response_local_close_tcp(InitState) end). @@ -3196,24 +3239,24 @@ acceptor_response_local_close_tcp4(_Config) when is_list(_Config) -> %% git the setup anyway. %% Socket is IPv6. -acceptor_response_local_close_tcp6(suite) -> +sc_acceptor_response_local_close_tcp6(suite) -> []; -acceptor_response_local_close_tcp6(doc) -> +sc_acceptor_response_local_close_tcp6(doc) -> []; -acceptor_response_local_close_tcp6(_Config) when is_list(_Config) -> - tc_try(acceptor_response_local_close_tcp6, +sc_acceptor_response_local_close_tcp6(_Config) when is_list(_Config) -> + tc_try(sc_acceptor_response_local_close_tcp6, fun() -> not_yet_implemented(), InitState = #{domain => inet, type => stream, protocol => tcp}, - ok = acceptor_response_local_close_tcp(InitState) + ok = sc_acceptor_response_local_close_tcp(InitState) end). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -acceptor_response_local_close_tcp(_InitState) -> +sc_acceptor_response_local_close_tcp(_InitState) -> ok. @@ -3225,18 +3268,18 @@ acceptor_response_local_close_tcp(_InitState) -> %% git the setup anyway. %% Socket is IPv4. -acceptor_response_remote_close_tcp4(suite) -> +sc_acceptor_response_remote_close_tcp4(suite) -> []; -acceptor_response_remote_close_tcp4(doc) -> +sc_acceptor_response_remote_close_tcp4(doc) -> []; -acceptor_response_remote_close_tcp4(_Config) when is_list(_Config) -> - tc_try(acceptor_response_remote_close_tcp4, +sc_acceptor_response_remote_close_tcp4(_Config) when is_list(_Config) -> + tc_try(sc_acceptor_response_remote_close_tcp4, fun() -> not_yet_implemented(), InitState = #{domain => inet, type => stream, protocol => tcp}, - ok = acceptor_response_remote_close_tcp(InitState) + ok = sc_acceptor_response_remote_close_tcp(InitState) end). @@ -3247,24 +3290,24 @@ acceptor_response_remote_close_tcp4(_Config) when is_list(_Config) -> %% git the setup anyway. %% Socket is IPv6. -acceptor_response_remote_close_tcp6(suite) -> +sc_acceptor_response_remote_close_tcp6(suite) -> []; -acceptor_response_remote_close_tcp6(doc) -> +sc_acceptor_response_remote_close_tcp6(doc) -> []; -acceptor_response_remote_close_tcp6(_Config) when is_list(_Config) -> +sc_acceptor_response_remote_close_tcp6(_Config) when is_list(_Config) -> tc_try(acceptor_response_remote_close_tcp6, fun() -> not_yet_implemented(), InitState = #{domain => inet, type => stream, protocol => tcp}, - ok = acceptor_response_remote_close_tcp(InitState) + ok = sc_acceptor_response_remote_close_tcp(InitState) end). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -acceptor_response_remote_close_tcp(_InitState) -> +sc_acceptor_response_remote_close_tcp(_InitState) -> ok. -- cgit v1.2.3 From 043624804888dc021a75b01c0a3d8c1c0a1fde23 Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Tue, 16 Oct 2018 15:53:37 +0200 Subject: [socket-nif] Recv handling of closing sockets Fixed more issues regarding closing sockets. Specifically with respect to recv calls. Also, added demonitor for all *current* processes. Also fixed a buffer overflow problem when writing an warning message (the timestamp buffer was not large enough). OTP-14831 --- erts/emulator/nifs/common/socket_nif.c | 61 +++++++++++++++++++++++++++------ erts/emulator/nifs/common/socket_util.c | 2 +- 2 files changed, 51 insertions(+), 12 deletions(-) diff --git a/erts/emulator/nifs/common/socket_nif.c b/erts/emulator/nifs/common/socket_nif.c index f09f618653..3ec19c5a60 100644 --- a/erts/emulator/nifs/common/socket_nif.c +++ b/erts/emulator/nifs/common/socket_nif.c @@ -4776,17 +4776,23 @@ ERL_NIF_TERM nclose(ErlNifEnv* env, if (selectRes & ERL_NIF_SELECT_STOP_CALLED) { /* Prep done - inform the caller it can finalize (close) directly */ SSDBG( descP, - ("SOCKET", "nclose -> [%d] stop called\r\n", descP->sock) ); + ("SOCKET", "nclose -> [%d] stop was called\r\n", descP->sock) ); dec_socket(domain, type, protocol); reply = esock_atom_ok; } else if (selectRes & ERL_NIF_SELECT_STOP_SCHEDULED) { /* The stop callback function has been *scheduled* which means that we * have to wait for it to complete. */ SSDBG( descP, - ("SOCKET", "nclose -> [%d] stop scheduled\r\n", descP->sock) ); + ("SOCKET", "nclose -> [%d] stop was scheduled\r\n", + descP->sock) ); dec_socket(domain, type, protocol); // SHALL WE DO THIS AT finalize? reply = esock_make_ok2(env, descP->closeRef); } else { + + SSDBG( descP, + ("SOCKET", "nclose -> [%d] stop failed: %d\r\n", + descP->sock, selectRes) ); + /* * * WE SHOULD REALLY HAVE A WAY TO CLOBBER THE SOCKET, @@ -4796,6 +4802,7 @@ ERL_NIF_TERM nclose(ErlNifEnv* env, * * */ + reason = MKT2(env, atom_select, MKI(env, selectRes)); reply = esock_make_error(env, reason); } @@ -12000,6 +12007,9 @@ ERL_NIF_TERM recv_check_result(ErlNifEnv* env, SSDBG( descP, ("SOCKET", "recv_check_result -> [%d] eagain\r\n", toRead) ); + if ((xres = recv_init_current_reader(env, descP, recvRef)) != NULL) + return esock_make_error_str(env, xres); + SELECT(env, descP->sock, (ERL_NIF_SELECT_READ), descP, NULL, recvRef); @@ -15005,6 +15015,7 @@ static void socket_stop(ErlNifEnv* env, void* obj, int fd, int is_direct_call) { SocketDescriptor* descP = (SocketDescriptor*) obj; + int dres; SSDBG( descP, ("SOCKET", "socket_stop -> entry when" @@ -15033,15 +15044,24 @@ void socket_stop(ErlNifEnv* env, void* obj, int fd, int is_direct_call) */ SSDBG( descP, ("SOCKET", - "socket_stop -> demonitor (maybe) controlling process\r\n") ); - DEMONP(env, descP, &descP->ctrlMon); + "socket_stop -> demonitor (maybe) controlling process (%T)\r\n", + descP->ctrlPid) ); + dres = DEMONP(env, descP, &descP->ctrlMon); + SSDBG( descP, ("SOCKET", "socket_stop -> demonitor result: %d\r\n", dres) ); + if (descP->currentWriterP != NULL) { /* We have a (current) writer and *may* therefor also have * writers waiting. */ - SSDBG( descP, ("SOCKET", "socket_stop -> handle writer(s)\r\n") ); + SSDBG( descP, + ("SOCKET", + "socket_stop -> demonitor (maybe) current writer: %T, %T\r\n", + descP->currentWriter.pid, descP->currentWriter.ref) ); + DEMONP(env, descP, &descP->currentWriter.mon); + + SSDBG( descP, ("SOCKET", "socket_stop -> handle current writer\r\n") ); if (!compare_pids(env, &descP->closerPid, &descP->currentWriter.pid) && @@ -15059,6 +15079,7 @@ void socket_stop(ErlNifEnv* env, void* obj, int fd, int is_direct_call) } /* And also deal with the waiting writers (in the same way) */ + SSDBG( descP, ("SOCKET", "socket_stop -> handle waiting writer(s)\r\n") ); inform_waiting_procs(env, descP, &descP->writersQ, TRUE, atom_closed); } @@ -15068,7 +15089,13 @@ void socket_stop(ErlNifEnv* env, void* obj, int fd, int is_direct_call) * readers waiting. */ - SSDBG( descP, ("SOCKET", "socket_stop -> handle reader(s)\r\n") ); + SSDBG( descP, + ("SOCKET", + "socket_stop -> demonitor (maybe) current reader: %T, %T\r\n", + descP->currentReader.pid, descP->currentReader.ref) ); + DEMONP(env, descP, &descP->currentReader.mon); + + SSDBG( descP, ("SOCKET", "socket_stop -> handle current reader\r\n") ); if (!compare_pids(env, &descP->closerPid, &descP->currentReader.pid) && @@ -15086,6 +15113,7 @@ void socket_stop(ErlNifEnv* env, void* obj, int fd, int is_direct_call) } /* And also deal with the waiting readers (in the same way) */ + SSDBG( descP, ("SOCKET", "socket_stop -> handle waiting reader(s)\r\n") ); inform_waiting_procs(env, descP, &descP->readersQ, TRUE, atom_closed); } @@ -15094,7 +15122,13 @@ void socket_stop(ErlNifEnv* env, void* obj, int fd, int is_direct_call) * acceptors waiting. */ - SSDBG( descP, ("SOCKET", "socket_stop -> handle acceptor(s)\r\n") ); + SSDBG( descP, + ("SOCKET", + "socket_stop -> demonitor (maybe) current acceptor: %T, %T\r\n", + descP->currentAcceptor.pid, descP->currentAcceptor.ref) ); + DEMONP(env, descP, &descP->currentAcceptor.mon); + + SSDBG( descP, ("SOCKET", "socket_stop -> handle current acceptor\r\n") ); if (!compare_pids(env, &descP->closerPid, &descP->currentAcceptor.pid) && @@ -15112,6 +15146,7 @@ void socket_stop(ErlNifEnv* env, void* obj, int fd, int is_direct_call) } /* And also deal with the waiting acceptors (in the same way) */ + SSDBG( descP, ("SOCKET", "socket_stop -> handle waiting acceptor(s)\r\n") ); inform_waiting_procs(env, descP, &descP->acceptorsQ, TRUE, atom_closed); } @@ -15237,9 +15272,13 @@ void socket_down(ErlNifEnv* env, SocketDescriptor* descP = (SocketDescriptor*) obj; SSDBG( descP, ("SOCKET", "socket_down -> entry with" - "\r\n sock: %d" - "\r\n pid: %T" - "\r\n", descP->sock, *pid) ); + "\r\n sock: %d" + "\r\n pid: %T" + "\r\n Closed: %s" + "\r\n Closing: %s" + "\r\n", + descP->sock, *pid, + B2S(IS_CLOSED(descP)), B2S(IS_CLOSING(descP))) ); if (compare_pids(env, &descP->ctrlPid, pid)) { @@ -15249,7 +15288,7 @@ void socket_down(ErlNifEnv* env, * we leave it to the stop callback function. */ - SSDBG( descP, ("SOCKET", "socket_down -> controlling process term\r\n") ); + SSDBG( descP, ("SOCKET", "socket_down -> controlling process exit\r\n") ); descP->state = SOCKET_STATE_CLOSING; descP->closeLocal = TRUE; diff --git a/erts/emulator/nifs/common/socket_util.c b/erts/emulator/nifs/common/socket_util.c index ff50fd2384..766d3724c1 100644 --- a/erts/emulator/nifs/common/socket_util.c +++ b/erts/emulator/nifs/common/socket_util.c @@ -1506,7 +1506,7 @@ void esock_warning_msg( const char* format, ... ) { va_list args; char f[512 + sizeof(format)]; // This has to suffice... - char stamp[32]; + char stamp[64]; // Just in case... struct timespec ts; int res; -- cgit v1.2.3 From f945aa4a8067d745ee75fe695272104e220d7bc3 Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Tue, 16 Oct 2018 15:57:17 +0200 Subject: [socket-nif|test] Added test case for closed socket while recv Added a test cases for testing "socket cleanup" while process is reading using recv. OTP-14831 --- lib/kernel/test/socket_SUITE.erl | 632 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 596 insertions(+), 36 deletions(-) diff --git a/lib/kernel/test/socket_SUITE.erl b/lib/kernel/test/socket_SUITE.erl index 42420a37dc..8ab39f6ffe 100644 --- a/lib/kernel/test/socket_SUITE.erl +++ b/lib/kernel/test/socket_SUITE.erl @@ -66,11 +66,11 @@ api_to_recvmsg_tcp4/1, api_to_recvmsg_tcp6/1, - %% Controlling Process - sc_socket_cleanup_tcp4/1, - sc_socket_cleanup_tcp6/1, - sc_socket_cleanup_udp4/1, - sc_socket_cleanup_udp6/1, + %% Socket Closure + sc_cpe_socket_cleanup_tcp4/1, + sc_cpe_socket_cleanup_tcp6/1, + sc_cpe_socket_cleanup_udp4/1, + sc_cpe_socket_cleanup_udp6/1, sc_recv_response_local_close_tcp4/1, sc_recv_response_local_close_tcp6/1, sc_recv_response_remote_close_tcp4/1, @@ -130,7 +130,8 @@ groups() -> {api_basic, [], api_basic_cases()}, {api_options, [], api_options_cases()}, {api_op_with_timeout, [], api_op_with_timeout_cases()}, - {socket_closure, [], socket_closure_cases()} + {socket_closure, [], socket_closure_cases()}, + {sc_ctrl_proc_exit, [], sc_cp_exit_cases()} %% {tickets, [], ticket_cases()} ]. @@ -185,10 +186,7 @@ api_op_with_timeout_cases() -> %% remotely. socket_closure_cases() -> [ - sc_socket_cleanup_tcp4, - sc_socket_cleanup_tcp6, - sc_socket_cleanup_udp4, - sc_socket_cleanup_udp6, + {group, sc_ctrl_proc_exit}, sc_recv_response_local_close_tcp4, sc_recv_response_local_close_tcp6, @@ -206,6 +204,16 @@ socket_closure_cases() -> sc_acceptor_response_remote_close_tcp6 ]. +%% These cases are all about socket cleanup after the controlling process +%% exits *without* calling socket:close/1. +sc_cp_exit_cases() -> + [ + sc_cpe_socket_cleanup_tcp4, + sc_cpe_socket_cleanup_tcp6, + sc_cpe_socket_cleanup_udp4, + sc_cpe_socket_cleanup_udp6 + ]. + %% ticket_cases() -> %% []. @@ -1782,7 +1790,7 @@ api_to_maccept_tcp(InitState) -> #{desc => "close (listen) socket", cmd => fun(#{lsock := LSock} = State) -> sock_close(LSock), - {ok, maps:remove(sock3, State)} + {ok, maps:remove(lsock, State)} end}, %% *** We are done *** @@ -2805,18 +2813,18 @@ api_to_recvmsg_tcp6(_Config) when is_list(_Config) -> %% ("removed") when the controlling process terminates (without explicitly %% calling the close function). For a IPv4 TCP (stream) socket. -sc_socket_cleanup_tcp4(suite) -> +sc_cpe_socket_cleanup_tcp4(suite) -> []; -sc_socket_cleanup_tcp4(doc) -> +sc_cpe_socket_cleanup_tcp4(doc) -> []; -sc_socket_cleanup_tcp4(_Config) when is_list(_Config) -> - tc_try(sc_socket_cleanup_tcp4, +sc_cpe_socket_cleanup_tcp4(_Config) when is_list(_Config) -> + tc_try(sc_cpe_socket_cleanup_tcp4, fun() -> %% not_yet_implemented(), InitState = #{domain => inet, type => stream, protocol => tcp}, - ok = sc_socket_cleanup(InitState) + ok = sc_cpe_socket_cleanup(InitState) end). @@ -2825,18 +2833,18 @@ sc_socket_cleanup_tcp4(_Config) when is_list(_Config) -> %% ("removed") when the controlling process terminates (without explicitly %% calling the close function). For a IPv6 TCP (stream) socket. -sc_socket_cleanup_tcp6(suite) -> +sc_cpe_socket_cleanup_tcp6(suite) -> []; -sc_socket_cleanup_tcp6(doc) -> +sc_cpe_socket_cleanup_tcp6(doc) -> []; -sc_socket_cleanup_tcp6(_Config) when is_list(_Config) -> - tc_try(sc_socket_cleanup_tcp6, +sc_cpe_socket_cleanup_tcp6(_Config) when is_list(_Config) -> + tc_try(sc_cpe_socket_cleanup_tcp6, fun() -> not_yet_implemented(), InitState = #{domain => inet6, type => stream, protocol => tcp}, - ok = sc_socket_cleanup(InitState) + ok = sc_cpe_socket_cleanup(InitState) end). @@ -2845,17 +2853,17 @@ sc_socket_cleanup_tcp6(_Config) when is_list(_Config) -> %% ("removed") when the controlling process terminates (without explicitly %% calling the close function). For a IPv4 UDP (dgram) socket. -sc_socket_cleanup_udp4(suite) -> +sc_cpe_socket_cleanup_udp4(suite) -> []; -sc_socket_cleanup_udp4(doc) -> +sc_cpe_socket_cleanup_udp4(doc) -> []; -sc_socket_cleanup_udp4(_Config) when is_list(_Config) -> - tc_try(sc_socket_cleanup_udp4, +sc_cpe_socket_cleanup_udp4(_Config) when is_list(_Config) -> + tc_try(sc_cpe_socket_cleanup_udp4, fun() -> InitState = #{domain => inet, type => dgram, protocol => udp}, - ok = sc_socket_cleanup(InitState) + ok = sc_cpe_socket_cleanup(InitState) end). @@ -2865,24 +2873,24 @@ sc_socket_cleanup_udp4(_Config) when is_list(_Config) -> %% (removed) when the controlling process terminates (without explicitly %% calling the close function). For a IPv6 UDP (dgram) socket. -sc_socket_cleanup_udp6(suite) -> +sc_cpe_socket_cleanup_udp6(suite) -> []; -sc_socket_cleanup_udp6(doc) -> +sc_cpe_socket_cleanup_udp6(doc) -> []; -sc_socket_cleanup_udp6(_Config) when is_list(_Config) -> - tc_try(sc_socket_cleanup_udp6, +sc_cpe_socket_cleanup_udp6(_Config) when is_list(_Config) -> + tc_try(sc_cpe_socket_cleanup_udp6, fun() -> not_yet_implemented(), InitState = #{domain => inet6, type => dgram, protocol => udp}, - ok = sc_socket_cleanup(InitState) + ok = sc_cpe_socket_cleanup(InitState) end). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -sc_socket_cleanup(InitState) -> +sc_cpe_socket_cleanup(InitState) -> OwnerSeq = [ %% *** Wait for start order part *** @@ -3022,8 +3030,16 @@ sc_socket_cleanup(InitState) -> %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% This test case is intended to test what happens when a socket is -%% locally closed while the process is calling the recv function. +%% locally closed while a process is calling the recv function. %% Socket is IPv4. +%% +%% +%% +%% We should really have a similar test cases for when the controlling +%% process exits and there are other processes in recv, accept, and +%% all the other functions. +%% +%% sc_recv_response_local_close_tcp4(suite) -> []; @@ -3032,7 +3048,7 @@ sc_recv_response_local_close_tcp4(doc) -> sc_recv_response_local_close_tcp4(_Config) when is_list(_Config) -> tc_try(sc_recv_response_local_close_tcp4, fun() -> - not_yet_implemented(), + %% not_yet_implemented(), Recv = fun(Sock) -> socket:recv(Sock) end, InitState = #{domain => inet, type => stream, @@ -3066,8 +3082,548 @@ sc_recv_response_local_close_tcp6(_Config) when is_list(_Config) -> %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -sc_receive_response_local_close_tcp(_InitState) -> - ok. +sc_receive_response_local_close_tcp(InitState) -> + %% This is the server that accepts connections. + %% But it is also suppose to close the connection socket, + %% and trigger the read failure for the handler process. + AcceptorSeq = + [ + %% *** Wait for start order part *** + #{desc => "await start (from tester)", + cmd => fun(State) -> + receive + {start, Tester} when is_pid(Tester) -> + {ok, State#{tester => Tester}} + end + end}, + #{desc => "monitor tester", + cmd => fun(#{tester := Tester} = _State) -> + _MRef = erlang:monitor(process, Tester), + ok + end}, + + %% *** Init part *** + #{desc => "which local address", + cmd => fun(#{domain := Domain} = State) -> + LAddr = which_local_addr(Domain), + LSA = #{family => Domain, addr => LAddr}, + {ok, State#{lsa => LSA}} + end}, + #{desc => "create (listen) socket", + cmd => fun(#{domain := Domain, + type := Type, + protocol := Proto} = State) -> + case socket:open(Domain, Type, Proto) of + {ok, Sock} -> + {ok, State#{lsock => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "bind to local address", + cmd => fun(#{lsock := LSock, lsa := LSA} = State) -> + case socket:bind(LSock, LSA) of + {ok, Port} -> + {ok, State#{lport => Port}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "make listen socket", + cmd => fun(#{lsock := LSock}) -> + socket:listen(LSock) + end}, + #{desc => "announce ready (init)", + cmd => fun(#{tester := Tester, lport := Port}) -> + Tester ! {ready, self(), Port}, + ok + end}, + + %% The actual test + #{desc => "await continue (connection)", + cmd => fun(#{tester := Tester} = State) -> + receive + {'DOWN', _, process, Tester, Reason} -> + ee("Unexpected DOWN regarding tester ~p: " + "~n ~p", [Reason]), + {error, {unexpected_exit, tester}}; + {continue, Tester, Handler} -> + {ok, State#{handler => Handler}} + end + end}, + #{desc => "await connection", + cmd => fun(#{lsock := LSock} = State) -> + case socket:accept(LSock) of + {ok, Sock} -> + ei("connection accepted"), + {ok, State#{csock => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "transfer new connection to handler", + cmd => fun(#{handler := Handler, csock := Sock}) -> + ok = socket:setopt(Sock, + otp, controlling_process, + Handler), + Handler ! {connection, Sock}, + ok + end}, + #{desc => "announce ready (connection)", + cmd => fun(#{tester := Tester}) -> + Tester ! {ready, self()}, + ok + end}, + #{desc => "await continue (close)", + cmd => fun(#{tester := Tester} = _State) -> + receive + {'DOWN', _, process, Tester, Reason} -> + ee("Unexpected DOWN regarding tester ~p: " + "~n ~p", [Reason]), + {error, {unexpected_exit, tester}}; + {continue, Tester} -> + ok + end + end}, + %% #{desc => "enable debug", + %% cmd => fun(#{csock := Sock}) -> + %% socket:setopt(Sock, otp, debug, true) + %% end}, + #{desc => "close (the connection) socket", + cmd => fun(#{csock := Sock}) -> + socket:close(Sock) + end}, + + #{desc => "await terminate", + cmd => fun(#{tester := Tester} = State) -> + receive + {'DOWN', _, process, Tester, Reason} -> + ee("Unexpected DOWN regarding tester ~p: " + "~n ~p", [Reason]), + {error, {unexpected_exit, tester}}; + {terminate, Tester} -> + {ok, maps:remove(tester, State)} + end + end}, + #{desc => "socket cleanup", + cmd => fun(#{lsock := Sock} = State) -> + ok = socket:close(Sock), + State1 = maps:remove(csock, State), + State2 = maps:remove(lsock, State1), + State3 = maps:remove(lport, State2), + {ok, State3} + end}, + + %% *** We are done *** + #{desc => "finish", + cmd => fun(_) -> + {ok, normal} + end} + ], + + %% The point of this is to perform the recv for which we are testing the reponse + HandlerSeq = + [ + %% *** Wait for start order part *** + #{desc => "await start (from tester)", + cmd => fun(State) -> + receive + {start, Tester} when is_pid(Tester) -> + {ok, State#{tester => Tester}} + end + end}, + #{desc => "monitor server", + cmd => fun(#{tester := Tester} = _State) -> + _MRef = erlang:monitor(process, Tester), + ok + end}, + #{desc => "announce ready (init)", + cmd => fun(#{tester := Tester}) -> + Tester ! {ready, self()}, + ok + end}, + + %% The actual test + #{desc => "await connection socket", + cmd => fun(#{tester := Tester} = State) -> + receive + {'DOWN', _, process, Tester, Reason} -> + ee("Unexpected DOWN regarding tester ~p: " + "~n ~p", [Reason]), + {error, {unexpected_exit, tester}}; + {connection, Sock} -> + {ok, State#{sock => Sock}} + end + end}, + #{desc => "announce ready (connection)", + cmd => fun(#{tester := Tester}) -> + Tester ! {ready, self()}, + ok + end}, + %% #{desc => "enable debug", + %% cmd => fun(#{sock := Sock}) -> + %% socket:setopt(Sock, otp, debug, true) + %% end}, + %% #{desc => "monitored-by", + %% cmd => fun(_) -> + %% {_, Mons} = process_info(self(), monitored_by), + %% ei("Monitored By: ~p", [Mons]), + %% ok + %% end}, + #{desc => "attempt recv", + cmd => fun(#{sock := Sock} = State) -> + case socket:recv(Sock) of + {ok, _Data} -> + ee("Unexpected data received"), + {error, unexpected_data}; + {error, closed} -> + State1 = maps:remove(sock, State), + {ok, State1}; + {error, Reason} = ERROR -> + ee("Unexpected read faulure: " + "~n ~p", [Reason]), + ERROR + end + end}, + %% #{desc => "monitored-by", + %% cmd => fun(_) -> + %% {_, Mons} = process_info(self(), monitored_by), + %% ei("Monitored By: ~p", [Mons]), + %% ok + %% end}, + #{desc => "announce ready (close)", + cmd => fun(#{tester := Tester}) -> + Tester ! {ready, self()}, + ok + end}, + #{desc => "sleep some", + cmd => fun(_) -> + ?SLEEP(1000), + ok + end}, + %% #{desc => "monitored-by", + %% cmd => fun(_) -> + %% {_, Mons} = process_info(self(), monitored_by), + %% ei("Monitored By: ~p", [Mons]), + %% ok + %% end}, + #{desc => "await terminate", + cmd => fun(#{tester := Tester} = _State) -> + receive + {'DOWN', _, process, Tester, Reason} -> + ee("Unexpected DOWN regarding tester ~p: " + "~n ~p", [Reason]), + {error, {unexpected_exit, tester}}; + {terminate, Tester} -> + ok + end + end}, + + + %% *** We are done *** + #{desc => "finish", + cmd => fun(_) -> + {ok, normal} + end} + ], + + %% The point of this is basically just to create the connection. + ClientSeq = + [ + %% *** Wait for start order part *** + #{desc => "await start (from tester)", + cmd => fun(State) -> + receive + {start, Tester} when is_pid(Tester) -> + {ok, State#{tester => Tester}} + end + end}, + + %% Init + #{desc => "which local address", + cmd => fun(#{domain := Domain} = State) -> + LAddr = which_local_addr(Domain), + LSA = #{family => Domain, addr => LAddr}, + {ok, State#{lsa => LSA}} + end}, + #{desc => "create socket", + cmd => fun(#{domain := Domain, + type := Type, + protocol := Proto} = State) -> + case socket:open(Domain, Type, Proto) of + {ok, Sock} -> + {ok, State#{sock => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "bind socket to local address", + cmd => fun(#{sock := Sock, lsa := LSA} = _State) -> + case socket:bind(Sock, LSA) of + {ok, _} -> + ok; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready", + cmd => fun(#{tester := Tester} = _State) -> + Tester ! {ready, self()}, + ok + end}, + + %% The actual test + #{desc => "await continue", + cmd => fun(#{tester := Tester} = State) -> + receive + {'DOWN', _, process, Tester, Reason} -> + ee("Unexpected DOWN regarding tester ~p: " + "~n ~p", [Tester, Reason]), + {error, {unexpected_exit, tester, Reason}}; + {continue, Tester, Port} -> + {ok, State#{lport => Port}} + end + end}, + #{desc => "connect to server", + cmd => fun(#{sock := Sock, lsa := LSA, lport := LPort}) -> + socket:connect(Sock, LSA#{port => LPort}) + end}, + #{desc => "announce ready (connection)", + cmd => fun(#{tester := Tester} = _State) -> + Tester ! {ready, self()}, + ok + end}, + + %% Cleaning up + #{desc => "await terminate (from tester)", + cmd => fun(#{tester := Tester} = State) -> + receive + {'DOWN', _, process, Tester, Reason} -> + ee("Unexpected DOWN regarding tester ~p: " + "~n ~p", [Tester, Reason]), + {error, {unexpected_exit, tester}}; + {terminate, Tester} -> + {ok, maps:remove(tester, State)} + end + end}, + #{desc => "close socket", + cmd => fun(#{sock := Sock} = State) -> + sock_close(Sock), + {ok, maps:remove(sock, State)} + end}, + + %% *** We are done *** + #{desc => "finish", + cmd => fun(_) -> + {ok, normal} + end} + ], + + TesterSeq = + [ + %% *** Init part *** + #{desc => "monitor acceptor", + cmd => fun(#{acceptor := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + #{desc => "monitor handler", + cmd => fun(#{handler := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + #{desc => "monitor client", + cmd => fun(#{client := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + + %% Start the acceptor + #{desc => "order acceptor start", + cmd => fun(#{acceptor := Pid} = _State) -> + Pid ! {start, self()}, + ok + end}, + #{desc => "await acceptor ready (init)", + cmd => fun(#{acceptor := Pid} = State) -> + receive + {'DOWN', _, process, Pid, Reason} -> + ee("Unexpected DOWN regarding acceptor ~p: " + "~n ~p", [Pid, Reason]), + {error, {unexpected_exit, acceptor}}; + {ready, Pid, Port} -> + {ok, State#{lport => Port}} + end + end}, + + %% Start the handler + #{desc => "order handler start", + cmd => fun(#{handler := Pid} = _State) -> + Pid ! {start, self()}, + ok + end}, + #{desc => "await handler ready (init)", + cmd => fun(#{handler := Pid} = _State) -> + receive + {'DOWN', _, process, Pid, Reason} -> + ee("Unexpected DOWN regarding handler ~p: " + "~n ~p", [Pid, Reason]), + {error, {unexpected_exit, acceptor}}; + {ready, Pid} -> + ok + end + end}, + + %% Start the client + #{desc => "order client start", + cmd => fun(#{client := Pid} = _State) -> + Pid ! {start, self()}, + ok + end}, + #{desc => "await client ready (init)", + cmd => fun(#{client := Pid} = _State) -> + receive + {'DOWN', _, process, Pid, Reason} -> + ee("Unexpected DOWN regarding cient ~p: " + "~n ~p", [Pid, Reason]), + {error, {unexpected_exit, acceptor}}; + {ready, Pid} -> + ok + end + end}, + + %% The actual test + #{desc => "order acceptor to continue", + cmd => fun(#{acceptor := Pid, handler := Handler} = _State) -> + Pid ! {continue, self(), Handler}, + ok + end}, + #{desc => "order client to continue", + cmd => fun(#{client := Pid, lport := Port} = _State) -> + Pid ! {continue, self(), Port}, + ok + end}, + #{desc => "await acceptor ready (connection)", + cmd => fun(#{acceptor := Pid} = _State) -> + receive + {'DOWN', _, process, Pid, Reason} -> + ee("Unexpected DOWN regarding acceptor ~p: " + "~n ~p", [Pid, Reason]), + {error, {unexpected_exit, acceptor}}; + {ready, Pid} -> + ok + end + end}, + #{desc => "await client ready (connection)", + cmd => fun(#{client := Pid} = _State) -> + receive + {'DOWN', _, process, Pid, Reason} -> + ee("Unexpected DOWN regarding client ~p: " + "~n ~p", [Pid, Reason]), + {error, {unexpected_exit, acceptor}}; + {ready, Pid} -> + ok + end + end}, + #{desc => "await handler ready (connection)", + cmd => fun(#{handler := Pid} = _State) -> + receive + {'DOWN', _, process, Pid, Reason} -> + ee("Unexpected DOWN regarding handler ~p: " + "~n ~p", [Reason]), + {error, {unexpected_exit, acceptor}}; + {ready, Pid} -> + ok + end + end}, + #{desc => "sleep some", + cmd => fun(_State) -> + ?SLEEP(1000), + ok + end}, + #{desc => "order acceptor to continue (close)", + cmd => fun(#{acceptor := Pid} = _State) -> + Pid ! {continue, self()}, + ok + end}, + #{desc => "await handler ready (close)", + cmd => fun(#{handler := Pid} = _State) -> + receive + {'DOWN', _, process, Pid, Reason} -> + ee("Unexpected DOWN regarding handler ~p: " + "~n ~p", [Pid, Reason]), + {error, {unexpected_exit, acceptor}}; + {ready, Pid} -> + ok + end + end}, + + %% Terminations + #{desc => "order handler to terminate", + cmd => fun(#{handler := Pid} = _State) -> + Pid ! {terminate, self()}, + ok + end}, + #{desc => "await handler termination", + cmd => fun(#{handler := Pid} = State) -> + receive + {'DOWN', _, process, Pid, _} -> + {ok, maps:remove(handler, State)} + end + end}, + #{desc => "order client to terminate", + cmd => fun(#{client := Pid} = _State) -> + Pid ! {terminate, self()}, + ok + end}, + #{desc => "await client termination", + cmd => fun(#{client := Pid} = State) -> + receive + {'DOWN', _, process, Pid, _} -> + {ok, maps:remove(client, State)} + end + end}, + #{desc => "order acceptor to terminate", + cmd => fun(#{acceptor := Pid} = _State) -> + Pid ! {terminate, self()}, + ok + end}, + #{desc => "await acceptor termination", + cmd => fun(#{acceptor := Pid} = State) -> + receive + {'DOWN', _, process, Pid, _} -> + {ok, maps:remove(acceptor, State)} + end + end}, + + + %% *** We are done *** + #{desc => "finish", + cmd => fun(_) -> + {ok, normal} + end} + ], + + p("start acceptor evaluator"), + AccInitState = InitState, + Acceptor = evaluator_start("acceptor", AcceptorSeq, AccInitState), + + p("start handler evaluator"), + HandlerInitState = #{}, + Handler = evaluator_start("handler", HandlerSeq, HandlerInitState), + + p("start client evaluator"), + ClientInitState = InitState, + Client = evaluator_start("client", ClientSeq, ClientInitState), + + p("start tester evaluator"), + TesterInitState = #{acceptor => Acceptor, + handler => Handler, + client => Client}, + Tester = evaluator_start("tester", TesterSeq, TesterInitState), + + p("await evaluator"), + ok = await_evaluator_finish([Acceptor, Handler, Client, Tester]). @@ -3426,9 +3982,13 @@ await_evaluator_finish(Evs, Fails) -> end. +ei(F) -> + ei(F, []). ei(F, A) -> eprint("", F, A). +ee(F) -> + ee(F, []). ee(F, A) -> eprint(" ", F, A). -- cgit v1.2.3 From 6a5013d89a02401b132483711325ca07b8357020 Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Thu, 18 Oct 2018 16:17:42 +0200 Subject: [socket-nif] Socket close Fixed a number of issues regarding monitor handling. Monitors are now wrapped in its own type (ESockMonitor). This is hopefully temporary! The reason for this is that there is no way to "initialize" a monitor after a demonitor. This could mean that after a successful demonitor, you could still get a down-callback, and thenh compare with, for instance, ctrlMOn field and get a match, even though it was just (successfully) demonitor'ed. To make debugging easier, the monitor and demonitor calls are now wrapped in their own functions, with debug printouts before and in case of failure, also after the actual calls Also, fixed a number of problems with cancel, where monitors where left still active after cleaning up current and active reader, writer and acceptor. OTP-14831 --- erts/emulator/nifs/common/socket_int.h | 8 +- erts/emulator/nifs/common/socket_nif.c | 412 ++++++++++++++++++++++++++------- 2 files changed, 329 insertions(+), 91 deletions(-) diff --git a/erts/emulator/nifs/common/socket_int.h b/erts/emulator/nifs/common/socket_int.h index f9246856fa..d89970ecd6 100644 --- a/erts/emulator/nifs/common/socket_int.h +++ b/erts/emulator/nifs/common/socket_int.h @@ -216,8 +216,12 @@ extern ERL_NIF_TERM esock_atom_einval; #define MLOCK(M) enif_mutex_lock((M)) #define MUNLOCK(M) enif_mutex_unlock((M)) -#define MONP(E,D,P,M) enif_monitor_process((E), (D), (P), (M)) -#define DEMONP(E,D,M) enif_demonitor_process((E), (D), (M)) +// #define MONP(S,E,D,P,M) enif_monitor_process((E), (D), (P), (M)) +// #define DEMONP(S,E,D,M) enif_demonitor_process((E), (D), (M)) +#define MONP(S,E,D,P,M) esock_monitor((S), (E), (D), (P), (M)) +#define DEMONP(S,E,D,M) esock_demonitor((S), (E), (D), (M)) +#define MON_INIT(M) esock_monitor_init((M)) +// #define MON_COMP(M1, M2) esock_monitor_compare((M1), (M2)) #define SELECT(E,FD,M,O,P,R) \ if (enif_select((E), (FD), (M), (O), (P), (R)) < 0) \ diff --git a/erts/emulator/nifs/common/socket_nif.c b/erts/emulator/nifs/common/socket_nif.c index 3ec19c5a60..5d2cfbc10b 100644 --- a/erts/emulator/nifs/common/socket_nif.c +++ b/erts/emulator/nifs/common/socket_nif.c @@ -725,9 +725,16 @@ static unsigned long one_value = 1; ((sap)->in4.sin_port) : -1) +typedef union { + ErlNifMonitor mon; + uint32_t raw[4]; +} ESockMonitor; + + typedef struct { ErlNifPid pid; // PID of the requesting process - ErlNifMonitor mon; // Monitor to the requesting process + // ErlNifMonitor mon; Monitor to the requesting process + ESockMonitor mon; // Monitor to the requesting process ERL_NIF_TERM ref; // The (unique) reference (ID) of the request } SocketRequestor; @@ -760,7 +767,8 @@ typedef struct { /* +++ Controller (owner) process +++ */ ErlNifPid ctrlPid; - ErlNifMonitor ctrlMon; + // ErlNifMonitor ctrlMon; + ESockMonitor ctrlMon; /* +++ Write stuff +++ */ ErlNifMutex* writeMtx; @@ -803,7 +811,8 @@ typedef struct { /* +++ Close stuff +++ */ ErlNifMutex* closeMtx; ErlNifPid closerPid; - ErlNifMonitor closerMon; + // ErlNifMonitor closerMon; + ESockMonitor closerMon; ERL_NIF_TERM closeRef; BOOLEAN_T closeLocal; @@ -2185,7 +2194,8 @@ static ERL_NIF_TERM acceptor_push(ErlNifEnv* env, static BOOLEAN_T acceptor_pop(ErlNifEnv* env, SocketDescriptor* descP, ErlNifPid* pid, - ErlNifMonitor* mon, + // ErlNifMonitor* mon, + ESockMonitor* mon, ERL_NIF_TERM* ref); static BOOLEAN_T acceptor_unqueue(ErlNifEnv* env, SocketDescriptor* descP, @@ -2201,7 +2211,8 @@ static ERL_NIF_TERM writer_push(ErlNifEnv* env, static BOOLEAN_T writer_pop(ErlNifEnv* env, SocketDescriptor* descP, ErlNifPid* pid, - ErlNifMonitor* mon, + // ErlNifMonitor* mon, + ESockMonitor* mon, ERL_NIF_TERM* ref); static BOOLEAN_T writer_unqueue(ErlNifEnv* env, SocketDescriptor* descP, @@ -2217,7 +2228,8 @@ static ERL_NIF_TERM reader_push(ErlNifEnv* env, static BOOLEAN_T reader_pop(ErlNifEnv* env, SocketDescriptor* descP, ErlNifPid* pid, - ErlNifMonitor* mon, + // ErlNifMonitor* mon, + ESockMonitor* mon, ERL_NIF_TERM* ref); static BOOLEAN_T reader_unqueue(ErlNifEnv* env, SocketDescriptor* descP, @@ -2230,8 +2242,27 @@ static void qpush(SocketRequestQueue* q, SocketRequestQueueElement* e); static SocketRequestQueueElement* qpop(SocketRequestQueue* q); static BOOLEAN_T qunqueue(ErlNifEnv* env, + SocketDescriptor* descP, + const char* slogan, SocketRequestQueue* q, const ErlNifPid* pid); + +static int esock_monitor(const char* slogan, + ErlNifEnv* env, + SocketDescriptor* descP, + const ErlNifPid* pid, + ESockMonitor* mon); +static int esock_demonitor(const char* slogan, + ErlNifEnv* env, + SocketDescriptor* descP, + ESockMonitor* monP); +static void esock_monitor_init(ESockMonitor* mon); +/* +static int esock_monitor_compare(const ErlNifMonitor* mon1, + const ESockMonitor* mon2); +*/ + + /* #if defined(HAVE_SYS_UN_H) || defined(SO_BINDTODEVICE) static size_t my_strnlen(const char *s, size_t maxlen); @@ -2282,6 +2313,7 @@ static BOOLEAN_T extract_iow(ErlNifEnv* env, static int on_load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info); + #if HAVE_IN6 # if ! defined(HAVE_IN6ADDR_ANY) || ! HAVE_IN6ADDR_ANY # if HAVE_DECL_IN6ADDR_ANY_INIT @@ -2786,7 +2818,6 @@ ERL_NIF_TERM nopen(ErlNifEnv* env, res = enif_make_resource(env, descP); enif_release_resource(descP); // We should really store a reference ... - /* Keep track of the creator * This should not be a problem but just in case * the *open* function is used with the wrong kind @@ -2795,9 +2826,10 @@ ERL_NIF_TERM nopen(ErlNifEnv* env, if (enif_self(env, &descP->ctrlPid) == NULL) return esock_make_error(env, atom_exself); - if (MONP(env, descP, + if (MONP("nopen -> ctrl", + env, descP, &descP->ctrlPid, - &descP->ctrlMon) > 0) + &descP->ctrlMon) != 0) return esock_make_error(env, atom_exmon); @@ -3396,9 +3428,10 @@ ERL_NIF_TERM naccept_listening(ErlNifEnv* env, SSDBG( descP, ("SOCKET", "naccept_listening -> would block\r\n") ); descP->currentAcceptor.pid = caller; - if (MONP(env, descP, + if (MONP("naccept_listening -> current acceptor", + env, descP, &descP->currentAcceptor.pid, - &descP->currentAcceptor.mon) > 0) + &descP->currentAcceptor.mon) != 0) return esock_make_error(env, atom_exmon); descP->currentAcceptor.ref = enif_make_copy(descP->env, ref); @@ -3473,9 +3506,10 @@ ERL_NIF_TERM naccept_listening(ErlNifEnv* env, enif_release_resource(accDescP); // We should really store a reference ... accDescP->ctrlPid = caller; - if (MONP(env, accDescP, + if (MONP("naccept_listening -> ctrl", + env, accDescP, &accDescP->ctrlPid, - &accDescP->ctrlMon) > 0) { + &accDescP->ctrlMon) != 0) { sock_close(accSock); return esock_make_error(env, atom_exmon); } @@ -3589,7 +3623,8 @@ ERL_NIF_TERM naccept_accepting(ErlNifEnv* env, SSDBG( descP, ("SOCKET", "naccept_accepting -> accept success\r\n") ); - DEMONP(env, descP, &descP->currentAcceptor.mon); + DEMONP("naccept_accepting -> current acceptor", + env, descP, &descP->currentAcceptor.mon); if ((accEvent = sock_create_event(accSock)) == INVALID_EVENT) { save_errno = sock_errno(); @@ -3611,9 +3646,10 @@ ERL_NIF_TERM naccept_accepting(ErlNifEnv* env, enif_release_resource(accDescP); // We should really store a reference ... accDescP->ctrlPid = caller; - if (MONP(env, accDescP, + if (MONP("naccept_accepting -> ctrl", + env, accDescP, &accDescP->ctrlPid, - &accDescP->ctrlMon) > 0) { + &accDescP->ctrlMon) != 0) { sock_close(accSock); return esock_make_error(env, atom_exmon); } @@ -4728,7 +4764,11 @@ ERL_NIF_TERM nclose(ErlNifEnv* env, int type = descP->type; int protocol = descP->protocol; - SSDBG( descP, ("SOCKET", "nclose -> [%d] entry\r\n", descP->sock) ); + SSDBG( descP, ("SOCKET", "nclose -> [%d] entry (0x%lX, 0x%lX, 0x%lX)\r\n", + descP->sock, + descP->currentWriterP, + descP->currentReaderP, + descP->currentAcceptorP) ); MLOCK(descP->closeMtx); @@ -4753,11 +4793,18 @@ ERL_NIF_TERM nclose(ErlNifEnv* env, /* Monitor the caller, since we should complete this operation even if * the caller dies (for whatever reason). + * + * + * + * Can we actiually use this for anything? + * + * */ - if (MONP(env, descP, - &descP->closerPid, - &descP->closerMon) > 0) { + if (MONP("nclose -> closer", + env, descP, + &descP->closerPid, + &descP->closerMon) != 0) { MUNLOCK(descP->closeMtx); return esock_make_error(env, atom_exmon); } @@ -4778,6 +4825,7 @@ ERL_NIF_TERM nclose(ErlNifEnv* env, SSDBG( descP, ("SOCKET", "nclose -> [%d] stop was called\r\n", descP->sock) ); dec_socket(domain, type, protocol); + DEMONP("nclose -> closer", env, descP, &descP->closerMon); reply = esock_atom_ok; } else if (selectRes & ERL_NIF_SELECT_STOP_SCHEDULED) { /* The stop callback function has been *scheduled* which means that we @@ -4803,6 +4851,9 @@ ERL_NIF_TERM nclose(ErlNifEnv* env, * */ + // No point in having this? + DEMONP("nclose -> closer", env, descP, &descP->closerMon); + reason = MKT2(env, atom_select, MKI(env, selectRes)); reply = esock_make_error(env, reason); } @@ -5160,7 +5211,8 @@ ERL_NIF_TERM nsetopt_otp_ctrl_proc(ErlNifEnv* env, ERL_NIF_TERM eVal) { ErlNifPid caller, newCtrlPid; - ErlNifMonitor newCtrlMon; + // ErlNifMonitor newCtrlMon; + ESockMonitor newCtrlMon; int xres; SSDBG( descP, @@ -5183,20 +5235,22 @@ ERL_NIF_TERM nsetopt_otp_ctrl_proc(ErlNifEnv* env, return esock_make_error(env, esock_atom_einval); } - if ((xres = MONP(env, descP, &newCtrlPid, &newCtrlMon)) != 0) { + if ((xres = MONP("nsetopt_otp_ctrl_proc -> (new) ctrl", + env, descP, &newCtrlPid, &newCtrlMon)) != 0) { esock_warning_msg("Failed monitor %d) (new) controlling process\r\n", xres); return esock_make_error(env, esock_atom_einval); } - if ((xres = DEMONP(env, descP, &descP->ctrlMon)) != 0) { + if ((xres = DEMONP("nsetopt_otp_ctrl_proc -> (old) ctrl", + env, descP, &descP->ctrlMon)) != 0) { esock_warning_msg("Failed demonitor (%d) " "old controlling process %T (%T)\r\n", xres, descP->ctrlPid, descP->ctrlMon); } descP->ctrlPid = newCtrlPid; - descP->ctrlMon = newCtrlMon; - + descP->ctrlMon = newCtrlMon; + SSDBG( descP, ("SOCKET", "nsetopt_otp_ctrl_proc -> done\r\n") ); return esock_atom_ok; @@ -11137,6 +11191,8 @@ ERL_NIF_TERM ncancel_accept_current(ErlNifEnv* env, SSDBG( descP, ("SOCKET", "ncancel_accept_current -> entry\r\n") ); + DEMONP("ncancel_accept_current -> current acceptor", + env, descP, &descP->currentAcceptor.mon); res = ncancel_read_select(env, descP, descP->currentAcceptor.ref); SSDBG( descP, ("SOCKET", "ncancel_accept_current -> cancel res: %T\r\n", res) ); @@ -11255,6 +11311,8 @@ ERL_NIF_TERM ncancel_send_current(ErlNifEnv* env, SSDBG( descP, ("SOCKET", "ncancel_send_current -> entry\r\n") ); + DEMONP("ncancel_recv_current -> current writer", + env, descP, &descP->currentWriter.mon); res = ncancel_write_select(env, descP, descP->currentWriter.ref); SSDBG( descP, ("SOCKET", "ncancel_send_current -> cancel res: %T\r\n", res) ); @@ -11371,6 +11429,8 @@ ERL_NIF_TERM ncancel_recv_current(ErlNifEnv* env, SSDBG( descP, ("SOCKET", "ncancel_recv_current -> entry\r\n") ); + DEMONP("ncancel_recv_current -> current reader", + env, descP, &descP->currentReader.mon); res = ncancel_read_select(env, descP, descP->currentReader.ref); SSDBG( descP, ("SOCKET", "ncancel_recv_current -> cancel res: %T\r\n", res) ); @@ -11568,7 +11628,8 @@ ERL_NIF_TERM send_check_result(ErlNifEnv* env, cnt_inc(&descP->writePkgCnt, 1); cnt_inc(&descP->writeByteCnt, written); if (descP->currentWriterP != NULL) - DEMONP(env, descP, &descP->currentWriter.mon); + DEMONP("send_check_result -> current writer", + env, descP, &descP->currentWriter.mon); SSDBG( descP, ("SOCKET", "send_check_result -> " @@ -11607,7 +11668,8 @@ ERL_NIF_TERM send_check_result(ErlNifEnv* env, if ((saveErrno != EAGAIN) && (saveErrno != EINTR)) { ErlNifPid pid; - ErlNifMonitor mon; + // ErlNifMonitor mon; + ESockMonitor mon; ERL_NIF_TERM ref, res; /* @@ -11622,13 +11684,15 @@ ERL_NIF_TERM send_check_result(ErlNifEnv* env, res = esock_make_error_errno(env, saveErrno); if (descP->currentWriterP != NULL) { - DEMONP(env, descP, &descP->currentWriter.mon); + DEMONP("send_check_result -> current writer", + env, descP, &descP->currentWriter.mon); while (writer_pop(env, descP, &pid, &mon, &ref)) { SSDBG( descP, ("SOCKET", "send_check_result -> abort %T\r\n", pid) ); send_msg_nif_abort(env, ref, res, &pid); - DEMONP(env, descP, &mon); + DEMONP("send_check_result -> pop'ed writer", + env, descP, &mon); } } @@ -11660,9 +11724,10 @@ ERL_NIF_TERM send_check_result(ErlNifEnv* env, if (enif_self(env, &caller) == NULL) return esock_make_error(env, atom_exself); descP->currentWriter.pid = caller; - if (MONP(env, descP, + if (MONP("send_check_result -> current writer", + env, descP, &descP->currentWriter.pid, - &descP->currentWriter.mon) > 0) + &descP->currentWriter.mon) != 0) return esock_make_error(env, atom_exmon); descP->currentWriter.ref = enif_make_copy(descP->env, sendRef); descP->currentWriterP = &descP->currentWriter; @@ -11747,9 +11812,10 @@ char* recv_init_current_reader(ErlNifEnv* env, return str_exself; descP->currentReader.pid = caller; - if (MONP(env, descP, + if (MONP("recv_init_current_reader -> current reader", + env, descP, &descP->currentReader.pid, - &descP->currentReader.mon) > 0) { + &descP->currentReader.mon) != 0) { return str_exmon; } descP->currentReader.ref = enif_make_copy(descP->env, recvRef); @@ -11774,7 +11840,8 @@ ERL_NIF_TERM recv_update_current_reader(ErlNifEnv* env, { if (descP->currentReaderP != NULL) { - DEMONP(env, descP, &descP->currentReader.mon); + DEMONP("recv_update_current_reader -> current reader", + env, descP, &descP->currentReader.mon); if (reader_pop(env, descP, &descP->currentReader.pid, @@ -11822,16 +11889,19 @@ void recv_error_current_reader(ErlNifEnv* env, { if (descP->currentReaderP != NULL) { ErlNifPid pid; - ErlNifMonitor mon; + // ErlNifMonitor mon; + ESockMonitor mon; ERL_NIF_TERM ref; - DEMONP(env, descP, &descP->currentReader.mon); + DEMONP("recv_error_current_reader -> current reader", + env, descP, &descP->currentReader.mon); while (reader_pop(env, descP, &pid, &mon, &ref)) { SSDBG( descP, ("SOCKET", "recv_error_current_reader -> abort %T\r\n", pid) ); send_msg_nif_abort(env, ref, reason, &pid); - DEMONP(env, descP, &mon); + DEMONP("recv_error_current_reader -> pop'ed reader", + env, descP, &mon); } } } @@ -13975,6 +14045,11 @@ SocketDescriptor* alloc_descriptor(SOCKET sock, HANDLE event) descP->sock = sock; descP->event = event; + MON_INIT(&descP->currentWriter.mon); + MON_INIT(&descP->currentReader.mon); + MON_INIT(&descP->currentAcceptor.mon); + MON_INIT(&descP->ctrlMon); + MON_INIT(&descP->closerMon); } return descP; @@ -14593,7 +14668,8 @@ ERL_NIF_TERM acceptor_push(ErlNifEnv* env, reqP->pid = pid; reqP->ref = enif_make_copy(descP->env, ref); - if (MONP(env, descP, &pid, &reqP->mon) > 0) { + if (MONP("acceptor_push -> acceptor request", + env, descP, &pid, &reqP->mon) != 0) { FREE(reqP); return esock_make_error(env, atom_exmon); } @@ -14613,7 +14689,8 @@ static BOOLEAN_T acceptor_pop(ErlNifEnv* env, SocketDescriptor* descP, ErlNifPid* pid, - ErlNifMonitor* mon, + // ErlNifMonitor* mon, + ESockMonitor* mon, ERL_NIF_TERM* ref) { SocketRequestQueueElement* e = qpop(&descP->acceptorsQ); @@ -14644,7 +14721,8 @@ BOOLEAN_T acceptor_unqueue(ErlNifEnv* env, SocketDescriptor* descP, const ErlNifPid* pid) { - return qunqueue(env, &descP->acceptorsQ, pid); + return qunqueue(env, descP, "qunqueue -> waiting acceptor", + &descP->acceptorsQ, pid); } @@ -14679,7 +14757,8 @@ ERL_NIF_TERM writer_push(ErlNifEnv* env, reqP->pid = pid; reqP->ref = enif_make_copy(descP->env, ref); - if (MONP(env, descP, &pid, &reqP->mon) > 0) { + if (MONP("writer_push -> writer request", + env, descP, &pid, &reqP->mon) != 0) { FREE(reqP); return esock_make_error(env, atom_exmon); } @@ -14699,7 +14778,8 @@ static BOOLEAN_T writer_pop(ErlNifEnv* env, SocketDescriptor* descP, ErlNifPid* pid, - ErlNifMonitor* mon, + // ErlNifMonitor* mon, + ESockMonitor* mon, ERL_NIF_TERM* ref) { SocketRequestQueueElement* e = qpop(&descP->writersQ); @@ -14730,7 +14810,8 @@ BOOLEAN_T writer_unqueue(ErlNifEnv* env, SocketDescriptor* descP, const ErlNifPid* pid) { - return qunqueue(env, &descP->writersQ, pid); + return qunqueue(env, descP, "qunqueue -> waiting writer", + &descP->writersQ, pid); } @@ -14765,7 +14846,8 @@ ERL_NIF_TERM reader_push(ErlNifEnv* env, reqP->pid = pid; reqP->ref = enif_make_copy(descP->env, ref); - if (MONP(env, descP, &pid, &reqP->mon) > 0) { + if (MONP("reader_push -> reader request", + env, descP, &pid, &reqP->mon) != 0) { FREE(reqP); return esock_make_error(env, atom_exmon); } @@ -14785,7 +14867,8 @@ static BOOLEAN_T reader_pop(ErlNifEnv* env, SocketDescriptor* descP, ErlNifPid* pid, - ErlNifMonitor* mon, + // ErlNifMonitor* mon, + ESockMonitor* mon, ERL_NIF_TERM* ref) { SocketRequestQueueElement* e = qpop(&descP->readersQ); @@ -14816,7 +14899,8 @@ BOOLEAN_T reader_unqueue(ErlNifEnv* env, SocketDescriptor* descP, const ErlNifPid* pid) { - return qunqueue(env, &descP->readersQ, pid); + return qunqueue(env, descP, "qunqueue -> waiting reader", + &descP->readersQ, pid); } @@ -14880,6 +14964,8 @@ SocketRequestQueueElement* qpop(SocketRequestQueue* q) static BOOLEAN_T qunqueue(ErlNifEnv* env, + SocketDescriptor* descP, + const char* slogan, SocketRequestQueue* q, const ErlNifPid* pid) { @@ -14892,6 +14978,8 @@ BOOLEAN_T qunqueue(ErlNifEnv* env, /* We have a match */ + DEMONP(slogan, env, descP, &e->data.mon); + if (p != NULL) { /* Not the first, but could be the last */ if (q->last == e) { @@ -14965,6 +15053,97 @@ void cnt_dec(uint32_t* cnt, uint32_t dec) +/* ---------------------------------------------------------------------- + * M o n i t o r W r a p p e r F u n c t i o n s + * ---------------------------------------------------------------------- + */ + +static +int esock_monitor(const char* slogan, + ErlNifEnv* env, + SocketDescriptor* descP, + const ErlNifPid* pid, + ESockMonitor* monP) +{ + int res; + + SSDBG( descP, ("SOCKET", "[%d] %s: try monitor", descP->sock, slogan) ); + // esock_dbg_printf("MONP", "[%d] %s\r\n", descP->sock, slogan); + res = enif_monitor_process(env, descP, pid, &monP->mon); + + if (res != 0) { + SSDBG( descP, ("SOCKET", "[%d] monitor failed: %d", descP->sock, res) ); + // esock_dbg_printf("MONP", "[%d] failed: %d\r\n", descP->sock, res); + } /* else { + esock_dbg_printf("MONP", + "[%d] success: " + "%u,%u,%u,%u\r\n", + descP->sock, + monP->raw[0], monP->raw[1], + monP->raw[2], monP->raw[3]); + } */ + + return res; +} + + +static +int esock_demonitor(const char* slogan, + ErlNifEnv* env, + SocketDescriptor* descP, + ESockMonitor* monP) +{ + int res; + + SSDBG( descP, ("SOCKET", "[%d] %s: try demonitor\r\n", descP->sock, slogan) ); + /* + esock_dbg_printf("DEMONP", "[%d] %s: %u,%u,%u,%u\r\n", + descP->sock, slogan, + monP->raw[0], monP->raw[1], + monP->raw[2], monP->raw[3]); + */ + + res = enif_demonitor_process(env, descP, &monP->mon); + + if (res == 0) { + esock_monitor_init(monP); + } else { + SSDBG( descP, + ("SOCKET", "[%d] demonitor failed: %d\r\n", descP->sock, res) ); + /* + esock_dbg_printf("DEMONP", "[%d] failed: %d\r\n", descP->sock, res); + */ + } + + return res; +} + + +static +void esock_monitor_init(ESockMonitor* monP) +{ + int i; + + /* + * UGLY, + * but since we don't have a ERL_NIF_MONITOR_NULL, + * this will have to do for now... + */ + for (i = 0; i < 4; i++) + monP->raw[i] = 0; + +} + +/* +static +int esock_monitor_compare(const ErlNifMonitor* mon1, + const ESockMonitor* mon2) +{ + return enif_compare_monitors(mon1, &mon2->mon); +} +*/ + + /* ---------------------------------------------------------------------- * C a l l b a c k F u n c t i o n s * ---------------------------------------------------------------------- @@ -15015,13 +15194,15 @@ static void socket_stop(ErlNifEnv* env, void* obj, int fd, int is_direct_call) { SocketDescriptor* descP = (SocketDescriptor*) obj; - int dres; - + + /* + esock_dbg_printf("STOP", "[%d] begin\r\n", descP->sock); + */ + SSDBG( descP, - ("SOCKET", "socket_stop -> entry when" - "\r\n sock: %d (%d)" - "\r\n Is Direct Call: %s" - "\r\n", descP->sock, fd, B2S(is_direct_call)) ); + ("SOCKET", "socket_stop -> entry when %s" + "\r\n sock: %d (%d)" + "\r\n", ((is_direct_call) ? "called" : "scheduled"), descP->sock, fd) ); MLOCK(descP->writeMtx); MLOCK(descP->readMtx); @@ -15033,8 +15214,7 @@ void socket_stop(ErlNifEnv* env, void* obj, int fd, int is_direct_call) descP->state = SOCKET_STATE_CLOSING; // Just in case...??? descP->isReadable = FALSE; descP->isWritable = FALSE; - - + /* We should check that we actually have a monitor. * This *should* be done with a "NULL" monitor value, * which there currently is none... @@ -15042,24 +15222,19 @@ void socket_stop(ErlNifEnv* env, void* obj, int fd, int is_direct_call) * its no point in demonitor. Also, we not actually have * a monitor in that case... */ - SSDBG( descP, - ("SOCKET", - "socket_stop -> demonitor (maybe) controlling process (%T)\r\n", - descP->ctrlPid) ); - dres = DEMONP(env, descP, &descP->ctrlMon); - SSDBG( descP, ("SOCKET", "socket_stop -> demonitor result: %d\r\n", dres) ); - - + DEMONP("socket_stop -> ctrl", env, descP, &descP->ctrlMon); + + /* + esock_dbg_printf("STOP", "[%d] maybe handle current writer (0x%lX)\r\n", + descP->sock, descP->currentReaderP); + */ if (descP->currentWriterP != NULL) { /* We have a (current) writer and *may* therefor also have * writers waiting. */ - SSDBG( descP, - ("SOCKET", - "socket_stop -> demonitor (maybe) current writer: %T, %T\r\n", - descP->currentWriter.pid, descP->currentWriter.ref) ); - DEMONP(env, descP, &descP->currentWriter.mon); + DEMONP("socket_stop -> current writer", + env, descP, &descP->currentWriter.mon); SSDBG( descP, ("SOCKET", "socket_stop -> handle current writer\r\n") ); if (!compare_pids(env, @@ -15082,18 +15257,19 @@ void socket_stop(ErlNifEnv* env, void* obj, int fd, int is_direct_call) SSDBG( descP, ("SOCKET", "socket_stop -> handle waiting writer(s)\r\n") ); inform_waiting_procs(env, descP, &descP->writersQ, TRUE, atom_closed); } - + + /* + esock_dbg_printf("STOP", "[%d] maybe handle current reader (0x%lX)\r\n", + descP->sock, descP->currentReaderP); + */ if (descP->currentReaderP != NULL) { /* We have a (current) reader and *may* therefor also have * readers waiting. */ - SSDBG( descP, - ("SOCKET", - "socket_stop -> demonitor (maybe) current reader: %T, %T\r\n", - descP->currentReader.pid, descP->currentReader.ref) ); - DEMONP(env, descP, &descP->currentReader.mon); + DEMONP("socket_stop -> current reader", + env, descP, &descP->currentReader.mon); SSDBG( descP, ("SOCKET", "socket_stop -> handle current reader\r\n") ); if (!compare_pids(env, @@ -15116,17 +15292,18 @@ void socket_stop(ErlNifEnv* env, void* obj, int fd, int is_direct_call) SSDBG( descP, ("SOCKET", "socket_stop -> handle waiting reader(s)\r\n") ); inform_waiting_procs(env, descP, &descP->readersQ, TRUE, atom_closed); } - + + /* + esock_dbg_printf("STOP", "[%d] maybe handle current acceptor (0x%lX)\r\n", + descP->sock, descP->currentReaderP); + */ if (descP->currentAcceptorP != NULL) { /* We have a (current) acceptor and *may* therefor also have * acceptors waiting. */ - SSDBG( descP, - ("SOCKET", - "socket_stop -> demonitor (maybe) current acceptor: %T, %T\r\n", - descP->currentAcceptor.pid, descP->currentAcceptor.ref) ); - DEMONP(env, descP, &descP->currentAcceptor.mon); + DEMONP("socket_stop -> current acceptor", + env, descP, &descP->currentAcceptor.mon); SSDBG( descP, ("SOCKET", "socket_stop -> handle current acceptor\r\n") ); if (!compare_pids(env, @@ -15178,7 +15355,8 @@ void socket_stop(ErlNifEnv* env, void* obj, int fd, int is_direct_call) send_msg(env, MKT2(env, atom_close, descP->closeRef), &descP->closerPid); - DEMONP(env, descP, &descP->closerMon); + DEMONP("socket_stop -> closer", + env, descP, &descP->closerMon); } else { @@ -15194,6 +15372,10 @@ void socket_stop(ErlNifEnv* env, void* obj, int fd, int is_direct_call) if (!is_direct_call) { + if (descP->closeLocal) { + DEMONP("socket_stop -> closer", + env, descP, &descP->closerMon); + } descP->sock = INVALID_SOCKET; descP->event = INVALID_EVENT; descP->state = SOCKET_STATE_CLOSED; @@ -15206,6 +15388,10 @@ void socket_stop(ErlNifEnv* env, void* obj, int fd, int is_direct_call) MUNLOCK(descP->readMtx); MUNLOCK(descP->writeMtx); + /* + esock_dbg_printf("STOP", "[%d] end\r\n", descP->sock); + */ + SSDBG( descP, ("SOCKET", "socket_stop -> done (%d, %d)\r\n", descP->sock, fd) ); @@ -15245,7 +15431,8 @@ void inform_waiting_procs(ErlNifEnv* env, currentP->data.ref, reason, ¤tP->data.pid)) ); - DEMONP(env, descP, ¤tP->data.mon); + DEMONP("inform_waiting_procs -> current 'request'", + env, descP, ¤tP->data.mon); nextP = currentP->nextP; if (free) FREE(currentP); currentP = nextP; @@ -15270,16 +15457,43 @@ void socket_down(ErlNifEnv* env, const ErlNifMonitor* mon) { SocketDescriptor* descP = (SocketDescriptor*) obj; + ESockMonitor* monP = (ESockMonitor*) mon; SSDBG( descP, ("SOCKET", "socket_down -> entry with" - "\r\n sock: %d" - "\r\n pid: %T" - "\r\n Closed: %s" - "\r\n Closing: %s" + "\r\n sock: %d" + "\r\n pid: %T" + "\r\n Close: %s (%s)" "\r\n", descP->sock, *pid, - B2S(IS_CLOSED(descP)), B2S(IS_CLOSING(descP))) ); + B2S(IS_CLOSED(descP)), + B2S(IS_CLOSING(descP))) ); + + /* + esock_dbg_printf("DOWN", + "[%d] begin %u,%u,%u,%d\r\n", + descP->sock, + monP->raw[0], monP->raw[1], + monP->raw[2], monP->raw[3]); + */ + /* + if (MON_COMP(mon, &descP->ctrlMon) == 0) { + SSDBG( descP, ("SOCKET", "socket_down -> controlling process mon\r\n") ); + } else if (MON_COMP(mon, &descP->closerMon) == 0) { + SSDBG( descP, ("SOCKET", "socket_down -> closer mon\r\n") ); + } else if ((descP->currentWriterP != NULL) && + (MON_COMP(mon, &descP->currentWriter.mon) == 0)) { + SSDBG( descP, ("SOCKET", "socket_down -> current writer mon\r\n") ); + } else if ((descP->currentReaderP != NULL) && + (MON_COMP(mon, &descP->currentReader.mon) == 0)) { + SSDBG( descP, ("SOCKET", "socket_down -> current reader mon\r\n") ); + } else if ((descP->currentAcceptorP != NULL) && + (MON_COMP(mon, &descP->currentAcceptor.mon) == 0)) { + SSDBG( descP, ("SOCKET", "socket_down -> current acceptor mon\r\n") ); + } else { + SSDBG( descP, ("SOCKET", "socket_down -> OTHER mon\r\n") ); + } + */ if (compare_pids(env, &descP->ctrlPid, pid)) { int selectRes; @@ -15288,14 +15502,23 @@ void socket_down(ErlNifEnv* env, * we leave it to the stop callback function. */ - SSDBG( descP, ("SOCKET", "socket_down -> controlling process exit\r\n") ); + SSDBG( descP, + ("SOCKET", "socket_down -> controlling process exit\r\n") ); descP->state = SOCKET_STATE_CLOSING; descP->closeLocal = TRUE; descP->closerPid = *pid; - descP->closerMon = *mon; + descP->closerMon = (ESockMonitor) *mon; descP->closeRef = MKREF(env); // Do we really need this in this case? + /* + esock_dbg_printf("DOWN", + "[%d] select stop %u,%u,%u,%d\r\n", + descP->sock, + monP->raw[0], monP->raw[1], + monP->raw[2], monP->raw[3]); + */ + selectRes = enif_select(env, descP->sock, (ERL_NIF_SELECT_STOP), descP, NULL, descP->closeRef); @@ -15374,7 +15597,10 @@ void socket_down(ErlNifEnv* env, "\r\n Select Res: %d" "\r\n Controlling Process: %T" "\r\n Descriptor: %d" - "\r\n", selectRes, pid, descP->sock); + "\r\n Monitor: %u.%u.%u.%u" + "\r\n", selectRes, pid, descP->sock, + monP->raw[0], monP->raw[1], + monP->raw[2], monP->raw[3]); } @@ -15400,6 +15626,14 @@ void socket_down(ErlNifEnv* env, MUNLOCK(descP->readMtx); } + + /* + esock_dbg_printf("DOWN", + "[%d] end %u,%u,%u,%d\r\n", + descP->sock, + monP->raw[0], monP->raw[1], + monP->raw[2], monP->raw[3]); + */ SSDBG( descP, ("SOCKET", "socket_down -> done\r\n") ); -- cgit v1.2.3 From 598ecec8b59509f223807d36e9dd1244d7d80fa2 Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Thu, 18 Oct 2018 16:25:47 +0200 Subject: [socket-nif|test] Some minor test case re-grouping --- lib/kernel/test/socket_SUITE.erl | 267 +++++++++++++++++++-------------------- 1 file changed, 128 insertions(+), 139 deletions(-) diff --git a/lib/kernel/test/socket_SUITE.erl b/lib/kernel/test/socket_SUITE.erl index 8ab39f6ffe..4ee2eec39a 100644 --- a/lib/kernel/test/socket_SUITE.erl +++ b/lib/kernel/test/socket_SUITE.erl @@ -71,18 +71,16 @@ sc_cpe_socket_cleanup_tcp6/1, sc_cpe_socket_cleanup_udp4/1, sc_cpe_socket_cleanup_udp6/1, - sc_recv_response_local_close_tcp4/1, - sc_recv_response_local_close_tcp6/1, - sc_recv_response_remote_close_tcp4/1, - sc_recv_response_remote_close_tcp6/1, - sc_recvmsg_response_local_close_tcp4/1, - sc_recvmsg_response_local_close_tcp6/1, - sc_recvmsg_response_remote_close_tcp4/1, - sc_recvmsg_response_remote_close_tcp6/1, - sc_acceptor_response_local_close_tcp4/1, - sc_acceptor_response_local_close_tcp6/1, - sc_acceptor_response_remote_close_tcp4/1, - sc_acceptor_response_remote_close_tcp6/1 + sc_lc_recv_response_tcp4/1, + sc_lc_recv_response_tcp6/1, + sc_lc_recvmsg_response_tcp4/1, + sc_lc_recvmsg_response_tcp6/1, + sc_lc_acceptor_response_tcp4/1, + sc_lc_acceptor_response_tcp6/1, + sc_rc_recv_response_tcp4/1, + sc_rc_recv_response_tcp6/1, + sc_rc_recvmsg_response_tcp4/1, + sc_rc_recvmsg_response_tcp6/1 %% Tickets ]). @@ -131,7 +129,9 @@ groups() -> {api_options, [], api_options_cases()}, {api_op_with_timeout, [], api_op_with_timeout_cases()}, {socket_closure, [], socket_closure_cases()}, - {sc_ctrl_proc_exit, [], sc_cp_exit_cases()} + {sc_ctrl_proc_exit, [], sc_cp_exit_cases()}, + {sc_local_close, [], sc_lc_cases()}, + {sc_remote_close, [], sc_rc_cases()} %% {tickets, [], ticket_cases()} ]. @@ -187,21 +187,8 @@ api_op_with_timeout_cases() -> socket_closure_cases() -> [ {group, sc_ctrl_proc_exit}, - - sc_recv_response_local_close_tcp4, - sc_recv_response_local_close_tcp6, - sc_recv_response_remote_close_tcp4, - sc_recv_response_remote_close_tcp6, - - sc_recvmsg_response_local_close_tcp4, - sc_recvmsg_response_local_close_tcp6, - sc_recvmsg_response_remote_close_tcp4, - sc_recvmsg_response_remote_close_tcp6, - - sc_acceptor_response_local_close_tcp4, - sc_acceptor_response_local_close_tcp6, - sc_acceptor_response_remote_close_tcp4, - sc_acceptor_response_remote_close_tcp6 + {group, sc_local_close}, + {group, sc_remote_close} ]. %% These cases are all about socket cleanup after the controlling process @@ -214,6 +201,30 @@ sc_cp_exit_cases() -> sc_cpe_socket_cleanup_udp6 ]. +%% These cases tests what happens when the socket is closed locally. +sc_lc_cases() -> + [ + sc_lc_recv_response_tcp4, + sc_lc_recv_response_tcp6, + + sc_lc_recvmsg_response_tcp4, + sc_lc_recvmsg_response_tcp6, + + sc_lc_acceptor_response_tcp4, + sc_lc_acceptor_response_tcp6 + ]. + +%% These cases tests what happens when the socket is closed remotely. +sc_rc_cases() -> + [ + sc_rc_recv_response_tcp4, + sc_rc_recv_response_tcp6, + + sc_rc_recvmsg_response_tcp4, + sc_rc_recvmsg_response_tcp6 + ]. + + %% ticket_cases() -> %% []. @@ -2332,6 +2343,7 @@ api_to_receive_tcp(InitState) -> cmd => fun(#{lsock := LSock} = State) -> case socket:accept(LSock) of {ok, Sock} -> + %% ok = socket:setopt(Sock, otp, debug, true), {ok, State#{sock => Sock}}; {error, _} = ERROR -> ERROR @@ -2375,16 +2387,44 @@ api_to_receive_tcp(InitState) -> {ok, maps:remove(tester, State)} end end}, + %% #{desc => "sleep some (before traffic close)", + %% cmd => fun(_) -> + %% ?SLEEP(1000), + %% ok + %% end}, + %% #{desc => "monitored-by", + %% cmd => fun(_) -> + %% {_, Mons} = process_info(self(), monitored_by), + %% ei("Monitored By: ~p", [Mons]), + %% ok + %% end}, #{desc => "close (traffic) socket", cmd => fun(#{sock := Sock} = State) -> + %% ok = socket:setopt(Sock, otp, debug, true), sock_close(Sock), {ok, maps:remove(sock, State)} end}, + %% #{desc => "monitored-by", + %% cmd => fun(_) -> + %% {_, Mons} = process_info(self(), monitored_by), + %% ei("Monitored By: ~p", [Mons]), + %% ok + %% end}, + %% #{desc => "sleep some (before listen close)", + %% cmd => fun(_) -> + %% ?SLEEP(1000), + %% ok + %% end}, #{desc => "close (listen) socket", cmd => fun(#{lsock := LSock} = State) -> sock_close(LSock), {ok, maps:remove(lsock, State)} end}, + %% #{desc => "sleep some (after listen close)", + %% cmd => fun(_) -> + %% ?SLEEP(1000), + %% ok + %% end}, %% *** We are done *** #{desc => "finish", @@ -2938,10 +2978,10 @@ sc_cpe_socket_cleanup(InitState) -> {ok, maps:remove(tester, State)} end end}, - #{desc => "enable (otp) debug", - cmd => fun(#{sock := Sock} = _State) -> - ok = socket:setopt(Sock, otp, debug, true) - end}, + %% #{desc => "enable (otp) debug", + %% cmd => fun(#{sock := Sock} = _State) -> + %% ok = socket:setopt(Sock, otp, debug, true) + %% end}, %% *** We are done *** #{desc => "finish", @@ -3041,12 +3081,12 @@ sc_cpe_socket_cleanup(InitState) -> %% %% -sc_recv_response_local_close_tcp4(suite) -> +sc_lc_recv_response_tcp4(suite) -> []; -sc_recv_response_local_close_tcp4(doc) -> +sc_lc_recv_response_tcp4(doc) -> []; -sc_recv_response_local_close_tcp4(_Config) when is_list(_Config) -> - tc_try(sc_recv_response_local_close_tcp4, +sc_lc_recv_response_tcp4(_Config) when is_list(_Config) -> + tc_try(sc_lc_recv_response_tcp4, fun() -> %% not_yet_implemented(), Recv = fun(Sock) -> socket:recv(Sock) end, @@ -3054,7 +3094,7 @@ sc_recv_response_local_close_tcp4(_Config) when is_list(_Config) -> type => stream, protocol => tcp, recv => Recv}, - ok = sc_receive_response_local_close_tcp(InitState) + ok = sc_lc_receive_response_tcp(InitState) end). @@ -3063,12 +3103,12 @@ sc_recv_response_local_close_tcp4(_Config) when is_list(_Config) -> %% locally closed while the process is calling the recv function. %% Socket is IPv6. -sc_recv_response_local_close_tcp6(suite) -> +sc_lc_recv_response_tcp6(suite) -> []; -sc_recv_response_local_close_tcp6(doc) -> +sc_lc_recv_response_tcp6(doc) -> []; -sc_recv_response_local_close_tcp6(_Config) when is_list(_Config) -> - tc_try(sc_recv_response_local_close_tcp6, +sc_lc_recv_response_tcp6(_Config) when is_list(_Config) -> + tc_try(sc_lc_recv_response_tcp6, fun() -> not_yet_implemented(), Recv = fun(Sock) -> socket:recv(Sock) end, @@ -3076,13 +3116,13 @@ sc_recv_response_local_close_tcp6(_Config) when is_list(_Config) -> type => stream, protocol => tcp, recv => Recv}, - ok = sc_receive_response_local_close_tcp(InitState) + ok = sc_lc_receive_response_tcp(InitState) end). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -sc_receive_response_local_close_tcp(InitState) -> +sc_lc_receive_response_tcp(InitState) -> %% This is the server that accepts connections. %% But it is also suppose to close the connection socket, %% and trigger the read failure for the handler process. @@ -3271,8 +3311,8 @@ sc_receive_response_local_close_tcp(InitState) -> %% ok %% end}, #{desc => "attempt recv", - cmd => fun(#{sock := Sock} = State) -> - case socket:recv(Sock) of + cmd => fun(#{sock := Sock, recv := Recv} = State) -> + case Recv(Sock) of {ok, _Data} -> ee("Unexpected data received"), {error, unexpected_data}; @@ -3609,7 +3649,7 @@ sc_receive_response_local_close_tcp(InitState) -> Acceptor = evaluator_start("acceptor", AcceptorSeq, AccInitState), p("start handler evaluator"), - HandlerInitState = #{}, + HandlerInitState = #{recv => maps:get(recv, InitState)}, Handler = evaluator_start("handler", HandlerSeq, HandlerInitState), p("start client evaluator"), @@ -3632,12 +3672,12 @@ sc_receive_response_local_close_tcp(InitState) -> %% remotely closed while the process is calling the recv function. %% Socket is IPv4. -sc_recv_response_remote_close_tcp4(suite) -> +sc_rc_recv_response_tcp4(suite) -> []; -sc_recv_response_remote_close_tcp4(doc) -> +sc_rc_recv_response_tcp4(doc) -> []; -sc_recv_response_remote_close_tcp4(_Config) when is_list(_Config) -> - tc_try(sc_recv_response_remote_close_tcp4, +sc_rc_recv_response_tcp4(_Config) when is_list(_Config) -> + tc_try(sc_rc_recv_response_tcp4, fun() -> not_yet_implemented(), Recv = fun(Sock) -> socket:recv(Sock) end, @@ -3645,7 +3685,7 @@ sc_recv_response_remote_close_tcp4(_Config) when is_list(_Config) -> type => stream, protocol => tcp, recv => Recv}, - ok = sc_receive_response_remote_close_tcp(InitState) + ok = sc_rc_receive_response_tcp(InitState) end). @@ -3654,12 +3694,12 @@ sc_recv_response_remote_close_tcp4(_Config) when is_list(_Config) -> %% remotely closed while the process is calling the recv function. %% Socket is IPv6. -sc_recv_response_remote_close_tcp6(suite) -> +sc_rc_recv_response_tcp6(suite) -> []; -sc_recv_response_remote_close_tcp6(doc) -> +sc_rc_recv_response_tcp6(doc) -> []; -sc_recv_response_remote_close_tcp6(_Config) when is_list(_Config) -> - tc_try(sc_recv_response_remote_close_tcp6, +sc_rc_recv_response_tcp6(_Config) when is_list(_Config) -> + tc_try(sc_rc_recv_response_tcp6, fun() -> not_yet_implemented(), Recv = fun(Sock) -> socket:recv(Sock) end, @@ -3667,13 +3707,13 @@ sc_recv_response_remote_close_tcp6(_Config) when is_list(_Config) -> type => stream, protocol => tcp, recv => Recv}, - ok = sc_receive_response_remote_close_tcp(InitState) + ok = sc_rc_receive_response_tcp(InitState) end). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -sc_receive_response_remote_close_tcp(_InitState) -> +sc_rc_receive_response_tcp(_InitState) -> ok. @@ -3683,12 +3723,12 @@ sc_receive_response_remote_close_tcp(_InitState) -> %% locally closed while the process is calling the recvmsg function. %% Socket is IPv4. -sc_recvmsg_response_local_close_tcp4(suite) -> +sc_lc_recvmsg_response_tcp4(suite) -> []; -sc_recvmsg_response_local_close_tcp4(doc) -> +sc_lc_recvmsg_response_tcp4(doc) -> []; -sc_recvmsg_response_local_close_tcp4(_Config) when is_list(_Config) -> - tc_try(sc_recvmsg_response_local_close_tcp4, +sc_lc_recvmsg_response_tcp4(_Config) when is_list(_Config) -> + tc_try(sc_lc_recvmsg_response_tcp4, fun() -> not_yet_implemented(), Recv = fun(Sock) -> socket:recvmsg(Sock) end, @@ -3696,7 +3736,7 @@ sc_recvmsg_response_local_close_tcp4(_Config) when is_list(_Config) -> type => stream, protocol => tcp, recv => Recv}, - ok = sc_receive_response_local_close_tcp(InitState) + ok = sc_lc_receive_response_tcp(InitState) end). @@ -3705,12 +3745,12 @@ sc_recvmsg_response_local_close_tcp4(_Config) when is_list(_Config) -> %% locally closed while the process is calling the recvmsg function. %% Socket is IPv6. -sc_recvmsg_response_local_close_tcp6(suite) -> +sc_lc_recvmsg_response_tcp6(suite) -> []; -sc_recvmsg_response_local_close_tcp6(doc) -> +sc_lc_recvmsg_response_tcp6(doc) -> []; -sc_recvmsg_response_local_close_tcp6(_Config) when is_list(_Config) -> - tc_try(sc_recvmsg_response_local_close_tcp6, +sc_lc_recvmsg_response_tcp6(_Config) when is_list(_Config) -> + tc_try(sc_recvmsg_response_tcp6, fun() -> not_yet_implemented(), Recv = fun(Sock) -> socket:recvmsg(Sock) end, @@ -3718,7 +3758,7 @@ sc_recvmsg_response_local_close_tcp6(_Config) when is_list(_Config) -> type => stream, protocol => tcp, recv => Recv}, - ok = sc_receive_response_local_close_tcp(InitState) + ok = sc_lc_receive_response_tcp(InitState) end). @@ -3727,12 +3767,12 @@ sc_recvmsg_response_local_close_tcp6(_Config) when is_list(_Config) -> %% remotely closed while the process is calling the recvmsg function. %% Socket is IPv4. -sc_recvmsg_response_remote_close_tcp4(suite) -> +sc_rc_recvmsg_response_tcp4(suite) -> []; -sc_recvmsg_response_remote_close_tcp4(doc) -> +sc_rc_recvmsg_response_tcp4(doc) -> []; -sc_recvmsg_response_remote_close_tcp4(_Config) when is_list(_Config) -> - tc_try(sc_recvmsg_response_remote_close_tcp4, +sc_rc_recvmsg_response_tcp4(_Config) when is_list(_Config) -> + tc_try(sc_rc_recvmsg_response_tcp4, fun() -> not_yet_implemented(), Recv = fun(Sock) -> socket:recvmsg(Sock) end, @@ -3740,7 +3780,7 @@ sc_recvmsg_response_remote_close_tcp4(_Config) when is_list(_Config) -> type => stream, protocol => tcp, recv => Recv}, - ok = sc_receive_response_remote_close_tcp(InitState) + ok = sc_rc_receive_response_tcp(InitState) end). @@ -3749,12 +3789,12 @@ sc_recvmsg_response_remote_close_tcp4(_Config) when is_list(_Config) -> %% remotely closed while the process is calling the recvmsg function. %% Socket is IPv6. -sc_recvmsg_response_remote_close_tcp6(suite) -> +sc_rc_recvmsg_response_tcp6(suite) -> []; -sc_recvmsg_response_remote_close_tcp6(doc) -> +sc_rc_recvmsg_response_tcp6(doc) -> []; -sc_recvmsg_response_remote_close_tcp6(_Config) when is_list(_Config) -> - tc_try(sc_recvmsg_response_remote_close_tcp6, +sc_rc_recvmsg_response_tcp6(_Config) when is_list(_Config) -> + tc_try(sc_rc_recvmsg_response_tcp6, fun() -> not_yet_implemented(), Recv = fun(Sock) -> socket:recvmsg(Sock) end, @@ -3762,7 +3802,7 @@ sc_recvmsg_response_remote_close_tcp6(_Config) when is_list(_Config) -> type => stream, protocol => tcp, recv => Recv}, - ok = sc_receive_response_remote_close_tcp(InitState) + ok = sc_rc_receive_response_tcp(InitState) end). @@ -3773,18 +3813,18 @@ sc_recvmsg_response_remote_close_tcp6(_Config) when is_list(_Config) -> %% git the setup anyway. %% Socket is IPv4. -sc_acceptor_response_local_close_tcp4(suite) -> +sc_lc_acceptor_response_tcp4(suite) -> []; -sc_acceptor_response_local_close_tcp4(doc) -> +sc_lc_acceptor_response_tcp4(doc) -> []; -sc_acceptor_response_local_close_tcp4(_Config) when is_list(_Config) -> - tc_try(sc_acceptor_response_local_close_tcp4, +sc_lc_acceptor_response_tcp4(_Config) when is_list(_Config) -> + tc_try(sc_lc_acceptor_response_tcp4, fun() -> not_yet_implemented(), InitState = #{domain => inet, type => stream, protocol => tcp}, - ok = sc_acceptor_response_local_close_tcp(InitState) + ok = sc_lc_acceptor_response_tcp(InitState) end). @@ -3795,75 +3835,24 @@ sc_acceptor_response_local_close_tcp4(_Config) when is_list(_Config) -> %% git the setup anyway. %% Socket is IPv6. -sc_acceptor_response_local_close_tcp6(suite) -> - []; -sc_acceptor_response_local_close_tcp6(doc) -> - []; -sc_acceptor_response_local_close_tcp6(_Config) when is_list(_Config) -> - tc_try(sc_acceptor_response_local_close_tcp6, - fun() -> - not_yet_implemented(), - InitState = #{domain => inet, - type => stream, - protocol => tcp}, - ok = sc_acceptor_response_local_close_tcp(InitState) - end). - - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -sc_acceptor_response_local_close_tcp(_InitState) -> - ok. - - - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% This test case is intended to test what happens when a socket is -%% remotely closed while the process is calling the accept function. -%% We test what happens with a non-controlling_process also, since we -%% git the setup anyway. -%% Socket is IPv4. - -sc_acceptor_response_remote_close_tcp4(suite) -> - []; -sc_acceptor_response_remote_close_tcp4(doc) -> - []; -sc_acceptor_response_remote_close_tcp4(_Config) when is_list(_Config) -> - tc_try(sc_acceptor_response_remote_close_tcp4, - fun() -> - not_yet_implemented(), - InitState = #{domain => inet, - type => stream, - protocol => tcp}, - ok = sc_acceptor_response_remote_close_tcp(InitState) - end). - - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% This test case is intended to test what happens when a socket is -%% remotely closed while the process is calling the accept function. -%% We test what happens with a non-controlling_process also, since we -%% git the setup anyway. -%% Socket is IPv6. - -sc_acceptor_response_remote_close_tcp6(suite) -> +sc_lc_acceptor_response_tcp6(suite) -> []; -sc_acceptor_response_remote_close_tcp6(doc) -> +sc_lc_acceptor_response_tcp6(doc) -> []; -sc_acceptor_response_remote_close_tcp6(_Config) when is_list(_Config) -> - tc_try(acceptor_response_remote_close_tcp6, +sc_lc_acceptor_response_tcp6(_Config) when is_list(_Config) -> + tc_try(sc_lc_acceptor_response_tcp6, fun() -> not_yet_implemented(), InitState = #{domain => inet, type => stream, protocol => tcp}, - ok = sc_acceptor_response_remote_close_tcp(InitState) + ok = sc_lc_acceptor_response_tcp(InitState) end). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -sc_acceptor_response_remote_close_tcp(_InitState) -> +sc_lc_acceptor_response_tcp(_InitState) -> ok. -- cgit v1.2.3 From 3eb4d5160f6a9f7cca799c97daf33b1a8b154f5d Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Thu, 18 Oct 2018 17:18:10 +0200 Subject: [socket-nif] Initiation of "current reader" missing for recvmsg When calling the recvmsg function when there is no data, we should be wait (for the specified amount of time). But this not not wotk because the "current reader" structure was not initiated. OTP-14831 --- erts/emulator/nifs/common/socket_nif.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/erts/emulator/nifs/common/socket_nif.c b/erts/emulator/nifs/common/socket_nif.c index 5d2cfbc10b..2eddcb5658 100644 --- a/erts/emulator/nifs/common/socket_nif.c +++ b/erts/emulator/nifs/common/socket_nif.c @@ -12334,9 +12334,13 @@ ERL_NIF_TERM recvmsg_check_result(ErlNifEnv* env, } else if ((saveErrno == ERRNO_BLOCK) || (saveErrno == EAGAIN)) { + char* xres; SSDBG( descP, ("SOCKET", "recvmsg_check_result -> eagain\r\n") ); + if ((xres = recv_init_current_reader(env, descP, recvRef)) != NULL) + return esock_make_error_str(env, xres); + SELECT(env, descP->sock, (ERL_NIF_SELECT_READ), descP, NULL, recvRef); -- cgit v1.2.3 From 3c4c36587df5a847dd6f03011a4ecb76d0b70b40 Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Thu, 18 Oct 2018 17:21:11 +0200 Subject: [socket-nif|test] Enables test case sc_lc_recvmsg_response_tcp4 --- lib/kernel/test/socket_SUITE.erl | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/kernel/test/socket_SUITE.erl b/lib/kernel/test/socket_SUITE.erl index 4ee2eec39a..022e83a944 100644 --- a/lib/kernel/test/socket_SUITE.erl +++ b/lib/kernel/test/socket_SUITE.erl @@ -3730,7 +3730,6 @@ sc_lc_recvmsg_response_tcp4(doc) -> sc_lc_recvmsg_response_tcp4(_Config) when is_list(_Config) -> tc_try(sc_lc_recvmsg_response_tcp4, fun() -> - not_yet_implemented(), Recv = fun(Sock) -> socket:recvmsg(Sock) end, InitState = #{domain => inet, type => stream, -- cgit v1.2.3 From 466470070f7f22f5a1b42daf66390adfedd2cc28 Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Thu, 18 Oct 2018 18:33:51 +0200 Subject: [socket-nif] setopt and getopt with level ipv6 The level argument for IPv6 can be either SOL_IPV6 or IPPROTO_IPV6, whichever is defined. But it was hard- coded to SOL_IPV6, which is not defined on FreeBSD. OTP-14831 --- erts/emulator/nifs/common/socket_nif.c | 244 ++++++++++++++++++++++++++++----- 1 file changed, 212 insertions(+), 32 deletions(-) diff --git a/erts/emulator/nifs/common/socket_nif.c b/erts/emulator/nifs/common/socket_nif.c index a137692d68..45462ff772 100644 --- a/erts/emulator/nifs/common/socket_nif.c +++ b/erts/emulator/nifs/common/socket_nif.c @@ -8533,7 +8533,13 @@ ERL_NIF_TERM nsetopt_lvl_ipv6_dstopts(ErlNifEnv* env, SocketDescriptor* descP, ERL_NIF_TERM eVal) { - return nsetopt_bool_opt(env, descP, SOL_IPV6, IPV6_DSTOPTS, eVal); +#if defined(SOL_IPV6) + int level = SOL_IPV6; +#else + int level = IPPROTO_IPV6; +#endif + + return nsetopt_bool_opt(env, descP, level, IPV6_DSTOPTS, eVal); } #endif @@ -8544,7 +8550,13 @@ ERL_NIF_TERM nsetopt_lvl_ipv6_flowinfo(ErlNifEnv* env, SocketDescriptor* descP, ERL_NIF_TERM eVal) { - return nsetopt_bool_opt(env, descP, SOL_IPV6, IPV6_FLOWINFO, eVal); +#if defined(SOL_IPV6) + int level = SOL_IPV6; +#else + int level = IPPROTO_IPV6; +#endif + + return nsetopt_bool_opt(env, descP, level, IPV6_FLOWINFO, eVal); } #endif @@ -8555,7 +8567,13 @@ ERL_NIF_TERM nsetopt_lvl_ipv6_hoplimit(ErlNifEnv* env, SocketDescriptor* descP, ERL_NIF_TERM eVal) { - return nsetopt_bool_opt(env, descP, SOL_IPV6, IPV6_HOPLIMIT, eVal); +#if defined(SOL_IPV6) + int level = SOL_IPV6; +#else + int level = IPPROTO_IPV6; +#endif + + return nsetopt_bool_opt(env, descP, level, IPV6_HOPLIMIT, eVal); } #endif @@ -8566,7 +8584,13 @@ ERL_NIF_TERM nsetopt_lvl_ipv6_hopopts(ErlNifEnv* env, SocketDescriptor* descP, ERL_NIF_TERM eVal) { - return nsetopt_bool_opt(env, descP, SOL_IPV6, IPV6_HOPOPTS, eVal); +#if defined(SOL_IPV6) + int level = SOL_IPV6; +#else + int level = IPPROTO_IPV6; +#endif + + return nsetopt_bool_opt(env, descP, level, IPV6_HOPOPTS, eVal); } #endif @@ -8577,7 +8601,13 @@ ERL_NIF_TERM nsetopt_lvl_ipv6_mtu(ErlNifEnv* env, SocketDescriptor* descP, ERL_NIF_TERM eVal) { - return nsetopt_int_opt(env, descP, SOL_IPV6, IPV6_MTU, eVal); +#if defined(SOL_IPV6) + int level = SOL_IPV6; +#else + int level = IPPROTO_IPV6; +#endif + + return nsetopt_int_opt(env, descP, level, IPV6_MTU, eVal); } #endif @@ -8602,8 +8632,14 @@ ERL_NIF_TERM nsetopt_lvl_ipv6_mtu_discover(ErlNifEnv* env, result = esock_make_error_str(env, xres); } else { +#if defined(SOL_IPV6) + int level = SOL_IPV6; +#else + int level = IPPROTO_IPV6; +#endif + - res = socket_setopt(descP->sock, SOL_IPV6, IPV6_MTU_DISCOVER, + res = socket_setopt(descP->sock, level, IPV6_MTU_DISCOVER, &val, sizeof(val)); if (res != 0) @@ -8624,7 +8660,13 @@ ERL_NIF_TERM nsetopt_lvl_ipv6_multicast_hops(ErlNifEnv* env, SocketDescriptor* descP, ERL_NIF_TERM eVal) { - return nsetopt_int_opt(env, descP, SOL_IPV6, IPV6_MULTICAST_HOPS, eVal); +#if defined(SOL_IPV6) + int level = SOL_IPV6; +#else + int level = IPPROTO_IPV6; +#endif + + return nsetopt_int_opt(env, descP, level, IPV6_MULTICAST_HOPS, eVal); } #endif @@ -8636,7 +8678,13 @@ ERL_NIF_TERM nsetopt_lvl_ipv6_multicast_if(ErlNifEnv* env, SocketDescriptor* descP, ERL_NIF_TERM eVal) { - return nsetopt_int_opt(env, descP, SOL_IPV6, IPV6_MULTICAST_IF, eVal); +#if defined(SOL_IPV6) + int level = SOL_IPV6; +#else + int level = IPPROTO_IPV6; +#endif + + return nsetopt_int_opt(env, descP, level, IPV6_MULTICAST_IF, eVal); } #endif @@ -8648,7 +8696,13 @@ ERL_NIF_TERM nsetopt_lvl_ipv6_multicast_loop(ErlNifEnv* env, SocketDescriptor* descP, ERL_NIF_TERM eVal) { - return nsetopt_bool_opt(env, descP, SOL_IPV6, IPV6_MULTICAST_LOOP, eVal); +#if defined(SOL_IPV6) + int level = SOL_IPV6; +#else + int level = IPPROTO_IPV6; +#endif + + return nsetopt_bool_opt(env, descP, level, IPV6_MULTICAST_LOOP, eVal); } #endif @@ -8659,7 +8713,13 @@ ERL_NIF_TERM nsetopt_lvl_ipv6_recverr(ErlNifEnv* env, SocketDescriptor* descP, ERL_NIF_TERM eVal) { - return nsetopt_bool_opt(env, descP, SOL_IPV6, IPV6_RECVERR, eVal); +#if defined(SOL_IPV6) + int level = SOL_IPV6; +#else + int level = IPPROTO_IPV6; +#endif + + return nsetopt_bool_opt(env, descP, level, IPV6_RECVERR, eVal); } #endif @@ -8670,13 +8730,18 @@ ERL_NIF_TERM nsetopt_lvl_ipv6_recvpktinfo(ErlNifEnv* env, SocketDescriptor* descP, ERL_NIF_TERM eVal) { +#if defined(SOL_IPV6) + int level = SOL_IPV6; +#else + int level = IPPROTO_IPV6; +#endif #if defined(IPV6_RECVPKTINFO) int opt = IPV6_RECVPKTINFO; #else int opt = IPV6_PKTINFO; #endif - return nsetopt_bool_opt(env, descP, SOL_IPV6, opt, eVal); + return nsetopt_bool_opt(env, descP, level, opt, eVal); } #endif @@ -8687,7 +8752,13 @@ ERL_NIF_TERM nsetopt_lvl_ipv6_router_alert(ErlNifEnv* env, SocketDescriptor* descP, ERL_NIF_TERM eVal) { - return nsetopt_int_opt(env, descP, SOL_IPV6, IPV6_ROUTER_ALERT, eVal); +#if defined(SOL_IPV6) + int level = SOL_IPV6; +#else + int level = IPPROTO_IPV6; +#endif + + return nsetopt_int_opt(env, descP, level, IPV6_ROUTER_ALERT, eVal); } #endif @@ -8699,7 +8770,13 @@ ERL_NIF_TERM nsetopt_lvl_ipv6_rthdr(ErlNifEnv* env, SocketDescriptor* descP, ERL_NIF_TERM eVal) { - return nsetopt_bool_opt(env, descP, SOL_IPV6, IPV6_RTHDR, eVal); +#if defined(SOL_IPV6) + int level = SOL_IPV6; +#else + int level = IPPROTO_IPV6; +#endif + + return nsetopt_bool_opt(env, descP, level, IPV6_RTHDR, eVal); } #endif @@ -8710,7 +8787,13 @@ ERL_NIF_TERM nsetopt_lvl_ipv6_unicast_hops(ErlNifEnv* env, SocketDescriptor* descP, ERL_NIF_TERM eVal) { - return nsetopt_int_opt(env, descP, SOL_IPV6, IPV6_UNICAST_HOPS, eVal); +#if defined(SOL_IPV6) + int level = SOL_IPV6; +#else + int level = IPPROTO_IPV6; +#endif + + return nsetopt_int_opt(env, descP, level, IPV6_UNICAST_HOPS, eVal); } #endif @@ -8722,7 +8805,13 @@ ERL_NIF_TERM nsetopt_lvl_ipv6_v6only(ErlNifEnv* env, SocketDescriptor* descP, ERL_NIF_TERM eVal) { - return nsetopt_bool_opt(env, descP, SOL_IPV6, IPV6_V6ONLY, eVal); +#if defined(SOL_IPV6) + int level = SOL_IPV6; +#else + int level = IPPROTO_IPV6; +#endif + + return nsetopt_bool_opt(env, descP, level, IPV6_V6ONLY, eVal); } #endif @@ -8739,7 +8828,11 @@ ERL_NIF_TERM nsetopt_lvl_ipv6_update_membership(ErlNifEnv* env, char* xres; int res; size_t sz; +#if defined(SOL_IPV6) + int level = SOL_IPV6; +#else int level = IPPROTO_IPV6; +#endif // It must be a map if (!IS_MAP(env, eVal)) @@ -11581,7 +11674,12 @@ static ERL_NIF_TERM ngetopt_lvl_ipv6_dstopts(ErlNifEnv* env, SocketDescriptor* descP) { - return ngetopt_bool_opt(env, descP, SOL_IPV6, IPV6_DSTOPTS); +#if defined(SOL_IPV6) + int level = SOL_IPV6; +#else + int level = IPPROTO_IPV6; +#endif + return ngetopt_bool_opt(env, descP, level, IPV6_DSTOPTS); } #endif @@ -11591,7 +11689,13 @@ static ERL_NIF_TERM ngetopt_lvl_ipv6_flowinfo(ErlNifEnv* env, SocketDescriptor* descP) { - return ngetopt_bool_opt(env, descP, SOL_IPV6, IPV6_FLOWINFO); +#if defined(SOL_IPV6) + int level = SOL_IPV6; +#else + int level = IPPROTO_IPV6; +#endif + + return ngetopt_bool_opt(env, descP, level, IPV6_FLOWINFO); } #endif @@ -11601,7 +11705,13 @@ static ERL_NIF_TERM ngetopt_lvl_ipv6_hoplimit(ErlNifEnv* env, SocketDescriptor* descP) { - return ngetopt_bool_opt(env, descP, SOL_IPV6, IPV6_HOPLIMIT); +#if defined(SOL_IPV6) + int level = SOL_IPV6; +#else + int level = IPPROTO_IPV6; +#endif + + return ngetopt_bool_opt(env, descP, level, IPV6_HOPLIMIT); } #endif @@ -11611,7 +11721,13 @@ static ERL_NIF_TERM ngetopt_lvl_ipv6_hopopts(ErlNifEnv* env, SocketDescriptor* descP) { - return ngetopt_bool_opt(env, descP, SOL_IPV6, IPV6_HOPOPTS); +#if defined(SOL_IPV6) + int level = SOL_IPV6; +#else + int level = IPPROTO_IPV6; +#endif + + return ngetopt_bool_opt(env, descP, level, IPV6_HOPOPTS); } #endif @@ -11621,7 +11737,13 @@ static ERL_NIF_TERM ngetopt_lvl_ipv6_mtu(ErlNifEnv* env, SocketDescriptor* descP) { - return ngetopt_int_opt(env, descP, SOL_IPV6, IPV6_MTU); +#if defined(SOL_IPV6) + int level = SOL_IPV6; +#else + int level = IPPROTO_IPV6; +#endif + + return ngetopt_int_opt(env, descP, level, IPV6_MTU); } #endif @@ -11638,8 +11760,13 @@ ERL_NIF_TERM ngetopt_lvl_ipv6_mtu_discover(ErlNifEnv* env, int mtuDisc; SOCKOPTLEN_T mtuDiscSz = sizeof(mtuDisc); int res; +#if defined(SOL_IPV6) + int level = SOL_IPV6; +#else + int level = IPPROTO_IPV6; +#endif - res = sock_getopt(descP->sock, SOL_IPV6, IPV6_MTU_DISCOVER, + res = sock_getopt(descP->sock, level, IPV6_MTU_DISCOVER, &mtuDisc, &mtuDiscSz); if (res != 0) { @@ -11660,7 +11787,13 @@ static ERL_NIF_TERM ngetopt_lvl_ipv6_multicast_hops(ErlNifEnv* env, SocketDescriptor* descP) { - return ngetopt_int_opt(env, descP, SOL_IPV6, IPV6_MULTICAST_HOPS); +#if defined(SOL_IPV6) + int level = SOL_IPV6; +#else + int level = IPPROTO_IPV6; +#endif + + return ngetopt_int_opt(env, descP, level, IPV6_MULTICAST_HOPS); } #endif @@ -11670,7 +11803,13 @@ static ERL_NIF_TERM ngetopt_lvl_ipv6_multicast_if(ErlNifEnv* env, SocketDescriptor* descP) { - return ngetopt_int_opt(env, descP, SOL_IPV6, IPV6_MULTICAST_IF); +#if defined(SOL_IPV6) + int level = SOL_IPV6; +#else + int level = IPPROTO_IPV6; +#endif + + return ngetopt_int_opt(env, descP, level, IPV6_MULTICAST_IF); } #endif @@ -11680,7 +11819,13 @@ static ERL_NIF_TERM ngetopt_lvl_ipv6_multicast_loop(ErlNifEnv* env, SocketDescriptor* descP) { - return ngetopt_bool_opt(env, descP, SOL_IPV6, IPV6_MULTICAST_LOOP); +#if defined(SOL_IPV6) + int level = SOL_IPV6; +#else + int level = IPPROTO_IPV6; +#endif + + return ngetopt_bool_opt(env, descP, level, IPV6_MULTICAST_LOOP); } #endif @@ -11690,7 +11835,13 @@ static ERL_NIF_TERM ngetopt_lvl_ipv6_recverr(ErlNifEnv* env, SocketDescriptor* descP) { - return ngetopt_bool_opt(env, descP, SOL_IPV6, IPV6_RECVERR); +#if defined(SOL_IPV6) + int level = SOL_IPV6; +#else + int level = IPPROTO_IPV6; +#endif + + return ngetopt_bool_opt(env, descP, level, IPV6_RECVERR); } #endif @@ -11700,13 +11851,18 @@ static ERL_NIF_TERM ngetopt_lvl_ipv6_recvpktinfo(ErlNifEnv* env, SocketDescriptor* descP) { +#if defined(SOL_IPV6) + int level = SOL_IPV6; +#else + int level = IPPROTO_IPV6; +#endif #if defined(IPV6_RECVPKTINFO) - int opt = IPV6_RECVPKTINFO; + int opt = IPV6_RECVPKTINFO; #else - int opt = IPV6_PKTINFO; + int opt = IPV6_PKTINFO; #endif - return ngetopt_bool_opt(env, descP, SOL_IPV6, opt); + return ngetopt_bool_opt(env, descP, level, opt); } #endif @@ -11716,7 +11872,13 @@ static ERL_NIF_TERM ngetopt_lvl_ipv6_router_alert(ErlNifEnv* env, SocketDescriptor* descP) { - return ngetopt_int_opt(env, descP, SOL_IPV6, IPV6_ROUTER_ALERT); +#if defined(SOL_IPV6) + int level = SOL_IPV6; +#else + int level = IPPROTO_IPV6; +#endif + + return ngetopt_int_opt(env, descP, level, IPV6_ROUTER_ALERT); } #endif @@ -11726,7 +11888,13 @@ static ERL_NIF_TERM ngetopt_lvl_ipv6_rthdr(ErlNifEnv* env, SocketDescriptor* descP) { - return ngetopt_bool_opt(env, descP, SOL_IPV6, IPV6_RTHDR); +#if defined(SOL_IPV6) + int level = SOL_IPV6; +#else + int level = IPPROTO_IPV6; +#endif + + return ngetopt_bool_opt(env, descP, level, IPV6_RTHDR); } #endif @@ -11736,7 +11904,13 @@ static ERL_NIF_TERM ngetopt_lvl_ipv6_unicast_hops(ErlNifEnv* env, SocketDescriptor* descP) { - return ngetopt_int_opt(env, descP, SOL_IPV6, IPV6_UNICAST_HOPS); +#if defined(SOL_IPV6) + int level = SOL_IPV6; +#else + int level = IPPROTO_IPV6; +#endif + + return ngetopt_int_opt(env, descP, level, IPV6_UNICAST_HOPS); } #endif @@ -11746,7 +11920,13 @@ static ERL_NIF_TERM ngetopt_lvl_ipv6_v6only(ErlNifEnv* env, SocketDescriptor* descP) { - return ngetopt_bool_opt(env, descP, SOL_IPV6, IPV6_V6ONLY); +#if defined(SOL_IPV6) + int level = SOL_IPV6; +#else + int level = IPPROTO_IPV6; +#endif + + return ngetopt_bool_opt(env, descP, level, IPV6_V6ONLY); } #endif -- cgit v1.2.3 From b4d61414565e6c6aa34249bf5d6eb3d5e5952b76 Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Fri, 19 Oct 2018 12:13:26 +0200 Subject: [socket-nif|test] Moved socket tests from kernel to erts/emulator OTP-14831 --- erts/emulator/test/Makefile | 11 + erts/emulator/test/socket_SUITE.erl | 4186 ++++++++++++++++++++++++++++++++++ erts/emulator/test/socket_client.erl | 538 +++++ erts/emulator/test/socket_lib.erl | 133 ++ erts/emulator/test/socket_server.erl | 954 ++++++++ lib/kernel/test/Makefile | 12 +- lib/kernel/test/socket_SUITE.erl | 4186 ---------------------------------- lib/kernel/test/socket_client.erl | 538 ----- lib/kernel/test/socket_lib.erl | 133 -- lib/kernel/test/socket_server.erl | 954 -------- 10 files changed, 5823 insertions(+), 5822 deletions(-) create mode 100644 erts/emulator/test/socket_SUITE.erl create mode 100644 erts/emulator/test/socket_client.erl create mode 100644 erts/emulator/test/socket_lib.erl create mode 100644 erts/emulator/test/socket_server.erl delete mode 100644 lib/kernel/test/socket_SUITE.erl delete mode 100644 lib/kernel/test/socket_client.erl delete mode 100644 lib/kernel/test/socket_lib.erl delete mode 100644 lib/kernel/test/socket_server.erl diff --git a/erts/emulator/test/Makefile b/erts/emulator/test/Makefile index bf00de2204..4f47ec47ef 100644 --- a/erts/emulator/test/Makefile +++ b/erts/emulator/test/Makefile @@ -28,6 +28,12 @@ EBIN = . # Target Specs # ---------------------------------------------------- +SOCKET_MODULES = \ + socket_lib \ + socket_server \ + socket_client \ + socket_SUITE + MODULES= \ a_SUITE \ after_SUITE \ @@ -103,6 +109,7 @@ MODULES= \ sensitive_SUITE \ signal_SUITE \ smoke_test_SUITE \ + $(SOCKET_MODULES) \ statistics_SUITE \ system_info_SUITE \ system_profile_SUITE \ @@ -151,6 +158,7 @@ NATIVE_ERL_FILES= $(NATIVE_MODULES:%=%.erl) ERL_FILES= $(MODULES:%=%.erl) TARGET_FILES = $(MODULES:%=$(EBIN)/%.$(EMULATOR)) +SOCKET_TARGET = $(SOCKET_MODULES:%=$(EBIN)/%.$(EMULATOR)) EMAKEFILE=Emakefile @@ -197,6 +205,9 @@ clean: docs: +targets: $(TARGET_FILES) +socket_targets: $(SOCKET_TARGETS) + # ---------------------------------------------------- # Special targets # ---------------------------------------------------- diff --git a/erts/emulator/test/socket_SUITE.erl b/erts/emulator/test/socket_SUITE.erl new file mode 100644 index 0000000000..022e83a944 --- /dev/null +++ b/erts/emulator/test/socket_SUITE.erl @@ -0,0 +1,4186 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2018-2018. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% + +-module(socket_SUITE). + +-include_lib("common_test/include/ct.hrl"). +-include_lib("common_test/include/ct_event.hrl"). + +%% Suite exports +-export([suite/0, all/0, groups/0]). +-export([init_per_suite/1, end_per_suite/1, + init_per_testcase/2, end_per_testcase/2]). + +%% Test cases +-export([ + %% API Basic + api_b_open_and_close_udp4/1, + api_b_open_and_close_tcp4/1, + api_b_sendto_and_recvfrom_udp4/1, + api_b_sendmsg_and_recvmsg_udp4/1, + api_b_send_and_recv_tcp4/1, + api_b_sendmsg_and_recvmsg_tcp4/1, + + %% API Options + api_opt_simple_otp_options/1, + api_opt_simple_otp_controlling_process/1, + + %% API Operation Timeout + api_to_connect_tcp4/1, + api_to_connect_tcp6/1, + api_to_accept_tcp4/1, + api_to_accept_tcp6/1, + api_to_maccept_tcp4/1, + api_to_maccept_tcp6/1, + api_to_send_tcp4/1, + api_to_send_tcp6/1, + api_to_sendto_udp4/1, + api_to_sendto_udp6/1, + api_to_sendmsg_tcp4/1, + api_to_sendmsg_tcp6/1, + api_to_recv_udp4/1, + api_to_recv_udp6/1, + api_to_recv_tcp4/1, + api_to_recv_tcp6/1, + api_to_recvfrom_udp4/1, + api_to_recvfrom_udp6/1, + api_to_recvmsg_udp4/1, + api_to_recvmsg_udp6/1, + api_to_recvmsg_tcp4/1, + api_to_recvmsg_tcp6/1, + + %% Socket Closure + sc_cpe_socket_cleanup_tcp4/1, + sc_cpe_socket_cleanup_tcp6/1, + sc_cpe_socket_cleanup_udp4/1, + sc_cpe_socket_cleanup_udp6/1, + sc_lc_recv_response_tcp4/1, + sc_lc_recv_response_tcp6/1, + sc_lc_recvmsg_response_tcp4/1, + sc_lc_recvmsg_response_tcp6/1, + sc_lc_acceptor_response_tcp4/1, + sc_lc_acceptor_response_tcp6/1, + sc_rc_recv_response_tcp4/1, + sc_rc_recv_response_tcp6/1, + sc_rc_recvmsg_response_tcp4/1, + sc_rc_recvmsg_response_tcp6/1 + + %% Tickets + ]). + +%% Internal exports +%% -export([]). + + +-type initial_evaluator_state() :: map(). +-type evaluator_state() :: term(). +-type command_fun() :: + fun((State :: evaluator_state()) -> ok) | + fun((State :: evaluator_state()) -> {ok, evaluator_state()}) | + fun((State :: evaluator_state()) -> {error, term()}). + +-type command() :: #{desc := string(), + cmd := command_fun()}. + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +-define(BASIC_REQ, <<"hejsan">>). +-define(BASIC_REP, <<"hoppsan">>). + +-define(FAIL(R), exit(R)). + +-define(SLEEP(T), receive after T -> ok end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +suite() -> + [{ct_hooks,[ts_install_cth]}, + {timetrap,{minutes,1}}]. + +all() -> + [ + {group, api}, + {group, socket_closure} + %% {group, tickets} + ]. + +groups() -> + [{api, [], api_cases()}, + {api_basic, [], api_basic_cases()}, + {api_options, [], api_options_cases()}, + {api_op_with_timeout, [], api_op_with_timeout_cases()}, + {socket_closure, [], socket_closure_cases()}, + {sc_ctrl_proc_exit, [], sc_cp_exit_cases()}, + {sc_local_close, [], sc_lc_cases()}, + {sc_remote_close, [], sc_rc_cases()} + %% {tickets, [], ticket_cases()} + ]. + +api_cases() -> + [ + {group, api_basic}, + {group, api_options}, + {group, api_op_with_timeout} + ]. + +api_basic_cases() -> + [ + api_b_open_and_close_udp4, + api_b_open_and_close_tcp4, + api_b_sendto_and_recvfrom_udp4, + api_b_sendmsg_and_recvmsg_udp4, + api_b_send_and_recv_tcp4, + api_b_sendmsg_and_recvmsg_tcp4 + ]. + +api_options_cases() -> + [ + api_opt_simple_otp_options, + api_opt_simple_otp_controlling_process + ]. + +api_op_with_timeout_cases() -> + [ + api_to_connect_tcp4, + api_to_connect_tcp6, + api_to_accept_tcp4, + api_to_accept_tcp6, + api_to_send_tcp4, + api_to_send_tcp6, + api_to_sendto_udp4, + api_to_sendto_udp6, + api_to_sendmsg_tcp4, + api_to_sendmsg_tcp6, + api_to_recv_udp4, + api_to_recv_udp6, + api_to_recv_tcp4, + api_to_recv_tcp6, + api_to_recvfrom_udp4, + api_to_recvfrom_udp6, + api_to_recvmsg_udp4, + api_to_recvmsg_udp6, + api_to_recvmsg_tcp4, + api_to_recvmsg_tcp6 + ]. + +%% These cases tests what happens when the socket is closed, locally or +%% remotely. +socket_closure_cases() -> + [ + {group, sc_ctrl_proc_exit}, + {group, sc_local_close}, + {group, sc_remote_close} + ]. + +%% These cases are all about socket cleanup after the controlling process +%% exits *without* calling socket:close/1. +sc_cp_exit_cases() -> + [ + sc_cpe_socket_cleanup_tcp4, + sc_cpe_socket_cleanup_tcp6, + sc_cpe_socket_cleanup_udp4, + sc_cpe_socket_cleanup_udp6 + ]. + +%% These cases tests what happens when the socket is closed locally. +sc_lc_cases() -> + [ + sc_lc_recv_response_tcp4, + sc_lc_recv_response_tcp6, + + sc_lc_recvmsg_response_tcp4, + sc_lc_recvmsg_response_tcp6, + + sc_lc_acceptor_response_tcp4, + sc_lc_acceptor_response_tcp6 + ]. + +%% These cases tests what happens when the socket is closed remotely. +sc_rc_cases() -> + [ + sc_rc_recv_response_tcp4, + sc_rc_recv_response_tcp6, + + sc_rc_recvmsg_response_tcp4, + sc_rc_recvmsg_response_tcp6 + ]. + + +%% ticket_cases() -> +%% []. + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +init_per_suite(Config) -> + Config. + +end_per_suite(_) -> + ok. + +init_per_testcase(_TC, Config) -> + Config. + +end_per_testcase(_TC, Config) -> + Config. + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% %% +%% API BASIC %% +%% %% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Basically open (create) and close an IPv4 UDP (dgram) socket. +%% With some extra checks... +api_b_open_and_close_udp4(suite) -> + []; +api_b_open_and_close_udp4(doc) -> + []; +api_b_open_and_close_udp4(_Config) when is_list(_Config) -> + tc_try(api_b_open_and_close_udp4, + fun() -> + InitState = #{domain => inet, + type => dgram, + protocol => udp}, + ok = api_b_open_and_close(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Basically open (create) and close an IPv4 TCP (stream) socket. +%% With some extra checks... +api_b_open_and_close_tcp4(suite) -> + []; +api_b_open_and_close_tcp4(doc) -> + []; +api_b_open_and_close_tcp4(_Config) when is_list(_Config) -> + tc_try(api_b_open_and_close_tcp4, + fun() -> + InitState = #{domain => inet, + type => stream, + protocol => tcp}, + ok = api_b_open_and_close(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +api_b_open_and_close(InitState) -> + Seq = + [ + #{desc => "open", + cmd => fun(#{domain := Domain, + type := Type, + protocol := Protocol} = S) -> + Res = socket:open(Domain, Type, Protocol), + {ok, {S, Res}} + end}, + #{desc => "validate open", + cmd => fun({S, {ok, Sock}}) -> + NewS = S#{socket => Sock}, + {ok, NewS}; + ({_, {error, _} = ERROR}) -> + ERROR + end}, + #{desc => "get domain (maybe)", + cmd => fun(#{socket := Sock} = S) -> + Res = socket:getopt(Sock, socket, domain), + {ok, {S, Res}} + end}, + #{desc => "validate domain (maybe)", + cmd => fun({#{domain := Domain} = S, {ok, Domain}}) -> + {ok, S}; + ({#{domain := ExpDomain}, {ok, Domain}}) -> + {error, {unexpected_domain, ExpDomain, Domain}}; + %% Some platforms do not support this option + ({S, {error, einval}}) -> + {ok, S}; + ({_, {error, _} = ERROR}) -> + ERROR + end}, + #{desc => "get type", + cmd => fun(#{socket := Sock} = State) -> + Res = socket:getopt(Sock, socket, type), + {ok, {State, Res}} + end}, + #{desc => "validate type", + cmd => fun({#{type := Type} = State, {ok, Type}}) -> + {ok, State}; + ({#{type := ExpType}, {ok, Type}}) -> + {error, {unexpected_type, ExpType, Type}}; + ({_, {error, _} = ERROR}) -> + ERROR + end}, + #{desc => "get protocol", + cmd => fun(#{socket := Sock} = State) -> + Res = socket:getopt(Sock, socket, protocol), + {ok, {State, Res}} + end}, + #{desc => "validate protocol", + cmd => fun({#{protocol := Protocol} = State, {ok, Protocol}}) -> + {ok, State}; + ({#{protocol := ExpProtocol}, {ok, Protocol}}) -> + {error, {unexpected_type, ExpProtocol, Protocol}}; + ({_, {error, _} = ERROR}) -> + ERROR + end}, + #{desc => "get controlling-process", + cmd => fun(#{socket := Sock} = State) -> + Res = socket:getopt(Sock, otp, controlling_process), + {ok, {State, Res}} + end}, + #{desc => "validate controlling-process", + cmd => fun({State, {ok, Pid}}) -> + case self() of + Pid -> + {ok, State}; + _ -> + {error, {unexpected_owner, Pid}} + end; + ({_, {error, _} = ERROR}) -> + ERROR + end}, + #{desc => "close socket", + cmd => fun(#{socket := Sock} = State) -> + Res = socket:close(Sock), + {ok, {State, Res}} + end}, + #{desc => "validate socket close", + cmd => fun({_, ok}) -> + {ok, normal}; + ({_, {error, _} = ERROR}) -> + ERROR + end}], + Evaluator = evaluator_start("tester", Seq, InitState), + ok = await_evaluator_finish([Evaluator]). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Basically send and receive on an IPv4 UDP (dgram) socket using +%% sendto and recvfrom.. +api_b_sendto_and_recvfrom_udp4(suite) -> + []; +api_b_sendto_and_recvfrom_udp4(doc) -> + []; +api_b_sendto_and_recvfrom_udp4(_Config) when is_list(_Config) -> + tc_try(api_b_sendto_and_recvfrom_udp4, + fun() -> + Send = fun(Sock, Data, Dest) -> + socket:sendto(Sock, Data, Dest) + end, + Recv = fun(Sock) -> + socket:recvfrom(Sock) + end, + InitState = #{domain => inet, + send => Send, + recv => Recv}, + ok = api_b_send_and_recv_udp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Basically send and receive on an IPv4 UDP (dgram) socket +%% using sendmsg and recvmsg. +api_b_sendmsg_and_recvmsg_udp4(suite) -> + []; +api_b_sendmsg_and_recvmsg_udp4(doc) -> + []; +api_b_sendmsg_and_recvmsg_udp4(_Config) when is_list(_Config) -> + tc_try(api_b_sendmsg_and_recvmsg_udp4, + fun() -> + Send = fun(Sock, Data, Dest) -> + %% CMsgHdr = #{level => ip, + %% type => tos, + %% data => reliability}, + %% CMsgHdrs = [CMsgHdr], + MsgHdr = #{addr => Dest, + %% ctrl => CMsgHdrs, + iov => [Data]}, + socket:sendmsg(Sock, MsgHdr) + end, + Recv = fun(Sock) -> + case socket:recvmsg(Sock) of + {ok, #{addr := Source, + iov := [Data]}} -> + {ok, {Source, Data}}; + {error, _} = ERROR -> + ERROR + end + end, + InitState = #{domain => inet, + send => Send, + recv => Recv}, + ok = api_b_send_and_recv_udp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +api_b_send_and_recv_udp(InitState) -> + Seq = + [ + #{desc => "local address", + cmd => fun(#{domain := Domain} = State) -> + LAddr = which_local_addr(Domain), + LSA = #{family => Domain, addr => LAddr}, + {ok, State#{lsa => LSA}} + end}, + #{desc => "open src socket", + cmd => fun(#{domain := Domain} = State) -> + Sock = sock_open(Domain, dgram, udp), + SASrc = sock_sockname(Sock), + {ok, State#{sock_src => Sock, sa_src => SASrc}} + end}, + #{desc => "bind src", + cmd => fun(#{sock_src := Sock, lsa := LSA}) -> + sock_bind(Sock, LSA), + ok + end}, + #{desc => "sockname src socket", + cmd => fun(#{sock_src := Sock} = State) -> + SASrc = sock_sockname(Sock), + %% ei("src sockaddr: ~p", [SASrc]), + {ok, State#{sa_src => SASrc}} + end}, + #{desc => "open dst socket", + cmd => fun(#{domain := Domain} = State) -> + Sock = sock_open(Domain, dgram, udp), + {ok, State#{sock_dst => Sock}} + end}, + #{desc => "bind dst", + cmd => fun(#{sock_dst := Sock, lsa := LSA}) -> + sock_bind(Sock, LSA), + ok + end}, + #{desc => "sockname dst socket", + cmd => fun(#{sock_dst := Sock} = State) -> + SADst = sock_sockname(Sock), + %% ei("dst sockaddr: ~p", [SADst]), + {ok, State#{sa_dst => SADst}} + end}, + #{desc => "send req (to dst)", + cmd => fun(#{sock_src := Sock, sa_dst := Dst, send := Send}) -> + ok = Send(Sock, ?BASIC_REQ, Dst) + end}, + #{desc => "recv req (from src)", + cmd => fun(#{sock_dst := Sock, sa_src := Src, recv := Recv}) -> + {ok, {Src, ?BASIC_REQ}} = Recv(Sock), + ok + end}, + #{desc => "send rep (to src)", + cmd => fun(#{sock_dst := Sock, sa_src := Src, send := Send}) -> + ok = Send(Sock, ?BASIC_REP, Src) + end}, + #{desc => "recv rep (from dst)", + cmd => fun(#{sock_src := Sock, sa_dst := Dst, recv := Recv}) -> + {ok, {Dst, ?BASIC_REP}} = Recv(Sock), + ok + end}, + #{desc => "close src socket", + cmd => fun(#{sock_src := Sock}) -> + ok = socket:close(Sock) + end}, + #{desc => "close dst socket", + cmd => fun(#{sock_dst := Sock}) -> + ok = socket:close(Sock), + {ok, normal} + end} + ], + Evaluator = evaluator_start("tester", Seq, InitState), + ok = await_evaluator_finish([Evaluator]). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Basically send and receive using the "common" functions (send and recv) +%% on an IPv4 TCP (stream) socket. +api_b_send_and_recv_tcp4(suite) -> + []; +api_b_send_and_recv_tcp4(doc) -> + []; +api_b_send_and_recv_tcp4(_Config) when is_list(_Config) -> + tc_try(api_b_send_and_recv_tcp4, + fun() -> + Send = fun(Sock, Data) -> + socket:send(Sock, Data) + end, + Recv = fun(Sock) -> + socket:recv(Sock) + end, + InitState = #{domain => inet, + send => Send, + recv => Recv}, + ok = api_b_send_and_recv_tcp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Basically send and receive using the msg functions (sendmsg and recvmsg) +%% on an IPv4 TCP (stream) socket. +api_b_sendmsg_and_recvmsg_tcp4(suite) -> + []; +api_b_sendmsg_and_recvmsg_tcp4(doc) -> + []; +api_b_sendmsg_and_recvmsg_tcp4(_Config) when is_list(_Config) -> + tc_try(api_b_sendmsg_and_recvmsg_tcp4, + fun() -> + Send = fun(Sock, Data) -> + MsgHdr = #{iov => [Data]}, + socket:sendmsg(Sock, MsgHdr) + end, + Recv = fun(Sock) -> + case socket:recvmsg(Sock) of + {ok, #{addr := undefined, + iov := [Data]}} -> + {ok, Data}; + {error, _} = ERROR -> + ERROR + end + end, + InitState = #{domain => inet, + send => Send, + recv => Recv}, + ok = api_b_send_and_recv_tcp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +api_b_send_and_recv_tcp(InitState) -> + process_flag(trap_exit, true), + ServerSeq = + [ + #{desc => "which local address", + cmd => fun(#{domain := Domain} = State) -> + LAddr = which_local_addr(Domain), + LSA = #{family => Domain, addr => LAddr}, + {ok, State#{lsa => LSA}} + end}, + #{desc => "create listen socket", + cmd => fun(#{domain := Domain} = State) -> + case socket:open(Domain, stream, tcp) of + {ok, Sock} -> + {ok, State#{lsock => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "bind to local address", + cmd => fun(#{lsock := LSock, lsa := LSA} = State) -> + case socket:bind(LSock, LSA) of + {ok, Port} -> + {ok, State#{lport => Port}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "make listen socket", + cmd => fun(#{lsock := LSock}) -> + socket:listen(LSock) + end}, + #{desc => "announce server port", + cmd => fun(#{parent := Parent, lport := Port}) -> + ei("announcing port to parent (~p)", [Parent]), + Parent ! {server_port, self(), Port}, + ok + end}, + #{desc => "await connection", + cmd => fun(#{lsock := LSock} = State) -> + case socket:accept(LSock) of + {ok, Sock} -> + ei("accepted: ~n ~p", [Sock]), + {ok, State#{tsock => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "await request", + cmd => fun(#{tsock := Sock, recv := Recv}) -> + case Recv(Sock) of + {ok, ?BASIC_REQ} -> + ok; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "send reply", + cmd => fun(#{tsock := Sock, send := Send}) -> + Send(Sock, ?BASIC_REP) + end}, + #{desc => "sleep some", + cmd => fun(_) -> + ?SLEEP(1000), + ok + end}, + #{desc => "close traffic socket", + cmd => fun(#{tsock := Sock}) -> + socket:close(Sock) + end}, + #{desc => "close listen socket", + cmd => fun(#{lsock := Sock}) -> + socket:close(Sock) + end}, + #{desc => "finish", + cmd => fun(_) -> + {ok, normal} + end} + ], + + ClientSeq = + [ + #{desc => "which server (local) address", + cmd => fun(#{domain := Domain, server_port := Port} = State) -> + LAddr = which_local_addr(Domain), + LSA = #{family => Domain, + addr => LAddr}, + SSA = LSA#{port => Port}, + {ok, State#{lsa => LSA, ssa => SSA}} + end}, + #{desc => "create socket", + cmd => fun(#{domain := Domain} = State) -> + case socket:open(Domain, stream, tcp) of + {ok, Sock} -> + {ok, State#{sock => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "bind to local address", + cmd => fun(#{sock := Sock, lsa := LSA} = State) -> + case socket:bind(Sock, LSA) of + {ok, Port} -> + {ok, State#{port => Port}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "connect to server", + cmd => fun(#{sock := Sock, ssa := SSA}) -> + socket:connect(Sock, SSA) + end}, + #{desc => "send request (to server)", + cmd => fun(#{sock := Sock, send := Send}) -> + Send(Sock, ?BASIC_REQ) + end}, + #{desc => "recv reply (from server)", + cmd => fun(#{sock := Sock, recv := Recv}) -> + {ok, ?BASIC_REP} = Recv(Sock), + ok + end}, + #{desc => "close socket", + cmd => fun(#{sock := Sock}) -> + socket:close(Sock) + end}, + #{desc => "finish", + cmd => fun(_) -> + {ok, normal} + end} + ], + + p("start server evaluator"), + Server = evaluator_start("server", ServerSeq, InitState), + p("await server (~p) port", [Server]), + SPort = receive + {server_port, Server, Port} -> + Port + end, + p("start client evaluator"), + Client = evaluator_start("client", ClientSeq, InitState#{server_port => SPort}), + p("await evaluator(s)"), + ok = await_evaluator_finish([Server, Client]). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% %% +%% API OPTIONS %% +%% %% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Perform some simple getopt and setopt with the level = otp options +api_opt_simple_otp_options(suite) -> + []; +api_opt_simple_otp_options(doc) -> + []; +api_opt_simple_otp_options(_Config) when is_list(_Config) -> + tc_try(api_opt_simple_otp_options, + fun() -> api_opt_simple_otp_options() end). + +api_opt_simple_otp_options() -> + Get = fun(S, Key) -> + socket:getopt(S, otp, Key) + end, + Set = fun(S, Key, Val) -> + socket:setopt(S, otp, Key, Val) + end, + + Seq = + [ + %% *** Init part *** + #{desc => "create socket", + cmd => fun(#{domain := Domain, + type := Type, + protocol := Protocol} = State) -> + Sock = sock_open(Domain, Type, Protocol), + {ok, State#{sock => Sock}} + end}, + #{desc => "create dummy process", + cmd => fun(State) -> + Pid = spawn_link(fun() -> + put(sname, "dummy"), + receive + die -> + exit(normal) + end + end), + {ok, State#{dummy => Pid}} + end}, + + %% *** Check iow part *** + #{desc => "get iow", + cmd => fun(#{sock := Sock} = State) -> + case Get(Sock, iow) of + {ok, IOW} when is_boolean(IOW) -> + {ok, State#{iow => IOW}}; + {ok, InvalidIOW} -> + {error, {invalid, InvalidIOW}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "set (new) iow", + cmd => fun(#{sock := Sock, iow := OldIOW} = State) -> + NewIOW = not OldIOW, + case Set(Sock, iow, NewIOW) of + ok -> + {ok, State#{iow => NewIOW}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "get (new) iow", + cmd => fun(#{sock := Sock, iow := IOW}) -> + case Get(Sock, iow) of + {ok, IOW} -> + ok; + {ok, InvalidIOW} -> + {error, {invalid, InvalidIOW}}; + {error, _} = ERROR -> + ERROR + end + end}, + + %% *** Check rcvbuf part *** + #{desc => "get rcvbuf", + cmd => fun(#{sock := Sock} = State) -> + case Get(Sock, rcvbuf) of + {ok, RcvBuf} when is_integer(RcvBuf) -> + {ok, State#{rcvbuf => RcvBuf}}; + {ok, InvalidRcvBuf} -> + {error, {invalid, InvalidRcvBuf}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "set (new) rcvbuf", + cmd => fun(#{sock := Sock, rcvbuf := OldRcvBuf} = State) -> + NewRcvBuf = 2 * OldRcvBuf, + case Set(Sock, rcvbuf, NewRcvBuf) of + ok -> + {ok, State#{rcvbuf => NewRcvBuf}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "get (new) rcvbuf", + cmd => fun(#{sock := Sock, rcvbuf := RcvBuf}) -> + case Get(Sock, rcvbuf) of + {ok, RcvBuf} -> + ok; + {ok, InvalidRcvBuf} -> + {error, {invalid, InvalidRcvBuf}}; + {error, _} = ERROR -> + ERROR + end + end}, + + %% *** Check rcvctrlbuf part *** + #{desc => "get rcvctrlbuf", + cmd => fun(#{sock := Sock} = State) -> + case Get(Sock, rcvctrlbuf) of + {ok, RcvCtrlBuf} when is_integer(RcvCtrlBuf) -> + {ok, State#{rcvctrlbuf => RcvCtrlBuf}}; + {ok, InvalidRcvCtrlBuf} -> + {error, {invalid, InvalidRcvCtrlBuf}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "set (new) rcvctrlbuf", + cmd => fun(#{sock := Sock, rcvctrlbuf := OldRcvCtrlBuf} = State) -> + NewRcvCtrlBuf = 2 * OldRcvCtrlBuf, + case Set(Sock, rcvctrlbuf, NewRcvCtrlBuf) of + ok -> + {ok, State#{rcvctrlbuf => NewRcvCtrlBuf}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "get (new) rcvctrlbuf", + cmd => fun(#{sock := Sock, rcvctrlbuf := RcvCtrlBuf}) -> + case Get(Sock, rcvctrlbuf) of + {ok, RcvCtrlBuf} -> + ok; + {ok, InvalidRcvCtrlBuf} -> + {error, {invalid, InvalidRcvCtrlBuf}}; + {error, _} = ERROR -> + ERROR + end + end}, + %% *** Check rcvctrlbuf part *** + #{desc => "get rcvctrlbuf", + cmd => fun(#{sock := Sock} = State) -> + case Get(Sock, rcvctrlbuf) of + {ok, RcvCtrlBuf} when is_integer(RcvCtrlBuf) -> + {ok, State#{rcvctrlbuf => RcvCtrlBuf}}; + {ok, InvalidRcvCtrlBuf} -> + {error, {invalid, InvalidRcvCtrlBuf}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "set (new) rcvctrlbuf", + cmd => fun(#{sock := Sock, rcvctrlbuf := OldRcvCtrlBuf} = State) -> + NewRcvCtrlBuf = 2 * OldRcvCtrlBuf, + case Set(Sock, rcvctrlbuf, NewRcvCtrlBuf) of + ok -> + {ok, State#{rcvctrlbuf => NewRcvCtrlBuf}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "get (new) rcvctrlbuf", + cmd => fun(#{sock := Sock, rcvctrlbuf := RcvCtrlBuf}) -> + case Get(Sock, rcvctrlbuf) of + {ok, RcvCtrlBuf} -> + ok; + {ok, InvalidRcvCtrlBuf} -> + {error, {invalid, InvalidRcvCtrlBuf}}; + {error, _} = ERROR -> + ERROR + end + end}, + + + %% *** Check sndctrlbuf part *** + #{desc => "get sndctrlbuf", + cmd => fun(#{sock := Sock} = State) -> + case Get(Sock, sndctrlbuf) of + {ok, SndCtrlBuf} when is_integer(SndCtrlBuf) -> + {ok, State#{sndctrlbuf => SndCtrlBuf}}; + {ok, InvalidSndCtrlBuf} -> + {error, {invalid, InvalidSndCtrlBuf}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "set (new) sndctrlbuf", + cmd => fun(#{sock := Sock, sndctrlbuf := OldSndCtrlBuf} = State) -> + NewSndCtrlBuf = 2 * OldSndCtrlBuf, + case Set(Sock, sndctrlbuf, NewSndCtrlBuf) of + ok -> + {ok, State#{sndctrlbuf => NewSndCtrlBuf}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "get (new) sndctrlbuf", + cmd => fun(#{sock := Sock, sndctrlbuf := SndCtrlBuf}) -> + case Get(Sock, sndctrlbuf) of + {ok, SndCtrlBuf} -> + ok; + {ok, InvalidSndCtrlBuf} -> + {error, {invalid, InvalidSndCtrlBuf}}; + {error, _} = ERROR -> + ERROR + end + end}, + + %% *** Check controlling-process part *** + #{desc => "verify self as controlling-process", + cmd => fun(#{sock := Sock}) -> + Self = self(), + case Get(Sock, controlling_process) of + {ok, Self} -> + ok; + {ok, InvalidPid} -> + {error, {invalid, InvalidPid}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "set dummy as controlling-process", + cmd => fun(#{sock := Sock, dummy := Dummy}) -> + Set(Sock, controlling_process, Dummy) + end}, + #{desc => "verify dummy as controlling-process", + cmd => fun(#{sock := Sock, dummy := Dummy}) -> + case Get(Sock, controlling_process) of + {ok, Dummy} -> + ok; + {ok, InvalidPid} -> + {error, {invalid, InvalidPid}}; + {error, _} = ERROR -> + ERROR + end + end}, + + %% *** We are done *** + #{desc => "finish", + cmd => fun(_) -> + {ok, normal} + end} + ], + + p("Run test for stream/tcp socket"), + InitState1 = #{domain => inet, type => stream, protocol => tcp}, + Tester1 = evaluator_start("tcp-tester", Seq, InitState1), + p("await evaluator 1"), + ok = await_evaluator_finish([Tester1]), + + p("Run test for dgram/udp socket"), + InitState2 = #{domain => inet, type => dgram, protocol => udp}, + Tester2 = evaluator_start("udp-tester", Seq, InitState2), + p("await evaluator 2"), + ok = await_evaluator_finish([Tester2]). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Perform some simple getopt and setopt with the level = otp options +api_opt_simple_otp_controlling_process(suite) -> + []; +api_opt_simple_otp_controlling_process(doc) -> + []; +api_opt_simple_otp_controlling_process(_Config) when is_list(_Config) -> + tc_try(api_opt_simple_otp_controlling_process, + fun() -> api_opt_simple_otp_controlling_process() end). + +api_opt_simple_otp_controlling_process() -> + Get = fun(S, Key) -> + socket:getopt(S, otp, Key) + end, + Set = fun(S, Key, Val) -> + socket:setopt(S, otp, Key, Val) + end, + + ClientSeq = + [ + %% *** Init part *** + #{desc => "await start", + cmd => fun(State) -> + receive + {start, Tester, Socket} -> + {ok, State#{tester => Tester, + sock => Socket}} + end + end}, + #{desc => "verify tester as controlling-process", + cmd => fun(#{tester := Tester, sock := Sock} = _State) -> + case Get(Sock, controlling_process) of + {ok, Tester} -> + ok; + {ok, InvalidPid} -> + {error, {invalid, InvalidPid}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "attempt invalid controlling-process transfer (to self)", + cmd => fun(#{sock := Sock} = _State) -> + case Set(Sock, controlling_process, self()) of + {error, not_owner} -> + ok; + ok -> + {error, unexpected_success}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (1)", + cmd => fun(#{tester := Tester} = _State) -> + Tester ! {ready, self()}, + ok + end}, + #{desc => "await continue", + cmd => fun(#{tester := Tester} = _State) -> + receive + {continue, Tester} -> + ok + end + end}, + #{desc => "verify self as controlling-process", + cmd => fun(#{sock := Sock} = _State) -> + Self = self(), + case Get(Sock, controlling_process) of + {ok, Self} -> + ok; + {ok, InvalidPid} -> + {error, {invalid, InvalidPid}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "attempt controlling-process transfer to tester", + cmd => fun(#{tester := Tester, sock := Sock} = _State) -> + Set(Sock, controlling_process, Tester) + end}, + #{desc => "attempt invalid controlling-process transfer (to self)", + cmd => fun(#{sock := Sock} = _State) -> + case Set(Sock, controlling_process, self()) of + {error, not_owner} -> + ok; + ok -> + {error, unexpected_success}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (2)", + cmd => fun(#{tester := Tester} = _State) -> + Tester ! {ready, self()}, + ok + end}, + #{desc => "await termination", + cmd => fun(#{tester := Tester} = State) -> + receive + {terminate, Tester} -> + State1 = maps:remove(tester, State), + State2 = maps:remove(sock, State1), + {ok, State2} + end + end}, + + %% *** We are done *** + #{desc => "finish", + cmd => fun(_) -> + {ok, normal} + end} + ], + + TesterSeq = + [ + %% *** Init part *** + #{desc => "create socket", + cmd => fun(#{domain := Domain, + type := Type, + protocol := Protocol} = State) -> + Sock = sock_open(Domain, Type, Protocol), + {ok, State#{sock => Sock}} + end}, + #{desc => "verify self as controlling-process", + cmd => fun(#{sock := Sock} = _State) -> + Self = self(), + case Get(Sock, controlling_process) of + {ok, Self} -> + ok; + {ok, InvalidPid} -> + {error, {invalid, InvalidPid}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "order (client) start", + cmd => fun(#{client := Client, sock := Sock} = _State) -> + Client ! {start, self(), Sock}, + ok + end}, + #{desc => "await (client) ready (1)", + cmd => fun(#{client := Client} = _State) -> + receive + {ready, Client} -> + ok + end + end}, + #{desc => "attempt controlling-process transfer to client", + cmd => fun(#{client := Client, sock := Sock} = _State) -> + Set(Sock, controlling_process, Client) + end}, + #{desc => "verify client as controlling-process", + cmd => fun(#{client := Client, sock := Sock} = _State) -> + case Get(Sock, controlling_process) of + {ok, Client} -> + ok; + {ok, InvalidPid} -> + {error, {invalid, InvalidPid}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "attempt invalid controlling-process transfer (to self)", + cmd => fun(#{sock := Sock} = _State) -> + case Set(Sock, controlling_process, self()) of + {error, not_owner} -> + ok; + ok -> + {error, unexpected_success}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "order (client) continue", + cmd => fun(#{client := Client} = _State) -> + Client ! {continue, self()}, + ok + end}, + #{desc => "await (client) ready (2)", + cmd => fun(#{client := Client} = _State) -> + receive + {ready, Client} -> + ok + end + end}, + #{desc => "verify self as controlling-process", + cmd => fun(#{sock := Sock} = _State) -> + Self = self(), + case Get(Sock, controlling_process) of + {ok, Self} -> + ok; + {ok, InvalidPid} -> + {error, {invalid, InvalidPid}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "monitor client", + cmd => fun(#{client := Client} = State) -> + MRef = erlang:monitor(process, Client), + {ok, State#{client_mref => MRef}} + end}, + #{desc => "order (client) terminate", + cmd => fun(#{client := Client} = _State) -> + Client ! {terminate, self()}, + ok + end}, + #{desc => "await (client) down", + cmd => fun(#{client := Client} = State) -> + receive + {'DOWN', _, process, Client, _} -> + {ok, maps:remove(client, State)} + end + end}, + #{desc => "close socket", + cmd => fun(#{sock := Sock} = State) -> + sock_close(Sock), + {ok, maps:remove(sock, State)} + end}, + + %% *** We are done *** + #{desc => "finish", + cmd => fun(_) -> + {ok, normal} + end} + ], + + p("Run test for stream/tcp socket"), + ClientInitState1 = #{}, + Client1 = evaluator_start("tcp-client", ClientSeq, ClientInitState1), + TesterInitState1 = #{domain => inet, + type => stream, + protocol => tcp, + client => Client1}, + Tester1 = evaluator_start("tcp-tester", TesterSeq, TesterInitState1), + p("await stream/tcp evaluator"), + ok = await_evaluator_finish([Tester1, Client1]), + + p("Run test for dgram/udp socket"), + ClientInitState2 = #{}, + Client2 = evaluator_start("udp-client", ClientSeq, ClientInitState2), + TesterInitState2 = #{domain => inet, + type => dgram, + protocol => udp, + client => Client2}, + Tester2 = evaluator_start("udp-tester", TesterSeq, TesterInitState2), + p("await dgram/udp evaluator"), + ok = await_evaluator_finish([Tester2, Client2]). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% %% +%% API OPERATIONS WITH TIMEOUT %% +%% %% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% This test case is intended to test the connect timeout option +%% on an IPv4 TCP (stream) socket. +api_to_connect_tcp4(suite) -> + []; +api_to_connect_tcp4(doc) -> + []; +api_to_connect_tcp4(_Config) when is_list(_Config) -> + tc_try(api_to_connect_tcp4, + fun() -> + InitState = #{domain => inet, timeout => 5000}, + ok = api_to_connect_tcp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% This test case is intended to test the connect timeout option +%% on an IPv6 TCP (stream) socket. +api_to_connect_tcp6(suite) -> + []; +api_to_connect_tcp6(doc) -> + []; +api_to_connect_tcp6(_Config) when is_list(_Config) -> + tc_try(api_to_connect_tcp6, + fun() -> + not_yet_implemented(), + InitState = #{domain => inet6, timeout => 5000}, + ok = api_to_connect_tcp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% We use the backlog (listen) argument to test this. +%% Note that the behaviour of the TCP "server side" can vary when +%% a client connect to a "busy" server (full backlog). +%% For instance, on FreeBSD (11.2) the reponse when the backlog is full +%% is a econreset. + +api_to_connect_tcp(InitState) -> + process_flag(trap_exit, true), + + ServerSeq = + [ + %% *** Wait for start order part *** + #{desc => "await start (from tester)", + cmd => fun(State) -> + receive + {start, Tester} when is_pid(Tester) -> + {ok, State#{tester => Tester}} + end + end}, + + %% *** Init part *** + #{desc => "which local address", + cmd => fun(#{domain := Domain} = State) -> + LAddr = which_local_addr(Domain), + LSA = #{family => Domain, addr => LAddr}, + {ok, State#{lsa => LSA}} + end}, + #{desc => "create listen socket", + cmd => fun(#{domain := Domain} = State) -> + case socket:open(Domain, stream, tcp) of + {ok, Sock} -> + {ok, State#{lsock => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "bind to local address", + cmd => fun(#{lsock := LSock, lsa := LSA} = State) -> + case socket:bind(LSock, LSA) of + {ok, Port} -> + {ok, State#{lport => Port}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "make listen socket (with backlog = 1)", + cmd => fun(#{lsock := LSock}) -> + socket:listen(LSock, 1) + end}, + #{desc => "monitor server", + cmd => fun(#{tester := Tester} = State) -> + MRef = erlang:monitor(process, Tester), + {ok, State#{tester_mref => MRef}} + end}, + #{desc => "announce ready", + cmd => fun(#{tester := Tester, lport := Port}) -> + ei("announcing ready to tester (~p)", [Tester]), + Tester ! {ready, self(), Port}, + ok + end}, + #{desc => "await terminate (from tester)", + cmd => fun(#{tester := Tester} = State) -> + receive + {'DOWN', _, process, Tester, Reason} -> + ee("Unexpected DOWN regarding tester ~p: " + "~n ~p", [Reason]), + {error, {unexpected_exit, tester}}; + {terminate, Tester} -> + {ok, maps:remove(tester, State)} + end + end}, + + %% *** We are done *** + #{desc => "finish", + cmd => fun(_) -> + {ok, normal} + end} + ], + + TesterSeq = + [ + %% *** Init part *** + #{desc => "which local address", + cmd => fun(#{domain := Domain} = State) -> + LAddr = which_local_addr(Domain), + LSA = #{family => Domain, addr => LAddr}, + {ok, State#{lsa => LSA}} + end}, + #{desc => "create socket 1", + cmd => fun(#{domain := Domain} = State) -> + case socket:open(Domain, stream, tcp) of + {ok, Sock} -> + {ok, State#{sock1 => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "create socket 2", + cmd => fun(#{domain := Domain} = State) -> + case socket:open(Domain, stream, tcp) of + {ok, Sock} -> + {ok, State#{sock2 => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "create socket 3", + cmd => fun(#{domain := Domain} = State) -> + case socket:open(Domain, stream, tcp) of + {ok, Sock} -> + {ok, State#{sock3 => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "bind socket 1 to local address", + cmd => fun(#{sock1 := Sock, lsa := LSA} = _State) -> + case socket:bind(Sock, LSA) of + {ok, _} -> + ok; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "bind socket 2 to local address", + cmd => fun(#{sock2 := Sock, lsa := LSA} = _State) -> + case socket:bind(Sock, LSA) of + {ok, _} -> + ok; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "bind socket 3 to local address", + cmd => fun(#{sock3 := Sock, lsa := LSA} = _State) -> + case socket:bind(Sock, LSA) of + {ok, _} -> + ok; + {error, _} = ERROR -> + ERROR + end + end}, + + %% *** Synchronize with the server *** + #{desc => "order (server) start", + cmd => fun(#{server := Server}) -> + Server ! {start, self()}, + ok + end}, + #{desc => "await ready (from server)", + cmd => fun(#{server := Server, lsa := LSA} = State) -> + receive + {ready, Server, Port} -> + {ok, State#{ssa => LSA#{port => Port}}} + end + end}, + + %% *** Connect sequence *** + #{desc => "order (server) start", + cmd => fun(#{sock1 := Sock1, + sock2 := Sock2, + sock3 := Sock3, + ssa := SSA, + timeout := To}) -> + Socks = [Sock1, Sock2, Sock3], + api_to_connect_tcp_await_timeout(Socks, To, SSA) + end}, + + %% *** Terminate server *** + #{desc => "monitor server", + cmd => fun(#{server := Server} = State) -> + MRef = erlang:monitor(process, Server), + {ok, State#{server_mref => MRef}} + end}, + #{desc => "order (server) terminate", + cmd => fun(#{server := Server} = _State) -> + Server ! {terminate, self()}, + ok + end}, + #{desc => "await (server) down", + cmd => fun(#{server := Server} = State) -> + receive + {'DOWN', _, process, Server, _} -> + State1 = maps:remove(server, State), + State2 = maps:remove(ssa, State1), + {ok, State2} + end + end}, + + %% *** Close our sockets *** + #{desc => "close socket 3", + cmd => fun(#{sock3 := Sock} = State) -> + sock_close(Sock), + {ok, maps:remove(sock3, State)} + + end}, + #{desc => "close socket 2", + cmd => fun(#{sock2 := Sock} = State) -> + sock_close(Sock), + {ok, maps:remove(sock2, State)} + + end}, + #{desc => "close socket 1", + cmd => fun(#{sock1 := Sock} = State) -> + sock_close(Sock), + {ok, maps:remove(sock1, State)} + + end}, + + %% *** We are done *** + #{desc => "finish", + cmd => fun(_) -> + {ok, normal} + end} + ], + + p("create server evaluator"), + ServerInitState = InitState, + Server = evaluator_start("server", ServerSeq, ServerInitState), + + p("create tester evaluator"), + TesterInitState = InitState#{server => Server}, + Tester = evaluator_start("tester", TesterSeq, TesterInitState), + + p("await evaluator(s)"), + ok = await_evaluator_finish([Server, Tester]). + + +api_to_connect_tcp_await_timeout(Socks, To, ServerSA) -> + api_to_connect_tcp_await_timeout(Socks, To, ServerSA, 1). + +api_to_connect_tcp_await_timeout([], _To, _ServerSA, _ID) -> + ?FAIL(unexpected_success); +api_to_connect_tcp_await_timeout([Sock|Socks], To, ServerSA, ID) -> + ei("~w: try connect", [ID]), + Start = t(), + case socket:connect(Sock, ServerSA, To) of + {error, timeout} -> + ei("expected timeout (~w)", [ID]), + Stop = t(), + TDiff = tdiff(Start, Stop), + if + (TDiff >= To) -> + ok; + true -> + {error, {unexpected_timeout, TDiff, To}} + end; + {error, econnreset = Reason} -> + ei("failed connecting: ~p - giving up", [Reason]), + ok; + {error, Reason} -> + ee("failed connecting: ~p", [Reason]), + ?FAIL({connect, Reason}); + ok -> + ei("unexpected success (~w) - try next", [ID]), + api_to_connect_tcp_await_timeout(Socks, To, ServerSA, ID+1) + end. + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% This test case is intended to test the accept timeout option +%% on an IPv4 TCP (stream) socket. +api_to_accept_tcp4(suite) -> + []; +api_to_accept_tcp4(doc) -> + []; +api_to_accept_tcp4(_Config) when is_list(_Config) -> + tc_try(api_to_accept_tcp4, + fun() -> + InitState = #{domain => inet, timeout => 5000}, + ok = api_to_accept_tcp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% This test case is intended to test the accept timeout option +%% on an IPv6 TCP (stream) socket. +api_to_accept_tcp6(suite) -> + []; +api_to_accept_tcp6(doc) -> + []; +api_to_accept_tcp6(_Config) when is_list(_Config) -> + tc_try(api_to_accept_tcp4, + fun() -> + not_yet_implemented(), + InitState = #{domain => inet6, timeout => 5000}, + ok = api_to_accept_tcp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +api_to_accept_tcp(InitState) -> + TesterSeq = + [ + %% *** Init part *** + #{desc => "which local address", + cmd => fun(#{domain := Domain} = State) -> + LAddr = which_local_addr(Domain), + LSA = #{family => Domain, addr => LAddr}, + {ok, State#{lsa => LSA}} + end}, + #{desc => "create (listen) socket", + cmd => fun(#{domain := Domain} = State) -> + case socket:open(Domain, stream, tcp) of + {ok, Sock} -> + {ok, State#{lsock => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "bind to local address", + cmd => fun(#{lsock := LSock, lsa := LSA} = _State) -> + case socket:bind(LSock, LSA) of + {ok, _} -> + ok; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "make listen socket", + cmd => fun(#{lsock := LSock}) -> + socket:listen(LSock) + end}, + + %% *** The actual test part *** + #{desc => "attempt to accept (without success)", + cmd => fun(#{lsock := LSock, timeout := To} = State) -> + Start = t(), + case socket:accept(LSock, To) of + {error, timeout} -> + {ok, State#{start => Start, stop => t()}}; + {ok, Sock} -> + (catch socket:close(Sock)), + {error, unexpected_success}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "validate timeout time", + cmd => fun(#{start := Start, stop := Stop, timeout := To} = _State) -> + TDiff = tdiff(Start, Stop), + if + (TDiff >= To) -> + ok; + true -> + {error, {unexpected_timeout, TDiff, To}} + end + end}, + + %% *** Close (listen) socket *** + #{desc => "close (listen) socket", + cmd => fun(#{lsock := LSock} = State) -> + sock_close(LSock), + {ok, maps:remove(sock3, State)} + end}, + + %% *** We are done *** + #{desc => "finish", + cmd => fun(_) -> + {ok, normal} + end} + ], + + p("create tester evaluator"), + Tester = evaluator_start("tester", TesterSeq, InitState), + + p("await evaluator"), + ok = await_evaluator_finish([Tester]). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% This test case is intended to test the multi accept timeout option +%% on an IPv4 TCP (stream) socket with multiple acceptor processes +%% (three in this case). +api_to_maccept_tcp4(suite) -> + []; +api_to_maccept_tcp4(doc) -> + []; +api_to_maccept_tcp4(_Config) when is_list(_Config) -> + tc_try(api_to_maccept_tcp4, + fun() -> + InitState = #{domain => inet, timeout => 5000}, + ok = api_to_maccept_tcp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% This test case is intended to test the accept timeout option +%% on an IPv6 TCP (stream) socket. +api_to_maccept_tcp6(suite) -> + []; +api_to_maccept_tcp6(doc) -> + []; +api_to_maccept_tcp6(_Config) when is_list(_Config) -> + tc_try(api_to_maccept_tcp4, + fun() -> + not_yet_implemented(), + InitState = #{domain => inet6, timeout => 5000}, + ok = api_to_maccept_tcp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +api_to_maccept_tcp(InitState) -> + PrimAcceptorSeq = + [ + %% *** Init part *** + #{desc => "await start", + cmd => fun(State) -> + receive + {start, Tester} -> + MRef = erlang:monitor(process, Tester), + {ok, State#{tester => Tester, + tester_mref => MRef}} + end + end}, + #{desc => "which local address", + cmd => fun(#{domain := Domain} = State) -> + LAddr = which_local_addr(Domain), + LSA = #{family => Domain, addr => LAddr}, + {ok, State#{lsa => LSA}} + end}, + #{desc => "create (listen) socket", + cmd => fun(#{domain := Domain} = State) -> + case socket:open(Domain, stream, tcp) of + {ok, Sock} -> + {ok, State#{lsock => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "bind to local address", + cmd => fun(#{lsock := LSock, lsa := LSA} = _State) -> + case socket:bind(LSock, LSA) of + {ok, _} -> + ok; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "make listen socket", + cmd => fun(#{lsock := LSock}) -> + socket:listen(LSock) + end}, + + #{desc => "announce ready", + cmd => fun(#{lsock := LSock, tester := Tester}) -> + ei("announcing port to tester (~p)", [Tester]), + Tester ! {ready, self(), LSock}, + ok + end}, + #{desc => "await continue", + cmd => fun(#{tester := Tester} = _State) -> + receive + {'DOWN', _, process, Tester, Reason} -> + ee("Unexpected DOWN regarding tester ~p: " + "~n ~p", [Reason]), + {error, {unexpected_exit, tester}}; + {continue, Tester} -> + ok + end + end}, + + %% *** The actual test part *** + #{desc => "attempt to accept (without success)", + cmd => fun(#{lsock := LSock, timeout := To} = State) -> + Start = t(), + case socket:accept(LSock, To) of + {error, timeout} -> + {ok, State#{start => Start, stop => t()}}; + {ok, Sock} -> + (catch socket:close(Sock)), + {error, unexpected_success}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "validate timeout time", + cmd => fun(#{start := Start, stop := Stop, timeout := To} = _State) -> + TDiff = tdiff(Start, Stop), + if + (TDiff >= To) -> + ok; + true -> + {error, {unexpected_timeout, TDiff, To}} + end + end}, + #{desc => "announce ready", + cmd => fun(#{tester := Tester}) -> + ei("announcing port to tester (~p)", [Tester]), + Tester ! {ready, self()}, + ok + end}, + #{desc => "await terminate", + cmd => fun(#{tester := Tester} = _State) -> + receive + {'DOWN', _, process, Tester, Reason} -> + ee("Unexpected DOWN regarding tester ~p: " + "~n ~p", [Reason]), + {error, {unexpected_exit, tester}}; + {terminate, Tester} -> + ok + end + end}, + + %% *** Close (listen) socket *** + #{desc => "close (listen) socket", + cmd => fun(#{lsock := LSock} = State) -> + sock_close(LSock), + {ok, maps:remove(lsock, State)} + end}, + + %% *** We are done *** + #{desc => "finish", + cmd => fun(_) -> + {ok, normal} + end} + ], + + + SecAcceptorSeq = + [ + %% *** Init part *** + #{desc => "await start", + cmd => fun(State) -> + receive + {start, Tester, LSock} -> + MRef = erlang:monitor(process, Tester), + {ok, State#{tester => Tester, + lsock => LSock, + tester_mref => MRef}} + end + end}, + #{desc => "announce ready (1)", + cmd => fun(#{tester := Tester} = _State) -> + Tester ! {ready, self()}, + ok + end}, + #{desc => "await continue", + cmd => fun(#{tester := Tester} = _State) -> + receive + {'DOWN', _, process, Tester, Reason} -> + ee("Unexpected DOWN regarding tester ~p: " + "~n ~p", [Reason]), + {error, {unexpected_exit, tester, Reason}}; + {continue, Tester} -> + ok + end + end}, + + %% *** The actual test part *** + #{desc => "attempt to accept (without success)", + cmd => fun(#{lsock := LSock, timeout := To} = State) -> + Start = t(), + case socket:accept(LSock, To) of + {error, timeout} -> + {ok, State#{start => Start, stop => t()}}; + {ok, Sock} -> + (catch socket:close(Sock)), + {error, unexpected_success}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "validate timeout time", + cmd => fun(#{start := Start, stop := Stop, timeout := To} = _State) -> + TDiff = tdiff(Start, Stop), + if + (TDiff >= To) -> + ok; + true -> + {error, {unexpected_timeout, TDiff, To}} + end + end}, + #{desc => "announce ready (2)", + cmd => fun(#{tester := Tester} = _State) -> + Tester ! {ready, self()}, + ok + end}, + #{desc => "await terminate", + cmd => fun(#{tester := Tester} = _State) -> + receive + {'DOWN', _, process, Tester, Reason} -> + ee("Unexpected DOWN regarding tester ~p: " + "~n ~p", [Reason]), + {error, {unexpected_exit, tester, Reason}}; + {terminate, Tester} -> + ok + end + end}, + + %% *** We are done *** + #{desc => "finish", + cmd => fun(_) -> + {ok, normal} + end} + ], + + + TesterSeq = + [ + %% Init part + #{desc => "monitor prim-acceptor", + cmd => fun(#{prim_acceptor := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + #{desc => "monitor sec-acceptor 1", + cmd => fun(#{sec_acceptor1 := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + #{desc => "monitor sec-acceptor 2", + cmd => fun(#{sec_acceptor2 := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + + + %% Start the prim-acceptor + #{desc => "start prim-acceptor", + cmd => fun(#{prim_acceptor := Pid} = _State) -> + Pid ! {start, self()}, + ok + end}, + #{desc => "await prim-acceptor ready (1)", + cmd => fun(#{prim_acceptor := Pid} = State) -> + receive + {'DOWN', _, process, Pid, Reason} -> + ee("Unexpected DOWN regarding prim-acceptor ~p:" + "~n ~p", [Reason]), + {error, {unexpected_exit, prim_acceptor}}; + {ready, Pid, LSock} -> + {ok, State#{lsock => LSock}} + end + end}, + + %% Start sec-acceptor-1 + #{desc => "start sec-acceptor 1", + cmd => fun(#{sec_acceptor1 := Pid, lsock := LSock} = _State) -> + Pid ! {start, self(), LSock}, + ok + end}, + #{desc => "await sec-acceptor 1 ready (1)", + cmd => fun(#{sec_acceptor1 := Pid} = _State) -> + receive + {'DOWN', _, process, Pid, Reason} -> + ee("Unexpected DOWN regarding sec-acceptor 1 ~p:" + "~n ~p", [Reason]), + {error, {unexpected_exit, sec_acceptor_1}}; + {ready, Pid} -> + ok + end + end}, + + %% Start sec-acceptor-2 + #{desc => "start sec-acceptor 2", + cmd => fun(#{sec_acceptor2 := Pid, lsock := LSock} = _State) -> + Pid ! {start, self(), LSock}, + ok + end}, + #{desc => "await sec-acceptor 2 ready (1)", + cmd => fun(#{sec_acceptor2 := Pid} = _State) -> + receive + {'DOWN', _, process, Pid, Reason} -> + ee("Unexpected DOWN regarding sec-acceptor 2 ~p:" + "~n ~p", [Reason]), + {error, {unexpected_exit, sec_acceptor_2}}; + {ready, Pid} -> + ok + end + end}, + + %% Activate the acceptor(s) + #{desc => "active prim-acceptor", + cmd => fun(#{prim_acceptor := Pid} = _State) -> + Pid ! {continue, self()}, + ok + end}, + #{desc => "active sec-acceptor 1", + cmd => fun(#{sec_acceptor1 := Pid} = _State) -> + Pid ! {continue, self()}, + ok + end}, + #{desc => "active sec-acceptor 2", + cmd => fun(#{sec_acceptor2 := Pid} = _State) -> + Pid ! {continue, self()}, + ok + end}, + + %% Await acceptor(s) completions + #{desc => "await prim-acceptor ready (2)", + cmd => fun(#{prim_acceptor := Pid} = _State) -> + receive + {'DOWN', _, process, Pid, Reason} -> + ee("Unexpected DOWN regarding prim-acceptor ~p:" + "~n ~p", [Reason]), + {error, {unexpected_exit, prim_acceptor}}; + {ready, Pid} -> + ok + end + end}, + #{desc => "await sec-acceptor 1 ready (2)", + cmd => fun(#{sec_acceptor1 := Pid} = _State) -> + receive + {'DOWN', _, process, Pid, Reason} -> + ee("Unexpected DOWN regarding sec-acceptor 1 ~p:" + "~n ~p", [Reason]), + {error, {unexpected_exit, sec_acceptor_1}}; + {ready, Pid} -> + ok + end + end}, + #{desc => "await sec-acceptor 2 ready (2)", + cmd => fun(#{sec_acceptor2 := Pid} = _State) -> + receive + {'DOWN', _, process, Pid, Reason} -> + ee("Unexpected DOWN regarding sec-acceptor 2 ~p:" + "~n ~p", [Reason]), + {error, {unexpected_exit, sec_acceptor_2}}; + {ready, Pid} -> + ok + end + end}, + + + %% Terminate the acceptor(s) + #{desc => "order prim-acceptor to terminate", + cmd => fun(#{prim_acceptor := Pid} = _State) -> + ei("send terminate command to prim-acceptor (~p)", [Pid]), + Pid ! {terminate, self()}, + ok + end}, + #{desc => "order sec-acceptor 1 to terminate", + cmd => fun(#{sec_acceptor1 := Pid} = _State) -> + ei("send terminate command to sec-acceptor-1 (~p)", [Pid]), + Pid ! {terminate, self()}, + ok + end}, + #{desc => "order sec-acceptor 2 to terminate", + cmd => fun(#{sec_acceptor2 := Pid} = _State) -> + ei("send terminate command to sec-acceptor-2 (~p)", [Pid]), + Pid ! {terminate, self()}, + ok + end}, + + %% Await acceptor(s) termination + #{desc => "await prim-acceptor termination", + cmd => fun(#{prim_acceptor := Pid} = State) -> + receive + {'DOWN', _, process, Pid, _} -> + State1 = maps:remove(prim_acceptor, State), + {ok, State1} + end + end}, + #{desc => "await sec-acceptor 1 termination", + cmd => fun(#{sec_acceptor1 := Pid} = State) -> + receive + {'DOWN', _, process, Pid, _} -> + State1 = maps:remove(sec_acceptor1, State), + {ok, State1} + end + end}, + #{desc => "await sec-acceptor 2 termination", + cmd => fun(#{sec_acceptor2 := Pid} = State) -> + receive + {'DOWN', _, process, Pid, _} -> + State1 = maps:remove(sec_acceptor2, State), + {ok, State1} + end + end}, + + %% *** We are done *** + #{desc => "finish", + cmd => fun(_) -> + {ok, normal} + end} + ], + + p("create prim-acceptor evaluator"), + PrimAInitState = InitState, + PrimAcceptor = evaluator_start("prim-acceptor", + PrimAcceptorSeq, PrimAInitState), + + p("create prim-acceptor 1 evaluator"), + SecAInitState1 = maps:remove(domain, InitState), + SecAcceptor1 = evaluator_start("sec-acceptor-1", + SecAcceptorSeq, SecAInitState1), + + p("create prim-acceptor 2 evaluator"), + SecAInitState2 = SecAInitState1, + SecAcceptor2 = evaluator_start("sec-acceptor-2", + SecAcceptorSeq, SecAInitState2), + + p("create tester evaluator"), + TesterInitState = #{prim_acceptor => PrimAcceptor, + sec_acceptor1 => SecAcceptor1, + sec_acceptor2 => SecAcceptor2}, + Tester = evaluator_start("tester", TesterSeq, TesterInitState), + + p("await evaluator(s)"), + ok = await_evaluator_finish([PrimAcceptor, SecAcceptor1, SecAcceptor2, Tester]). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% This test case is intended to test the send timeout option +%% on an IPv4 TCP (stream) socket. +api_to_send_tcp4(suite) -> + []; +api_to_send_tcp4(doc) -> + []; +api_to_send_tcp4(_Config) when is_list(_Config) -> + tc_try(api_to_send_tcp4, + fun() -> + not_yet_implemented()%% , + %% ok = api_to_send_tcp(inet) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% This test case is intended to test the send timeout option +%% on an IPv6 TCP (stream) socket. +api_to_send_tcp6(suite) -> + []; +api_to_send_tcp6(doc) -> + []; +api_to_send_tcp6(_Config) when is_list(_Config) -> + tc_try(api_to_send_tcp6, + fun() -> + not_yet_implemented()%% , + %% ok = api_to_send_tcp(inet6) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% This test case is intended to test the sendto timeout option +%% on an IPv4 UDP (dgram) socket. +api_to_sendto_udp4(suite) -> + []; +api_to_sendto_udp4(doc) -> + []; +api_to_sendto_udp4(_Config) when is_list(_Config) -> + tc_try(api_to_sendto_udp4, + fun() -> + not_yet_implemented()%% , + %% ok = api_to_sendto_to_udp(inet) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% This test case is intended to test the sendto timeout option +%% on an IPv6 UDP (dgram) socket. +api_to_sendto_udp6(suite) -> + []; +api_to_sendto_udp6(doc) -> + []; +api_to_sendto_udp6(_Config) when is_list(_Config) -> + tc_try(api_to_sendto_udp6, + fun() -> + not_yet_implemented()%% , + %% ok = api_to_sendto_to_udp(inet6) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% This test case is intended to test the sendmsg timeout option +%% on an IPv4 TCP (stream) socket. +api_to_sendmsg_tcp4(suite) -> + []; +api_to_sendmsg_tcp4(doc) -> + []; +api_to_sendmsg_tcp4(_Config) when is_list(_Config) -> + tc_try(api_to_sendmsg_tcp4, + fun() -> + not_yet_implemented()%% , + %% ok = api_to_sendmsg_tcp(inet) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% This test case is intended to test the sendmsg timeout option +%% on an IPv6 TCP (stream) socket. +api_to_sendmsg_tcp6(suite) -> + []; +api_to_sendmsg_tcp6(doc) -> + []; +api_to_sendmsg_tcp6(_Config) when is_list(_Config) -> + tc_try(api_to_sendmsg_tcp6, + fun() -> + not_yet_implemented()%% , + %% ok = api_to_sendmsg_tcp(inet6) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% This test case is intended to test the recv timeout option +%% on an IPv4 UDP (dgram) socket. To test this we must connect +%% the socket. +api_to_recv_udp4(suite) -> + []; +api_to_recv_udp4(doc) -> + []; +api_to_recv_udp4(_Config) when is_list(_Config) -> + tc_try(api_to_recv_udp4, + fun() -> + not_yet_implemented()%%, + %%ok = api_to_recv_udp(inet) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% This test case is intended to test the recv timeout option +%% on an IPv6 UDP (dgram) socket. To test this we must connect +%% the socket. +api_to_recv_udp6(suite) -> + []; +api_to_recv_udp6(doc) -> + []; +api_to_recv_udp6(_Config) when is_list(_Config) -> + tc_try(api_to_recv_udp6, + fun() -> + not_yet_implemented()%% , + %% ok = api_to_recv_udp(inet6) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% This test case is intended to test the recv timeout option +%% on an IPv4 TCP (stream) socket. +api_to_recv_tcp4(suite) -> + []; +api_to_recv_tcp4(doc) -> + []; +api_to_recv_tcp4(_Config) when is_list(_Config) -> + tc_try(api_to_recv_tcp4, + fun() -> + Recv = fun(Sock, To) -> socket:recv(Sock, 0, To) end, + InitState = #{domain => inet, + recv => Recv, + timeout => 5000}, + ok = api_to_receive_tcp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% This test case is intended to test the recv timeout option +%% on an IPv6 TCP (stream) socket. +api_to_recv_tcp6(suite) -> + []; +api_to_recv_tcp6(doc) -> + []; +api_to_recv_tcp6(_Config) when is_list(_Config) -> + tc_try(api_to_recv_tcp6, + fun() -> + not_yet_implemented(), + case socket:supports(ipv6) of + true -> + Recv = fun(Sock, To) -> + socket:recv(Sock, 0, To) + end, + InitState = #{domain => inet6, + recv => Recv, + timeout => 5000}, + ok = api_to_receive_tcp(InitState); + false -> + skip("ipv6 not supported") + end + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +api_to_receive_tcp(InitState) -> + process_flag(trap_exit, true), + + ServerSeq = + [ + %% *** Wait for start order *** + #{desc => "await start (from tester)", + cmd => fun(State) -> + receive + {start, Tester} when is_pid(Tester) -> + {ok, State#{tester => Tester}} + end + end}, + + %% *** Init part *** + #{desc => "which local address", + cmd => fun(#{domain := Domain} = State) -> + LAddr = which_local_addr(Domain), + LSA = #{family => Domain, addr => LAddr}, + {ok, State#{lsa => LSA}} + end}, + #{desc => "create listen socket", + cmd => fun(#{domain := Domain} = State) -> + case socket:open(Domain, stream, tcp) of + {ok, Sock} -> + {ok, State#{lsock => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "bind to local address", + cmd => fun(#{lsock := LSock, lsa := LSA} = State) -> + case socket:bind(LSock, LSA) of + {ok, Port} -> + {ok, State#{lport => Port}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "make listen socket (with backlog = 1)", + cmd => fun(#{lsock := LSock}) -> + socket:listen(LSock, 1) + end}, + #{desc => "monitor tester", + cmd => fun(#{tester := Tester} = State) -> + MRef = erlang:monitor(process, Tester), + {ok, State#{tester_mref => MRef}} + end}, + #{desc => "announce ready", + cmd => fun(#{tester := Tester, lport := Port}) -> + Tester ! {ready, self(), Port}, + ok + end}, + #{desc => "await continue", + cmd => fun(#{tester := Tester}) -> + receive + {'DOWN', _, process, Tester, Reason} -> + {error, {unexpected_exit, tester, Reason}}; + {continue, Tester} -> + ok + end + end}, + + %% *** The actual test *** + #{desc => "await accept", + cmd => fun(#{lsock := LSock} = State) -> + case socket:accept(LSock) of + {ok, Sock} -> + %% ok = socket:setopt(Sock, otp, debug, true), + {ok, State#{sock => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "attempt to recv (without success)", + cmd => fun(#{sock := Sock, recv := Recv, timeout := To} = State) -> + Start = t(), + case Recv(Sock, To) of + {error, timeout} -> + {ok, State#{start => Start, stop => t()}}; + {ok, _Data} -> + {error, unexpected_success}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "validate timeout time", + cmd => fun(#{start := Start, stop := Stop, timeout := To} = _State) -> + TDiff = tdiff(Start, Stop), + if + (TDiff >= To) -> + ok; + true -> + {error, {unexpected_timeout, TDiff, To}} + end + end}, + #{desc => "announce ready (recv timeout success)", + cmd => fun(#{tester := Tester} = _State) -> + Tester ! {ready, self()}, + ok + end}, + + %% *** Termination *** + #{desc => "await terminate", + cmd => fun(#{tester := Tester} = State) -> + receive + {'DOWN', _, process, Tester, Reason} -> + {error, {unexpected_exit, tester, Reason}}; + {terminate, Tester} -> + {ok, maps:remove(tester, State)} + end + end}, + %% #{desc => "sleep some (before traffic close)", + %% cmd => fun(_) -> + %% ?SLEEP(1000), + %% ok + %% end}, + %% #{desc => "monitored-by", + %% cmd => fun(_) -> + %% {_, Mons} = process_info(self(), monitored_by), + %% ei("Monitored By: ~p", [Mons]), + %% ok + %% end}, + #{desc => "close (traffic) socket", + cmd => fun(#{sock := Sock} = State) -> + %% ok = socket:setopt(Sock, otp, debug, true), + sock_close(Sock), + {ok, maps:remove(sock, State)} + end}, + %% #{desc => "monitored-by", + %% cmd => fun(_) -> + %% {_, Mons} = process_info(self(), monitored_by), + %% ei("Monitored By: ~p", [Mons]), + %% ok + %% end}, + %% #{desc => "sleep some (before listen close)", + %% cmd => fun(_) -> + %% ?SLEEP(1000), + %% ok + %% end}, + #{desc => "close (listen) socket", + cmd => fun(#{lsock := LSock} = State) -> + sock_close(LSock), + {ok, maps:remove(lsock, State)} + end}, + %% #{desc => "sleep some (after listen close)", + %% cmd => fun(_) -> + %% ?SLEEP(1000), + %% ok + %% end}, + + %% *** We are done *** + #{desc => "finish", + cmd => fun(_) -> + {ok, normal} + end} + ], + + ClientSeq = + [ + %% *** Wait for start order part *** + #{desc => "await start (from tester)", + cmd => fun(State) -> + receive + {start, Tester, Port} when is_pid(Tester) -> + {ok, State#{tester => Tester, + server_port => Port}} + end + end}, + + %% *** Init part *** + #{desc => "which local address", + cmd => fun(#{domain := Domain, server_port := Port} = State) -> + LAddr = which_local_addr(Domain), + LSA = #{family => Domain, + addr => LAddr}, + SSA = LSA#{port => Port}, + {ok, State#{lsa => LSA, ssa => SSA}} + end}, + #{desc => "create socket", + cmd => fun(#{domain := Domain} = State) -> + case socket:open(Domain, stream, tcp) of + {ok, Sock} -> + {ok, State#{sock => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "bind to local address", + cmd => fun(#{sock := Sock, lsa := LSA} = _State) -> + case socket:bind(Sock, LSA) of + {ok, _} -> + ok; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "monitor tester", + cmd => fun(#{tester := Tester} = State) -> + MRef = erlang:monitor(process, Tester), + {ok, State#{tester_mref => MRef}} + end}, + #{desc => "announce ready", + cmd => fun(#{tester := Tester} = _State) -> + Tester ! {ready, self()}, + ok + end}, + + %% *** The actual test *** + #{desc => "await continue (with connect)", + cmd => fun(#{tester := Tester} = _State) -> + receive + {'DOWN', _, process, Tester, Reason} -> + {error, {unexpected_exit, tester, Reason}}; + {continue, Tester} -> + ok + end + end}, + #{desc => "connect", + cmd => fun(#{sock := Sock, ssa := SSA}) -> + sock_connect(Sock, SSA), + ok + end}, + + %% *** Termination *** + #{desc => "await terminate", + cmd => fun(#{tester := Tester} = State) -> + receive + {'DOWN', _, process, Tester, Reason} -> + {error, {unexpected_exit, tester, Reason}}; + {terminate, Tester} -> + {ok, maps:remove(tester, State)} + end + end}, + #{desc => "close socket", + cmd => fun(#{sock := Sock} = State) -> + sock_close(Sock), + {ok, maps:remove(sock, State)} + end}, + + %% *** We are done *** + #{desc => "finish", + cmd => fun(_) -> + {ok, normal} + end} + ], + + TesterSeq = + [ + %% *** Init part *** + #{desc => "monitor server", + cmd => fun(#{server := Server} = State) -> + MRef = erlang:monitor(process, Server), + {ok, State#{server_mref => MRef}} + end}, + #{desc => "monitor client", + cmd => fun(#{client := Client} = State) -> + MRef = erlang:monitor(process, Client), + {ok, State#{client_mref => MRef}} + end}, + + %% *** Activate server *** + #{desc => "start server", + cmd => fun(#{server := Server} = _State) -> + Server ! {start, self()}, + ok + end}, + #{desc => "await server ready (init)", + cmd => fun(#{server := Server} = State) -> + receive + {'DOWN', _, process, Server, Reason} -> + {error, {unexpected_exit, server, Reason}}; + {ready, Server, Port} -> + {ok, State#{server_port => Port}} + end + end}, + #{desc => "order server to continue (with accept)", + cmd => fun(#{server := Server} = _State) -> + Server ! {continue, self()}, + ok + end}, + + %% *** Activate client *** + #{desc => "start client", + cmd => fun(#{client := Client, server_port := Port} = _State) -> + Client ! {start, self(), Port}, + ok + end}, + #{desc => "await client ready", + cmd => fun(#{client := Client} = _State) -> + receive + {'DOWN', _, process, Client, Reason} -> + {error, {unexpected_exit, client, Reason}}; + {ready, Client} -> + ok + end + end}, + + %% *** The actual test *** + #{desc => "order client to continue (with connect)", + cmd => fun(#{client := Client} = _State) -> + Client ! {continue, self()}, + ok + end}, + #{desc => "await server ready (accept/recv)", + cmd => fun(#{server := Server} = _State) -> + receive + {'DOWN', _, process, Server, Reason} -> + {error, {unexpected_exit, server, Reason}}; + {ready, Server} -> + ok + end + end}, + + %% *** Termination *** + #{desc => "order client to terminate", + cmd => fun(#{client := Client} = _State) -> + Client ! {terminate, self()}, + ok + end}, + #{desc => "await client termination", + cmd => fun(#{client := Client} = State) -> + receive + {'DOWN', _, process, Client, _Reason} -> + State1 = maps:remove(client, State), + State2 = maps:remove(client_mref, State1), + {ok, State2} + end + end}, + #{desc => "order server to terminate", + cmd => fun(#{server := Server} = _State) -> + Server ! {terminate, self()}, + ok + end}, + #{desc => "await server termination", + cmd => fun(#{server := Server} = State) -> + receive + {'DOWN', _, process, Server, _Reason} -> + State1 = maps:remove(server, State), + State2 = maps:remove(server_mref, State1), + State3 = maps:remove(server_port, State2), + {ok, State3} + end + end}, + + %% *** We are done *** + #{desc => "finish", + cmd => fun(_) -> + {ok, normal} + end} + ], + + + p("start server evaluator"), + ServerInitState = InitState, + Server = evaluator_start("server", ServerSeq, ServerInitState), + + p("start client evaluator"), + ClientInitState = InitState, + Client = evaluator_start("client", ClientSeq, ClientInitState), + + p("start tester evaluator"), + TesterInitState = #{server => Server, client => Client}, + Tester = evaluator_start("tester", TesterSeq, TesterInitState), + + p("await evaluator(s)"), + ok = await_evaluator_finish([Server, Client, Tester]). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% This test case is intended to test the recvfrom timeout option +%% on an IPv4 UDP (dgram) socket. +api_to_recvfrom_udp4(suite) -> + []; +api_to_recvfrom_udp4(doc) -> + []; +api_to_recvfrom_udp4(_Config) when is_list(_Config) -> + tc_try(api_to_recvfrom_udp4, + fun() -> + Recv = fun(Sock, To) -> socket:recvfrom(Sock, 0, To) end, + InitState = #{domain => inet, + recv => Recv, + timeout => 5000}, + ok = api_to_receive_udp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% This test case is intended to test the recvfrom timeout option +%% on an IPv6 UDP (dgram) socket. +api_to_recvfrom_udp6(suite) -> + []; +api_to_recvfrom_udp6(doc) -> + []; +api_to_recvfrom_udp6(_Config) when is_list(_Config) -> + tc_try(api_to_recvfrom_udp6, + fun() -> + not_yet_implemented(), + Recv = fun(Sock, To) -> socket:recvfrom(Sock, 0, To) end, + InitState = #{domain => inet6, + recv => Recv, + timeout => 5000}, + ok = api_to_receive_udp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +api_to_receive_udp(InitState) -> + TesterSeq = + [ + %% *** Init part *** + #{desc => "which local address", + cmd => fun(#{domain := Domain} = State) -> + LAddr = which_local_addr(Domain), + LSA = #{family => Domain, addr => LAddr}, + {ok, State#{lsa => LSA}} + end}, + #{desc => "create socket", + cmd => fun(#{domain := Domain} = State) -> + case socket:open(Domain, dgram, udp) of + {ok, Sock} -> + {ok, State#{sock => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "bind to local address", + cmd => fun(#{sock := Sock, lsa := LSA} = _State) -> + case socket:bind(Sock, LSA) of + {ok, _Port} -> + ok; + {error, _} = ERROR -> + ERROR + end + end}, + + %% *** The actual test *** + #{desc => "attempt to read (without success)", + cmd => fun(#{sock := Sock, recv := Recv, timeout := To} = State) -> + Start = t(), + case Recv(Sock, To) of + {error, timeout} -> + {ok, State#{start => Start, stop => t()}}; + {ok, _} -> + {error, unexpected_sucsess}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "validate timeout time", + cmd => fun(#{start := Start, stop := Stop, timeout := To} = _State) -> + TDiff = tdiff(Start, Stop), + if + (TDiff >= To) -> + ok; + true -> + {error, {unexpected_timeout, TDiff, To}} + end + end}, + + %% *** Termination *** + #{desc => "close socket", + cmd => fun(#{sock := Sock} = _State) -> + sock_close(Sock), + ok + end}, + + %% *** We are done *** + #{desc => "finish", + cmd => fun(_) -> + {ok, normal} + end} + ], + + p("start tester evaluator"), + Tester = evaluator_start("tester", TesterSeq, InitState), + + p("await evaluator"), + ok = await_evaluator_finish([Tester]). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% This test case is intended to test the recvmsg timeout option +%% on an IPv4 UDP (dgram) socket. +api_to_recvmsg_udp4(suite) -> + []; +api_to_recvmsg_udp4(doc) -> + []; +api_to_recvmsg_udp4(_Config) when is_list(_Config) -> + tc_try(api_to_recvmsg_udp4, + fun() -> + Recv = fun(Sock, To) -> socket:recvmsg(Sock, To) end, + InitState = #{domain => inet, + recv => Recv, + timeout => 5000}, + ok = api_to_receive_udp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% This test case is intended to test the recvmsg timeout option +%% on an IPv6 UDP (dgram) socket. +api_to_recvmsg_udp6(suite) -> + []; +api_to_recvmsg_udp6(doc) -> + []; +api_to_recvmsg_udp6(_Config) when is_list(_Config) -> + tc_try(api_to_recvmsg_udp6, + fun() -> + not_yet_implemented(), + Recv = fun(Sock, To) -> socket:recvmsg(Sock, To) end, + InitState = #{domain => inet6, + recv => Recv, + timeout => 5000}, + ok = api_to_receive_udp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% This test case is intended to test the recvmsg timeout option +%% on an IPv4 TCP (stream) socket. +api_to_recvmsg_tcp4(suite) -> + []; +api_to_recvmsg_tcp4(doc) -> + []; +api_to_recvmsg_tcp4(_Config) when is_list(_Config) -> + tc_try(api_to_recvmsg_tcp4, + fun() -> + Recv = fun(Sock, To) -> socket:recvmsg(Sock, To) end, + InitState = #{domain => inet, + recv => Recv, + timeout => 5000}, + ok = api_to_receive_tcp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% This test case is intended to test the recvmsg timeout option +%% on an IPv6 TCP (stream) socket. +api_to_recvmsg_tcp6(suite) -> + []; +api_to_recvmsg_tcp6(doc) -> + []; +api_to_recvmsg_tcp6(_Config) when is_list(_Config) -> + tc_try(api_to_recvmsg_tcp6, + fun() -> + not_yet_implemented(), + Recv = fun(Sock, To) -> socket:recvmsg(Sock, To) end, + InitState = #{domain => inet6, + recv => Recv, + timeout => 5000}, + ok = api_to_receive_tcp(InitState) + end). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% %% +%% SOCKET CLOSURE %% +%% %% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test that the sockets are cleaned up +%% ("removed") when the controlling process terminates (without explicitly +%% calling the close function). For a IPv4 TCP (stream) socket. + +sc_cpe_socket_cleanup_tcp4(suite) -> + []; +sc_cpe_socket_cleanup_tcp4(doc) -> + []; +sc_cpe_socket_cleanup_tcp4(_Config) when is_list(_Config) -> + tc_try(sc_cpe_socket_cleanup_tcp4, + fun() -> + %% not_yet_implemented(), + InitState = #{domain => inet, + type => stream, + protocol => tcp}, + ok = sc_cpe_socket_cleanup(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test that the sockets are cleaned up +%% ("removed") when the controlling process terminates (without explicitly +%% calling the close function). For a IPv6 TCP (stream) socket. + +sc_cpe_socket_cleanup_tcp6(suite) -> + []; +sc_cpe_socket_cleanup_tcp6(doc) -> + []; +sc_cpe_socket_cleanup_tcp6(_Config) when is_list(_Config) -> + tc_try(sc_cpe_socket_cleanup_tcp6, + fun() -> + not_yet_implemented(), + InitState = #{domain => inet6, + type => stream, + protocol => tcp}, + ok = sc_cpe_socket_cleanup(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test that the sockets are cleaned up +%% ("removed") when the controlling process terminates (without explicitly +%% calling the close function). For a IPv4 UDP (dgram) socket. + +sc_cpe_socket_cleanup_udp4(suite) -> + []; +sc_cpe_socket_cleanup_udp4(doc) -> + []; +sc_cpe_socket_cleanup_udp4(_Config) when is_list(_Config) -> + tc_try(sc_cpe_socket_cleanup_udp4, + fun() -> + InitState = #{domain => inet, + type => dgram, + protocol => udp}, + ok = sc_cpe_socket_cleanup(InitState) + end). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test that the sockets are cleaned up +%% (removed) when the controlling process terminates (without explicitly +%% calling the close function). For a IPv6 UDP (dgram) socket. + +sc_cpe_socket_cleanup_udp6(suite) -> + []; +sc_cpe_socket_cleanup_udp6(doc) -> + []; +sc_cpe_socket_cleanup_udp6(_Config) when is_list(_Config) -> + tc_try(sc_cpe_socket_cleanup_udp6, + fun() -> + not_yet_implemented(), + InitState = #{domain => inet6, + type => dgram, + protocol => udp}, + ok = sc_cpe_socket_cleanup(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +sc_cpe_socket_cleanup(InitState) -> + OwnerSeq = + [ + %% *** Wait for start order part *** + #{desc => "await start (from tester)", + cmd => fun(State) -> + receive + {start, Tester} when is_pid(Tester) -> + {ok, State#{tester => Tester}} + end + end}, + + %% *** Init part *** + #{desc => "monitor tester", + cmd => fun(#{tester := Tester} = _State) -> + _MRef = erlang:monitor(process, Tester), + ok + end}, + #{desc => "create socket", + cmd => fun(#{domain := Domain, + type := Type, + protocol := Proto} = State) -> + case socket:open(Domain, Type, Proto) of + {ok, Sock} -> + {ok, State#{sock => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready", + cmd => fun(#{tester := Tester, sock := Sock} = _State) -> + Tester ! {ready, self(), Sock}, + ok + end}, + + %% *** The actual test *** + %% We intentially leave the socket "as is", no explicit close + #{desc => "await terminate (from tester)", + cmd => fun(#{tester := Tester} = State) -> + receive + {'DOWN', _, process, Tester, Reason} -> + ee("Unexpected DOWN regarding tester ~p: " + "~n ~p", [Reason]), + {error, {unexpected_exit, tester}}; + {terminate, Tester} -> + {ok, maps:remove(tester, State)} + end + end}, + %% #{desc => "enable (otp) debug", + %% cmd => fun(#{sock := Sock} = _State) -> + %% ok = socket:setopt(Sock, otp, debug, true) + %% end}, + + %% *** We are done *** + #{desc => "finish", + cmd => fun(_) -> + {ok, normal} + end} + ], + + TesterSeq = + [ + %% *** Init part *** + #{desc => "monitor owner", + cmd => fun(#{owner := Owner} = _State) -> + _MRef = erlang:monitor(process, Owner), + ok + end}, + #{desc => "order (owner) start", + cmd => fun(#{owner := Pid} = _State) -> + Pid ! {start, self()}, + ok + end}, + #{desc => "await (owner) ready", + cmd => fun(#{owner := Owner} = State) -> + receive + {'DOWN', _, process, Owner, Reason} -> + ee("Unexpected DOWN regarding owner ~p: " + "~n ~p", [Reason]), + {error, {unexpected_exit, owner}}; + {ready, Owner, Sock} -> + {ok, State#{sock => Sock}} + end + end}, + #{desc => "verify owner as controlling-process", + cmd => fun(#{owner := Owner, sock := Sock} = _State) -> + case socket:getopt(Sock, otp, controlling_process) of + {ok, Owner} -> + ok; + {ok, Other} -> + {error, {unexpected_owner, Other}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "order (owner) terminate", + cmd => fun(#{owner := Pid} = _State) -> + Pid ! {terminate, self()}, + ok + end}, + #{desc => "await (owner) termination", + cmd => fun(#{owner := Owner} = _State) -> + receive + {'DOWN', _, process, Owner, _} -> + ok + end + end}, + #{desc => "verify no socket (closed)", + cmd => fun(#{owner := Owner, sock := Sock} = _State) -> + case socket:getopt(Sock, otp, controlling_process) of + {ok, Pid} -> + {error, {unexpected_success, Owner, Pid}}; + {error, closed} -> + ok; + {error, Reason} -> + ei("expected failure: ~p", [Reason]), + {error, {unexpected_failure, Reason}} + end + end}, + + %% *** We are done *** + #{desc => "finish", + cmd => fun(_) -> + {ok, normal} + end} + ], + + p("start (socket) owner evaluator"), + Owner = evaluator_start("owner", OwnerSeq, InitState), + + p("start tester evaluator"), + TesterInitState = #{owner => Owner}, + Tester = evaluator_start("tester", TesterSeq, TesterInitState), + + p("await evaluator"), + ok = await_evaluator_finish([Owner, Tester]). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test what happens when a socket is +%% locally closed while a process is calling the recv function. +%% Socket is IPv4. +%% +%% +%% +%% We should really have a similar test cases for when the controlling +%% process exits and there are other processes in recv, accept, and +%% all the other functions. +%% +%% + +sc_lc_recv_response_tcp4(suite) -> + []; +sc_lc_recv_response_tcp4(doc) -> + []; +sc_lc_recv_response_tcp4(_Config) when is_list(_Config) -> + tc_try(sc_lc_recv_response_tcp4, + fun() -> + %% not_yet_implemented(), + Recv = fun(Sock) -> socket:recv(Sock) end, + InitState = #{domain => inet, + type => stream, + protocol => tcp, + recv => Recv}, + ok = sc_lc_receive_response_tcp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test what happens when a socket is +%% locally closed while the process is calling the recv function. +%% Socket is IPv6. + +sc_lc_recv_response_tcp6(suite) -> + []; +sc_lc_recv_response_tcp6(doc) -> + []; +sc_lc_recv_response_tcp6(_Config) when is_list(_Config) -> + tc_try(sc_lc_recv_response_tcp6, + fun() -> + not_yet_implemented(), + Recv = fun(Sock) -> socket:recv(Sock) end, + InitState = #{domain => inet6, + type => stream, + protocol => tcp, + recv => Recv}, + ok = sc_lc_receive_response_tcp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +sc_lc_receive_response_tcp(InitState) -> + %% This is the server that accepts connections. + %% But it is also suppose to close the connection socket, + %% and trigger the read failure for the handler process. + AcceptorSeq = + [ + %% *** Wait for start order part *** + #{desc => "await start (from tester)", + cmd => fun(State) -> + receive + {start, Tester} when is_pid(Tester) -> + {ok, State#{tester => Tester}} + end + end}, + #{desc => "monitor tester", + cmd => fun(#{tester := Tester} = _State) -> + _MRef = erlang:monitor(process, Tester), + ok + end}, + + %% *** Init part *** + #{desc => "which local address", + cmd => fun(#{domain := Domain} = State) -> + LAddr = which_local_addr(Domain), + LSA = #{family => Domain, addr => LAddr}, + {ok, State#{lsa => LSA}} + end}, + #{desc => "create (listen) socket", + cmd => fun(#{domain := Domain, + type := Type, + protocol := Proto} = State) -> + case socket:open(Domain, Type, Proto) of + {ok, Sock} -> + {ok, State#{lsock => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "bind to local address", + cmd => fun(#{lsock := LSock, lsa := LSA} = State) -> + case socket:bind(LSock, LSA) of + {ok, Port} -> + {ok, State#{lport => Port}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "make listen socket", + cmd => fun(#{lsock := LSock}) -> + socket:listen(LSock) + end}, + #{desc => "announce ready (init)", + cmd => fun(#{tester := Tester, lport := Port}) -> + Tester ! {ready, self(), Port}, + ok + end}, + + %% The actual test + #{desc => "await continue (connection)", + cmd => fun(#{tester := Tester} = State) -> + receive + {'DOWN', _, process, Tester, Reason} -> + ee("Unexpected DOWN regarding tester ~p: " + "~n ~p", [Reason]), + {error, {unexpected_exit, tester}}; + {continue, Tester, Handler} -> + {ok, State#{handler => Handler}} + end + end}, + #{desc => "await connection", + cmd => fun(#{lsock := LSock} = State) -> + case socket:accept(LSock) of + {ok, Sock} -> + ei("connection accepted"), + {ok, State#{csock => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "transfer new connection to handler", + cmd => fun(#{handler := Handler, csock := Sock}) -> + ok = socket:setopt(Sock, + otp, controlling_process, + Handler), + Handler ! {connection, Sock}, + ok + end}, + #{desc => "announce ready (connection)", + cmd => fun(#{tester := Tester}) -> + Tester ! {ready, self()}, + ok + end}, + #{desc => "await continue (close)", + cmd => fun(#{tester := Tester} = _State) -> + receive + {'DOWN', _, process, Tester, Reason} -> + ee("Unexpected DOWN regarding tester ~p: " + "~n ~p", [Reason]), + {error, {unexpected_exit, tester}}; + {continue, Tester} -> + ok + end + end}, + %% #{desc => "enable debug", + %% cmd => fun(#{csock := Sock}) -> + %% socket:setopt(Sock, otp, debug, true) + %% end}, + #{desc => "close (the connection) socket", + cmd => fun(#{csock := Sock}) -> + socket:close(Sock) + end}, + + #{desc => "await terminate", + cmd => fun(#{tester := Tester} = State) -> + receive + {'DOWN', _, process, Tester, Reason} -> + ee("Unexpected DOWN regarding tester ~p: " + "~n ~p", [Reason]), + {error, {unexpected_exit, tester}}; + {terminate, Tester} -> + {ok, maps:remove(tester, State)} + end + end}, + #{desc => "socket cleanup", + cmd => fun(#{lsock := Sock} = State) -> + ok = socket:close(Sock), + State1 = maps:remove(csock, State), + State2 = maps:remove(lsock, State1), + State3 = maps:remove(lport, State2), + {ok, State3} + end}, + + %% *** We are done *** + #{desc => "finish", + cmd => fun(_) -> + {ok, normal} + end} + ], + + %% The point of this is to perform the recv for which we are testing the reponse + HandlerSeq = + [ + %% *** Wait for start order part *** + #{desc => "await start (from tester)", + cmd => fun(State) -> + receive + {start, Tester} when is_pid(Tester) -> + {ok, State#{tester => Tester}} + end + end}, + #{desc => "monitor server", + cmd => fun(#{tester := Tester} = _State) -> + _MRef = erlang:monitor(process, Tester), + ok + end}, + #{desc => "announce ready (init)", + cmd => fun(#{tester := Tester}) -> + Tester ! {ready, self()}, + ok + end}, + + %% The actual test + #{desc => "await connection socket", + cmd => fun(#{tester := Tester} = State) -> + receive + {'DOWN', _, process, Tester, Reason} -> + ee("Unexpected DOWN regarding tester ~p: " + "~n ~p", [Reason]), + {error, {unexpected_exit, tester}}; + {connection, Sock} -> + {ok, State#{sock => Sock}} + end + end}, + #{desc => "announce ready (connection)", + cmd => fun(#{tester := Tester}) -> + Tester ! {ready, self()}, + ok + end}, + %% #{desc => "enable debug", + %% cmd => fun(#{sock := Sock}) -> + %% socket:setopt(Sock, otp, debug, true) + %% end}, + %% #{desc => "monitored-by", + %% cmd => fun(_) -> + %% {_, Mons} = process_info(self(), monitored_by), + %% ei("Monitored By: ~p", [Mons]), + %% ok + %% end}, + #{desc => "attempt recv", + cmd => fun(#{sock := Sock, recv := Recv} = State) -> + case Recv(Sock) of + {ok, _Data} -> + ee("Unexpected data received"), + {error, unexpected_data}; + {error, closed} -> + State1 = maps:remove(sock, State), + {ok, State1}; + {error, Reason} = ERROR -> + ee("Unexpected read faulure: " + "~n ~p", [Reason]), + ERROR + end + end}, + %% #{desc => "monitored-by", + %% cmd => fun(_) -> + %% {_, Mons} = process_info(self(), monitored_by), + %% ei("Monitored By: ~p", [Mons]), + %% ok + %% end}, + #{desc => "announce ready (close)", + cmd => fun(#{tester := Tester}) -> + Tester ! {ready, self()}, + ok + end}, + #{desc => "sleep some", + cmd => fun(_) -> + ?SLEEP(1000), + ok + end}, + %% #{desc => "monitored-by", + %% cmd => fun(_) -> + %% {_, Mons} = process_info(self(), monitored_by), + %% ei("Monitored By: ~p", [Mons]), + %% ok + %% end}, + #{desc => "await terminate", + cmd => fun(#{tester := Tester} = _State) -> + receive + {'DOWN', _, process, Tester, Reason} -> + ee("Unexpected DOWN regarding tester ~p: " + "~n ~p", [Reason]), + {error, {unexpected_exit, tester}}; + {terminate, Tester} -> + ok + end + end}, + + + %% *** We are done *** + #{desc => "finish", + cmd => fun(_) -> + {ok, normal} + end} + ], + + %% The point of this is basically just to create the connection. + ClientSeq = + [ + %% *** Wait for start order part *** + #{desc => "await start (from tester)", + cmd => fun(State) -> + receive + {start, Tester} when is_pid(Tester) -> + {ok, State#{tester => Tester}} + end + end}, + + %% Init + #{desc => "which local address", + cmd => fun(#{domain := Domain} = State) -> + LAddr = which_local_addr(Domain), + LSA = #{family => Domain, addr => LAddr}, + {ok, State#{lsa => LSA}} + end}, + #{desc => "create socket", + cmd => fun(#{domain := Domain, + type := Type, + protocol := Proto} = State) -> + case socket:open(Domain, Type, Proto) of + {ok, Sock} -> + {ok, State#{sock => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "bind socket to local address", + cmd => fun(#{sock := Sock, lsa := LSA} = _State) -> + case socket:bind(Sock, LSA) of + {ok, _} -> + ok; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready", + cmd => fun(#{tester := Tester} = _State) -> + Tester ! {ready, self()}, + ok + end}, + + %% The actual test + #{desc => "await continue", + cmd => fun(#{tester := Tester} = State) -> + receive + {'DOWN', _, process, Tester, Reason} -> + ee("Unexpected DOWN regarding tester ~p: " + "~n ~p", [Tester, Reason]), + {error, {unexpected_exit, tester, Reason}}; + {continue, Tester, Port} -> + {ok, State#{lport => Port}} + end + end}, + #{desc => "connect to server", + cmd => fun(#{sock := Sock, lsa := LSA, lport := LPort}) -> + socket:connect(Sock, LSA#{port => LPort}) + end}, + #{desc => "announce ready (connection)", + cmd => fun(#{tester := Tester} = _State) -> + Tester ! {ready, self()}, + ok + end}, + + %% Cleaning up + #{desc => "await terminate (from tester)", + cmd => fun(#{tester := Tester} = State) -> + receive + {'DOWN', _, process, Tester, Reason} -> + ee("Unexpected DOWN regarding tester ~p: " + "~n ~p", [Tester, Reason]), + {error, {unexpected_exit, tester}}; + {terminate, Tester} -> + {ok, maps:remove(tester, State)} + end + end}, + #{desc => "close socket", + cmd => fun(#{sock := Sock} = State) -> + sock_close(Sock), + {ok, maps:remove(sock, State)} + end}, + + %% *** We are done *** + #{desc => "finish", + cmd => fun(_) -> + {ok, normal} + end} + ], + + TesterSeq = + [ + %% *** Init part *** + #{desc => "monitor acceptor", + cmd => fun(#{acceptor := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + #{desc => "monitor handler", + cmd => fun(#{handler := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + #{desc => "monitor client", + cmd => fun(#{client := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + + %% Start the acceptor + #{desc => "order acceptor start", + cmd => fun(#{acceptor := Pid} = _State) -> + Pid ! {start, self()}, + ok + end}, + #{desc => "await acceptor ready (init)", + cmd => fun(#{acceptor := Pid} = State) -> + receive + {'DOWN', _, process, Pid, Reason} -> + ee("Unexpected DOWN regarding acceptor ~p: " + "~n ~p", [Pid, Reason]), + {error, {unexpected_exit, acceptor}}; + {ready, Pid, Port} -> + {ok, State#{lport => Port}} + end + end}, + + %% Start the handler + #{desc => "order handler start", + cmd => fun(#{handler := Pid} = _State) -> + Pid ! {start, self()}, + ok + end}, + #{desc => "await handler ready (init)", + cmd => fun(#{handler := Pid} = _State) -> + receive + {'DOWN', _, process, Pid, Reason} -> + ee("Unexpected DOWN regarding handler ~p: " + "~n ~p", [Pid, Reason]), + {error, {unexpected_exit, acceptor}}; + {ready, Pid} -> + ok + end + end}, + + %% Start the client + #{desc => "order client start", + cmd => fun(#{client := Pid} = _State) -> + Pid ! {start, self()}, + ok + end}, + #{desc => "await client ready (init)", + cmd => fun(#{client := Pid} = _State) -> + receive + {'DOWN', _, process, Pid, Reason} -> + ee("Unexpected DOWN regarding cient ~p: " + "~n ~p", [Pid, Reason]), + {error, {unexpected_exit, acceptor}}; + {ready, Pid} -> + ok + end + end}, + + %% The actual test + #{desc => "order acceptor to continue", + cmd => fun(#{acceptor := Pid, handler := Handler} = _State) -> + Pid ! {continue, self(), Handler}, + ok + end}, + #{desc => "order client to continue", + cmd => fun(#{client := Pid, lport := Port} = _State) -> + Pid ! {continue, self(), Port}, + ok + end}, + #{desc => "await acceptor ready (connection)", + cmd => fun(#{acceptor := Pid} = _State) -> + receive + {'DOWN', _, process, Pid, Reason} -> + ee("Unexpected DOWN regarding acceptor ~p: " + "~n ~p", [Pid, Reason]), + {error, {unexpected_exit, acceptor}}; + {ready, Pid} -> + ok + end + end}, + #{desc => "await client ready (connection)", + cmd => fun(#{client := Pid} = _State) -> + receive + {'DOWN', _, process, Pid, Reason} -> + ee("Unexpected DOWN regarding client ~p: " + "~n ~p", [Pid, Reason]), + {error, {unexpected_exit, acceptor}}; + {ready, Pid} -> + ok + end + end}, + #{desc => "await handler ready (connection)", + cmd => fun(#{handler := Pid} = _State) -> + receive + {'DOWN', _, process, Pid, Reason} -> + ee("Unexpected DOWN regarding handler ~p: " + "~n ~p", [Reason]), + {error, {unexpected_exit, acceptor}}; + {ready, Pid} -> + ok + end + end}, + #{desc => "sleep some", + cmd => fun(_State) -> + ?SLEEP(1000), + ok + end}, + #{desc => "order acceptor to continue (close)", + cmd => fun(#{acceptor := Pid} = _State) -> + Pid ! {continue, self()}, + ok + end}, + #{desc => "await handler ready (close)", + cmd => fun(#{handler := Pid} = _State) -> + receive + {'DOWN', _, process, Pid, Reason} -> + ee("Unexpected DOWN regarding handler ~p: " + "~n ~p", [Pid, Reason]), + {error, {unexpected_exit, acceptor}}; + {ready, Pid} -> + ok + end + end}, + + %% Terminations + #{desc => "order handler to terminate", + cmd => fun(#{handler := Pid} = _State) -> + Pid ! {terminate, self()}, + ok + end}, + #{desc => "await handler termination", + cmd => fun(#{handler := Pid} = State) -> + receive + {'DOWN', _, process, Pid, _} -> + {ok, maps:remove(handler, State)} + end + end}, + #{desc => "order client to terminate", + cmd => fun(#{client := Pid} = _State) -> + Pid ! {terminate, self()}, + ok + end}, + #{desc => "await client termination", + cmd => fun(#{client := Pid} = State) -> + receive + {'DOWN', _, process, Pid, _} -> + {ok, maps:remove(client, State)} + end + end}, + #{desc => "order acceptor to terminate", + cmd => fun(#{acceptor := Pid} = _State) -> + Pid ! {terminate, self()}, + ok + end}, + #{desc => "await acceptor termination", + cmd => fun(#{acceptor := Pid} = State) -> + receive + {'DOWN', _, process, Pid, _} -> + {ok, maps:remove(acceptor, State)} + end + end}, + + + %% *** We are done *** + #{desc => "finish", + cmd => fun(_) -> + {ok, normal} + end} + ], + + p("start acceptor evaluator"), + AccInitState = InitState, + Acceptor = evaluator_start("acceptor", AcceptorSeq, AccInitState), + + p("start handler evaluator"), + HandlerInitState = #{recv => maps:get(recv, InitState)}, + Handler = evaluator_start("handler", HandlerSeq, HandlerInitState), + + p("start client evaluator"), + ClientInitState = InitState, + Client = evaluator_start("client", ClientSeq, ClientInitState), + + p("start tester evaluator"), + TesterInitState = #{acceptor => Acceptor, + handler => Handler, + client => Client}, + Tester = evaluator_start("tester", TesterSeq, TesterInitState), + + p("await evaluator"), + ok = await_evaluator_finish([Acceptor, Handler, Client, Tester]). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test what happens when a socket is +%% remotely closed while the process is calling the recv function. +%% Socket is IPv4. + +sc_rc_recv_response_tcp4(suite) -> + []; +sc_rc_recv_response_tcp4(doc) -> + []; +sc_rc_recv_response_tcp4(_Config) when is_list(_Config) -> + tc_try(sc_rc_recv_response_tcp4, + fun() -> + not_yet_implemented(), + Recv = fun(Sock) -> socket:recv(Sock) end, + InitState = #{domain => inet, + type => stream, + protocol => tcp, + recv => Recv}, + ok = sc_rc_receive_response_tcp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test what happens when a socket is +%% remotely closed while the process is calling the recv function. +%% Socket is IPv6. + +sc_rc_recv_response_tcp6(suite) -> + []; +sc_rc_recv_response_tcp6(doc) -> + []; +sc_rc_recv_response_tcp6(_Config) when is_list(_Config) -> + tc_try(sc_rc_recv_response_tcp6, + fun() -> + not_yet_implemented(), + Recv = fun(Sock) -> socket:recv(Sock) end, + InitState = #{domain => inet6, + type => stream, + protocol => tcp, + recv => Recv}, + ok = sc_rc_receive_response_tcp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +sc_rc_receive_response_tcp(_InitState) -> + ok. + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test what happens when a socket is +%% locally closed while the process is calling the recvmsg function. +%% Socket is IPv4. + +sc_lc_recvmsg_response_tcp4(suite) -> + []; +sc_lc_recvmsg_response_tcp4(doc) -> + []; +sc_lc_recvmsg_response_tcp4(_Config) when is_list(_Config) -> + tc_try(sc_lc_recvmsg_response_tcp4, + fun() -> + Recv = fun(Sock) -> socket:recvmsg(Sock) end, + InitState = #{domain => inet, + type => stream, + protocol => tcp, + recv => Recv}, + ok = sc_lc_receive_response_tcp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test what happens when a socket is +%% locally closed while the process is calling the recvmsg function. +%% Socket is IPv6. + +sc_lc_recvmsg_response_tcp6(suite) -> + []; +sc_lc_recvmsg_response_tcp6(doc) -> + []; +sc_lc_recvmsg_response_tcp6(_Config) when is_list(_Config) -> + tc_try(sc_recvmsg_response_tcp6, + fun() -> + not_yet_implemented(), + Recv = fun(Sock) -> socket:recvmsg(Sock) end, + InitState = #{domain => inet6, + type => stream, + protocol => tcp, + recv => Recv}, + ok = sc_lc_receive_response_tcp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test what happens when a socket is +%% remotely closed while the process is calling the recvmsg function. +%% Socket is IPv4. + +sc_rc_recvmsg_response_tcp4(suite) -> + []; +sc_rc_recvmsg_response_tcp4(doc) -> + []; +sc_rc_recvmsg_response_tcp4(_Config) when is_list(_Config) -> + tc_try(sc_rc_recvmsg_response_tcp4, + fun() -> + not_yet_implemented(), + Recv = fun(Sock) -> socket:recvmsg(Sock) end, + InitState = #{domain => inet, + type => stream, + protocol => tcp, + recv => Recv}, + ok = sc_rc_receive_response_tcp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test what happens when a socket is +%% remotely closed while the process is calling the recvmsg function. +%% Socket is IPv6. + +sc_rc_recvmsg_response_tcp6(suite) -> + []; +sc_rc_recvmsg_response_tcp6(doc) -> + []; +sc_rc_recvmsg_response_tcp6(_Config) when is_list(_Config) -> + tc_try(sc_rc_recvmsg_response_tcp6, + fun() -> + not_yet_implemented(), + Recv = fun(Sock) -> socket:recvmsg(Sock) end, + InitState = #{domain => inet6, + type => stream, + protocol => tcp, + recv => Recv}, + ok = sc_rc_receive_response_tcp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test what happens when a socket is +%% locally closed while the process is calling the accept function. +%% We test what happens with a non-controlling_process also, since we +%% git the setup anyway. +%% Socket is IPv4. + +sc_lc_acceptor_response_tcp4(suite) -> + []; +sc_lc_acceptor_response_tcp4(doc) -> + []; +sc_lc_acceptor_response_tcp4(_Config) when is_list(_Config) -> + tc_try(sc_lc_acceptor_response_tcp4, + fun() -> + not_yet_implemented(), + InitState = #{domain => inet, + type => stream, + protocol => tcp}, + ok = sc_lc_acceptor_response_tcp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test what happens when a socket is +%% locally closed while the process is calling the accept function. +%% We test what happens with a non-controlling_process also, since we +%% git the setup anyway. +%% Socket is IPv6. + +sc_lc_acceptor_response_tcp6(suite) -> + []; +sc_lc_acceptor_response_tcp6(doc) -> + []; +sc_lc_acceptor_response_tcp6(_Config) when is_list(_Config) -> + tc_try(sc_lc_acceptor_response_tcp6, + fun() -> + not_yet_implemented(), + InitState = #{domain => inet, + type => stream, + protocol => tcp}, + ok = sc_lc_acceptor_response_tcp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +sc_lc_acceptor_response_tcp(_InitState) -> + ok. + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% This gets the local address (not 127.0...) +%% We should really implement this using the (new) net module, +%% but until that gets the necessary functionality... +which_local_addr(Domain) -> + case inet:getifaddrs() of + {ok, IFL} -> + which_addr(Domain, IFL); + {error, Reason} -> + ?FAIL({inet, getifaddrs, Reason}) + end. + +which_addr(_Domain, []) -> + ?FAIL(no_address); +which_addr(Domain, [{Name, IFO}|_IFL]) when (Name =/= "lo") -> + which_addr2(Domain, IFO); +which_addr(Domain, [_|IFL]) -> + which_addr(Domain, IFL). + +which_addr2(_Domain, []) -> + ?FAIL(no_address); +which_addr2(inet = _Domain, [{addr, Addr}|_IFO]) when (size(Addr) =:= 4) -> + Addr; +which_addr2(inet6 = _Domain, [{addr, Addr}|_IFO]) when (size(Addr) =:= 8) -> + Addr; +which_addr2(Domain, [_|IFO]) -> + which_addr2(Domain, IFO). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% An evaluator is a process that executes a command sequence. +%% A test case will consist of atleast one evaluator (one for +%% each actor). +%% The evaluator process *always* run locally. Which means that +%% it will act as a "proxy" for remote nodes in necessary. +%% When the command sequence has been processed, the final state +%% will be used as exit reason. +%% A successful command shall evaluate to ok | {ok, NewState} + +-spec evaluator_start(Name, Seq, Init) -> {Pid, MRef} when + Name :: string(), + Seq :: [command()], + Init :: initial_evaluator_state(), + Pid :: pid(), + MRef :: reference(). + +evaluator_start(Name, Seq, Init) + when is_list(Name) andalso is_list(Seq) andalso (Seq =/= []) -> + Init2 = Init#{parent => self()}, + {Pid, _} = erlang:spawn_monitor(fun() -> evaluator_init(Name, Seq, Init2) end), + Pid. + +evaluator_init(Name, Seq, Init) -> + put(sname, Name), + evaluator_loop(1, Seq, Init). + +evaluator_loop(_ID, [], FinalState) -> + exit(FinalState); +evaluator_loop(ID, [#{desc := Desc, + cmd := Cmd}|Cmds], State) when is_function(Cmd, 1) -> + ei("evaluate command ~2w: ~s", [ID, Desc]), + try Cmd(State) of + ok -> + evaluator_loop(ID + 1, Cmds, State); + {ok, NewState} -> + evaluator_loop(ID + 1, Cmds, NewState); + {error, Reason} -> + ee("command ~w failed: " + "~n Reason: ~p", [ID, Reason]), + exit({command_failed, ID, Reason, State}) + catch + C:E:S -> + ee("command ~w crashed: " + "~n Class: ~p" + "~n Error: ~p" + "~n Call Stack: ~p", [ID, C, E, S]), + exit({command_crashed, ID, {C,E,S}, State}) + end. + +await_evaluator_finish(Evs) -> + await_evaluator_finish(Evs, []). + +await_evaluator_finish([], []) -> + ok; +await_evaluator_finish([], Fails) -> + Fails; +await_evaluator_finish(Evs, Fails) -> + receive + {'DOWN', _MRef, process, Pid, normal} -> + case lists:delete(Pid, Evs) of + Evs -> + p("unknown process ~p died (normal)", [Pid]), + await_evaluator_finish(Evs, Fails); + NewEvs -> + p("evaluator ~p success", [Pid]), + await_evaluator_finish(NewEvs, Fails) + end; + {'DOWN', _MRef, process, Pid, Reason} -> + case lists:delete(Pid, Evs) of + Evs -> + p("unknown process ~p died: " + "~n ~p", [Pid, Reason]), + await_evaluator_finish(Evs, Fails); + NewEvs -> + p("Evaluator ~p failed", [Pid]), + await_evaluator_finish(NewEvs, [{Pid, Reason}|Fails]) + end + end. + + +ei(F) -> + ei(F, []). +ei(F, A) -> + eprint("", F, A). + +ee(F) -> + ee(F, []). +ee(F, A) -> + eprint(" ", F, A). + +eprint(Prefix, F, A) -> + io:format(user, "[~s][~s][~p] ~s" ++ F ++ "~n", + [formated_timestamp(), get(sname), self(), Prefix | A]). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +sock_open(Domain, Type, Proto) -> + try socket:open(Domain, Type, Proto) of + {ok, Socket} -> + Socket; + {error, Reason} -> + ?FAIL({open, Reason}) + catch + C:E:S -> + ?FAIL({open, C, E, S}) + end. + + +sock_bind(Sock, SockAddr) -> + try socket:bind(Sock, SockAddr) of + {ok, Port} -> + Port; + {error, Reason} -> + p("sock_bind -> error: ~p", [Reason]), + ?FAIL({bind, Reason}) + catch + C:E:S -> + p("sock_bind -> failed: ~p, ~p, ~p", [C, E, S]), + ?FAIL({bind, C, E, S}) + end. + +sock_connect(Sock, SockAddr) -> + try socket:connect(Sock, SockAddr) of + ok -> + ok; + {error, Reason} -> + ?FAIL({connect, Reason}) + catch + C:E:S -> + ?FAIL({connect, C, E, S}) + end. + +sock_sockname(Sock) -> + try socket:sockname(Sock) of + {ok, SockAddr} -> + SockAddr; + {error, Reason} -> + ?FAIL({sockname, Reason}) + catch + C:E:S -> + ?FAIL({sockname, C, E, S}) + end. + + +%% sock_listen(Sock) -> +%% sock_listen2(fun() -> socket:listen(Sock) end). + +%% sock_listen(Sock, BackLog) -> +%% sock_listen2(fun() -> socket:listen(Sock, BackLog) end). + +%% sock_listen2(Listen) -> +%% try Listen() of +%% ok -> +%% ok; +%% {error, Reason} -> +%% ?FAIL({listen, Reason}) +%% catch +%% C:E:S -> +%% ?FAIL({listen, C, E, S}) +%% end. + + +%% sock_accept(LSock) -> +%% try socket:accept(LSock) of +%% {ok, Sock} -> +%% Sock; +%% {error, Reason} -> +%% p("sock_accept -> error: ~p", [Reason]), +%% ?FAIL({accept, Reason}) +%% catch +%% C:E:S -> +%% p("sock_accept -> failed: ~p, ~p, ~p", [C, E, S]), +%% ?FAIL({accept, C, E, S}) +%% end. + + +sock_close(Sock) -> + try socket:close(Sock) of + ok -> + ok; + {error, Reason} -> + p("sock_close -> error: ~p", [Reason]), + ?FAIL({close, Reason}) + catch + C:E:S -> + p("sock_close -> failed: ~p, ~p, ~p", [C, E, S]), + ?FAIL({close, C, E, S}) + end. + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +not_yet_implemented() -> + skip("not yet implemented"). + +skip(Reason) -> + throw({skip, Reason}). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +t() -> + os:timestamp(). + + +tdiff({A1, B1, C1} = _T1x, {A2, B2, C2} = _T2x) -> + T1 = A1*1000000000+B1*1000+(C1 div 1000), + T2 = A2*1000000000+B2*1000+(C2 div 1000), + T2 - T1. + + +formated_timestamp() -> + format_timestamp(os:timestamp()). + +format_timestamp({_N1, _N2, _N3} = TS) -> + {_Date, Time} = calendar:now_to_local_time(TS), + %% {YYYY,MM,DD} = Date, + {Hour,Min,Sec} = Time, + %% FormatTS = + %% io_lib:format("~.4w-~.2.0w-~.2.0w ~.2.0w:~.2.0w:~.2.0w.~w", + %% [YYYY, MM, DD, Hour, Min, Sec, N3]), + FormatTS = io_lib:format("~.2.0w:~.2.0w:~.2.0w", [Hour, Min, Sec]), + lists:flatten(FormatTS). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +set_tc_name(N) when is_atom(N) -> + set_tc_name(atom_to_list(N)); +set_tc_name(N) when is_list(N) -> + put(tc_name, N). + +%% get_tc_name() -> +%% get(tc_name). + +tc_begin(TC) -> + set_tc_name(TC), + p("begin ***"). + +tc_end(Result) when is_list(Result) -> + p("done: ~s", [Result]), + ok. + + +tc_try(Case, Fun) when is_atom(Case) andalso is_function(Fun, 0) -> + tc_begin(Case), + try + begin + Fun(), + tc_end("ok") + end + catch + throw:{skip, _} = SKIP -> + tc_end("skipping"), + SKIP; + Class:Error:Stack -> + tc_end("failed"), + erlang:raise(Class, Error, Stack) + end. + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% f(F, A) -> +%% lists:flatten(io_lib:format(F, A)). + +p(F) -> + p(F, []). + +p(F, A) -> + TcName = + case get(tc_name) of + undefined -> + case get(sname) of + undefined -> + ""; + SName when is_list(SName) -> + SName + end; + Name when is_list(Name) -> + Name + end, + i("*** [~s][~s][~p] " ++ F, [formated_timestamp(),TcName,self()|A]). + + +%% i(F) -> +%% i(F, []). + +i(F, A) -> + io:format(user, F ++ "~n", A). diff --git a/erts/emulator/test/socket_client.erl b/erts/emulator/test/socket_client.erl new file mode 100644 index 0000000000..1c07e799b8 --- /dev/null +++ b/erts/emulator/test/socket_client.erl @@ -0,0 +1,538 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2018-2018. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% + +-module(socket_client). + +-export([ + start/1, start/2, start/5, start/6, + start_tcp/1, start_tcp/2, start_tcp/3, + start_tcp4/1, start_tcp4/2, start_tcp6/1, start_tcp6/2, + start_udp/1, start_udp/2, start_udp/3, + start_udp4/1, start_udp4/2, start_udp6/1, start_udp6/2 + ]). + +-define(LIB, socket_lib). + +-record(client, {socket, verbose = true, msg = true, type, dest, msg_id = 1}). + +start(Port) -> + start(Port, 1). + +start(Port, Num) -> + start_tcp(Port, Num). + +start_tcp(Port) -> + start_tcp(Port, 1). + +start_tcp(Port, Num) -> + start_tcp4(Port, Num). + +start_tcp4(Port) -> + start_tcp4(Port, 1). + +start_tcp4(Port, Num) -> + start(inet, stream, tcp, Port, Num). + +start_tcp6(Port) -> + start_tcp6(Port, 1). + +start_tcp6(Port, Num) -> + start(inet6, stream, tcp, Port, Num). + +start_tcp(Addr, Port, Num) when (size(Addr) =:= 4) andalso + is_integer(Num) andalso + (Num > 0) -> + start(inet, stream, tcp, Addr, Port, Num); +start_tcp(Addr, Port, Num) when (size(Addr) =:= 8) andalso + is_integer(Num) andalso + (Num > 0) -> + start(inet6, stream, tcp, Addr, Port, Num). + + +start_udp(Port) -> + start_udp(Port, 1). + +start_udp(Port, Num) -> + start_udp4(Port, Num). + +start_udp4(Port) -> + start_udp4(Port, 1). + +start_udp4(Port, Num) -> + start(inet, dgram, udp, Port, Num). + +start_udp6(Port) -> + start_udp6(Port, 1). + +start_udp6(Port, Num) -> + start(inet6, dgram, udp, Port, Num). + +start_udp(Addr, Port, Num) when (size(Addr) =:= 4) -> + start(inet, dgram, udp, Addr, Port, Num); +start_udp(Addr, Port, Num) when (size(Addr) =:= 8) -> + start(inet6, dgram, udp, Addr, Port, Num). + + +start(Domain, Type, Proto, Port, Num) + when is_integer(Port) andalso is_integer(Num) -> + start(Domain, Type, Proto, which_addr(Domain), Port, Num); + +start(Domain, Type, Proto, Addr, Port) -> + start(Domain, Type, Proto, Addr, Port, 1). + +start(Domain, Type, Proto, Addr, Port, 1 = Num) -> + start(Domain, Type, Proto, Addr, Port, Num, true); +start(Domain, Type, Proto, Addr, Port, Num) + when is_integer(Num) andalso (Num > 1) -> + start(Domain, Type, Proto, Addr, Port, Num, false). + +start(Domain, Type, Proto, Addr, Port, Num, Verbose) -> + put(sname, "starter"), + Clients = start_clients(Num, Domain, Type, Proto, Addr, Port, Verbose), + await_clients(Clients). + +start_clients(Num, Domain, Type, Proto, Addr, Port, Verbose) -> + start_clients(Num, 1, Domain, Type, Proto, Addr, Port, Verbose, []). + +start_clients(Num, ID, Domain, Type, Proto, Addr, Port, Verbose, Acc) + when (Num > 0) -> + StartClient = fun() -> + start_client(ID, Domain, Type, Proto, Addr, Port, Verbose) + end, + {Pid, _} = spawn_monitor(StartClient), + ?LIB:sleep(500), + i("start client ~w", [ID]), + start_clients(Num-1, ID+1, Domain, Type, Proto, Addr, Port, Verbose, [Pid|Acc]); +start_clients(_, _, _, _, _, _, _, _, Acc) -> + i("all client(s) started"), + lists:reverse(Acc). + +await_clients([]) -> + i("all clients done"); +await_clients(Clients) -> + receive + {'DOWN', _MRef, process, Pid, _Reason} -> + case lists:delete(Pid, Clients) of + Clients2 when (Clients2 =/= Clients) -> + i("client ~p done", [Pid]), + await_clients(Clients2); + _ -> + await_clients(Clients) + end + end. + + +start_client(ID, Domain, Type, Proto, Addr, Port, Verbose) -> + put(sname, ?LIB:f("client[~w]", [ID])), + SA = #{family => Domain, + addr => Addr, + port => Port}, + %% The way we use tos only works because we + %% send so few messages (a new value for every + %% message). + tos_init(), + do_start(Domain, Type, Proto, SA, Verbose). + +do_start(Domain, stream = Type, Proto, SA, Verbose) -> + try do_init(Domain, Type, Proto) of + Sock -> + connect(Sock, SA), + maybe_print_start_info(Verbose, Sock, Type), + %% Give the server some time... + ?LIB:sleep(5000), + %% ok = socket:close(Sock), + send_loop(#client{socket = Sock, + type = Type, + verbose = Verbose}) + catch + throw:E -> + e("Failed initiate: " + "~n Error: ~p", [E]) + end; +do_start(Domain, dgram = Type, Proto, SA, Verbose) -> + try do_init(Domain, Type, Proto) of + Sock -> + maybe_print_start_info(Verbose, Sock, Type), + %% Give the server some time... + ?LIB:sleep(5000), + %% ok = socket:close(Sock), + send_loop(#client{socket = Sock, + type = Type, + dest = SA, + verbose = Verbose}) + catch + throw:E -> + e("Failed initiate: " + "~n Error: ~p", [E]) + end. + +maybe_print_start_info(true = _Verbose, Sock, stream = _Type) -> + {ok, Name} = socket:sockname(Sock), + {ok, Peer} = socket:peername(Sock), + {ok, Domain} = socket:getopt(Sock, socket, domain), + {ok, Type} = socket:getopt(Sock, socket, type), + {ok, Proto} = socket:getopt(Sock, socket, protocol), + {ok, OOBI} = socket:getopt(Sock, socket, oobinline), + {ok, SndBuf} = socket:getopt(Sock, socket, sndbuf), + {ok, RcvBuf} = socket:getopt(Sock, socket, rcvbuf), + {ok, Linger} = socket:getopt(Sock, socket, linger), + {ok, MTU} = socket:getopt(Sock, ip, mtu), + {ok, MTUDisc} = socket:getopt(Sock, ip, mtu_discover), + {ok, MALL} = socket:getopt(Sock, ip, multicast_all), + {ok, MIF} = socket:getopt(Sock, ip, multicast_if), + {ok, MLoop} = socket:getopt(Sock, ip, multicast_loop), + {ok, MTTL} = socket:getopt(Sock, ip, multicast_ttl), + {ok, RecvTOS} = socket:getopt(Sock, ip, recvtos), + i("connected: " + "~n From: ~p" + "~n To: ~p" + "~nwhen" + "~n (socket) Domain: ~p" + "~n (socket) Type: ~p" + "~n (socket) Protocol: ~p" + "~n (socket) OOBInline: ~p" + "~n (socket) SndBuf: ~p" + "~n (socket) RcvBuf: ~p" + "~n (socket) Linger: ~p" + "~n (ip) MTU: ~p" + "~n (ip) MTU Discovery: ~p" + "~n (ip) Multicast ALL: ~p" + "~n (ip) Multicast IF: ~p" + "~n (ip) Multicast Loop: ~p" + "~n (ip) Multicast TTL: ~p" + "~n (ip) RecvTOS: ~p" + "~n => wait some", + [Name, Peer, + Domain, Type, Proto, + OOBI, SndBuf, RcvBuf, Linger, + MTU, MTUDisc, MALL, MIF, MLoop, MTTL, + RecvTOS]); +maybe_print_start_info(true = _Verbose, Sock, dgram = _Type) -> + {ok, Domain} = socket:getopt(Sock, socket, domain), + {ok, Type} = socket:getopt(Sock, socket, type), + {ok, Proto} = socket:getopt(Sock, socket, protocol), + {ok, OOBI} = socket:getopt(Sock, socket, oobinline), + {ok, SndBuf} = socket:getopt(Sock, socket, sndbuf), + {ok, RcvBuf} = socket:getopt(Sock, socket, rcvbuf), + {ok, Linger} = socket:getopt(Sock, socket, linger), + {ok, MALL} = socket:getopt(Sock, ip, multicast_all), + {ok, MIF} = socket:getopt(Sock, ip, multicast_if), + {ok, MLoop} = socket:getopt(Sock, ip, multicast_loop), + {ok, MTTL} = socket:getopt(Sock, ip, multicast_ttl), + {ok, RecvTOS} = socket:getopt(Sock, ip, recvtos), + {ok, RecvTTL} = socket:getopt(Sock, ip, recvttl), + i("initiated when: " + "~n (socket) Domain: ~p" + "~n (socket) Type: ~p" + "~n (socket) Protocol: ~p" + "~n (socket) OOBInline: ~p" + "~n (socket) SndBuf: ~p" + "~n (socket) RcvBuf: ~p" + "~n (socket) Linger: ~p" + "~n (ip) Multicast ALL: ~p" + "~n (ip) Multicast IF: ~p" + "~n (ip) Multicast Loop: ~p" + "~n (ip) Multicast TTL: ~p" + "~n (ip) RecvTOS: ~p" + "~n (ip) RecvTTL: ~p" + "~n => wait some", + [Domain, Type, Proto, + OOBI, SndBuf, RcvBuf, Linger, + MALL, MIF, MLoop, MTTL, + RecvTOS, RecvTTL]); +maybe_print_start_info(_Verbose, _Sock, _Type) -> + ok. + + +do_init(Domain, stream = Type, Proto) -> + i("try (socket) open"), + Sock = case socket:open(Domain, Type, Proto) of + {ok, S} -> + S; + {error, OReason} -> + throw({open, OReason}) + end, + i("try (socket) bind"), + case socket:bind(Sock, any) of + {ok, _P} -> + ok = socket:setopt(Sock, socket, timestamp, true), + ok = socket:setopt(Sock, ip, tos, mincost), + ok = socket:setopt(Sock, ip, recvtos, true), + Sock; + {error, BReason} -> + throw({bind, BReason}) + end; +do_init(Domain, dgram = Type, Proto) -> + i("try (socket) open"), + Sock = case socket:open(Domain, Type, Proto) of + {ok, S} -> + S; + {error, OReason} -> + throw({open, OReason}) + end, + case socket:bind(Sock, any) of + {ok, _} -> + ok = socket:setopt(Sock, socket, timestamp, true), + ok = socket:setopt(Sock, ip, tos, mincost), + ok = socket:setopt(Sock, ip, recvtos, true), + ok = socket:setopt(Sock, ip, recvttl, true), + Sock; + {error, BReason} -> + throw({bind, BReason}) + end. + + +which_addr(Domain) -> + Iflist = case inet:getifaddrs() of + {ok, IFL} -> + IFL; + {error, Reason} -> + throw({inet,getifaddrs,Reason}) + end, + which_addr(Domain, Iflist). + + +connect(Sock, SA) -> + i("try (socket) connect to:" + "~n ~p", [SA]), + case socket:connect(Sock, SA) of + ok -> + ok; + {error, Reason} -> + e("connect failure: " + "~n ~p", [Reason]), + exit({connect, Reason}) + end. + + +send_loop(#client{msg_id = N} = C) when (N =< 10) -> + i("try send request ~w", [N]), + Req = ?LIB:enc_req_msg(N, "hejsan"), + case send(C, Req) of + ok -> + i("request ~w sent - now try read answer", [N]), + case recv(C) of + {ok, {Source, Msg}} -> + if + (C#client.verbose =:= true) -> + i("received ~w bytes of data~s", + [size(Msg), case Source of + undefined -> ""; + _ -> ?LIB:f(" from:~n ~p", [Source]) + end]); + true -> + i("received ~w bytes", [size(Msg)]) + end, + case ?LIB:dec_msg(Msg) of + {reply, N, Reply} -> + if + (C#client.verbose =:= true) -> + i("received reply ~w: ~p", [N, Reply]); + true -> + i("received reply ~w", [N]) + end, + ?LIB:sleep(500), % Just to spread it out a bit + send_loop(C#client{msg_id = N+1}) + end; + {error, RReason} -> + e("Failed recv response for request ~w: " + "~n ~p", [N, RReason]), + exit({failed_recv, RReason}) + end; + {error, SReason} -> + e("Failed send request ~w: " + "~n ~p", [N, SReason]), + exit({failed_send, SReason}) + end; +send_loop(Client) -> + sock_close(Client). + +sock_close(#client{socket = Sock, verbose = true}) -> + i("we are done - close the socket when: " + "~n ~p", [socket:info()]), + ok = socket:close(Sock), + i("we are done - socket closed when: " + "~n ~p", [socket:info()]); +sock_close(#client{socket = Sock}) -> + i("we are done"), + ok = socket:close(Sock). + + + +send(#client{socket = Sock, type = stream}, Msg) -> + socket:send(Sock, Msg); +send(#client{socket = Sock, type = dgram, dest = Dest}, Msg) -> + %% i("try send to: " + %% "~n ~p", [Dest]), + %% ok = socket:setopt(Sock, otp, debug, true), + TOS = tos_next(), + ok = socket:setopt(Sock, ip, tos, TOS), + case socket:sendto(Sock, Msg, Dest) of + ok = OK -> + OK; + {error, _} = ERROR -> + ERROR + end. + +recv(#client{socket = Sock, type = stream, msg = false}) -> + case socket:recv(Sock) of + {ok, Msg} -> + {ok, {undefined, Msg}}; + {error, _} = ERROR -> + ERROR + end; +recv(#client{socket = Sock, verbose = Verbose, type = stream, msg = true}) -> + case socket:recvmsg(Sock) of + %% An iov of length 1 is an simplification... + {ok, #{addr := undefined = Source, + iov := [Msg], + ctrl := CMsgHdrs, + flags := Flags}} -> + if + (Verbose =:= true) -> + i("received message: " + "~n CMsgHdr: ~p" + "~n Flags: ~p", [CMsgHdrs, Flags]); + true -> + ok + end, + {ok, {Source, Msg}}; + {error, _} = ERROR -> + ERROR + end; +recv(#client{socket = Sock, type = dgram, msg = false}) -> + socket:recvfrom(Sock); +recv(#client{socket = Sock, verbose = Verbose, type = dgram, msg = true}) -> + case socket:recvmsg(Sock) of + {ok, #{addr := Source, + iov := [Msg], + ctrl := CMsgHdrs, + flags := Flags}} -> + if + (Verbose =:= true) -> + i("received message: " + "~n CMsgHdr: ~p" + "~n Flags: ~p", [CMsgHdrs, Flags]); + true -> + ok + end, + {ok, {Source, Msg}}; + {error, _} = ERROR -> + ERROR + end. + + + +which_addr(_Domain, []) -> + throw(no_address); +which_addr(Domain, [{Name, IFO}|_IFL]) when (Name =/= "lo") -> + which_addr2(Domain, IFO); +which_addr(Domain, [_|IFL]) -> + which_addr(Domain, IFL). + +which_addr2(inet = _Domain, [{addr, Addr}|_IFO]) when (size(Addr) =:= 4) -> + Addr; +which_addr2(inet6 = _Domain, [{addr, Addr}|_IFO]) when (size(Addr) =:= 8) -> + Addr; +which_addr2(Domain, [_|IFO]) -> + which_addr2(Domain, IFO). + + +%% --- + +%% enc_req_msg(N, Data) -> +%% enc_msg(?REQ, N, Data). + +%% enc_rep_msg(N, Data) -> +%% enc_msg(?REP, N, Data). + +%% enc_msg(Type, N, Data) when is_list(Data) -> +%% enc_msg(Type, N, list_to_binary(Data)); +%% enc_msg(Type, N, Data) +%% when is_integer(Type) andalso is_integer(N) andalso is_binary(Data) -> +%% <>. + +%% dec_msg(<>) -> +%% {request, N, Data}; +%% dec_msg(<>) -> +%% {reply, N, Data}. + + +%% --- + +%% sleep(T) -> +%% receive after T -> ok end. + + +%% --- + +%% formated_timestamp() -> +%% format_timestamp(os:timestamp()). + +%% format_timestamp(Now) -> +%% N2T = fun(N) -> calendar:now_to_local_time(N) end, +%% format_timestamp(Now, N2T, true). + +%% format_timestamp({_N1, _N2, N3} = N, N2T, true) -> +%% FormatExtra = ".~.2.0w", +%% ArgsExtra = [N3 div 10000], +%% format_timestamp(N, N2T, FormatExtra, ArgsExtra); +%% format_timestamp({_N1, _N2, _N3} = N, N2T, false) -> +%% FormatExtra = "", +%% ArgsExtra = [], +%% format_timestamp(N, N2T, FormatExtra, ArgsExtra). + +%% format_timestamp(N, N2T, FormatExtra, ArgsExtra) -> +%% {Date, Time} = N2T(N), +%% {YYYY,MM,DD} = Date, +%% {Hour,Min,Sec} = Time, +%% FormatDate = +%% io_lib:format("~.4w-~.2.0w-~.2.0w ~.2.0w:~.2.0w:~.2.0w" ++ FormatExtra, +%% [YYYY, MM, DD, Hour, Min, Sec] ++ ArgsExtra), +%% lists:flatten(FormatDate). + + +%% --- + +tos_init() -> + put(tos, 1). + +tos_next() -> + case get(tos) of + TOS when (TOS < 100) -> + put(tos, TOS + 1), + TOS; + _ -> + put(tos, 1), + 1 + end. + + +%% --- + +e(F, A) -> + ?LIB:e(F, A). + +i(F) -> + ?LIB:i(F). + +i(F, A) -> + ?LIB:i(F, A). + diff --git a/erts/emulator/test/socket_lib.erl b/erts/emulator/test/socket_lib.erl new file mode 100644 index 0000000000..9d6524d467 --- /dev/null +++ b/erts/emulator/test/socket_lib.erl @@ -0,0 +1,133 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2018-2018. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% + +-module(socket_lib). + +-export([ + sleep/1, + req/0, rep/0, + enc_req_msg/2, enc_rep_msg/2, + enc_msg/3, dec_msg/1, + request/3, reply/4, + f/2, + i/1, i/2, + e/2 + ]). + + +-define(REQ, 0). +-define(REP, 1). + + +%% --- + +sleep(T) -> + receive after T -> ok end. + + +%% --- + +req() -> ?REQ. +rep() -> ?REP. + +enc_req_msg(N, Data) -> + enc_msg(?REQ, N, Data). + +enc_rep_msg(N, Data) -> + enc_msg(?REP, N, Data). + +enc_msg(Type, N, Data) when is_list(Data) -> + enc_msg(Type, N, list_to_binary(Data)); +enc_msg(Type, N, Data) + when is_integer(Type) andalso is_integer(N) andalso is_binary(Data) -> + <>. + +dec_msg(<>) -> + {request, N, Data}; +dec_msg(<>) -> + {reply, N, Data}. + + +%% --- + +request(Tag, Pid, Request) -> + Ref = make_ref(), + Pid ! {Tag, self(), Ref, Request}, + receive + {Tag, Pid, Ref, Reply} -> + Reply + end. + +reply(Tag, Pid, Ref, Reply) -> + Pid ! {Tag, self(), Ref, Reply}. + + +%% --- + +f(F, A) -> + lists:flatten(io_lib:format(F, A)). + + +%% --- + +e(F, A) -> + p(" " ++ F, A). + +i(F) -> + i(F, []). +i(F, A) -> + p("*** " ++ F, A). + +p(F, A) -> + p(get(sname), F, A). + +p(SName, F, A) -> + io:format("[~s,~p][~s] " ++ F ++ "~n", + [SName,self(),formated_timestamp()|A]). + + +%% --- + +formated_timestamp() -> + format_timestamp(os:timestamp()). + +format_timestamp(Now) -> + N2T = fun(N) -> calendar:now_to_local_time(N) end, + format_timestamp(Now, N2T, true). + +format_timestamp({_N1, _N2, N3} = N, N2T, true) -> + FormatExtra = ".~.2.0w", + ArgsExtra = [N3 div 10000], + format_timestamp(N, N2T, FormatExtra, ArgsExtra); +format_timestamp({_N1, _N2, _N3} = N, N2T, false) -> + FormatExtra = "", + ArgsExtra = [], + format_timestamp(N, N2T, FormatExtra, ArgsExtra). + +format_timestamp(N, N2T, FormatExtra, ArgsExtra) -> + {Date, Time} = N2T(N), + {YYYY,MM,DD} = Date, + {Hour,Min,Sec} = Time, + FormatDate = + io_lib:format("~.4w-~.2.0w-~.2.0w ~.2.0w:~.2.0w:~.2.0w" ++ FormatExtra, + [YYYY, MM, DD, Hour, Min, Sec] ++ ArgsExtra), + lists:flatten(FormatDate). + + diff --git a/erts/emulator/test/socket_server.erl b/erts/emulator/test/socket_server.erl new file mode 100644 index 0000000000..45adffc5e6 --- /dev/null +++ b/erts/emulator/test/socket_server.erl @@ -0,0 +1,954 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2018-2018. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% + +-module(socket_server). + +-export([ + start/0, start/5, + start_tcp/0, start_tcp/1, start_tcp/3, + start_tcp4/0, start_tcp4/1, start_tcp4/2, + start_tcp6/0, start_tcp6/1, start_tcp6/2, + start_udp/0, start_udp/1, start_udp/3, + start_udp4/0, start_udp4/1, start_udp4/2, + start_udp6/0, start_udp6/1, start_udp6/2, + start_sctp/0, start_sctp/1 + ]). + +-define(LIB, socket_lib). + +-record(manager, {socket, msg, peek, acceptors, handler_id, handlers}). +-record(acceptor, {id, socket, manager, + atimeout = 5000}). +-record(handler, {socket, peek, msg, type, manager, + stimeout = 5000, rtimeout = 5000}). + +-define(NUM_ACCEPTORS, 5). + +start() -> + start_tcp(). + +start_tcp() -> + start_tcp4(). + +start_tcp(Peek) -> + start_tcp4(Peek). + +start_tcp4() -> + start_tcp4(false). + +start_tcp4(Peek) -> + start_tcp4(false, Peek). + +start_tcp4(UseMsg, Peek) -> + start_tcp(inet, UseMsg, Peek). + +start_tcp6() -> + start_tcp6(false). + +start_tcp6(Peek) -> + start_tcp6(false, Peek). + +start_tcp6(UseMsg, Peek) -> + start_tcp(inet6, UseMsg, Peek). + +start_tcp(Domain, UseMsg, Peek) when is_boolean(UseMsg) andalso is_boolean(Peek) -> + start(Domain, stream, tcp, UseMsg, Peek). + +start_udp() -> + start_udp4(). + +start_udp(Peek) -> + start_udp4(Peek). + +start_udp4() -> + start_udp4(false). + +start_udp4(Peek) -> + start_udp4(false, Peek). + +start_udp4(UseMsg, Peek) -> + start_udp(inet, UseMsg, Peek). + +start_udp6() -> + start_udp6(false, false). + +start_udp6(Peek) -> + start_udp6(false, Peek). + +start_udp6(UseMsg, Peek) -> + start_udp(inet6, UseMsg, Peek). + +start_udp(Domain, UseMsg, Peek) when is_boolean(UseMsg) andalso is_boolean(Peek) -> + start(Domain, dgram, udp, UseMsg, Peek). + + +start_sctp() -> + start_sctp(inet). + +start_sctp(Domain) when ((Domain =:= inet) orelse (Domain =:= inet6)) -> + start(Domain, seqpacket, sctp, true, false). + +start(Domain, Type, Proto, UseMsg, Peek) -> + put(sname, "starter"), + i("try start manager"), + {Pid, MRef} = manager_start(Domain, Type, Proto, UseMsg, Peek), + i("manager (~p) started", [Pid]), + loop(Pid, MRef). + +loop(Pid, MRef) -> + receive + {'DOWN', MRef, process, Pid, Reason} -> + i("manager process exited: " + "~n ~p", [Reason]), + ok + end. + + +%% ========================================================================= + +manager_start(Domain, Type, Proto, UseMsg, Peek) -> + spawn_monitor(fun() -> manager_init(Domain, Type, Proto, UseMsg, Peek) end). + +manager_start_handler(Pid, Sock) -> + manager_request(Pid, {start_handler, Sock}). + +manager_stop(Pid, Reason) -> + manager_request(Pid, {stop, Reason}). + +manager_request(Pid, Request) -> + ?LIB:request(manager, Pid, Request). + +manager_reply(Pid, Ref, Reply) -> + ?LIB:reply(manager, Pid, Ref, Reply). + + +manager_init(Domain, Type, Proto, UseMsg, Peek) -> + put(sname, "manager"), + do_manager_init(Domain, Type, Proto, UseMsg, Peek). + +do_manager_init(Domain, stream = Type, Proto, UseMsg, Peek) -> + i("try start acceptor(s)"), + {Sock, Acceptors} = manager_stream_init(Domain, Type, Proto), + manager_loop(#manager{socket = Sock, + msg = UseMsg, + peek = Peek, + acceptors = Acceptors, + handler_id = 1, + handlers = []}); +do_manager_init(Domain, dgram = Type, Proto, UseMsg, Peek) -> + i("try open socket"), + case socket:open(Domain, Type, Proto) of + {ok, Sock} -> + F = fun(X) -> case socket:getopt(Sock, socket, X) of + {ok, V} -> f("~p", [V]); + {error, R} -> f("error: ~p", [R]) + end + end, + i("socket opened (~s,~s,~s): " + "~n broadcast: ~s" + "~n dontroute: ~s" + "~n keepalive: ~s" + "~n reuseaddr: ~s" + "~n linger: ~s" + "~n debug: ~s" + "~n prio: ~s" + "~n rcvbuf: ~s" + "~n rcvtimeo: ~s" + "~n sndbuf: ~s" + "~n sndtimeo: ~s" + "~n => try find (local) address", + [F(domain), F(type), F(protocol), + F(broadcast), F(dontroute), F(keepalive), F(reuseaddr), F(linger), + F(debug), F(priority), + F(rcvbuf), F(rcvtimeo), F(sndbuf), F(sndtimeo)]), + Addr = which_addr(Domain), + SA = #{family => Domain, + addr => Addr}, + i("try bind to: " + "~n ~p", [Addr]), + case socket:bind(Sock, SA) of + {ok, _P} -> + ok; + {error, BReason} -> + throw({bind, BReason}) + end, + i("bound to: " + "~n ~s" + "~n => try start handler", + [case socket:sockname(Sock) of + {ok, Name} -> f("~p", [Name]); + {error, R} -> f("error: ~p", [R]) + end]), + case handler_start(1, Sock, UseMsg, Peek) of + {ok, {Pid, MRef}} -> + i("handler (~p) started", [Pid]), + handler_continue(Pid), + manager_loop(#manager{peek = Peek, + msg = UseMsg, + handler_id = 2, % Just in case + handlers = [{1, Pid, MRef}]}); + {error, SReason} -> + e("Failed starting handler: " + "~n ~p", [SReason]), + exit({failed_start_handler, SReason}) + end; + {error, OReason} -> + e("Failed open socket: " + "~n ~p", [OReason]), + exit({failed_open_socket, OReason}) + end; +do_manager_init(Domain, seqpacket = Type, sctp = Proto, _UseMsg, _Peek) -> + %% This is as far as I have got with SCTP at the moment... + case socket:open(Domain, Type, Proto) of + {ok, Sock} -> + i("(sctp) socket opened: " + "~n ~p", [Sock]), + EXP = fun(_Desc, Expect, Expect) -> + Expect; + (Desc, Expect, Actual) -> + e("Unexpected result ~w: " + "~n Expect: ~p" + "~n Actual: ~p", [Desc, Expect, Actual]), + exit({Desc, Expect, Actual}) + end, + GO = fun(O) -> case socket:getopt(Sock, sctp, O) of + {ok, V} -> f("~p", [V]); + {error, R} -> f("error: ~p", [R]) + end + end, + %% ok = socket:setopt(Sock, otp, debug, true), + + i("Miscellaneous options: " + "~n associnfo: ~s" + "~n autoclose: ~s" + "~n disable-fragments: ~s" + "~n initmsg: ~s" + "~n maxseg: ~s" + "~n nodelay: ~s" + "~n rtoinfo: ~s", + [GO(associnfo), + GO(autoclose), + GO(disable_fragments), + GO(initmsg), + GO(maxseg), + GO(nodelay), + GO(rtoinfo)]), + + Events = #{data_in => true, + association => true, + address => true, + send_failure => true, + peer_error => true, + shutdown => true, + partial_delivery => true, + adaptation_layer => true, + authentication => true, + sender_dry => true}, + EXP(set_sctp_events, ok, socket:setopt(Sock, sctp, events, Events)), + EXP(close_socket, ok, socket:close(Sock)); + {error, Reason} -> + exit({failed_open, Reason}) + end; +do_manager_init(Domain, raw = Type, Proto, UseMsg, Peek) when is_integer(Proto) -> + do_manager_init(Domain, Type, {raw, Proto}, UseMsg, Peek); +do_manager_init(Domain, raw = Type, Proto, _UseMsg, _Peek) -> + case socket:open(Domain, Type, Proto) of + {ok, Sock} -> + i("(sctp) socket opened: " + "~n ~p", [Sock]), + socket:close(Sock); + {error, Reason} -> + exit({failed_open, Reason}) + end. + + + +manager_stream_init(Domain, Type, Proto) -> + i("try (socket) open"), + Sock = case socket:open(Domain, Type, Proto) of + {ok, S} -> + S; + {error, OReason} -> + throw({open, OReason}) + end, + F = fun(X) -> case socket:getopt(Sock, socket, X) of + {ok, V} -> f("~p", [V]); + {error, R} -> f("error: ~p", [R]) + end + end, + i("(socket) open (~s,~s,~s): " + "~n debug: ~s" + "~n prio: ~s" + "~n => try find (local) address", + [F(domain), F(type), F(protocol), F(debug), F(priority)]), + Addr = which_addr(Domain), + SA = #{family => Domain, + addr => Addr}, + i("found: " + "~n ~p" + "~n => try (socket) bind", [Addr]), + %% ok = socket:setopt(Sock, otp, debug, true), + %% ok = socket:setopt(Sock, socket, debug, 1), %% must have rights!! + Port = case socket:bind(Sock, SA) of + {ok, P} -> + %% ok = socket:setopt(Sock, socket, debug, 0), %% must have rights!! + %% ok = socket:setopt(Sock, otp, debug, false), + P; + {error, BReason} -> + throw({bind, BReason}) + end, + i("bound to: " + "~n ~p" + "~n => try (socket) listen (acceptconn: ~s)", + [Port, F(acceptconn)]), + case socket:listen(Sock) of + ok -> + i("listening (acceptconn: ~s)", + [F(acceptconn)]), + manager_stream_init(Sock, 1, ?NUM_ACCEPTORS, []); + {error, LReason} -> + throw({listen, LReason}) + end. + +which_addr(Domain) -> + Iflist = case inet:getifaddrs() of + {ok, IFL} -> + IFL; + {error, Reason} -> + throw({inet,getifaddrs,Reason}) + end, + which_addr(Domain, Iflist). + +which_addr(_Domain, []) -> + throw(no_address); +which_addr(Domain, [{Name, IFO}|_IFL]) when (Name =/= "lo") -> + which_addr2(Domain, IFO); +which_addr(Domain, [_|IFL]) -> + which_addr(Domain, IFL). + +which_addr2(_, []) -> + throw(no_address); +which_addr2(inet = _Domain, [{addr, Addr}|_IFO]) when (size(Addr) =:= 4) -> + Addr; +which_addr2(inet6 = _Domain, [{addr, Addr}|_IFO]) when (size(Addr) =:= 8) -> + Addr; +which_addr2(Domain, [_|IFO]) -> + which_addr2(Domain, IFO). + + +manager_stream_init(Sock, ID, NumAcceptors, Acc) + when (NumAcceptors > 0) -> + i("try start acceptor"), + case acceptor_start(Sock, ID) of + {ok, {Pid, MRef}} -> + i("acceptor ~w (~p) started", [ID, Pid]), + ?LIB:sleep(2000), + manager_stream_init(Sock, ID+1, NumAcceptors-1, + [{ID, Pid, MRef}|Acc]); + {error, Reason} -> + exit({failed_starting_acceptor, Reason}) + end; +manager_stream_init(Sock, _ID, 0, Acc) -> + %% Req = {kill_acceptor, length(Acc)}, % Last in the queue + %% Req = {kill_acceptor, 3}, % In the "middle" of the queue + %% Req = {kill_acceptor, 2}, % The first in the queue + %% Req = {kill_acceptor, 1}, % Current acceptor + %% Msg = {manager, self(), make_ref(), Req}, + %% erlang:send_after(timer:seconds(10), self(), Msg), + {Sock, lists:reverse(Acc)}. + + +manager_loop(M) -> + receive + {'DOWN', MRef, process, Pid, Reason} -> + M2 = manager_handle_down(M, MRef, Pid, Reason), + manager_loop(M2); + + {manager, Pid, Ref, Request} -> + M2 = manager_handle_request(M, Pid, Ref, Request), + manager_loop(M2) + end. + + +manager_handle_down(#manager{acceptors = Acceptors, + handlers = Handlers} = M, MRef, Pid, Reason) -> + case lists:keysearch(Pid, 2, Acceptors) of + {value, {ID, Pid, MRef}} when (Reason =:= normal) -> + i("acceptor ~w exited (normally)", [ID]), + case lists:keydelete(Pid, 2, Acceptors) of + [] -> + %% We are done + i("the last acceptor - we are done"), + exit(normal); + Acceptors2 -> + M#manager{acceptors = Acceptors2} + end; + {value, {ID, Pid, MRef}} -> + e("acceptor ~w crashed: " + "~n ~p", [ID, Reason]), + exit({acceptor_died, Reason}); + + false -> %% handler! + if + (Reason =/= normal) -> + e("handler ~p died: " + "~n ~p", [Pid, Reason]); + true -> + i("handler ~p terminated", [Pid]) + end, + Handlers2 = lists:keydelete(Pid, 2, Handlers), + M#manager{handlers = Handlers2} + end. + + +manager_handle_request(#manager{peek = Peek, + msg = UseMsg, + handler_id = HID, + handlers = Handlers} = M, Pid, Ref, + {start_handler, Sock}) -> + i("try start handler (~w)", [HID]), + case handler_start(HID, Sock, UseMsg, Peek) of + {ok, {HPid, HMRef}} -> + i("handler ~w started", [HID]), + manager_reply(Pid, Ref, {ok, HPid}), + M#manager{handler_id = HID+1, + handlers = [{HID, HPid, HMRef}|Handlers]}; + {error, Reason} = ERROR -> + e("Failed starting new handler: " + "~n Sock: ~p" + "~n Reason: ~p", [Sock, Reason]), + manager_reply(Pid, Ref, ERROR), + M + end; +manager_handle_request(#manager{socket = Sock, + acceptors = [{AID, APid, AMRef}]} = M, _Pid, _Ref, + {kill_acceptor, AID}) -> + i("try kill (only remeining) acceptor ~w", [AID]), + socket:setopt(Sock, otp, debug, true), + manager_stop_acceptor(APid, AMRef, AID, kill), + M#manager{acceptors = []}; +manager_handle_request(#manager{socket = Sock, + acceptors = Acceptors} = M, _Pid, _Ref, + {kill_acceptor, AID}) -> + i("try kill acceptor ~w", [AID]), + case lists:keysearch(AID, 1, Acceptors) of + {value, {AID, APid, AMRef}} -> + socket:setopt(Sock, otp, debug, true), + manager_stop_acceptor(APid, AMRef, AID, kill), + Acceptors2 = lists:keydelete(AID, 1, Acceptors), + M#manager{acceptors = Acceptors2}; + false -> + e("no such acceptor"), + M + end; +manager_handle_request(#manager{acceptors = Acceptors, + handlers = Handlers}, Pid, Ref, + {stop, Reason}) -> + i("stop"), + manager_reply(Pid, Ref, ok), + manager_stop_handlers(Handlers, Reason), + manager_stop_acceptors(Acceptors, Reason), + i("stopped", []), + exit(Reason). + +manager_stop_acceptors(Acceptors, Reason) -> + lists:foreach(fun({ID,P,M}) -> + manager_stop_acceptor(P, M, ID, Reason) + end, Acceptors). + +manager_stop_acceptor(Pid, MRef, ID, Reason) -> + i("try stop acceptor ~w (~p): ~p", [ID, Pid, Reason]), + erlang:demonitor(MRef, [flush]), + acceptor_stop(Pid, Reason), + ok. + +manager_stop_handlers(Handlers, Reason) -> + lists:foreach(fun({ID,P,M}) -> + manager_stop_handler(P, M, ID, Reason) + end, Handlers). + +manager_stop_handler(Pid, MRef, ID, Reason) -> + i("try stop handler ~w (~p): ~p", [ID, Pid, Reason]), + erlang:demonitor(MRef, [flush]), + handler_stop(Pid, Reason), + ok. + + + +%% ========================================================================= + +acceptor_start(Sock, ID) -> + Self = self(), + A = {Pid, _} = spawn_monitor(fun() -> + acceptor_init(Self, Sock, ID) + end), + receive + {acceptor, Pid, ok} -> + {ok, A}; + {acceptor, Pid, {error, _} = Error} -> + exit(Pid, kill), % Just in case + Error; + {'DOWN', _MRef, process, Pid, Reason} -> + {error, {crashed, Reason}} + end. + +acceptor_stop(Pid, _Reason) -> + %% acceptor_request(Pid, {stop, Reason}). + exit(Pid, kill). + +%% acceptor_request(Pid, Request) -> +%% request(acceptor, Pid, Request). + +%% acceptor_reply(Pid, Ref, Reply) -> +%% reply(acceptor, Pid, Ref, Reply). + + +acceptor_init(Manager, Sock, ID) -> + put(sname, f("acceptor[~w]", [ID])), + Manager ! {acceptor, self(), ok}, + %% ok = socket:setopt(Sock, otp, debug, true), + acceptor_loop(#acceptor{id = ID, + manager = Manager, + socket = Sock}). + +acceptor_loop(#acceptor{socket = LSock, atimeout = Timeout} = A) -> + i("try accept"), + case socket:accept(LSock, Timeout) of + {ok, Sock} -> + i("accepted: " + "~n ~p" + "~nwhen" + "~n ~p", [Sock, socket:info()]), + case acceptor_handle_accept_success(A, Sock) of + ok -> + acceptor_loop(A); + {error, Reason} -> + e("Failed starting handler: " + "~n ~p", [Reason]), + socket:close(Sock), + exit({failed_starting_handler, Reason}) + end; + {error, timeout} -> + i("timeout"), + acceptor_loop(A); + {error, Reason} -> + e("accept failure: " + "~n ~p", [Reason]), + exit({accept, Reason}) + end. + +acceptor_handle_accept_success(#acceptor{manager = Manager}, Sock) -> + i("try start handler for peer" + "~n ~p", [case socket:peername(Sock) of + {ok, Peer} -> Peer; + {error, _} = E -> E + end]), + case manager_start_handler(Manager, Sock) of + {ok, Pid} -> + i("handler (~p) started - now change 'ownership'", [Pid]), + case socket:setopt(Sock, otp, controlling_process, Pid) of + ok -> + %% Normally we should have a msgs collection here + %% (of messages we receive before the control was + %% handled over to Handler), but since we don't + %% have active implemented yet... + i("new handler (~p) now controlling process", [Pid]), + handler_continue(Pid), + ok; + {error, _} = ERROR -> + exit(Pid, kill), + ERROR + end; + {error, Reason2} -> + e("failed starting handler: " + "~n (new) Socket: ~p" + "~n Reason: ~p", [Sock, Reason2]), + exit({failed_starting_handler, Reason2}) + end. + + + +%% ========================================================================= + +handler_start(ID, Sock, UseMsg, Peek) -> + Self = self(), + H = {Pid, _} = spawn_monitor(fun() -> + handler_init(Self, ID, UseMsg, Peek, Sock) + end), + receive + {handler, Pid, ok} -> + {ok, H}; + {handler, Pid, {error, _} = ERROR} -> + exit(Pid, kill), % Just in case + ERROR + end. + +handler_stop(Pid, _Reason) -> + %% handler_request(Pid, {stop, Reason}). + exit(Pid, kill). + +handler_continue(Pid) -> + handler_request(Pid, continue). + +handler_request(Pid, Request) -> + ?LIB:request(handler, Pid, Request). + +handler_reply(Pid, Ref, Reply) -> + ?LIB:reply(handler, Pid, Ref, Reply). + + +handler_init(Manager, ID, Msg, Peek, Sock) -> + put(sname, f("handler:~w", [ID])), + i("starting"), + Manager ! {handler, self(), ok}, + receive + {handler, Pid, Ref, continue} -> + i("got continue"), + handler_reply(Pid, Ref, ok), + G = fun(L, O) -> case socket:getopt(Sock, L, O) of + {ok, Val} -> + f("~p", [Val]); + {error, R} when is_atom(R) -> + f("error: ~w", [R]); + {error, {T, R}} when is_atom(T) -> + f("error: ~w, ~p", [T, R]); + {error, R} -> + f("error: ~p", [R]) + end + end, + GSO = fun(O) -> G(socket, O) end, + GIP4 = fun(O) -> G(ip, O) end, + GIP6 = fun(O) -> G(ipv6, O) end, + {ok, Domain} = socket:getopt(Sock, socket, domain), + {ok, Type} = socket:getopt(Sock, socket, type), + {ok, Proto} = socket:getopt(Sock, socket, protocol), + B2D = GSO(bindtodevice), + RA = GSO(reuseaddr), + RP = GSO(reuseport), + OOBI = GSO(oobinline), + RcvBuf = GSO(rcvbuf), + RcvLW = GSO(rcvlowat), + RcvTO = GSO(rcvtimeo), + SndBuf = GSO(sndbuf), + SndLW = GSO(sndlowat), + SndTO = GSO(sndtimeo), + Linger = GSO(linger), + Timestamp = GSO(timestamp), + FreeBind = GIP4(freebind), + MTU = GIP4(mtu), + MTUDisc = GIP4(mtu_discover), + MALL = GIP4(multicast_all), + MIF4 = GIP4(multicast_if), + MLoop4 = GIP4(multicast_loop), + MTTL = GIP4(multicast_ttl), + NF = GIP4(nodefrag), % raw only + PktInfo = GIP4(pktinfo), % dgram only + RecvErr4 = GIP4(recverr), + RecvIF = GIP4(recvif), % Only dgram and raw (and FreeBSD) + RecvOPTS = GIP4(recvopts), % Not stream + RecvOrigDstAddr = GIP4(recvorigdstaddr), + RecvTOS = GIP4(recvtos), + RecvTTL = GIP4(recvttl), % not stream + RetOpts = GIP4(retopts), % not stream + SendSrcAddr = GIP4(sendsrcaddr), + TOS = GIP4(tos), + Transparent = GIP4(transparent), + TTL = GIP4(ttl), + MHops = GIP6(multicast_hops), + MIF6 = GIP6(multicast_if), % Only dgram and raw + MLoop6 = GIP6(multicast_loop), + RecvErr6 = GIP6(recverr), + RecvPktInfo = GIP6(recvpktinfo), + RtHdr = GIP6(rthdr), + AuthHdr = GIP6(authhdr), + HopLimit = GIP6(hoplimit), + HopOpts = GIP6(hopopts), + DstOpts = GIP6(dstopts), + FlowInfo = GIP6(flowinfo), + UHops = GIP6(unicast_hops), + i("got continue when: " + "~n (socket) Domain: ~p" + "~n (socket) Type: ~p" + "~n (socket) Protocol: ~p" + "~n (socket) Reuse Address: ~s" + "~n (socket) Reuse Port: ~s" + "~n (socket) Bind To Device: ~s" + "~n (socket) OOBInline: ~s" + "~n (socket) RcvBuf: ~s" + "~n (socket) RcvLW: ~s" + "~n (socket) RcvTO: ~s" + "~n (socket) SndBuf: ~s" + "~n (socket) SndLW: ~s" + "~n (socket) SndTO: ~s" + "~n (socket) Linger: ~s" + "~n (socket) Timestamp: ~s" + "~n (ip) FreeBind: ~s" + "~n (ip) MTU: ~s" + "~n (ip) MTU Discovery: ~s" + "~n (ip) Multicast ALL: ~s" + "~n (ip) Multicast IF: ~s" + "~n (ip) Multicast Loop: ~s" + "~n (ip) Multicast TTL: ~s" + "~n (ip) Node Frag: ~s" + "~n (ip) Pkt Info: ~s" + "~n (ip) Recv Err: ~s" + "~n (ip) Recv IF: ~s" + "~n (ip) Recv OPTS: ~s" + "~n (ip) Recv Orig Dst Addr: ~s" + "~n (ip) Recv TOS: ~s" + "~n (ip) Recv TTL: ~s" + "~n (ip) Ret Opts: ~s" + "~n (ip) Send Src Addr: ~s" + "~n (ip) TOS: ~s" + "~n (ip) Transparent: ~s" + "~n (ip) TTL: ~s" + "~n (ipv6) Multicast Hops: ~s" + "~n (ipv6) Multicast IF: ~s" + "~n (ipv6) Multicast Loop: ~s" + "~n (ipv6) Recv Err: ~s" + "~n (ipv6) Recv Pkt Info: ~s" + "~n (ipv6) RT Hdr: ~s" + "~n (ipv6) Auth Hdr: ~s" + "~n (ipv6) Hop Limit: ~s" + "~n (ipv6) Hop Opts: ~s" + "~n (ipv6) Dst Opts: ~s" + "~n (ipv6) Flow Info: ~s" + "~n (ipv6) Unicast Hops: ~s", + [Domain, Type, Proto, + RA, RP, B2D, OOBI, + RcvBuf, RcvLW, RcvTO, SndBuf, SndLW, SndTO, + Linger, Timestamp, + FreeBind, MTU, MTUDisc, MALL, MIF4, MLoop4, MTTL, + NF, PktInfo,RecvErr4, + RecvIF, RecvOPTS, RecvOrigDstAddr, RecvTOS, RecvTTL, RetOpts, + SendSrcAddr, TOS, Transparent, TTL, + MHops, MIF6, MLoop6, RecvErr6, RecvPktInfo, + RtHdr, AuthHdr, HopLimit, HopOpts, DstOpts, FlowInfo, + UHops]), + + %% ok = socket:setopt(Sock, otp, debug, true), + %% case socket:getopt(Sock, 0, {13, int}) of + %% {ok, Val} -> + %% i("PktOpts ok: ~p", [Val]); + %% {error, Reason} -> + %% e("PktOpts err: ~p", [Reason]) + %% end, + %% ok = socket:setopt(Sock, otp, debug, false), + SSO = fun(O, V) -> soso(Sock, O, V) end, + SIP4 = + fun(O, V) -> + if + (Type =:= dgram) -> + ok = soip(Sock, O, V); + true -> + ok + end + end, + SSO(timestamp, true), + SIP4(pktinfo, true), + ok = soip(Sock, recvtos, true), + SIP4(recvttl, true), + ok = soip(Sock, recvorigdstaddr, true), + + handler_loop(#handler{msg = Msg, + peek = Peek, + manager = Manager, + type = Type, + socket = Sock}) + end. + +so(Sock, Lvl, Opt, Val) -> + ok = socket:setopt(Sock, Lvl, Opt, Val). + +soso(Sock, Opt, Val) -> + so(Sock, socket, Opt, Val). + +soip(Sock, Opt, Val) -> + so(Sock, ip, Opt, Val). + +%% soipv6(Sock, Opt, Val) -> +%% so(Sock, ipv6, Opt, Val). + +handler_loop(H) -> + i("try read message"), + case recv(H) of + {ok, {Source, Msg}} -> + i("received ~w bytes of data~s", + [size(Msg), case Source of + undefined -> ""; + _ -> f(" from:~n ~p", [Source]) + end]), + case ?LIB:dec_msg(Msg) of + {request, N, Req} -> + i("received request ~w: " + "~n ~p", [N, Req]), + Reply = ?LIB:enc_rep_msg(N, "hoppsan"), + case send(H, Reply, Source) of + ok -> + i("successfully sent reply ~w", [N]), + handler_loop(H); + {error, SReason} -> + e("failed sending reply ~w:" + "~n ~p", [N, SReason]), + exit({failed_sending_reply, SReason}) + end + end; + + {error, closed} -> + i("closed when" + "~n ~p", [socket:info()]), + exit(normal); + + {error, RReason} -> + e("failed reading request: " + "~n ~p", [RReason]), + exit({failed_reading_request, RReason}) + end. + + +recv(#handler{peek = true, socket = Sock, type = stream}) -> + peek_recv(Sock); +recv(#handler{socket = Sock, msg = true, type = stream}) -> + case socket:recvmsg(Sock) of + {ok, #{addr := undefined = Source, + iov := [Data], + ctrl := CMsgHdrs, + flags := Flags}} -> + i("received message: " + "~n CMsgHdrs: ~p" + "~n Flags: ~p", [CMsgHdrs, Flags]), + {ok, {Source, Data}}; + {ok, X} -> + e("received *unexpected* message: " + "~n ~p", [X]), + {error, {unexpected, X}}; + {error, _} = ERROR -> + ERROR + end; +recv(#handler{socket = Sock, msg = true, type = dgram}) -> + case socket:recvmsg(Sock) of + {ok, #{addr := Source, + iov := [Data], + ctrl := CMsgHdrs, + flags := Flags}} -> + i("received message: " + "~n CMsgHdrs: ~p" + "~n Flags: ~p", [CMsgHdrs, Flags]), + {ok, {Source, Data}}; + {ok, X} -> + {error, {unexpected, X}}; + {error, _} = ERROR -> + ERROR + end; +recv(#handler{peek = false, socket = Sock, type = stream}) -> + do_recv(Sock); +recv(#handler{peek = Peek, socket = Sock, type = dgram}) + when (Peek =:= true) -> + %% ok = socket:setopt(Sock, otp, debug, true), + RES = peek_recvfrom(Sock, 5), + %% ok = socket:setopt(Sock, otp, debug, false), + RES; +recv(#handler{peek = Peek, socket = Sock, type = dgram}) + when (Peek =:= false) -> + %% ok = socket:setopt(Sock, otp, debug, true), + socket:recvfrom(Sock). + +do_recv(Sock) -> + case socket:recv(Sock) of + {ok, Msg} -> + {ok, {undefined, Msg}}; + {error, _} = ERROR -> + ERROR + end. + +peek_recv(Sock) -> + i("try peek on the message type (expect request)"), + Type = ?LIB:req(), + case socket:recv(Sock, 4, [peek]) of + {ok, <>} -> + i("was request - do proper recv"), + do_recv(Sock); + {error, _} = ERROR -> + ERROR + end. + +peek_recvfrom(Sock, BufSz) -> + i("try peek recvfrom with buffer size ~w", [BufSz]), + case socket:recvfrom(Sock, BufSz, [peek]) of + {ok, {_Source, Msg}} when (BufSz =:= size(Msg)) -> + %% i("we filled the buffer: " + %% "~n ~p", [Msg]), + %% It *may not* fit => try again with double size + peek_recvfrom(Sock, BufSz*2); + {ok, _} -> + %% It fits => read for real + i("we did *not* fill the buffer - do the 'real' read"), + socket:recvfrom(Sock); + {error, _} = ERROR -> + ERROR + end. + + +send(#handler{socket = Sock, msg = true, type = stream, stimeout = Timeout}, + Msg, _) -> + CMsgHdr = #{level => ip, type => tos, data => reliability}, + CMsgHdrs = [CMsgHdr], + MsgHdr = #{iov => [Msg], ctrl => CMsgHdrs}, + %% socket:setopt(Sock, otp, debug, true), + Res = socket:sendmsg(Sock, MsgHdr, Timeout), + %% socket:setopt(Sock, otp, debug, false), + Res; +send(#handler{socket = Sock, type = stream, stimeout = Timeout}, Msg, _) -> + socket:send(Sock, Msg, Timeout); +send(#handler{socket = Sock, msg = true, type = dgram, stimeout = Timeout}, + Msg, Dest) -> + CMsgHdr = #{level => ip, type => tos, data => reliability}, + CMsgHdrs = [CMsgHdr], + MsgHdr = #{addr => Dest, + ctrl => CMsgHdrs, + iov => [Msg]}, + %% ok = socket:setopt(Sock, otp, debug, true), + Res = socket:sendmsg(Sock, MsgHdr, Timeout), + %% ok = socket:setopt(Sock, otp, debug, false), + Res; +send(#handler{socket = Sock, type = dgram, stimeout = Timeout}, Msg, Dest) -> + socket:sendto(Sock, Msg, Dest, Timeout). + +%% filler() -> +%% list_to_binary(lists:duplicate(2048, " FILLER ")). + + + +%% ========================================================================= + +f(F, A) -> + ?LIB:f(F, A). + +e(F) -> + e(F, []). +e(F, A) -> + ?LIB:e(F, A). + +i(F) -> + ?LIB:i(F). + +i(F, A) -> + ?LIB:i(F, A). + diff --git a/lib/kernel/test/Makefile b/lib/kernel/test/Makefile index 28edf4889b..8b16e83707 100644 --- a/lib/kernel/test/Makefile +++ b/lib/kernel/test/Makefile @@ -24,11 +24,6 @@ include $(ERL_TOP)/make/$(TARGET)/otp.mk # Target Specs # ---------------------------------------------------- -SOCKET_MODULES = \ - socket_lib \ - socket_server \ - socket_client - MODULES= \ rpc_SUITE \ pdict_SUITE \ @@ -95,9 +90,7 @@ MODULES= \ sendfile_SUITE \ standard_error_SUITE \ multi_load_SUITE \ - zzz_SUITE \ - socket_SUITE \ - $(SOCKET_MODULES) + zzz_SUITE APP_FILES = \ appinc.app \ @@ -134,7 +127,6 @@ ERL_COMPILE_FLAGS += EBIN = . -SOCKET_TARGETS = $(SOCKET_MODULES:%=$(EBIN)/%.$(EMULATOR)) TARGETS = $(MODULES:%=$(EBIN)/%.$(EMULATOR)) @@ -160,8 +152,6 @@ docs: targets: $(TARGETS) -socket: $(SOCKET_TARGETS) - # ---------------------------------------------------- # Release Target diff --git a/lib/kernel/test/socket_SUITE.erl b/lib/kernel/test/socket_SUITE.erl deleted file mode 100644 index 022e83a944..0000000000 --- a/lib/kernel/test/socket_SUITE.erl +++ /dev/null @@ -1,4186 +0,0 @@ -%% -%% %CopyrightBegin% -%% -%% Copyright Ericsson AB 2018-2018. All Rights Reserved. -%% -%% Licensed under the Apache License, Version 2.0 (the "License"); -%% you may not use this file except in compliance with the License. -%% You may obtain a copy of the License at -%% -%% http://www.apache.org/licenses/LICENSE-2.0 -%% -%% Unless required by applicable law or agreed to in writing, software -%% distributed under the License is distributed on an "AS IS" BASIS, -%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -%% See the License for the specific language governing permissions and -%% limitations under the License. -%% -%% %CopyrightEnd% -%% - --module(socket_SUITE). - --include_lib("common_test/include/ct.hrl"). --include_lib("common_test/include/ct_event.hrl"). - -%% Suite exports --export([suite/0, all/0, groups/0]). --export([init_per_suite/1, end_per_suite/1, - init_per_testcase/2, end_per_testcase/2]). - -%% Test cases --export([ - %% API Basic - api_b_open_and_close_udp4/1, - api_b_open_and_close_tcp4/1, - api_b_sendto_and_recvfrom_udp4/1, - api_b_sendmsg_and_recvmsg_udp4/1, - api_b_send_and_recv_tcp4/1, - api_b_sendmsg_and_recvmsg_tcp4/1, - - %% API Options - api_opt_simple_otp_options/1, - api_opt_simple_otp_controlling_process/1, - - %% API Operation Timeout - api_to_connect_tcp4/1, - api_to_connect_tcp6/1, - api_to_accept_tcp4/1, - api_to_accept_tcp6/1, - api_to_maccept_tcp4/1, - api_to_maccept_tcp6/1, - api_to_send_tcp4/1, - api_to_send_tcp6/1, - api_to_sendto_udp4/1, - api_to_sendto_udp6/1, - api_to_sendmsg_tcp4/1, - api_to_sendmsg_tcp6/1, - api_to_recv_udp4/1, - api_to_recv_udp6/1, - api_to_recv_tcp4/1, - api_to_recv_tcp6/1, - api_to_recvfrom_udp4/1, - api_to_recvfrom_udp6/1, - api_to_recvmsg_udp4/1, - api_to_recvmsg_udp6/1, - api_to_recvmsg_tcp4/1, - api_to_recvmsg_tcp6/1, - - %% Socket Closure - sc_cpe_socket_cleanup_tcp4/1, - sc_cpe_socket_cleanup_tcp6/1, - sc_cpe_socket_cleanup_udp4/1, - sc_cpe_socket_cleanup_udp6/1, - sc_lc_recv_response_tcp4/1, - sc_lc_recv_response_tcp6/1, - sc_lc_recvmsg_response_tcp4/1, - sc_lc_recvmsg_response_tcp6/1, - sc_lc_acceptor_response_tcp4/1, - sc_lc_acceptor_response_tcp6/1, - sc_rc_recv_response_tcp4/1, - sc_rc_recv_response_tcp6/1, - sc_rc_recvmsg_response_tcp4/1, - sc_rc_recvmsg_response_tcp6/1 - - %% Tickets - ]). - -%% Internal exports -%% -export([]). - - --type initial_evaluator_state() :: map(). --type evaluator_state() :: term(). --type command_fun() :: - fun((State :: evaluator_state()) -> ok) | - fun((State :: evaluator_state()) -> {ok, evaluator_state()}) | - fun((State :: evaluator_state()) -> {error, term()}). - --type command() :: #{desc := string(), - cmd := command_fun()}. - - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - --define(BASIC_REQ, <<"hejsan">>). --define(BASIC_REP, <<"hoppsan">>). - --define(FAIL(R), exit(R)). - --define(SLEEP(T), receive after T -> ok end). - - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -suite() -> - [{ct_hooks,[ts_install_cth]}, - {timetrap,{minutes,1}}]. - -all() -> - [ - {group, api}, - {group, socket_closure} - %% {group, tickets} - ]. - -groups() -> - [{api, [], api_cases()}, - {api_basic, [], api_basic_cases()}, - {api_options, [], api_options_cases()}, - {api_op_with_timeout, [], api_op_with_timeout_cases()}, - {socket_closure, [], socket_closure_cases()}, - {sc_ctrl_proc_exit, [], sc_cp_exit_cases()}, - {sc_local_close, [], sc_lc_cases()}, - {sc_remote_close, [], sc_rc_cases()} - %% {tickets, [], ticket_cases()} - ]. - -api_cases() -> - [ - {group, api_basic}, - {group, api_options}, - {group, api_op_with_timeout} - ]. - -api_basic_cases() -> - [ - api_b_open_and_close_udp4, - api_b_open_and_close_tcp4, - api_b_sendto_and_recvfrom_udp4, - api_b_sendmsg_and_recvmsg_udp4, - api_b_send_and_recv_tcp4, - api_b_sendmsg_and_recvmsg_tcp4 - ]. - -api_options_cases() -> - [ - api_opt_simple_otp_options, - api_opt_simple_otp_controlling_process - ]. - -api_op_with_timeout_cases() -> - [ - api_to_connect_tcp4, - api_to_connect_tcp6, - api_to_accept_tcp4, - api_to_accept_tcp6, - api_to_send_tcp4, - api_to_send_tcp6, - api_to_sendto_udp4, - api_to_sendto_udp6, - api_to_sendmsg_tcp4, - api_to_sendmsg_tcp6, - api_to_recv_udp4, - api_to_recv_udp6, - api_to_recv_tcp4, - api_to_recv_tcp6, - api_to_recvfrom_udp4, - api_to_recvfrom_udp6, - api_to_recvmsg_udp4, - api_to_recvmsg_udp6, - api_to_recvmsg_tcp4, - api_to_recvmsg_tcp6 - ]. - -%% These cases tests what happens when the socket is closed, locally or -%% remotely. -socket_closure_cases() -> - [ - {group, sc_ctrl_proc_exit}, - {group, sc_local_close}, - {group, sc_remote_close} - ]. - -%% These cases are all about socket cleanup after the controlling process -%% exits *without* calling socket:close/1. -sc_cp_exit_cases() -> - [ - sc_cpe_socket_cleanup_tcp4, - sc_cpe_socket_cleanup_tcp6, - sc_cpe_socket_cleanup_udp4, - sc_cpe_socket_cleanup_udp6 - ]. - -%% These cases tests what happens when the socket is closed locally. -sc_lc_cases() -> - [ - sc_lc_recv_response_tcp4, - sc_lc_recv_response_tcp6, - - sc_lc_recvmsg_response_tcp4, - sc_lc_recvmsg_response_tcp6, - - sc_lc_acceptor_response_tcp4, - sc_lc_acceptor_response_tcp6 - ]. - -%% These cases tests what happens when the socket is closed remotely. -sc_rc_cases() -> - [ - sc_rc_recv_response_tcp4, - sc_rc_recv_response_tcp6, - - sc_rc_recvmsg_response_tcp4, - sc_rc_recvmsg_response_tcp6 - ]. - - -%% ticket_cases() -> -%% []. - - - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -init_per_suite(Config) -> - Config. - -end_per_suite(_) -> - ok. - -init_per_testcase(_TC, Config) -> - Config. - -end_per_testcase(_TC, Config) -> - Config. - - - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% %% -%% API BASIC %% -%% %% -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -%% Basically open (create) and close an IPv4 UDP (dgram) socket. -%% With some extra checks... -api_b_open_and_close_udp4(suite) -> - []; -api_b_open_and_close_udp4(doc) -> - []; -api_b_open_and_close_udp4(_Config) when is_list(_Config) -> - tc_try(api_b_open_and_close_udp4, - fun() -> - InitState = #{domain => inet, - type => dgram, - protocol => udp}, - ok = api_b_open_and_close(InitState) - end). - - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -%% Basically open (create) and close an IPv4 TCP (stream) socket. -%% With some extra checks... -api_b_open_and_close_tcp4(suite) -> - []; -api_b_open_and_close_tcp4(doc) -> - []; -api_b_open_and_close_tcp4(_Config) when is_list(_Config) -> - tc_try(api_b_open_and_close_tcp4, - fun() -> - InitState = #{domain => inet, - type => stream, - protocol => tcp}, - ok = api_b_open_and_close(InitState) - end). - - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -api_b_open_and_close(InitState) -> - Seq = - [ - #{desc => "open", - cmd => fun(#{domain := Domain, - type := Type, - protocol := Protocol} = S) -> - Res = socket:open(Domain, Type, Protocol), - {ok, {S, Res}} - end}, - #{desc => "validate open", - cmd => fun({S, {ok, Sock}}) -> - NewS = S#{socket => Sock}, - {ok, NewS}; - ({_, {error, _} = ERROR}) -> - ERROR - end}, - #{desc => "get domain (maybe)", - cmd => fun(#{socket := Sock} = S) -> - Res = socket:getopt(Sock, socket, domain), - {ok, {S, Res}} - end}, - #{desc => "validate domain (maybe)", - cmd => fun({#{domain := Domain} = S, {ok, Domain}}) -> - {ok, S}; - ({#{domain := ExpDomain}, {ok, Domain}}) -> - {error, {unexpected_domain, ExpDomain, Domain}}; - %% Some platforms do not support this option - ({S, {error, einval}}) -> - {ok, S}; - ({_, {error, _} = ERROR}) -> - ERROR - end}, - #{desc => "get type", - cmd => fun(#{socket := Sock} = State) -> - Res = socket:getopt(Sock, socket, type), - {ok, {State, Res}} - end}, - #{desc => "validate type", - cmd => fun({#{type := Type} = State, {ok, Type}}) -> - {ok, State}; - ({#{type := ExpType}, {ok, Type}}) -> - {error, {unexpected_type, ExpType, Type}}; - ({_, {error, _} = ERROR}) -> - ERROR - end}, - #{desc => "get protocol", - cmd => fun(#{socket := Sock} = State) -> - Res = socket:getopt(Sock, socket, protocol), - {ok, {State, Res}} - end}, - #{desc => "validate protocol", - cmd => fun({#{protocol := Protocol} = State, {ok, Protocol}}) -> - {ok, State}; - ({#{protocol := ExpProtocol}, {ok, Protocol}}) -> - {error, {unexpected_type, ExpProtocol, Protocol}}; - ({_, {error, _} = ERROR}) -> - ERROR - end}, - #{desc => "get controlling-process", - cmd => fun(#{socket := Sock} = State) -> - Res = socket:getopt(Sock, otp, controlling_process), - {ok, {State, Res}} - end}, - #{desc => "validate controlling-process", - cmd => fun({State, {ok, Pid}}) -> - case self() of - Pid -> - {ok, State}; - _ -> - {error, {unexpected_owner, Pid}} - end; - ({_, {error, _} = ERROR}) -> - ERROR - end}, - #{desc => "close socket", - cmd => fun(#{socket := Sock} = State) -> - Res = socket:close(Sock), - {ok, {State, Res}} - end}, - #{desc => "validate socket close", - cmd => fun({_, ok}) -> - {ok, normal}; - ({_, {error, _} = ERROR}) -> - ERROR - end}], - Evaluator = evaluator_start("tester", Seq, InitState), - ok = await_evaluator_finish([Evaluator]). - - - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -%% Basically send and receive on an IPv4 UDP (dgram) socket using -%% sendto and recvfrom.. -api_b_sendto_and_recvfrom_udp4(suite) -> - []; -api_b_sendto_and_recvfrom_udp4(doc) -> - []; -api_b_sendto_and_recvfrom_udp4(_Config) when is_list(_Config) -> - tc_try(api_b_sendto_and_recvfrom_udp4, - fun() -> - Send = fun(Sock, Data, Dest) -> - socket:sendto(Sock, Data, Dest) - end, - Recv = fun(Sock) -> - socket:recvfrom(Sock) - end, - InitState = #{domain => inet, - send => Send, - recv => Recv}, - ok = api_b_send_and_recv_udp(InitState) - end). - - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -%% Basically send and receive on an IPv4 UDP (dgram) socket -%% using sendmsg and recvmsg. -api_b_sendmsg_and_recvmsg_udp4(suite) -> - []; -api_b_sendmsg_and_recvmsg_udp4(doc) -> - []; -api_b_sendmsg_and_recvmsg_udp4(_Config) when is_list(_Config) -> - tc_try(api_b_sendmsg_and_recvmsg_udp4, - fun() -> - Send = fun(Sock, Data, Dest) -> - %% CMsgHdr = #{level => ip, - %% type => tos, - %% data => reliability}, - %% CMsgHdrs = [CMsgHdr], - MsgHdr = #{addr => Dest, - %% ctrl => CMsgHdrs, - iov => [Data]}, - socket:sendmsg(Sock, MsgHdr) - end, - Recv = fun(Sock) -> - case socket:recvmsg(Sock) of - {ok, #{addr := Source, - iov := [Data]}} -> - {ok, {Source, Data}}; - {error, _} = ERROR -> - ERROR - end - end, - InitState = #{domain => inet, - send => Send, - recv => Recv}, - ok = api_b_send_and_recv_udp(InitState) - end). - - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -api_b_send_and_recv_udp(InitState) -> - Seq = - [ - #{desc => "local address", - cmd => fun(#{domain := Domain} = State) -> - LAddr = which_local_addr(Domain), - LSA = #{family => Domain, addr => LAddr}, - {ok, State#{lsa => LSA}} - end}, - #{desc => "open src socket", - cmd => fun(#{domain := Domain} = State) -> - Sock = sock_open(Domain, dgram, udp), - SASrc = sock_sockname(Sock), - {ok, State#{sock_src => Sock, sa_src => SASrc}} - end}, - #{desc => "bind src", - cmd => fun(#{sock_src := Sock, lsa := LSA}) -> - sock_bind(Sock, LSA), - ok - end}, - #{desc => "sockname src socket", - cmd => fun(#{sock_src := Sock} = State) -> - SASrc = sock_sockname(Sock), - %% ei("src sockaddr: ~p", [SASrc]), - {ok, State#{sa_src => SASrc}} - end}, - #{desc => "open dst socket", - cmd => fun(#{domain := Domain} = State) -> - Sock = sock_open(Domain, dgram, udp), - {ok, State#{sock_dst => Sock}} - end}, - #{desc => "bind dst", - cmd => fun(#{sock_dst := Sock, lsa := LSA}) -> - sock_bind(Sock, LSA), - ok - end}, - #{desc => "sockname dst socket", - cmd => fun(#{sock_dst := Sock} = State) -> - SADst = sock_sockname(Sock), - %% ei("dst sockaddr: ~p", [SADst]), - {ok, State#{sa_dst => SADst}} - end}, - #{desc => "send req (to dst)", - cmd => fun(#{sock_src := Sock, sa_dst := Dst, send := Send}) -> - ok = Send(Sock, ?BASIC_REQ, Dst) - end}, - #{desc => "recv req (from src)", - cmd => fun(#{sock_dst := Sock, sa_src := Src, recv := Recv}) -> - {ok, {Src, ?BASIC_REQ}} = Recv(Sock), - ok - end}, - #{desc => "send rep (to src)", - cmd => fun(#{sock_dst := Sock, sa_src := Src, send := Send}) -> - ok = Send(Sock, ?BASIC_REP, Src) - end}, - #{desc => "recv rep (from dst)", - cmd => fun(#{sock_src := Sock, sa_dst := Dst, recv := Recv}) -> - {ok, {Dst, ?BASIC_REP}} = Recv(Sock), - ok - end}, - #{desc => "close src socket", - cmd => fun(#{sock_src := Sock}) -> - ok = socket:close(Sock) - end}, - #{desc => "close dst socket", - cmd => fun(#{sock_dst := Sock}) -> - ok = socket:close(Sock), - {ok, normal} - end} - ], - Evaluator = evaluator_start("tester", Seq, InitState), - ok = await_evaluator_finish([Evaluator]). - - - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -%% Basically send and receive using the "common" functions (send and recv) -%% on an IPv4 TCP (stream) socket. -api_b_send_and_recv_tcp4(suite) -> - []; -api_b_send_and_recv_tcp4(doc) -> - []; -api_b_send_and_recv_tcp4(_Config) when is_list(_Config) -> - tc_try(api_b_send_and_recv_tcp4, - fun() -> - Send = fun(Sock, Data) -> - socket:send(Sock, Data) - end, - Recv = fun(Sock) -> - socket:recv(Sock) - end, - InitState = #{domain => inet, - send => Send, - recv => Recv}, - ok = api_b_send_and_recv_tcp(InitState) - end). - - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -%% Basically send and receive using the msg functions (sendmsg and recvmsg) -%% on an IPv4 TCP (stream) socket. -api_b_sendmsg_and_recvmsg_tcp4(suite) -> - []; -api_b_sendmsg_and_recvmsg_tcp4(doc) -> - []; -api_b_sendmsg_and_recvmsg_tcp4(_Config) when is_list(_Config) -> - tc_try(api_b_sendmsg_and_recvmsg_tcp4, - fun() -> - Send = fun(Sock, Data) -> - MsgHdr = #{iov => [Data]}, - socket:sendmsg(Sock, MsgHdr) - end, - Recv = fun(Sock) -> - case socket:recvmsg(Sock) of - {ok, #{addr := undefined, - iov := [Data]}} -> - {ok, Data}; - {error, _} = ERROR -> - ERROR - end - end, - InitState = #{domain => inet, - send => Send, - recv => Recv}, - ok = api_b_send_and_recv_tcp(InitState) - end). - - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -api_b_send_and_recv_tcp(InitState) -> - process_flag(trap_exit, true), - ServerSeq = - [ - #{desc => "which local address", - cmd => fun(#{domain := Domain} = State) -> - LAddr = which_local_addr(Domain), - LSA = #{family => Domain, addr => LAddr}, - {ok, State#{lsa => LSA}} - end}, - #{desc => "create listen socket", - cmd => fun(#{domain := Domain} = State) -> - case socket:open(Domain, stream, tcp) of - {ok, Sock} -> - {ok, State#{lsock => Sock}}; - {error, _} = ERROR -> - ERROR - end - end}, - #{desc => "bind to local address", - cmd => fun(#{lsock := LSock, lsa := LSA} = State) -> - case socket:bind(LSock, LSA) of - {ok, Port} -> - {ok, State#{lport => Port}}; - {error, _} = ERROR -> - ERROR - end - end}, - #{desc => "make listen socket", - cmd => fun(#{lsock := LSock}) -> - socket:listen(LSock) - end}, - #{desc => "announce server port", - cmd => fun(#{parent := Parent, lport := Port}) -> - ei("announcing port to parent (~p)", [Parent]), - Parent ! {server_port, self(), Port}, - ok - end}, - #{desc => "await connection", - cmd => fun(#{lsock := LSock} = State) -> - case socket:accept(LSock) of - {ok, Sock} -> - ei("accepted: ~n ~p", [Sock]), - {ok, State#{tsock => Sock}}; - {error, _} = ERROR -> - ERROR - end - end}, - #{desc => "await request", - cmd => fun(#{tsock := Sock, recv := Recv}) -> - case Recv(Sock) of - {ok, ?BASIC_REQ} -> - ok; - {error, _} = ERROR -> - ERROR - end - end}, - #{desc => "send reply", - cmd => fun(#{tsock := Sock, send := Send}) -> - Send(Sock, ?BASIC_REP) - end}, - #{desc => "sleep some", - cmd => fun(_) -> - ?SLEEP(1000), - ok - end}, - #{desc => "close traffic socket", - cmd => fun(#{tsock := Sock}) -> - socket:close(Sock) - end}, - #{desc => "close listen socket", - cmd => fun(#{lsock := Sock}) -> - socket:close(Sock) - end}, - #{desc => "finish", - cmd => fun(_) -> - {ok, normal} - end} - ], - - ClientSeq = - [ - #{desc => "which server (local) address", - cmd => fun(#{domain := Domain, server_port := Port} = State) -> - LAddr = which_local_addr(Domain), - LSA = #{family => Domain, - addr => LAddr}, - SSA = LSA#{port => Port}, - {ok, State#{lsa => LSA, ssa => SSA}} - end}, - #{desc => "create socket", - cmd => fun(#{domain := Domain} = State) -> - case socket:open(Domain, stream, tcp) of - {ok, Sock} -> - {ok, State#{sock => Sock}}; - {error, _} = ERROR -> - ERROR - end - end}, - #{desc => "bind to local address", - cmd => fun(#{sock := Sock, lsa := LSA} = State) -> - case socket:bind(Sock, LSA) of - {ok, Port} -> - {ok, State#{port => Port}}; - {error, _} = ERROR -> - ERROR - end - end}, - #{desc => "connect to server", - cmd => fun(#{sock := Sock, ssa := SSA}) -> - socket:connect(Sock, SSA) - end}, - #{desc => "send request (to server)", - cmd => fun(#{sock := Sock, send := Send}) -> - Send(Sock, ?BASIC_REQ) - end}, - #{desc => "recv reply (from server)", - cmd => fun(#{sock := Sock, recv := Recv}) -> - {ok, ?BASIC_REP} = Recv(Sock), - ok - end}, - #{desc => "close socket", - cmd => fun(#{sock := Sock}) -> - socket:close(Sock) - end}, - #{desc => "finish", - cmd => fun(_) -> - {ok, normal} - end} - ], - - p("start server evaluator"), - Server = evaluator_start("server", ServerSeq, InitState), - p("await server (~p) port", [Server]), - SPort = receive - {server_port, Server, Port} -> - Port - end, - p("start client evaluator"), - Client = evaluator_start("client", ClientSeq, InitState#{server_port => SPort}), - p("await evaluator(s)"), - ok = await_evaluator_finish([Server, Client]). - - - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% %% -%% API OPTIONS %% -%% %% -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -%% Perform some simple getopt and setopt with the level = otp options -api_opt_simple_otp_options(suite) -> - []; -api_opt_simple_otp_options(doc) -> - []; -api_opt_simple_otp_options(_Config) when is_list(_Config) -> - tc_try(api_opt_simple_otp_options, - fun() -> api_opt_simple_otp_options() end). - -api_opt_simple_otp_options() -> - Get = fun(S, Key) -> - socket:getopt(S, otp, Key) - end, - Set = fun(S, Key, Val) -> - socket:setopt(S, otp, Key, Val) - end, - - Seq = - [ - %% *** Init part *** - #{desc => "create socket", - cmd => fun(#{domain := Domain, - type := Type, - protocol := Protocol} = State) -> - Sock = sock_open(Domain, Type, Protocol), - {ok, State#{sock => Sock}} - end}, - #{desc => "create dummy process", - cmd => fun(State) -> - Pid = spawn_link(fun() -> - put(sname, "dummy"), - receive - die -> - exit(normal) - end - end), - {ok, State#{dummy => Pid}} - end}, - - %% *** Check iow part *** - #{desc => "get iow", - cmd => fun(#{sock := Sock} = State) -> - case Get(Sock, iow) of - {ok, IOW} when is_boolean(IOW) -> - {ok, State#{iow => IOW}}; - {ok, InvalidIOW} -> - {error, {invalid, InvalidIOW}}; - {error, _} = ERROR -> - ERROR - end - end}, - #{desc => "set (new) iow", - cmd => fun(#{sock := Sock, iow := OldIOW} = State) -> - NewIOW = not OldIOW, - case Set(Sock, iow, NewIOW) of - ok -> - {ok, State#{iow => NewIOW}}; - {error, _} = ERROR -> - ERROR - end - end}, - #{desc => "get (new) iow", - cmd => fun(#{sock := Sock, iow := IOW}) -> - case Get(Sock, iow) of - {ok, IOW} -> - ok; - {ok, InvalidIOW} -> - {error, {invalid, InvalidIOW}}; - {error, _} = ERROR -> - ERROR - end - end}, - - %% *** Check rcvbuf part *** - #{desc => "get rcvbuf", - cmd => fun(#{sock := Sock} = State) -> - case Get(Sock, rcvbuf) of - {ok, RcvBuf} when is_integer(RcvBuf) -> - {ok, State#{rcvbuf => RcvBuf}}; - {ok, InvalidRcvBuf} -> - {error, {invalid, InvalidRcvBuf}}; - {error, _} = ERROR -> - ERROR - end - end}, - #{desc => "set (new) rcvbuf", - cmd => fun(#{sock := Sock, rcvbuf := OldRcvBuf} = State) -> - NewRcvBuf = 2 * OldRcvBuf, - case Set(Sock, rcvbuf, NewRcvBuf) of - ok -> - {ok, State#{rcvbuf => NewRcvBuf}}; - {error, _} = ERROR -> - ERROR - end - end}, - #{desc => "get (new) rcvbuf", - cmd => fun(#{sock := Sock, rcvbuf := RcvBuf}) -> - case Get(Sock, rcvbuf) of - {ok, RcvBuf} -> - ok; - {ok, InvalidRcvBuf} -> - {error, {invalid, InvalidRcvBuf}}; - {error, _} = ERROR -> - ERROR - end - end}, - - %% *** Check rcvctrlbuf part *** - #{desc => "get rcvctrlbuf", - cmd => fun(#{sock := Sock} = State) -> - case Get(Sock, rcvctrlbuf) of - {ok, RcvCtrlBuf} when is_integer(RcvCtrlBuf) -> - {ok, State#{rcvctrlbuf => RcvCtrlBuf}}; - {ok, InvalidRcvCtrlBuf} -> - {error, {invalid, InvalidRcvCtrlBuf}}; - {error, _} = ERROR -> - ERROR - end - end}, - #{desc => "set (new) rcvctrlbuf", - cmd => fun(#{sock := Sock, rcvctrlbuf := OldRcvCtrlBuf} = State) -> - NewRcvCtrlBuf = 2 * OldRcvCtrlBuf, - case Set(Sock, rcvctrlbuf, NewRcvCtrlBuf) of - ok -> - {ok, State#{rcvctrlbuf => NewRcvCtrlBuf}}; - {error, _} = ERROR -> - ERROR - end - end}, - #{desc => "get (new) rcvctrlbuf", - cmd => fun(#{sock := Sock, rcvctrlbuf := RcvCtrlBuf}) -> - case Get(Sock, rcvctrlbuf) of - {ok, RcvCtrlBuf} -> - ok; - {ok, InvalidRcvCtrlBuf} -> - {error, {invalid, InvalidRcvCtrlBuf}}; - {error, _} = ERROR -> - ERROR - end - end}, - %% *** Check rcvctrlbuf part *** - #{desc => "get rcvctrlbuf", - cmd => fun(#{sock := Sock} = State) -> - case Get(Sock, rcvctrlbuf) of - {ok, RcvCtrlBuf} when is_integer(RcvCtrlBuf) -> - {ok, State#{rcvctrlbuf => RcvCtrlBuf}}; - {ok, InvalidRcvCtrlBuf} -> - {error, {invalid, InvalidRcvCtrlBuf}}; - {error, _} = ERROR -> - ERROR - end - end}, - #{desc => "set (new) rcvctrlbuf", - cmd => fun(#{sock := Sock, rcvctrlbuf := OldRcvCtrlBuf} = State) -> - NewRcvCtrlBuf = 2 * OldRcvCtrlBuf, - case Set(Sock, rcvctrlbuf, NewRcvCtrlBuf) of - ok -> - {ok, State#{rcvctrlbuf => NewRcvCtrlBuf}}; - {error, _} = ERROR -> - ERROR - end - end}, - #{desc => "get (new) rcvctrlbuf", - cmd => fun(#{sock := Sock, rcvctrlbuf := RcvCtrlBuf}) -> - case Get(Sock, rcvctrlbuf) of - {ok, RcvCtrlBuf} -> - ok; - {ok, InvalidRcvCtrlBuf} -> - {error, {invalid, InvalidRcvCtrlBuf}}; - {error, _} = ERROR -> - ERROR - end - end}, - - - %% *** Check sndctrlbuf part *** - #{desc => "get sndctrlbuf", - cmd => fun(#{sock := Sock} = State) -> - case Get(Sock, sndctrlbuf) of - {ok, SndCtrlBuf} when is_integer(SndCtrlBuf) -> - {ok, State#{sndctrlbuf => SndCtrlBuf}}; - {ok, InvalidSndCtrlBuf} -> - {error, {invalid, InvalidSndCtrlBuf}}; - {error, _} = ERROR -> - ERROR - end - end}, - #{desc => "set (new) sndctrlbuf", - cmd => fun(#{sock := Sock, sndctrlbuf := OldSndCtrlBuf} = State) -> - NewSndCtrlBuf = 2 * OldSndCtrlBuf, - case Set(Sock, sndctrlbuf, NewSndCtrlBuf) of - ok -> - {ok, State#{sndctrlbuf => NewSndCtrlBuf}}; - {error, _} = ERROR -> - ERROR - end - end}, - #{desc => "get (new) sndctrlbuf", - cmd => fun(#{sock := Sock, sndctrlbuf := SndCtrlBuf}) -> - case Get(Sock, sndctrlbuf) of - {ok, SndCtrlBuf} -> - ok; - {ok, InvalidSndCtrlBuf} -> - {error, {invalid, InvalidSndCtrlBuf}}; - {error, _} = ERROR -> - ERROR - end - end}, - - %% *** Check controlling-process part *** - #{desc => "verify self as controlling-process", - cmd => fun(#{sock := Sock}) -> - Self = self(), - case Get(Sock, controlling_process) of - {ok, Self} -> - ok; - {ok, InvalidPid} -> - {error, {invalid, InvalidPid}}; - {error, _} = ERROR -> - ERROR - end - end}, - #{desc => "set dummy as controlling-process", - cmd => fun(#{sock := Sock, dummy := Dummy}) -> - Set(Sock, controlling_process, Dummy) - end}, - #{desc => "verify dummy as controlling-process", - cmd => fun(#{sock := Sock, dummy := Dummy}) -> - case Get(Sock, controlling_process) of - {ok, Dummy} -> - ok; - {ok, InvalidPid} -> - {error, {invalid, InvalidPid}}; - {error, _} = ERROR -> - ERROR - end - end}, - - %% *** We are done *** - #{desc => "finish", - cmd => fun(_) -> - {ok, normal} - end} - ], - - p("Run test for stream/tcp socket"), - InitState1 = #{domain => inet, type => stream, protocol => tcp}, - Tester1 = evaluator_start("tcp-tester", Seq, InitState1), - p("await evaluator 1"), - ok = await_evaluator_finish([Tester1]), - - p("Run test for dgram/udp socket"), - InitState2 = #{domain => inet, type => dgram, protocol => udp}, - Tester2 = evaluator_start("udp-tester", Seq, InitState2), - p("await evaluator 2"), - ok = await_evaluator_finish([Tester2]). - - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -%% Perform some simple getopt and setopt with the level = otp options -api_opt_simple_otp_controlling_process(suite) -> - []; -api_opt_simple_otp_controlling_process(doc) -> - []; -api_opt_simple_otp_controlling_process(_Config) when is_list(_Config) -> - tc_try(api_opt_simple_otp_controlling_process, - fun() -> api_opt_simple_otp_controlling_process() end). - -api_opt_simple_otp_controlling_process() -> - Get = fun(S, Key) -> - socket:getopt(S, otp, Key) - end, - Set = fun(S, Key, Val) -> - socket:setopt(S, otp, Key, Val) - end, - - ClientSeq = - [ - %% *** Init part *** - #{desc => "await start", - cmd => fun(State) -> - receive - {start, Tester, Socket} -> - {ok, State#{tester => Tester, - sock => Socket}} - end - end}, - #{desc => "verify tester as controlling-process", - cmd => fun(#{tester := Tester, sock := Sock} = _State) -> - case Get(Sock, controlling_process) of - {ok, Tester} -> - ok; - {ok, InvalidPid} -> - {error, {invalid, InvalidPid}}; - {error, _} = ERROR -> - ERROR - end - end}, - #{desc => "attempt invalid controlling-process transfer (to self)", - cmd => fun(#{sock := Sock} = _State) -> - case Set(Sock, controlling_process, self()) of - {error, not_owner} -> - ok; - ok -> - {error, unexpected_success}; - {error, _} = ERROR -> - ERROR - end - end}, - #{desc => "announce ready (1)", - cmd => fun(#{tester := Tester} = _State) -> - Tester ! {ready, self()}, - ok - end}, - #{desc => "await continue", - cmd => fun(#{tester := Tester} = _State) -> - receive - {continue, Tester} -> - ok - end - end}, - #{desc => "verify self as controlling-process", - cmd => fun(#{sock := Sock} = _State) -> - Self = self(), - case Get(Sock, controlling_process) of - {ok, Self} -> - ok; - {ok, InvalidPid} -> - {error, {invalid, InvalidPid}}; - {error, _} = ERROR -> - ERROR - end - end}, - #{desc => "attempt controlling-process transfer to tester", - cmd => fun(#{tester := Tester, sock := Sock} = _State) -> - Set(Sock, controlling_process, Tester) - end}, - #{desc => "attempt invalid controlling-process transfer (to self)", - cmd => fun(#{sock := Sock} = _State) -> - case Set(Sock, controlling_process, self()) of - {error, not_owner} -> - ok; - ok -> - {error, unexpected_success}; - {error, _} = ERROR -> - ERROR - end - end}, - #{desc => "announce ready (2)", - cmd => fun(#{tester := Tester} = _State) -> - Tester ! {ready, self()}, - ok - end}, - #{desc => "await termination", - cmd => fun(#{tester := Tester} = State) -> - receive - {terminate, Tester} -> - State1 = maps:remove(tester, State), - State2 = maps:remove(sock, State1), - {ok, State2} - end - end}, - - %% *** We are done *** - #{desc => "finish", - cmd => fun(_) -> - {ok, normal} - end} - ], - - TesterSeq = - [ - %% *** Init part *** - #{desc => "create socket", - cmd => fun(#{domain := Domain, - type := Type, - protocol := Protocol} = State) -> - Sock = sock_open(Domain, Type, Protocol), - {ok, State#{sock => Sock}} - end}, - #{desc => "verify self as controlling-process", - cmd => fun(#{sock := Sock} = _State) -> - Self = self(), - case Get(Sock, controlling_process) of - {ok, Self} -> - ok; - {ok, InvalidPid} -> - {error, {invalid, InvalidPid}}; - {error, _} = ERROR -> - ERROR - end - end}, - #{desc => "order (client) start", - cmd => fun(#{client := Client, sock := Sock} = _State) -> - Client ! {start, self(), Sock}, - ok - end}, - #{desc => "await (client) ready (1)", - cmd => fun(#{client := Client} = _State) -> - receive - {ready, Client} -> - ok - end - end}, - #{desc => "attempt controlling-process transfer to client", - cmd => fun(#{client := Client, sock := Sock} = _State) -> - Set(Sock, controlling_process, Client) - end}, - #{desc => "verify client as controlling-process", - cmd => fun(#{client := Client, sock := Sock} = _State) -> - case Get(Sock, controlling_process) of - {ok, Client} -> - ok; - {ok, InvalidPid} -> - {error, {invalid, InvalidPid}}; - {error, _} = ERROR -> - ERROR - end - end}, - #{desc => "attempt invalid controlling-process transfer (to self)", - cmd => fun(#{sock := Sock} = _State) -> - case Set(Sock, controlling_process, self()) of - {error, not_owner} -> - ok; - ok -> - {error, unexpected_success}; - {error, _} = ERROR -> - ERROR - end - end}, - #{desc => "order (client) continue", - cmd => fun(#{client := Client} = _State) -> - Client ! {continue, self()}, - ok - end}, - #{desc => "await (client) ready (2)", - cmd => fun(#{client := Client} = _State) -> - receive - {ready, Client} -> - ok - end - end}, - #{desc => "verify self as controlling-process", - cmd => fun(#{sock := Sock} = _State) -> - Self = self(), - case Get(Sock, controlling_process) of - {ok, Self} -> - ok; - {ok, InvalidPid} -> - {error, {invalid, InvalidPid}}; - {error, _} = ERROR -> - ERROR - end - end}, - #{desc => "monitor client", - cmd => fun(#{client := Client} = State) -> - MRef = erlang:monitor(process, Client), - {ok, State#{client_mref => MRef}} - end}, - #{desc => "order (client) terminate", - cmd => fun(#{client := Client} = _State) -> - Client ! {terminate, self()}, - ok - end}, - #{desc => "await (client) down", - cmd => fun(#{client := Client} = State) -> - receive - {'DOWN', _, process, Client, _} -> - {ok, maps:remove(client, State)} - end - end}, - #{desc => "close socket", - cmd => fun(#{sock := Sock} = State) -> - sock_close(Sock), - {ok, maps:remove(sock, State)} - end}, - - %% *** We are done *** - #{desc => "finish", - cmd => fun(_) -> - {ok, normal} - end} - ], - - p("Run test for stream/tcp socket"), - ClientInitState1 = #{}, - Client1 = evaluator_start("tcp-client", ClientSeq, ClientInitState1), - TesterInitState1 = #{domain => inet, - type => stream, - protocol => tcp, - client => Client1}, - Tester1 = evaluator_start("tcp-tester", TesterSeq, TesterInitState1), - p("await stream/tcp evaluator"), - ok = await_evaluator_finish([Tester1, Client1]), - - p("Run test for dgram/udp socket"), - ClientInitState2 = #{}, - Client2 = evaluator_start("udp-client", ClientSeq, ClientInitState2), - TesterInitState2 = #{domain => inet, - type => dgram, - protocol => udp, - client => Client2}, - Tester2 = evaluator_start("udp-tester", TesterSeq, TesterInitState2), - p("await dgram/udp evaluator"), - ok = await_evaluator_finish([Tester2, Client2]). - - - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% %% -%% API OPERATIONS WITH TIMEOUT %% -%% %% -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -%% This test case is intended to test the connect timeout option -%% on an IPv4 TCP (stream) socket. -api_to_connect_tcp4(suite) -> - []; -api_to_connect_tcp4(doc) -> - []; -api_to_connect_tcp4(_Config) when is_list(_Config) -> - tc_try(api_to_connect_tcp4, - fun() -> - InitState = #{domain => inet, timeout => 5000}, - ok = api_to_connect_tcp(InitState) - end). - - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -%% This test case is intended to test the connect timeout option -%% on an IPv6 TCP (stream) socket. -api_to_connect_tcp6(suite) -> - []; -api_to_connect_tcp6(doc) -> - []; -api_to_connect_tcp6(_Config) when is_list(_Config) -> - tc_try(api_to_connect_tcp6, - fun() -> - not_yet_implemented(), - InitState = #{domain => inet6, timeout => 5000}, - ok = api_to_connect_tcp(InitState) - end). - - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% We use the backlog (listen) argument to test this. -%% Note that the behaviour of the TCP "server side" can vary when -%% a client connect to a "busy" server (full backlog). -%% For instance, on FreeBSD (11.2) the reponse when the backlog is full -%% is a econreset. - -api_to_connect_tcp(InitState) -> - process_flag(trap_exit, true), - - ServerSeq = - [ - %% *** Wait for start order part *** - #{desc => "await start (from tester)", - cmd => fun(State) -> - receive - {start, Tester} when is_pid(Tester) -> - {ok, State#{tester => Tester}} - end - end}, - - %% *** Init part *** - #{desc => "which local address", - cmd => fun(#{domain := Domain} = State) -> - LAddr = which_local_addr(Domain), - LSA = #{family => Domain, addr => LAddr}, - {ok, State#{lsa => LSA}} - end}, - #{desc => "create listen socket", - cmd => fun(#{domain := Domain} = State) -> - case socket:open(Domain, stream, tcp) of - {ok, Sock} -> - {ok, State#{lsock => Sock}}; - {error, _} = ERROR -> - ERROR - end - end}, - #{desc => "bind to local address", - cmd => fun(#{lsock := LSock, lsa := LSA} = State) -> - case socket:bind(LSock, LSA) of - {ok, Port} -> - {ok, State#{lport => Port}}; - {error, _} = ERROR -> - ERROR - end - end}, - #{desc => "make listen socket (with backlog = 1)", - cmd => fun(#{lsock := LSock}) -> - socket:listen(LSock, 1) - end}, - #{desc => "monitor server", - cmd => fun(#{tester := Tester} = State) -> - MRef = erlang:monitor(process, Tester), - {ok, State#{tester_mref => MRef}} - end}, - #{desc => "announce ready", - cmd => fun(#{tester := Tester, lport := Port}) -> - ei("announcing ready to tester (~p)", [Tester]), - Tester ! {ready, self(), Port}, - ok - end}, - #{desc => "await terminate (from tester)", - cmd => fun(#{tester := Tester} = State) -> - receive - {'DOWN', _, process, Tester, Reason} -> - ee("Unexpected DOWN regarding tester ~p: " - "~n ~p", [Reason]), - {error, {unexpected_exit, tester}}; - {terminate, Tester} -> - {ok, maps:remove(tester, State)} - end - end}, - - %% *** We are done *** - #{desc => "finish", - cmd => fun(_) -> - {ok, normal} - end} - ], - - TesterSeq = - [ - %% *** Init part *** - #{desc => "which local address", - cmd => fun(#{domain := Domain} = State) -> - LAddr = which_local_addr(Domain), - LSA = #{family => Domain, addr => LAddr}, - {ok, State#{lsa => LSA}} - end}, - #{desc => "create socket 1", - cmd => fun(#{domain := Domain} = State) -> - case socket:open(Domain, stream, tcp) of - {ok, Sock} -> - {ok, State#{sock1 => Sock}}; - {error, _} = ERROR -> - ERROR - end - end}, - #{desc => "create socket 2", - cmd => fun(#{domain := Domain} = State) -> - case socket:open(Domain, stream, tcp) of - {ok, Sock} -> - {ok, State#{sock2 => Sock}}; - {error, _} = ERROR -> - ERROR - end - end}, - #{desc => "create socket 3", - cmd => fun(#{domain := Domain} = State) -> - case socket:open(Domain, stream, tcp) of - {ok, Sock} -> - {ok, State#{sock3 => Sock}}; - {error, _} = ERROR -> - ERROR - end - end}, - #{desc => "bind socket 1 to local address", - cmd => fun(#{sock1 := Sock, lsa := LSA} = _State) -> - case socket:bind(Sock, LSA) of - {ok, _} -> - ok; - {error, _} = ERROR -> - ERROR - end - end}, - #{desc => "bind socket 2 to local address", - cmd => fun(#{sock2 := Sock, lsa := LSA} = _State) -> - case socket:bind(Sock, LSA) of - {ok, _} -> - ok; - {error, _} = ERROR -> - ERROR - end - end}, - #{desc => "bind socket 3 to local address", - cmd => fun(#{sock3 := Sock, lsa := LSA} = _State) -> - case socket:bind(Sock, LSA) of - {ok, _} -> - ok; - {error, _} = ERROR -> - ERROR - end - end}, - - %% *** Synchronize with the server *** - #{desc => "order (server) start", - cmd => fun(#{server := Server}) -> - Server ! {start, self()}, - ok - end}, - #{desc => "await ready (from server)", - cmd => fun(#{server := Server, lsa := LSA} = State) -> - receive - {ready, Server, Port} -> - {ok, State#{ssa => LSA#{port => Port}}} - end - end}, - - %% *** Connect sequence *** - #{desc => "order (server) start", - cmd => fun(#{sock1 := Sock1, - sock2 := Sock2, - sock3 := Sock3, - ssa := SSA, - timeout := To}) -> - Socks = [Sock1, Sock2, Sock3], - api_to_connect_tcp_await_timeout(Socks, To, SSA) - end}, - - %% *** Terminate server *** - #{desc => "monitor server", - cmd => fun(#{server := Server} = State) -> - MRef = erlang:monitor(process, Server), - {ok, State#{server_mref => MRef}} - end}, - #{desc => "order (server) terminate", - cmd => fun(#{server := Server} = _State) -> - Server ! {terminate, self()}, - ok - end}, - #{desc => "await (server) down", - cmd => fun(#{server := Server} = State) -> - receive - {'DOWN', _, process, Server, _} -> - State1 = maps:remove(server, State), - State2 = maps:remove(ssa, State1), - {ok, State2} - end - end}, - - %% *** Close our sockets *** - #{desc => "close socket 3", - cmd => fun(#{sock3 := Sock} = State) -> - sock_close(Sock), - {ok, maps:remove(sock3, State)} - - end}, - #{desc => "close socket 2", - cmd => fun(#{sock2 := Sock} = State) -> - sock_close(Sock), - {ok, maps:remove(sock2, State)} - - end}, - #{desc => "close socket 1", - cmd => fun(#{sock1 := Sock} = State) -> - sock_close(Sock), - {ok, maps:remove(sock1, State)} - - end}, - - %% *** We are done *** - #{desc => "finish", - cmd => fun(_) -> - {ok, normal} - end} - ], - - p("create server evaluator"), - ServerInitState = InitState, - Server = evaluator_start("server", ServerSeq, ServerInitState), - - p("create tester evaluator"), - TesterInitState = InitState#{server => Server}, - Tester = evaluator_start("tester", TesterSeq, TesterInitState), - - p("await evaluator(s)"), - ok = await_evaluator_finish([Server, Tester]). - - -api_to_connect_tcp_await_timeout(Socks, To, ServerSA) -> - api_to_connect_tcp_await_timeout(Socks, To, ServerSA, 1). - -api_to_connect_tcp_await_timeout([], _To, _ServerSA, _ID) -> - ?FAIL(unexpected_success); -api_to_connect_tcp_await_timeout([Sock|Socks], To, ServerSA, ID) -> - ei("~w: try connect", [ID]), - Start = t(), - case socket:connect(Sock, ServerSA, To) of - {error, timeout} -> - ei("expected timeout (~w)", [ID]), - Stop = t(), - TDiff = tdiff(Start, Stop), - if - (TDiff >= To) -> - ok; - true -> - {error, {unexpected_timeout, TDiff, To}} - end; - {error, econnreset = Reason} -> - ei("failed connecting: ~p - giving up", [Reason]), - ok; - {error, Reason} -> - ee("failed connecting: ~p", [Reason]), - ?FAIL({connect, Reason}); - ok -> - ei("unexpected success (~w) - try next", [ID]), - api_to_connect_tcp_await_timeout(Socks, To, ServerSA, ID+1) - end. - - - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -%% This test case is intended to test the accept timeout option -%% on an IPv4 TCP (stream) socket. -api_to_accept_tcp4(suite) -> - []; -api_to_accept_tcp4(doc) -> - []; -api_to_accept_tcp4(_Config) when is_list(_Config) -> - tc_try(api_to_accept_tcp4, - fun() -> - InitState = #{domain => inet, timeout => 5000}, - ok = api_to_accept_tcp(InitState) - end). - - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -%% This test case is intended to test the accept timeout option -%% on an IPv6 TCP (stream) socket. -api_to_accept_tcp6(suite) -> - []; -api_to_accept_tcp6(doc) -> - []; -api_to_accept_tcp6(_Config) when is_list(_Config) -> - tc_try(api_to_accept_tcp4, - fun() -> - not_yet_implemented(), - InitState = #{domain => inet6, timeout => 5000}, - ok = api_to_accept_tcp(InitState) - end). - - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -api_to_accept_tcp(InitState) -> - TesterSeq = - [ - %% *** Init part *** - #{desc => "which local address", - cmd => fun(#{domain := Domain} = State) -> - LAddr = which_local_addr(Domain), - LSA = #{family => Domain, addr => LAddr}, - {ok, State#{lsa => LSA}} - end}, - #{desc => "create (listen) socket", - cmd => fun(#{domain := Domain} = State) -> - case socket:open(Domain, stream, tcp) of - {ok, Sock} -> - {ok, State#{lsock => Sock}}; - {error, _} = ERROR -> - ERROR - end - end}, - #{desc => "bind to local address", - cmd => fun(#{lsock := LSock, lsa := LSA} = _State) -> - case socket:bind(LSock, LSA) of - {ok, _} -> - ok; - {error, _} = ERROR -> - ERROR - end - end}, - #{desc => "make listen socket", - cmd => fun(#{lsock := LSock}) -> - socket:listen(LSock) - end}, - - %% *** The actual test part *** - #{desc => "attempt to accept (without success)", - cmd => fun(#{lsock := LSock, timeout := To} = State) -> - Start = t(), - case socket:accept(LSock, To) of - {error, timeout} -> - {ok, State#{start => Start, stop => t()}}; - {ok, Sock} -> - (catch socket:close(Sock)), - {error, unexpected_success}; - {error, _} = ERROR -> - ERROR - end - end}, - #{desc => "validate timeout time", - cmd => fun(#{start := Start, stop := Stop, timeout := To} = _State) -> - TDiff = tdiff(Start, Stop), - if - (TDiff >= To) -> - ok; - true -> - {error, {unexpected_timeout, TDiff, To}} - end - end}, - - %% *** Close (listen) socket *** - #{desc => "close (listen) socket", - cmd => fun(#{lsock := LSock} = State) -> - sock_close(LSock), - {ok, maps:remove(sock3, State)} - end}, - - %% *** We are done *** - #{desc => "finish", - cmd => fun(_) -> - {ok, normal} - end} - ], - - p("create tester evaluator"), - Tester = evaluator_start("tester", TesterSeq, InitState), - - p("await evaluator"), - ok = await_evaluator_finish([Tester]). - - - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -%% This test case is intended to test the multi accept timeout option -%% on an IPv4 TCP (stream) socket with multiple acceptor processes -%% (three in this case). -api_to_maccept_tcp4(suite) -> - []; -api_to_maccept_tcp4(doc) -> - []; -api_to_maccept_tcp4(_Config) when is_list(_Config) -> - tc_try(api_to_maccept_tcp4, - fun() -> - InitState = #{domain => inet, timeout => 5000}, - ok = api_to_maccept_tcp(InitState) - end). - - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -%% This test case is intended to test the accept timeout option -%% on an IPv6 TCP (stream) socket. -api_to_maccept_tcp6(suite) -> - []; -api_to_maccept_tcp6(doc) -> - []; -api_to_maccept_tcp6(_Config) when is_list(_Config) -> - tc_try(api_to_maccept_tcp4, - fun() -> - not_yet_implemented(), - InitState = #{domain => inet6, timeout => 5000}, - ok = api_to_maccept_tcp(InitState) - end). - - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -api_to_maccept_tcp(InitState) -> - PrimAcceptorSeq = - [ - %% *** Init part *** - #{desc => "await start", - cmd => fun(State) -> - receive - {start, Tester} -> - MRef = erlang:monitor(process, Tester), - {ok, State#{tester => Tester, - tester_mref => MRef}} - end - end}, - #{desc => "which local address", - cmd => fun(#{domain := Domain} = State) -> - LAddr = which_local_addr(Domain), - LSA = #{family => Domain, addr => LAddr}, - {ok, State#{lsa => LSA}} - end}, - #{desc => "create (listen) socket", - cmd => fun(#{domain := Domain} = State) -> - case socket:open(Domain, stream, tcp) of - {ok, Sock} -> - {ok, State#{lsock => Sock}}; - {error, _} = ERROR -> - ERROR - end - end}, - #{desc => "bind to local address", - cmd => fun(#{lsock := LSock, lsa := LSA} = _State) -> - case socket:bind(LSock, LSA) of - {ok, _} -> - ok; - {error, _} = ERROR -> - ERROR - end - end}, - #{desc => "make listen socket", - cmd => fun(#{lsock := LSock}) -> - socket:listen(LSock) - end}, - - #{desc => "announce ready", - cmd => fun(#{lsock := LSock, tester := Tester}) -> - ei("announcing port to tester (~p)", [Tester]), - Tester ! {ready, self(), LSock}, - ok - end}, - #{desc => "await continue", - cmd => fun(#{tester := Tester} = _State) -> - receive - {'DOWN', _, process, Tester, Reason} -> - ee("Unexpected DOWN regarding tester ~p: " - "~n ~p", [Reason]), - {error, {unexpected_exit, tester}}; - {continue, Tester} -> - ok - end - end}, - - %% *** The actual test part *** - #{desc => "attempt to accept (without success)", - cmd => fun(#{lsock := LSock, timeout := To} = State) -> - Start = t(), - case socket:accept(LSock, To) of - {error, timeout} -> - {ok, State#{start => Start, stop => t()}}; - {ok, Sock} -> - (catch socket:close(Sock)), - {error, unexpected_success}; - {error, _} = ERROR -> - ERROR - end - end}, - #{desc => "validate timeout time", - cmd => fun(#{start := Start, stop := Stop, timeout := To} = _State) -> - TDiff = tdiff(Start, Stop), - if - (TDiff >= To) -> - ok; - true -> - {error, {unexpected_timeout, TDiff, To}} - end - end}, - #{desc => "announce ready", - cmd => fun(#{tester := Tester}) -> - ei("announcing port to tester (~p)", [Tester]), - Tester ! {ready, self()}, - ok - end}, - #{desc => "await terminate", - cmd => fun(#{tester := Tester} = _State) -> - receive - {'DOWN', _, process, Tester, Reason} -> - ee("Unexpected DOWN regarding tester ~p: " - "~n ~p", [Reason]), - {error, {unexpected_exit, tester}}; - {terminate, Tester} -> - ok - end - end}, - - %% *** Close (listen) socket *** - #{desc => "close (listen) socket", - cmd => fun(#{lsock := LSock} = State) -> - sock_close(LSock), - {ok, maps:remove(lsock, State)} - end}, - - %% *** We are done *** - #{desc => "finish", - cmd => fun(_) -> - {ok, normal} - end} - ], - - - SecAcceptorSeq = - [ - %% *** Init part *** - #{desc => "await start", - cmd => fun(State) -> - receive - {start, Tester, LSock} -> - MRef = erlang:monitor(process, Tester), - {ok, State#{tester => Tester, - lsock => LSock, - tester_mref => MRef}} - end - end}, - #{desc => "announce ready (1)", - cmd => fun(#{tester := Tester} = _State) -> - Tester ! {ready, self()}, - ok - end}, - #{desc => "await continue", - cmd => fun(#{tester := Tester} = _State) -> - receive - {'DOWN', _, process, Tester, Reason} -> - ee("Unexpected DOWN regarding tester ~p: " - "~n ~p", [Reason]), - {error, {unexpected_exit, tester, Reason}}; - {continue, Tester} -> - ok - end - end}, - - %% *** The actual test part *** - #{desc => "attempt to accept (without success)", - cmd => fun(#{lsock := LSock, timeout := To} = State) -> - Start = t(), - case socket:accept(LSock, To) of - {error, timeout} -> - {ok, State#{start => Start, stop => t()}}; - {ok, Sock} -> - (catch socket:close(Sock)), - {error, unexpected_success}; - {error, _} = ERROR -> - ERROR - end - end}, - #{desc => "validate timeout time", - cmd => fun(#{start := Start, stop := Stop, timeout := To} = _State) -> - TDiff = tdiff(Start, Stop), - if - (TDiff >= To) -> - ok; - true -> - {error, {unexpected_timeout, TDiff, To}} - end - end}, - #{desc => "announce ready (2)", - cmd => fun(#{tester := Tester} = _State) -> - Tester ! {ready, self()}, - ok - end}, - #{desc => "await terminate", - cmd => fun(#{tester := Tester} = _State) -> - receive - {'DOWN', _, process, Tester, Reason} -> - ee("Unexpected DOWN regarding tester ~p: " - "~n ~p", [Reason]), - {error, {unexpected_exit, tester, Reason}}; - {terminate, Tester} -> - ok - end - end}, - - %% *** We are done *** - #{desc => "finish", - cmd => fun(_) -> - {ok, normal} - end} - ], - - - TesterSeq = - [ - %% Init part - #{desc => "monitor prim-acceptor", - cmd => fun(#{prim_acceptor := Pid} = _State) -> - _MRef = erlang:monitor(process, Pid), - ok - end}, - #{desc => "monitor sec-acceptor 1", - cmd => fun(#{sec_acceptor1 := Pid} = _State) -> - _MRef = erlang:monitor(process, Pid), - ok - end}, - #{desc => "monitor sec-acceptor 2", - cmd => fun(#{sec_acceptor2 := Pid} = _State) -> - _MRef = erlang:monitor(process, Pid), - ok - end}, - - - %% Start the prim-acceptor - #{desc => "start prim-acceptor", - cmd => fun(#{prim_acceptor := Pid} = _State) -> - Pid ! {start, self()}, - ok - end}, - #{desc => "await prim-acceptor ready (1)", - cmd => fun(#{prim_acceptor := Pid} = State) -> - receive - {'DOWN', _, process, Pid, Reason} -> - ee("Unexpected DOWN regarding prim-acceptor ~p:" - "~n ~p", [Reason]), - {error, {unexpected_exit, prim_acceptor}}; - {ready, Pid, LSock} -> - {ok, State#{lsock => LSock}} - end - end}, - - %% Start sec-acceptor-1 - #{desc => "start sec-acceptor 1", - cmd => fun(#{sec_acceptor1 := Pid, lsock := LSock} = _State) -> - Pid ! {start, self(), LSock}, - ok - end}, - #{desc => "await sec-acceptor 1 ready (1)", - cmd => fun(#{sec_acceptor1 := Pid} = _State) -> - receive - {'DOWN', _, process, Pid, Reason} -> - ee("Unexpected DOWN regarding sec-acceptor 1 ~p:" - "~n ~p", [Reason]), - {error, {unexpected_exit, sec_acceptor_1}}; - {ready, Pid} -> - ok - end - end}, - - %% Start sec-acceptor-2 - #{desc => "start sec-acceptor 2", - cmd => fun(#{sec_acceptor2 := Pid, lsock := LSock} = _State) -> - Pid ! {start, self(), LSock}, - ok - end}, - #{desc => "await sec-acceptor 2 ready (1)", - cmd => fun(#{sec_acceptor2 := Pid} = _State) -> - receive - {'DOWN', _, process, Pid, Reason} -> - ee("Unexpected DOWN regarding sec-acceptor 2 ~p:" - "~n ~p", [Reason]), - {error, {unexpected_exit, sec_acceptor_2}}; - {ready, Pid} -> - ok - end - end}, - - %% Activate the acceptor(s) - #{desc => "active prim-acceptor", - cmd => fun(#{prim_acceptor := Pid} = _State) -> - Pid ! {continue, self()}, - ok - end}, - #{desc => "active sec-acceptor 1", - cmd => fun(#{sec_acceptor1 := Pid} = _State) -> - Pid ! {continue, self()}, - ok - end}, - #{desc => "active sec-acceptor 2", - cmd => fun(#{sec_acceptor2 := Pid} = _State) -> - Pid ! {continue, self()}, - ok - end}, - - %% Await acceptor(s) completions - #{desc => "await prim-acceptor ready (2)", - cmd => fun(#{prim_acceptor := Pid} = _State) -> - receive - {'DOWN', _, process, Pid, Reason} -> - ee("Unexpected DOWN regarding prim-acceptor ~p:" - "~n ~p", [Reason]), - {error, {unexpected_exit, prim_acceptor}}; - {ready, Pid} -> - ok - end - end}, - #{desc => "await sec-acceptor 1 ready (2)", - cmd => fun(#{sec_acceptor1 := Pid} = _State) -> - receive - {'DOWN', _, process, Pid, Reason} -> - ee("Unexpected DOWN regarding sec-acceptor 1 ~p:" - "~n ~p", [Reason]), - {error, {unexpected_exit, sec_acceptor_1}}; - {ready, Pid} -> - ok - end - end}, - #{desc => "await sec-acceptor 2 ready (2)", - cmd => fun(#{sec_acceptor2 := Pid} = _State) -> - receive - {'DOWN', _, process, Pid, Reason} -> - ee("Unexpected DOWN regarding sec-acceptor 2 ~p:" - "~n ~p", [Reason]), - {error, {unexpected_exit, sec_acceptor_2}}; - {ready, Pid} -> - ok - end - end}, - - - %% Terminate the acceptor(s) - #{desc => "order prim-acceptor to terminate", - cmd => fun(#{prim_acceptor := Pid} = _State) -> - ei("send terminate command to prim-acceptor (~p)", [Pid]), - Pid ! {terminate, self()}, - ok - end}, - #{desc => "order sec-acceptor 1 to terminate", - cmd => fun(#{sec_acceptor1 := Pid} = _State) -> - ei("send terminate command to sec-acceptor-1 (~p)", [Pid]), - Pid ! {terminate, self()}, - ok - end}, - #{desc => "order sec-acceptor 2 to terminate", - cmd => fun(#{sec_acceptor2 := Pid} = _State) -> - ei("send terminate command to sec-acceptor-2 (~p)", [Pid]), - Pid ! {terminate, self()}, - ok - end}, - - %% Await acceptor(s) termination - #{desc => "await prim-acceptor termination", - cmd => fun(#{prim_acceptor := Pid} = State) -> - receive - {'DOWN', _, process, Pid, _} -> - State1 = maps:remove(prim_acceptor, State), - {ok, State1} - end - end}, - #{desc => "await sec-acceptor 1 termination", - cmd => fun(#{sec_acceptor1 := Pid} = State) -> - receive - {'DOWN', _, process, Pid, _} -> - State1 = maps:remove(sec_acceptor1, State), - {ok, State1} - end - end}, - #{desc => "await sec-acceptor 2 termination", - cmd => fun(#{sec_acceptor2 := Pid} = State) -> - receive - {'DOWN', _, process, Pid, _} -> - State1 = maps:remove(sec_acceptor2, State), - {ok, State1} - end - end}, - - %% *** We are done *** - #{desc => "finish", - cmd => fun(_) -> - {ok, normal} - end} - ], - - p("create prim-acceptor evaluator"), - PrimAInitState = InitState, - PrimAcceptor = evaluator_start("prim-acceptor", - PrimAcceptorSeq, PrimAInitState), - - p("create prim-acceptor 1 evaluator"), - SecAInitState1 = maps:remove(domain, InitState), - SecAcceptor1 = evaluator_start("sec-acceptor-1", - SecAcceptorSeq, SecAInitState1), - - p("create prim-acceptor 2 evaluator"), - SecAInitState2 = SecAInitState1, - SecAcceptor2 = evaluator_start("sec-acceptor-2", - SecAcceptorSeq, SecAInitState2), - - p("create tester evaluator"), - TesterInitState = #{prim_acceptor => PrimAcceptor, - sec_acceptor1 => SecAcceptor1, - sec_acceptor2 => SecAcceptor2}, - Tester = evaluator_start("tester", TesterSeq, TesterInitState), - - p("await evaluator(s)"), - ok = await_evaluator_finish([PrimAcceptor, SecAcceptor1, SecAcceptor2, Tester]). - - - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -%% This test case is intended to test the send timeout option -%% on an IPv4 TCP (stream) socket. -api_to_send_tcp4(suite) -> - []; -api_to_send_tcp4(doc) -> - []; -api_to_send_tcp4(_Config) when is_list(_Config) -> - tc_try(api_to_send_tcp4, - fun() -> - not_yet_implemented()%% , - %% ok = api_to_send_tcp(inet) - end). - - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -%% This test case is intended to test the send timeout option -%% on an IPv6 TCP (stream) socket. -api_to_send_tcp6(suite) -> - []; -api_to_send_tcp6(doc) -> - []; -api_to_send_tcp6(_Config) when is_list(_Config) -> - tc_try(api_to_send_tcp6, - fun() -> - not_yet_implemented()%% , - %% ok = api_to_send_tcp(inet6) - end). - - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -%% This test case is intended to test the sendto timeout option -%% on an IPv4 UDP (dgram) socket. -api_to_sendto_udp4(suite) -> - []; -api_to_sendto_udp4(doc) -> - []; -api_to_sendto_udp4(_Config) when is_list(_Config) -> - tc_try(api_to_sendto_udp4, - fun() -> - not_yet_implemented()%% , - %% ok = api_to_sendto_to_udp(inet) - end). - - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -%% This test case is intended to test the sendto timeout option -%% on an IPv6 UDP (dgram) socket. -api_to_sendto_udp6(suite) -> - []; -api_to_sendto_udp6(doc) -> - []; -api_to_sendto_udp6(_Config) when is_list(_Config) -> - tc_try(api_to_sendto_udp6, - fun() -> - not_yet_implemented()%% , - %% ok = api_to_sendto_to_udp(inet6) - end). - - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -%% This test case is intended to test the sendmsg timeout option -%% on an IPv4 TCP (stream) socket. -api_to_sendmsg_tcp4(suite) -> - []; -api_to_sendmsg_tcp4(doc) -> - []; -api_to_sendmsg_tcp4(_Config) when is_list(_Config) -> - tc_try(api_to_sendmsg_tcp4, - fun() -> - not_yet_implemented()%% , - %% ok = api_to_sendmsg_tcp(inet) - end). - - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -%% This test case is intended to test the sendmsg timeout option -%% on an IPv6 TCP (stream) socket. -api_to_sendmsg_tcp6(suite) -> - []; -api_to_sendmsg_tcp6(doc) -> - []; -api_to_sendmsg_tcp6(_Config) when is_list(_Config) -> - tc_try(api_to_sendmsg_tcp6, - fun() -> - not_yet_implemented()%% , - %% ok = api_to_sendmsg_tcp(inet6) - end). - - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -%% This test case is intended to test the recv timeout option -%% on an IPv4 UDP (dgram) socket. To test this we must connect -%% the socket. -api_to_recv_udp4(suite) -> - []; -api_to_recv_udp4(doc) -> - []; -api_to_recv_udp4(_Config) when is_list(_Config) -> - tc_try(api_to_recv_udp4, - fun() -> - not_yet_implemented()%%, - %%ok = api_to_recv_udp(inet) - end). - - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -%% This test case is intended to test the recv timeout option -%% on an IPv6 UDP (dgram) socket. To test this we must connect -%% the socket. -api_to_recv_udp6(suite) -> - []; -api_to_recv_udp6(doc) -> - []; -api_to_recv_udp6(_Config) when is_list(_Config) -> - tc_try(api_to_recv_udp6, - fun() -> - not_yet_implemented()%% , - %% ok = api_to_recv_udp(inet6) - end). - - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -%% This test case is intended to test the recv timeout option -%% on an IPv4 TCP (stream) socket. -api_to_recv_tcp4(suite) -> - []; -api_to_recv_tcp4(doc) -> - []; -api_to_recv_tcp4(_Config) when is_list(_Config) -> - tc_try(api_to_recv_tcp4, - fun() -> - Recv = fun(Sock, To) -> socket:recv(Sock, 0, To) end, - InitState = #{domain => inet, - recv => Recv, - timeout => 5000}, - ok = api_to_receive_tcp(InitState) - end). - - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -%% This test case is intended to test the recv timeout option -%% on an IPv6 TCP (stream) socket. -api_to_recv_tcp6(suite) -> - []; -api_to_recv_tcp6(doc) -> - []; -api_to_recv_tcp6(_Config) when is_list(_Config) -> - tc_try(api_to_recv_tcp6, - fun() -> - not_yet_implemented(), - case socket:supports(ipv6) of - true -> - Recv = fun(Sock, To) -> - socket:recv(Sock, 0, To) - end, - InitState = #{domain => inet6, - recv => Recv, - timeout => 5000}, - ok = api_to_receive_tcp(InitState); - false -> - skip("ipv6 not supported") - end - end). - - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -api_to_receive_tcp(InitState) -> - process_flag(trap_exit, true), - - ServerSeq = - [ - %% *** Wait for start order *** - #{desc => "await start (from tester)", - cmd => fun(State) -> - receive - {start, Tester} when is_pid(Tester) -> - {ok, State#{tester => Tester}} - end - end}, - - %% *** Init part *** - #{desc => "which local address", - cmd => fun(#{domain := Domain} = State) -> - LAddr = which_local_addr(Domain), - LSA = #{family => Domain, addr => LAddr}, - {ok, State#{lsa => LSA}} - end}, - #{desc => "create listen socket", - cmd => fun(#{domain := Domain} = State) -> - case socket:open(Domain, stream, tcp) of - {ok, Sock} -> - {ok, State#{lsock => Sock}}; - {error, _} = ERROR -> - ERROR - end - end}, - #{desc => "bind to local address", - cmd => fun(#{lsock := LSock, lsa := LSA} = State) -> - case socket:bind(LSock, LSA) of - {ok, Port} -> - {ok, State#{lport => Port}}; - {error, _} = ERROR -> - ERROR - end - end}, - #{desc => "make listen socket (with backlog = 1)", - cmd => fun(#{lsock := LSock}) -> - socket:listen(LSock, 1) - end}, - #{desc => "monitor tester", - cmd => fun(#{tester := Tester} = State) -> - MRef = erlang:monitor(process, Tester), - {ok, State#{tester_mref => MRef}} - end}, - #{desc => "announce ready", - cmd => fun(#{tester := Tester, lport := Port}) -> - Tester ! {ready, self(), Port}, - ok - end}, - #{desc => "await continue", - cmd => fun(#{tester := Tester}) -> - receive - {'DOWN', _, process, Tester, Reason} -> - {error, {unexpected_exit, tester, Reason}}; - {continue, Tester} -> - ok - end - end}, - - %% *** The actual test *** - #{desc => "await accept", - cmd => fun(#{lsock := LSock} = State) -> - case socket:accept(LSock) of - {ok, Sock} -> - %% ok = socket:setopt(Sock, otp, debug, true), - {ok, State#{sock => Sock}}; - {error, _} = ERROR -> - ERROR - end - end}, - #{desc => "attempt to recv (without success)", - cmd => fun(#{sock := Sock, recv := Recv, timeout := To} = State) -> - Start = t(), - case Recv(Sock, To) of - {error, timeout} -> - {ok, State#{start => Start, stop => t()}}; - {ok, _Data} -> - {error, unexpected_success}; - {error, _} = ERROR -> - ERROR - end - end}, - #{desc => "validate timeout time", - cmd => fun(#{start := Start, stop := Stop, timeout := To} = _State) -> - TDiff = tdiff(Start, Stop), - if - (TDiff >= To) -> - ok; - true -> - {error, {unexpected_timeout, TDiff, To}} - end - end}, - #{desc => "announce ready (recv timeout success)", - cmd => fun(#{tester := Tester} = _State) -> - Tester ! {ready, self()}, - ok - end}, - - %% *** Termination *** - #{desc => "await terminate", - cmd => fun(#{tester := Tester} = State) -> - receive - {'DOWN', _, process, Tester, Reason} -> - {error, {unexpected_exit, tester, Reason}}; - {terminate, Tester} -> - {ok, maps:remove(tester, State)} - end - end}, - %% #{desc => "sleep some (before traffic close)", - %% cmd => fun(_) -> - %% ?SLEEP(1000), - %% ok - %% end}, - %% #{desc => "monitored-by", - %% cmd => fun(_) -> - %% {_, Mons} = process_info(self(), monitored_by), - %% ei("Monitored By: ~p", [Mons]), - %% ok - %% end}, - #{desc => "close (traffic) socket", - cmd => fun(#{sock := Sock} = State) -> - %% ok = socket:setopt(Sock, otp, debug, true), - sock_close(Sock), - {ok, maps:remove(sock, State)} - end}, - %% #{desc => "monitored-by", - %% cmd => fun(_) -> - %% {_, Mons} = process_info(self(), monitored_by), - %% ei("Monitored By: ~p", [Mons]), - %% ok - %% end}, - %% #{desc => "sleep some (before listen close)", - %% cmd => fun(_) -> - %% ?SLEEP(1000), - %% ok - %% end}, - #{desc => "close (listen) socket", - cmd => fun(#{lsock := LSock} = State) -> - sock_close(LSock), - {ok, maps:remove(lsock, State)} - end}, - %% #{desc => "sleep some (after listen close)", - %% cmd => fun(_) -> - %% ?SLEEP(1000), - %% ok - %% end}, - - %% *** We are done *** - #{desc => "finish", - cmd => fun(_) -> - {ok, normal} - end} - ], - - ClientSeq = - [ - %% *** Wait for start order part *** - #{desc => "await start (from tester)", - cmd => fun(State) -> - receive - {start, Tester, Port} when is_pid(Tester) -> - {ok, State#{tester => Tester, - server_port => Port}} - end - end}, - - %% *** Init part *** - #{desc => "which local address", - cmd => fun(#{domain := Domain, server_port := Port} = State) -> - LAddr = which_local_addr(Domain), - LSA = #{family => Domain, - addr => LAddr}, - SSA = LSA#{port => Port}, - {ok, State#{lsa => LSA, ssa => SSA}} - end}, - #{desc => "create socket", - cmd => fun(#{domain := Domain} = State) -> - case socket:open(Domain, stream, tcp) of - {ok, Sock} -> - {ok, State#{sock => Sock}}; - {error, _} = ERROR -> - ERROR - end - end}, - #{desc => "bind to local address", - cmd => fun(#{sock := Sock, lsa := LSA} = _State) -> - case socket:bind(Sock, LSA) of - {ok, _} -> - ok; - {error, _} = ERROR -> - ERROR - end - end}, - #{desc => "monitor tester", - cmd => fun(#{tester := Tester} = State) -> - MRef = erlang:monitor(process, Tester), - {ok, State#{tester_mref => MRef}} - end}, - #{desc => "announce ready", - cmd => fun(#{tester := Tester} = _State) -> - Tester ! {ready, self()}, - ok - end}, - - %% *** The actual test *** - #{desc => "await continue (with connect)", - cmd => fun(#{tester := Tester} = _State) -> - receive - {'DOWN', _, process, Tester, Reason} -> - {error, {unexpected_exit, tester, Reason}}; - {continue, Tester} -> - ok - end - end}, - #{desc => "connect", - cmd => fun(#{sock := Sock, ssa := SSA}) -> - sock_connect(Sock, SSA), - ok - end}, - - %% *** Termination *** - #{desc => "await terminate", - cmd => fun(#{tester := Tester} = State) -> - receive - {'DOWN', _, process, Tester, Reason} -> - {error, {unexpected_exit, tester, Reason}}; - {terminate, Tester} -> - {ok, maps:remove(tester, State)} - end - end}, - #{desc => "close socket", - cmd => fun(#{sock := Sock} = State) -> - sock_close(Sock), - {ok, maps:remove(sock, State)} - end}, - - %% *** We are done *** - #{desc => "finish", - cmd => fun(_) -> - {ok, normal} - end} - ], - - TesterSeq = - [ - %% *** Init part *** - #{desc => "monitor server", - cmd => fun(#{server := Server} = State) -> - MRef = erlang:monitor(process, Server), - {ok, State#{server_mref => MRef}} - end}, - #{desc => "monitor client", - cmd => fun(#{client := Client} = State) -> - MRef = erlang:monitor(process, Client), - {ok, State#{client_mref => MRef}} - end}, - - %% *** Activate server *** - #{desc => "start server", - cmd => fun(#{server := Server} = _State) -> - Server ! {start, self()}, - ok - end}, - #{desc => "await server ready (init)", - cmd => fun(#{server := Server} = State) -> - receive - {'DOWN', _, process, Server, Reason} -> - {error, {unexpected_exit, server, Reason}}; - {ready, Server, Port} -> - {ok, State#{server_port => Port}} - end - end}, - #{desc => "order server to continue (with accept)", - cmd => fun(#{server := Server} = _State) -> - Server ! {continue, self()}, - ok - end}, - - %% *** Activate client *** - #{desc => "start client", - cmd => fun(#{client := Client, server_port := Port} = _State) -> - Client ! {start, self(), Port}, - ok - end}, - #{desc => "await client ready", - cmd => fun(#{client := Client} = _State) -> - receive - {'DOWN', _, process, Client, Reason} -> - {error, {unexpected_exit, client, Reason}}; - {ready, Client} -> - ok - end - end}, - - %% *** The actual test *** - #{desc => "order client to continue (with connect)", - cmd => fun(#{client := Client} = _State) -> - Client ! {continue, self()}, - ok - end}, - #{desc => "await server ready (accept/recv)", - cmd => fun(#{server := Server} = _State) -> - receive - {'DOWN', _, process, Server, Reason} -> - {error, {unexpected_exit, server, Reason}}; - {ready, Server} -> - ok - end - end}, - - %% *** Termination *** - #{desc => "order client to terminate", - cmd => fun(#{client := Client} = _State) -> - Client ! {terminate, self()}, - ok - end}, - #{desc => "await client termination", - cmd => fun(#{client := Client} = State) -> - receive - {'DOWN', _, process, Client, _Reason} -> - State1 = maps:remove(client, State), - State2 = maps:remove(client_mref, State1), - {ok, State2} - end - end}, - #{desc => "order server to terminate", - cmd => fun(#{server := Server} = _State) -> - Server ! {terminate, self()}, - ok - end}, - #{desc => "await server termination", - cmd => fun(#{server := Server} = State) -> - receive - {'DOWN', _, process, Server, _Reason} -> - State1 = maps:remove(server, State), - State2 = maps:remove(server_mref, State1), - State3 = maps:remove(server_port, State2), - {ok, State3} - end - end}, - - %% *** We are done *** - #{desc => "finish", - cmd => fun(_) -> - {ok, normal} - end} - ], - - - p("start server evaluator"), - ServerInitState = InitState, - Server = evaluator_start("server", ServerSeq, ServerInitState), - - p("start client evaluator"), - ClientInitState = InitState, - Client = evaluator_start("client", ClientSeq, ClientInitState), - - p("start tester evaluator"), - TesterInitState = #{server => Server, client => Client}, - Tester = evaluator_start("tester", TesterSeq, TesterInitState), - - p("await evaluator(s)"), - ok = await_evaluator_finish([Server, Client, Tester]). - - - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -%% This test case is intended to test the recvfrom timeout option -%% on an IPv4 UDP (dgram) socket. -api_to_recvfrom_udp4(suite) -> - []; -api_to_recvfrom_udp4(doc) -> - []; -api_to_recvfrom_udp4(_Config) when is_list(_Config) -> - tc_try(api_to_recvfrom_udp4, - fun() -> - Recv = fun(Sock, To) -> socket:recvfrom(Sock, 0, To) end, - InitState = #{domain => inet, - recv => Recv, - timeout => 5000}, - ok = api_to_receive_udp(InitState) - end). - - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -%% This test case is intended to test the recvfrom timeout option -%% on an IPv6 UDP (dgram) socket. -api_to_recvfrom_udp6(suite) -> - []; -api_to_recvfrom_udp6(doc) -> - []; -api_to_recvfrom_udp6(_Config) when is_list(_Config) -> - tc_try(api_to_recvfrom_udp6, - fun() -> - not_yet_implemented(), - Recv = fun(Sock, To) -> socket:recvfrom(Sock, 0, To) end, - InitState = #{domain => inet6, - recv => Recv, - timeout => 5000}, - ok = api_to_receive_udp(InitState) - end). - - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -api_to_receive_udp(InitState) -> - TesterSeq = - [ - %% *** Init part *** - #{desc => "which local address", - cmd => fun(#{domain := Domain} = State) -> - LAddr = which_local_addr(Domain), - LSA = #{family => Domain, addr => LAddr}, - {ok, State#{lsa => LSA}} - end}, - #{desc => "create socket", - cmd => fun(#{domain := Domain} = State) -> - case socket:open(Domain, dgram, udp) of - {ok, Sock} -> - {ok, State#{sock => Sock}}; - {error, _} = ERROR -> - ERROR - end - end}, - #{desc => "bind to local address", - cmd => fun(#{sock := Sock, lsa := LSA} = _State) -> - case socket:bind(Sock, LSA) of - {ok, _Port} -> - ok; - {error, _} = ERROR -> - ERROR - end - end}, - - %% *** The actual test *** - #{desc => "attempt to read (without success)", - cmd => fun(#{sock := Sock, recv := Recv, timeout := To} = State) -> - Start = t(), - case Recv(Sock, To) of - {error, timeout} -> - {ok, State#{start => Start, stop => t()}}; - {ok, _} -> - {error, unexpected_sucsess}; - {error, _} = ERROR -> - ERROR - end - end}, - #{desc => "validate timeout time", - cmd => fun(#{start := Start, stop := Stop, timeout := To} = _State) -> - TDiff = tdiff(Start, Stop), - if - (TDiff >= To) -> - ok; - true -> - {error, {unexpected_timeout, TDiff, To}} - end - end}, - - %% *** Termination *** - #{desc => "close socket", - cmd => fun(#{sock := Sock} = _State) -> - sock_close(Sock), - ok - end}, - - %% *** We are done *** - #{desc => "finish", - cmd => fun(_) -> - {ok, normal} - end} - ], - - p("start tester evaluator"), - Tester = evaluator_start("tester", TesterSeq, InitState), - - p("await evaluator"), - ok = await_evaluator_finish([Tester]). - - - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -%% This test case is intended to test the recvmsg timeout option -%% on an IPv4 UDP (dgram) socket. -api_to_recvmsg_udp4(suite) -> - []; -api_to_recvmsg_udp4(doc) -> - []; -api_to_recvmsg_udp4(_Config) when is_list(_Config) -> - tc_try(api_to_recvmsg_udp4, - fun() -> - Recv = fun(Sock, To) -> socket:recvmsg(Sock, To) end, - InitState = #{domain => inet, - recv => Recv, - timeout => 5000}, - ok = api_to_receive_udp(InitState) - end). - - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -%% This test case is intended to test the recvmsg timeout option -%% on an IPv6 UDP (dgram) socket. -api_to_recvmsg_udp6(suite) -> - []; -api_to_recvmsg_udp6(doc) -> - []; -api_to_recvmsg_udp6(_Config) when is_list(_Config) -> - tc_try(api_to_recvmsg_udp6, - fun() -> - not_yet_implemented(), - Recv = fun(Sock, To) -> socket:recvmsg(Sock, To) end, - InitState = #{domain => inet6, - recv => Recv, - timeout => 5000}, - ok = api_to_receive_udp(InitState) - end). - - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -%% This test case is intended to test the recvmsg timeout option -%% on an IPv4 TCP (stream) socket. -api_to_recvmsg_tcp4(suite) -> - []; -api_to_recvmsg_tcp4(doc) -> - []; -api_to_recvmsg_tcp4(_Config) when is_list(_Config) -> - tc_try(api_to_recvmsg_tcp4, - fun() -> - Recv = fun(Sock, To) -> socket:recvmsg(Sock, To) end, - InitState = #{domain => inet, - recv => Recv, - timeout => 5000}, - ok = api_to_receive_tcp(InitState) - end). - - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -%% This test case is intended to test the recvmsg timeout option -%% on an IPv6 TCP (stream) socket. -api_to_recvmsg_tcp6(suite) -> - []; -api_to_recvmsg_tcp6(doc) -> - []; -api_to_recvmsg_tcp6(_Config) when is_list(_Config) -> - tc_try(api_to_recvmsg_tcp6, - fun() -> - not_yet_implemented(), - Recv = fun(Sock, To) -> socket:recvmsg(Sock, To) end, - InitState = #{domain => inet6, - recv => Recv, - timeout => 5000}, - ok = api_to_receive_tcp(InitState) - end). - - - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% %% -%% SOCKET CLOSURE %% -%% %% -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% This test case is intended to test that the sockets are cleaned up -%% ("removed") when the controlling process terminates (without explicitly -%% calling the close function). For a IPv4 TCP (stream) socket. - -sc_cpe_socket_cleanup_tcp4(suite) -> - []; -sc_cpe_socket_cleanup_tcp4(doc) -> - []; -sc_cpe_socket_cleanup_tcp4(_Config) when is_list(_Config) -> - tc_try(sc_cpe_socket_cleanup_tcp4, - fun() -> - %% not_yet_implemented(), - InitState = #{domain => inet, - type => stream, - protocol => tcp}, - ok = sc_cpe_socket_cleanup(InitState) - end). - - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% This test case is intended to test that the sockets are cleaned up -%% ("removed") when the controlling process terminates (without explicitly -%% calling the close function). For a IPv6 TCP (stream) socket. - -sc_cpe_socket_cleanup_tcp6(suite) -> - []; -sc_cpe_socket_cleanup_tcp6(doc) -> - []; -sc_cpe_socket_cleanup_tcp6(_Config) when is_list(_Config) -> - tc_try(sc_cpe_socket_cleanup_tcp6, - fun() -> - not_yet_implemented(), - InitState = #{domain => inet6, - type => stream, - protocol => tcp}, - ok = sc_cpe_socket_cleanup(InitState) - end). - - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% This test case is intended to test that the sockets are cleaned up -%% ("removed") when the controlling process terminates (without explicitly -%% calling the close function). For a IPv4 UDP (dgram) socket. - -sc_cpe_socket_cleanup_udp4(suite) -> - []; -sc_cpe_socket_cleanup_udp4(doc) -> - []; -sc_cpe_socket_cleanup_udp4(_Config) when is_list(_Config) -> - tc_try(sc_cpe_socket_cleanup_udp4, - fun() -> - InitState = #{domain => inet, - type => dgram, - protocol => udp}, - ok = sc_cpe_socket_cleanup(InitState) - end). - - - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% This test case is intended to test that the sockets are cleaned up -%% (removed) when the controlling process terminates (without explicitly -%% calling the close function). For a IPv6 UDP (dgram) socket. - -sc_cpe_socket_cleanup_udp6(suite) -> - []; -sc_cpe_socket_cleanup_udp6(doc) -> - []; -sc_cpe_socket_cleanup_udp6(_Config) when is_list(_Config) -> - tc_try(sc_cpe_socket_cleanup_udp6, - fun() -> - not_yet_implemented(), - InitState = #{domain => inet6, - type => dgram, - protocol => udp}, - ok = sc_cpe_socket_cleanup(InitState) - end). - - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -sc_cpe_socket_cleanup(InitState) -> - OwnerSeq = - [ - %% *** Wait for start order part *** - #{desc => "await start (from tester)", - cmd => fun(State) -> - receive - {start, Tester} when is_pid(Tester) -> - {ok, State#{tester => Tester}} - end - end}, - - %% *** Init part *** - #{desc => "monitor tester", - cmd => fun(#{tester := Tester} = _State) -> - _MRef = erlang:monitor(process, Tester), - ok - end}, - #{desc => "create socket", - cmd => fun(#{domain := Domain, - type := Type, - protocol := Proto} = State) -> - case socket:open(Domain, Type, Proto) of - {ok, Sock} -> - {ok, State#{sock => Sock}}; - {error, _} = ERROR -> - ERROR - end - end}, - #{desc => "announce ready", - cmd => fun(#{tester := Tester, sock := Sock} = _State) -> - Tester ! {ready, self(), Sock}, - ok - end}, - - %% *** The actual test *** - %% We intentially leave the socket "as is", no explicit close - #{desc => "await terminate (from tester)", - cmd => fun(#{tester := Tester} = State) -> - receive - {'DOWN', _, process, Tester, Reason} -> - ee("Unexpected DOWN regarding tester ~p: " - "~n ~p", [Reason]), - {error, {unexpected_exit, tester}}; - {terminate, Tester} -> - {ok, maps:remove(tester, State)} - end - end}, - %% #{desc => "enable (otp) debug", - %% cmd => fun(#{sock := Sock} = _State) -> - %% ok = socket:setopt(Sock, otp, debug, true) - %% end}, - - %% *** We are done *** - #{desc => "finish", - cmd => fun(_) -> - {ok, normal} - end} - ], - - TesterSeq = - [ - %% *** Init part *** - #{desc => "monitor owner", - cmd => fun(#{owner := Owner} = _State) -> - _MRef = erlang:monitor(process, Owner), - ok - end}, - #{desc => "order (owner) start", - cmd => fun(#{owner := Pid} = _State) -> - Pid ! {start, self()}, - ok - end}, - #{desc => "await (owner) ready", - cmd => fun(#{owner := Owner} = State) -> - receive - {'DOWN', _, process, Owner, Reason} -> - ee("Unexpected DOWN regarding owner ~p: " - "~n ~p", [Reason]), - {error, {unexpected_exit, owner}}; - {ready, Owner, Sock} -> - {ok, State#{sock => Sock}} - end - end}, - #{desc => "verify owner as controlling-process", - cmd => fun(#{owner := Owner, sock := Sock} = _State) -> - case socket:getopt(Sock, otp, controlling_process) of - {ok, Owner} -> - ok; - {ok, Other} -> - {error, {unexpected_owner, Other}}; - {error, _} = ERROR -> - ERROR - end - end}, - #{desc => "order (owner) terminate", - cmd => fun(#{owner := Pid} = _State) -> - Pid ! {terminate, self()}, - ok - end}, - #{desc => "await (owner) termination", - cmd => fun(#{owner := Owner} = _State) -> - receive - {'DOWN', _, process, Owner, _} -> - ok - end - end}, - #{desc => "verify no socket (closed)", - cmd => fun(#{owner := Owner, sock := Sock} = _State) -> - case socket:getopt(Sock, otp, controlling_process) of - {ok, Pid} -> - {error, {unexpected_success, Owner, Pid}}; - {error, closed} -> - ok; - {error, Reason} -> - ei("expected failure: ~p", [Reason]), - {error, {unexpected_failure, Reason}} - end - end}, - - %% *** We are done *** - #{desc => "finish", - cmd => fun(_) -> - {ok, normal} - end} - ], - - p("start (socket) owner evaluator"), - Owner = evaluator_start("owner", OwnerSeq, InitState), - - p("start tester evaluator"), - TesterInitState = #{owner => Owner}, - Tester = evaluator_start("tester", TesterSeq, TesterInitState), - - p("await evaluator"), - ok = await_evaluator_finish([Owner, Tester]). - - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% This test case is intended to test what happens when a socket is -%% locally closed while a process is calling the recv function. -%% Socket is IPv4. -%% -%% -%% -%% We should really have a similar test cases for when the controlling -%% process exits and there are other processes in recv, accept, and -%% all the other functions. -%% -%% - -sc_lc_recv_response_tcp4(suite) -> - []; -sc_lc_recv_response_tcp4(doc) -> - []; -sc_lc_recv_response_tcp4(_Config) when is_list(_Config) -> - tc_try(sc_lc_recv_response_tcp4, - fun() -> - %% not_yet_implemented(), - Recv = fun(Sock) -> socket:recv(Sock) end, - InitState = #{domain => inet, - type => stream, - protocol => tcp, - recv => Recv}, - ok = sc_lc_receive_response_tcp(InitState) - end). - - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% This test case is intended to test what happens when a socket is -%% locally closed while the process is calling the recv function. -%% Socket is IPv6. - -sc_lc_recv_response_tcp6(suite) -> - []; -sc_lc_recv_response_tcp6(doc) -> - []; -sc_lc_recv_response_tcp6(_Config) when is_list(_Config) -> - tc_try(sc_lc_recv_response_tcp6, - fun() -> - not_yet_implemented(), - Recv = fun(Sock) -> socket:recv(Sock) end, - InitState = #{domain => inet6, - type => stream, - protocol => tcp, - recv => Recv}, - ok = sc_lc_receive_response_tcp(InitState) - end). - - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -sc_lc_receive_response_tcp(InitState) -> - %% This is the server that accepts connections. - %% But it is also suppose to close the connection socket, - %% and trigger the read failure for the handler process. - AcceptorSeq = - [ - %% *** Wait for start order part *** - #{desc => "await start (from tester)", - cmd => fun(State) -> - receive - {start, Tester} when is_pid(Tester) -> - {ok, State#{tester => Tester}} - end - end}, - #{desc => "monitor tester", - cmd => fun(#{tester := Tester} = _State) -> - _MRef = erlang:monitor(process, Tester), - ok - end}, - - %% *** Init part *** - #{desc => "which local address", - cmd => fun(#{domain := Domain} = State) -> - LAddr = which_local_addr(Domain), - LSA = #{family => Domain, addr => LAddr}, - {ok, State#{lsa => LSA}} - end}, - #{desc => "create (listen) socket", - cmd => fun(#{domain := Domain, - type := Type, - protocol := Proto} = State) -> - case socket:open(Domain, Type, Proto) of - {ok, Sock} -> - {ok, State#{lsock => Sock}}; - {error, _} = ERROR -> - ERROR - end - end}, - #{desc => "bind to local address", - cmd => fun(#{lsock := LSock, lsa := LSA} = State) -> - case socket:bind(LSock, LSA) of - {ok, Port} -> - {ok, State#{lport => Port}}; - {error, _} = ERROR -> - ERROR - end - end}, - #{desc => "make listen socket", - cmd => fun(#{lsock := LSock}) -> - socket:listen(LSock) - end}, - #{desc => "announce ready (init)", - cmd => fun(#{tester := Tester, lport := Port}) -> - Tester ! {ready, self(), Port}, - ok - end}, - - %% The actual test - #{desc => "await continue (connection)", - cmd => fun(#{tester := Tester} = State) -> - receive - {'DOWN', _, process, Tester, Reason} -> - ee("Unexpected DOWN regarding tester ~p: " - "~n ~p", [Reason]), - {error, {unexpected_exit, tester}}; - {continue, Tester, Handler} -> - {ok, State#{handler => Handler}} - end - end}, - #{desc => "await connection", - cmd => fun(#{lsock := LSock} = State) -> - case socket:accept(LSock) of - {ok, Sock} -> - ei("connection accepted"), - {ok, State#{csock => Sock}}; - {error, _} = ERROR -> - ERROR - end - end}, - #{desc => "transfer new connection to handler", - cmd => fun(#{handler := Handler, csock := Sock}) -> - ok = socket:setopt(Sock, - otp, controlling_process, - Handler), - Handler ! {connection, Sock}, - ok - end}, - #{desc => "announce ready (connection)", - cmd => fun(#{tester := Tester}) -> - Tester ! {ready, self()}, - ok - end}, - #{desc => "await continue (close)", - cmd => fun(#{tester := Tester} = _State) -> - receive - {'DOWN', _, process, Tester, Reason} -> - ee("Unexpected DOWN regarding tester ~p: " - "~n ~p", [Reason]), - {error, {unexpected_exit, tester}}; - {continue, Tester} -> - ok - end - end}, - %% #{desc => "enable debug", - %% cmd => fun(#{csock := Sock}) -> - %% socket:setopt(Sock, otp, debug, true) - %% end}, - #{desc => "close (the connection) socket", - cmd => fun(#{csock := Sock}) -> - socket:close(Sock) - end}, - - #{desc => "await terminate", - cmd => fun(#{tester := Tester} = State) -> - receive - {'DOWN', _, process, Tester, Reason} -> - ee("Unexpected DOWN regarding tester ~p: " - "~n ~p", [Reason]), - {error, {unexpected_exit, tester}}; - {terminate, Tester} -> - {ok, maps:remove(tester, State)} - end - end}, - #{desc => "socket cleanup", - cmd => fun(#{lsock := Sock} = State) -> - ok = socket:close(Sock), - State1 = maps:remove(csock, State), - State2 = maps:remove(lsock, State1), - State3 = maps:remove(lport, State2), - {ok, State3} - end}, - - %% *** We are done *** - #{desc => "finish", - cmd => fun(_) -> - {ok, normal} - end} - ], - - %% The point of this is to perform the recv for which we are testing the reponse - HandlerSeq = - [ - %% *** Wait for start order part *** - #{desc => "await start (from tester)", - cmd => fun(State) -> - receive - {start, Tester} when is_pid(Tester) -> - {ok, State#{tester => Tester}} - end - end}, - #{desc => "monitor server", - cmd => fun(#{tester := Tester} = _State) -> - _MRef = erlang:monitor(process, Tester), - ok - end}, - #{desc => "announce ready (init)", - cmd => fun(#{tester := Tester}) -> - Tester ! {ready, self()}, - ok - end}, - - %% The actual test - #{desc => "await connection socket", - cmd => fun(#{tester := Tester} = State) -> - receive - {'DOWN', _, process, Tester, Reason} -> - ee("Unexpected DOWN regarding tester ~p: " - "~n ~p", [Reason]), - {error, {unexpected_exit, tester}}; - {connection, Sock} -> - {ok, State#{sock => Sock}} - end - end}, - #{desc => "announce ready (connection)", - cmd => fun(#{tester := Tester}) -> - Tester ! {ready, self()}, - ok - end}, - %% #{desc => "enable debug", - %% cmd => fun(#{sock := Sock}) -> - %% socket:setopt(Sock, otp, debug, true) - %% end}, - %% #{desc => "monitored-by", - %% cmd => fun(_) -> - %% {_, Mons} = process_info(self(), monitored_by), - %% ei("Monitored By: ~p", [Mons]), - %% ok - %% end}, - #{desc => "attempt recv", - cmd => fun(#{sock := Sock, recv := Recv} = State) -> - case Recv(Sock) of - {ok, _Data} -> - ee("Unexpected data received"), - {error, unexpected_data}; - {error, closed} -> - State1 = maps:remove(sock, State), - {ok, State1}; - {error, Reason} = ERROR -> - ee("Unexpected read faulure: " - "~n ~p", [Reason]), - ERROR - end - end}, - %% #{desc => "monitored-by", - %% cmd => fun(_) -> - %% {_, Mons} = process_info(self(), monitored_by), - %% ei("Monitored By: ~p", [Mons]), - %% ok - %% end}, - #{desc => "announce ready (close)", - cmd => fun(#{tester := Tester}) -> - Tester ! {ready, self()}, - ok - end}, - #{desc => "sleep some", - cmd => fun(_) -> - ?SLEEP(1000), - ok - end}, - %% #{desc => "monitored-by", - %% cmd => fun(_) -> - %% {_, Mons} = process_info(self(), monitored_by), - %% ei("Monitored By: ~p", [Mons]), - %% ok - %% end}, - #{desc => "await terminate", - cmd => fun(#{tester := Tester} = _State) -> - receive - {'DOWN', _, process, Tester, Reason} -> - ee("Unexpected DOWN regarding tester ~p: " - "~n ~p", [Reason]), - {error, {unexpected_exit, tester}}; - {terminate, Tester} -> - ok - end - end}, - - - %% *** We are done *** - #{desc => "finish", - cmd => fun(_) -> - {ok, normal} - end} - ], - - %% The point of this is basically just to create the connection. - ClientSeq = - [ - %% *** Wait for start order part *** - #{desc => "await start (from tester)", - cmd => fun(State) -> - receive - {start, Tester} when is_pid(Tester) -> - {ok, State#{tester => Tester}} - end - end}, - - %% Init - #{desc => "which local address", - cmd => fun(#{domain := Domain} = State) -> - LAddr = which_local_addr(Domain), - LSA = #{family => Domain, addr => LAddr}, - {ok, State#{lsa => LSA}} - end}, - #{desc => "create socket", - cmd => fun(#{domain := Domain, - type := Type, - protocol := Proto} = State) -> - case socket:open(Domain, Type, Proto) of - {ok, Sock} -> - {ok, State#{sock => Sock}}; - {error, _} = ERROR -> - ERROR - end - end}, - #{desc => "bind socket to local address", - cmd => fun(#{sock := Sock, lsa := LSA} = _State) -> - case socket:bind(Sock, LSA) of - {ok, _} -> - ok; - {error, _} = ERROR -> - ERROR - end - end}, - #{desc => "announce ready", - cmd => fun(#{tester := Tester} = _State) -> - Tester ! {ready, self()}, - ok - end}, - - %% The actual test - #{desc => "await continue", - cmd => fun(#{tester := Tester} = State) -> - receive - {'DOWN', _, process, Tester, Reason} -> - ee("Unexpected DOWN regarding tester ~p: " - "~n ~p", [Tester, Reason]), - {error, {unexpected_exit, tester, Reason}}; - {continue, Tester, Port} -> - {ok, State#{lport => Port}} - end - end}, - #{desc => "connect to server", - cmd => fun(#{sock := Sock, lsa := LSA, lport := LPort}) -> - socket:connect(Sock, LSA#{port => LPort}) - end}, - #{desc => "announce ready (connection)", - cmd => fun(#{tester := Tester} = _State) -> - Tester ! {ready, self()}, - ok - end}, - - %% Cleaning up - #{desc => "await terminate (from tester)", - cmd => fun(#{tester := Tester} = State) -> - receive - {'DOWN', _, process, Tester, Reason} -> - ee("Unexpected DOWN regarding tester ~p: " - "~n ~p", [Tester, Reason]), - {error, {unexpected_exit, tester}}; - {terminate, Tester} -> - {ok, maps:remove(tester, State)} - end - end}, - #{desc => "close socket", - cmd => fun(#{sock := Sock} = State) -> - sock_close(Sock), - {ok, maps:remove(sock, State)} - end}, - - %% *** We are done *** - #{desc => "finish", - cmd => fun(_) -> - {ok, normal} - end} - ], - - TesterSeq = - [ - %% *** Init part *** - #{desc => "monitor acceptor", - cmd => fun(#{acceptor := Pid} = _State) -> - _MRef = erlang:monitor(process, Pid), - ok - end}, - #{desc => "monitor handler", - cmd => fun(#{handler := Pid} = _State) -> - _MRef = erlang:monitor(process, Pid), - ok - end}, - #{desc => "monitor client", - cmd => fun(#{client := Pid} = _State) -> - _MRef = erlang:monitor(process, Pid), - ok - end}, - - %% Start the acceptor - #{desc => "order acceptor start", - cmd => fun(#{acceptor := Pid} = _State) -> - Pid ! {start, self()}, - ok - end}, - #{desc => "await acceptor ready (init)", - cmd => fun(#{acceptor := Pid} = State) -> - receive - {'DOWN', _, process, Pid, Reason} -> - ee("Unexpected DOWN regarding acceptor ~p: " - "~n ~p", [Pid, Reason]), - {error, {unexpected_exit, acceptor}}; - {ready, Pid, Port} -> - {ok, State#{lport => Port}} - end - end}, - - %% Start the handler - #{desc => "order handler start", - cmd => fun(#{handler := Pid} = _State) -> - Pid ! {start, self()}, - ok - end}, - #{desc => "await handler ready (init)", - cmd => fun(#{handler := Pid} = _State) -> - receive - {'DOWN', _, process, Pid, Reason} -> - ee("Unexpected DOWN regarding handler ~p: " - "~n ~p", [Pid, Reason]), - {error, {unexpected_exit, acceptor}}; - {ready, Pid} -> - ok - end - end}, - - %% Start the client - #{desc => "order client start", - cmd => fun(#{client := Pid} = _State) -> - Pid ! {start, self()}, - ok - end}, - #{desc => "await client ready (init)", - cmd => fun(#{client := Pid} = _State) -> - receive - {'DOWN', _, process, Pid, Reason} -> - ee("Unexpected DOWN regarding cient ~p: " - "~n ~p", [Pid, Reason]), - {error, {unexpected_exit, acceptor}}; - {ready, Pid} -> - ok - end - end}, - - %% The actual test - #{desc => "order acceptor to continue", - cmd => fun(#{acceptor := Pid, handler := Handler} = _State) -> - Pid ! {continue, self(), Handler}, - ok - end}, - #{desc => "order client to continue", - cmd => fun(#{client := Pid, lport := Port} = _State) -> - Pid ! {continue, self(), Port}, - ok - end}, - #{desc => "await acceptor ready (connection)", - cmd => fun(#{acceptor := Pid} = _State) -> - receive - {'DOWN', _, process, Pid, Reason} -> - ee("Unexpected DOWN regarding acceptor ~p: " - "~n ~p", [Pid, Reason]), - {error, {unexpected_exit, acceptor}}; - {ready, Pid} -> - ok - end - end}, - #{desc => "await client ready (connection)", - cmd => fun(#{client := Pid} = _State) -> - receive - {'DOWN', _, process, Pid, Reason} -> - ee("Unexpected DOWN regarding client ~p: " - "~n ~p", [Pid, Reason]), - {error, {unexpected_exit, acceptor}}; - {ready, Pid} -> - ok - end - end}, - #{desc => "await handler ready (connection)", - cmd => fun(#{handler := Pid} = _State) -> - receive - {'DOWN', _, process, Pid, Reason} -> - ee("Unexpected DOWN regarding handler ~p: " - "~n ~p", [Reason]), - {error, {unexpected_exit, acceptor}}; - {ready, Pid} -> - ok - end - end}, - #{desc => "sleep some", - cmd => fun(_State) -> - ?SLEEP(1000), - ok - end}, - #{desc => "order acceptor to continue (close)", - cmd => fun(#{acceptor := Pid} = _State) -> - Pid ! {continue, self()}, - ok - end}, - #{desc => "await handler ready (close)", - cmd => fun(#{handler := Pid} = _State) -> - receive - {'DOWN', _, process, Pid, Reason} -> - ee("Unexpected DOWN regarding handler ~p: " - "~n ~p", [Pid, Reason]), - {error, {unexpected_exit, acceptor}}; - {ready, Pid} -> - ok - end - end}, - - %% Terminations - #{desc => "order handler to terminate", - cmd => fun(#{handler := Pid} = _State) -> - Pid ! {terminate, self()}, - ok - end}, - #{desc => "await handler termination", - cmd => fun(#{handler := Pid} = State) -> - receive - {'DOWN', _, process, Pid, _} -> - {ok, maps:remove(handler, State)} - end - end}, - #{desc => "order client to terminate", - cmd => fun(#{client := Pid} = _State) -> - Pid ! {terminate, self()}, - ok - end}, - #{desc => "await client termination", - cmd => fun(#{client := Pid} = State) -> - receive - {'DOWN', _, process, Pid, _} -> - {ok, maps:remove(client, State)} - end - end}, - #{desc => "order acceptor to terminate", - cmd => fun(#{acceptor := Pid} = _State) -> - Pid ! {terminate, self()}, - ok - end}, - #{desc => "await acceptor termination", - cmd => fun(#{acceptor := Pid} = State) -> - receive - {'DOWN', _, process, Pid, _} -> - {ok, maps:remove(acceptor, State)} - end - end}, - - - %% *** We are done *** - #{desc => "finish", - cmd => fun(_) -> - {ok, normal} - end} - ], - - p("start acceptor evaluator"), - AccInitState = InitState, - Acceptor = evaluator_start("acceptor", AcceptorSeq, AccInitState), - - p("start handler evaluator"), - HandlerInitState = #{recv => maps:get(recv, InitState)}, - Handler = evaluator_start("handler", HandlerSeq, HandlerInitState), - - p("start client evaluator"), - ClientInitState = InitState, - Client = evaluator_start("client", ClientSeq, ClientInitState), - - p("start tester evaluator"), - TesterInitState = #{acceptor => Acceptor, - handler => Handler, - client => Client}, - Tester = evaluator_start("tester", TesterSeq, TesterInitState), - - p("await evaluator"), - ok = await_evaluator_finish([Acceptor, Handler, Client, Tester]). - - - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% This test case is intended to test what happens when a socket is -%% remotely closed while the process is calling the recv function. -%% Socket is IPv4. - -sc_rc_recv_response_tcp4(suite) -> - []; -sc_rc_recv_response_tcp4(doc) -> - []; -sc_rc_recv_response_tcp4(_Config) when is_list(_Config) -> - tc_try(sc_rc_recv_response_tcp4, - fun() -> - not_yet_implemented(), - Recv = fun(Sock) -> socket:recv(Sock) end, - InitState = #{domain => inet, - type => stream, - protocol => tcp, - recv => Recv}, - ok = sc_rc_receive_response_tcp(InitState) - end). - - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% This test case is intended to test what happens when a socket is -%% remotely closed while the process is calling the recv function. -%% Socket is IPv6. - -sc_rc_recv_response_tcp6(suite) -> - []; -sc_rc_recv_response_tcp6(doc) -> - []; -sc_rc_recv_response_tcp6(_Config) when is_list(_Config) -> - tc_try(sc_rc_recv_response_tcp6, - fun() -> - not_yet_implemented(), - Recv = fun(Sock) -> socket:recv(Sock) end, - InitState = #{domain => inet6, - type => stream, - protocol => tcp, - recv => Recv}, - ok = sc_rc_receive_response_tcp(InitState) - end). - - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -sc_rc_receive_response_tcp(_InitState) -> - ok. - - - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% This test case is intended to test what happens when a socket is -%% locally closed while the process is calling the recvmsg function. -%% Socket is IPv4. - -sc_lc_recvmsg_response_tcp4(suite) -> - []; -sc_lc_recvmsg_response_tcp4(doc) -> - []; -sc_lc_recvmsg_response_tcp4(_Config) when is_list(_Config) -> - tc_try(sc_lc_recvmsg_response_tcp4, - fun() -> - Recv = fun(Sock) -> socket:recvmsg(Sock) end, - InitState = #{domain => inet, - type => stream, - protocol => tcp, - recv => Recv}, - ok = sc_lc_receive_response_tcp(InitState) - end). - - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% This test case is intended to test what happens when a socket is -%% locally closed while the process is calling the recvmsg function. -%% Socket is IPv6. - -sc_lc_recvmsg_response_tcp6(suite) -> - []; -sc_lc_recvmsg_response_tcp6(doc) -> - []; -sc_lc_recvmsg_response_tcp6(_Config) when is_list(_Config) -> - tc_try(sc_recvmsg_response_tcp6, - fun() -> - not_yet_implemented(), - Recv = fun(Sock) -> socket:recvmsg(Sock) end, - InitState = #{domain => inet6, - type => stream, - protocol => tcp, - recv => Recv}, - ok = sc_lc_receive_response_tcp(InitState) - end). - - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% This test case is intended to test what happens when a socket is -%% remotely closed while the process is calling the recvmsg function. -%% Socket is IPv4. - -sc_rc_recvmsg_response_tcp4(suite) -> - []; -sc_rc_recvmsg_response_tcp4(doc) -> - []; -sc_rc_recvmsg_response_tcp4(_Config) when is_list(_Config) -> - tc_try(sc_rc_recvmsg_response_tcp4, - fun() -> - not_yet_implemented(), - Recv = fun(Sock) -> socket:recvmsg(Sock) end, - InitState = #{domain => inet, - type => stream, - protocol => tcp, - recv => Recv}, - ok = sc_rc_receive_response_tcp(InitState) - end). - - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% This test case is intended to test what happens when a socket is -%% remotely closed while the process is calling the recvmsg function. -%% Socket is IPv6. - -sc_rc_recvmsg_response_tcp6(suite) -> - []; -sc_rc_recvmsg_response_tcp6(doc) -> - []; -sc_rc_recvmsg_response_tcp6(_Config) when is_list(_Config) -> - tc_try(sc_rc_recvmsg_response_tcp6, - fun() -> - not_yet_implemented(), - Recv = fun(Sock) -> socket:recvmsg(Sock) end, - InitState = #{domain => inet6, - type => stream, - protocol => tcp, - recv => Recv}, - ok = sc_rc_receive_response_tcp(InitState) - end). - - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% This test case is intended to test what happens when a socket is -%% locally closed while the process is calling the accept function. -%% We test what happens with a non-controlling_process also, since we -%% git the setup anyway. -%% Socket is IPv4. - -sc_lc_acceptor_response_tcp4(suite) -> - []; -sc_lc_acceptor_response_tcp4(doc) -> - []; -sc_lc_acceptor_response_tcp4(_Config) when is_list(_Config) -> - tc_try(sc_lc_acceptor_response_tcp4, - fun() -> - not_yet_implemented(), - InitState = #{domain => inet, - type => stream, - protocol => tcp}, - ok = sc_lc_acceptor_response_tcp(InitState) - end). - - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% This test case is intended to test what happens when a socket is -%% locally closed while the process is calling the accept function. -%% We test what happens with a non-controlling_process also, since we -%% git the setup anyway. -%% Socket is IPv6. - -sc_lc_acceptor_response_tcp6(suite) -> - []; -sc_lc_acceptor_response_tcp6(doc) -> - []; -sc_lc_acceptor_response_tcp6(_Config) when is_list(_Config) -> - tc_try(sc_lc_acceptor_response_tcp6, - fun() -> - not_yet_implemented(), - InitState = #{domain => inet, - type => stream, - protocol => tcp}, - ok = sc_lc_acceptor_response_tcp(InitState) - end). - - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -sc_lc_acceptor_response_tcp(_InitState) -> - ok. - - - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -%% This gets the local address (not 127.0...) -%% We should really implement this using the (new) net module, -%% but until that gets the necessary functionality... -which_local_addr(Domain) -> - case inet:getifaddrs() of - {ok, IFL} -> - which_addr(Domain, IFL); - {error, Reason} -> - ?FAIL({inet, getifaddrs, Reason}) - end. - -which_addr(_Domain, []) -> - ?FAIL(no_address); -which_addr(Domain, [{Name, IFO}|_IFL]) when (Name =/= "lo") -> - which_addr2(Domain, IFO); -which_addr(Domain, [_|IFL]) -> - which_addr(Domain, IFL). - -which_addr2(_Domain, []) -> - ?FAIL(no_address); -which_addr2(inet = _Domain, [{addr, Addr}|_IFO]) when (size(Addr) =:= 4) -> - Addr; -which_addr2(inet6 = _Domain, [{addr, Addr}|_IFO]) when (size(Addr) =:= 8) -> - Addr; -which_addr2(Domain, [_|IFO]) -> - which_addr2(Domain, IFO). - - - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -%% An evaluator is a process that executes a command sequence. -%% A test case will consist of atleast one evaluator (one for -%% each actor). -%% The evaluator process *always* run locally. Which means that -%% it will act as a "proxy" for remote nodes in necessary. -%% When the command sequence has been processed, the final state -%% will be used as exit reason. -%% A successful command shall evaluate to ok | {ok, NewState} - --spec evaluator_start(Name, Seq, Init) -> {Pid, MRef} when - Name :: string(), - Seq :: [command()], - Init :: initial_evaluator_state(), - Pid :: pid(), - MRef :: reference(). - -evaluator_start(Name, Seq, Init) - when is_list(Name) andalso is_list(Seq) andalso (Seq =/= []) -> - Init2 = Init#{parent => self()}, - {Pid, _} = erlang:spawn_monitor(fun() -> evaluator_init(Name, Seq, Init2) end), - Pid. - -evaluator_init(Name, Seq, Init) -> - put(sname, Name), - evaluator_loop(1, Seq, Init). - -evaluator_loop(_ID, [], FinalState) -> - exit(FinalState); -evaluator_loop(ID, [#{desc := Desc, - cmd := Cmd}|Cmds], State) when is_function(Cmd, 1) -> - ei("evaluate command ~2w: ~s", [ID, Desc]), - try Cmd(State) of - ok -> - evaluator_loop(ID + 1, Cmds, State); - {ok, NewState} -> - evaluator_loop(ID + 1, Cmds, NewState); - {error, Reason} -> - ee("command ~w failed: " - "~n Reason: ~p", [ID, Reason]), - exit({command_failed, ID, Reason, State}) - catch - C:E:S -> - ee("command ~w crashed: " - "~n Class: ~p" - "~n Error: ~p" - "~n Call Stack: ~p", [ID, C, E, S]), - exit({command_crashed, ID, {C,E,S}, State}) - end. - -await_evaluator_finish(Evs) -> - await_evaluator_finish(Evs, []). - -await_evaluator_finish([], []) -> - ok; -await_evaluator_finish([], Fails) -> - Fails; -await_evaluator_finish(Evs, Fails) -> - receive - {'DOWN', _MRef, process, Pid, normal} -> - case lists:delete(Pid, Evs) of - Evs -> - p("unknown process ~p died (normal)", [Pid]), - await_evaluator_finish(Evs, Fails); - NewEvs -> - p("evaluator ~p success", [Pid]), - await_evaluator_finish(NewEvs, Fails) - end; - {'DOWN', _MRef, process, Pid, Reason} -> - case lists:delete(Pid, Evs) of - Evs -> - p("unknown process ~p died: " - "~n ~p", [Pid, Reason]), - await_evaluator_finish(Evs, Fails); - NewEvs -> - p("Evaluator ~p failed", [Pid]), - await_evaluator_finish(NewEvs, [{Pid, Reason}|Fails]) - end - end. - - -ei(F) -> - ei(F, []). -ei(F, A) -> - eprint("", F, A). - -ee(F) -> - ee(F, []). -ee(F, A) -> - eprint(" ", F, A). - -eprint(Prefix, F, A) -> - io:format(user, "[~s][~s][~p] ~s" ++ F ++ "~n", - [formated_timestamp(), get(sname), self(), Prefix | A]). - - - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -sock_open(Domain, Type, Proto) -> - try socket:open(Domain, Type, Proto) of - {ok, Socket} -> - Socket; - {error, Reason} -> - ?FAIL({open, Reason}) - catch - C:E:S -> - ?FAIL({open, C, E, S}) - end. - - -sock_bind(Sock, SockAddr) -> - try socket:bind(Sock, SockAddr) of - {ok, Port} -> - Port; - {error, Reason} -> - p("sock_bind -> error: ~p", [Reason]), - ?FAIL({bind, Reason}) - catch - C:E:S -> - p("sock_bind -> failed: ~p, ~p, ~p", [C, E, S]), - ?FAIL({bind, C, E, S}) - end. - -sock_connect(Sock, SockAddr) -> - try socket:connect(Sock, SockAddr) of - ok -> - ok; - {error, Reason} -> - ?FAIL({connect, Reason}) - catch - C:E:S -> - ?FAIL({connect, C, E, S}) - end. - -sock_sockname(Sock) -> - try socket:sockname(Sock) of - {ok, SockAddr} -> - SockAddr; - {error, Reason} -> - ?FAIL({sockname, Reason}) - catch - C:E:S -> - ?FAIL({sockname, C, E, S}) - end. - - -%% sock_listen(Sock) -> -%% sock_listen2(fun() -> socket:listen(Sock) end). - -%% sock_listen(Sock, BackLog) -> -%% sock_listen2(fun() -> socket:listen(Sock, BackLog) end). - -%% sock_listen2(Listen) -> -%% try Listen() of -%% ok -> -%% ok; -%% {error, Reason} -> -%% ?FAIL({listen, Reason}) -%% catch -%% C:E:S -> -%% ?FAIL({listen, C, E, S}) -%% end. - - -%% sock_accept(LSock) -> -%% try socket:accept(LSock) of -%% {ok, Sock} -> -%% Sock; -%% {error, Reason} -> -%% p("sock_accept -> error: ~p", [Reason]), -%% ?FAIL({accept, Reason}) -%% catch -%% C:E:S -> -%% p("sock_accept -> failed: ~p, ~p, ~p", [C, E, S]), -%% ?FAIL({accept, C, E, S}) -%% end. - - -sock_close(Sock) -> - try socket:close(Sock) of - ok -> - ok; - {error, Reason} -> - p("sock_close -> error: ~p", [Reason]), - ?FAIL({close, Reason}) - catch - C:E:S -> - p("sock_close -> failed: ~p, ~p, ~p", [C, E, S]), - ?FAIL({close, C, E, S}) - end. - - - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -not_yet_implemented() -> - skip("not yet implemented"). - -skip(Reason) -> - throw({skip, Reason}). - - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -t() -> - os:timestamp(). - - -tdiff({A1, B1, C1} = _T1x, {A2, B2, C2} = _T2x) -> - T1 = A1*1000000000+B1*1000+(C1 div 1000), - T2 = A2*1000000000+B2*1000+(C2 div 1000), - T2 - T1. - - -formated_timestamp() -> - format_timestamp(os:timestamp()). - -format_timestamp({_N1, _N2, _N3} = TS) -> - {_Date, Time} = calendar:now_to_local_time(TS), - %% {YYYY,MM,DD} = Date, - {Hour,Min,Sec} = Time, - %% FormatTS = - %% io_lib:format("~.4w-~.2.0w-~.2.0w ~.2.0w:~.2.0w:~.2.0w.~w", - %% [YYYY, MM, DD, Hour, Min, Sec, N3]), - FormatTS = io_lib:format("~.2.0w:~.2.0w:~.2.0w", [Hour, Min, Sec]), - lists:flatten(FormatTS). - - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -set_tc_name(N) when is_atom(N) -> - set_tc_name(atom_to_list(N)); -set_tc_name(N) when is_list(N) -> - put(tc_name, N). - -%% get_tc_name() -> -%% get(tc_name). - -tc_begin(TC) -> - set_tc_name(TC), - p("begin ***"). - -tc_end(Result) when is_list(Result) -> - p("done: ~s", [Result]), - ok. - - -tc_try(Case, Fun) when is_atom(Case) andalso is_function(Fun, 0) -> - tc_begin(Case), - try - begin - Fun(), - tc_end("ok") - end - catch - throw:{skip, _} = SKIP -> - tc_end("skipping"), - SKIP; - Class:Error:Stack -> - tc_end("failed"), - erlang:raise(Class, Error, Stack) - end. - - - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -%% f(F, A) -> -%% lists:flatten(io_lib:format(F, A)). - -p(F) -> - p(F, []). - -p(F, A) -> - TcName = - case get(tc_name) of - undefined -> - case get(sname) of - undefined -> - ""; - SName when is_list(SName) -> - SName - end; - Name when is_list(Name) -> - Name - end, - i("*** [~s][~s][~p] " ++ F, [formated_timestamp(),TcName,self()|A]). - - -%% i(F) -> -%% i(F, []). - -i(F, A) -> - io:format(user, F ++ "~n", A). diff --git a/lib/kernel/test/socket_client.erl b/lib/kernel/test/socket_client.erl deleted file mode 100644 index 1c07e799b8..0000000000 --- a/lib/kernel/test/socket_client.erl +++ /dev/null @@ -1,538 +0,0 @@ -%% -%% %CopyrightBegin% -%% -%% Copyright Ericsson AB 2018-2018. All Rights Reserved. -%% -%% Licensed under the Apache License, Version 2.0 (the "License"); -%% you may not use this file except in compliance with the License. -%% You may obtain a copy of the License at -%% -%% http://www.apache.org/licenses/LICENSE-2.0 -%% -%% Unless required by applicable law or agreed to in writing, software -%% distributed under the License is distributed on an "AS IS" BASIS, -%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -%% See the License for the specific language governing permissions and -%% limitations under the License. -%% -%% %CopyrightEnd% -%% - --module(socket_client). - --export([ - start/1, start/2, start/5, start/6, - start_tcp/1, start_tcp/2, start_tcp/3, - start_tcp4/1, start_tcp4/2, start_tcp6/1, start_tcp6/2, - start_udp/1, start_udp/2, start_udp/3, - start_udp4/1, start_udp4/2, start_udp6/1, start_udp6/2 - ]). - --define(LIB, socket_lib). - --record(client, {socket, verbose = true, msg = true, type, dest, msg_id = 1}). - -start(Port) -> - start(Port, 1). - -start(Port, Num) -> - start_tcp(Port, Num). - -start_tcp(Port) -> - start_tcp(Port, 1). - -start_tcp(Port, Num) -> - start_tcp4(Port, Num). - -start_tcp4(Port) -> - start_tcp4(Port, 1). - -start_tcp4(Port, Num) -> - start(inet, stream, tcp, Port, Num). - -start_tcp6(Port) -> - start_tcp6(Port, 1). - -start_tcp6(Port, Num) -> - start(inet6, stream, tcp, Port, Num). - -start_tcp(Addr, Port, Num) when (size(Addr) =:= 4) andalso - is_integer(Num) andalso - (Num > 0) -> - start(inet, stream, tcp, Addr, Port, Num); -start_tcp(Addr, Port, Num) when (size(Addr) =:= 8) andalso - is_integer(Num) andalso - (Num > 0) -> - start(inet6, stream, tcp, Addr, Port, Num). - - -start_udp(Port) -> - start_udp(Port, 1). - -start_udp(Port, Num) -> - start_udp4(Port, Num). - -start_udp4(Port) -> - start_udp4(Port, 1). - -start_udp4(Port, Num) -> - start(inet, dgram, udp, Port, Num). - -start_udp6(Port) -> - start_udp6(Port, 1). - -start_udp6(Port, Num) -> - start(inet6, dgram, udp, Port, Num). - -start_udp(Addr, Port, Num) when (size(Addr) =:= 4) -> - start(inet, dgram, udp, Addr, Port, Num); -start_udp(Addr, Port, Num) when (size(Addr) =:= 8) -> - start(inet6, dgram, udp, Addr, Port, Num). - - -start(Domain, Type, Proto, Port, Num) - when is_integer(Port) andalso is_integer(Num) -> - start(Domain, Type, Proto, which_addr(Domain), Port, Num); - -start(Domain, Type, Proto, Addr, Port) -> - start(Domain, Type, Proto, Addr, Port, 1). - -start(Domain, Type, Proto, Addr, Port, 1 = Num) -> - start(Domain, Type, Proto, Addr, Port, Num, true); -start(Domain, Type, Proto, Addr, Port, Num) - when is_integer(Num) andalso (Num > 1) -> - start(Domain, Type, Proto, Addr, Port, Num, false). - -start(Domain, Type, Proto, Addr, Port, Num, Verbose) -> - put(sname, "starter"), - Clients = start_clients(Num, Domain, Type, Proto, Addr, Port, Verbose), - await_clients(Clients). - -start_clients(Num, Domain, Type, Proto, Addr, Port, Verbose) -> - start_clients(Num, 1, Domain, Type, Proto, Addr, Port, Verbose, []). - -start_clients(Num, ID, Domain, Type, Proto, Addr, Port, Verbose, Acc) - when (Num > 0) -> - StartClient = fun() -> - start_client(ID, Domain, Type, Proto, Addr, Port, Verbose) - end, - {Pid, _} = spawn_monitor(StartClient), - ?LIB:sleep(500), - i("start client ~w", [ID]), - start_clients(Num-1, ID+1, Domain, Type, Proto, Addr, Port, Verbose, [Pid|Acc]); -start_clients(_, _, _, _, _, _, _, _, Acc) -> - i("all client(s) started"), - lists:reverse(Acc). - -await_clients([]) -> - i("all clients done"); -await_clients(Clients) -> - receive - {'DOWN', _MRef, process, Pid, _Reason} -> - case lists:delete(Pid, Clients) of - Clients2 when (Clients2 =/= Clients) -> - i("client ~p done", [Pid]), - await_clients(Clients2); - _ -> - await_clients(Clients) - end - end. - - -start_client(ID, Domain, Type, Proto, Addr, Port, Verbose) -> - put(sname, ?LIB:f("client[~w]", [ID])), - SA = #{family => Domain, - addr => Addr, - port => Port}, - %% The way we use tos only works because we - %% send so few messages (a new value for every - %% message). - tos_init(), - do_start(Domain, Type, Proto, SA, Verbose). - -do_start(Domain, stream = Type, Proto, SA, Verbose) -> - try do_init(Domain, Type, Proto) of - Sock -> - connect(Sock, SA), - maybe_print_start_info(Verbose, Sock, Type), - %% Give the server some time... - ?LIB:sleep(5000), - %% ok = socket:close(Sock), - send_loop(#client{socket = Sock, - type = Type, - verbose = Verbose}) - catch - throw:E -> - e("Failed initiate: " - "~n Error: ~p", [E]) - end; -do_start(Domain, dgram = Type, Proto, SA, Verbose) -> - try do_init(Domain, Type, Proto) of - Sock -> - maybe_print_start_info(Verbose, Sock, Type), - %% Give the server some time... - ?LIB:sleep(5000), - %% ok = socket:close(Sock), - send_loop(#client{socket = Sock, - type = Type, - dest = SA, - verbose = Verbose}) - catch - throw:E -> - e("Failed initiate: " - "~n Error: ~p", [E]) - end. - -maybe_print_start_info(true = _Verbose, Sock, stream = _Type) -> - {ok, Name} = socket:sockname(Sock), - {ok, Peer} = socket:peername(Sock), - {ok, Domain} = socket:getopt(Sock, socket, domain), - {ok, Type} = socket:getopt(Sock, socket, type), - {ok, Proto} = socket:getopt(Sock, socket, protocol), - {ok, OOBI} = socket:getopt(Sock, socket, oobinline), - {ok, SndBuf} = socket:getopt(Sock, socket, sndbuf), - {ok, RcvBuf} = socket:getopt(Sock, socket, rcvbuf), - {ok, Linger} = socket:getopt(Sock, socket, linger), - {ok, MTU} = socket:getopt(Sock, ip, mtu), - {ok, MTUDisc} = socket:getopt(Sock, ip, mtu_discover), - {ok, MALL} = socket:getopt(Sock, ip, multicast_all), - {ok, MIF} = socket:getopt(Sock, ip, multicast_if), - {ok, MLoop} = socket:getopt(Sock, ip, multicast_loop), - {ok, MTTL} = socket:getopt(Sock, ip, multicast_ttl), - {ok, RecvTOS} = socket:getopt(Sock, ip, recvtos), - i("connected: " - "~n From: ~p" - "~n To: ~p" - "~nwhen" - "~n (socket) Domain: ~p" - "~n (socket) Type: ~p" - "~n (socket) Protocol: ~p" - "~n (socket) OOBInline: ~p" - "~n (socket) SndBuf: ~p" - "~n (socket) RcvBuf: ~p" - "~n (socket) Linger: ~p" - "~n (ip) MTU: ~p" - "~n (ip) MTU Discovery: ~p" - "~n (ip) Multicast ALL: ~p" - "~n (ip) Multicast IF: ~p" - "~n (ip) Multicast Loop: ~p" - "~n (ip) Multicast TTL: ~p" - "~n (ip) RecvTOS: ~p" - "~n => wait some", - [Name, Peer, - Domain, Type, Proto, - OOBI, SndBuf, RcvBuf, Linger, - MTU, MTUDisc, MALL, MIF, MLoop, MTTL, - RecvTOS]); -maybe_print_start_info(true = _Verbose, Sock, dgram = _Type) -> - {ok, Domain} = socket:getopt(Sock, socket, domain), - {ok, Type} = socket:getopt(Sock, socket, type), - {ok, Proto} = socket:getopt(Sock, socket, protocol), - {ok, OOBI} = socket:getopt(Sock, socket, oobinline), - {ok, SndBuf} = socket:getopt(Sock, socket, sndbuf), - {ok, RcvBuf} = socket:getopt(Sock, socket, rcvbuf), - {ok, Linger} = socket:getopt(Sock, socket, linger), - {ok, MALL} = socket:getopt(Sock, ip, multicast_all), - {ok, MIF} = socket:getopt(Sock, ip, multicast_if), - {ok, MLoop} = socket:getopt(Sock, ip, multicast_loop), - {ok, MTTL} = socket:getopt(Sock, ip, multicast_ttl), - {ok, RecvTOS} = socket:getopt(Sock, ip, recvtos), - {ok, RecvTTL} = socket:getopt(Sock, ip, recvttl), - i("initiated when: " - "~n (socket) Domain: ~p" - "~n (socket) Type: ~p" - "~n (socket) Protocol: ~p" - "~n (socket) OOBInline: ~p" - "~n (socket) SndBuf: ~p" - "~n (socket) RcvBuf: ~p" - "~n (socket) Linger: ~p" - "~n (ip) Multicast ALL: ~p" - "~n (ip) Multicast IF: ~p" - "~n (ip) Multicast Loop: ~p" - "~n (ip) Multicast TTL: ~p" - "~n (ip) RecvTOS: ~p" - "~n (ip) RecvTTL: ~p" - "~n => wait some", - [Domain, Type, Proto, - OOBI, SndBuf, RcvBuf, Linger, - MALL, MIF, MLoop, MTTL, - RecvTOS, RecvTTL]); -maybe_print_start_info(_Verbose, _Sock, _Type) -> - ok. - - -do_init(Domain, stream = Type, Proto) -> - i("try (socket) open"), - Sock = case socket:open(Domain, Type, Proto) of - {ok, S} -> - S; - {error, OReason} -> - throw({open, OReason}) - end, - i("try (socket) bind"), - case socket:bind(Sock, any) of - {ok, _P} -> - ok = socket:setopt(Sock, socket, timestamp, true), - ok = socket:setopt(Sock, ip, tos, mincost), - ok = socket:setopt(Sock, ip, recvtos, true), - Sock; - {error, BReason} -> - throw({bind, BReason}) - end; -do_init(Domain, dgram = Type, Proto) -> - i("try (socket) open"), - Sock = case socket:open(Domain, Type, Proto) of - {ok, S} -> - S; - {error, OReason} -> - throw({open, OReason}) - end, - case socket:bind(Sock, any) of - {ok, _} -> - ok = socket:setopt(Sock, socket, timestamp, true), - ok = socket:setopt(Sock, ip, tos, mincost), - ok = socket:setopt(Sock, ip, recvtos, true), - ok = socket:setopt(Sock, ip, recvttl, true), - Sock; - {error, BReason} -> - throw({bind, BReason}) - end. - - -which_addr(Domain) -> - Iflist = case inet:getifaddrs() of - {ok, IFL} -> - IFL; - {error, Reason} -> - throw({inet,getifaddrs,Reason}) - end, - which_addr(Domain, Iflist). - - -connect(Sock, SA) -> - i("try (socket) connect to:" - "~n ~p", [SA]), - case socket:connect(Sock, SA) of - ok -> - ok; - {error, Reason} -> - e("connect failure: " - "~n ~p", [Reason]), - exit({connect, Reason}) - end. - - -send_loop(#client{msg_id = N} = C) when (N =< 10) -> - i("try send request ~w", [N]), - Req = ?LIB:enc_req_msg(N, "hejsan"), - case send(C, Req) of - ok -> - i("request ~w sent - now try read answer", [N]), - case recv(C) of - {ok, {Source, Msg}} -> - if - (C#client.verbose =:= true) -> - i("received ~w bytes of data~s", - [size(Msg), case Source of - undefined -> ""; - _ -> ?LIB:f(" from:~n ~p", [Source]) - end]); - true -> - i("received ~w bytes", [size(Msg)]) - end, - case ?LIB:dec_msg(Msg) of - {reply, N, Reply} -> - if - (C#client.verbose =:= true) -> - i("received reply ~w: ~p", [N, Reply]); - true -> - i("received reply ~w", [N]) - end, - ?LIB:sleep(500), % Just to spread it out a bit - send_loop(C#client{msg_id = N+1}) - end; - {error, RReason} -> - e("Failed recv response for request ~w: " - "~n ~p", [N, RReason]), - exit({failed_recv, RReason}) - end; - {error, SReason} -> - e("Failed send request ~w: " - "~n ~p", [N, SReason]), - exit({failed_send, SReason}) - end; -send_loop(Client) -> - sock_close(Client). - -sock_close(#client{socket = Sock, verbose = true}) -> - i("we are done - close the socket when: " - "~n ~p", [socket:info()]), - ok = socket:close(Sock), - i("we are done - socket closed when: " - "~n ~p", [socket:info()]); -sock_close(#client{socket = Sock}) -> - i("we are done"), - ok = socket:close(Sock). - - - -send(#client{socket = Sock, type = stream}, Msg) -> - socket:send(Sock, Msg); -send(#client{socket = Sock, type = dgram, dest = Dest}, Msg) -> - %% i("try send to: " - %% "~n ~p", [Dest]), - %% ok = socket:setopt(Sock, otp, debug, true), - TOS = tos_next(), - ok = socket:setopt(Sock, ip, tos, TOS), - case socket:sendto(Sock, Msg, Dest) of - ok = OK -> - OK; - {error, _} = ERROR -> - ERROR - end. - -recv(#client{socket = Sock, type = stream, msg = false}) -> - case socket:recv(Sock) of - {ok, Msg} -> - {ok, {undefined, Msg}}; - {error, _} = ERROR -> - ERROR - end; -recv(#client{socket = Sock, verbose = Verbose, type = stream, msg = true}) -> - case socket:recvmsg(Sock) of - %% An iov of length 1 is an simplification... - {ok, #{addr := undefined = Source, - iov := [Msg], - ctrl := CMsgHdrs, - flags := Flags}} -> - if - (Verbose =:= true) -> - i("received message: " - "~n CMsgHdr: ~p" - "~n Flags: ~p", [CMsgHdrs, Flags]); - true -> - ok - end, - {ok, {Source, Msg}}; - {error, _} = ERROR -> - ERROR - end; -recv(#client{socket = Sock, type = dgram, msg = false}) -> - socket:recvfrom(Sock); -recv(#client{socket = Sock, verbose = Verbose, type = dgram, msg = true}) -> - case socket:recvmsg(Sock) of - {ok, #{addr := Source, - iov := [Msg], - ctrl := CMsgHdrs, - flags := Flags}} -> - if - (Verbose =:= true) -> - i("received message: " - "~n CMsgHdr: ~p" - "~n Flags: ~p", [CMsgHdrs, Flags]); - true -> - ok - end, - {ok, {Source, Msg}}; - {error, _} = ERROR -> - ERROR - end. - - - -which_addr(_Domain, []) -> - throw(no_address); -which_addr(Domain, [{Name, IFO}|_IFL]) when (Name =/= "lo") -> - which_addr2(Domain, IFO); -which_addr(Domain, [_|IFL]) -> - which_addr(Domain, IFL). - -which_addr2(inet = _Domain, [{addr, Addr}|_IFO]) when (size(Addr) =:= 4) -> - Addr; -which_addr2(inet6 = _Domain, [{addr, Addr}|_IFO]) when (size(Addr) =:= 8) -> - Addr; -which_addr2(Domain, [_|IFO]) -> - which_addr2(Domain, IFO). - - -%% --- - -%% enc_req_msg(N, Data) -> -%% enc_msg(?REQ, N, Data). - -%% enc_rep_msg(N, Data) -> -%% enc_msg(?REP, N, Data). - -%% enc_msg(Type, N, Data) when is_list(Data) -> -%% enc_msg(Type, N, list_to_binary(Data)); -%% enc_msg(Type, N, Data) -%% when is_integer(Type) andalso is_integer(N) andalso is_binary(Data) -> -%% <>. - -%% dec_msg(<>) -> -%% {request, N, Data}; -%% dec_msg(<>) -> -%% {reply, N, Data}. - - -%% --- - -%% sleep(T) -> -%% receive after T -> ok end. - - -%% --- - -%% formated_timestamp() -> -%% format_timestamp(os:timestamp()). - -%% format_timestamp(Now) -> -%% N2T = fun(N) -> calendar:now_to_local_time(N) end, -%% format_timestamp(Now, N2T, true). - -%% format_timestamp({_N1, _N2, N3} = N, N2T, true) -> -%% FormatExtra = ".~.2.0w", -%% ArgsExtra = [N3 div 10000], -%% format_timestamp(N, N2T, FormatExtra, ArgsExtra); -%% format_timestamp({_N1, _N2, _N3} = N, N2T, false) -> -%% FormatExtra = "", -%% ArgsExtra = [], -%% format_timestamp(N, N2T, FormatExtra, ArgsExtra). - -%% format_timestamp(N, N2T, FormatExtra, ArgsExtra) -> -%% {Date, Time} = N2T(N), -%% {YYYY,MM,DD} = Date, -%% {Hour,Min,Sec} = Time, -%% FormatDate = -%% io_lib:format("~.4w-~.2.0w-~.2.0w ~.2.0w:~.2.0w:~.2.0w" ++ FormatExtra, -%% [YYYY, MM, DD, Hour, Min, Sec] ++ ArgsExtra), -%% lists:flatten(FormatDate). - - -%% --- - -tos_init() -> - put(tos, 1). - -tos_next() -> - case get(tos) of - TOS when (TOS < 100) -> - put(tos, TOS + 1), - TOS; - _ -> - put(tos, 1), - 1 - end. - - -%% --- - -e(F, A) -> - ?LIB:e(F, A). - -i(F) -> - ?LIB:i(F). - -i(F, A) -> - ?LIB:i(F, A). - diff --git a/lib/kernel/test/socket_lib.erl b/lib/kernel/test/socket_lib.erl deleted file mode 100644 index 9d6524d467..0000000000 --- a/lib/kernel/test/socket_lib.erl +++ /dev/null @@ -1,133 +0,0 @@ -%% -%% %CopyrightBegin% -%% -%% Copyright Ericsson AB 2018-2018. All Rights Reserved. -%% -%% Licensed under the Apache License, Version 2.0 (the "License"); -%% you may not use this file except in compliance with the License. -%% You may obtain a copy of the License at -%% -%% http://www.apache.org/licenses/LICENSE-2.0 -%% -%% Unless required by applicable law or agreed to in writing, software -%% distributed under the License is distributed on an "AS IS" BASIS, -%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -%% See the License for the specific language governing permissions and -%% limitations under the License. -%% -%% %CopyrightEnd% -%% - --module(socket_lib). - --export([ - sleep/1, - req/0, rep/0, - enc_req_msg/2, enc_rep_msg/2, - enc_msg/3, dec_msg/1, - request/3, reply/4, - f/2, - i/1, i/2, - e/2 - ]). - - --define(REQ, 0). --define(REP, 1). - - -%% --- - -sleep(T) -> - receive after T -> ok end. - - -%% --- - -req() -> ?REQ. -rep() -> ?REP. - -enc_req_msg(N, Data) -> - enc_msg(?REQ, N, Data). - -enc_rep_msg(N, Data) -> - enc_msg(?REP, N, Data). - -enc_msg(Type, N, Data) when is_list(Data) -> - enc_msg(Type, N, list_to_binary(Data)); -enc_msg(Type, N, Data) - when is_integer(Type) andalso is_integer(N) andalso is_binary(Data) -> - <>. - -dec_msg(<>) -> - {request, N, Data}; -dec_msg(<>) -> - {reply, N, Data}. - - -%% --- - -request(Tag, Pid, Request) -> - Ref = make_ref(), - Pid ! {Tag, self(), Ref, Request}, - receive - {Tag, Pid, Ref, Reply} -> - Reply - end. - -reply(Tag, Pid, Ref, Reply) -> - Pid ! {Tag, self(), Ref, Reply}. - - -%% --- - -f(F, A) -> - lists:flatten(io_lib:format(F, A)). - - -%% --- - -e(F, A) -> - p(" " ++ F, A). - -i(F) -> - i(F, []). -i(F, A) -> - p("*** " ++ F, A). - -p(F, A) -> - p(get(sname), F, A). - -p(SName, F, A) -> - io:format("[~s,~p][~s] " ++ F ++ "~n", - [SName,self(),formated_timestamp()|A]). - - -%% --- - -formated_timestamp() -> - format_timestamp(os:timestamp()). - -format_timestamp(Now) -> - N2T = fun(N) -> calendar:now_to_local_time(N) end, - format_timestamp(Now, N2T, true). - -format_timestamp({_N1, _N2, N3} = N, N2T, true) -> - FormatExtra = ".~.2.0w", - ArgsExtra = [N3 div 10000], - format_timestamp(N, N2T, FormatExtra, ArgsExtra); -format_timestamp({_N1, _N2, _N3} = N, N2T, false) -> - FormatExtra = "", - ArgsExtra = [], - format_timestamp(N, N2T, FormatExtra, ArgsExtra). - -format_timestamp(N, N2T, FormatExtra, ArgsExtra) -> - {Date, Time} = N2T(N), - {YYYY,MM,DD} = Date, - {Hour,Min,Sec} = Time, - FormatDate = - io_lib:format("~.4w-~.2.0w-~.2.0w ~.2.0w:~.2.0w:~.2.0w" ++ FormatExtra, - [YYYY, MM, DD, Hour, Min, Sec] ++ ArgsExtra), - lists:flatten(FormatDate). - - diff --git a/lib/kernel/test/socket_server.erl b/lib/kernel/test/socket_server.erl deleted file mode 100644 index 45adffc5e6..0000000000 --- a/lib/kernel/test/socket_server.erl +++ /dev/null @@ -1,954 +0,0 @@ -%% -%% %CopyrightBegin% -%% -%% Copyright Ericsson AB 2018-2018. All Rights Reserved. -%% -%% Licensed under the Apache License, Version 2.0 (the "License"); -%% you may not use this file except in compliance with the License. -%% You may obtain a copy of the License at -%% -%% http://www.apache.org/licenses/LICENSE-2.0 -%% -%% Unless required by applicable law or agreed to in writing, software -%% distributed under the License is distributed on an "AS IS" BASIS, -%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -%% See the License for the specific language governing permissions and -%% limitations under the License. -%% -%% %CopyrightEnd% -%% - --module(socket_server). - --export([ - start/0, start/5, - start_tcp/0, start_tcp/1, start_tcp/3, - start_tcp4/0, start_tcp4/1, start_tcp4/2, - start_tcp6/0, start_tcp6/1, start_tcp6/2, - start_udp/0, start_udp/1, start_udp/3, - start_udp4/0, start_udp4/1, start_udp4/2, - start_udp6/0, start_udp6/1, start_udp6/2, - start_sctp/0, start_sctp/1 - ]). - --define(LIB, socket_lib). - --record(manager, {socket, msg, peek, acceptors, handler_id, handlers}). --record(acceptor, {id, socket, manager, - atimeout = 5000}). --record(handler, {socket, peek, msg, type, manager, - stimeout = 5000, rtimeout = 5000}). - --define(NUM_ACCEPTORS, 5). - -start() -> - start_tcp(). - -start_tcp() -> - start_tcp4(). - -start_tcp(Peek) -> - start_tcp4(Peek). - -start_tcp4() -> - start_tcp4(false). - -start_tcp4(Peek) -> - start_tcp4(false, Peek). - -start_tcp4(UseMsg, Peek) -> - start_tcp(inet, UseMsg, Peek). - -start_tcp6() -> - start_tcp6(false). - -start_tcp6(Peek) -> - start_tcp6(false, Peek). - -start_tcp6(UseMsg, Peek) -> - start_tcp(inet6, UseMsg, Peek). - -start_tcp(Domain, UseMsg, Peek) when is_boolean(UseMsg) andalso is_boolean(Peek) -> - start(Domain, stream, tcp, UseMsg, Peek). - -start_udp() -> - start_udp4(). - -start_udp(Peek) -> - start_udp4(Peek). - -start_udp4() -> - start_udp4(false). - -start_udp4(Peek) -> - start_udp4(false, Peek). - -start_udp4(UseMsg, Peek) -> - start_udp(inet, UseMsg, Peek). - -start_udp6() -> - start_udp6(false, false). - -start_udp6(Peek) -> - start_udp6(false, Peek). - -start_udp6(UseMsg, Peek) -> - start_udp(inet6, UseMsg, Peek). - -start_udp(Domain, UseMsg, Peek) when is_boolean(UseMsg) andalso is_boolean(Peek) -> - start(Domain, dgram, udp, UseMsg, Peek). - - -start_sctp() -> - start_sctp(inet). - -start_sctp(Domain) when ((Domain =:= inet) orelse (Domain =:= inet6)) -> - start(Domain, seqpacket, sctp, true, false). - -start(Domain, Type, Proto, UseMsg, Peek) -> - put(sname, "starter"), - i("try start manager"), - {Pid, MRef} = manager_start(Domain, Type, Proto, UseMsg, Peek), - i("manager (~p) started", [Pid]), - loop(Pid, MRef). - -loop(Pid, MRef) -> - receive - {'DOWN', MRef, process, Pid, Reason} -> - i("manager process exited: " - "~n ~p", [Reason]), - ok - end. - - -%% ========================================================================= - -manager_start(Domain, Type, Proto, UseMsg, Peek) -> - spawn_monitor(fun() -> manager_init(Domain, Type, Proto, UseMsg, Peek) end). - -manager_start_handler(Pid, Sock) -> - manager_request(Pid, {start_handler, Sock}). - -manager_stop(Pid, Reason) -> - manager_request(Pid, {stop, Reason}). - -manager_request(Pid, Request) -> - ?LIB:request(manager, Pid, Request). - -manager_reply(Pid, Ref, Reply) -> - ?LIB:reply(manager, Pid, Ref, Reply). - - -manager_init(Domain, Type, Proto, UseMsg, Peek) -> - put(sname, "manager"), - do_manager_init(Domain, Type, Proto, UseMsg, Peek). - -do_manager_init(Domain, stream = Type, Proto, UseMsg, Peek) -> - i("try start acceptor(s)"), - {Sock, Acceptors} = manager_stream_init(Domain, Type, Proto), - manager_loop(#manager{socket = Sock, - msg = UseMsg, - peek = Peek, - acceptors = Acceptors, - handler_id = 1, - handlers = []}); -do_manager_init(Domain, dgram = Type, Proto, UseMsg, Peek) -> - i("try open socket"), - case socket:open(Domain, Type, Proto) of - {ok, Sock} -> - F = fun(X) -> case socket:getopt(Sock, socket, X) of - {ok, V} -> f("~p", [V]); - {error, R} -> f("error: ~p", [R]) - end - end, - i("socket opened (~s,~s,~s): " - "~n broadcast: ~s" - "~n dontroute: ~s" - "~n keepalive: ~s" - "~n reuseaddr: ~s" - "~n linger: ~s" - "~n debug: ~s" - "~n prio: ~s" - "~n rcvbuf: ~s" - "~n rcvtimeo: ~s" - "~n sndbuf: ~s" - "~n sndtimeo: ~s" - "~n => try find (local) address", - [F(domain), F(type), F(protocol), - F(broadcast), F(dontroute), F(keepalive), F(reuseaddr), F(linger), - F(debug), F(priority), - F(rcvbuf), F(rcvtimeo), F(sndbuf), F(sndtimeo)]), - Addr = which_addr(Domain), - SA = #{family => Domain, - addr => Addr}, - i("try bind to: " - "~n ~p", [Addr]), - case socket:bind(Sock, SA) of - {ok, _P} -> - ok; - {error, BReason} -> - throw({bind, BReason}) - end, - i("bound to: " - "~n ~s" - "~n => try start handler", - [case socket:sockname(Sock) of - {ok, Name} -> f("~p", [Name]); - {error, R} -> f("error: ~p", [R]) - end]), - case handler_start(1, Sock, UseMsg, Peek) of - {ok, {Pid, MRef}} -> - i("handler (~p) started", [Pid]), - handler_continue(Pid), - manager_loop(#manager{peek = Peek, - msg = UseMsg, - handler_id = 2, % Just in case - handlers = [{1, Pid, MRef}]}); - {error, SReason} -> - e("Failed starting handler: " - "~n ~p", [SReason]), - exit({failed_start_handler, SReason}) - end; - {error, OReason} -> - e("Failed open socket: " - "~n ~p", [OReason]), - exit({failed_open_socket, OReason}) - end; -do_manager_init(Domain, seqpacket = Type, sctp = Proto, _UseMsg, _Peek) -> - %% This is as far as I have got with SCTP at the moment... - case socket:open(Domain, Type, Proto) of - {ok, Sock} -> - i("(sctp) socket opened: " - "~n ~p", [Sock]), - EXP = fun(_Desc, Expect, Expect) -> - Expect; - (Desc, Expect, Actual) -> - e("Unexpected result ~w: " - "~n Expect: ~p" - "~n Actual: ~p", [Desc, Expect, Actual]), - exit({Desc, Expect, Actual}) - end, - GO = fun(O) -> case socket:getopt(Sock, sctp, O) of - {ok, V} -> f("~p", [V]); - {error, R} -> f("error: ~p", [R]) - end - end, - %% ok = socket:setopt(Sock, otp, debug, true), - - i("Miscellaneous options: " - "~n associnfo: ~s" - "~n autoclose: ~s" - "~n disable-fragments: ~s" - "~n initmsg: ~s" - "~n maxseg: ~s" - "~n nodelay: ~s" - "~n rtoinfo: ~s", - [GO(associnfo), - GO(autoclose), - GO(disable_fragments), - GO(initmsg), - GO(maxseg), - GO(nodelay), - GO(rtoinfo)]), - - Events = #{data_in => true, - association => true, - address => true, - send_failure => true, - peer_error => true, - shutdown => true, - partial_delivery => true, - adaptation_layer => true, - authentication => true, - sender_dry => true}, - EXP(set_sctp_events, ok, socket:setopt(Sock, sctp, events, Events)), - EXP(close_socket, ok, socket:close(Sock)); - {error, Reason} -> - exit({failed_open, Reason}) - end; -do_manager_init(Domain, raw = Type, Proto, UseMsg, Peek) when is_integer(Proto) -> - do_manager_init(Domain, Type, {raw, Proto}, UseMsg, Peek); -do_manager_init(Domain, raw = Type, Proto, _UseMsg, _Peek) -> - case socket:open(Domain, Type, Proto) of - {ok, Sock} -> - i("(sctp) socket opened: " - "~n ~p", [Sock]), - socket:close(Sock); - {error, Reason} -> - exit({failed_open, Reason}) - end. - - - -manager_stream_init(Domain, Type, Proto) -> - i("try (socket) open"), - Sock = case socket:open(Domain, Type, Proto) of - {ok, S} -> - S; - {error, OReason} -> - throw({open, OReason}) - end, - F = fun(X) -> case socket:getopt(Sock, socket, X) of - {ok, V} -> f("~p", [V]); - {error, R} -> f("error: ~p", [R]) - end - end, - i("(socket) open (~s,~s,~s): " - "~n debug: ~s" - "~n prio: ~s" - "~n => try find (local) address", - [F(domain), F(type), F(protocol), F(debug), F(priority)]), - Addr = which_addr(Domain), - SA = #{family => Domain, - addr => Addr}, - i("found: " - "~n ~p" - "~n => try (socket) bind", [Addr]), - %% ok = socket:setopt(Sock, otp, debug, true), - %% ok = socket:setopt(Sock, socket, debug, 1), %% must have rights!! - Port = case socket:bind(Sock, SA) of - {ok, P} -> - %% ok = socket:setopt(Sock, socket, debug, 0), %% must have rights!! - %% ok = socket:setopt(Sock, otp, debug, false), - P; - {error, BReason} -> - throw({bind, BReason}) - end, - i("bound to: " - "~n ~p" - "~n => try (socket) listen (acceptconn: ~s)", - [Port, F(acceptconn)]), - case socket:listen(Sock) of - ok -> - i("listening (acceptconn: ~s)", - [F(acceptconn)]), - manager_stream_init(Sock, 1, ?NUM_ACCEPTORS, []); - {error, LReason} -> - throw({listen, LReason}) - end. - -which_addr(Domain) -> - Iflist = case inet:getifaddrs() of - {ok, IFL} -> - IFL; - {error, Reason} -> - throw({inet,getifaddrs,Reason}) - end, - which_addr(Domain, Iflist). - -which_addr(_Domain, []) -> - throw(no_address); -which_addr(Domain, [{Name, IFO}|_IFL]) when (Name =/= "lo") -> - which_addr2(Domain, IFO); -which_addr(Domain, [_|IFL]) -> - which_addr(Domain, IFL). - -which_addr2(_, []) -> - throw(no_address); -which_addr2(inet = _Domain, [{addr, Addr}|_IFO]) when (size(Addr) =:= 4) -> - Addr; -which_addr2(inet6 = _Domain, [{addr, Addr}|_IFO]) when (size(Addr) =:= 8) -> - Addr; -which_addr2(Domain, [_|IFO]) -> - which_addr2(Domain, IFO). - - -manager_stream_init(Sock, ID, NumAcceptors, Acc) - when (NumAcceptors > 0) -> - i("try start acceptor"), - case acceptor_start(Sock, ID) of - {ok, {Pid, MRef}} -> - i("acceptor ~w (~p) started", [ID, Pid]), - ?LIB:sleep(2000), - manager_stream_init(Sock, ID+1, NumAcceptors-1, - [{ID, Pid, MRef}|Acc]); - {error, Reason} -> - exit({failed_starting_acceptor, Reason}) - end; -manager_stream_init(Sock, _ID, 0, Acc) -> - %% Req = {kill_acceptor, length(Acc)}, % Last in the queue - %% Req = {kill_acceptor, 3}, % In the "middle" of the queue - %% Req = {kill_acceptor, 2}, % The first in the queue - %% Req = {kill_acceptor, 1}, % Current acceptor - %% Msg = {manager, self(), make_ref(), Req}, - %% erlang:send_after(timer:seconds(10), self(), Msg), - {Sock, lists:reverse(Acc)}. - - -manager_loop(M) -> - receive - {'DOWN', MRef, process, Pid, Reason} -> - M2 = manager_handle_down(M, MRef, Pid, Reason), - manager_loop(M2); - - {manager, Pid, Ref, Request} -> - M2 = manager_handle_request(M, Pid, Ref, Request), - manager_loop(M2) - end. - - -manager_handle_down(#manager{acceptors = Acceptors, - handlers = Handlers} = M, MRef, Pid, Reason) -> - case lists:keysearch(Pid, 2, Acceptors) of - {value, {ID, Pid, MRef}} when (Reason =:= normal) -> - i("acceptor ~w exited (normally)", [ID]), - case lists:keydelete(Pid, 2, Acceptors) of - [] -> - %% We are done - i("the last acceptor - we are done"), - exit(normal); - Acceptors2 -> - M#manager{acceptors = Acceptors2} - end; - {value, {ID, Pid, MRef}} -> - e("acceptor ~w crashed: " - "~n ~p", [ID, Reason]), - exit({acceptor_died, Reason}); - - false -> %% handler! - if - (Reason =/= normal) -> - e("handler ~p died: " - "~n ~p", [Pid, Reason]); - true -> - i("handler ~p terminated", [Pid]) - end, - Handlers2 = lists:keydelete(Pid, 2, Handlers), - M#manager{handlers = Handlers2} - end. - - -manager_handle_request(#manager{peek = Peek, - msg = UseMsg, - handler_id = HID, - handlers = Handlers} = M, Pid, Ref, - {start_handler, Sock}) -> - i("try start handler (~w)", [HID]), - case handler_start(HID, Sock, UseMsg, Peek) of - {ok, {HPid, HMRef}} -> - i("handler ~w started", [HID]), - manager_reply(Pid, Ref, {ok, HPid}), - M#manager{handler_id = HID+1, - handlers = [{HID, HPid, HMRef}|Handlers]}; - {error, Reason} = ERROR -> - e("Failed starting new handler: " - "~n Sock: ~p" - "~n Reason: ~p", [Sock, Reason]), - manager_reply(Pid, Ref, ERROR), - M - end; -manager_handle_request(#manager{socket = Sock, - acceptors = [{AID, APid, AMRef}]} = M, _Pid, _Ref, - {kill_acceptor, AID}) -> - i("try kill (only remeining) acceptor ~w", [AID]), - socket:setopt(Sock, otp, debug, true), - manager_stop_acceptor(APid, AMRef, AID, kill), - M#manager{acceptors = []}; -manager_handle_request(#manager{socket = Sock, - acceptors = Acceptors} = M, _Pid, _Ref, - {kill_acceptor, AID}) -> - i("try kill acceptor ~w", [AID]), - case lists:keysearch(AID, 1, Acceptors) of - {value, {AID, APid, AMRef}} -> - socket:setopt(Sock, otp, debug, true), - manager_stop_acceptor(APid, AMRef, AID, kill), - Acceptors2 = lists:keydelete(AID, 1, Acceptors), - M#manager{acceptors = Acceptors2}; - false -> - e("no such acceptor"), - M - end; -manager_handle_request(#manager{acceptors = Acceptors, - handlers = Handlers}, Pid, Ref, - {stop, Reason}) -> - i("stop"), - manager_reply(Pid, Ref, ok), - manager_stop_handlers(Handlers, Reason), - manager_stop_acceptors(Acceptors, Reason), - i("stopped", []), - exit(Reason). - -manager_stop_acceptors(Acceptors, Reason) -> - lists:foreach(fun({ID,P,M}) -> - manager_stop_acceptor(P, M, ID, Reason) - end, Acceptors). - -manager_stop_acceptor(Pid, MRef, ID, Reason) -> - i("try stop acceptor ~w (~p): ~p", [ID, Pid, Reason]), - erlang:demonitor(MRef, [flush]), - acceptor_stop(Pid, Reason), - ok. - -manager_stop_handlers(Handlers, Reason) -> - lists:foreach(fun({ID,P,M}) -> - manager_stop_handler(P, M, ID, Reason) - end, Handlers). - -manager_stop_handler(Pid, MRef, ID, Reason) -> - i("try stop handler ~w (~p): ~p", [ID, Pid, Reason]), - erlang:demonitor(MRef, [flush]), - handler_stop(Pid, Reason), - ok. - - - -%% ========================================================================= - -acceptor_start(Sock, ID) -> - Self = self(), - A = {Pid, _} = spawn_monitor(fun() -> - acceptor_init(Self, Sock, ID) - end), - receive - {acceptor, Pid, ok} -> - {ok, A}; - {acceptor, Pid, {error, _} = Error} -> - exit(Pid, kill), % Just in case - Error; - {'DOWN', _MRef, process, Pid, Reason} -> - {error, {crashed, Reason}} - end. - -acceptor_stop(Pid, _Reason) -> - %% acceptor_request(Pid, {stop, Reason}). - exit(Pid, kill). - -%% acceptor_request(Pid, Request) -> -%% request(acceptor, Pid, Request). - -%% acceptor_reply(Pid, Ref, Reply) -> -%% reply(acceptor, Pid, Ref, Reply). - - -acceptor_init(Manager, Sock, ID) -> - put(sname, f("acceptor[~w]", [ID])), - Manager ! {acceptor, self(), ok}, - %% ok = socket:setopt(Sock, otp, debug, true), - acceptor_loop(#acceptor{id = ID, - manager = Manager, - socket = Sock}). - -acceptor_loop(#acceptor{socket = LSock, atimeout = Timeout} = A) -> - i("try accept"), - case socket:accept(LSock, Timeout) of - {ok, Sock} -> - i("accepted: " - "~n ~p" - "~nwhen" - "~n ~p", [Sock, socket:info()]), - case acceptor_handle_accept_success(A, Sock) of - ok -> - acceptor_loop(A); - {error, Reason} -> - e("Failed starting handler: " - "~n ~p", [Reason]), - socket:close(Sock), - exit({failed_starting_handler, Reason}) - end; - {error, timeout} -> - i("timeout"), - acceptor_loop(A); - {error, Reason} -> - e("accept failure: " - "~n ~p", [Reason]), - exit({accept, Reason}) - end. - -acceptor_handle_accept_success(#acceptor{manager = Manager}, Sock) -> - i("try start handler for peer" - "~n ~p", [case socket:peername(Sock) of - {ok, Peer} -> Peer; - {error, _} = E -> E - end]), - case manager_start_handler(Manager, Sock) of - {ok, Pid} -> - i("handler (~p) started - now change 'ownership'", [Pid]), - case socket:setopt(Sock, otp, controlling_process, Pid) of - ok -> - %% Normally we should have a msgs collection here - %% (of messages we receive before the control was - %% handled over to Handler), but since we don't - %% have active implemented yet... - i("new handler (~p) now controlling process", [Pid]), - handler_continue(Pid), - ok; - {error, _} = ERROR -> - exit(Pid, kill), - ERROR - end; - {error, Reason2} -> - e("failed starting handler: " - "~n (new) Socket: ~p" - "~n Reason: ~p", [Sock, Reason2]), - exit({failed_starting_handler, Reason2}) - end. - - - -%% ========================================================================= - -handler_start(ID, Sock, UseMsg, Peek) -> - Self = self(), - H = {Pid, _} = spawn_monitor(fun() -> - handler_init(Self, ID, UseMsg, Peek, Sock) - end), - receive - {handler, Pid, ok} -> - {ok, H}; - {handler, Pid, {error, _} = ERROR} -> - exit(Pid, kill), % Just in case - ERROR - end. - -handler_stop(Pid, _Reason) -> - %% handler_request(Pid, {stop, Reason}). - exit(Pid, kill). - -handler_continue(Pid) -> - handler_request(Pid, continue). - -handler_request(Pid, Request) -> - ?LIB:request(handler, Pid, Request). - -handler_reply(Pid, Ref, Reply) -> - ?LIB:reply(handler, Pid, Ref, Reply). - - -handler_init(Manager, ID, Msg, Peek, Sock) -> - put(sname, f("handler:~w", [ID])), - i("starting"), - Manager ! {handler, self(), ok}, - receive - {handler, Pid, Ref, continue} -> - i("got continue"), - handler_reply(Pid, Ref, ok), - G = fun(L, O) -> case socket:getopt(Sock, L, O) of - {ok, Val} -> - f("~p", [Val]); - {error, R} when is_atom(R) -> - f("error: ~w", [R]); - {error, {T, R}} when is_atom(T) -> - f("error: ~w, ~p", [T, R]); - {error, R} -> - f("error: ~p", [R]) - end - end, - GSO = fun(O) -> G(socket, O) end, - GIP4 = fun(O) -> G(ip, O) end, - GIP6 = fun(O) -> G(ipv6, O) end, - {ok, Domain} = socket:getopt(Sock, socket, domain), - {ok, Type} = socket:getopt(Sock, socket, type), - {ok, Proto} = socket:getopt(Sock, socket, protocol), - B2D = GSO(bindtodevice), - RA = GSO(reuseaddr), - RP = GSO(reuseport), - OOBI = GSO(oobinline), - RcvBuf = GSO(rcvbuf), - RcvLW = GSO(rcvlowat), - RcvTO = GSO(rcvtimeo), - SndBuf = GSO(sndbuf), - SndLW = GSO(sndlowat), - SndTO = GSO(sndtimeo), - Linger = GSO(linger), - Timestamp = GSO(timestamp), - FreeBind = GIP4(freebind), - MTU = GIP4(mtu), - MTUDisc = GIP4(mtu_discover), - MALL = GIP4(multicast_all), - MIF4 = GIP4(multicast_if), - MLoop4 = GIP4(multicast_loop), - MTTL = GIP4(multicast_ttl), - NF = GIP4(nodefrag), % raw only - PktInfo = GIP4(pktinfo), % dgram only - RecvErr4 = GIP4(recverr), - RecvIF = GIP4(recvif), % Only dgram and raw (and FreeBSD) - RecvOPTS = GIP4(recvopts), % Not stream - RecvOrigDstAddr = GIP4(recvorigdstaddr), - RecvTOS = GIP4(recvtos), - RecvTTL = GIP4(recvttl), % not stream - RetOpts = GIP4(retopts), % not stream - SendSrcAddr = GIP4(sendsrcaddr), - TOS = GIP4(tos), - Transparent = GIP4(transparent), - TTL = GIP4(ttl), - MHops = GIP6(multicast_hops), - MIF6 = GIP6(multicast_if), % Only dgram and raw - MLoop6 = GIP6(multicast_loop), - RecvErr6 = GIP6(recverr), - RecvPktInfo = GIP6(recvpktinfo), - RtHdr = GIP6(rthdr), - AuthHdr = GIP6(authhdr), - HopLimit = GIP6(hoplimit), - HopOpts = GIP6(hopopts), - DstOpts = GIP6(dstopts), - FlowInfo = GIP6(flowinfo), - UHops = GIP6(unicast_hops), - i("got continue when: " - "~n (socket) Domain: ~p" - "~n (socket) Type: ~p" - "~n (socket) Protocol: ~p" - "~n (socket) Reuse Address: ~s" - "~n (socket) Reuse Port: ~s" - "~n (socket) Bind To Device: ~s" - "~n (socket) OOBInline: ~s" - "~n (socket) RcvBuf: ~s" - "~n (socket) RcvLW: ~s" - "~n (socket) RcvTO: ~s" - "~n (socket) SndBuf: ~s" - "~n (socket) SndLW: ~s" - "~n (socket) SndTO: ~s" - "~n (socket) Linger: ~s" - "~n (socket) Timestamp: ~s" - "~n (ip) FreeBind: ~s" - "~n (ip) MTU: ~s" - "~n (ip) MTU Discovery: ~s" - "~n (ip) Multicast ALL: ~s" - "~n (ip) Multicast IF: ~s" - "~n (ip) Multicast Loop: ~s" - "~n (ip) Multicast TTL: ~s" - "~n (ip) Node Frag: ~s" - "~n (ip) Pkt Info: ~s" - "~n (ip) Recv Err: ~s" - "~n (ip) Recv IF: ~s" - "~n (ip) Recv OPTS: ~s" - "~n (ip) Recv Orig Dst Addr: ~s" - "~n (ip) Recv TOS: ~s" - "~n (ip) Recv TTL: ~s" - "~n (ip) Ret Opts: ~s" - "~n (ip) Send Src Addr: ~s" - "~n (ip) TOS: ~s" - "~n (ip) Transparent: ~s" - "~n (ip) TTL: ~s" - "~n (ipv6) Multicast Hops: ~s" - "~n (ipv6) Multicast IF: ~s" - "~n (ipv6) Multicast Loop: ~s" - "~n (ipv6) Recv Err: ~s" - "~n (ipv6) Recv Pkt Info: ~s" - "~n (ipv6) RT Hdr: ~s" - "~n (ipv6) Auth Hdr: ~s" - "~n (ipv6) Hop Limit: ~s" - "~n (ipv6) Hop Opts: ~s" - "~n (ipv6) Dst Opts: ~s" - "~n (ipv6) Flow Info: ~s" - "~n (ipv6) Unicast Hops: ~s", - [Domain, Type, Proto, - RA, RP, B2D, OOBI, - RcvBuf, RcvLW, RcvTO, SndBuf, SndLW, SndTO, - Linger, Timestamp, - FreeBind, MTU, MTUDisc, MALL, MIF4, MLoop4, MTTL, - NF, PktInfo,RecvErr4, - RecvIF, RecvOPTS, RecvOrigDstAddr, RecvTOS, RecvTTL, RetOpts, - SendSrcAddr, TOS, Transparent, TTL, - MHops, MIF6, MLoop6, RecvErr6, RecvPktInfo, - RtHdr, AuthHdr, HopLimit, HopOpts, DstOpts, FlowInfo, - UHops]), - - %% ok = socket:setopt(Sock, otp, debug, true), - %% case socket:getopt(Sock, 0, {13, int}) of - %% {ok, Val} -> - %% i("PktOpts ok: ~p", [Val]); - %% {error, Reason} -> - %% e("PktOpts err: ~p", [Reason]) - %% end, - %% ok = socket:setopt(Sock, otp, debug, false), - SSO = fun(O, V) -> soso(Sock, O, V) end, - SIP4 = - fun(O, V) -> - if - (Type =:= dgram) -> - ok = soip(Sock, O, V); - true -> - ok - end - end, - SSO(timestamp, true), - SIP4(pktinfo, true), - ok = soip(Sock, recvtos, true), - SIP4(recvttl, true), - ok = soip(Sock, recvorigdstaddr, true), - - handler_loop(#handler{msg = Msg, - peek = Peek, - manager = Manager, - type = Type, - socket = Sock}) - end. - -so(Sock, Lvl, Opt, Val) -> - ok = socket:setopt(Sock, Lvl, Opt, Val). - -soso(Sock, Opt, Val) -> - so(Sock, socket, Opt, Val). - -soip(Sock, Opt, Val) -> - so(Sock, ip, Opt, Val). - -%% soipv6(Sock, Opt, Val) -> -%% so(Sock, ipv6, Opt, Val). - -handler_loop(H) -> - i("try read message"), - case recv(H) of - {ok, {Source, Msg}} -> - i("received ~w bytes of data~s", - [size(Msg), case Source of - undefined -> ""; - _ -> f(" from:~n ~p", [Source]) - end]), - case ?LIB:dec_msg(Msg) of - {request, N, Req} -> - i("received request ~w: " - "~n ~p", [N, Req]), - Reply = ?LIB:enc_rep_msg(N, "hoppsan"), - case send(H, Reply, Source) of - ok -> - i("successfully sent reply ~w", [N]), - handler_loop(H); - {error, SReason} -> - e("failed sending reply ~w:" - "~n ~p", [N, SReason]), - exit({failed_sending_reply, SReason}) - end - end; - - {error, closed} -> - i("closed when" - "~n ~p", [socket:info()]), - exit(normal); - - {error, RReason} -> - e("failed reading request: " - "~n ~p", [RReason]), - exit({failed_reading_request, RReason}) - end. - - -recv(#handler{peek = true, socket = Sock, type = stream}) -> - peek_recv(Sock); -recv(#handler{socket = Sock, msg = true, type = stream}) -> - case socket:recvmsg(Sock) of - {ok, #{addr := undefined = Source, - iov := [Data], - ctrl := CMsgHdrs, - flags := Flags}} -> - i("received message: " - "~n CMsgHdrs: ~p" - "~n Flags: ~p", [CMsgHdrs, Flags]), - {ok, {Source, Data}}; - {ok, X} -> - e("received *unexpected* message: " - "~n ~p", [X]), - {error, {unexpected, X}}; - {error, _} = ERROR -> - ERROR - end; -recv(#handler{socket = Sock, msg = true, type = dgram}) -> - case socket:recvmsg(Sock) of - {ok, #{addr := Source, - iov := [Data], - ctrl := CMsgHdrs, - flags := Flags}} -> - i("received message: " - "~n CMsgHdrs: ~p" - "~n Flags: ~p", [CMsgHdrs, Flags]), - {ok, {Source, Data}}; - {ok, X} -> - {error, {unexpected, X}}; - {error, _} = ERROR -> - ERROR - end; -recv(#handler{peek = false, socket = Sock, type = stream}) -> - do_recv(Sock); -recv(#handler{peek = Peek, socket = Sock, type = dgram}) - when (Peek =:= true) -> - %% ok = socket:setopt(Sock, otp, debug, true), - RES = peek_recvfrom(Sock, 5), - %% ok = socket:setopt(Sock, otp, debug, false), - RES; -recv(#handler{peek = Peek, socket = Sock, type = dgram}) - when (Peek =:= false) -> - %% ok = socket:setopt(Sock, otp, debug, true), - socket:recvfrom(Sock). - -do_recv(Sock) -> - case socket:recv(Sock) of - {ok, Msg} -> - {ok, {undefined, Msg}}; - {error, _} = ERROR -> - ERROR - end. - -peek_recv(Sock) -> - i("try peek on the message type (expect request)"), - Type = ?LIB:req(), - case socket:recv(Sock, 4, [peek]) of - {ok, <>} -> - i("was request - do proper recv"), - do_recv(Sock); - {error, _} = ERROR -> - ERROR - end. - -peek_recvfrom(Sock, BufSz) -> - i("try peek recvfrom with buffer size ~w", [BufSz]), - case socket:recvfrom(Sock, BufSz, [peek]) of - {ok, {_Source, Msg}} when (BufSz =:= size(Msg)) -> - %% i("we filled the buffer: " - %% "~n ~p", [Msg]), - %% It *may not* fit => try again with double size - peek_recvfrom(Sock, BufSz*2); - {ok, _} -> - %% It fits => read for real - i("we did *not* fill the buffer - do the 'real' read"), - socket:recvfrom(Sock); - {error, _} = ERROR -> - ERROR - end. - - -send(#handler{socket = Sock, msg = true, type = stream, stimeout = Timeout}, - Msg, _) -> - CMsgHdr = #{level => ip, type => tos, data => reliability}, - CMsgHdrs = [CMsgHdr], - MsgHdr = #{iov => [Msg], ctrl => CMsgHdrs}, - %% socket:setopt(Sock, otp, debug, true), - Res = socket:sendmsg(Sock, MsgHdr, Timeout), - %% socket:setopt(Sock, otp, debug, false), - Res; -send(#handler{socket = Sock, type = stream, stimeout = Timeout}, Msg, _) -> - socket:send(Sock, Msg, Timeout); -send(#handler{socket = Sock, msg = true, type = dgram, stimeout = Timeout}, - Msg, Dest) -> - CMsgHdr = #{level => ip, type => tos, data => reliability}, - CMsgHdrs = [CMsgHdr], - MsgHdr = #{addr => Dest, - ctrl => CMsgHdrs, - iov => [Msg]}, - %% ok = socket:setopt(Sock, otp, debug, true), - Res = socket:sendmsg(Sock, MsgHdr, Timeout), - %% ok = socket:setopt(Sock, otp, debug, false), - Res; -send(#handler{socket = Sock, type = dgram, stimeout = Timeout}, Msg, Dest) -> - socket:sendto(Sock, Msg, Dest, Timeout). - -%% filler() -> -%% list_to_binary(lists:duplicate(2048, " FILLER ")). - - - -%% ========================================================================= - -f(F, A) -> - ?LIB:f(F, A). - -e(F) -> - e(F, []). -e(F, A) -> - ?LIB:e(F, A). - -i(F) -> - ?LIB:i(F). - -i(F, A) -> - ?LIB:i(F, A). - -- cgit v1.2.3 From ee058dabf9474006179eba6a63282a1398e1c6fc Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Fri, 19 Oct 2018 12:57:35 +0200 Subject: [socket-nif|test] IO fixes --- erts/emulator/test/Makefile | 2 +- erts/emulator/test/socket_SUITE.erl | 178 ++++++++++++++++++++++-------------- 2 files changed, 108 insertions(+), 72 deletions(-) diff --git a/erts/emulator/test/Makefile b/erts/emulator/test/Makefile index 4f47ec47ef..39a3ef2a3d 100644 --- a/erts/emulator/test/Makefile +++ b/erts/emulator/test/Makefile @@ -158,7 +158,7 @@ NATIVE_ERL_FILES= $(NATIVE_MODULES:%=%.erl) ERL_FILES= $(MODULES:%=%.erl) TARGET_FILES = $(MODULES:%=$(EBIN)/%.$(EMULATOR)) -SOCKET_TARGET = $(SOCKET_MODULES:%=$(EBIN)/%.$(EMULATOR)) +SOCKET_TARGETS = $(SOCKET_MODULES:%=$(EBIN)/%.$(EMULATOR)) EMAKEFILE=Emakefile diff --git a/erts/emulator/test/socket_SUITE.erl b/erts/emulator/test/socket_SUITE.erl index 022e83a944..c83b3f0022 100644 --- a/erts/emulator/test/socket_SUITE.erl +++ b/erts/emulator/test/socket_SUITE.erl @@ -709,16 +709,16 @@ api_b_send_and_recv_tcp(InitState) -> end} ], - p("start server evaluator"), + i("start server evaluator"), Server = evaluator_start("server", ServerSeq, InitState), - p("await server (~p) port", [Server]), + i("await server (~p) port", [Server]), SPort = receive {server_port, Server, Port} -> Port end, - p("start client evaluator"), + i("start client evaluator"), Client = evaluator_start("client", ClientSeq, InitState#{server_port => SPort}), - p("await evaluator(s)"), + i("await evaluator(s)"), ok = await_evaluator_finish([Server, Client]). @@ -978,16 +978,16 @@ api_opt_simple_otp_options() -> end} ], - p("Run test for stream/tcp socket"), + i("start tcp (stream) evaluator"), InitState1 = #{domain => inet, type => stream, protocol => tcp}, Tester1 = evaluator_start("tcp-tester", Seq, InitState1), - p("await evaluator 1"), + i("await tcp evaluator"), ok = await_evaluator_finish([Tester1]), - p("Run test for dgram/udp socket"), + i("start udp (dgram) socket"), InitState2 = #{domain => inet, type => dgram, protocol => udp}, Tester2 = evaluator_start("udp-tester", Seq, InitState2), - p("await evaluator 2"), + i("await udp evaluator"), ok = await_evaluator_finish([Tester2]). @@ -1218,7 +1218,7 @@ api_opt_simple_otp_controlling_process() -> end} ], - p("Run test for stream/tcp socket"), + i("start tcp (stream) socket"), ClientInitState1 = #{}, Client1 = evaluator_start("tcp-client", ClientSeq, ClientInitState1), TesterInitState1 = #{domain => inet, @@ -1226,10 +1226,10 @@ api_opt_simple_otp_controlling_process() -> protocol => tcp, client => Client1}, Tester1 = evaluator_start("tcp-tester", TesterSeq, TesterInitState1), - p("await stream/tcp evaluator"), + i("await tcp evaluator"), ok = await_evaluator_finish([Tester1, Client1]), - p("Run test for dgram/udp socket"), + i("start udp (dgram) socket"), ClientInitState2 = #{}, Client2 = evaluator_start("udp-client", ClientSeq, ClientInitState2), TesterInitState2 = #{domain => inet, @@ -1237,7 +1237,7 @@ api_opt_simple_otp_controlling_process() -> protocol => udp, client => Client2}, Tester2 = evaluator_start("udp-tester", TesterSeq, TesterInitState2), - p("await dgram/udp evaluator"), + i("await udp evaluator"), ok = await_evaluator_finish([Tester2, Client2]). @@ -1500,15 +1500,15 @@ api_to_connect_tcp(InitState) -> end} ], - p("create server evaluator"), + i("create server evaluator"), ServerInitState = InitState, Server = evaluator_start("server", ServerSeq, ServerInitState), - p("create tester evaluator"), + i("create tester evaluator"), TesterInitState = InitState#{server => Server}, Tester = evaluator_start("tester", TesterSeq, TesterInitState), - p("await evaluator(s)"), + i("await evaluator(s)"), ok = await_evaluator_finish([Server, Tester]). @@ -1651,10 +1651,10 @@ api_to_accept_tcp(InitState) -> end} ], - p("create tester evaluator"), + i("create tester evaluator"), Tester = evaluator_start("tester", TesterSeq, InitState), - p("await evaluator"), + i("await evaluator"), ok = await_evaluator_finish([Tester]). @@ -2071,28 +2071,28 @@ api_to_maccept_tcp(InitState) -> end} ], - p("create prim-acceptor evaluator"), + i("create prim-acceptor evaluator"), PrimAInitState = InitState, PrimAcceptor = evaluator_start("prim-acceptor", PrimAcceptorSeq, PrimAInitState), - p("create prim-acceptor 1 evaluator"), + i("create sec-acceptor 1 evaluator"), SecAInitState1 = maps:remove(domain, InitState), SecAcceptor1 = evaluator_start("sec-acceptor-1", SecAcceptorSeq, SecAInitState1), - p("create prim-acceptor 2 evaluator"), + i("create sec-acceptor 2 evaluator"), SecAInitState2 = SecAInitState1, SecAcceptor2 = evaluator_start("sec-acceptor-2", SecAcceptorSeq, SecAInitState2), - p("create tester evaluator"), + i("create tester evaluator"), TesterInitState = #{prim_acceptor => PrimAcceptor, sec_acceptor1 => SecAcceptor1, sec_acceptor2 => SecAcceptor2}, Tester = evaluator_start("tester", TesterSeq, TesterInitState), - p("await evaluator(s)"), + i("await evaluator(s)"), ok = await_evaluator_finish([PrimAcceptor, SecAcceptor1, SecAcceptor2, Tester]). @@ -2628,19 +2628,19 @@ api_to_receive_tcp(InitState) -> ], - p("start server evaluator"), + i("start server evaluator"), ServerInitState = InitState, Server = evaluator_start("server", ServerSeq, ServerInitState), - p("start client evaluator"), + i("start client evaluator"), ClientInitState = InitState, Client = evaluator_start("client", ClientSeq, ClientInitState), - p("start tester evaluator"), + i("start tester evaluator"), TesterInitState = #{server => Server, client => Client}, Tester = evaluator_start("tester", TesterSeq, TesterInitState), - p("await evaluator(s)"), + i("await evaluator(s)"), ok = await_evaluator_finish([Server, Client, Tester]). @@ -2753,10 +2753,10 @@ api_to_receive_udp(InitState) -> end} ], - p("start tester evaluator"), + i("start tester evaluator"), Tester = evaluator_start("tester", TesterSeq, InitState), - p("await evaluator"), + i("await evaluator"), ok = await_evaluator_finish([Tester]). @@ -3057,14 +3057,14 @@ sc_cpe_socket_cleanup(InitState) -> end} ], - p("start (socket) owner evaluator"), + i("start (socket) owner evaluator"), Owner = evaluator_start("owner", OwnerSeq, InitState), - p("start tester evaluator"), + i("start tester evaluator"), TesterInitState = #{owner => Owner}, Tester = evaluator_start("tester", TesterSeq, TesterInitState), - p("await evaluator"), + i("await evaluator"), ok = await_evaluator_finish([Owner, Tester]). @@ -3644,25 +3644,25 @@ sc_lc_receive_response_tcp(InitState) -> end} ], - p("start acceptor evaluator"), + i("start acceptor evaluator"), AccInitState = InitState, Acceptor = evaluator_start("acceptor", AcceptorSeq, AccInitState), - p("start handler evaluator"), + i("start handler evaluator"), HandlerInitState = #{recv => maps:get(recv, InitState)}, Handler = evaluator_start("handler", HandlerSeq, HandlerInitState), - p("start client evaluator"), + i("start client evaluator"), ClientInitState = InitState, Client = evaluator_start("client", ClientSeq, ClientInitState), - p("start tester evaluator"), + i("start tester evaluator"), TesterInitState = #{acceptor => Acceptor, handler => Handler, client => Client}, Tester = evaluator_start("tester", TesterSeq, TesterInitState), - p("await evaluator"), + i("await evaluator"), ok = await_evaluator_finish([Acceptor, Handler, Client, Tester]). @@ -3951,20 +3951,20 @@ await_evaluator_finish(Evs, Fails) -> {'DOWN', _MRef, process, Pid, normal} -> case lists:delete(Pid, Evs) of Evs -> - p("unknown process ~p died (normal)", [Pid]), + i("unknown process ~p died (normal)", [Pid]), await_evaluator_finish(Evs, Fails); NewEvs -> - p("evaluator ~p success", [Pid]), + i("evaluator ~p success", [Pid]), await_evaluator_finish(NewEvs, Fails) end; {'DOWN', _MRef, process, Pid, Reason} -> case lists:delete(Pid, Evs) of Evs -> - p("unknown process ~p died: " + i("unknown process ~p died: " "~n ~p", [Pid, Reason]), await_evaluator_finish(Evs, Fails); NewEvs -> - p("Evaluator ~p failed", [Pid]), + i("Evaluator ~p failed", [Pid]), await_evaluator_finish(NewEvs, [{Pid, Reason}|Fails]) end end. @@ -3981,8 +3981,12 @@ ee(F, A) -> eprint(" ", F, A). eprint(Prefix, F, A) -> - io:format(user, "[~s][~s][~p] ~s" ++ F ++ "~n", - [formated_timestamp(), get(sname), self(), Prefix | A]). + %% The two prints is to get the output both in the shell (for when + %% "personal" testing is going on) and in the logs. + FStr = f("[~s][~s][~p] ~s" ++ F, + [formated_timestamp(), get(sname), self(), Prefix | A]), + io:format(user, FStr ++ "~n", []), + io:format(FStr, []). @@ -4005,11 +4009,11 @@ sock_bind(Sock, SockAddr) -> {ok, Port} -> Port; {error, Reason} -> - p("sock_bind -> error: ~p", [Reason]), + i("sock_bind -> error: ~p", [Reason]), ?FAIL({bind, Reason}) catch C:E:S -> - p("sock_bind -> failed: ~p, ~p, ~p", [C, E, S]), + i("sock_bind -> failed: ~p, ~p, ~p", [C, E, S]), ?FAIL({bind, C, E, S}) end. @@ -4059,11 +4063,11 @@ sock_sockname(Sock) -> %% {ok, Sock} -> %% Sock; %% {error, Reason} -> -%% p("sock_accept -> error: ~p", [Reason]), +%% i("sock_accept -> error: ~p", [Reason]), %% ?FAIL({accept, Reason}) %% catch %% C:E:S -> -%% p("sock_accept -> failed: ~p, ~p, ~p", [C, E, S]), +%% i("sock_accept -> failed: ~p, ~p, ~p", [C, E, S]), %% ?FAIL({accept, C, E, S}) %% end. @@ -4073,11 +4077,11 @@ sock_close(Sock) -> ok -> ok; {error, Reason} -> - p("sock_close -> error: ~p", [Reason]), + i("sock_close -> error: ~p", [Reason]), ?FAIL({close, Reason}) catch C:E:S -> - p("sock_close -> failed: ~p, ~p, ~p", [C, E, S]), + i("sock_close -> failed: ~p, ~p, ~p", [C, E, S]), ?FAIL({close, C, E, S}) end. @@ -4130,10 +4134,12 @@ set_tc_name(N) when is_list(N) -> tc_begin(TC) -> set_tc_name(TC), - p("begin ***"). + tc_print("begin ***", + "~n----------------------------------------------------~n", ""). tc_end(Result) when is_list(Result) -> - p("done: ~s", [Result]), + tc_print("done: ~s", [Result], + "", "----------------------------------------------------~n~n"), ok. @@ -4154,33 +4160,63 @@ tc_try(Case, Fun) when is_atom(Case) andalso is_function(Fun, 0) -> end. +tc_print(F, Before, After) -> + tc_print(F, [], Before, After). +tc_print(F, A, Before, After) -> + Name = tc_which_name(), + FStr = f("*** [~s][~s][~p] " ++ F ++ "~n", + [formated_timestamp(),Name,self()|A]), + io:format(user, Before ++ FStr ++ After, []). + +tc_which_name() -> + case get(tc_name) of + undefined -> + case get(sname) of + undefined -> + ""; + SName when is_list(SName) -> + SName + end; + Name when is_list(Name) -> + Name + end. + + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% f(F, A) -> -%% lists:flatten(io_lib:format(F, A)). +f(F, A) -> + lists:flatten(io_lib:format(F, A)). + +%% p(F) -> +%% p(F, []). -p(F) -> - p(F, []). +%% p(F, A) -> +%% p(F, A, "", ""). -p(F, A) -> - TcName = - case get(tc_name) of - undefined -> - case get(sname) of - undefined -> - ""; - SName when is_list(SName) -> - SName - end; - Name when is_list(Name) -> - Name - end, - i("*** [~s][~s][~p] " ++ F, [formated_timestamp(),TcName,self()|A]). +%% p(F, A, Before, After) when is_list(Before) andalso is_list(After) -> +%% TcName = +%% case get(tc_name) of +%% undefined -> +%% case get(sname) of +%% undefined -> +%% ""; +%% SName when is_list(SName) -> +%% SName +%% end; +%% Name when is_list(Name) -> +%% Name +%% end, +%% FStr = f("*** [~s][~s][~p] " ++ F ++ "~n", +%% [formated_timestamp(),TcName,self()|A]), +%% i(Before ++ FStr ++ After, []). -%% i(F) -> -%% i(F, []). +i(F) -> + i(F, []). i(F, A) -> - io:format(user, F ++ "~n", A). + FStr = f("[~s] " ++ F, [formated_timestamp()|A]), + io:format(user, FStr ++ "~n", []), + io:format(FStr, []). + -- cgit v1.2.3 From 09eadb08535c10ad98d7ed092ef9bb2618bccf94 Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Fri, 19 Oct 2018 15:18:02 +0200 Subject: [socket-nif|test] Evaluator improvements Add name to evaluator printouts. Also added timetraps to all test- cases to shorten the time to wait in case a test case fails. OTP-14831 --- erts/emulator/test/socket_SUITE.erl | 163 ++++++++++++++++++++++++------------ 1 file changed, 111 insertions(+), 52 deletions(-) diff --git a/erts/emulator/test/socket_SUITE.erl b/erts/emulator/test/socket_SUITE.erl index c83b3f0022..92dd5e7097 100644 --- a/erts/emulator/test/socket_SUITE.erl +++ b/erts/emulator/test/socket_SUITE.erl @@ -88,7 +88,11 @@ %% Internal exports %% -export([]). - +-record(ev, {name :: string(), + pid :: pid(), + mref :: reference()}). +-type ev() :: #ev{}. +-define(MKEV(N,P,R), #ev{name = N, pid = P, mref = R}). -type initial_evaluator_state() :: map(). -type evaluator_state() :: term(). -type command_fun() :: @@ -109,6 +113,10 @@ -define(SLEEP(T), receive after T -> ok end). +-define(MINS(M), timer:minutes(M)). +-define(SECS(S), timer:seconds(S)). + +-define(TT(T), ct:timetrap(T)). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -164,6 +172,8 @@ api_op_with_timeout_cases() -> api_to_connect_tcp6, api_to_accept_tcp4, api_to_accept_tcp6, + api_to_maccept_tcp4, + api_to_maccept_tcp6, api_to_send_tcp4, api_to_send_tcp6, api_to_sendto_udp4, @@ -263,6 +273,7 @@ api_b_open_and_close_udp4(suite) -> api_b_open_and_close_udp4(doc) -> []; api_b_open_and_close_udp4(_Config) when is_list(_Config) -> + ?TT(?SECS(5)), tc_try(api_b_open_and_close_udp4, fun() -> InitState = #{domain => inet, @@ -281,6 +292,7 @@ api_b_open_and_close_tcp4(suite) -> api_b_open_and_close_tcp4(doc) -> []; api_b_open_and_close_tcp4(_Config) when is_list(_Config) -> + ?TT(?SECS(5)), tc_try(api_b_open_and_close_tcp4, fun() -> InitState = #{domain => inet, @@ -392,6 +404,7 @@ api_b_sendto_and_recvfrom_udp4(suite) -> api_b_sendto_and_recvfrom_udp4(doc) -> []; api_b_sendto_and_recvfrom_udp4(_Config) when is_list(_Config) -> + ?TT(?SECS(5)), tc_try(api_b_sendto_and_recvfrom_udp4, fun() -> Send = fun(Sock, Data, Dest) -> @@ -416,6 +429,7 @@ api_b_sendmsg_and_recvmsg_udp4(suite) -> api_b_sendmsg_and_recvmsg_udp4(doc) -> []; api_b_sendmsg_and_recvmsg_udp4(_Config) when is_list(_Config) -> + ?TT(?SECS(5)), tc_try(api_b_sendmsg_and_recvmsg_udp4, fun() -> Send = fun(Sock, Data, Dest) -> @@ -530,6 +544,7 @@ api_b_send_and_recv_tcp4(suite) -> api_b_send_and_recv_tcp4(doc) -> []; api_b_send_and_recv_tcp4(_Config) when is_list(_Config) -> + ?TT(?SECS(10)), tc_try(api_b_send_and_recv_tcp4, fun() -> Send = fun(Sock, Data) -> @@ -554,6 +569,7 @@ api_b_sendmsg_and_recvmsg_tcp4(suite) -> api_b_sendmsg_and_recvmsg_tcp4(doc) -> []; api_b_sendmsg_and_recvmsg_tcp4(_Config) when is_list(_Config) -> + ?TT(?SECS(10)), tc_try(api_b_sendmsg_and_recvmsg_tcp4, fun() -> Send = fun(Sock, Data) -> @@ -710,10 +726,10 @@ api_b_send_and_recv_tcp(InitState) -> ], i("start server evaluator"), - Server = evaluator_start("server", ServerSeq, InitState), - i("await server (~p) port", [Server]), + #ev{pid = Pid} = Server = evaluator_start("server", ServerSeq, InitState), + i("await server (~p) port", [Pid]), SPort = receive - {server_port, Server, Port} -> + {server_port, Pid, Port} -> Port end, i("start client evaluator"), @@ -739,6 +755,7 @@ api_opt_simple_otp_options(suite) -> api_opt_simple_otp_options(doc) -> []; api_opt_simple_otp_options(_Config) when is_list(_Config) -> + ?TT(?SECS(5)), tc_try(api_opt_simple_otp_options, fun() -> api_opt_simple_otp_options() end). @@ -999,6 +1016,7 @@ api_opt_simple_otp_controlling_process(suite) -> api_opt_simple_otp_controlling_process(doc) -> []; api_opt_simple_otp_controlling_process(_Config) when is_list(_Config) -> + ?TT(?SECS(5)), tc_try(api_opt_simple_otp_controlling_process, fun() -> api_opt_simple_otp_controlling_process() end). @@ -1220,22 +1238,24 @@ api_opt_simple_otp_controlling_process() -> i("start tcp (stream) socket"), ClientInitState1 = #{}, - Client1 = evaluator_start("tcp-client", ClientSeq, ClientInitState1), + #ev{pid = Pid1} = Client1 = evaluator_start("tcp-client", + ClientSeq, ClientInitState1), TesterInitState1 = #{domain => inet, type => stream, protocol => tcp, - client => Client1}, + client => Pid1}, Tester1 = evaluator_start("tcp-tester", TesterSeq, TesterInitState1), i("await tcp evaluator"), ok = await_evaluator_finish([Tester1, Client1]), i("start udp (dgram) socket"), ClientInitState2 = #{}, - Client2 = evaluator_start("udp-client", ClientSeq, ClientInitState2), + #ev{pid = Pid2} = Client2 = evaluator_start("udp-client", + ClientSeq, ClientInitState2), TesterInitState2 = #{domain => inet, type => dgram, protocol => udp, - client => Client2}, + client => Pid2}, Tester2 = evaluator_start("udp-tester", TesterSeq, TesterInitState2), i("await udp evaluator"), ok = await_evaluator_finish([Tester2, Client2]). @@ -1261,6 +1281,7 @@ api_to_connect_tcp4(doc) -> api_to_connect_tcp4(_Config) when is_list(_Config) -> tc_try(api_to_connect_tcp4, fun() -> + ?TT(?SECS(10)), InitState = #{domain => inet, timeout => 5000}, ok = api_to_connect_tcp(InitState) end). @@ -1278,6 +1299,7 @@ api_to_connect_tcp6(_Config) when is_list(_Config) -> tc_try(api_to_connect_tcp6, fun() -> not_yet_implemented(), + ?TT(?SECS(10)), InitState = #{domain => inet6, timeout => 5000}, ok = api_to_connect_tcp(InitState) end). @@ -1505,7 +1527,7 @@ api_to_connect_tcp(InitState) -> Server = evaluator_start("server", ServerSeq, ServerInitState), i("create tester evaluator"), - TesterInitState = InitState#{server => Server}, + TesterInitState = InitState#{server => Server#ev.pid}, Tester = evaluator_start("tester", TesterSeq, TesterInitState), i("await evaluator(s)"), @@ -1555,6 +1577,7 @@ api_to_accept_tcp4(doc) -> api_to_accept_tcp4(_Config) when is_list(_Config) -> tc_try(api_to_accept_tcp4, fun() -> + ?TT(?SECS(10)), InitState = #{domain => inet, timeout => 5000}, ok = api_to_accept_tcp(InitState) end). @@ -1572,6 +1595,7 @@ api_to_accept_tcp6(_Config) when is_list(_Config) -> tc_try(api_to_accept_tcp4, fun() -> not_yet_implemented(), + ?TT(?SECS(10)), InitState = #{domain => inet6, timeout => 5000}, ok = api_to_accept_tcp(InitState) end). @@ -2073,23 +2097,26 @@ api_to_maccept_tcp(InitState) -> i("create prim-acceptor evaluator"), PrimAInitState = InitState, - PrimAcceptor = evaluator_start("prim-acceptor", - PrimAcceptorSeq, PrimAInitState), + #ev{pid = PPid} = PrimAcceptor = evaluator_start("prim-acceptor", + PrimAcceptorSeq, + PrimAInitState), i("create sec-acceptor 1 evaluator"), SecAInitState1 = maps:remove(domain, InitState), - SecAcceptor1 = evaluator_start("sec-acceptor-1", - SecAcceptorSeq, SecAInitState1), - + #ev{pid = SPid1} = SecAcceptor1 = evaluator_start("sec-acceptor-1", + SecAcceptorSeq, + SecAInitState1), + i("create sec-acceptor 2 evaluator"), SecAInitState2 = SecAInitState1, - SecAcceptor2 = evaluator_start("sec-acceptor-2", - SecAcceptorSeq, SecAInitState2), + #ev{pid = SPid2} = SecAcceptor2 = evaluator_start("sec-acceptor-2", + SecAcceptorSeq, + SecAInitState2), i("create tester evaluator"), - TesterInitState = #{prim_acceptor => PrimAcceptor, - sec_acceptor1 => SecAcceptor1, - sec_acceptor2 => SecAcceptor2}, + TesterInitState = #{prim_acceptor => PPid, + sec_acceptor1 => SPid1, + sec_acceptor2 => SPid2}, Tester = evaluator_start("tester", TesterSeq, TesterInitState), i("await evaluator(s)"), @@ -2238,6 +2265,7 @@ api_to_recv_tcp4(doc) -> api_to_recv_tcp4(_Config) when is_list(_Config) -> tc_try(api_to_recv_tcp4, fun() -> + ?TT(?SECS(10)), Recv = fun(Sock, To) -> socket:recv(Sock, 0, To) end, InitState = #{domain => inet, recv => Recv, @@ -2260,6 +2288,7 @@ api_to_recv_tcp6(_Config) when is_list(_Config) -> not_yet_implemented(), case socket:supports(ipv6) of true -> + ?TT(?SECS(10)), Recv = fun(Sock, To) -> socket:recv(Sock, 0, To) end, @@ -2630,14 +2659,18 @@ api_to_receive_tcp(InitState) -> i("start server evaluator"), ServerInitState = InitState, - Server = evaluator_start("server", ServerSeq, ServerInitState), + #ev{pid = SPid} = Server = evaluator_start("server", + ServerSeq, + ServerInitState), i("start client evaluator"), ClientInitState = InitState, - Client = evaluator_start("client", ClientSeq, ClientInitState), + #ev{pid = CPid} = Client = evaluator_start("client", + ClientSeq, + ClientInitState), i("start tester evaluator"), - TesterInitState = #{server => Server, client => Client}, + TesterInitState = #{server => SPid, client => CPid}, Tester = evaluator_start("tester", TesterSeq, TesterInitState), i("await evaluator(s)"), @@ -2656,6 +2689,7 @@ api_to_recvfrom_udp4(doc) -> api_to_recvfrom_udp4(_Config) when is_list(_Config) -> tc_try(api_to_recvfrom_udp4, fun() -> + ?TT(?SECS(10)), Recv = fun(Sock, To) -> socket:recvfrom(Sock, 0, To) end, InitState = #{domain => inet, recv => Recv, @@ -2676,6 +2710,7 @@ api_to_recvfrom_udp6(_Config) when is_list(_Config) -> tc_try(api_to_recvfrom_udp6, fun() -> not_yet_implemented(), + ?TT(?SECS(10)), Recv = fun(Sock, To) -> socket:recvfrom(Sock, 0, To) end, InitState = #{domain => inet6, recv => Recv, @@ -2772,6 +2807,7 @@ api_to_recvmsg_udp4(doc) -> api_to_recvmsg_udp4(_Config) when is_list(_Config) -> tc_try(api_to_recvmsg_udp4, fun() -> + ?TT(?SECS(10)), Recv = fun(Sock, To) -> socket:recvmsg(Sock, To) end, InitState = #{domain => inet, recv => Recv, @@ -2792,6 +2828,7 @@ api_to_recvmsg_udp6(_Config) when is_list(_Config) -> tc_try(api_to_recvmsg_udp6, fun() -> not_yet_implemented(), + ?TT(?SECS(10)), Recv = fun(Sock, To) -> socket:recvmsg(Sock, To) end, InitState = #{domain => inet6, recv => Recv, @@ -2811,6 +2848,7 @@ api_to_recvmsg_tcp4(doc) -> api_to_recvmsg_tcp4(_Config) when is_list(_Config) -> tc_try(api_to_recvmsg_tcp4, fun() -> + ?TT(?SECS(10)), Recv = fun(Sock, To) -> socket:recvmsg(Sock, To) end, InitState = #{domain => inet, recv => Recv, @@ -2831,6 +2869,7 @@ api_to_recvmsg_tcp6(_Config) when is_list(_Config) -> tc_try(api_to_recvmsg_tcp6, fun() -> not_yet_implemented(), + ?TT(?SECS(10)), Recv = fun(Sock, To) -> socket:recvmsg(Sock, To) end, InitState = #{domain => inet6, recv => Recv, @@ -2861,6 +2900,7 @@ sc_cpe_socket_cleanup_tcp4(_Config) when is_list(_Config) -> tc_try(sc_cpe_socket_cleanup_tcp4, fun() -> %% not_yet_implemented(), + ?TT(?SECS(5)), InitState = #{domain => inet, type => stream, protocol => tcp}, @@ -2881,6 +2921,7 @@ sc_cpe_socket_cleanup_tcp6(_Config) when is_list(_Config) -> tc_try(sc_cpe_socket_cleanup_tcp6, fun() -> not_yet_implemented(), + ?TT(?SECS(5)), InitState = #{domain => inet6, type => stream, protocol => tcp}, @@ -2900,6 +2941,7 @@ sc_cpe_socket_cleanup_udp4(doc) -> sc_cpe_socket_cleanup_udp4(_Config) when is_list(_Config) -> tc_try(sc_cpe_socket_cleanup_udp4, fun() -> + ?TT(?SECS(5)), InitState = #{domain => inet, type => dgram, protocol => udp}, @@ -2921,6 +2963,7 @@ sc_cpe_socket_cleanup_udp6(_Config) when is_list(_Config) -> tc_try(sc_cpe_socket_cleanup_udp6, fun() -> not_yet_implemented(), + ?TT(?SECS(5)), InitState = #{domain => inet6, type => dgram, protocol => udp}, @@ -3058,10 +3101,10 @@ sc_cpe_socket_cleanup(InitState) -> ], i("start (socket) owner evaluator"), - Owner = evaluator_start("owner", OwnerSeq, InitState), + #ev{pid = Pid} = Owner = evaluator_start("owner", OwnerSeq, InitState), i("start tester evaluator"), - TesterInitState = #{owner => Owner}, + TesterInitState = #{owner => Pid}, Tester = evaluator_start("tester", TesterSeq, TesterInitState), i("await evaluator"), @@ -3088,7 +3131,7 @@ sc_lc_recv_response_tcp4(doc) -> sc_lc_recv_response_tcp4(_Config) when is_list(_Config) -> tc_try(sc_lc_recv_response_tcp4, fun() -> - %% not_yet_implemented(), + ?TT(?SECS(10)), Recv = fun(Sock) -> socket:recv(Sock) end, InitState = #{domain => inet, type => stream, @@ -3111,6 +3154,7 @@ sc_lc_recv_response_tcp6(_Config) when is_list(_Config) -> tc_try(sc_lc_recv_response_tcp6, fun() -> not_yet_implemented(), + ?TT(?SECS(10)), Recv = fun(Sock) -> socket:recv(Sock) end, InitState = #{domain => inet6, type => stream, @@ -3646,20 +3690,26 @@ sc_lc_receive_response_tcp(InitState) -> i("start acceptor evaluator"), AccInitState = InitState, - Acceptor = evaluator_start("acceptor", AcceptorSeq, AccInitState), + #ev{pid = APid} = Acceptor = evaluator_start("acceptor", + AcceptorSeq, + AccInitState), i("start handler evaluator"), HandlerInitState = #{recv => maps:get(recv, InitState)}, - Handler = evaluator_start("handler", HandlerSeq, HandlerInitState), + #ev{pid = HPid} = Handler = evaluator_start("handler", + HandlerSeq, + HandlerInitState), i("start client evaluator"), ClientInitState = InitState, - Client = evaluator_start("client", ClientSeq, ClientInitState), + #ev{pid = CPid} = Client = evaluator_start("client", + ClientSeq, + ClientInitState), i("start tester evaluator"), - TesterInitState = #{acceptor => Acceptor, - handler => Handler, - client => Client}, + TesterInitState = #{acceptor => APid, + handler => HPid, + client => CPid}, Tester = evaluator_start("tester", TesterSeq, TesterInitState), i("await evaluator"), @@ -3680,6 +3730,7 @@ sc_rc_recv_response_tcp4(_Config) when is_list(_Config) -> tc_try(sc_rc_recv_response_tcp4, fun() -> not_yet_implemented(), + ?TT(?SECS(10)), Recv = fun(Sock) -> socket:recv(Sock) end, InitState = #{domain => inet, type => stream, @@ -3702,6 +3753,7 @@ sc_rc_recv_response_tcp6(_Config) when is_list(_Config) -> tc_try(sc_rc_recv_response_tcp6, fun() -> not_yet_implemented(), + ?TT(?SECS(10)), Recv = fun(Sock) -> socket:recv(Sock) end, InitState = #{domain => inet6, type => stream, @@ -3730,6 +3782,7 @@ sc_lc_recvmsg_response_tcp4(doc) -> sc_lc_recvmsg_response_tcp4(_Config) when is_list(_Config) -> tc_try(sc_lc_recvmsg_response_tcp4, fun() -> + ?TT(?SECS(10)), Recv = fun(Sock) -> socket:recvmsg(Sock) end, InitState = #{domain => inet, type => stream, @@ -3752,6 +3805,7 @@ sc_lc_recvmsg_response_tcp6(_Config) when is_list(_Config) -> tc_try(sc_recvmsg_response_tcp6, fun() -> not_yet_implemented(), + ?TT(?SECS(10)), Recv = fun(Sock) -> socket:recvmsg(Sock) end, InitState = #{domain => inet6, type => stream, @@ -3774,6 +3828,7 @@ sc_rc_recvmsg_response_tcp4(_Config) when is_list(_Config) -> tc_try(sc_rc_recvmsg_response_tcp4, fun() -> not_yet_implemented(), + ?TT(?SECS(10)), Recv = fun(Sock) -> socket:recvmsg(Sock) end, InitState = #{domain => inet, type => stream, @@ -3796,6 +3851,7 @@ sc_rc_recvmsg_response_tcp6(_Config) when is_list(_Config) -> tc_try(sc_rc_recvmsg_response_tcp6, fun() -> not_yet_implemented(), + ?TT(?SECS(10)), Recv = fun(Sock) -> socket:recvmsg(Sock) end, InitState = #{domain => inet6, type => stream, @@ -3820,6 +3876,7 @@ sc_lc_acceptor_response_tcp4(_Config) when is_list(_Config) -> tc_try(sc_lc_acceptor_response_tcp4, fun() -> not_yet_implemented(), + ?TT(?SECS(10)), InitState = #{domain => inet, type => stream, protocol => tcp}, @@ -3842,6 +3899,7 @@ sc_lc_acceptor_response_tcp6(_Config) when is_list(_Config) -> tc_try(sc_lc_acceptor_response_tcp6, fun() -> not_yet_implemented(), + ?TT(?SECS(10)), InitState = #{domain => inet, type => stream, protocol => tcp}, @@ -3899,18 +3957,17 @@ which_addr2(Domain, [_|IFO]) -> %% will be used as exit reason. %% A successful command shall evaluate to ok | {ok, NewState} --spec evaluator_start(Name, Seq, Init) -> {Pid, MRef} when +-spec evaluator_start(Name, Seq, Init) -> ev() when Name :: string(), Seq :: [command()], - Init :: initial_evaluator_state(), - Pid :: pid(), - MRef :: reference(). + Init :: initial_evaluator_state(). evaluator_start(Name, Seq, Init) when is_list(Name) andalso is_list(Seq) andalso (Seq =/= []) -> Init2 = Init#{parent => self()}, - {Pid, _} = erlang:spawn_monitor(fun() -> evaluator_init(Name, Seq, Init2) end), - Pid. + {Pid, MRef} = erlang:spawn_monitor( + fun() -> evaluator_init(Name, Seq, Init2) end), + ?MKEV(Name, Pid, MRef). evaluator_init(Name, Seq, Init) -> put(sname, Name), @@ -3949,24 +4006,26 @@ await_evaluator_finish([], Fails) -> await_evaluator_finish(Evs, Fails) -> receive {'DOWN', _MRef, process, Pid, normal} -> - case lists:delete(Pid, Evs) of - Evs -> + case lists:keysearch(Pid, #ev.pid, Evs) of + {value, #ev{name = Name}} -> + i("evaluator '~s' (~p) success", [Name, Pid]), + NewEvs = lists:keydelete(Pid, #ev.pid, Evs), + await_evaluator_finish(NewEvs, Fails); + false -> i("unknown process ~p died (normal)", [Pid]), - await_evaluator_finish(Evs, Fails); - NewEvs -> - i("evaluator ~p success", [Pid]), - await_evaluator_finish(NewEvs, Fails) - end; + await_evaluator_finish(Evs, Fails) + end; {'DOWN', _MRef, process, Pid, Reason} -> - case lists:delete(Pid, Evs) of - Evs -> + case lists:keysearch(Pid, #ev.pid, Evs) of + {value, #ev{name = Name}} -> + i("evaluator '~s' (~p) failed", [Name, Pid]), + NewEvs = lists:keydelete(Pid, #ev.pid, Evs), + await_evaluator_finish(NewEvs, [{Pid, Reason}|Fails]); + false -> i("unknown process ~p died: " - "~n ~p", [Pid, Reason]), - await_evaluator_finish(Evs, Fails); - NewEvs -> - i("Evaluator ~p failed", [Pid]), - await_evaluator_finish(NewEvs, [{Pid, Reason}|Fails]) - end + "~n ~p", [Pid, Reason]), + await_evaluator_finish(Evs, Fails) + end end. -- cgit v1.2.3 From 76d0b4f3814273ee92efd2d4aaeac12c6f655f05 Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Fri, 19 Oct 2018 18:18:37 +0200 Subject: [socket-nif|test] Local close cases extended The receive response local close tcp cases was extended wityh two more handler (readers). OTP-14831 --- erts/emulator/test/socket_SUITE.erl | 268 +++++++++++++++++++++++++----------- 1 file changed, 189 insertions(+), 79 deletions(-) diff --git a/erts/emulator/test/socket_SUITE.erl b/erts/emulator/test/socket_SUITE.erl index 92dd5e7097..0edcc3eb04 100644 --- a/erts/emulator/test/socket_SUITE.erl +++ b/erts/emulator/test/socket_SUITE.erl @@ -3231,8 +3231,10 @@ sc_lc_receive_response_tcp(InitState) -> ee("Unexpected DOWN regarding tester ~p: " "~n ~p", [Reason]), {error, {unexpected_exit, tester}}; - {continue, Tester, Handler} -> - {ok, State#{handler => Handler}} + {continue, Tester, {H1, H2, H3}} -> + {ok, State#{handler1 => H1, + handler2 => H2, + handler3 => H3}} end end}, #{desc => "await connection", @@ -3245,11 +3247,27 @@ sc_lc_receive_response_tcp(InitState) -> ERROR end end}, - #{desc => "transfer new connection to handler", - cmd => fun(#{handler := Handler, csock := Sock}) -> - ok = socket:setopt(Sock, - otp, controlling_process, - Handler), + #{desc => "transfer connection to handler 1", + cmd => fun(#{handler1 := Handler, csock := Sock}) -> + %% ok = socket:setopt(Sock, + %% otp, controlling_process, + %% Handler), + Handler ! {connection, Sock}, + ok + end}, + #{desc => "transfer connection to handler 2", + cmd => fun(#{handler2 := Handler, csock := Sock}) -> + %% ok = socket:setopt(Sock, + %% otp, controlling_process, + %% Handler), + Handler ! {connection, Sock}, + ok + end}, + #{desc => "transfer connection to handler 3", + cmd => fun(#{handler3 := Handler, csock := Sock}) -> + %% ok = socket:setopt(Sock, + %% otp, controlling_process, + %% Handler), Handler ! {connection, Sock}, ok end}, @@ -3269,11 +3287,7 @@ sc_lc_receive_response_tcp(InitState) -> ok end end}, - %% #{desc => "enable debug", - %% cmd => fun(#{csock := Sock}) -> - %% socket:setopt(Sock, otp, debug, true) - %% end}, - #{desc => "close (the connection) socket", + #{desc => "close the connection socket", cmd => fun(#{csock := Sock}) -> socket:close(Sock) end}, @@ -3344,16 +3358,6 @@ sc_lc_receive_response_tcp(InitState) -> Tester ! {ready, self()}, ok end}, - %% #{desc => "enable debug", - %% cmd => fun(#{sock := Sock}) -> - %% socket:setopt(Sock, otp, debug, true) - %% end}, - %% #{desc => "monitored-by", - %% cmd => fun(_) -> - %% {_, Mons} = process_info(self(), monitored_by), - %% ei("Monitored By: ~p", [Mons]), - %% ok - %% end}, #{desc => "attempt recv", cmd => fun(#{sock := Sock, recv := Recv} = State) -> case Recv(Sock) of @@ -3361,6 +3365,7 @@ sc_lc_receive_response_tcp(InitState) -> ee("Unexpected data received"), {error, unexpected_data}; {error, closed} -> + ei("received expected 'closed' result"), State1 = maps:remove(sock, State), {ok, State1}; {error, Reason} = ERROR -> @@ -3369,28 +3374,11 @@ sc_lc_receive_response_tcp(InitState) -> ERROR end end}, - %% #{desc => "monitored-by", - %% cmd => fun(_) -> - %% {_, Mons} = process_info(self(), monitored_by), - %% ei("Monitored By: ~p", [Mons]), - %% ok - %% end}, #{desc => "announce ready (close)", cmd => fun(#{tester := Tester}) -> Tester ! {ready, self()}, ok end}, - #{desc => "sleep some", - cmd => fun(_) -> - ?SLEEP(1000), - ok - end}, - %% #{desc => "monitored-by", - %% cmd => fun(_) -> - %% {_, Mons} = process_info(self(), monitored_by), - %% ei("Monitored By: ~p", [Mons]), - %% ok - %% end}, #{desc => "await terminate", cmd => fun(#{tester := Tester} = _State) -> receive @@ -3468,6 +3456,11 @@ sc_lc_receive_response_tcp(InitState) -> {ok, State#{lport => Port}} end end}, + #{desc => "sleep", + cmd => fun(_) -> + ?SLEEP(?SECS(1)), + ok + end}, #{desc => "connect to server", cmd => fun(#{sock := Sock, lsa := LSA, lport := LPort}) -> socket:connect(Sock, LSA#{port => LPort}) @@ -3511,8 +3504,18 @@ sc_lc_receive_response_tcp(InitState) -> _MRef = erlang:monitor(process, Pid), ok end}, - #{desc => "monitor handler", - cmd => fun(#{handler := Pid} = _State) -> + #{desc => "monitor handler 1", + cmd => fun(#{handler1 := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + #{desc => "monitor handler 2", + cmd => fun(#{handler2 := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + #{desc => "monitor handler 3", + cmd => fun(#{handler3 := Pid} = _State) -> _MRef = erlang:monitor(process, Pid), ok end}, @@ -3540,19 +3543,51 @@ sc_lc_receive_response_tcp(InitState) -> end end}, - %% Start the handler - #{desc => "order handler start", - cmd => fun(#{handler := Pid} = _State) -> + %% Start the handler(s) + #{desc => "order handler 1 start", + cmd => fun(#{handler1 := Pid} = _State) -> Pid ! {start, self()}, ok end}, - #{desc => "await handler ready (init)", - cmd => fun(#{handler := Pid} = _State) -> + #{desc => "await handler 1 ready (init)", + cmd => fun(#{handler1 := Pid} = _State) -> receive {'DOWN', _, process, Pid, Reason} -> - ee("Unexpected DOWN regarding handler ~p: " + ee("Unexpected DOWN regarding handler 1 ~p: " "~n ~p", [Pid, Reason]), - {error, {unexpected_exit, acceptor}}; + {error, {unexpected_exit, handler1}}; + {ready, Pid} -> + ok + end + end}, + #{desc => "order handler 2 start", + cmd => fun(#{handler2 := Pid} = _State) -> + Pid ! {start, self()}, + ok + end}, + #{desc => "await handler 2 ready (init)", + cmd => fun(#{handler2 := Pid} = _State) -> + receive + {'DOWN', _, process, Pid, Reason} -> + ee("Unexpected DOWN regarding handler 2 ~p: " + "~n ~p", [Pid, Reason]), + {error, {unexpected_exit, handler2}}; + {ready, Pid} -> + ok + end + end}, + #{desc => "order handler 3 start", + cmd => fun(#{handler3 := Pid} = _State) -> + Pid ! {start, self()}, + ok + end}, + #{desc => "await handler 3 ready (init)", + cmd => fun(#{handler3 := Pid} = _State) -> + receive + {'DOWN', _, process, Pid, Reason} -> + ee("Unexpected DOWN regarding handler 3 ~p: " + "~n ~p", [Pid, Reason]), + {error, {unexpected_exit, handler3}}; {ready, Pid} -> ok end @@ -3578,8 +3613,11 @@ sc_lc_receive_response_tcp(InitState) -> %% The actual test #{desc => "order acceptor to continue", - cmd => fun(#{acceptor := Pid, handler := Handler} = _State) -> - Pid ! {continue, self(), Handler}, + cmd => fun(#{acceptor := Pid, + handler1 := H1, + handler2 := H2, + handler3 := H3} = _State) -> + Pid ! {continue, self(), {H1, H2, H3}}, ok end}, #{desc => "order client to continue", @@ -3609,20 +3647,42 @@ sc_lc_receive_response_tcp(InitState) -> ok end end}, - #{desc => "await handler ready (connection)", - cmd => fun(#{handler := Pid} = _State) -> + #{desc => "await handler 1 ready (connection)", + cmd => fun(#{handler1 := Pid} = _State) -> receive {'DOWN', _, process, Pid, Reason} -> - ee("Unexpected DOWN regarding handler ~p: " + ee("Unexpected DOWN regarding handler 1 ~p: " "~n ~p", [Reason]), - {error, {unexpected_exit, acceptor}}; + {error, {unexpected_exit, handler1}}; {ready, Pid} -> ok end end}, - #{desc => "sleep some", + #{desc => "await handler 2 ready (connection)", + cmd => fun(#{handler2 := Pid} = _State) -> + receive + {'DOWN', _, process, Pid, Reason} -> + ee("Unexpected DOWN regarding handler 2 ~p: " + "~n ~p", [Reason]), + {error, {unexpected_exit, handler2}}; + {ready, Pid} -> + ok + end + end}, + #{desc => "await handler 3 ready (connection)", + cmd => fun(#{handler3 := Pid} = _State) -> + receive + {'DOWN', _, process, Pid, Reason} -> + ee("Unexpected DOWN regarding handler 3 ~p: " + "~n ~p", [Reason]), + {error, {unexpected_exit, handler3}}; + {ready, Pid} -> + ok + end + end}, + #{desc => "sleep", cmd => fun(_State) -> - ?SLEEP(1000), + ?SLEEP(?SECS(1)), ok end}, #{desc => "order acceptor to continue (close)", @@ -3630,29 +3690,75 @@ sc_lc_receive_response_tcp(InitState) -> Pid ! {continue, self()}, ok end}, - #{desc => "await handler ready (close)", - cmd => fun(#{handler := Pid} = _State) -> + #{desc => "await handler 1 ready (close)", + cmd => fun(#{handler1 := Pid} = _State) -> receive {'DOWN', _, process, Pid, Reason} -> - ee("Unexpected DOWN regarding handler ~p: " + ee("Unexpected DOWN regarding handler 1 ~p: " "~n ~p", [Pid, Reason]), - {error, {unexpected_exit, acceptor}}; + {error, {unexpected_exit, handler1}}; + {ready, Pid} -> + ok + end + end}, + #{desc => "await handler 2 ready (close)", + cmd => fun(#{handler2 := Pid} = _State) -> + receive + {'DOWN', _, process, Pid, Reason} -> + ee("Unexpected DOWN regarding handler 2 ~p: " + "~n ~p", [Pid, Reason]), + {error, {unexpected_exit, handler2}}; + {ready, Pid} -> + ok + end + end}, + #{desc => "await handler 3 ready (close)", + cmd => fun(#{handler3 := Pid} = _State) -> + receive + {'DOWN', _, process, Pid, Reason} -> + ee("Unexpected DOWN regarding handler 2 ~p: " + "~n ~p", [Pid, Reason]), + {error, {unexpected_exit, handler2}}; {ready, Pid} -> ok end end}, %% Terminations - #{desc => "order handler to terminate", - cmd => fun(#{handler := Pid} = _State) -> + #{desc => "order handler 1 to terminate", + cmd => fun(#{handler1 := Pid} = _State) -> + Pid ! {terminate, self()}, + ok + end}, + #{desc => "await handler 1 termination", + cmd => fun(#{handler1 := Pid} = State) -> + receive + {'DOWN', _, process, Pid, _} -> + {ok, maps:remove(handler1, State)} + end + end}, + #{desc => "order handler 2 to terminate", + cmd => fun(#{handler2 := Pid} = _State) -> + Pid ! {terminate, self()}, + ok + end}, + #{desc => "await handler 2 termination", + cmd => fun(#{handler2 := Pid} = State) -> + receive + {'DOWN', _, process, Pid, _} -> + {ok, maps:remove(handler2, State)} + end + end}, + #{desc => "order handler 3 to terminate", + cmd => fun(#{handler3 := Pid} = _State) -> Pid ! {terminate, self()}, ok end}, - #{desc => "await handler termination", - cmd => fun(#{handler := Pid} = State) -> + #{desc => "await handler 3 termination", + cmd => fun(#{handler3 := Pid} = State) -> receive {'DOWN', _, process, Pid, _} -> - {ok, maps:remove(handler, State)} + {ok, maps:remove(handler3, State)} end end}, #{desc => "order client to terminate", @@ -3690,30 +3796,34 @@ sc_lc_receive_response_tcp(InitState) -> i("start acceptor evaluator"), AccInitState = InitState, - #ev{pid = APid} = Acceptor = evaluator_start("acceptor", - AcceptorSeq, - AccInitState), + Acceptor = evaluator_start("acceptor", AcceptorSeq, AccInitState), - i("start handler evaluator"), + i("start handler 1 evaluator"), HandlerInitState = #{recv => maps:get(recv, InitState)}, - #ev{pid = HPid} = Handler = evaluator_start("handler", - HandlerSeq, - HandlerInitState), + Handler1 = evaluator_start("handler-1", HandlerSeq, HandlerInitState), + + i("start handler 2 evaluator"), + Handler2 = evaluator_start("handler-2", HandlerSeq, HandlerInitState), + + i("start handler 3 evaluator"), + Handler3 = evaluator_start("handler-3", HandlerSeq, HandlerInitState), i("start client evaluator"), ClientInitState = InitState, - #ev{pid = CPid} = Client = evaluator_start("client", - ClientSeq, - ClientInitState), + Client = evaluator_start("client", ClientSeq, ClientInitState), i("start tester evaluator"), - TesterInitState = #{acceptor => APid, - handler => HPid, - client => CPid}, + TesterInitState = #{acceptor => Acceptor#ev.pid, + handler1 => Handler1#ev.pid, + handler2 => Handler2#ev.pid, + handler3 => Handler3#ev.pid, + client => Client#ev.pid}, Tester = evaluator_start("tester", TesterSeq, TesterInitState), i("await evaluator"), - ok = await_evaluator_finish([Acceptor, Handler, Client, Tester]). + ok = await_evaluator_finish([Acceptor, + Handler1, Handler2, Handler3, + Client, Tester]). -- cgit v1.2.3 From ada0029d11c33cef2b2aabc9ef2902a98f574ffb Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Mon, 22 Oct 2018 14:19:29 +0200 Subject: [socket-nif] Initiation of "current reader" missing for recvfrom When calling the recvfrom function when there is no data, we should be made wait (for the specified amount of time). But this did not work because the "current reader" structure was not initiated. OTP-14831 --- erts/emulator/nifs/common/socket_nif.c | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/erts/emulator/nifs/common/socket_nif.c b/erts/emulator/nifs/common/socket_nif.c index 45462ff772..f9eb041ad1 100644 --- a/erts/emulator/nifs/common/socket_nif.c +++ b/erts/emulator/nifs/common/socket_nif.c @@ -13377,21 +13377,24 @@ BOOLEAN_T recv_check_reader(ErlNifEnv* env, } if (!compare_pids(env, &descP->currentReader.pid, &caller)) { + ERL_NIF_TERM tmp; + /* Not the "current reader", so (maybe) push onto queue */ SSDBG( descP, ("SOCKET", "recv_check_reader -> not (current) reader\r\n") ); if (!reader_search4pid(env, descP, &caller)) - *checkResult = reader_push(env, descP, caller, ref); + tmp = reader_push(env, descP, caller, ref); else - *checkResult = esock_make_error(env, esock_atom_eagain); + tmp = esock_make_error(env, esock_atom_eagain); SSDBG( descP, ("SOCKET", - "recv_check_reader -> queue (push) result: %T\r\n", - checkResult) ); - + "recv_check_reader -> queue (push) result: %T\r\n", tmp) ); + + *checkResult = tmp; + return FALSE; } @@ -13769,6 +13772,7 @@ ERL_NIF_TERM recvfrom_check_result(ErlNifEnv* env, unsigned int fromAddrLen, ERL_NIF_TERM recvRef) { + char* xres; ERL_NIF_TERM data; SSDBG( descP, @@ -13818,6 +13822,9 @@ ERL_NIF_TERM recvfrom_check_result(ErlNifEnv* env, SSDBG( descP, ("SOCKET", "recvfrom_check_result -> eagain\r\n") ); + if ((xres = recv_init_current_reader(env, descP, recvRef)) != NULL) + return esock_make_error_str(env, xres); + SELECT(env, descP->sock, (ERL_NIF_SELECT_READ), descP, NULL, recvRef); -- cgit v1.2.3 From e54fc6d2a4749f637f29e726de55102d37a9dbb1 Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Mon, 22 Oct 2018 14:20:46 +0200 Subject: [socket-nif|test] Add test cases for UDP recvfrom and recvmsg Add test cases for udp local socket close for UDP recvfrom and recvmsg. OTP-14831 --- erts/emulator/test/socket_SUITE.erl | 583 +++++++++++++++++++++++++++++++++++- 1 file changed, 575 insertions(+), 8 deletions(-) diff --git a/erts/emulator/test/socket_SUITE.erl b/erts/emulator/test/socket_SUITE.erl index 0edcc3eb04..85b8fcce77 100644 --- a/erts/emulator/test/socket_SUITE.erl +++ b/erts/emulator/test/socket_SUITE.erl @@ -73,8 +73,12 @@ sc_cpe_socket_cleanup_udp6/1, sc_lc_recv_response_tcp4/1, sc_lc_recv_response_tcp6/1, + sc_lc_recvfrom_response_udp4/1, + sc_lc_recvfrom_response_udp6/1, sc_lc_recvmsg_response_tcp4/1, sc_lc_recvmsg_response_tcp6/1, + sc_lc_recvmsg_response_udp4/1, + sc_lc_recvmsg_response_udp6/1, sc_lc_acceptor_response_tcp4/1, sc_lc_acceptor_response_tcp6/1, sc_rc_recv_response_tcp4/1, @@ -217,8 +221,13 @@ sc_lc_cases() -> sc_lc_recv_response_tcp4, sc_lc_recv_response_tcp6, + sc_lc_recvfrom_response_udp4, + sc_lc_recvfrom_response_udp6, + sc_lc_recvmsg_response_tcp4, sc_lc_recvmsg_response_tcp6, + sc_lc_recvmsg_response_udp4, + sc_lc_recvmsg_response_udp6, sc_lc_acceptor_response_tcp4, sc_lc_acceptor_response_tcp6 @@ -1238,24 +1247,22 @@ api_opt_simple_otp_controlling_process() -> i("start tcp (stream) socket"), ClientInitState1 = #{}, - #ev{pid = Pid1} = Client1 = evaluator_start("tcp-client", - ClientSeq, ClientInitState1), + Client1 = evaluator_start("tcp-client", ClientSeq, ClientInitState1), TesterInitState1 = #{domain => inet, type => stream, protocol => tcp, - client => Pid1}, + client => Client1#ev.pid}, Tester1 = evaluator_start("tcp-tester", TesterSeq, TesterInitState1), i("await tcp evaluator"), ok = await_evaluator_finish([Tester1, Client1]), i("start udp (dgram) socket"), ClientInitState2 = #{}, - #ev{pid = Pid2} = Client2 = evaluator_start("udp-client", - ClientSeq, ClientInitState2), + Client2 = evaluator_start("udp-client", ClientSeq, ClientInitState2), TesterInitState2 = #{domain => inet, type => dgram, protocol => udp, - client => Pid2}, + client => Client2#ev.pid}, Tester2 = evaluator_start("udp-tester", TesterSeq, TesterInitState2), i("await udp evaluator"), ok = await_evaluator_finish([Tester2, Client2]). @@ -3605,7 +3612,7 @@ sc_lc_receive_response_tcp(InitState) -> {'DOWN', _, process, Pid, Reason} -> ee("Unexpected DOWN regarding cient ~p: " "~n ~p", [Pid, Reason]), - {error, {unexpected_exit, acceptor}}; + {error, {unexpected_exit, client}}; {ready, Pid} -> ok end @@ -3642,7 +3649,7 @@ sc_lc_receive_response_tcp(InitState) -> {'DOWN', _, process, Pid, Reason} -> ee("Unexpected DOWN regarding client ~p: " "~n ~p", [Pid, Reason]), - {error, {unexpected_exit, acceptor}}; + {error, {unexpected_exit, client}}; {ready, Pid} -> ok end @@ -3827,6 +3834,525 @@ sc_lc_receive_response_tcp(InitState) -> +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test what happens when a socket is +%% locally closed while a process is calling the recvfrom function. +%% Socket is IPv4. +%% + +sc_lc_recvfrom_response_udp4(suite) -> + []; +sc_lc_recvfrom_response_udp4(doc) -> + []; +sc_lc_recvfrom_response_udp4(_Config) when is_list(_Config) -> + tc_try(sc_lc_recvfrom_response_udp4, + fun() -> + ?TT(?SECS(30)), + Recv = fun(Sock, To) -> socket:recvfrom(Sock, [], To) end, + InitState = #{domain => inet, + type => dgram, + protocol => udp, + recv => Recv}, + ok = sc_lc_receive_response_udp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test what happens when a socket is +%% locally closed while the process is calling the recv function. +%% Socket is IPv6. + +sc_lc_recvfrom_response_udp6(suite) -> + []; +sc_lc_recvfrom_response_udp6(doc) -> + []; +sc_lc_recvfrom_response_udp6(_Config) when is_list(_Config) -> + tc_try(sc_lc_recvfrom_response_udp6, + fun() -> + not_yet_implemented(), + ?TT(?SECS(30)), + Recv = fun(Sock, To) -> socket:recvfrom(Sock, [], To) end, + InitState = #{domain => inet6, + recv => Recv}, + ok = sc_lc_receive_response_udp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +sc_lc_receive_response_udp(InitState) -> + PrimServerSeq = + [ + %% *** Init part *** + #{desc => "await start", + cmd => fun(State) -> + receive + {start, Tester} -> + {ok, State#{tester => Tester}} + end + end}, + #{desc => "monitor tester", + cmd => fun(#{tester := Tester} = _State) -> + _MRef = erlang:monitor(process, Tester), + ok + end}, + #{desc => "local address", + cmd => fun(#{domain := Domain} = State) -> + LAddr = which_local_addr(Domain), + LSA = #{family => Domain, addr => LAddr}, + {ok, State#{lsa => LSA}} + end}, + #{desc => "open socket", + cmd => fun(#{domain := Domain} = State) -> + Sock = sock_open(Domain, dgram, udp), + SA = sock_sockname(Sock), + {ok, State#{sock => Sock, sa => SA}} + end}, + #{desc => "bind socket", + cmd => fun(#{sock := Sock, lsa := LSA}) -> + sock_bind(Sock, LSA), + ok + end}, + #{desc => "announce ready (init)", + cmd => fun(#{tester := Tester, sock := Sock}) -> + Tester ! {ready, self(), Sock}, + ok + end}, + + %% The actual test + #{desc => "await continue (receive)", + cmd => fun(#{tester := Tester} = State) -> + receive + {'DOWN', _, process, Tester, Reason} -> + ee("Unexpected DOWN regarding tester ~p: " + "~n ~p", [Reason]), + {error, {unexpected_exit, tester}}; + {continue, Tester, Timeout} -> + {ok, State#{timeout => Timeout}} + end + end}, + #{desc => "receive with timeout", + cmd => fun(#{sock := Sock, recv := Recv, timeout := Timeout}) -> + case Recv(Sock, Timeout) of + {error, timeout} -> + ok; + {ok, _} -> + {error, unexpected_success}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (timeout)", + cmd => fun(#{tester := Tester}) -> + Tester ! {ready, self()}, + ok + end}, + #{desc => "await continue (close)", + cmd => fun(#{tester := Tester} = _State) -> + receive + {'DOWN', _, process, Tester, Reason} -> + ee("Unexpected DOWN regarding tester ~p: " + "~n ~p", [Reason]), + {error, {unexpected_exit, tester}}; + {continue, Tester} -> + ok + end + end}, + #{desc => "close socket", + cmd => fun(#{sock := Sock} = State) -> + case socket:close(Sock) of + ok -> + {ok, maps:remove(sock, State)}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (closed)", + cmd => fun(#{tester := Tester}) -> + Tester ! {ready, self()}, + ok + end}, + + %% Termination + #{desc => "await terminate (from tester)", + cmd => fun(#{tester := Tester} = State) -> + receive + {'DOWN', _, process, Tester, Reason} -> + ee("Unexpected DOWN regarding tester ~p: " + "~n ~p", [Reason]), + {error, {unexpected_exit, tester}}; + {terminate, Tester} -> + {ok, maps:remove(tester, State)} + end + end}, + + %% *** We are done *** + #{desc => "finish", + cmd => fun(_) -> + {ok, normal} + end} + ], + + SecServerSeq = + [ + %% *** Init part *** + #{desc => "await start", + cmd => fun(State) -> + receive + {start, Tester, Sock} -> + {ok, State#{tester => Tester, sock => Sock}} + end + end}, + #{desc => "monitor tester", + cmd => fun(#{tester := Tester} = _State) -> + _MRef = erlang:monitor(process, Tester), + ok + end}, + #{desc => "announce ready (init)", + cmd => fun(#{tester := Tester}) -> + Tester ! {ready, self()}, + ok + end}, + + %% The actual test + #{desc => "await continue (receive)", + cmd => fun(#{tester := Tester} = _State) -> + receive + {'DOWN', _, process, Tester, Reason} -> + ee("Unexpected DOWN regarding tester ~p: " + "~n ~p", [Reason]), + {error, {unexpected_exit, tester}}; + {continue, Tester} -> + ok + end + end}, + #{desc => "receive", + cmd => fun(#{sock := Sock, recv := Recv} = State) -> + %% ok = socket:setopt(Sock, otp, debug, true), + case Recv(Sock, infinity) of + {error, closed} -> + {ok, maps:remove(sock, State)}; + {ok, _} -> + {error, unexpected_success}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (closed)", + cmd => fun(#{tester := Tester}) -> + Tester ! {ready, self()}, + ok + end}, + + %% Termination + #{desc => "await terminate (from tester)", + cmd => fun(#{tester := Tester} = State) -> + receive + {'DOWN', _, process, Tester, Reason} -> + ee("Unexpected DOWN regarding tester ~p: " + "~n ~p", [Reason]), + {error, {unexpected_exit, tester}}; + {terminate, Tester} -> + {ok, maps:remove(tester, State)} + end + end}, + + %% *** We are done *** + #{desc => "finish", + cmd => fun(_) -> + {ok, normal} + end} + ], + + + TesterSeq = + [ + %% *** Init part *** + #{desc => "monitor primary server", + cmd => fun(#{prim_server := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + #{desc => "monitor secondary server 1", + cmd => fun(#{sec_server1 := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + #{desc => "monitor secondary server 2", + cmd => fun(#{sec_server2 := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + #{desc => "monitor secondary server 3", + cmd => fun(#{sec_server3 := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + + %% Start the primary server + #{desc => "order 'primary server' start", + cmd => fun(#{prim_server := Pid} = _State) -> + Pid ! {start, self()}, + ok + end}, + #{desc => "await 'primary server' ready (init)", + cmd => fun(#{prim_server := Pid} = State) -> + receive + {'DOWN', _, process, Pid, Reason} -> + ee("Unexpected DOWN regarding prim-server ~p: " + "~n ~p", [Pid, Reason]), + {error, {unexpected_exit, prim_server}}; + {ready, Pid, Sock} -> + {ok, State#{sock => Sock}} + end + end}, + + %% Start the secondary server 1 + #{desc => "order 'secondary server 1' start", + cmd => fun(#{sec_server1 := Pid, sock := Sock} = _State) -> + Pid ! {start, self(), Sock}, + ok + end}, + #{desc => "await 'secondary server 1' ready (init)", + cmd => fun(#{sec_server1 := Pid} = _State) -> + receive + {'DOWN', _, process, Pid, Reason} -> + ee("Unexpected DOWN regarding sec-server ~p: " + "~n ~p", [Pid, Reason]), + {error, {unexpected_exit, sec_server1}}; + {ready, Pid} -> + ok + end + end}, + + %% Start the secondary server 2 + #{desc => "order 'secondary server 2' start", + cmd => fun(#{sec_server2 := Pid, sock := Sock} = _State) -> + Pid ! {start, self(), Sock}, + ok + end}, + #{desc => "await 'secondary server 2' ready (init)", + cmd => fun(#{sec_server2 := Pid} = _State) -> + receive + {'DOWN', _, process, Pid, Reason} -> + ee("Unexpected DOWN regarding sec-server ~p: " + "~n ~p", [Pid, Reason]), + {error, {unexpected_exit, sec_server1}}; + {ready, Pid} -> + ok + end + end}, + + %% Start the secondary server 3 + #{desc => "order 'secondary server 3' start", + cmd => fun(#{sec_server3 := Pid, sock := Sock} = _State) -> + Pid ! {start, self(), Sock}, + ok + end}, + #{desc => "await 'secondary server 3' ready (init)", + cmd => fun(#{sec_server3 := Pid} = _State) -> + receive + {'DOWN', _, process, Pid, Reason} -> + ee("Unexpected DOWN regarding sec-server ~p: " + "~n ~p", [Pid, Reason]), + {error, {unexpected_exit, sec_server1}}; + {ready, Pid} -> + ok + end + end}, + + + %% The actual test + %% Make all the seondary servers continue, with an infinit recvfrom + %% and then the prim-server with a timed recvfrom. + %% After the prim server notifies us (about the timeout) we order it + %% to close the socket, which should cause the all the secondary + %% server to return with error-closed. + + #{desc => "order 'secondary server 1' to continue", + cmd => fun(#{sec_server1 := Pid} = _State) -> + Pid ! {continue, self()}, + ok + end}, + #{desc => "sleep", + cmd => fun(_) -> + ?SLEEP(?SECS(1)), + ok + end}, + #{desc => "order 'secondary server 2' to continue", + cmd => fun(#{sec_server2 := Pid} = _State) -> + Pid ! {continue, self()}, + ok + end}, + #{desc => "sleep", + cmd => fun(_) -> + ?SLEEP(?SECS(1)), + ok + end}, + #{desc => "order 'secondary server 3' to continue", + cmd => fun(#{sec_server3 := Pid} = _State) -> + Pid ! {continue, self()}, + ok + end}, + #{desc => "sleep", + cmd => fun(_) -> + ?SLEEP(?SECS(1)), + ok + end}, + #{desc => "order 'primary server' to continue (recvfrom)", + cmd => fun(#{prim_server := Pid} = _State) -> + Pid ! {continue, self(), ?SECS(5)}, + ok + end}, + #{desc => "await 'primary server' ready (timeout)", + cmd => fun(#{prim_server := Pid} = _State) -> + receive + {'DOWN', _, process, Pid, Reason} -> + ee("Unexpected DOWN regarding prim-server ~p: " + "~n ~p", [Pid, Reason]), + {error, {unexpected_exit, prim_server}}; + {ready, Pid} -> + ok + end + end}, + #{desc => "order 'primary server' to continue (close)", + cmd => fun(#{prim_server := Pid} = _State) -> + Pid ! {continue, self()}, + ok + end}, + #{desc => "await 'primary server' ready (closed)", + cmd => fun(#{prim_server := Pid} = _State) -> + receive + {'DOWN', _, process, Pid, Reason} -> + ee("Unexpected DOWN regarding prim-server ~p: " + "~n ~p", [Pid, Reason]), + {error, {unexpected_exit, prim_server}}; + {ready, Pid} -> + ok + end + end}, + #{desc => "await 'secondary server 1' ready (closed)", + cmd => fun(#{sec_server1 := Pid} = _State) -> + receive + {'DOWN', _, process, Pid, Reason} -> + ee("Unexpected DOWN regarding sec-server-1 ~p: " + "~n ~p", [Pid, Reason]), + {error, {unexpected_exit, sec_server1}}; + {ready, Pid} -> + ok + end + end}, + #{desc => "await 'secondary server 2' ready (closed)", + cmd => fun(#{sec_server2 := Pid} = _State) -> + receive + {'DOWN', _, process, Pid, Reason} -> + ee("Unexpected DOWN regarding sec-server-2 ~p: " + "~n ~p", [Pid, Reason]), + {error, {unexpected_exit, sec_server2}}; + {ready, Pid} -> + ok + end + end}, + #{desc => "await 'secondary server 3' ready (closed)", + cmd => fun(#{sec_server3 := Pid} = _State) -> + receive + {'DOWN', _, process, Pid, Reason} -> + ee("Unexpected DOWN regarding sec-server-3 ~p: " + "~n ~p", [Pid, Reason]), + {error, {unexpected_exit, sec_server3}}; + {ready, Pid} -> + ok + end + end}, + + + %% Terminations + #{desc => "order 'secondary server 3' to terminate", + cmd => fun(#{sec_server3 := Pid} = _State) -> + Pid ! {terminate, self()}, + ok + end}, + #{desc => "await 'secondary server 3' termination", + cmd => fun(#{sec_server3 := Pid} = State) -> + receive + {'DOWN', _, process, Pid, _} -> + {ok, maps:remove(sec_server3, State)} + end + end}, + #{desc => "order 'secondary server 2' to terminate", + cmd => fun(#{sec_server2 := Pid} = _State) -> + Pid ! {terminate, self()}, + ok + end}, + #{desc => "await 'secondary server 2' termination", + cmd => fun(#{sec_server2 := Pid} = State) -> + receive + {'DOWN', _, process, Pid, _} -> + {ok, maps:remove(sec_server2, State)} + end + end}, + #{desc => "order 'secondary server 1' to terminate", + cmd => fun(#{sec_server1 := Pid} = _State) -> + Pid ! {terminate, self()}, + ok + end}, + #{desc => "await 'secondary server 1' termination", + cmd => fun(#{sec_server1 := Pid} = State) -> + receive + {'DOWN', _, process, Pid, _} -> + {ok, maps:remove(sec_server1, State)} + end + end}, + #{desc => "order 'primary server' to terminate", + cmd => fun(#{prim_server := Pid} = _State) -> + Pid ! {terminate, self()}, + ok + end}, + #{desc => "await 'primary server' termination", + cmd => fun(#{prim_server := Pid} = State) -> + receive + {'DOWN', _, process, Pid, _} -> + {ok, maps:remove(prim_server, State)} + end + end}, + + + %% *** We are done *** + #{desc => "finish", + cmd => fun(_) -> + {ok, normal} + end} + ], + + + i("start 'primary server' evaluator"), + PrimSrvInitState = InitState, + PrimServer = evaluator_start("prim-server", PrimServerSeq, PrimSrvInitState), + + i("start 'secondary server 1' evaluator"), + SecSrvInitState = #{recv => maps:get(recv, InitState)}, + SecServer1 = evaluator_start("sec-server-1", SecServerSeq, SecSrvInitState), + + i("start 'secondary server 2' evaluator"), + SecServer2 = evaluator_start("sec-server-2", SecServerSeq, SecSrvInitState), + + i("start 'secondary server 3' evaluator"), + SecServer3 = evaluator_start("sec-server-3", SecServerSeq, SecSrvInitState), + + i("start 'tester' evaluator"), + TesterInitState = #{prim_server => PrimServer#ev.pid, + sec_server1 => SecServer1#ev.pid, + sec_server2 => SecServer2#ev.pid, + sec_server3 => SecServer3#ev.pid}, + Tester = evaluator_start("tester", TesterSeq, TesterInitState), + + i("await evaluator"), + ok = await_evaluator_finish([PrimServer, + SecServer1, SecServer2, SecServer3, + Tester]). + + + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% This test case is intended to test what happens when a socket is %% remotely closed while the process is calling the recv function. @@ -3925,6 +4451,47 @@ sc_lc_recvmsg_response_tcp6(_Config) when is_list(_Config) -> end). +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test what happens when a socket is +%% locally closed while the process is calling the recvmsg function. +%% Socket is IPv4. + +sc_lc_recvmsg_response_udp4(suite) -> + []; +sc_lc_recvmsg_response_udp4(doc) -> + []; +sc_lc_recvmsg_response_udp4(_Config) when is_list(_Config) -> + tc_try(sc_lc_recvmsg_response_udp4, + fun() -> + ?TT(?SECS(10)), + Recv = fun(Sock, To) -> socket:recvmsg(Sock, To) end, + InitState = #{domain => inet, + recv => Recv}, + ok = sc_lc_receive_response_udp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test what happens when a socket is +%% locally closed while the process is calling the recvmsg function. +%% Socket is IPv6. + +sc_lc_recvmsg_response_udp6(suite) -> + []; +sc_lc_recvmsg_response_udp6(doc) -> + []; +sc_lc_recvmsg_response_udp6(_Config) when is_list(_Config) -> + tc_try(sc_recvmsg_response_udp6, + fun() -> + not_yet_implemented(), + ?TT(?SECS(10)), + Recv = fun(Sock, To) -> socket:recvmsg(Sock, To) end, + InitState = #{domain => inet6, + recv => Recv}, + ok = sc_lc_receive_response_udp(InitState) + end). + + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% This test case is intended to test what happens when a socket is %% remotely closed while the process is calling the recvmsg function. -- cgit v1.2.3 From 8d9b04f2fa58069829f84a6cb4d12c1dd169b468 Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Mon, 22 Oct 2018 18:31:26 +0200 Subject: [socket-nif|test] Add test case for accept handling socket close Add test cases for tcp local socket close for the accept function. OTP-14831 --- erts/emulator/test/socket_SUITE.erl | 495 +++++++++++++++++++++++++++++++++++- 1 file changed, 488 insertions(+), 7 deletions(-) diff --git a/erts/emulator/test/socket_SUITE.erl b/erts/emulator/test/socket_SUITE.erl index 85b8fcce77..692829d220 100644 --- a/erts/emulator/test/socket_SUITE.erl +++ b/erts/emulator/test/socket_SUITE.erl @@ -4135,9 +4135,9 @@ sc_lc_receive_response_udp(InitState) -> cmd => fun(#{sec_server2 := Pid} = _State) -> receive {'DOWN', _, process, Pid, Reason} -> - ee("Unexpected DOWN regarding sec-server ~p: " + ee("Unexpected DOWN regarding sec-server-2 ~p: " "~n ~p", [Pid, Reason]), - {error, {unexpected_exit, sec_server1}}; + {error, {unexpected_exit, sec_server2}}; {ready, Pid} -> ok end @@ -4153,9 +4153,9 @@ sc_lc_receive_response_udp(InitState) -> cmd => fun(#{sec_server3 := Pid} = _State) -> receive {'DOWN', _, process, Pid, Reason} -> - ee("Unexpected DOWN regarding sec-server ~p: " + ee("Unexpected DOWN regarding sec-server-3 ~p: " "~n ~p", [Pid, Reason]), - {error, {unexpected_exit, sec_server1}}; + {error, {unexpected_exit, sec_server3}}; {ready, Pid} -> ok end @@ -4552,7 +4552,6 @@ sc_lc_acceptor_response_tcp4(doc) -> sc_lc_acceptor_response_tcp4(_Config) when is_list(_Config) -> tc_try(sc_lc_acceptor_response_tcp4, fun() -> - not_yet_implemented(), ?TT(?SECS(10)), InitState = #{domain => inet, type => stream, @@ -4586,8 +4585,490 @@ sc_lc_acceptor_response_tcp6(_Config) when is_list(_Config) -> %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -sc_lc_acceptor_response_tcp(_InitState) -> - ok. +sc_lc_acceptor_response_tcp(InitState) -> + PrimAcceptorSeq = + [ + %% *** Wait for start order part *** + #{desc => "await start (from tester)", + cmd => fun(State) -> + receive + {start, Tester} when is_pid(Tester) -> + {ok, State#{tester => Tester}} + end + end}, + #{desc => "monitor tester", + cmd => fun(#{tester := Tester} = _State) -> + _MRef = erlang:monitor(process, Tester), + ok + end}, + + %% *** Init part *** + #{desc => "which local address", + cmd => fun(#{domain := Domain} = State) -> + LAddr = which_local_addr(Domain), + LSA = #{family => Domain, addr => LAddr}, + {ok, State#{lsa => LSA}} + end}, + #{desc => "create (listen) socket", + cmd => fun(#{domain := Domain, + type := Type, + protocol := Proto} = State) -> + case socket:open(Domain, Type, Proto) of + {ok, Sock} -> + {ok, State#{sock => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "bind to local address", + cmd => fun(#{sock := Sock, lsa := LSA} = _State) -> + case socket:bind(Sock, LSA) of + {ok, _Port} -> + ok; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "make listen socket", + cmd => fun(#{sock := Sock}) -> + socket:listen(Sock) + end}, + #{desc => "announce ready (init)", + cmd => fun(#{tester := Tester, sock := Sock} = _State) -> + Tester ! {ready, self(), Sock}, + ok + end}, + + %% The actual test + #{desc => "await continue (accept)", + cmd => fun(#{tester := Tester} = State) -> + receive + {'DOWN', _, process, Tester, Reason} -> + ee("Unexpected DOWN regarding tester ~p: " + "~n ~p", [Reason]), + {error, {unexpected_exit, tester}}; + {continue, Tester, Timeout} -> + {ok, State#{timeout => Timeout}} + end + end}, + #{desc => "await connection", + cmd => fun(#{sock := Sock, timeout := Timeout} = _State) -> + case socket:accept(Sock, Timeout) of + {error, timeout} -> + ok; + {ok, Sock} -> + ee("unexpected success"), + (catch socket:close(Sock)), + {error, unexpected_success}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (accept timeout)", + cmd => fun(#{tester := Tester}) -> + Tester ! {ready, self()}, + ok + end}, + #{desc => "await continue (close)", + cmd => fun(#{tester := Tester} = _State) -> + receive + {'DOWN', _, process, Tester, Reason} -> + ee("Unexpected DOWN regarding tester ~p: " + "~n ~p", [Reason]), + {error, {unexpected_exit, tester}}; + {continue, Tester} -> + ok + end + end}, + #{desc => "close socket", + cmd => fun(#{sock := Sock} = State) -> + case socket:close(Sock) of + ok -> + {ok, maps:remove(sock, State)}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (closed)", + cmd => fun(#{tester := Tester}) -> + Tester ! {ready, self()}, + ok + end}, + + % Termination + #{desc => "await terminate", + cmd => fun(#{tester := Tester} = State) -> + receive + {'DOWN', _, process, Tester, Reason} -> + ee("Unexpected DOWN regarding tester ~p: " + "~n ~p", [Reason]), + {error, {unexpected_exit, tester}}; + {terminate, Tester} -> + {ok, maps:remove(tester, State)} + end + end}, + + %% *** We are done *** + #{desc => "finish", + cmd => fun(_) -> + {ok, normal} + end} + ], + + SecAcceptorSeq = + [ + %% *** Init part *** + #{desc => "await start", + cmd => fun(State) -> + receive + {start, Tester, Sock} -> + {ok, State#{tester => Tester, sock => Sock}} + end + end}, + #{desc => "monitor tester", + cmd => fun(#{tester := Tester} = _State) -> + _MRef = erlang:monitor(process, Tester), + ok + end}, + #{desc => "announce ready (init)", + cmd => fun(#{tester := Tester}) -> + Tester ! {ready, self()}, + ok + end}, + + %% The actual test + #{desc => "await continue (accept)", + cmd => fun(#{tester := Tester} = _State) -> + receive + {'DOWN', _, process, Tester, Reason} -> + ee("Unexpected DOWN regarding tester ~p: " + "~n ~p", [Reason]), + {error, {unexpected_exit, tester}}; + {continue, Tester} -> + ok + end + end}, + #{desc => "accept", + cmd => fun(#{sock := Sock} = State) -> + %% ok = socket:setopt(Sock, otp, debug, true), + case socket:accept(Sock) of + {error, closed} -> + {ok, maps:remove(sock, State)}; + {ok, _} -> + {error, unexpected_success}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (closed)", + cmd => fun(#{tester := Tester}) -> + Tester ! {ready, self()}, + ok + end}, + + %% Termination + #{desc => "await terminate (from tester)", + cmd => fun(#{tester := Tester} = State) -> + receive + {'DOWN', _, process, Tester, Reason} -> + ee("Unexpected DOWN regarding tester ~p: " + "~n ~p", [Reason]), + {error, {unexpected_exit, tester}}; + {terminate, Tester} -> + {ok, maps:remove(tester, State)} + end + end}, + + %% *** We are done *** + #{desc => "finish", + cmd => fun(_) -> + {ok, normal} + end} + ], + + TesterSeq = + [ + %% *** Init part *** + #{desc => "monitor 'primary acceptor'", + cmd => fun(#{prim_acc := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + #{desc => "monitor 'secondary acceptor 1'", + cmd => fun(#{sec_acc1 := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + #{desc => "monitor secondary acceptor 2", + cmd => fun(#{sec_acc2 := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + #{desc => "monitor secondary acceptor 3", + cmd => fun(#{sec_acc3 := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + + %% Start the primary server + #{desc => "order 'primary acceptor' start", + cmd => fun(#{prim_acc := Pid} = _State) -> + Pid ! {start, self()}, + ok + end}, + #{desc => "await 'primary acceptor' ready (init)", + cmd => fun(#{prim_acc := Pid} = State) -> + receive + {'DOWN', _, process, Pid, Reason} -> + ee("Unexpected DOWN regarding prim-acc ~p: " + "~n ~p", [Pid, Reason]), + {error, {unexpected_exit, prim_acc}}; + {ready, Pid, Sock} -> + {ok, State#{sock => Sock}} + end + end}, + + %% Start the secondary acceptor 1 + #{desc => "order 'secondary acceptor 1' start", + cmd => fun(#{sec_acc1 := Pid, sock := Sock} = _State) -> + Pid ! {start, self(), Sock}, + ok + end}, + #{desc => "await 'secondary acceptor 1' ready (init)", + cmd => fun(#{sec_acc1 := Pid} = _State) -> + receive + {'DOWN', _, process, Pid, Reason} -> + ee("Unexpected DOWN regarding sec-acc-1 ~p: " + "~n ~p", [Pid, Reason]), + {error, {unexpected_exit, sec_acc1}}; + {ready, Pid} -> + ok + end + end}, + + %% Start the secondary acceptor 2 + #{desc => "order 'secondary acceptor 2' start", + cmd => fun(#{sec_acc2 := Pid, sock := Sock} = _State) -> + Pid ! {start, self(), Sock}, + ok + end}, + #{desc => "await 'secondary acceptor 2' ready (init)", + cmd => fun(#{sec_acc2 := Pid} = _State) -> + receive + {'DOWN', _, process, Pid, Reason} -> + ee("Unexpected DOWN regarding sec-acc-2 ~p: " + "~n ~p", [Pid, Reason]), + {error, {unexpected_exit, sec_acc2}}; + {ready, Pid} -> + ok + end + end}, + + %% Start the secondary acceptor 3 + #{desc => "order 'secondary acceptor 3' start", + cmd => fun(#{sec_acc3 := Pid, sock := Sock} = _State) -> + Pid ! {start, self(), Sock}, + ok + end}, + #{desc => "await 'secondary acceptor 3' ready (init)", + cmd => fun(#{sec_acc3 := Pid} = _State) -> + receive + {'DOWN', _, process, Pid, Reason} -> + ee("Unexpected DOWN regarding sec-acc-3 ~p: " + "~n ~p", [Pid, Reason]), + {error, {unexpected_exit, sec_acc3}}; + {ready, Pid} -> + ok + end + end}, + + + %% The actual test + %% Make all the seondary servers continue, with an infinit recvfrom + %% and then the prim-server with a timed recvfrom. + %% After the prim server notifies us (about the timeout) we order it + %% to close the socket, which should cause the all the secondary + %% server to return with error-closed. + + #{desc => "order 'secondary acceptor 1' to continue", + cmd => fun(#{sec_acc1 := Pid} = _State) -> + Pid ! {continue, self()}, + ok + end}, + #{desc => "sleep", + cmd => fun(_) -> + ?SLEEP(?SECS(1)), + ok + end}, + #{desc => "order 'secondary acceptor 2' to continue", + cmd => fun(#{sec_acc2 := Pid} = _State) -> + Pid ! {continue, self()}, + ok + end}, + #{desc => "sleep", + cmd => fun(_) -> + ?SLEEP(?SECS(1)), + ok + end}, + #{desc => "order 'secondary acceptor 3' to continue", + cmd => fun(#{sec_acc3 := Pid} = _State) -> + Pid ! {continue, self()}, + ok + end}, + #{desc => "sleep", + cmd => fun(_) -> + ?SLEEP(?SECS(1)), + ok + end}, + #{desc => "order 'primary acceptor' to continue", + cmd => fun(#{prim_acc := Pid} = _State) -> + Pid ! {continue, self(), ?SECS(5)}, + ok + end}, + #{desc => "await 'primary acceptor' ready (timeout)", + cmd => fun(#{prim_acc := Pid} = _State) -> + receive + {'DOWN', _, process, Pid, Reason} -> + ee("Unexpected DOWN regarding prim-acc ~p: " + "~n ~p", [Pid, Reason]), + {error, {unexpected_exit, prim_acc}}; + {ready, Pid} -> + ok + end + end}, + #{desc => "order 'primary acceptor' to continue (close)", + cmd => fun(#{prim_acc := Pid} = _State) -> + Pid ! {continue, self()}, + ok + end}, + #{desc => "await 'primary acceptor' ready (closed)", + cmd => fun(#{prim_acc := Pid} = _State) -> + receive + {'DOWN', _, process, Pid, Reason} -> + ee("Unexpected DOWN regarding prim-acc ~p: " + "~n ~p", [Pid, Reason]), + {error, {unexpected_exit, prim_acc}}; + {ready, Pid} -> + ok + end + end}, + #{desc => "await 'secondary acceptor 1' ready (closed)", + cmd => fun(#{sec_acc1 := Pid} = _State) -> + receive + {'DOWN', _, process, Pid, Reason} -> + ee("Unexpected DOWN regarding sec-acc-1 ~p: " + "~n ~p", [Pid, Reason]), + {error, {unexpected_exit, sec_acc1}}; + {ready, Pid} -> + ok + end + end}, + #{desc => "await 'secondary acceptor 2' ready (closed)", + cmd => fun(#{sec_acc2 := Pid} = _State) -> + receive + {'DOWN', _, process, Pid, Reason} -> + ee("Unexpected DOWN regarding sec-acc-2 ~p: " + "~n ~p", [Pid, Reason]), + {error, {unexpected_exit, sec_acc2}}; + {ready, Pid} -> + ok + end + end}, + #{desc => "await 'secondary acceptor 3' ready (closed)", + cmd => fun(#{sec_acc3 := Pid} = _State) -> + receive + {'DOWN', _, process, Pid, Reason} -> + ee("Unexpected DOWN regarding sec-acc-3 ~p: " + "~n ~p", [Pid, Reason]), + {error, {unexpected_exit, sec_acc3}}; + {ready, Pid} -> + ok + end + end}, + + + %% Terminations + #{desc => "order 'secondary acceptor 3' to terminate", + cmd => fun(#{sec_acc3 := Pid} = _State) -> + Pid ! {terminate, self()}, + ok + end}, + #{desc => "await 'secondary acceptor 3' termination", + cmd => fun(#{sec_acc3 := Pid} = State) -> + receive + {'DOWN', _, process, Pid, _} -> + {ok, maps:remove(sec_acc3, State)} + end + end}, + #{desc => "order 'secondary acceptor 2' to terminate", + cmd => fun(#{sec_acc2 := Pid} = _State) -> + Pid ! {terminate, self()}, + ok + end}, + #{desc => "await 'secondary acceptor 2' termination", + cmd => fun(#{sec_acc2 := Pid} = State) -> + receive + {'DOWN', _, process, Pid, _} -> + {ok, maps:remove(sec_acc2, State)} + end + end}, + #{desc => "order 'secondary acceptor 1' to terminate", + cmd => fun(#{sec_acc1 := Pid} = _State) -> + Pid ! {terminate, self()}, + ok + end}, + #{desc => "await 'secondary acceptor 1' termination", + cmd => fun(#{sec_acc1 := Pid} = State) -> + receive + {'DOWN', _, process, Pid, _} -> + {ok, maps:remove(sec_acc1, State)} + end + end}, + #{desc => "order 'primary acceptor' to terminate", + cmd => fun(#{prim_acc := Pid} = _State) -> + Pid ! {terminate, self()}, + ok + end}, + #{desc => "await 'primary acceptor' termination", + cmd => fun(#{prim_acc := Pid} = State) -> + receive + {'DOWN', _, process, Pid, _} -> + {ok, maps:remove(prim_acc, State)} + end + end}, + + + %% *** We are done *** + #{desc => "finish", + cmd => fun(_) -> + {ok, normal} + end} + ], + + + i("start 'primary acceptor' evaluator"), + PrimAccInitState = InitState, + PrimAcc = evaluator_start("prim-acceptor", PrimAcceptorSeq, PrimAccInitState), + + i("start 'secondary acceptor 1' evaluator"), + SecAccInitState = #{}, + SecAcc1 = evaluator_start("sec-acceptor-1", SecAcceptorSeq, SecAccInitState), + + i("start 'secondary acceptor 2' evaluator"), + SecAcc2 = evaluator_start("sec-acceptor-2", SecAcceptorSeq, SecAccInitState), + + i("start 'secondary acceptor 3' evaluator"), + SecAcc3 = evaluator_start("sec-acceptor-3", SecAcceptorSeq, SecAccInitState), + + i("start 'tester' evaluator"), + TesterInitState = #{prim_acc => PrimAcc#ev.pid, + sec_acc1 => SecAcc1#ev.pid, + sec_acc2 => SecAcc2#ev.pid, + sec_acc3 => SecAcc3#ev.pid}, + Tester = evaluator_start("tester", TesterSeq, TesterInitState), + + i("await evaluator"), + ok = await_evaluator_finish([PrimAcc, SecAcc1, SecAcc2, SecAcc3, Tester]). -- cgit v1.2.3 From 8956077d3d7b1cfb892998ce9be850c191d30159 Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Tue, 23 Oct 2018 17:16:09 +0200 Subject: [socket-nif|test] Add two "remote close" test cases Added two remote close socket closure test cases (for IPv4 TCP): sc_rc_recv_response_tcp4 and sc_rc_recvmsg_response_tcp4. OTP-14831 --- erts/emulator/test/socket_SUITE.erl | 1062 +++++++++++++++++++++++++++++------ 1 file changed, 882 insertions(+), 180 deletions(-) diff --git a/erts/emulator/test/socket_SUITE.erl b/erts/emulator/test/socket_SUITE.erl index 692829d220..92f13b77e7 100644 --- a/erts/emulator/test/socket_SUITE.erl +++ b/erts/emulator/test/socket_SUITE.erl @@ -86,6 +86,10 @@ sc_rc_recvmsg_response_tcp4/1, sc_rc_recvmsg_response_tcp6/1 + %% Traffic + + + %% Tickets ]). @@ -244,6 +248,10 @@ sc_rc_cases() -> ]. +%% traffic_cases() -> +%% []. + + %% ticket_cases() -> %% []. @@ -1378,7 +1386,7 @@ api_to_connect_tcp(InitState) -> receive {'DOWN', _, process, Tester, Reason} -> ee("Unexpected DOWN regarding tester ~p: " - "~n ~p", [Reason]), + "~n ~p", [Tester, Reason]), {error, {unexpected_exit, tester}}; {terminate, Tester} -> {ok, maps:remove(tester, State)} @@ -1779,7 +1787,7 @@ api_to_maccept_tcp(InitState) -> receive {'DOWN', _, process, Tester, Reason} -> ee("Unexpected DOWN regarding tester ~p: " - "~n ~p", [Reason]), + "~n ~p", [Tester, Reason]), {error, {unexpected_exit, tester}}; {continue, Tester} -> ok @@ -1821,7 +1829,7 @@ api_to_maccept_tcp(InitState) -> receive {'DOWN', _, process, Tester, Reason} -> ee("Unexpected DOWN regarding tester ~p: " - "~n ~p", [Reason]), + "~n ~p", [Tester, Reason]), {error, {unexpected_exit, tester}}; {terminate, Tester} -> ok @@ -1866,7 +1874,7 @@ api_to_maccept_tcp(InitState) -> receive {'DOWN', _, process, Tester, Reason} -> ee("Unexpected DOWN regarding tester ~p: " - "~n ~p", [Reason]), + "~n ~p", [Tester, Reason]), {error, {unexpected_exit, tester, Reason}}; {continue, Tester} -> ok @@ -1907,7 +1915,7 @@ api_to_maccept_tcp(InitState) -> receive {'DOWN', _, process, Tester, Reason} -> ee("Unexpected DOWN regarding tester ~p: " - "~n ~p", [Reason]), + "~n ~p", [Tester, Reason]), {error, {unexpected_exit, tester, Reason}}; {terminate, Tester} -> ok @@ -1953,7 +1961,7 @@ api_to_maccept_tcp(InitState) -> receive {'DOWN', _, process, Pid, Reason} -> ee("Unexpected DOWN regarding prim-acceptor ~p:" - "~n ~p", [Reason]), + "~n ~p", [Pid, Reason]), {error, {unexpected_exit, prim_acceptor}}; {ready, Pid, LSock} -> {ok, State#{lsock => LSock}} @@ -1971,7 +1979,7 @@ api_to_maccept_tcp(InitState) -> receive {'DOWN', _, process, Pid, Reason} -> ee("Unexpected DOWN regarding sec-acceptor 1 ~p:" - "~n ~p", [Reason]), + "~n ~p", [Pid, Reason]), {error, {unexpected_exit, sec_acceptor_1}}; {ready, Pid} -> ok @@ -1989,7 +1997,7 @@ api_to_maccept_tcp(InitState) -> receive {'DOWN', _, process, Pid, Reason} -> ee("Unexpected DOWN regarding sec-acceptor 2 ~p:" - "~n ~p", [Reason]), + "~n ~p", [Pid, Reason]), {error, {unexpected_exit, sec_acceptor_2}}; {ready, Pid} -> ok @@ -2019,7 +2027,7 @@ api_to_maccept_tcp(InitState) -> receive {'DOWN', _, process, Pid, Reason} -> ee("Unexpected DOWN regarding prim-acceptor ~p:" - "~n ~p", [Reason]), + "~n ~p", [Pid, Reason]), {error, {unexpected_exit, prim_acceptor}}; {ready, Pid} -> ok @@ -2030,7 +2038,7 @@ api_to_maccept_tcp(InitState) -> receive {'DOWN', _, process, Pid, Reason} -> ee("Unexpected DOWN regarding sec-acceptor 1 ~p:" - "~n ~p", [Reason]), + "~n ~p", [Pid, Reason]), {error, {unexpected_exit, sec_acceptor_1}}; {ready, Pid} -> ok @@ -2041,7 +2049,7 @@ api_to_maccept_tcp(InitState) -> receive {'DOWN', _, process, Pid, Reason} -> ee("Unexpected DOWN regarding sec-acceptor 2 ~p:" - "~n ~p", [Reason]), + "~n ~p", [Pid, Reason]), {error, {unexpected_exit, sec_acceptor_2}}; {ready, Pid} -> ok @@ -3022,7 +3030,7 @@ sc_cpe_socket_cleanup(InitState) -> receive {'DOWN', _, process, Tester, Reason} -> ee("Unexpected DOWN regarding tester ~p: " - "~n ~p", [Reason]), + "~n ~p", [Tester, Reason]), {error, {unexpected_exit, tester}}; {terminate, Tester} -> {ok, maps:remove(tester, State)} @@ -3058,7 +3066,7 @@ sc_cpe_socket_cleanup(InitState) -> receive {'DOWN', _, process, Owner, Reason} -> ee("Unexpected DOWN regarding owner ~p: " - "~n ~p", [Reason]), + "~n ~p", [Owner, Reason]), {error, {unexpected_exit, owner}}; {ready, Owner, Sock} -> {ok, State#{sock => Sock}} @@ -3236,7 +3244,7 @@ sc_lc_receive_response_tcp(InitState) -> receive {'DOWN', _, process, Tester, Reason} -> ee("Unexpected DOWN regarding tester ~p: " - "~n ~p", [Reason]), + "~n ~p", [Tester, Reason]), {error, {unexpected_exit, tester}}; {continue, Tester, {H1, H2, H3}} -> {ok, State#{handler1 => H1, @@ -3288,7 +3296,7 @@ sc_lc_receive_response_tcp(InitState) -> receive {'DOWN', _, process, Tester, Reason} -> ee("Unexpected DOWN regarding tester ~p: " - "~n ~p", [Reason]), + "~n ~p", [Tester, Reason]), {error, {unexpected_exit, tester}}; {continue, Tester} -> ok @@ -3304,7 +3312,7 @@ sc_lc_receive_response_tcp(InitState) -> receive {'DOWN', _, process, Tester, Reason} -> ee("Unexpected DOWN regarding tester ~p: " - "~n ~p", [Reason]), + "~n ~p", [Tester, Reason]), {error, {unexpected_exit, tester}}; {terminate, Tester} -> {ok, maps:remove(tester, State)} @@ -3354,7 +3362,7 @@ sc_lc_receive_response_tcp(InitState) -> receive {'DOWN', _, process, Tester, Reason} -> ee("Unexpected DOWN regarding tester ~p: " - "~n ~p", [Reason]), + "~n ~p", [Tester, Reason]), {error, {unexpected_exit, tester}}; {connection, Sock} -> {ok, State#{sock => Sock}} @@ -3391,7 +3399,7 @@ sc_lc_receive_response_tcp(InitState) -> receive {'DOWN', _, process, Tester, Reason} -> ee("Unexpected DOWN regarding tester ~p: " - "~n ~p", [Reason]), + "~n ~p", [Tester, Reason]), {error, {unexpected_exit, tester}}; {terminate, Tester} -> ok @@ -3659,7 +3667,7 @@ sc_lc_receive_response_tcp(InitState) -> receive {'DOWN', _, process, Pid, Reason} -> ee("Unexpected DOWN regarding handler 1 ~p: " - "~n ~p", [Reason]), + "~n ~p", [Pid, Reason]), {error, {unexpected_exit, handler1}}; {ready, Pid} -> ok @@ -3670,7 +3678,7 @@ sc_lc_receive_response_tcp(InitState) -> receive {'DOWN', _, process, Pid, Reason} -> ee("Unexpected DOWN regarding handler 2 ~p: " - "~n ~p", [Reason]), + "~n ~p", [Pid, Reason]), {error, {unexpected_exit, handler2}}; {ready, Pid} -> ok @@ -3681,7 +3689,7 @@ sc_lc_receive_response_tcp(InitState) -> receive {'DOWN', _, process, Pid, Reason} -> ee("Unexpected DOWN regarding handler 3 ~p: " - "~n ~p", [Reason]), + "~n ~p", [Pid, Reason]), {error, {unexpected_exit, handler3}}; {ready, Pid} -> ok @@ -3925,7 +3933,7 @@ sc_lc_receive_response_udp(InitState) -> receive {'DOWN', _, process, Tester, Reason} -> ee("Unexpected DOWN regarding tester ~p: " - "~n ~p", [Reason]), + "~n ~p", [Tester, Reason]), {error, {unexpected_exit, tester}}; {continue, Tester, Timeout} -> {ok, State#{timeout => Timeout}} @@ -3952,7 +3960,7 @@ sc_lc_receive_response_udp(InitState) -> receive {'DOWN', _, process, Tester, Reason} -> ee("Unexpected DOWN regarding tester ~p: " - "~n ~p", [Reason]), + "~n ~p", [Tester, Reason]), {error, {unexpected_exit, tester}}; {continue, Tester} -> ok @@ -3979,7 +3987,7 @@ sc_lc_receive_response_udp(InitState) -> receive {'DOWN', _, process, Tester, Reason} -> ee("Unexpected DOWN regarding tester ~p: " - "~n ~p", [Reason]), + "~n ~p", [Tester, Reason]), {error, {unexpected_exit, tester}}; {terminate, Tester} -> {ok, maps:remove(tester, State)} @@ -4020,7 +4028,7 @@ sc_lc_receive_response_udp(InitState) -> receive {'DOWN', _, process, Tester, Reason} -> ee("Unexpected DOWN regarding tester ~p: " - "~n ~p", [Reason]), + "~n ~p", [Tester, Reason]), {error, {unexpected_exit, tester}}; {continue, Tester} -> ok @@ -4050,7 +4058,7 @@ sc_lc_receive_response_udp(InitState) -> receive {'DOWN', _, process, Tester, Reason} -> ee("Unexpected DOWN regarding tester ~p: " - "~n ~p", [Reason]), + "~n ~p", [Tester, Reason]), {error, {unexpected_exit, tester}}; {terminate, Tester} -> {ok, maps:remove(tester, State)} @@ -4353,59 +4361,6 @@ sc_lc_receive_response_udp(InitState) -> -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% This test case is intended to test what happens when a socket is -%% remotely closed while the process is calling the recv function. -%% Socket is IPv4. - -sc_rc_recv_response_tcp4(suite) -> - []; -sc_rc_recv_response_tcp4(doc) -> - []; -sc_rc_recv_response_tcp4(_Config) when is_list(_Config) -> - tc_try(sc_rc_recv_response_tcp4, - fun() -> - not_yet_implemented(), - ?TT(?SECS(10)), - Recv = fun(Sock) -> socket:recv(Sock) end, - InitState = #{domain => inet, - type => stream, - protocol => tcp, - recv => Recv}, - ok = sc_rc_receive_response_tcp(InitState) - end). - - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% This test case is intended to test what happens when a socket is -%% remotely closed while the process is calling the recv function. -%% Socket is IPv6. - -sc_rc_recv_response_tcp6(suite) -> - []; -sc_rc_recv_response_tcp6(doc) -> - []; -sc_rc_recv_response_tcp6(_Config) when is_list(_Config) -> - tc_try(sc_rc_recv_response_tcp6, - fun() -> - not_yet_implemented(), - ?TT(?SECS(10)), - Recv = fun(Sock) -> socket:recv(Sock) end, - InitState = #{domain => inet6, - type => stream, - protocol => tcp, - recv => Recv}, - ok = sc_rc_receive_response_tcp(InitState) - end). - - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -sc_rc_receive_response_tcp(_InitState) -> - ok. - - - %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% This test case is intended to test what happens when a socket is %% locally closed while the process is calling the recvmsg function. @@ -4494,19 +4449,23 @@ sc_lc_recvmsg_response_udp6(_Config) when is_list(_Config) -> %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% This test case is intended to test what happens when a socket is -%% remotely closed while the process is calling the recvmsg function. +%% remotely closed while the process is calling the recv function. %% Socket is IPv4. +%% +%% To minimize the chance of "weirdness", we should really have test cases +%% where the two sides of the connection is on different machines. But for +%% now, we will make do with different VMs on the same host. +%% -sc_rc_recvmsg_response_tcp4(suite) -> +sc_rc_recv_response_tcp4(suite) -> []; -sc_rc_recvmsg_response_tcp4(doc) -> +sc_rc_recv_response_tcp4(doc) -> []; -sc_rc_recvmsg_response_tcp4(_Config) when is_list(_Config) -> - tc_try(sc_rc_recvmsg_response_tcp4, +sc_rc_recv_response_tcp4(_Config) when is_list(_Config) -> + tc_try(sc_rc_recv_response_tcp4, fun() -> - not_yet_implemented(), - ?TT(?SECS(10)), - Recv = fun(Sock) -> socket:recvmsg(Sock) end, + ?TT(?SECS(30)), + Recv = fun(Sock) -> socket:recv(Sock) end, InitState = #{domain => inet, type => stream, protocol => tcp, @@ -4517,19 +4476,19 @@ sc_rc_recvmsg_response_tcp4(_Config) when is_list(_Config) -> %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% This test case is intended to test what happens when a socket is -%% remotely closed while the process is calling the recvmsg function. +%% remotely closed while the process is calling the recv function. %% Socket is IPv6. -sc_rc_recvmsg_response_tcp6(suite) -> +sc_rc_recv_response_tcp6(suite) -> []; -sc_rc_recvmsg_response_tcp6(doc) -> +sc_rc_recv_response_tcp6(doc) -> []; -sc_rc_recvmsg_response_tcp6(_Config) when is_list(_Config) -> - tc_try(sc_rc_recvmsg_response_tcp6, +sc_rc_recv_response_tcp6(_Config) when is_list(_Config) -> + tc_try(sc_rc_recv_response_tcp6, fun() -> not_yet_implemented(), ?TT(?SECS(10)), - Recv = fun(Sock) -> socket:recvmsg(Sock) end, + Recv = fun(Sock) -> socket:recv(Sock) end, InitState = #{domain => inet6, type => stream, protocol => tcp, @@ -4539,60 +4498,15 @@ sc_rc_recvmsg_response_tcp6(_Config) when is_list(_Config) -> %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% This test case is intended to test what happens when a socket is -%% locally closed while the process is calling the accept function. -%% We test what happens with a non-controlling_process also, since we -%% git the setup anyway. -%% Socket is IPv4. - -sc_lc_acceptor_response_tcp4(suite) -> - []; -sc_lc_acceptor_response_tcp4(doc) -> - []; -sc_lc_acceptor_response_tcp4(_Config) when is_list(_Config) -> - tc_try(sc_lc_acceptor_response_tcp4, - fun() -> - ?TT(?SECS(10)), - InitState = #{domain => inet, - type => stream, - protocol => tcp}, - ok = sc_lc_acceptor_response_tcp(InitState) - end). - - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% This test case is intended to test what happens when a socket is -%% locally closed while the process is calling the accept function. -%% We test what happens with a non-controlling_process also, since we -%% git the setup anyway. -%% Socket is IPv6. - -sc_lc_acceptor_response_tcp6(suite) -> - []; -sc_lc_acceptor_response_tcp6(doc) -> - []; -sc_lc_acceptor_response_tcp6(_Config) when is_list(_Config) -> - tc_try(sc_lc_acceptor_response_tcp6, - fun() -> - not_yet_implemented(), - ?TT(?SECS(10)), - InitState = #{domain => inet, - type => stream, - protocol => tcp}, - ok = sc_lc_acceptor_response_tcp(InitState) - end). - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -sc_lc_acceptor_response_tcp(InitState) -> - PrimAcceptorSeq = +sc_rc_receive_response_tcp(InitState) -> + ServerSeq = [ - %% *** Wait for start order part *** - #{desc => "await start (from tester)", + %% *** Init part *** + #{desc => "await start", cmd => fun(State) -> receive - {start, Tester} when is_pid(Tester) -> + {start, Tester} -> {ok, State#{tester => Tester}} end end}, @@ -4601,80 +4515,868 @@ sc_lc_acceptor_response_tcp(InitState) -> _MRef = erlang:monitor(process, Tester), ok end}, - - %% *** Init part *** #{desc => "which local address", cmd => fun(#{domain := Domain} = State) -> LAddr = which_local_addr(Domain), LSA = #{family => Domain, addr => LAddr}, {ok, State#{lsa => LSA}} end}, - #{desc => "create (listen) socket", - cmd => fun(#{domain := Domain, - type := Type, - protocol := Proto} = State) -> - case socket:open(Domain, Type, Proto) of + #{desc => "create listen socket", + cmd => fun(#{domain := Domain} = State) -> + case socket:open(Domain, stream, tcp) of {ok, Sock} -> - {ok, State#{sock => Sock}}; + {ok, State#{lsock => Sock}}; {error, _} = ERROR -> ERROR end end}, #{desc => "bind to local address", - cmd => fun(#{sock := Sock, lsa := LSA} = _State) -> - case socket:bind(Sock, LSA) of - {ok, _Port} -> - ok; + cmd => fun(#{lsock := LSock, lsa := LSA} = State) -> + case socket:bind(LSock, LSA) of + {ok, Port} -> + {ok, State#{lport => Port}}; {error, _} = ERROR -> ERROR end end}, #{desc => "make listen socket", - cmd => fun(#{sock := Sock}) -> - socket:listen(Sock) + cmd => fun(#{lsock := LSock}) -> + socket:listen(LSock) end}, #{desc => "announce ready (init)", - cmd => fun(#{tester := Tester, sock := Sock} = _State) -> - Tester ! {ready, self(), Sock}, + cmd => fun(#{tester := Tester, lsa := LSA, lport := Port}) -> + SA = LSA#{port => Port}, + Tester ! {ready, self(), SA}, ok end}, - + %% The actual test #{desc => "await continue (accept)", - cmd => fun(#{tester := Tester} = State) -> + cmd => fun(#{tester := Tester} = _State) -> receive + {continue, Tester} -> + ok; {'DOWN', _, process, Tester, Reason} -> ee("Unexpected DOWN regarding tester ~p: " - "~n ~p", [Reason]), - {error, {unexpected_exit, tester}}; - {continue, Tester, Timeout} -> - {ok, State#{timeout => Timeout}} + "~n ~p", [Tester, Reason]), + {error, {unexpected_exit, tester}} end end}, - #{desc => "await connection", - cmd => fun(#{sock := Sock, timeout := Timeout} = _State) -> - case socket:accept(Sock, Timeout) of - {error, timeout} -> - ok; + #{desc => "accept", + cmd => fun(#{lsock := LSock} = State) -> + case socket:accept(LSock) of {ok, Sock} -> - ee("unexpected success"), - (catch socket:close(Sock)), + {ok, State#{csock => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (accepted)", + cmd => fun(#{tester := Tester}) -> + Tester ! {ready, self()}, + ok + end}, + #{desc => "receive", + cmd => fun(#{csock := Sock, recv := Recv} = State) -> + case Recv(Sock) of + {error, closed} -> + {ok, maps:remove(csock, State)}; + {ok, _} -> {error, unexpected_success}; {error, _} = ERROR -> ERROR end end}, - #{desc => "announce ready (accept timeout)", + #{desc => "announce ready (closed)", cmd => fun(#{tester := Tester}) -> Tester ! {ready, self()}, ok end}, - #{desc => "await continue (close)", - cmd => fun(#{tester := Tester} = _State) -> - receive + + %% Termination + #{desc => "await terminate (from tester)", + cmd => fun(#{tester := Tester} = State) -> + receive + {'DOWN', _, process, Tester, Reason} -> + ee("Unexpected DOWN regarding tester ~p: " + "~n ~p", [Tester, Reason]), + {error, {unexpected_exit, tester}}; + {terminate, Tester} -> + {ok, maps:remove(tester, State)} + end + end}, + #{desc => "close listen socket", + cmd => fun(#{lsock := LSock} = _State) -> + socket:close(LSock) + end}, + + %% *** We are done *** + #{desc => "finish", + cmd => fun(_) -> + {ok, normal} + end} + ], + + ClientSeq = + [ + %% *** Init part *** + #{desc => "await start", + cmd => fun(State) -> + receive + {start, Tester, Node, ServerSA} -> + {ok, State#{tester => Tester, + node => Node, + server_sa => ServerSA}} + end + end}, + #{desc => "monitor tester", + cmd => fun(#{tester := Tester} = _State) -> + _MRef = erlang:monitor(process, Tester), + ok + end}, + #{desc => "start client process on client node", + cmd => fun(#{node := Node} = State) -> + Pid = sc_rc_tcp_client_start(Node), + ei("client ~p started", [Pid]), + {ok, State#{client => Pid}} + end}, + #{desc => "monitor client process", + cmd => fun(#{client := Pid}) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + #{desc => "order client process to start", + cmd => fun(#{client := Client, server_sa := ServerSA}) -> + Client ! {start, self(), ServerSA}, + ok + end}, + #{desc => "await client process ready", + cmd => fun(#{tester := Tester, + client := Client} = _State) -> + receive + {ready, Client} -> + ok; + {'DOWN', _, process, Tester, Reason} -> + ee("Unexpected DOWN regarding tester ~p: " + "~n ~p", [Tester, Reason]), + {error, {unexpected_exit, tester}}; + {'DOWN', _, process, Client, Reason} -> + ee("Unexpected DOWN regarding client ~p: " + "~n ~p", [Client, Reason]), + {error, {unexpected_exit, client}} + end + end}, + #{desc => "announce ready (init)", + cmd => fun(#{tester := Tester}) -> + Tester ! {ready, self()}, + ok + end}, + + %% The actual test + #{desc => "await continue (connect)", + cmd => fun(#{tester := Tester, + client := Client} = _State) -> + receive + {continue, Tester} -> + ok; + {'DOWN', _, process, Tester, Reason} -> + ee("Unexpected DOWN regarding tester ~p: " + "~n ~p", [Tester, Reason]), + {error, {unexpected_exit, tester}}; + {'DOWN', _, process, Client, Reason} -> + ee("Unexpected DOWN regarding client ~p: " + "~n ~p", [Client, Reason]), + {error, {unexpected_exit, client}} + end + end}, + #{desc => "order client process to continue (connect)", + cmd => fun(#{client := Client}) -> + Client ! {continue, self()}, + ok + end}, + #{desc => "await client process ready (connected)", + cmd => fun(#{tester := Tester, + client := Client} = _State) -> + receive + {ready, Client} -> + ok; + {'DOWN', _, process, Tester, Reason} -> + ee("Unexpected DOWN regarding tester ~p: " + "~n ~p", [Tester, Reason]), + {error, {unexpected_exit, tester}}; + {'DOWN', _, process, Client, Reason} -> + ee("Unexpected DOWN regarding client ~p: " + "~n ~p", [Client, Reason]), + {error, {unexpected_exit, client}} + end + end}, + #{desc => "announce ready (connected)", + cmd => fun(#{tester := Tester}) -> + Tester ! {ready, self()}, + ok + end}, + #{desc => "await continue (close)", + cmd => fun(#{tester := Tester, + client := Client} = _State) -> + receive + {continue, Tester} -> + ok; + {'DOWN', _, process, Tester, Reason} -> + ee("Unexpected DOWN regarding tester ~p: " + "~n ~p", [Tester, Reason]), + {error, {unexpected_exit, tester}}; + {'DOWN', _, process, Client, Reason} -> + ee("Unexpected DOWN regarding client ~p: " + "~n ~p", [Client, Reason]), + {error, {unexpected_exit, client}} + end + end}, + #{desc => "order client process to close", + cmd => fun(#{client := Client}) -> + Client ! {continue, self()}, + ok + end}, + #{desc => "await client process ready (closed)", + cmd => fun(#{tester := Tester, + client := Client} = _State) -> + receive + {ready, Client} -> + ok; + {'DOWN', _, process, Tester, Reason} -> + ee("Unexpected DOWN regarding tester ~p: " + "~n ~p", [Tester, Reason]), + {error, {unexpected_exit, tester}}; + {'DOWN', _, process, Client, Reason} -> + ee("Unexpected DOWN regarding client ~p: " + "~n ~p", [Client, Reason]), + {error, {unexpected_exit, client}} + end + end}, + #{desc => "announce ready (closed)", + cmd => fun(#{tester := Tester}) -> + Tester ! {ready, self()}, + ok + end}, + + %% Termination + #{desc => "await terminate (from tester)", + cmd => fun(#{tester := Tester, client := Client} = State) -> + receive + {'DOWN', _, process, Tester, Reason} -> + ee("Unexpected DOWN regarding tester ~p: " + "~n ~p", [Tester, Reason]), + {error, {unexpected_exit, tester}}; + {'DOWN', _, process, Client, Reason} -> + ee("Unexpected DOWN regarding client ~p: " + "~n ~p", [Client, Reason]), + {error, {unexpected_exit, client}}; + {terminate, Tester} -> + {ok, maps:remove(tester, State)} + end + end}, + #{desc => "kill client process", + cmd => fun(#{client := Client}) -> + Client ! {terminate, self(), normal}, + ok + end}, + #{desc => "await client termination", + cmd => fun(#{client := Client} = State) -> + receive + {'DOWN', _, process, Client, _} -> + {ok, maps:remove(client, State)} + end + end}, + + %% *** We are done *** + #{desc => "finish", + cmd => fun(_) -> + {ok, normal} + end} + ], + + TesterSeq = + [ + %% *** Init part *** + #{desc => "create client node", + cmd => fun(#{host := Host} = State) -> + case start_node(Host, client) of + {ok, Node} -> + ei("client node ~p started", [Node]), + {ok, State#{client_node => Node}}; + {error, Reason, _} -> + {error, Reason} + end + end}, + #{desc => "monitor client node", + cmd => fun(#{client_node := Node} = _State) -> + true = erlang:monitor_node(Node, true), + ok + end}, + #{desc => "monitor server", + cmd => fun(#{server := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + #{desc => "monitor client", + cmd => fun(#{client := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + + %% Start the server + #{desc => "order server start", + cmd => fun(#{server := Pid} = _State) -> + Pid ! {start, self()}, + ok + end}, + #{desc => "await server ready (init)", + cmd => fun(#{server := Pid} = State) -> + receive + {ready, Pid, ServerSA} -> + {ok, State#{server_sa => ServerSA}}; + {'DOWN', _, process, Pid, Reason} -> + ee("Unexpected DOWN regarding server ~p: " + "~n ~p", [Pid, Reason]), + {error, {unexpected_exit, server}} + end + end}, + + %% Start the client + #{desc => "order client start", + cmd => fun(#{client := Pid, + client_node := Node, + server_sa := ServerSA} = _State) -> + Pid ! {start, self(), Node, ServerSA}, + ok + end}, + #{desc => "await client ready (init)", + cmd => fun(#{client := Pid} = _State) -> + receive + {ready, Pid} -> + ok; + {'DOWN', _, process, Pid, Reason} -> + ee("Unexpected DOWN regarding client ~p: " + "~n ~p", [Pid, Reason]), + {error, {unexpected_exit, client}} + end + end}, + + %% The actual test + #{desc => "order server accept", + cmd => fun(#{server := Pid} = _State) -> + Pid ! {continue, self()}, + ok + end}, + #{desc => "sleep", + cmd => fun(_) -> + ?SLEEP(?SECS(1)), + ok + end}, + #{desc => "order client connect", + cmd => fun(#{client := Pid} = _State) -> + Pid ! {continue, self()}, + ok + end}, + #{desc => "await client ready (connected)", + cmd => fun(#{server := Server, + client := Client} = _State) -> + receive + {ready, Client} -> + ok; + {'DOWN', _, process, Client, Reason} -> + ee("Unexpected DOWN regarding client ~p: " + "~n ~p", [Client, Reason]), + {error, {unexpected_exit, client}}; + {'DOWN', _, process, Server, Reason} -> + ee("Unexpected DOWN regarding client ~p: " + "~n ~p", [Server, Reason]), + {error, {unexpected_exit, client}} + end + end}, + #{desc => "await server ready (accepted)", + cmd => fun(#{server := Server, + client := Client} = _State) -> + receive + {ready, Server} -> + ok; + {'DOWN', _, process, Client, Reason} -> + ee("Unexpected DOWN regarding client ~p: " + "~n ~p", [Client, Reason]), + {error, {unexpected_exit, client}}; + {'DOWN', _, process, Server, Reason} -> + ee("Unexpected DOWN regarding client ~p: " + "~n ~p", [Server, Reason]), + {error, {unexpected_exit, client}} + end + end}, + #{desc => "sleep", + cmd => fun(_) -> + ?SLEEP(?SECS(1)), + ok + end}, + #{desc => "order client close", + cmd => fun(#{client := Pid} = _State) -> + Pid ! {continue, self()}, + ok + end}, + #{desc => "await client ready (closed)", + cmd => fun(#{server := Server, + client := Client} = _State) -> + receive + {ready, Client} -> + ok; + {'DOWN', _, process, Client, Reason} -> + ee("Unexpected DOWN regarding client ~p: " + "~n ~p", [Client, Reason]), + {error, {unexpected_exit, client}}; + {'DOWN', _, process, Server, Reason} -> + ee("Unexpected DOWN regarding client ~p: " + "~n ~p", [Server, Reason]), + {error, {unexpected_exit, client}} + end + end}, + #{desc => "await server ready (closed)", + cmd => fun(#{server := Server, + client := Client} = _State) -> + receive + {ready, Server} -> + ok; + {'DOWN', _, process, Client, Reason} -> + ee("Unexpected DOWN regarding client ~p: " + "~n ~p", [Client, Reason]), + {error, {unexpected_exit, client}}; + {'DOWN', _, process, Server, Reason} -> + ee("Unexpected DOWN regarding client ~p: " + "~n ~p", [Server, Reason]), + {error, {unexpected_exit, client}} + end + end}, + + %% Terminations + #{desc => "order client to terminate", + cmd => fun(#{client := Pid} = _State) -> + Pid ! {terminate, self()}, + ok + end}, + #{desc => "await client termination", + cmd => fun(#{client := Pid} = State) -> + receive + {'DOWN', _, process, Pid, _} -> + {ok, maps:remove(client, State)} + end + end}, + #{desc => "order server to terminate", + cmd => fun(#{server := Pid} = _State) -> + Pid ! {terminate, self()}, + ok + end}, + #{desc => "await server termination", + cmd => fun(#{server := Pid} = State) -> + receive + {'DOWN', _, process, Pid, _} -> + {ok, maps:remove(server, State)} + end + end}, + #{desc => "stop client node", + cmd => fun(#{client_node := Node} = _State) -> + stop_node(Node) + end}, + #{desc => "await client node termination", + cmd => fun(#{client_node := Node} = State) -> + receive + {nodedown, Node} -> + {ok, maps:remove(client_node, State)} + end + end}, + + %% *** We are done *** + #{desc => "finish", + cmd => fun(_) -> + {ok, normal} + end} + ], + + i("start server evaluator"), + ServerInitState = InitState, + Server = evaluator_start("server", ServerSeq, ServerInitState), + + i("start client evaluator"), + ClientInitState = InitState, + Client = evaluator_start("client", ClientSeq, ClientInitState), + + i("start 'tester' evaluator"), + TesterInitState = #{host => local_host(), + server => Server#ev.pid, + client => Client#ev.pid}, + Tester = evaluator_start("tester", TesterSeq, TesterInitState), + + i("await evaluator"), + ok = await_evaluator_finish([Server, Client, Tester]). + + +start_node(Host, NodeName) -> + Dir = filename:dirname(code:which(?MODULE)), + Flags = "-pa " ++ Dir, + Opts = [{monitor_master, true}, {erl_flags, Flags}], + ct_slave:start(Host, NodeName, Opts). + +stop_node(Node) -> + case ct_slave:stop(Node) of + {ok, _} -> + ok; + {error, _} = ERROR -> + ERROR + end. + +local_host() -> + try net_adm:localhost() of + Host when is_list(Host) -> + list_to_atom(Host) + catch + C:E:S -> + erlang:raise(C, E, S) + end. + + +sc_rc_tcp_client_start(Node) -> + Self = self(), + GL = group_leader(), + Fun = fun() -> sc_rc_tcp_client(Self, GL) end, + erlang:spawn(Node, Fun). + + +sc_rc_tcp_client(Parent, GL) -> + sc_rc_tcp_client_init(Parent, GL), + ServerSA = sc_rc_tcp_client_await_start(Parent), + Domain = maps:get(family, ServerSA), + Sock = sc_rc_tcp_client_create(Domain), + sc_rc_tcp_client_bind(Sock, Domain), + sc_rc_tcp_client_announce_ready(Parent), + sc_rc_tcp_client_await_continue(Parent), + sc_rc_tcp_client_connect(Sock, ServerSA), + sc_rc_tcp_client_announce_ready(Parent), + sc_rc_tcp_client_await_continue(Parent), + sc_rc_tcp_client_close(Sock), + sc_rc_tcp_client_announce_ready(Parent), + Reason = sc_rc_tcp_client_await_terminate(Parent), + exit(Reason). + +sc_rc_tcp_client_init(Parent, GL) -> + i("sc_rc_tcp_client_init -> entry"), + _MRef = erlang:monitor(process, Parent), + group_leader(self(), GL), + ok. + +sc_rc_tcp_client_await_start(Parent) -> + i("sc_rc_tcp_client_await_start -> entry"), + receive + {start, Parent, ServerSA} -> + ServerSA; + {'DOWN', _, process, Parent, _Reason} -> + init:stop() + end. + +sc_rc_tcp_client_create(Domain) -> + i("sc_rc_tcp_client_create -> entry"), + case socket:open(Domain, stream, tcp) of + {ok, Sock} -> + Sock; + {error, Reason} -> + exit({open_failed, Reason}) + end. + +sc_rc_tcp_client_bind(Sock, Domain) -> + i("sc_rc_tcp_client_bind -> entry"), + LAddr = which_local_addr(Domain), + LSA = #{family => Domain, + addr => LAddr}, + case socket:bind(Sock, LSA) of + {ok, _} -> + ok; + {error, Reason} -> + exit({bind, Reason}) + end. + +sc_rc_tcp_client_announce_ready(Parent) -> + Parent ! {ready, self()}, + ok. + +sc_rc_tcp_client_await_continue(Parent) -> + i("sc_rc_tcp_client_await_continue -> entry"), + receive + {continue, Parent} -> + ok; + {'DOWN', _, process, Parent, _Reason} -> + init:stop() + end. + +sc_rc_tcp_client_connect(Sock, ServerSA) -> + i("sc_rc_tcp_client_connect -> entry"), + case socket:connect(Sock, ServerSA) of + ok -> + ok; + {error, Reason} -> + exit({connect, Reason}) + end. + +sc_rc_tcp_client_close(Sock) -> + i("sc_rc_tcp_client_close -> entry"), + case socket:close(Sock) of + ok -> + ok; + {error, Reason} -> + exit({close, Reason}) + end. + +sc_rc_tcp_client_await_terminate(Parent) -> + i("sc_rc_tcp_client_await_terminate -> entry"), + receive + {terminate, Parent, Reason} -> + Reason; + {'DOWN', _, process, Parent, _Reason} -> + init:stop() + end. + + +%% ev_continue(Pid, Slogan) -> +%% ev_announce(Pid, continue, Slogan). + +%% ev_continue(Pid, Slogan, Extra) -> +%% ev_announce(Pid, continue, Slogan, Extra). + +%% ev_ready(Pid, Slogan) -> +%% ev_announce(Pid, ready, Slogan). + +%% ev_ready(Pid, Slogan, Extra) -> +%% ev_announce(Pid, ready, Slogan, Extra). + +%% ev_terminate(Pid) -> +%% Pid ! {terminate, self()}. + +%% ev_announce(To, Tag, Slogan) -> +%% ev_announce(To, Tag, Slogan, undefined) + +%% ev_announce(To, Tag, Slogan, Extra) -> +%% To ! {Tag, self(), Slogan, Extra}. + +%% ev_await_continue(Pid, Slogan, Pids) when is_pid(Pid) andalso is_list(Pids) -> +%% ev_await(Pid, continue, Slogan, Pids). + +%% ev_await_ready(Pid, Slogan, Pids) when is_pid(Pid) andalso is_list(Pids) -> +%% ev_await(Pid, ready, Slogan, Pids). + +%% ev_await_termination(Pid) -> +%% receive +%% {'DOWN', _, process, Pid, _} -> +%% ok +%% end. + +%% %% We expect a message from Pid, but we also watch for DOWN from +%% %% both Pid and Pids, in which case the test has failed! +%% ev_await(Pid, Tag, Slogan, Pids) -> +%% receive +%% {Tag, Pid, Slogan, undefined} -> +%% ok; +%% {Tag, Pid, Slogan, Extra} -> +%% {ok, Extra}; +%% {'DOWN', _, process, Pid, Reason} -> +%% ee("Unexpected DOWN regarding tester ~p: " +%% "~n ~p", [Tester, Reason]), +%% {error, {unexpected_exit, tester}}; + +%% ev_await_check_down(DownPid, DownReason, Pids) -> +%% case lists:keymember(DownPid, 1, Pids) of +%% {value, {_, Name}} -> +%% ee("Unexpected DOWN regarding ~w ~p: " +%% "~n ~p", [Name, DownPid, Reason]), +%% throw({error, {unexpected_exit, Name}}); +%% false -> +%% ok +%% end. + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test what happens when a socket is +%% remotely closed while the process is calling the recvmsg function. +%% Socket is IPv4. + +sc_rc_recvmsg_response_tcp4(suite) -> + []; +sc_rc_recvmsg_response_tcp4(doc) -> + []; +sc_rc_recvmsg_response_tcp4(_Config) when is_list(_Config) -> + tc_try(sc_rc_recvmsg_response_tcp4, + fun() -> + ?TT(?SECS(30)), + Recv = fun(Sock) -> socket:recvmsg(Sock) end, + InitState = #{domain => inet, + type => stream, + protocol => tcp, + recv => Recv}, + ok = sc_rc_receive_response_tcp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test what happens when a socket is +%% remotely closed while the process is calling the recvmsg function. +%% Socket is IPv6. + +sc_rc_recvmsg_response_tcp6(suite) -> + []; +sc_rc_recvmsg_response_tcp6(doc) -> + []; +sc_rc_recvmsg_response_tcp6(_Config) when is_list(_Config) -> + tc_try(sc_rc_recvmsg_response_tcp6, + fun() -> + not_yet_implemented(), + ?TT(?SECS(10)), + Recv = fun(Sock) -> socket:recvmsg(Sock) end, + InitState = #{domain => inet6, + type => stream, + protocol => tcp, + recv => Recv}, + ok = sc_rc_receive_response_tcp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test what happens when a socket is +%% locally closed while the process is calling the accept function. +%% We test what happens with a non-controlling_process also, since we +%% git the setup anyway. +%% Socket is IPv4. + +sc_lc_acceptor_response_tcp4(suite) -> + []; +sc_lc_acceptor_response_tcp4(doc) -> + []; +sc_lc_acceptor_response_tcp4(_Config) when is_list(_Config) -> + tc_try(sc_lc_acceptor_response_tcp4, + fun() -> + ?TT(?SECS(10)), + InitState = #{domain => inet, + type => stream, + protocol => tcp}, + ok = sc_lc_acceptor_response_tcp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test what happens when a socket is +%% locally closed while the process is calling the accept function. +%% We test what happens with a non-controlling_process also, since we +%% git the setup anyway. +%% Socket is IPv6. + +sc_lc_acceptor_response_tcp6(suite) -> + []; +sc_lc_acceptor_response_tcp6(doc) -> + []; +sc_lc_acceptor_response_tcp6(_Config) when is_list(_Config) -> + tc_try(sc_lc_acceptor_response_tcp6, + fun() -> + not_yet_implemented(), + ?TT(?SECS(10)), + InitState = #{domain => inet, + type => stream, + protocol => tcp}, + ok = sc_lc_acceptor_response_tcp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +sc_lc_acceptor_response_tcp(InitState) -> + PrimAcceptorSeq = + [ + %% *** Wait for start order part *** + #{desc => "await start (from tester)", + cmd => fun(State) -> + receive + {start, Tester} when is_pid(Tester) -> + {ok, State#{tester => Tester}} + end + end}, + #{desc => "monitor tester", + cmd => fun(#{tester := Tester} = _State) -> + _MRef = erlang:monitor(process, Tester), + ok + end}, + + %% *** Init part *** + #{desc => "which local address", + cmd => fun(#{domain := Domain} = State) -> + LAddr = which_local_addr(Domain), + LSA = #{family => Domain, addr => LAddr}, + {ok, State#{lsa => LSA}} + end}, + #{desc => "create (listen) socket", + cmd => fun(#{domain := Domain, + type := Type, + protocol := Proto} = State) -> + case socket:open(Domain, Type, Proto) of + {ok, Sock} -> + {ok, State#{sock => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "bind to local address", + cmd => fun(#{sock := Sock, lsa := LSA} = _State) -> + case socket:bind(Sock, LSA) of + {ok, _Port} -> + ok; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "make listen socket", + cmd => fun(#{sock := Sock}) -> + socket:listen(Sock) + end}, + #{desc => "announce ready (init)", + cmd => fun(#{tester := Tester, sock := Sock} = _State) -> + Tester ! {ready, self(), Sock}, + ok + end}, + + %% The actual test + #{desc => "await continue (accept)", + cmd => fun(#{tester := Tester} = State) -> + receive {'DOWN', _, process, Tester, Reason} -> ee("Unexpected DOWN regarding tester ~p: " - "~n ~p", [Reason]), + "~n ~p", [Tester, Reason]), + {error, {unexpected_exit, tester}}; + {continue, Tester, Timeout} -> + {ok, State#{timeout => Timeout}} + end + end}, + #{desc => "await connection", + cmd => fun(#{sock := Sock, timeout := Timeout} = _State) -> + case socket:accept(Sock, Timeout) of + {error, timeout} -> + ok; + {ok, Sock} -> + ee("unexpected success"), + (catch socket:close(Sock)), + {error, unexpected_success}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (accept timeout)", + cmd => fun(#{tester := Tester}) -> + Tester ! {ready, self()}, + ok + end}, + #{desc => "await continue (close)", + cmd => fun(#{tester := Tester} = _State) -> + receive + {'DOWN', _, process, Tester, Reason} -> + ee("Unexpected DOWN regarding tester ~p: " + "~n ~p", [Tester, Reason]), {error, {unexpected_exit, tester}}; {continue, Tester} -> ok @@ -4701,7 +5403,7 @@ sc_lc_acceptor_response_tcp(InitState) -> receive {'DOWN', _, process, Tester, Reason} -> ee("Unexpected DOWN regarding tester ~p: " - "~n ~p", [Reason]), + "~n ~p", [Tester, Reason]), {error, {unexpected_exit, tester}}; {terminate, Tester} -> {ok, maps:remove(tester, State)} @@ -4742,7 +5444,7 @@ sc_lc_acceptor_response_tcp(InitState) -> receive {'DOWN', _, process, Tester, Reason} -> ee("Unexpected DOWN regarding tester ~p: " - "~n ~p", [Reason]), + "~n ~p", [Tester, Reason]), {error, {unexpected_exit, tester}}; {continue, Tester} -> ok @@ -4772,7 +5474,7 @@ sc_lc_acceptor_response_tcp(InitState) -> receive {'DOWN', _, process, Tester, Reason} -> ee("Unexpected DOWN regarding tester ~p: " - "~n ~p", [Reason]), + "~n ~p", [Tester, Reason]), {error, {unexpected_exit, tester}}; {terminate, Tester} -> {ok, maps:remove(tester, State)} -- cgit v1.2.3 From 9383e012d203e61805ef9263473f41dc908c867d Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Tue, 23 Oct 2018 18:37:33 +0200 Subject: [socket-nif|test] Evaluator interface functions Added simple evaluator interface functions, so that it is simple for evaluators to interact (and unified). Only two test cases so far... OTP-14831 --- erts/emulator/test/socket_SUITE.erl | 317 ++++++++++++++++++------------------ 1 file changed, 156 insertions(+), 161 deletions(-) diff --git a/erts/emulator/test/socket_SUITE.erl b/erts/emulator/test/socket_SUITE.erl index 92f13b77e7..29b20e620b 100644 --- a/erts/emulator/test/socket_SUITE.erl +++ b/erts/emulator/test/socket_SUITE.erl @@ -2327,10 +2327,8 @@ api_to_receive_tcp(InitState) -> %% *** Wait for start order *** #{desc => "await start (from tester)", cmd => fun(State) -> - receive - {start, Tester} when is_pid(Tester) -> - {ok, State#{tester => Tester}} - end + Tester = ev_await_start(), + {ok, State#{tester => Tester}} end}, %% *** Init part *** @@ -2369,25 +2367,20 @@ api_to_receive_tcp(InitState) -> end}, #{desc => "announce ready", cmd => fun(#{tester := Tester, lport := Port}) -> - Tester ! {ready, self(), Port}, + %% Tester ! {ready, self(), Port}, + ev_ready(Tester, init, Port), ok end}, - #{desc => "await continue", + #{desc => "await continue (accept and recv)", cmd => fun(#{tester := Tester}) -> - receive - {'DOWN', _, process, Tester, Reason} -> - {error, {unexpected_exit, tester, Reason}}; - {continue, Tester} -> - ok - end + ok = ev_await_continue(Tester, tester, accept_recv) end}, %% *** The actual test *** - #{desc => "await accept", + #{desc => "attempt accept", cmd => fun(#{lsock := LSock} = State) -> case socket:accept(LSock) of {ok, Sock} -> - %% ok = socket:setopt(Sock, otp, debug, true), {ok, State#{sock => Sock}}; {error, _} = ERROR -> ERROR @@ -2417,58 +2410,32 @@ api_to_receive_tcp(InitState) -> end}, #{desc => "announce ready (recv timeout success)", cmd => fun(#{tester := Tester} = _State) -> - Tester ! {ready, self()}, + %% Tester ! {ready, self()}, + %% ok + ev_ready(Tester, accept_recv), ok end}, %% *** Termination *** #{desc => "await terminate", cmd => fun(#{tester := Tester} = State) -> - receive - {'DOWN', _, process, Tester, Reason} -> - {error, {unexpected_exit, tester, Reason}}; - {terminate, Tester} -> - {ok, maps:remove(tester, State)} + case ev_await_terminate(Tester, tester) of + ok -> + {ok, maps:remove(tester, State)}; + {error, _} = ERROR -> + ERROR end end}, - %% #{desc => "sleep some (before traffic close)", - %% cmd => fun(_) -> - %% ?SLEEP(1000), - %% ok - %% end}, - %% #{desc => "monitored-by", - %% cmd => fun(_) -> - %% {_, Mons} = process_info(self(), monitored_by), - %% ei("Monitored By: ~p", [Mons]), - %% ok - %% end}, #{desc => "close (traffic) socket", cmd => fun(#{sock := Sock} = State) -> - %% ok = socket:setopt(Sock, otp, debug, true), sock_close(Sock), {ok, maps:remove(sock, State)} end}, - %% #{desc => "monitored-by", - %% cmd => fun(_) -> - %% {_, Mons} = process_info(self(), monitored_by), - %% ei("Monitored By: ~p", [Mons]), - %% ok - %% end}, - %% #{desc => "sleep some (before listen close)", - %% cmd => fun(_) -> - %% ?SLEEP(1000), - %% ok - %% end}, #{desc => "close (listen) socket", cmd => fun(#{lsock := LSock} = State) -> sock_close(LSock), {ok, maps:remove(lsock, State)} end}, - %% #{desc => "sleep some (after listen close)", - %% cmd => fun(_) -> - %% ?SLEEP(1000), - %% ok - %% end}, %% *** We are done *** #{desc => "finish", @@ -2482,11 +2449,9 @@ api_to_receive_tcp(InitState) -> %% *** Wait for start order part *** #{desc => "await start (from tester)", cmd => fun(State) -> - receive - {start, Tester, Port} when is_pid(Tester) -> - {ok, State#{tester => Tester, - server_port => Port}} - end + {Tester, Port} = ev_await_start(), + {ok, State#{tester => Tester, + server_port => Port}} end}, %% *** Init part *** @@ -2521,21 +2486,16 @@ api_to_receive_tcp(InitState) -> MRef = erlang:monitor(process, Tester), {ok, State#{tester_mref => MRef}} end}, - #{desc => "announce ready", + #{desc => "announce ready (init)", cmd => fun(#{tester := Tester} = _State) -> - Tester ! {ready, self()}, + ev_ready(Tester, init), ok end}, %% *** The actual test *** #{desc => "await continue (with connect)", cmd => fun(#{tester := Tester} = _State) -> - receive - {'DOWN', _, process, Tester, Reason} -> - {error, {unexpected_exit, tester, Reason}}; - {continue, Tester} -> - ok - end + ok = ev_await_continue(Tester, tester, connect) end}, #{desc => "connect", cmd => fun(#{sock := Sock, ssa := SSA}) -> @@ -2546,11 +2506,11 @@ api_to_receive_tcp(InitState) -> %% *** Termination *** #{desc => "await terminate", cmd => fun(#{tester := Tester} = State) -> - receive - {'DOWN', _, process, Tester, Reason} -> - {error, {unexpected_exit, tester, Reason}}; - {terminate, Tester} -> - {ok, maps:remove(tester, State)} + case ev_await_terminate(Tester, tester) of + ok -> + {ok, maps:remove(tester, State)}; + {error, _} = ERROR -> + ERROR end end}, #{desc => "close socket", @@ -2583,85 +2543,69 @@ api_to_receive_tcp(InitState) -> %% *** Activate server *** #{desc => "start server", cmd => fun(#{server := Server} = _State) -> - Server ! {start, self()}, + ev_start(Server), ok end}, #{desc => "await server ready (init)", cmd => fun(#{server := Server} = State) -> - receive - {'DOWN', _, process, Server, Reason} -> - {error, {unexpected_exit, server, Reason}}; - {ready, Server, Port} -> - {ok, State#{server_port => Port}} - end + {ok, Port} = ev_await_ready(Server, server, init), + {ok, State#{server_port => Port}} end}, #{desc => "order server to continue (with accept)", cmd => fun(#{server := Server} = _State) -> - Server ! {continue, self()}, + ev_continue(Server, accept_recv), ok end}, %% *** Activate client *** #{desc => "start client", cmd => fun(#{client := Client, server_port := Port} = _State) -> - Client ! {start, self(), Port}, + ev_start(Client, Port), ok end}, - #{desc => "await client ready", + #{desc => "await client ready (init)", cmd => fun(#{client := Client} = _State) -> - receive - {'DOWN', _, process, Client, Reason} -> - {error, {unexpected_exit, client, Reason}}; - {ready, Client} -> - ok - end + ev_await_ready(Client, client, init) end}, %% *** The actual test *** #{desc => "order client to continue (with connect)", cmd => fun(#{client := Client} = _State) -> - Client ! {continue, self()}, + ev_continue(Client, connect), ok end}, #{desc => "await server ready (accept/recv)", cmd => fun(#{server := Server} = _State) -> - receive - {'DOWN', _, process, Server, Reason} -> - {error, {unexpected_exit, server, Reason}}; - {ready, Server} -> - ok - end + ev_await_ready(Server, server, accept_recv) end}, %% *** Termination *** #{desc => "order client to terminate", cmd => fun(#{client := Client} = _State) -> - Client ! {terminate, self()}, + %% Client ! {terminate, self()}, + %% ok + ev_terminate(Client), ok end}, #{desc => "await client termination", cmd => fun(#{client := Client} = State) -> - receive - {'DOWN', _, process, Client, _Reason} -> - State1 = maps:remove(client, State), - State2 = maps:remove(client_mref, State1), - {ok, State2} - end + ev_await_termination(Client), + State1 = maps:remove(client, State), + State2 = maps:remove(client_mref, State1), + {ok, State2} end}, #{desc => "order server to terminate", cmd => fun(#{server := Server} = _State) -> - Server ! {terminate, self()}, + ev_terminate(Server), ok end}, #{desc => "await server termination", cmd => fun(#{server := Server} = State) -> - receive - {'DOWN', _, process, Server, _Reason} -> - State1 = maps:remove(server, State), - State2 = maps:remove(server_mref, State1), - State3 = maps:remove(server_port, State2), - {ok, State3} - end + ev_await_termination(Server), + State1 = maps:remove(server, State), + State2 = maps:remove(server_mref, State1), + State3 = maps:remove(server_port, State2), + {ok, State3} end}, %% *** We are done *** @@ -5138,63 +5082,6 @@ sc_rc_tcp_client_await_terminate(Parent) -> end. -%% ev_continue(Pid, Slogan) -> -%% ev_announce(Pid, continue, Slogan). - -%% ev_continue(Pid, Slogan, Extra) -> -%% ev_announce(Pid, continue, Slogan, Extra). - -%% ev_ready(Pid, Slogan) -> -%% ev_announce(Pid, ready, Slogan). - -%% ev_ready(Pid, Slogan, Extra) -> -%% ev_announce(Pid, ready, Slogan, Extra). - -%% ev_terminate(Pid) -> -%% Pid ! {terminate, self()}. - -%% ev_announce(To, Tag, Slogan) -> -%% ev_announce(To, Tag, Slogan, undefined) - -%% ev_announce(To, Tag, Slogan, Extra) -> -%% To ! {Tag, self(), Slogan, Extra}. - -%% ev_await_continue(Pid, Slogan, Pids) when is_pid(Pid) andalso is_list(Pids) -> -%% ev_await(Pid, continue, Slogan, Pids). - -%% ev_await_ready(Pid, Slogan, Pids) when is_pid(Pid) andalso is_list(Pids) -> -%% ev_await(Pid, ready, Slogan, Pids). - -%% ev_await_termination(Pid) -> -%% receive -%% {'DOWN', _, process, Pid, _} -> -%% ok -%% end. - -%% %% We expect a message from Pid, but we also watch for DOWN from -%% %% both Pid and Pids, in which case the test has failed! -%% ev_await(Pid, Tag, Slogan, Pids) -> -%% receive -%% {Tag, Pid, Slogan, undefined} -> -%% ok; -%% {Tag, Pid, Slogan, Extra} -> -%% {ok, Extra}; -%% {'DOWN', _, process, Pid, Reason} -> -%% ee("Unexpected DOWN regarding tester ~p: " -%% "~n ~p", [Tester, Reason]), -%% {error, {unexpected_exit, tester}}; - -%% ev_await_check_down(DownPid, DownReason, Pids) -> -%% case lists:keymember(DownPid, 1, Pids) of -%% {value, {_, Name}} -> -%% ee("Unexpected DOWN regarding ~w ~p: " -%% "~n ~p", [Name, DownPid, Reason]), -%% throw({error, {unexpected_exit, Name}}); -%% false -> -%% ok -%% end. - - %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% This test case is intended to test what happens when a socket is %% remotely closed while the process is calling the recvmsg function. @@ -5909,6 +5796,114 @@ eprint(Prefix, F, A) -> +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +ev_start(Pid) -> + ev_announce(Pid, start, undefined). + +ev_start(Pid, Extra) -> + ev_announce(Pid, start, undefined, Extra). + +ev_continue(Pid, Slogan) -> + ev_announce(Pid, continue, Slogan). + +%% ev_continue(Pid, Slogan, Extra) -> +%% ev_announce(Pid, continue, Slogan, Extra). + +ev_ready(Pid, Slogan) -> + ev_announce(Pid, ready, Slogan). + +ev_ready(Pid, Slogan, Extra) -> + ev_announce(Pid, ready, Slogan, Extra). + +ev_terminate(Pid) -> + Pid ! {terminate, self()}. + +ev_announce(To, Tag, Slogan) -> + ev_announce(To, Tag, Slogan, undefined). + +ev_announce(To, Tag, Slogan, Extra) -> + To ! {Tag, self(), Slogan, Extra}. + +ev_await_start() -> + receive + {start, Pid, _, undefined} -> + Pid; + {start, Pid, _, Extra} -> + {Pid, Extra} + end. + +ev_await_continue(Pid, Name, Slogan) -> + ev_await_continue(Pid, Name, Slogan, []). +ev_await_continue(Pid, Name, Slogan, Pids) when is_pid(Pid) andalso is_list(Pids) -> + ev_await(Pid, Name, continue, Slogan, Pids). + +ev_await_ready(Pid, Name, Slogan) -> + ev_await_ready(Pid, Name, Slogan, []). +ev_await_ready(Pid, Name, Slogan, Pids) when is_pid(Pid) andalso is_list(Pids) -> + ev_await(Pid, Name, ready, Slogan, Pids). + +ev_await_terminate(Pid, Name) -> + ev_await_terminate(Pid, Name, []). +ev_await_terminate(Pid, Name, Pids) -> + receive + {terminate, Pid} -> + ok; + {'DOWN', _, process, Pid, Reason} -> + ee("Unexpected DOWN regarding ~w ~p: " + "~n ~p", [Name, Pid, Reason]), + {error, {unexpected_exit, Name}}; + {'DOWN', _, process, DownPid, Reason} -> + case ev_await_check_down(DownPid, Reason, Pids) of + ok -> + ei("DOWN from unknown process ~p: " + "~n ~p", [DownPid, Reason]), + ev_await_terminate(Pid, Name, Pids); + {error, _} = ERROR -> + ERROR + end + end. + +ev_await_termination(Pid) -> + receive + {'DOWN', _, process, Pid, _} -> + ok + end. + +%% We expect a message from Pid, but we also watch for DOWN from +%% both Pid and Pids, in which case the test has failed! +ev_await(Pid, Name, Tag, Slogan, Pids) -> + receive + {Tag, Pid, Slogan, undefined} -> + ok; + {Tag, Pid, Slogan, Extra} -> + {ok, Extra}; + {'DOWN', _, process, Pid, Reason} -> + ee("Unexpected DOWN regarding ~w ~p: " + "~n ~p", [Name, Pid, Reason]), + {error, {unexpected_exit, Name}}; + {'DOWN', _, process, DownPid, Reason} -> + case ev_await_check_down(DownPid, Reason, Pids) of + ok -> + ei("DOWN from unknown process ~p: " + "~n ~p", [DownPid, Reason]), + ev_await(Pid, Name, Tag, Slogan, Pids); + {error, _} = ERROR -> + ERROR + end + end. + +ev_await_check_down(DownPid, DownReason, Pids) -> + case lists:keymember(DownPid, 1, Pids) of + {value, {_, Name}} -> + ee("Unexpected DOWN regarding ~w ~p: " + "~n ~p", [Name, DownPid, DownReason]), + {error, {unexpected_exit, Name}}; + false -> + ok + end. + + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% sock_open(Domain, Type, Proto) -> -- cgit v1.2.3 From 6fb041dde9eb7a1c3dd99ffb43bb8316f77c1cbb Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Wed, 24 Oct 2018 12:20:02 +0200 Subject: [socket-nif|test] Updated the basic send and recv tcp test cases Updated the basic api test cases api_b_send_and_recv_tcp4 and api_b_sendmsg_and_recvmsg_tcp4 with the new evaluator interface functions. OTP-14831 --- erts/emulator/test/socket_SUITE.erl | 282 ++++++++++++++++++++++++++++++++---- 1 file changed, 253 insertions(+), 29 deletions(-) diff --git a/erts/emulator/test/socket_SUITE.erl b/erts/emulator/test/socket_SUITE.erl index 29b20e620b..593e71f03d 100644 --- a/erts/emulator/test/socket_SUITE.erl +++ b/erts/emulator/test/socket_SUITE.erl @@ -615,6 +615,19 @@ api_b_send_and_recv_tcp(InitState) -> process_flag(trap_exit, true), ServerSeq = [ + %% *** Wait for start order *** + #{desc => "await start (from tester)", + cmd => fun(State) -> + Tester = ev_await_start(), + {ok, State#{tester => Tester}} + end}, + #{desc => "monitor tester", + cmd => fun(#{tester := Tester}) -> + _MRef = erlang:monitor(process, Tester), + ok + end}, + + %% *** Init part *** #{desc => "which local address", cmd => fun(#{domain := Domain} = State) -> LAddr = which_local_addr(Domain), @@ -643,24 +656,34 @@ api_b_send_and_recv_tcp(InitState) -> cmd => fun(#{lsock := LSock}) -> socket:listen(LSock) end}, - #{desc => "announce server port", - cmd => fun(#{parent := Parent, lport := Port}) -> - ei("announcing port to parent (~p)", [Parent]), - Parent ! {server_port, self(), Port}, + #{desc => "announce ready (init)", + cmd => fun(#{tester := Tester, lport := Port}) -> + ev_ready(Tester, init, Port), ok end}, + + %% The actual test + #{desc => "await continue (accept)", + cmd => fun(#{tester := Tester}) -> + ok = ev_await_continue(Tester, tester, accept) + end}, #{desc => "await connection", cmd => fun(#{lsock := LSock} = State) -> case socket:accept(LSock) of {ok, Sock} -> ei("accepted: ~n ~p", [Sock]), - {ok, State#{tsock => Sock}}; + {ok, State#{csock => Sock}}; {error, _} = ERROR -> ERROR end end}, - #{desc => "await request", - cmd => fun(#{tsock := Sock, recv := Recv}) -> + #{desc => "announce ready (accept)", + cmd => fun(#{tester := Tester}) -> + ev_ready(Tester, accept), + ok + end}, + #{desc => "await (recv) request", + cmd => fun(#{csock := Sock, recv := Recv}) -> case Recv(Sock) of {ok, ?BASIC_REQ} -> ok; @@ -668,23 +691,47 @@ api_b_send_and_recv_tcp(InitState) -> ERROR end end}, + #{desc => "announce ready (recv request)", + cmd => fun(#{tester := Tester}) -> + %% Tester ! {ready, self(), Port}, + ev_ready(Tester, recv_req), + ok + end}, + #{desc => "await continue (with send reply)", + cmd => fun(#{tester := Tester}) -> + ok = ev_await_continue(Tester, tester, send_reply) + end}, #{desc => "send reply", - cmd => fun(#{tsock := Sock, send := Send}) -> + cmd => fun(#{csock := Sock, send := Send}) -> Send(Sock, ?BASIC_REP) end}, - #{desc => "sleep some", - cmd => fun(_) -> - ?SLEEP(1000), + #{desc => "announce ready (send reply)", + cmd => fun(#{tester := Tester}) -> + %% Tester ! {ready, self(), Port}, + ev_ready(Tester, send_reply), ok end}, - #{desc => "close traffic socket", - cmd => fun(#{tsock := Sock}) -> + + %% *** Termination *** + #{desc => "await terminate", + cmd => fun(#{tester := Tester} = State) -> + case ev_await_terminate(Tester, tester) of + ok -> + {ok, maps:remove(tester, State)}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "close connection socket", + cmd => fun(#{csock := Sock}) -> socket:close(Sock) end}, #{desc => "close listen socket", cmd => fun(#{lsock := Sock}) -> socket:close(Sock) end}, + + %% *** We are done *** #{desc => "finish", cmd => fun(_) -> {ok, normal} @@ -693,13 +740,26 @@ api_b_send_and_recv_tcp(InitState) -> ClientSeq = [ + %% *** Wait for start order *** + #{desc => "await start (from tester)", + cmd => fun(State) -> + {Tester, Port} = ev_await_start(), + {ok, State#{tester => Tester, server_port => Port}} + end}, + #{desc => "monitor tester", + cmd => fun(#{tester := Tester}) -> + _MRef = erlang:monitor(process, Tester), + ok + end}, + + %% *** The init part *** #{desc => "which server (local) address", cmd => fun(#{domain := Domain, server_port := Port} = State) -> LAddr = which_local_addr(Domain), LSA = #{family => Domain, addr => LAddr}, SSA = LSA#{port => Port}, - {ok, State#{lsa => LSA, ssa => SSA}} + {ok, State#{local_sa => LSA, server_sa => SSA}} end}, #{desc => "create socket", cmd => fun(#{domain := Domain} = State) -> @@ -711,31 +771,195 @@ api_b_send_and_recv_tcp(InitState) -> end end}, #{desc => "bind to local address", - cmd => fun(#{sock := Sock, lsa := LSA} = State) -> + cmd => fun(#{sock := Sock, local_sa := LSA} = _State) -> case socket:bind(Sock, LSA) of - {ok, Port} -> - {ok, State#{port => Port}}; + {ok, _Port} -> + ok; {error, _} = ERROR -> ERROR end end}, + #{desc => "announce ready (init)", + cmd => fun(#{tester := Tester}) -> + ev_ready(Tester, init), + ok + end}, + + %% *** The actual test *** + #{desc => "await continue (connect)", + cmd => fun(#{tester := Tester} = _State) -> + ev_await_continue(Tester, tester, connect) + end}, #{desc => "connect to server", - cmd => fun(#{sock := Sock, ssa := SSA}) -> + cmd => fun(#{sock := Sock, server_sa := SSA}) -> socket:connect(Sock, SSA) end}, + #{desc => "announce ready (connect)", + cmd => fun(#{tester := Tester}) -> + ev_ready(Tester, connect), + ok + end}, + #{desc => "await continue (send request)", + cmd => fun(#{tester := Tester} = _State) -> + ev_await_continue(Tester, tester, send_req) + end}, #{desc => "send request (to server)", cmd => fun(#{sock := Sock, send := Send}) -> Send(Sock, ?BASIC_REQ) end}, - #{desc => "recv reply (from server)", + #{desc => "announce ready (send request)", + cmd => fun(#{tester := Tester}) -> + ev_ready(Tester, send_req), + ok + end}, + #{desc => "await recv reply (from server)", cmd => fun(#{sock := Sock, recv := Recv}) -> {ok, ?BASIC_REP} = Recv(Sock), ok end}, + #{desc => "announce ready (recv reply)", + cmd => fun(#{tester := Tester}) -> + ev_ready(Tester, recv_reply), + ok + end}, + + %% *** Termination *** + #{desc => "await terminate", + cmd => fun(#{tester := Tester} = State) -> + case ev_await_terminate(Tester, tester) of + ok -> + {ok, maps:remove(tester, State)}; + {error, _} = ERROR -> + ERROR + end + end}, #{desc => "close socket", cmd => fun(#{sock := Sock}) -> socket:close(Sock) end}, + + %% *** We are done *** + #{desc => "finish", + cmd => fun(_) -> + {ok, normal} + end} + ], + + TesterSeq = + [ + %% *** Init part *** + #{desc => "monitor server", + cmd => fun(#{server := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + #{desc => "monitor client", + cmd => fun(#{client := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + + %% Start the server + #{desc => "order server start", + cmd => fun(#{server := Pid} = _State) -> + ev_start(Pid), + ok + end}, + #{desc => "await server ready (init)", + cmd => fun(#{server := Pid} = State) -> + {ok, Port} = ev_await_ready(Pid, server, init), + {ok, State#{server_port => Port}} + end}, + + %% Start the client + #{desc => "order client start", + cmd => fun(#{client := Pid, server_port := Port} = _State) -> + ev_start(Pid, Port), + ok + end}, + #{desc => "await client ready (init)", + cmd => fun(#{client := Pid} = _State) -> + ev_await_ready(Pid, client, init), + ok + end}, + + %% *** The actual test *** + #{desc => "order server to continue (with accept)", + cmd => fun(#{server := Server} = _State) -> + ev_continue(Server, accept), + ok + end}, + #{desc => "sleep", + cmd => fun(_) -> + ?SLEEP(?SECS(1)), + ok + end}, + #{desc => "order client to continue (with connect)", + cmd => fun(#{client := Client} = _State) -> + ev_continue(Client, connect), + ok + end}, + #{desc => "await client ready (connect)", + cmd => fun(#{client := Client} = _State) -> + ev_await_ready(Client, client, connect) + end}, + #{desc => "await server ready (accept)", + cmd => fun(#{server := Server} = _State) -> + ev_await_ready(Server, server, accept) + end}, + #{desc => "order client to continue (with send request)", + cmd => fun(#{client := Client} = _State) -> + ev_continue(Client, send_req), + ok + end}, + #{desc => "await client ready (with send request)", + cmd => fun(#{client := Client} = _State) -> + ev_await_ready(Client, client, send_req) + end}, + #{desc => "await server ready (request recv)", + cmd => fun(#{server := Server} = _State) -> + ev_await_ready(Server, server, recv_req) + end}, + #{desc => "order server to continue (with send reply)", + cmd => fun(#{server := Server} = _State) -> + ev_continue(Server, send_reply), + ok + end}, + #{desc => "await server ready (with reply sent)", + cmd => fun(#{server := Server} = _State) -> + ev_await_ready(Server, server, send_reply) + end}, + #{desc => "await client ready (reply recv)", + cmd => fun(#{client := Client} = _State) -> + ev_await_ready(Client, client, recv_reply) + end}, + + + %% *** Termination *** + #{desc => "order client to terminate", + cmd => fun(#{client := Client} = _State) -> + ev_terminate(Client), + ok + end}, + #{desc => "await client termination", + cmd => fun(#{client := Client} = State) -> + ev_await_termination(Client), + State1 = maps:remove(client, State), + {ok, State1} + end}, + #{desc => "order server to terminate", + cmd => fun(#{server := Server} = _State) -> + ev_terminate(Server), + ok + end}, + #{desc => "await server termination", + cmd => fun(#{server := Server} = State) -> + ev_await_termination(Server), + State1 = maps:remove(server, State), + {ok, State1} + end}, + + %% *** We are done *** #{desc => "finish", cmd => fun(_) -> {ok, normal} @@ -743,16 +967,18 @@ api_b_send_and_recv_tcp(InitState) -> ], i("start server evaluator"), - #ev{pid = Pid} = Server = evaluator_start("server", ServerSeq, InitState), - i("await server (~p) port", [Pid]), - SPort = receive - {server_port, Pid, Port} -> - Port - end, + Server = evaluator_start("server", ServerSeq, InitState), + i("start client evaluator"), - Client = evaluator_start("client", ClientSeq, InitState#{server_port => SPort}), + Client = evaluator_start("client", ClientSeq, InitState), i("await evaluator(s)"), - ok = await_evaluator_finish([Server, Client]). + + i("start tester evaluator"), + TesterInitState = #{server => Server#ev.pid, + client => Client#ev.pid}, + Tester = evaluator_start("tester", TesterSeq, TesterInitState), + + ok = await_evaluator_finish([Server, Client, Tester]). @@ -2582,8 +2808,6 @@ api_to_receive_tcp(InitState) -> %% *** Termination *** #{desc => "order client to terminate", cmd => fun(#{client := Client} = _State) -> - %% Client ! {terminate, self()}, - %% ok ev_terminate(Client), ok end}, -- cgit v1.2.3 From 8a3bb9f42698389388d16b68521d03c43400f9cb Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Wed, 24 Oct 2018 13:45:19 +0200 Subject: [socket-nif|test] Updated the basic option ctrl proc test case Updated the basic api option test case api_opt_simple_otp_controlling_process with the new evaluator interface functions. OTP-14831 --- erts/emulator/test/socket_SUITE.erl | 119 +++++++++++++++++++++++------------- 1 file changed, 76 insertions(+), 43 deletions(-) diff --git a/erts/emulator/test/socket_SUITE.erl b/erts/emulator/test/socket_SUITE.erl index 593e71f03d..2772e8a5c0 100644 --- a/erts/emulator/test/socket_SUITE.erl +++ b/erts/emulator/test/socket_SUITE.erl @@ -1276,12 +1276,22 @@ api_opt_simple_otp_controlling_process() -> %% *** Init part *** #{desc => "await start", cmd => fun(State) -> - receive - {start, Tester, Socket} -> - {ok, State#{tester => Tester, - sock => Socket}} - end + %% receive + %% {start, Tester, Socket} -> + %% {ok, State#{tester => Tester, + %% sock => Socket}} + %% end + {Tester, Sock} = ev_await_start(), + {ok, State#{tester => Tester, + sock => Sock}} + end}, + #{desc => "monitor tester", + cmd => fun(#{tester := Tester}) -> + _MRef = erlang:monitor(process, Tester), + ok end}, + + %% *** The actual test *** #{desc => "verify tester as controlling-process", cmd => fun(#{tester := Tester, sock := Sock} = _State) -> case Get(Sock, controlling_process) of @@ -1304,17 +1314,20 @@ api_opt_simple_otp_controlling_process() -> ERROR end end}, - #{desc => "announce ready (1)", + #{desc => "announce ready (not owner)", cmd => fun(#{tester := Tester} = _State) -> - Tester ! {ready, self()}, + ev_ready(Tester, not_owner), + %% Tester ! {ready, self()}, ok end}, - #{desc => "await continue", + #{desc => "await continue (owner)", cmd => fun(#{tester := Tester} = _State) -> - receive - {continue, Tester} -> - ok - end + %% receive + %% {continue, Tester} -> + %% ok + %% end + ev_await_continue(Tester, tester, owner), + ok end}, #{desc => "verify self as controlling-process", cmd => fun(#{sock := Sock} = _State) -> @@ -1343,19 +1356,27 @@ api_opt_simple_otp_controlling_process() -> ERROR end end}, - #{desc => "announce ready (2)", + #{desc => "announce ready (owner)", cmd => fun(#{tester := Tester} = _State) -> - Tester ! {ready, self()}, + %% Tester ! {ready, self()}, + ev_ready(Tester, owner), ok + end}, + + %% *** Termination *** #{desc => "await termination", cmd => fun(#{tester := Tester} = State) -> - receive - {terminate, Tester} -> - State1 = maps:remove(tester, State), - State2 = maps:remove(sock, State1), - {ok, State2} - end + %% receive + %% {terminate, Tester} -> + %% State1 = maps:remove(tester, State), + %% State2 = maps:remove(sock, State1), + %% {ok, State2} + %% end + ev_await_terminate(Tester, tester), + State1 = maps:remove(tester, State), + State2 = maps:remove(sock, State1), + {ok, State2} end}, %% *** We are done *** @@ -1375,6 +1396,13 @@ api_opt_simple_otp_controlling_process() -> Sock = sock_open(Domain, Type, Protocol), {ok, State#{sock => Sock}} end}, + #{desc => "monitor client", + cmd => fun(#{client := Client} = _State) -> + _MRef = erlang:monitor(process, Client), + ok + end}, + + %% *** The actual test *** #{desc => "verify self as controlling-process", cmd => fun(#{sock := Sock} = _State) -> Self = self(), @@ -1389,15 +1417,17 @@ api_opt_simple_otp_controlling_process() -> end}, #{desc => "order (client) start", cmd => fun(#{client := Client, sock := Sock} = _State) -> - Client ! {start, self(), Sock}, + %% Client ! {start, self(), Sock}, + ev_start(Client, Sock), ok end}, - #{desc => "await (client) ready (1)", + #{desc => "await (client) ready (not owner)", cmd => fun(#{client := Client} = _State) -> - receive - {ready, Client} -> - ok - end + %% receive + %% {ready, Client} -> + %% ok + %% end + ev_await_ready(Client, client, not_owner) end}, #{desc => "attempt controlling-process transfer to client", cmd => fun(#{client := Client, sock := Sock} = _State) -> @@ -1425,17 +1455,20 @@ api_opt_simple_otp_controlling_process() -> ERROR end end}, - #{desc => "order (client) continue", + #{desc => "order (client) continue (owner)", cmd => fun(#{client := Client} = _State) -> - Client ! {continue, self()}, + %% Client ! {continue, self()}, + ev_continue(Client, owner), ok end}, #{desc => "await (client) ready (2)", cmd => fun(#{client := Client} = _State) -> - receive - {ready, Client} -> - ok - end + %% receive + %% {ready, Client} -> + %% ok + %% end + ev_await_ready(Client, client, owner), + ok end}, #{desc => "verify self as controlling-process", cmd => fun(#{sock := Sock} = _State) -> @@ -1449,22 +1482,22 @@ api_opt_simple_otp_controlling_process() -> ERROR end end}, - #{desc => "monitor client", - cmd => fun(#{client := Client} = State) -> - MRef = erlang:monitor(process, Client), - {ok, State#{client_mref => MRef}} - end}, + + %% *** Termination *** #{desc => "order (client) terminate", cmd => fun(#{client := Client} = _State) -> - Client ! {terminate, self()}, + %% Client ! {terminate, self()}, + ev_terminate(Client), ok end}, - #{desc => "await (client) down", + #{desc => "await client termination", cmd => fun(#{client := Client} = State) -> - receive - {'DOWN', _, process, Client, _} -> - {ok, maps:remove(client, State)} - end + %% receive + %% {'DOWN', _, process, Client, _} -> + %% {ok, maps:remove(client, State)} + %% end + ev_await_termination(Client), + {ok, maps:remove(client, State)} end}, #{desc => "close socket", cmd => fun(#{sock := Sock} = State) -> -- cgit v1.2.3 From 4133d8c5f8d210404b233ef114acb166ce22198e Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Wed, 24 Oct 2018 15:13:20 +0200 Subject: [socket-nif|test] Updated the basic timeout connect test case Updated the basic api timeout test case api_to_connect_tcp with the new evaluator interface functions. OTP-14831 --- erts/emulator/test/socket_SUITE.erl | 105 ++++++++++++++++++------------------ 1 file changed, 52 insertions(+), 53 deletions(-) diff --git a/erts/emulator/test/socket_SUITE.erl b/erts/emulator/test/socket_SUITE.erl index 2772e8a5c0..215e5895ff 100644 --- a/erts/emulator/test/socket_SUITE.erl +++ b/erts/emulator/test/socket_SUITE.erl @@ -1594,10 +1594,13 @@ api_to_connect_tcp(InitState) -> %% *** Wait for start order part *** #{desc => "await start (from tester)", cmd => fun(State) -> - receive - {start, Tester} when is_pid(Tester) -> - {ok, State#{tester => Tester}} - end + Tester = ev_await_start(), + {ok, State#{tester => Tester}} + end}, + #{desc => "monitor tester", + cmd => fun(#{tester := Tester} = _State) -> + _MRef = erlang:monitor(process, Tester), + ok end}, %% *** Init part *** @@ -1605,7 +1608,7 @@ api_to_connect_tcp(InitState) -> cmd => fun(#{domain := Domain} = State) -> LAddr = which_local_addr(Domain), LSA = #{family => Domain, addr => LAddr}, - {ok, State#{lsa => LSA}} + {ok, State#{local_sa => LSA}} end}, #{desc => "create listen socket", cmd => fun(#{domain := Domain} = State) -> @@ -1617,7 +1620,7 @@ api_to_connect_tcp(InitState) -> end end}, #{desc => "bind to local address", - cmd => fun(#{lsock := LSock, lsa := LSA} = State) -> + cmd => fun(#{lsock := LSock, local_sa := LSA} = State) -> case socket:bind(LSock, LSA) of {ok, Port} -> {ok, State#{lport => Port}}; @@ -1629,28 +1632,29 @@ api_to_connect_tcp(InitState) -> cmd => fun(#{lsock := LSock}) -> socket:listen(LSock, 1) end}, - #{desc => "monitor server", - cmd => fun(#{tester := Tester} = State) -> - MRef = erlang:monitor(process, Tester), - {ok, State#{tester_mref => MRef}} - end}, - #{desc => "announce ready", + #{desc => "announce ready (init)", cmd => fun(#{tester := Tester, lport := Port}) -> - ei("announcing ready to tester (~p)", [Tester]), - Tester ! {ready, self(), Port}, + ev_ready(Tester, init, Port), ok end}, - #{desc => "await terminate (from tester)", + + %% Termination + #{desc => "await terminate", cmd => fun(#{tester := Tester} = State) -> - receive - {'DOWN', _, process, Tester, Reason} -> - ee("Unexpected DOWN regarding tester ~p: " - "~n ~p", [Tester, Reason]), - {error, {unexpected_exit, tester}}; - {terminate, Tester} -> - {ok, maps:remove(tester, State)} + case ev_await_terminate(Tester, tester) of + ok -> + {ok, maps:remove(tester, State)}; + {error, _} = ERROR -> + ERROR end end}, + #{desc => "close socket", + cmd => fun(#{lsock := Sock} = State) -> + sock_close(Sock), + State1 = maps:remove(lport, State), + State2 = maps:remove(sock, State1), + {ok, State2} + end}, %% *** We are done *** #{desc => "finish", @@ -1662,11 +1666,16 @@ api_to_connect_tcp(InitState) -> TesterSeq = [ %% *** Init part *** + #{desc => "monitor server", + cmd => fun(#{server := Server} = _State) -> + _MRef = erlang:monitor(process, Server), + ok + end}, #{desc => "which local address", cmd => fun(#{domain := Domain} = State) -> LAddr = which_local_addr(Domain), LSA = #{family => Domain, addr => LAddr}, - {ok, State#{lsa => LSA}} + {ok, State#{local_sa => LSA}} end}, #{desc => "create socket 1", cmd => fun(#{domain := Domain} = State) -> @@ -1696,7 +1705,7 @@ api_to_connect_tcp(InitState) -> end end}, #{desc => "bind socket 1 to local address", - cmd => fun(#{sock1 := Sock, lsa := LSA} = _State) -> + cmd => fun(#{sock1 := Sock, local_sa := LSA} = _State) -> case socket:bind(Sock, LSA) of {ok, _} -> ok; @@ -1705,7 +1714,7 @@ api_to_connect_tcp(InitState) -> end end}, #{desc => "bind socket 2 to local address", - cmd => fun(#{sock2 := Sock, lsa := LSA} = _State) -> + cmd => fun(#{sock2 := Sock, local_sa := LSA} = _State) -> case socket:bind(Sock, LSA) of {ok, _} -> ok; @@ -1714,7 +1723,7 @@ api_to_connect_tcp(InitState) -> end end}, #{desc => "bind socket 3 to local address", - cmd => fun(#{sock3 := Sock, lsa := LSA} = _State) -> + cmd => fun(#{sock3 := Sock, local_sa := LSA} = _State) -> case socket:bind(Sock, LSA) of {ok, _} -> ok; @@ -1724,52 +1733,42 @@ api_to_connect_tcp(InitState) -> end}, %% *** Synchronize with the server *** - #{desc => "order (server) start", + #{desc => "order server start", cmd => fun(#{server := Server}) -> - Server ! {start, self()}, + ev_start(Server), ok end}, - #{desc => "await ready (from server)", - cmd => fun(#{server := Server, lsa := LSA} = State) -> - receive - {ready, Server, Port} -> - {ok, State#{ssa => LSA#{port => Port}}} - end + #{desc => "await server ready (init)", + cmd => fun(#{server := Server, local_sa := LSA} = State) -> + {ok, Port} = ev_await_ready(Server, server, init), + ServerSA = LSA#{port => Port}, + {ok, State#{server_sa => ServerSA}} end}, %% *** Connect sequence *** #{desc => "order (server) start", - cmd => fun(#{sock1 := Sock1, - sock2 := Sock2, - sock3 := Sock3, - ssa := SSA, - timeout := To}) -> + cmd => fun(#{sock1 := Sock1, + sock2 := Sock2, + sock3 := Sock3, + server_sa := SSA, + timeout := To}) -> Socks = [Sock1, Sock2, Sock3], api_to_connect_tcp_await_timeout(Socks, To, SSA) end}, %% *** Terminate server *** - #{desc => "monitor server", - cmd => fun(#{server := Server} = State) -> - MRef = erlang:monitor(process, Server), - {ok, State#{server_mref => MRef}} - end}, #{desc => "order (server) terminate", cmd => fun(#{server := Server} = _State) -> - Server ! {terminate, self()}, + ev_terminate(Server), ok end}, #{desc => "await (server) down", cmd => fun(#{server := Server} = State) -> - receive - {'DOWN', _, process, Server, _} -> - State1 = maps:remove(server, State), - State2 = maps:remove(ssa, State1), - {ok, State2} - end + ev_await_termination(Server), + State1 = maps:remove(server, State), + State2 = maps:remove(server_sa, State1), + {ok, State2} end}, - - %% *** Close our sockets *** #{desc => "close socket 3", cmd => fun(#{sock3 := Sock} = State) -> sock_close(Sock), -- cgit v1.2.3 From 6c4130feb9ff0ca48a92276f26272b765a9a3065 Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Wed, 24 Oct 2018 18:36:06 +0200 Subject: [socket-nif|test] Updated the basic timeout multi accept test case Updated the basic api timeout test case(s) api_to_maccept_tcp with the new evaluator interface functions. OTP-14831 --- erts/emulator/test/socket_SUITE.erl | 267 ++++++++++++++---------------------- 1 file changed, 100 insertions(+), 167 deletions(-) diff --git a/erts/emulator/test/socket_SUITE.erl b/erts/emulator/test/socket_SUITE.erl index 215e5895ff..7ddf5c357b 100644 --- a/erts/emulator/test/socket_SUITE.erl +++ b/erts/emulator/test/socket_SUITE.erl @@ -1966,6 +1966,7 @@ api_to_maccept_tcp4(suite) -> api_to_maccept_tcp4(doc) -> []; api_to_maccept_tcp4(_Config) when is_list(_Config) -> + ?TT(?SECS(20)), tc_try(api_to_maccept_tcp4, fun() -> InitState = #{domain => inet, timeout => 5000}, @@ -1982,6 +1983,7 @@ api_to_maccept_tcp6(suite) -> api_to_maccept_tcp6(doc) -> []; api_to_maccept_tcp6(_Config) when is_list(_Config) -> + ?TT(?SECS(20)), tc_try(api_to_maccept_tcp4, fun() -> not_yet_implemented(), @@ -1998,12 +2000,13 @@ api_to_maccept_tcp(InitState) -> %% *** Init part *** #{desc => "await start", cmd => fun(State) -> - receive - {start, Tester} -> - MRef = erlang:monitor(process, Tester), - {ok, State#{tester => Tester, - tester_mref => MRef}} - end + Tester = ev_await_start(), + {ok, State#{tester => Tester}} + end}, + #{desc => "monitor tester", + cmd => fun(#{tester := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), + ok end}, #{desc => "which local address", cmd => fun(#{domain := Domain} = State) -> @@ -2033,26 +2036,18 @@ api_to_maccept_tcp(InitState) -> cmd => fun(#{lsock := LSock}) -> socket:listen(LSock) end}, - - #{desc => "announce ready", + #{desc => "announce ready (init)", cmd => fun(#{lsock := LSock, tester := Tester}) -> - ei("announcing port to tester (~p)", [Tester]), - Tester ! {ready, self(), LSock}, + ev_ready(Tester, init, LSock), ok end}, - #{desc => "await continue", + + %% *** The actual test *** + #{desc => "await continue (accept)", cmd => fun(#{tester := Tester} = _State) -> - receive - {'DOWN', _, process, Tester, Reason} -> - ee("Unexpected DOWN regarding tester ~p: " - "~n ~p", [Tester, Reason]), - {error, {unexpected_exit, tester}}; - {continue, Tester} -> - ok - end + ev_await_continue(Tester, tester, accept), + ok end}, - - %% *** The actual test part *** #{desc => "attempt to accept (without success)", cmd => fun(#{lsock := LSock, timeout := To} = State) -> Start = t(), @@ -2060,6 +2055,8 @@ api_to_maccept_tcp(InitState) -> {error, timeout} -> {ok, State#{start => Start, stop => t()}}; {ok, Sock} -> + ee("Unexpected accept success: " + "~n ~p", [Sock]), (catch socket:close(Sock)), {error, unexpected_success}; {error, _} = ERROR -> @@ -2076,24 +2073,18 @@ api_to_maccept_tcp(InitState) -> {error, {unexpected_timeout, TDiff, To}} end end}, - #{desc => "announce ready", + #{desc => "announce ready (accept)", cmd => fun(#{tester := Tester}) -> - ei("announcing port to tester (~p)", [Tester]), - Tester ! {ready, self()}, + ev_ready(Tester, accept), ok end}, + + %% *** Terminate *** #{desc => "await terminate", cmd => fun(#{tester := Tester} = _State) -> - receive - {'DOWN', _, process, Tester, Reason} -> - ee("Unexpected DOWN regarding tester ~p: " - "~n ~p", [Tester, Reason]), - {error, {unexpected_exit, tester}}; - {terminate, Tester} -> - ok - end + ev_await_terminate(Tester, tester), + ok end}, - %% *** Close (listen) socket *** #{desc => "close (listen) socket", cmd => fun(#{lsock := LSock} = State) -> @@ -2114,32 +2105,28 @@ api_to_maccept_tcp(InitState) -> %% *** Init part *** #{desc => "await start", cmd => fun(State) -> - receive - {start, Tester, LSock} -> - MRef = erlang:monitor(process, Tester), - {ok, State#{tester => Tester, - lsock => LSock, - tester_mref => MRef}} - end + {Tester, LSock} = ev_await_start(), + {ok, State#{tester => Tester, + lsock => LSock}} + end}, - #{desc => "announce ready (1)", - cmd => fun(#{tester := Tester} = _State) -> - Tester ! {ready, self()}, + #{desc => "monitor tester", + cmd => fun(#{tester := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), ok end}, - #{desc => "await continue", + #{desc => "announce ready (init)", cmd => fun(#{tester := Tester} = _State) -> - receive - {'DOWN', _, process, Tester, Reason} -> - ee("Unexpected DOWN regarding tester ~p: " - "~n ~p", [Tester, Reason]), - {error, {unexpected_exit, tester, Reason}}; - {continue, Tester} -> - ok - end + ev_ready(Tester, init), + ok end}, %% *** The actual test part *** + #{desc => "await continue (accept)", + cmd => fun(#{tester := Tester} = _State) -> + ev_await_continue(Tester, tester, accept), + ok + end}, #{desc => "attempt to accept (without success)", cmd => fun(#{lsock := LSock, timeout := To} = State) -> Start = t(), @@ -2154,29 +2141,31 @@ api_to_maccept_tcp(InitState) -> end end}, #{desc => "validate timeout time", - cmd => fun(#{start := Start, stop := Stop, timeout := To} = _State) -> + cmd => fun(#{start := Start, stop := Stop, timeout := To} = State) -> TDiff = tdiff(Start, Stop), if (TDiff >= To) -> - ok; + State1 = maps:remove(start, State), + State2 = maps:remove(stop, State1), + {ok, State2}; true -> {error, {unexpected_timeout, TDiff, To}} end end}, - #{desc => "announce ready (2)", + #{desc => "announce ready (accept)", cmd => fun(#{tester := Tester} = _State) -> - Tester ! {ready, self()}, + ev_ready(Tester, accept), ok end}, + + %% *** Terminate *** #{desc => "await terminate", - cmd => fun(#{tester := Tester} = _State) -> - receive - {'DOWN', _, process, Tester, Reason} -> - ee("Unexpected DOWN regarding tester ~p: " - "~n ~p", [Tester, Reason]), - {error, {unexpected_exit, tester, Reason}}; - {terminate, Tester} -> - ok + cmd => fun(#{tester := Tester} = State) -> + case ev_await_terminate(Tester, tester) of + ok -> + {ok, maps:remove(tester, State)}; + {error, _} = ERROR -> + ERROR end end}, @@ -2211,154 +2200,101 @@ api_to_maccept_tcp(InitState) -> %% Start the prim-acceptor #{desc => "start prim-acceptor", cmd => fun(#{prim_acceptor := Pid} = _State) -> - Pid ! {start, self()}, + ev_start(Pid), ok end}, - #{desc => "await prim-acceptor ready (1)", + #{desc => "await prim-acceptor ready (init)", cmd => fun(#{prim_acceptor := Pid} = State) -> - receive - {'DOWN', _, process, Pid, Reason} -> - ee("Unexpected DOWN regarding prim-acceptor ~p:" - "~n ~p", [Pid, Reason]), - {error, {unexpected_exit, prim_acceptor}}; - {ready, Pid, LSock} -> - {ok, State#{lsock => LSock}} - end + {ok, Sock} = ev_await_ready(Pid, prim_acceptor, init), + {ok, State#{lsock => Sock}} end}, %% Start sec-acceptor-1 #{desc => "start sec-acceptor 1", cmd => fun(#{sec_acceptor1 := Pid, lsock := LSock} = _State) -> - Pid ! {start, self(), LSock}, + ev_start(Pid, LSock), ok end}, - #{desc => "await sec-acceptor 1 ready (1)", + #{desc => "await sec-acceptor 1 ready (init)", cmd => fun(#{sec_acceptor1 := Pid} = _State) -> - receive - {'DOWN', _, process, Pid, Reason} -> - ee("Unexpected DOWN regarding sec-acceptor 1 ~p:" - "~n ~p", [Pid, Reason]), - {error, {unexpected_exit, sec_acceptor_1}}; - {ready, Pid} -> - ok - end + ev_await_ready(Pid, sec_acceptor1, init) end}, %% Start sec-acceptor-2 #{desc => "start sec-acceptor 2", cmd => fun(#{sec_acceptor2 := Pid, lsock := LSock} = _State) -> - Pid ! {start, self(), LSock}, + ev_start(Pid, LSock), ok end}, - #{desc => "await sec-acceptor 2 ready (1)", + #{desc => "await sec-acceptor 2 ready (init)", cmd => fun(#{sec_acceptor2 := Pid} = _State) -> - receive - {'DOWN', _, process, Pid, Reason} -> - ee("Unexpected DOWN regarding sec-acceptor 2 ~p:" - "~n ~p", [Pid, Reason]), - {error, {unexpected_exit, sec_acceptor_2}}; - {ready, Pid} -> - ok - end + ev_await_ready(Pid, sec_acceptor2, init) end}, %% Activate the acceptor(s) #{desc => "active prim-acceptor", cmd => fun(#{prim_acceptor := Pid} = _State) -> - Pid ! {continue, self()}, + ev_continue(Pid, accept), ok end}, #{desc => "active sec-acceptor 1", cmd => fun(#{sec_acceptor1 := Pid} = _State) -> - Pid ! {continue, self()}, + ev_continue(Pid, accept), ok end}, #{desc => "active sec-acceptor 2", cmd => fun(#{sec_acceptor2 := Pid} = _State) -> - Pid ! {continue, self()}, + ev_continue(Pid, accept), ok end}, %% Await acceptor(s) completions - #{desc => "await prim-acceptor ready (2)", + #{desc => "await prim-acceptor ready (accept)", cmd => fun(#{prim_acceptor := Pid} = _State) -> - receive - {'DOWN', _, process, Pid, Reason} -> - ee("Unexpected DOWN regarding prim-acceptor ~p:" - "~n ~p", [Pid, Reason]), - {error, {unexpected_exit, prim_acceptor}}; - {ready, Pid} -> - ok - end + ev_await_ready(Pid, prim_acceptor, accept) end}, - #{desc => "await sec-acceptor 1 ready (2)", + #{desc => "await sec-acceptor 1 ready (accept)", cmd => fun(#{sec_acceptor1 := Pid} = _State) -> - receive - {'DOWN', _, process, Pid, Reason} -> - ee("Unexpected DOWN regarding sec-acceptor 1 ~p:" - "~n ~p", [Pid, Reason]), - {error, {unexpected_exit, sec_acceptor_1}}; - {ready, Pid} -> - ok - end + ev_await_ready(Pid, sec_acceptor1, accept) end}, - #{desc => "await sec-acceptor 2 ready (2)", + #{desc => "await sec-acceptor 2 ready (accept)", cmd => fun(#{sec_acceptor2 := Pid} = _State) -> - receive - {'DOWN', _, process, Pid, Reason} -> - ee("Unexpected DOWN regarding sec-acceptor 2 ~p:" - "~n ~p", [Pid, Reason]), - {error, {unexpected_exit, sec_acceptor_2}}; - {ready, Pid} -> - ok - end + ev_await_ready(Pid, sec_acceptor2, accept) end}, - - %% Terminate the acceptor(s) + %% Terminate #{desc => "order prim-acceptor to terminate", cmd => fun(#{prim_acceptor := Pid} = _State) -> - ei("send terminate command to prim-acceptor (~p)", [Pid]), - Pid ! {terminate, self()}, + ev_terminate(Pid), ok end}, + #{desc => "await prim-acceptor termination", + cmd => fun(#{prim_acceptor := Pid} = State) -> + ev_await_termination(Pid), + State1 = maps:remove(prim_acceptor, State), + {ok, State1} + end}, #{desc => "order sec-acceptor 1 to terminate", cmd => fun(#{sec_acceptor1 := Pid} = _State) -> - ei("send terminate command to sec-acceptor-1 (~p)", [Pid]), - Pid ! {terminate, self()}, + ev_terminate(Pid), ok end}, + #{desc => "await sec-acceptor 1 termination", + cmd => fun(#{sec_acceptor1 := Pid} = State) -> + ev_await_termination(Pid), + State1 = maps:remove(sec_acceptor1, State), + {ok, State1} + end}, #{desc => "order sec-acceptor 2 to terminate", cmd => fun(#{sec_acceptor2 := Pid} = _State) -> - ei("send terminate command to sec-acceptor-2 (~p)", [Pid]), - Pid ! {terminate, self()}, + ev_terminate(Pid), ok end}, - - %% Await acceptor(s) termination - #{desc => "await prim-acceptor termination", - cmd => fun(#{prim_acceptor := Pid} = State) -> - receive - {'DOWN', _, process, Pid, _} -> - State1 = maps:remove(prim_acceptor, State), - {ok, State1} - end - end}, - #{desc => "await sec-acceptor 1 termination", - cmd => fun(#{sec_acceptor1 := Pid} = State) -> - receive - {'DOWN', _, process, Pid, _} -> - State1 = maps:remove(sec_acceptor1, State), - {ok, State1} - end - end}, #{desc => "await sec-acceptor 2 termination", cmd => fun(#{sec_acceptor2 := Pid} = State) -> - receive - {'DOWN', _, process, Pid, _} -> - State1 = maps:remove(sec_acceptor2, State), - {ok, State1} - end + ev_await_termination(Pid), + State1 = maps:remove(sec_acceptor2, State), + {ok, State1} end}, %% *** We are done *** @@ -2370,26 +2306,23 @@ api_to_maccept_tcp(InitState) -> i("create prim-acceptor evaluator"), PrimAInitState = InitState, - #ev{pid = PPid} = PrimAcceptor = evaluator_start("prim-acceptor", - PrimAcceptorSeq, - PrimAInitState), + PrimAcceptor = evaluator_start("prim-acceptor", + PrimAcceptorSeq, PrimAInitState), i("create sec-acceptor 1 evaluator"), SecAInitState1 = maps:remove(domain, InitState), - #ev{pid = SPid1} = SecAcceptor1 = evaluator_start("sec-acceptor-1", - SecAcceptorSeq, - SecAInitState1), + SecAcceptor1 = evaluator_start("sec-acceptor-1", + SecAcceptorSeq, SecAInitState1), i("create sec-acceptor 2 evaluator"), SecAInitState2 = SecAInitState1, - #ev{pid = SPid2} = SecAcceptor2 = evaluator_start("sec-acceptor-2", - SecAcceptorSeq, - SecAInitState2), + SecAcceptor2 = evaluator_start("sec-acceptor-2", + SecAcceptorSeq, SecAInitState2), i("create tester evaluator"), - TesterInitState = #{prim_acceptor => PPid, - sec_acceptor1 => SPid1, - sec_acceptor2 => SPid2}, + TesterInitState = #{prim_acceptor => PrimAcceptor#ev.pid, + sec_acceptor1 => SecAcceptor1#ev.pid, + sec_acceptor2 => SecAcceptor2#ev.pid}, Tester = evaluator_start("tester", TesterSeq, TesterInitState), i("await evaluator(s)"), -- cgit v1.2.3 From d1f8a81891788db66205d533a807060ba4c2a069 Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Thu, 25 Oct 2018 09:28:58 +0200 Subject: [socket-nif|test] Updated socket close test case(s) Updated the socket close test case(s) sc_cpe_socket_cleanup with the new evaluator interface functions. OTP-14831 --- erts/emulator/test/socket_SUITE.erl | 140 ++++++++++++++++-------------------- 1 file changed, 61 insertions(+), 79 deletions(-) diff --git a/erts/emulator/test/socket_SUITE.erl b/erts/emulator/test/socket_SUITE.erl index 7ddf5c357b..6a5b77dd4e 100644 --- a/erts/emulator/test/socket_SUITE.erl +++ b/erts/emulator/test/socket_SUITE.erl @@ -2521,13 +2521,18 @@ api_to_receive_tcp(InitState) -> Tester = ev_await_start(), {ok, State#{tester => Tester}} end}, + #{desc => "monitor tester", + cmd => fun(#{tester := Tester}) -> + _MRef = erlang:monitor(process, Tester), + ok + end}, %% *** Init part *** #{desc => "which local address", cmd => fun(#{domain := Domain} = State) -> LAddr = which_local_addr(Domain), LSA = #{family => Domain, addr => LAddr}, - {ok, State#{lsa => LSA}} + {ok, State#{local_sa => LSA}} end}, #{desc => "create listen socket", cmd => fun(#{domain := Domain} = State) -> @@ -2539,7 +2544,7 @@ api_to_receive_tcp(InitState) -> end end}, #{desc => "bind to local address", - cmd => fun(#{lsock := LSock, lsa := LSA} = State) -> + cmd => fun(#{lsock := LSock, local_sa := LSA} = State) -> case socket:bind(LSock, LSA) of {ok, Port} -> {ok, State#{lport => Port}}; @@ -2551,23 +2556,17 @@ api_to_receive_tcp(InitState) -> cmd => fun(#{lsock := LSock}) -> socket:listen(LSock, 1) end}, - #{desc => "monitor tester", - cmd => fun(#{tester := Tester} = State) -> - MRef = erlang:monitor(process, Tester), - {ok, State#{tester_mref => MRef}} - end}, - #{desc => "announce ready", + #{desc => "announce ready (init)", cmd => fun(#{tester := Tester, lport := Port}) -> - %% Tester ! {ready, self(), Port}, ev_ready(Tester, init, Port), ok end}, + + %% *** The actual test *** #{desc => "await continue (accept and recv)", cmd => fun(#{tester := Tester}) -> ok = ev_await_continue(Tester, tester, accept_recv) end}, - - %% *** The actual test *** #{desc => "attempt accept", cmd => fun(#{lsock := LSock} = State) -> case socket:accept(LSock) of @@ -2590,18 +2589,19 @@ api_to_receive_tcp(InitState) -> end end}, #{desc => "validate timeout time", - cmd => fun(#{start := Start, stop := Stop, timeout := To} = _State) -> + cmd => fun(#{start := Start, stop := Stop, timeout := To} = State) -> TDiff = tdiff(Start, Stop), if (TDiff >= To) -> - ok; + State1 = maps:remove(start, State), + State2 = maps:remove(stop, State1), + {ok, State2}; true -> {error, {unexpected_timeout, TDiff, To}} end end}, #{desc => "announce ready (recv timeout success)", cmd => fun(#{tester := Tester} = _State) -> - %% Tester ! {ready, self()}, %% ok ev_ready(Tester, accept_recv), ok @@ -2644,6 +2644,11 @@ api_to_receive_tcp(InitState) -> {ok, State#{tester => Tester, server_port => Port}} end}, + #{desc => "monitor tester", + cmd => fun(#{tester := Tester} = _State) -> + _MRef = erlang:monitor(process, Tester), + ok + end}, %% *** Init part *** #{desc => "which local address", @@ -2652,7 +2657,7 @@ api_to_receive_tcp(InitState) -> LSA = #{family => Domain, addr => LAddr}, SSA = LSA#{port => Port}, - {ok, State#{lsa => LSA, ssa => SSA}} + {ok, State#{local_sa => LSA, server_sa => SSA}} end}, #{desc => "create socket", cmd => fun(#{domain := Domain} = State) -> @@ -2664,7 +2669,7 @@ api_to_receive_tcp(InitState) -> end end}, #{desc => "bind to local address", - cmd => fun(#{sock := Sock, lsa := LSA} = _State) -> + cmd => fun(#{sock := Sock, local_sa := LSA} = _State) -> case socket:bind(Sock, LSA) of {ok, _} -> ok; @@ -2672,11 +2677,6 @@ api_to_receive_tcp(InitState) -> ERROR end end}, - #{desc => "monitor tester", - cmd => fun(#{tester := Tester} = State) -> - MRef = erlang:monitor(process, Tester), - {ok, State#{tester_mref => MRef}} - end}, #{desc => "announce ready (init)", cmd => fun(#{tester := Tester} = _State) -> ev_ready(Tester, init), @@ -2689,7 +2689,7 @@ api_to_receive_tcp(InitState) -> ok = ev_await_continue(Tester, tester, connect) end}, #{desc => "connect", - cmd => fun(#{sock := Sock, ssa := SSA}) -> + cmd => fun(#{sock := Sock, server_sa := SSA}) -> sock_connect(Sock, SSA), ok end}, @@ -2721,14 +2721,14 @@ api_to_receive_tcp(InitState) -> [ %% *** Init part *** #{desc => "monitor server", - cmd => fun(#{server := Server} = State) -> - MRef = erlang:monitor(process, Server), - {ok, State#{server_mref => MRef}} + cmd => fun(#{server := Server} = _State) -> + _MRef = erlang:monitor(process, Server), + ok end}, #{desc => "monitor client", - cmd => fun(#{client := Client} = State) -> - MRef = erlang:monitor(process, Client), - {ok, State#{client_mref => MRef}} + cmd => fun(#{client := Client} = _State) -> + _MRef = erlang:monitor(process, Client), + ok end}, %% *** Activate server *** @@ -2780,8 +2780,7 @@ api_to_receive_tcp(InitState) -> cmd => fun(#{client := Client} = State) -> ev_await_termination(Client), State1 = maps:remove(client, State), - State2 = maps:remove(client_mref, State1), - {ok, State2} + {ok, State1} end}, #{desc => "order server to terminate", cmd => fun(#{server := Server} = _State) -> @@ -2792,9 +2791,8 @@ api_to_receive_tcp(InitState) -> cmd => fun(#{server := Server} = State) -> ev_await_termination(Server), State1 = maps:remove(server, State), - State2 = maps:remove(server_mref, State1), - State3 = maps:remove(server_port, State2), - {ok, State3} + State2 = maps:remove(server_port, State1), + {ok, State2} end}, %% *** We are done *** @@ -2807,18 +2805,15 @@ api_to_receive_tcp(InitState) -> i("start server evaluator"), ServerInitState = InitState, - #ev{pid = SPid} = Server = evaluator_start("server", - ServerSeq, - ServerInitState), + Server = evaluator_start("server", ServerSeq, ServerInitState), i("start client evaluator"), ClientInitState = InitState, - #ev{pid = CPid} = Client = evaluator_start("client", - ClientSeq, - ClientInitState), + Client = evaluator_start("client", ClientSeq, ClientInitState), i("start tester evaluator"), - TesterInitState = #{server => SPid, client => CPid}, + TesterInitState = #{server => Server#ev.pid, + client => Client#ev.pid}, Tester = evaluator_start("tester", TesterSeq, TesterInitState), i("await evaluator(s)"), @@ -3127,10 +3122,8 @@ sc_cpe_socket_cleanup(InitState) -> %% *** Wait for start order part *** #{desc => "await start (from tester)", cmd => fun(State) -> - receive - {start, Tester} when is_pid(Tester) -> - {ok, State#{tester => Tester}} - end + Tester = ev_await_start(), + {ok, State#{tester => Tester}} end}, %% *** Init part *** @@ -3150,29 +3143,23 @@ sc_cpe_socket_cleanup(InitState) -> ERROR end end}, - #{desc => "announce ready", + #{desc => "announce ready (init)", cmd => fun(#{tester := Tester, sock := Sock} = _State) -> - Tester ! {ready, self(), Sock}, + ev_ready(Tester, init, Sock), ok end}, %% *** The actual test *** - %% We intentially leave the socket "as is", no explicit close + %% We *intentially* leave the socket "as is", no explicit close #{desc => "await terminate (from tester)", cmd => fun(#{tester := Tester} = State) -> - receive - {'DOWN', _, process, Tester, Reason} -> - ee("Unexpected DOWN regarding tester ~p: " - "~n ~p", [Tester, Reason]), - {error, {unexpected_exit, tester}}; - {terminate, Tester} -> - {ok, maps:remove(tester, State)} + case ev_await_terminate(Tester, tester) of + ok -> + {ok, maps:remove(tester, State)}; + {error, _} = ERROR -> + ERROR end end}, - %% #{desc => "enable (otp) debug", - %% cmd => fun(#{sock := Sock} = _State) -> - %% ok = socket:setopt(Sock, otp, debug, true) - %% end}, %% *** We are done *** #{desc => "finish", @@ -3191,24 +3178,18 @@ sc_cpe_socket_cleanup(InitState) -> end}, #{desc => "order (owner) start", cmd => fun(#{owner := Pid} = _State) -> - Pid ! {start, self()}, + ev_start(Pid), ok end}, #{desc => "await (owner) ready", - cmd => fun(#{owner := Owner} = State) -> - receive - {'DOWN', _, process, Owner, Reason} -> - ee("Unexpected DOWN regarding owner ~p: " - "~n ~p", [Owner, Reason]), - {error, {unexpected_exit, owner}}; - {ready, Owner, Sock} -> - {ok, State#{sock => Sock}} - end + cmd => fun(#{owner := Pid} = State) -> + {ok, Sock} = ev_await_ready(Pid, owner, init), + {ok, State#{sock => Sock}} end}, #{desc => "verify owner as controlling-process", - cmd => fun(#{owner := Owner, sock := Sock} = _State) -> + cmd => fun(#{owner := Pid, sock := Sock} = _State) -> case socket:getopt(Sock, otp, controlling_process) of - {ok, Owner} -> + {ok, Pid} -> ok; {ok, Other} -> {error, {unexpected_owner, Other}}; @@ -3218,21 +3199,21 @@ sc_cpe_socket_cleanup(InitState) -> end}, #{desc => "order (owner) terminate", cmd => fun(#{owner := Pid} = _State) -> - Pid ! {terminate, self()}, + ev_terminate(Pid), ok end}, #{desc => "await (owner) termination", - cmd => fun(#{owner := Owner} = _State) -> - receive - {'DOWN', _, process, Owner, _} -> - ok - end + cmd => fun(#{owner := Pid} = _State) -> + ev_await_termination(Pid), + ok end}, + %% The reason we get closed, is that as long as there is a ref to + %% the resource (socket), then it will not be garbage collected. #{desc => "verify no socket (closed)", - cmd => fun(#{owner := Owner, sock := Sock} = _State) -> + cmd => fun(#{owner := Pid, sock := Sock} = _State) -> case socket:getopt(Sock, otp, controlling_process) of - {ok, Pid} -> - {error, {unexpected_success, Owner, Pid}}; + {ok, OtherPid} -> + {error, {unexpected_success, Pid, OtherPid}}; {error, closed} -> ok; {error, Reason} -> @@ -3259,6 +3240,7 @@ sc_cpe_socket_cleanup(InitState) -> ok = await_evaluator_finish([Owner, Tester]). + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% This test case is intended to test what happens when a socket is %% locally closed while a process is calling the recv function. -- cgit v1.2.3 From 01e58eb25f2bf4bb09e5cc23e000ef196a326ce2 Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Thu, 25 Oct 2018 11:32:31 +0200 Subject: [socket-nif|test] Updated socket (local) close test case(s) Updated the socket (local) close test case(s) sc_lc_receive_response_tcp with the new evaluator interface functions. OTP-14831 --- erts/emulator/test/socket_SUITE.erl | 486 ++++++++++++++---------------------- 1 file changed, 184 insertions(+), 302 deletions(-) diff --git a/erts/emulator/test/socket_SUITE.erl b/erts/emulator/test/socket_SUITE.erl index 6a5b77dd4e..cb681e8fa1 100644 --- a/erts/emulator/test/socket_SUITE.erl +++ b/erts/emulator/test/socket_SUITE.erl @@ -665,7 +665,7 @@ api_b_send_and_recv_tcp(InitState) -> %% The actual test #{desc => "await continue (accept)", cmd => fun(#{tester := Tester}) -> - ok = ev_await_continue(Tester, tester, accept) + ev_await_continue(Tester, tester, accept) end}, #{desc => "await connection", cmd => fun(#{lsock := LSock} = State) -> @@ -699,7 +699,7 @@ api_b_send_and_recv_tcp(InitState) -> end}, #{desc => "await continue (with send reply)", cmd => fun(#{tester := Tester}) -> - ok = ev_await_continue(Tester, tester, send_reply) + ev_await_continue(Tester, tester, send_reply) end}, #{desc => "send reply", cmd => fun(#{csock := Sock, send := Send}) -> @@ -1322,12 +1322,7 @@ api_opt_simple_otp_controlling_process() -> end}, #{desc => "await continue (owner)", cmd => fun(#{tester := Tester} = _State) -> - %% receive - %% {continue, Tester} -> - %% ok - %% end - ev_await_continue(Tester, tester, owner), - ok + ev_await_continue(Tester, tester, owner) end}, #{desc => "verify self as controlling-process", cmd => fun(#{sock := Sock} = _State) -> @@ -2045,8 +2040,7 @@ api_to_maccept_tcp(InitState) -> %% *** The actual test *** #{desc => "await continue (accept)", cmd => fun(#{tester := Tester} = _State) -> - ev_await_continue(Tester, tester, accept), - ok + ev_await_continue(Tester, tester, accept) end}, #{desc => "attempt to accept (without success)", cmd => fun(#{lsock := LSock, timeout := To} = State) -> @@ -2124,8 +2118,7 @@ api_to_maccept_tcp(InitState) -> %% *** The actual test part *** #{desc => "await continue (accept)", cmd => fun(#{tester := Tester} = _State) -> - ev_await_continue(Tester, tester, accept), - ok + ev_await_continue(Tester, tester, accept) end}, #{desc => "attempt to accept (without success)", cmd => fun(#{lsock := LSock, timeout := To} = State) -> @@ -2565,7 +2558,7 @@ api_to_receive_tcp(InitState) -> %% *** The actual test *** #{desc => "await continue (accept and recv)", cmd => fun(#{tester := Tester}) -> - ok = ev_await_continue(Tester, tester, accept_recv) + ev_await_continue(Tester, tester, accept_recv) end}, #{desc => "attempt accept", cmd => fun(#{lsock := LSock} = State) -> @@ -2686,7 +2679,7 @@ api_to_receive_tcp(InitState) -> %% *** The actual test *** #{desc => "await continue (with connect)", cmd => fun(#{tester := Tester} = _State) -> - ok = ev_await_continue(Tester, tester, connect) + ev_await_continue(Tester, tester, connect) end}, #{desc => "connect", cmd => fun(#{sock := Sock, server_sa := SSA}) -> @@ -3297,18 +3290,16 @@ sc_lc_recv_response_tcp6(_Config) when is_list(_Config) -> %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% sc_lc_receive_response_tcp(InitState) -> - %% This is the server that accepts connections. + %% This (acceptor) is the server that accepts connections. %% But it is also suppose to close the connection socket, - %% and trigger the read failure for the handler process. + %% and trigger the read failure (=closed) for the handler process. AcceptorSeq = [ %% *** Wait for start order part *** #{desc => "await start (from tester)", cmd => fun(State) -> - receive - {start, Tester} when is_pid(Tester) -> - {ok, State#{tester => Tester}} - end + Tester = ev_await_start(), + {ok, State#{tester => Tester}} end}, #{desc => "monitor tester", cmd => fun(#{tester := Tester} = _State) -> @@ -3321,7 +3312,7 @@ sc_lc_receive_response_tcp(InitState) -> cmd => fun(#{domain := Domain} = State) -> LAddr = which_local_addr(Domain), LSA = #{family => Domain, addr => LAddr}, - {ok, State#{lsa => LSA}} + {ok, State#{local_sa => LSA}} end}, #{desc => "create (listen) socket", cmd => fun(#{domain := Domain, @@ -3335,7 +3326,7 @@ sc_lc_receive_response_tcp(InitState) -> end end}, #{desc => "bind to local address", - cmd => fun(#{lsock := LSock, lsa := LSA} = State) -> + cmd => fun(#{lsock := LSock, local_sa := LSA} = State) -> case socket:bind(LSock, LSA) of {ok, Port} -> {ok, State#{lport => Port}}; @@ -3349,25 +3340,20 @@ sc_lc_receive_response_tcp(InitState) -> end}, #{desc => "announce ready (init)", cmd => fun(#{tester := Tester, lport := Port}) -> - Tester ! {ready, self(), Port}, + ev_ready(Tester, init, Port), ok end}, %% The actual test - #{desc => "await continue (connection)", + #{desc => "await continue (accept)", cmd => fun(#{tester := Tester} = State) -> - receive - {'DOWN', _, process, Tester, Reason} -> - ee("Unexpected DOWN regarding tester ~p: " - "~n ~p", [Tester, Reason]), - {error, {unexpected_exit, tester}}; - {continue, Tester, {H1, H2, H3}} -> - {ok, State#{handler1 => H1, - handler2 => H2, - handler3 => H3}} - end + {ok, {H1, H2, H3}} = + ev_await_continue(Tester, tester, accept), + {ok, State#{handler1 => H1, + handler2 => H2, + handler3 => H3}} end}, - #{desc => "await connection", + #{desc => "await accept", cmd => fun(#{lsock := LSock} = State) -> case socket:accept(LSock) of {ok, Sock} -> @@ -3377,69 +3363,66 @@ sc_lc_receive_response_tcp(InitState) -> ERROR end end}, + #{desc => "announce ready (accept)", + cmd => fun(#{tester := Tester}) -> + ev_ready(Tester, accept), + ok + end}, #{desc => "transfer connection to handler 1", cmd => fun(#{handler1 := Handler, csock := Sock}) -> - %% ok = socket:setopt(Sock, - %% otp, controlling_process, - %% Handler), - Handler ! {connection, Sock}, + ev_continue(Handler, transfer, Sock), ok end}, #{desc => "transfer connection to handler 2", cmd => fun(#{handler2 := Handler, csock := Sock}) -> - %% ok = socket:setopt(Sock, - %% otp, controlling_process, - %% Handler), - Handler ! {connection, Sock}, + ev_continue(Handler, transfer, Sock), ok end}, #{desc => "transfer connection to handler 3", cmd => fun(#{handler3 := Handler, csock := Sock}) -> - %% ok = socket:setopt(Sock, - %% otp, controlling_process, - %% Handler), - Handler ! {connection, Sock}, - ok - end}, - #{desc => "announce ready (connection)", - cmd => fun(#{tester := Tester}) -> - Tester ! {ready, self()}, + ev_continue(Handler, transfer, Sock), ok end}, #{desc => "await continue (close)", cmd => fun(#{tester := Tester} = _State) -> - receive - {'DOWN', _, process, Tester, Reason} -> - ee("Unexpected DOWN regarding tester ~p: " - "~n ~p", [Tester, Reason]), - {error, {unexpected_exit, tester}}; - {continue, Tester} -> - ok - end + ev_await_continue(Tester, tester, close), + ok end}, #{desc => "close the connection socket", - cmd => fun(#{csock := Sock}) -> - socket:close(Sock) + cmd => fun(#{csock := Sock} = State) -> + case socket:close(Sock) of + ok -> + {ok, maps:remove(csock, State)}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (close)", + cmd => fun(#{tester := Tester}) -> + ev_ready(Tester, close), + ok end}, + %% *** Terminate *** #{desc => "await terminate", cmd => fun(#{tester := Tester} = State) -> - receive - {'DOWN', _, process, Tester, Reason} -> - ee("Unexpected DOWN regarding tester ~p: " - "~n ~p", [Tester, Reason]), - {error, {unexpected_exit, tester}}; - {terminate, Tester} -> - {ok, maps:remove(tester, State)} + case ev_await_terminate(Tester, tester) of + ok -> + {ok, maps:remove(tester, State)}; + {error, _} = ERROR -> + ERROR end end}, - #{desc => "socket cleanup", + #{desc => "close socket", cmd => fun(#{lsock := Sock} = State) -> - ok = socket:close(Sock), - State1 = maps:remove(csock, State), - State2 = maps:remove(lsock, State1), - State3 = maps:remove(lport, State2), - {ok, State3} + case socket:close(Sock) of + ok -> + State1 = maps:remove(lsock, State), + State2 = maps:remove(lport, State1), + {ok, State2}; + {error, _} = ERROR -> + ERROR + end end}, %% *** We are done *** @@ -3455,45 +3438,44 @@ sc_lc_receive_response_tcp(InitState) -> %% *** Wait for start order part *** #{desc => "await start (from tester)", cmd => fun(State) -> - receive - {start, Tester} when is_pid(Tester) -> - {ok, State#{tester => Tester}} - end + {Tester, Acceptor} = ev_await_start(), + {ok, State#{tester => Tester, + acceptor => Acceptor}} end}, - #{desc => "monitor server", + #{desc => "monitor tester", cmd => fun(#{tester := Tester} = _State) -> _MRef = erlang:monitor(process, Tester), ok end}, + #{desc => "monitor acceptor", + cmd => fun(#{acceptor := Acceptor} = _State) -> + _MRef = erlang:monitor(process, Acceptor), + ok + end}, #{desc => "announce ready (init)", cmd => fun(#{tester := Tester}) -> - Tester ! {ready, self()}, + ev_ready(Tester, init), ok end}, %% The actual test - #{desc => "await connection socket", - cmd => fun(#{tester := Tester} = State) -> - receive - {'DOWN', _, process, Tester, Reason} -> - ee("Unexpected DOWN regarding tester ~p: " - "~n ~p", [Tester, Reason]), - {error, {unexpected_exit, tester}}; - {connection, Sock} -> - {ok, State#{sock => Sock}} - end + #{desc => "await continue (transfer)", + cmd => fun(#{acceptor := Pid} = State) -> + {ok, Sock} = + ev_await_continue(Pid, acceptor, transfer), + {ok, State#{sock => Sock}} end}, - #{desc => "announce ready (connection)", + #{desc => "announce ready (transfer)", cmd => fun(#{tester := Tester}) -> - Tester ! {ready, self()}, + ev_ready(Tester, transfer), ok end}, - #{desc => "attempt recv", + #{desc => "attempt recv (=> closed)", cmd => fun(#{sock := Sock, recv := Recv} = State) -> case Recv(Sock) of {ok, _Data} -> ee("Unexpected data received"), - {error, unexpected_data}; + {error, unexpected_success}; {error, closed} -> ei("received expected 'closed' result"), State1 = maps:remove(sock, State), @@ -3504,20 +3486,20 @@ sc_lc_receive_response_tcp(InitState) -> ERROR end end}, - #{desc => "announce ready (close)", + #{desc => "announce ready (recv closed)", cmd => fun(#{tester := Tester}) -> - Tester ! {ready, self()}, + ev_ready(Tester, recv_closed), ok end}, + + %% *** Terminate *** #{desc => "await terminate", - cmd => fun(#{tester := Tester} = _State) -> - receive - {'DOWN', _, process, Tester, Reason} -> - ee("Unexpected DOWN regarding tester ~p: " - "~n ~p", [Tester, Reason]), - {error, {unexpected_exit, tester}}; - {terminate, Tester} -> - ok + cmd => fun(#{tester := Tester} = State) -> + case ev_await_terminate(Tester, tester) of + ok -> + {ok, maps:remove(tester, State)}; + {error, _} = ERROR -> + ERROR end end}, @@ -3535,18 +3517,21 @@ sc_lc_receive_response_tcp(InitState) -> %% *** Wait for start order part *** #{desc => "await start (from tester)", cmd => fun(State) -> - receive - {start, Tester} when is_pid(Tester) -> - {ok, State#{tester => Tester}} - end + Tester = ev_await_start(), + {ok, State#{tester => Tester}} + end}, + #{desc => "monitor tester", + cmd => fun(#{tester := Tester} = _State) -> + _MRef = erlang:monitor(process, Tester), + ok end}, - %% Init + %% *** Init part *** #{desc => "which local address", cmd => fun(#{domain := Domain} = State) -> LAddr = which_local_addr(Domain), LSA = #{family => Domain, addr => LAddr}, - {ok, State#{lsa => LSA}} + {ok, State#{local_sa => LSA}} end}, #{desc => "create socket", cmd => fun(#{domain := Domain, @@ -3560,7 +3545,7 @@ sc_lc_receive_response_tcp(InitState) -> end end}, #{desc => "bind socket to local address", - cmd => fun(#{sock := Sock, lsa := LSA} = _State) -> + cmd => fun(#{sock := Sock, local_sa := LSA} = _State) -> case socket:bind(Sock, LSA) of {ok, _} -> ok; @@ -3568,50 +3553,38 @@ sc_lc_receive_response_tcp(InitState) -> ERROR end end}, - #{desc => "announce ready", + #{desc => "announce ready (init)", cmd => fun(#{tester := Tester} = _State) -> - Tester ! {ready, self()}, + ev_ready(Tester, init), ok end}, %% The actual test - #{desc => "await continue", - cmd => fun(#{tester := Tester} = State) -> - receive - {'DOWN', _, process, Tester, Reason} -> - ee("Unexpected DOWN regarding tester ~p: " - "~n ~p", [Tester, Reason]), - {error, {unexpected_exit, tester, Reason}}; - {continue, Tester, Port} -> - {ok, State#{lport => Port}} - end - end}, - #{desc => "sleep", - cmd => fun(_) -> - ?SLEEP(?SECS(1)), - ok + #{desc => "await continue (connect)", + cmd => fun(#{tester := Tester, local_sa := LSA} = State) -> + {ok, Port} = ev_await_continue(Tester, tester, connect), + ServerSA = LSA#{port => Port}, + {ok, State#{server_sa => ServerSA}} end}, #{desc => "connect to server", - cmd => fun(#{sock := Sock, lsa := LSA, lport := LPort}) -> - socket:connect(Sock, LSA#{port => LPort}) + cmd => fun(#{sock := Sock, server_sa := ServerSA}) -> + socket:connect(Sock, ServerSA) end}, - #{desc => "announce ready (connection)", + #{desc => "announce ready (connect)", cmd => fun(#{tester := Tester} = _State) -> - Tester ! {ready, self()}, + ev_ready(Tester, connect), ok end}, - %% Cleaning up + %% *** Terminate *** #{desc => "await terminate (from tester)", cmd => fun(#{tester := Tester} = State) -> - receive - {'DOWN', _, process, Tester, Reason} -> - ee("Unexpected DOWN regarding tester ~p: " - "~n ~p", [Tester, Reason]), - {error, {unexpected_exit, tester}}; - {terminate, Tester} -> - {ok, maps:remove(tester, State)} - end + case ev_await_terminate(Tester, tester) of + ok -> + {ok, maps:remove(tester, State)}; + {error, _} = ERROR -> + ERROR + end end}, #{desc => "close socket", cmd => fun(#{sock := Sock} = State) -> @@ -3658,262 +3631,171 @@ sc_lc_receive_response_tcp(InitState) -> %% Start the acceptor #{desc => "order acceptor start", cmd => fun(#{acceptor := Pid} = _State) -> - Pid ! {start, self()}, + ev_start(Pid), ok end}, #{desc => "await acceptor ready (init)", cmd => fun(#{acceptor := Pid} = State) -> - receive - {'DOWN', _, process, Pid, Reason} -> - ee("Unexpected DOWN regarding acceptor ~p: " - "~n ~p", [Pid, Reason]), - {error, {unexpected_exit, acceptor}}; - {ready, Pid, Port} -> - {ok, State#{lport => Port}} - end + {ok, Port} = ev_await_ready(Pid, acceptor, init), + {ok, State#{lport => Port}} end}, %% Start the handler(s) #{desc => "order handler 1 start", - cmd => fun(#{handler1 := Pid} = _State) -> - Pid ! {start, self()}, + cmd => fun(#{acceptor := Acceptor, handler1 := Pid} = _State) -> + ev_start(Pid, Acceptor), ok end}, #{desc => "await handler 1 ready (init)", cmd => fun(#{handler1 := Pid} = _State) -> - receive - {'DOWN', _, process, Pid, Reason} -> - ee("Unexpected DOWN regarding handler 1 ~p: " - "~n ~p", [Pid, Reason]), - {error, {unexpected_exit, handler1}}; - {ready, Pid} -> - ok - end + ev_await_ready(Pid, handler1, init) end}, #{desc => "order handler 2 start", - cmd => fun(#{handler2 := Pid} = _State) -> - Pid ! {start, self()}, + cmd => fun(#{acceptor := Acceptor, handler2 := Pid} = _State) -> + ev_start(Pid, Acceptor), ok end}, #{desc => "await handler 2 ready (init)", cmd => fun(#{handler2 := Pid} = _State) -> - receive - {'DOWN', _, process, Pid, Reason} -> - ee("Unexpected DOWN regarding handler 2 ~p: " - "~n ~p", [Pid, Reason]), - {error, {unexpected_exit, handler2}}; - {ready, Pid} -> - ok - end + ev_await_ready(Pid, handler2, init) end}, #{desc => "order handler 3 start", - cmd => fun(#{handler3 := Pid} = _State) -> - Pid ! {start, self()}, + cmd => fun(#{acceptor := Acceptor, handler3 := Pid} = _State) -> + ev_start(Pid, Acceptor), ok end}, #{desc => "await handler 3 ready (init)", cmd => fun(#{handler3 := Pid} = _State) -> - receive - {'DOWN', _, process, Pid, Reason} -> - ee("Unexpected DOWN regarding handler 3 ~p: " - "~n ~p", [Pid, Reason]), - {error, {unexpected_exit, handler3}}; - {ready, Pid} -> - ok - end + ev_await_ready(Pid, handler3, init) end}, %% Start the client #{desc => "order client start", cmd => fun(#{client := Pid} = _State) -> - Pid ! {start, self()}, + ev_start(Pid), ok end}, #{desc => "await client ready (init)", cmd => fun(#{client := Pid} = _State) -> - receive - {'DOWN', _, process, Pid, Reason} -> - ee("Unexpected DOWN regarding cient ~p: " - "~n ~p", [Pid, Reason]), - {error, {unexpected_exit, client}}; - {ready, Pid} -> - ok - end + ev_await_ready(Pid, client, init) end}, %% The actual test - #{desc => "order acceptor to continue", + #{desc => "order acceptor to continue (accept)", cmd => fun(#{acceptor := Pid, handler1 := H1, handler2 := H2, handler3 := H3} = _State) -> - Pid ! {continue, self(), {H1, H2, H3}}, + ev_continue(Pid, accept, {H1, H2, H3}), + ok + end}, + #{desc => "sleep", + cmd => fun(_) -> + ?SLEEP(?SECS(1)), ok end}, - #{desc => "order client to continue", + #{desc => "order client to continue (connect)", cmd => fun(#{client := Pid, lport := Port} = _State) -> - Pid ! {continue, self(), Port}, + ev_continue(Pid, connect, Port), ok end}, - #{desc => "await acceptor ready (connection)", + #{desc => "await acceptor ready (accept)", cmd => fun(#{acceptor := Pid} = _State) -> - receive - {'DOWN', _, process, Pid, Reason} -> - ee("Unexpected DOWN regarding acceptor ~p: " - "~n ~p", [Pid, Reason]), - {error, {unexpected_exit, acceptor}}; - {ready, Pid} -> - ok - end + ok = ev_await_ready(Pid, acceptor, accept) end}, - #{desc => "await client ready (connection)", + #{desc => "await client ready (connect)", cmd => fun(#{client := Pid} = _State) -> - receive - {'DOWN', _, process, Pid, Reason} -> - ee("Unexpected DOWN regarding client ~p: " - "~n ~p", [Pid, Reason]), - {error, {unexpected_exit, client}}; - {ready, Pid} -> - ok - end + ok = ev_await_ready(Pid, client, connect) end}, - #{desc => "await handler 1 ready (connection)", + #{desc => "await handler 1 ready (transfer)", cmd => fun(#{handler1 := Pid} = _State) -> - receive - {'DOWN', _, process, Pid, Reason} -> - ee("Unexpected DOWN regarding handler 1 ~p: " - "~n ~p", [Pid, Reason]), - {error, {unexpected_exit, handler1}}; - {ready, Pid} -> - ok - end + ok = ev_await_ready(Pid, handler1, transfer) end}, - #{desc => "await handler 2 ready (connection)", + #{desc => "await handler 2 ready (transfer)", cmd => fun(#{handler2 := Pid} = _State) -> - receive - {'DOWN', _, process, Pid, Reason} -> - ee("Unexpected DOWN regarding handler 2 ~p: " - "~n ~p", [Pid, Reason]), - {error, {unexpected_exit, handler2}}; - {ready, Pid} -> - ok - end + ok = ev_await_ready(Pid, handler2, transfer) end}, - #{desc => "await handler 3 ready (connection)", + #{desc => "await handler 3 ready (transfer)", cmd => fun(#{handler3 := Pid} = _State) -> - receive - {'DOWN', _, process, Pid, Reason} -> - ee("Unexpected DOWN regarding handler 3 ~p: " - "~n ~p", [Pid, Reason]), - {error, {unexpected_exit, handler3}}; - {ready, Pid} -> - ok - end + ok = ev_await_ready(Pid, handler3, transfer) end}, #{desc => "sleep", cmd => fun(_State) -> ?SLEEP(?SECS(1)), ok end}, - #{desc => "order acceptor to continue (close)", + #{desc => "order acceptor to continue (close connection socket)", cmd => fun(#{acceptor := Pid} = _State) -> - Pid ! {continue, self()}, + ev_continue(Pid, close), ok end}, - #{desc => "await handler 1 ready (close)", + #{desc => "await acceptor ready (close)", + cmd => fun(#{acceptor := Pid} = _State) -> + ok = ev_await_ready(Pid, acceptor, close) + end}, + #{desc => "await handler 1 ready (recv closed)", cmd => fun(#{handler1 := Pid} = _State) -> - receive - {'DOWN', _, process, Pid, Reason} -> - ee("Unexpected DOWN regarding handler 1 ~p: " - "~n ~p", [Pid, Reason]), - {error, {unexpected_exit, handler1}}; - {ready, Pid} -> - ok - end + ok = ev_await_ready(Pid, handler1, recv_closed) end}, - #{desc => "await handler 2 ready (close)", + #{desc => "await handler 2 ready (recv closed)", cmd => fun(#{handler2 := Pid} = _State) -> - receive - {'DOWN', _, process, Pid, Reason} -> - ee("Unexpected DOWN regarding handler 2 ~p: " - "~n ~p", [Pid, Reason]), - {error, {unexpected_exit, handler2}}; - {ready, Pid} -> - ok - end + ok = ev_await_ready(Pid, handler2, recv_closed) end}, - #{desc => "await handler 3 ready (close)", + #{desc => "await handler 3 ready (recv closed)", cmd => fun(#{handler3 := Pid} = _State) -> - receive - {'DOWN', _, process, Pid, Reason} -> - ee("Unexpected DOWN regarding handler 2 ~p: " - "~n ~p", [Pid, Reason]), - {error, {unexpected_exit, handler2}}; - {ready, Pid} -> - ok - end + ok = ev_await_ready(Pid, handler3, recv_closed) end}, %% Terminations + #{desc => "order client to terminate", + cmd => fun(#{client := Pid} = _State) -> + ev_terminate(Pid), + ok + end}, + #{desc => "await client termination", + cmd => fun(#{client := Pid} = State) -> + ev_await_termination(Pid), + {ok, maps:remove(client, State)} + end}, #{desc => "order handler 1 to terminate", cmd => fun(#{handler1 := Pid} = _State) -> - Pid ! {terminate, self()}, + ev_terminate(Pid), ok end}, #{desc => "await handler 1 termination", cmd => fun(#{handler1 := Pid} = State) -> - receive - {'DOWN', _, process, Pid, _} -> - {ok, maps:remove(handler1, State)} - end + ev_await_termination(Pid), + {ok, maps:remove(handler1, State)} end}, #{desc => "order handler 2 to terminate", cmd => fun(#{handler2 := Pid} = _State) -> - Pid ! {terminate, self()}, + ev_terminate(Pid), ok end}, #{desc => "await handler 2 termination", cmd => fun(#{handler2 := Pid} = State) -> - receive - {'DOWN', _, process, Pid, _} -> - {ok, maps:remove(handler2, State)} - end + ev_await_termination(Pid), + {ok, maps:remove(handler2, State)} end}, #{desc => "order handler 3 to terminate", cmd => fun(#{handler3 := Pid} = _State) -> - Pid ! {terminate, self()}, + ev_terminate(Pid), ok end}, #{desc => "await handler 3 termination", cmd => fun(#{handler3 := Pid} = State) -> - receive - {'DOWN', _, process, Pid, _} -> - {ok, maps:remove(handler3, State)} - end - end}, - #{desc => "order client to terminate", - cmd => fun(#{client := Pid} = _State) -> - Pid ! {terminate, self()}, - ok - end}, - #{desc => "await client termination", - cmd => fun(#{client := Pid} = State) -> - receive - {'DOWN', _, process, Pid, _} -> - {ok, maps:remove(client, State)} - end + ev_await_termination(Pid), + {ok, maps:remove(handler3, State)} end}, #{desc => "order acceptor to terminate", cmd => fun(#{acceptor := Pid} = _State) -> - Pid ! {terminate, self()}, + ev_terminate(Pid), ok end}, #{desc => "await acceptor termination", cmd => fun(#{acceptor := Pid} = State) -> - receive - {'DOWN', _, process, Pid, _} -> - {ok, maps:remove(acceptor, State)} - end + ev_await_termination(Pid), + {ok, maps:remove(acceptor, State)} end}, @@ -5978,8 +5860,8 @@ ev_start(Pid, Extra) -> ev_continue(Pid, Slogan) -> ev_announce(Pid, continue, Slogan). -%% ev_continue(Pid, Slogan, Extra) -> -%% ev_announce(Pid, continue, Slogan, Extra). +ev_continue(Pid, Slogan, Extra) -> + ev_announce(Pid, continue, Slogan, Extra). ev_ready(Pid, Slogan) -> ev_announce(Pid, ready, Slogan). -- cgit v1.2.3 From 398c80c6d86b88cc9b171540893f063ad4a7768d Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Thu, 25 Oct 2018 15:53:16 +0200 Subject: [socket-nif|test] Updated socket (local) close test case(s) Updated the socket (local) close test case(s) sc_lc_receive_response_udp with the new evaluator interface functions. OTP-14831 --- erts/emulator/test/socket_SUITE.erl | 239 +++++++++++------------------------- 1 file changed, 71 insertions(+), 168 deletions(-) diff --git a/erts/emulator/test/socket_SUITE.erl b/erts/emulator/test/socket_SUITE.erl index cb681e8fa1..39dbb4c131 100644 --- a/erts/emulator/test/socket_SUITE.erl +++ b/erts/emulator/test/socket_SUITE.erl @@ -3891,10 +3891,9 @@ sc_lc_receive_response_udp(InitState) -> %% *** Init part *** #{desc => "await start", cmd => fun(State) -> - receive - {start, Tester} -> - {ok, State#{tester => Tester}} - end + Tester = ev_await_start(), + ei("Tester: ~p", [Tester]), + {ok, State#{tester => Tester}} end}, #{desc => "monitor tester", cmd => fun(#{tester := Tester} = _State) -> @@ -3905,7 +3904,7 @@ sc_lc_receive_response_udp(InitState) -> cmd => fun(#{domain := Domain} = State) -> LAddr = which_local_addr(Domain), LSA = #{family => Domain, addr => LAddr}, - {ok, State#{lsa => LSA}} + {ok, State#{local_sa => LSA}} end}, #{desc => "open socket", cmd => fun(#{domain := Domain} = State) -> @@ -3914,29 +3913,23 @@ sc_lc_receive_response_udp(InitState) -> {ok, State#{sock => Sock, sa => SA}} end}, #{desc => "bind socket", - cmd => fun(#{sock := Sock, lsa := LSA}) -> + cmd => fun(#{sock := Sock, local_sa := LSA}) -> sock_bind(Sock, LSA), ok end}, #{desc => "announce ready (init)", cmd => fun(#{tester := Tester, sock := Sock}) -> - Tester ! {ready, self(), Sock}, + ev_ready(Tester, init, Sock), ok end}, %% The actual test - #{desc => "await continue (receive)", + #{desc => "await continue (recv, with timeout)", cmd => fun(#{tester := Tester} = State) -> - receive - {'DOWN', _, process, Tester, Reason} -> - ee("Unexpected DOWN regarding tester ~p: " - "~n ~p", [Tester, Reason]), - {error, {unexpected_exit, tester}}; - {continue, Tester, Timeout} -> - {ok, State#{timeout => Timeout}} - end + {ok, Timeout} = ev_await_continue(Tester, tester, recv), + {ok, State#{timeout => Timeout}} end}, - #{desc => "receive with timeout", + #{desc => "receive, with timeout", cmd => fun(#{sock := Sock, recv := Recv, timeout := Timeout}) -> case Recv(Sock, Timeout) of {error, timeout} -> @@ -3947,21 +3940,14 @@ sc_lc_receive_response_udp(InitState) -> ERROR end end}, - #{desc => "announce ready (timeout)", + #{desc => "announce ready (recv, with timeout)", cmd => fun(#{tester := Tester}) -> - Tester ! {ready, self()}, + ev_ready(Tester, recv), ok end}, #{desc => "await continue (close)", cmd => fun(#{tester := Tester} = _State) -> - receive - {'DOWN', _, process, Tester, Reason} -> - ee("Unexpected DOWN regarding tester ~p: " - "~n ~p", [Tester, Reason]), - {error, {unexpected_exit, tester}}; - {continue, Tester} -> - ok - end + ok = ev_await_continue(Tester, tester, close) end}, #{desc => "close socket", cmd => fun(#{sock := Sock} = State) -> @@ -3972,22 +3958,20 @@ sc_lc_receive_response_udp(InitState) -> ERROR end end}, - #{desc => "announce ready (closed)", + #{desc => "announce ready (close)", cmd => fun(#{tester := Tester}) -> - Tester ! {ready, self()}, + ev_ready(Tester, close), ok end}, %% Termination #{desc => "await terminate (from tester)", cmd => fun(#{tester := Tester} = State) -> - receive - {'DOWN', _, process, Tester, Reason} -> - ee("Unexpected DOWN regarding tester ~p: " - "~n ~p", [Tester, Reason]), - {error, {unexpected_exit, tester}}; - {terminate, Tester} -> - {ok, maps:remove(tester, State)} + case ev_await_terminate(Tester, terminate) of + ok -> + {ok, maps:remove(tester, State)}; + {error, _} = ERROR -> + ERROR end end}, @@ -4003,10 +3987,8 @@ sc_lc_receive_response_udp(InitState) -> %% *** Init part *** #{desc => "await start", cmd => fun(State) -> - receive - {start, Tester, Sock} -> - {ok, State#{tester => Tester, sock => Sock}} - end + {Tester, Sock} = ev_await_start(), + {ok, State#{tester => Tester, sock => Sock}} end}, #{desc => "monitor tester", cmd => fun(#{tester := Tester} = _State) -> @@ -4015,25 +3997,18 @@ sc_lc_receive_response_udp(InitState) -> end}, #{desc => "announce ready (init)", cmd => fun(#{tester := Tester}) -> - Tester ! {ready, self()}, + ev_ready(Tester, init), ok end}, %% The actual test - #{desc => "await continue (receive)", + #{desc => "await continue (recv)", cmd => fun(#{tester := Tester} = _State) -> - receive - {'DOWN', _, process, Tester, Reason} -> - ee("Unexpected DOWN regarding tester ~p: " - "~n ~p", [Tester, Reason]), - {error, {unexpected_exit, tester}}; - {continue, Tester} -> - ok - end + ok = ev_await_continue(Tester, tester, recv) + end}, #{desc => "receive", cmd => fun(#{sock := Sock, recv := Recv} = State) -> - %% ok = socket:setopt(Sock, otp, debug, true), case Recv(Sock, infinity) of {error, closed} -> {ok, maps:remove(sock, State)}; @@ -4043,22 +4018,20 @@ sc_lc_receive_response_udp(InitState) -> ERROR end end}, - #{desc => "announce ready (closed)", + #{desc => "announce ready (recv closed)", cmd => fun(#{tester := Tester}) -> - Tester ! {ready, self()}, + ev_ready(Tester, recv_closed), ok end}, %% Termination #{desc => "await terminate (from tester)", cmd => fun(#{tester := Tester} = State) -> - receive - {'DOWN', _, process, Tester, Reason} -> - ee("Unexpected DOWN regarding tester ~p: " - "~n ~p", [Tester, Reason]), - {error, {unexpected_exit, tester}}; - {terminate, Tester} -> - {ok, maps:remove(tester, State)} + case ev_await_terminate(Tester, tester) of + ok -> + {ok, maps:remove(tester, State)}; + {error, _} = ERROR -> + ERROR end end}, @@ -4097,73 +4070,46 @@ sc_lc_receive_response_udp(InitState) -> %% Start the primary server #{desc => "order 'primary server' start", cmd => fun(#{prim_server := Pid} = _State) -> - Pid ! {start, self()}, + ev_start(Pid), ok end}, #{desc => "await 'primary server' ready (init)", cmd => fun(#{prim_server := Pid} = State) -> - receive - {'DOWN', _, process, Pid, Reason} -> - ee("Unexpected DOWN regarding prim-server ~p: " - "~n ~p", [Pid, Reason]), - {error, {unexpected_exit, prim_server}}; - {ready, Pid, Sock} -> - {ok, State#{sock => Sock}} - end + {ok, Sock} = ev_await_ready(Pid, prim_server, init), + {ok, State#{sock => Sock}} end}, %% Start the secondary server 1 #{desc => "order 'secondary server 1' start", cmd => fun(#{sec_server1 := Pid, sock := Sock} = _State) -> - Pid ! {start, self(), Sock}, + ev_start(Pid, Sock), ok end}, #{desc => "await 'secondary server 1' ready (init)", cmd => fun(#{sec_server1 := Pid} = _State) -> - receive - {'DOWN', _, process, Pid, Reason} -> - ee("Unexpected DOWN regarding sec-server ~p: " - "~n ~p", [Pid, Reason]), - {error, {unexpected_exit, sec_server1}}; - {ready, Pid} -> - ok - end + ok = ev_await_ready(Pid, sec_server1, init) end}, %% Start the secondary server 2 #{desc => "order 'secondary server 2' start", cmd => fun(#{sec_server2 := Pid, sock := Sock} = _State) -> - Pid ! {start, self(), Sock}, + ev_start(Pid, Sock), ok end}, #{desc => "await 'secondary server 2' ready (init)", cmd => fun(#{sec_server2 := Pid} = _State) -> - receive - {'DOWN', _, process, Pid, Reason} -> - ee("Unexpected DOWN regarding sec-server-2 ~p: " - "~n ~p", [Pid, Reason]), - {error, {unexpected_exit, sec_server2}}; - {ready, Pid} -> - ok - end + ok = ev_await_ready(Pid, sec_server2, init) end}, %% Start the secondary server 3 #{desc => "order 'secondary server 3' start", cmd => fun(#{sec_server3 := Pid, sock := Sock} = _State) -> - Pid ! {start, self(), Sock}, + ev_start(Pid, Sock), ok end}, #{desc => "await 'secondary server 3' ready (init)", cmd => fun(#{sec_server3 := Pid} = _State) -> - receive - {'DOWN', _, process, Pid, Reason} -> - ee("Unexpected DOWN regarding sec-server-3 ~p: " - "~n ~p", [Pid, Reason]), - {error, {unexpected_exit, sec_server3}}; - {ready, Pid} -> - ok - end + ok = ev_await_ready(Pid, sec_server3, init) end}, @@ -4174,9 +4120,9 @@ sc_lc_receive_response_udp(InitState) -> %% to close the socket, which should cause the all the secondary %% server to return with error-closed. - #{desc => "order 'secondary server 1' to continue", + #{desc => "order 'secondary server 1' to continue (recv)", cmd => fun(#{sec_server1 := Pid} = _State) -> - Pid ! {continue, self()}, + ev_continue(Pid, recv), ok end}, #{desc => "sleep", @@ -4184,9 +4130,9 @@ sc_lc_receive_response_udp(InitState) -> ?SLEEP(?SECS(1)), ok end}, - #{desc => "order 'secondary server 2' to continue", + #{desc => "order 'secondary server 2' to continue (recv)", cmd => fun(#{sec_server2 := Pid} = _State) -> - Pid ! {continue, self()}, + ev_continue(Pid, recv), ok end}, #{desc => "sleep", @@ -4194,9 +4140,9 @@ sc_lc_receive_response_udp(InitState) -> ?SLEEP(?SECS(1)), ok end}, - #{desc => "order 'secondary server 3' to continue", + #{desc => "order 'secondary server 3' to continue (recv)", cmd => fun(#{sec_server3 := Pid} = _State) -> - Pid ! {continue, self()}, + ev_continue(Pid, recv), ok end}, #{desc => "sleep", @@ -4204,121 +4150,78 @@ sc_lc_receive_response_udp(InitState) -> ?SLEEP(?SECS(1)), ok end}, - #{desc => "order 'primary server' to continue (recvfrom)", + #{desc => "order 'primary server' to continue (recv, with timeout)", cmd => fun(#{prim_server := Pid} = _State) -> - Pid ! {continue, self(), ?SECS(5)}, + ev_continue(Pid, recv, ?SECS(5)), ok end}, - #{desc => "await 'primary server' ready (timeout)", + #{desc => "await 'primary server' ready (recv, with timeout)", cmd => fun(#{prim_server := Pid} = _State) -> - receive - {'DOWN', _, process, Pid, Reason} -> - ee("Unexpected DOWN regarding prim-server ~p: " - "~n ~p", [Pid, Reason]), - {error, {unexpected_exit, prim_server}}; - {ready, Pid} -> - ok - end + ev_await_ready(Pid, prim_server, recv) end}, #{desc => "order 'primary server' to continue (close)", cmd => fun(#{prim_server := Pid} = _State) -> - Pid ! {continue, self()}, + %% Pid ! {continue, self()}, + ev_continue(Pid, close), ok end}, - #{desc => "await 'primary server' ready (closed)", + #{desc => "await 'primary server' ready (close)", cmd => fun(#{prim_server := Pid} = _State) -> - receive - {'DOWN', _, process, Pid, Reason} -> - ee("Unexpected DOWN regarding prim-server ~p: " - "~n ~p", [Pid, Reason]), - {error, {unexpected_exit, prim_server}}; - {ready, Pid} -> - ok - end + ev_await_ready(Pid, prim_server, close) end}, #{desc => "await 'secondary server 1' ready (closed)", cmd => fun(#{sec_server1 := Pid} = _State) -> - receive - {'DOWN', _, process, Pid, Reason} -> - ee("Unexpected DOWN regarding sec-server-1 ~p: " - "~n ~p", [Pid, Reason]), - {error, {unexpected_exit, sec_server1}}; - {ready, Pid} -> - ok - end + ev_await_ready(Pid, sec_server1, recv_closed) end}, #{desc => "await 'secondary server 2' ready (closed)", cmd => fun(#{sec_server2 := Pid} = _State) -> - receive - {'DOWN', _, process, Pid, Reason} -> - ee("Unexpected DOWN regarding sec-server-2 ~p: " - "~n ~p", [Pid, Reason]), - {error, {unexpected_exit, sec_server2}}; - {ready, Pid} -> - ok - end + ev_await_ready(Pid, sec_server2, recv_closed) end}, #{desc => "await 'secondary server 3' ready (closed)", cmd => fun(#{sec_server3 := Pid} = _State) -> - receive - {'DOWN', _, process, Pid, Reason} -> - ee("Unexpected DOWN regarding sec-server-3 ~p: " - "~n ~p", [Pid, Reason]), - {error, {unexpected_exit, sec_server3}}; - {ready, Pid} -> - ok - end + ev_await_ready(Pid, sec_server3, recv_closed) end}, - %% Terminations #{desc => "order 'secondary server 3' to terminate", cmd => fun(#{sec_server3 := Pid} = _State) -> - Pid ! {terminate, self()}, + ev_terminate(Pid), ok end}, #{desc => "await 'secondary server 3' termination", cmd => fun(#{sec_server3 := Pid} = State) -> - receive - {'DOWN', _, process, Pid, _} -> - {ok, maps:remove(sec_server3, State)} - end + ev_await_termination(Pid), + {ok, maps:remove(sec_server3, State)} end}, #{desc => "order 'secondary server 2' to terminate", cmd => fun(#{sec_server2 := Pid} = _State) -> - Pid ! {terminate, self()}, + ev_terminate(Pid), ok end}, #{desc => "await 'secondary server 2' termination", cmd => fun(#{sec_server2 := Pid} = State) -> - receive - {'DOWN', _, process, Pid, _} -> - {ok, maps:remove(sec_server2, State)} - end + ev_await_termination(Pid), + {ok, maps:remove(sec_server2, State)} end}, #{desc => "order 'secondary server 1' to terminate", cmd => fun(#{sec_server1 := Pid} = _State) -> - Pid ! {terminate, self()}, + ev_terminate(Pid), ok end}, #{desc => "await 'secondary server 1' termination", cmd => fun(#{sec_server1 := Pid} = State) -> - receive - {'DOWN', _, process, Pid, _} -> - {ok, maps:remove(sec_server1, State)} - end + ev_await_termination(Pid), + {ok, maps:remove(sec_server1, State)} end}, #{desc => "order 'primary server' to terminate", cmd => fun(#{prim_server := Pid} = _State) -> - Pid ! {terminate, self()}, + ev_terminate(Pid), ok end}, #{desc => "await 'primary server' termination", cmd => fun(#{prim_server := Pid} = State) -> - receive - {'DOWN', _, process, Pid, _} -> - {ok, maps:remove(prim_server, State)} - end + ev_await_termination(Pid), + {ok, maps:remove(prim_server, State)} end}, -- cgit v1.2.3 From 995b651734d9841072f5b1546fa1a39371500001 Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Thu, 25 Oct 2018 17:27:54 +0200 Subject: [socket-nif|test] Updated socket (remote) close test case(s) Updated the socket (remote) close test case(s) sc_rc_receive_response_tcp with the new evaluator interface functions. OTP-14831 --- erts/emulator/test/socket_SUITE.erl | 289 +++++++++++++++--------------------- 1 file changed, 117 insertions(+), 172 deletions(-) diff --git a/erts/emulator/test/socket_SUITE.erl b/erts/emulator/test/socket_SUITE.erl index 39dbb4c131..c9b40f9ad8 100644 --- a/erts/emulator/test/socket_SUITE.erl +++ b/erts/emulator/test/socket_SUITE.erl @@ -3892,7 +3892,6 @@ sc_lc_receive_response_udp(InitState) -> #{desc => "await start", cmd => fun(State) -> Tester = ev_await_start(), - ei("Tester: ~p", [Tester]), {ok, State#{tester => Tester}} end}, #{desc => "monitor tester", @@ -4405,10 +4404,8 @@ sc_rc_receive_response_tcp(InitState) -> %% *** Init part *** #{desc => "await start", cmd => fun(State) -> - receive - {start, Tester} -> - {ok, State#{tester => Tester}} - end + Tester = ev_await_start(), + {ok, State#{tester => Tester}} end}, #{desc => "monitor tester", cmd => fun(#{tester := Tester} = _State) -> @@ -4419,7 +4416,7 @@ sc_rc_receive_response_tcp(InitState) -> cmd => fun(#{domain := Domain} = State) -> LAddr = which_local_addr(Domain), LSA = #{family => Domain, addr => LAddr}, - {ok, State#{lsa => LSA}} + {ok, State#{local_sa => LSA}} end}, #{desc => "create listen socket", cmd => fun(#{domain := Domain} = State) -> @@ -4431,7 +4428,7 @@ sc_rc_receive_response_tcp(InitState) -> end end}, #{desc => "bind to local address", - cmd => fun(#{lsock := LSock, lsa := LSA} = State) -> + cmd => fun(#{lsock := LSock, local_sa := LSA} = State) -> case socket:bind(LSock, LSA) of {ok, Port} -> {ok, State#{lport => Port}}; @@ -4444,23 +4441,16 @@ sc_rc_receive_response_tcp(InitState) -> socket:listen(LSock) end}, #{desc => "announce ready (init)", - cmd => fun(#{tester := Tester, lsa := LSA, lport := Port}) -> - SA = LSA#{port => Port}, - Tester ! {ready, self(), SA}, + cmd => fun(#{tester := Tester, local_sa := LSA, lport := Port}) -> + ServerSA = LSA#{port => Port}, + ev_ready(Tester, init, ServerSA), ok end}, %% The actual test #{desc => "await continue (accept)", cmd => fun(#{tester := Tester} = _State) -> - receive - {continue, Tester} -> - ok; - {'DOWN', _, process, Tester, Reason} -> - ee("Unexpected DOWN regarding tester ~p: " - "~n ~p", [Tester, Reason]), - {error, {unexpected_exit, tester}} - end + ev_await_continue(Tester, tester, accept) end}, #{desc => "accept", cmd => fun(#{lsock := LSock} = State) -> @@ -4471,11 +4461,15 @@ sc_rc_receive_response_tcp(InitState) -> ERROR end end}, - #{desc => "announce ready (accepted)", + #{desc => "announce ready (accept)", cmd => fun(#{tester := Tester}) -> - Tester ! {ready, self()}, + ev_ready(Tester, accept), ok end}, + #{desc => "await continue (recv)", + cmd => fun(#{tester := Tester} = _State) -> + ev_await_continue(Tester, tester, recv) + end}, #{desc => "receive", cmd => fun(#{csock := Sock, recv := Recv} = State) -> case Recv(Sock) of @@ -4487,27 +4481,30 @@ sc_rc_receive_response_tcp(InitState) -> ERROR end end}, - #{desc => "announce ready (closed)", + #{desc => "announce ready (recv closed)", cmd => fun(#{tester := Tester}) -> - Tester ! {ready, self()}, + ev_ready(Tester, recv_closed), ok end}, %% Termination #{desc => "await terminate (from tester)", cmd => fun(#{tester := Tester} = State) -> - receive - {'DOWN', _, process, Tester, Reason} -> - ee("Unexpected DOWN regarding tester ~p: " - "~n ~p", [Tester, Reason]), - {error, {unexpected_exit, tester}}; - {terminate, Tester} -> - {ok, maps:remove(tester, State)} + case ev_await_terminate(Tester, tester) of + ok -> + {ok, maps:remove(tester, State)}; + {error, _} = ERROR -> + ERROR end end}, #{desc => "close listen socket", - cmd => fun(#{lsock := LSock} = _State) -> - socket:close(LSock) + cmd => fun(#{lsock := LSock} = State) -> + case socket:close(LSock) of + ok -> + {ok, maps:remove(lsock, State)}; + {error, _} = ERROR -> + ERROR + end end}, %% *** We are done *** @@ -4522,35 +4519,33 @@ sc_rc_receive_response_tcp(InitState) -> %% *** Init part *** #{desc => "await start", cmd => fun(State) -> - receive - {start, Tester, Node, ServerSA} -> - {ok, State#{tester => Tester, - node => Node, - server_sa => ServerSA}} - end + {Tester, {Node, ServerSA}} = ev_await_start(), + {ok, State#{tester => Tester, + node => Node, + server_sa => ServerSA}} end}, #{desc => "monitor tester", cmd => fun(#{tester := Tester} = _State) -> _MRef = erlang:monitor(process, Tester), ok end}, - #{desc => "start client process on client node", + #{desc => "start remote client on client node", cmd => fun(#{node := Node} = State) -> Pid = sc_rc_tcp_client_start(Node), ei("client ~p started", [Pid]), {ok, State#{client => Pid}} end}, - #{desc => "monitor client process", + #{desc => "monitor remote client", cmd => fun(#{client := Pid}) -> _MRef = erlang:monitor(process, Pid), ok end}, - #{desc => "order client process to start", + #{desc => "order remote client to start", cmd => fun(#{client := Client, server_sa := ServerSA}) -> Client ! {start, self(), ServerSA}, ok end}, - #{desc => "await client process ready", + #{desc => "await remote client ready", cmd => fun(#{tester := Tester, client := Client} = _State) -> receive @@ -4568,7 +4563,7 @@ sc_rc_receive_response_tcp(InitState) -> end}, #{desc => "announce ready (init)", cmd => fun(#{tester := Tester}) -> - Tester ! {ready, self()}, + ev_ready(Tester, init), ok end}, @@ -4576,25 +4571,16 @@ sc_rc_receive_response_tcp(InitState) -> #{desc => "await continue (connect)", cmd => fun(#{tester := Tester, client := Client} = _State) -> - receive - {continue, Tester} -> - ok; - {'DOWN', _, process, Tester, Reason} -> - ee("Unexpected DOWN regarding tester ~p: " - "~n ~p", [Tester, Reason]), - {error, {unexpected_exit, tester}}; - {'DOWN', _, process, Client, Reason} -> - ee("Unexpected DOWN regarding client ~p: " - "~n ~p", [Client, Reason]), - {error, {unexpected_exit, client}} - end + ev_await_continue(Tester, tester, connect, + [{rclient, Client}]), + ok end}, - #{desc => "order client process to continue (connect)", + #{desc => "order remote client to continue (connect)", cmd => fun(#{client := Client}) -> Client ! {continue, self()}, ok end}, - #{desc => "await client process ready (connected)", + #{desc => "await client process ready (connect)", cmd => fun(#{tester := Tester, client := Client} = _State) -> receive @@ -4612,31 +4598,22 @@ sc_rc_receive_response_tcp(InitState) -> end}, #{desc => "announce ready (connected)", cmd => fun(#{tester := Tester}) -> - Tester ! {ready, self()}, + ev_ready(Tester, connect), ok end}, #{desc => "await continue (close)", cmd => fun(#{tester := Tester, client := Client} = _State) -> - receive - {continue, Tester} -> - ok; - {'DOWN', _, process, Tester, Reason} -> - ee("Unexpected DOWN regarding tester ~p: " - "~n ~p", [Tester, Reason]), - {error, {unexpected_exit, tester}}; - {'DOWN', _, process, Client, Reason} -> - ee("Unexpected DOWN regarding client ~p: " - "~n ~p", [Client, Reason]), - {error, {unexpected_exit, client}} - end + ev_await_continue(Tester, tester, close, + [{rclient, Client}]), + ok end}, - #{desc => "order client process to close", + #{desc => "order remote client to close", cmd => fun(#{client := Client}) -> Client ! {continue, self()}, ok end}, - #{desc => "await client process ready (closed)", + #{desc => "await remote client ready (closed)", cmd => fun(#{tester := Tester, client := Client} = _State) -> receive @@ -4652,34 +4629,24 @@ sc_rc_receive_response_tcp(InitState) -> {error, {unexpected_exit, client}} end end}, - #{desc => "announce ready (closed)", + #{desc => "announce ready (close)", cmd => fun(#{tester := Tester}) -> - Tester ! {ready, self()}, + ev_ready(Tester, close), ok end}, %% Termination #{desc => "await terminate (from tester)", cmd => fun(#{tester := Tester, client := Client} = State) -> - receive - {'DOWN', _, process, Tester, Reason} -> - ee("Unexpected DOWN regarding tester ~p: " - "~n ~p", [Tester, Reason]), - {error, {unexpected_exit, tester}}; - {'DOWN', _, process, Client, Reason} -> - ee("Unexpected DOWN regarding client ~p: " - "~n ~p", [Client, Reason]), - {error, {unexpected_exit, client}}; - {terminate, Tester} -> - {ok, maps:remove(tester, State)} - end + ev_await_terminate(Tester, tester, [{rclient, Client}]), + {ok, maps:remove(tester, State)} end}, - #{desc => "kill client process", + #{desc => "kill remote client", cmd => fun(#{client := Client}) -> Client ! {terminate, self(), normal}, ok end}, - #{desc => "await client termination", + #{desc => "await remote client termination", cmd => fun(#{client := Client} = State) -> receive {'DOWN', _, process, Client, _} -> @@ -4726,19 +4693,13 @@ sc_rc_receive_response_tcp(InitState) -> %% Start the server #{desc => "order server start", cmd => fun(#{server := Pid} = _State) -> - Pid ! {start, self()}, + ev_start(Pid), ok end}, #{desc => "await server ready (init)", cmd => fun(#{server := Pid} = State) -> - receive - {ready, Pid, ServerSA} -> - {ok, State#{server_sa => ServerSA}}; - {'DOWN', _, process, Pid, Reason} -> - ee("Unexpected DOWN regarding server ~p: " - "~n ~p", [Pid, Reason]), - {error, {unexpected_exit, server}} - end + {ok, ServerSA} = ev_await_ready(Pid, server, init), + {ok, State#{server_sa => ServerSA}} end}, %% Start the client @@ -4746,25 +4707,18 @@ sc_rc_receive_response_tcp(InitState) -> cmd => fun(#{client := Pid, client_node := Node, server_sa := ServerSA} = _State) -> - Pid ! {start, self(), Node, ServerSA}, + ev_start(Pid, {Node, ServerSA}), ok end}, #{desc => "await client ready (init)", cmd => fun(#{client := Pid} = _State) -> - receive - {ready, Pid} -> - ok; - {'DOWN', _, process, Pid, Reason} -> - ee("Unexpected DOWN regarding client ~p: " - "~n ~p", [Pid, Reason]), - {error, {unexpected_exit, client}} - end + ok = ev_await_ready(Pid, client, init) end}, %% The actual test - #{desc => "order server accept", + #{desc => "order server continue (accept)", cmd => fun(#{server := Pid} = _State) -> - Pid ! {continue, self()}, + ev_continue(Pid, accept), ok end}, #{desc => "sleep", @@ -4772,110 +4726,77 @@ sc_rc_receive_response_tcp(InitState) -> ?SLEEP(?SECS(1)), ok end}, - #{desc => "order client connect", + #{desc => "order client continue (connect)", cmd => fun(#{client := Pid} = _State) -> - Pid ! {continue, self()}, + ev_continue(Pid, connect), ok end}, - #{desc => "await client ready (connected)", + #{desc => "await server ready (accept)", cmd => fun(#{server := Server, client := Client} = _State) -> - receive - {ready, Client} -> - ok; - {'DOWN', _, process, Client, Reason} -> - ee("Unexpected DOWN regarding client ~p: " - "~n ~p", [Client, Reason]), - {error, {unexpected_exit, client}}; - {'DOWN', _, process, Server, Reason} -> - ee("Unexpected DOWN regarding client ~p: " - "~n ~p", [Server, Reason]), - {error, {unexpected_exit, client}} - end + ev_await_ready(Server, server, accept, + [{client, Client}]), + ok end}, - #{desc => "await server ready (accepted)", + #{desc => "await client ready (connect)", cmd => fun(#{server := Server, client := Client} = _State) -> - receive - {ready, Server} -> - ok; - {'DOWN', _, process, Client, Reason} -> - ee("Unexpected DOWN regarding client ~p: " - "~n ~p", [Client, Reason]), - {error, {unexpected_exit, client}}; - {'DOWN', _, process, Server, Reason} -> - ee("Unexpected DOWN regarding client ~p: " - "~n ~p", [Server, Reason]), - {error, {unexpected_exit, client}} - end + ev_await_ready(Client, client, connect, + [{server, Server}]), + ok + end}, + #{desc => "order server continue (recv)", + cmd => fun(#{server := Pid} = _State) -> + ev_continue(Pid, recv), + ok end}, #{desc => "sleep", cmd => fun(_) -> ?SLEEP(?SECS(1)), ok end}, - #{desc => "order client close", + #{desc => "order client continue (close)", cmd => fun(#{client := Pid} = _State) -> - Pid ! {continue, self()}, + ev_continue(Pid, close), ok end}, - #{desc => "await client ready (closed)", + #{desc => "await client ready (close)", cmd => fun(#{server := Server, client := Client} = _State) -> - receive - {ready, Client} -> - ok; - {'DOWN', _, process, Client, Reason} -> - ee("Unexpected DOWN regarding client ~p: " - "~n ~p", [Client, Reason]), - {error, {unexpected_exit, client}}; - {'DOWN', _, process, Server, Reason} -> - ee("Unexpected DOWN regarding client ~p: " - "~n ~p", [Server, Reason]), - {error, {unexpected_exit, client}} - end + ev_await_ready(Client, client, close, + [{server, Server}]), + ok end}, #{desc => "await server ready (closed)", cmd => fun(#{server := Server, client := Client} = _State) -> - receive - {ready, Server} -> - ok; - {'DOWN', _, process, Client, Reason} -> - ee("Unexpected DOWN regarding client ~p: " - "~n ~p", [Client, Reason]), - {error, {unexpected_exit, client}}; - {'DOWN', _, process, Server, Reason} -> - ee("Unexpected DOWN regarding client ~p: " - "~n ~p", [Server, Reason]), - {error, {unexpected_exit, client}} - end + ev_await_ready(Server, server, recv_closed, + [{client, Client}]), + ok end}, %% Terminations #{desc => "order client to terminate", cmd => fun(#{client := Pid} = _State) -> - Pid ! {terminate, self()}, + ev_terminate(Pid), ok end}, #{desc => "await client termination", cmd => fun(#{client := Pid} = State) -> - receive - {'DOWN', _, process, Pid, _} -> - {ok, maps:remove(client, State)} - end + ev_await_termination(Pid), + State1 = maps:remove(client, State), + {ok, State1} end}, #{desc => "order server to terminate", cmd => fun(#{server := Pid} = _State) -> - Pid ! {terminate, self()}, + ev_terminate(Pid), ok end}, #{desc => "await server termination", cmd => fun(#{server := Pid} = State) -> - receive - {'DOWN', _, process, Pid, _} -> - {ok, maps:remove(server, State)} - end + ev_await_termination(Pid), + State1 = maps:remove(server, State), + {ok, State1} end}, #{desc => "stop client node", cmd => fun(#{client_node := Node} = _State) -> @@ -4915,10 +4836,22 @@ sc_rc_receive_response_tcp(InitState) -> start_node(Host, NodeName) -> + case do_start_node(Host, NodeName) of + {ok, _} = OK -> + OK; + {error, already_started, Node} -> + stop_node(Node), % We don't know in what state it is, so kill i t + do_start_node(Host, NodeName); + ERROR -> + ERROR + end. + +do_start_node(Host, NodeName) -> Dir = filename:dirname(code:which(?MODULE)), Flags = "-pa " ++ Dir, Opts = [{monitor_master, true}, {erl_flags, Flags}], ct_slave:start(Host, NodeName, Opts). + stop_node(Node) -> case ct_slave:stop(Node) of @@ -5847,6 +5780,10 @@ ev_await(Pid, Name, Tag, Slogan, Pids) -> {error, _} = ERROR -> ERROR end + after infinity -> + ei("ev_await -> timeout for msg from ~p (~w): " + "~n Messages: ~p", [Pid, Name, pi(messages)]), + ev_await(Pid, Name, Tag, Slogan, Pids) end. ev_await_check_down(DownPid, DownReason, Pids) -> @@ -5860,6 +5797,14 @@ ev_await_check_down(DownPid, DownReason, Pids) -> end. +pi(Item) -> + pi(self(), Item). + +pi(Pid, Item) -> + {Item, Info} = process_info(Pid, Item), + Info. + + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% sock_open(Domain, Type, Proto) -> -- cgit v1.2.3 From c6fbc166cbb91033b9feb7362e874cad035917f6 Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Thu, 25 Oct 2018 18:10:48 +0200 Subject: [socket-nif|test] Updated socket (local) close test case(s) Updated the socket (local) close test case(s) sc_lc_acceptor_response_tcp with the new evaluator interface functions. OTP-14831 --- erts/emulator/test/socket_SUITE.erl | 238 ++++++++++++------------------------ 1 file changed, 75 insertions(+), 163 deletions(-) diff --git a/erts/emulator/test/socket_SUITE.erl b/erts/emulator/test/socket_SUITE.erl index c9b40f9ad8..f399d097ad 100644 --- a/erts/emulator/test/socket_SUITE.erl +++ b/erts/emulator/test/socket_SUITE.erl @@ -5069,10 +5069,8 @@ sc_lc_acceptor_response_tcp(InitState) -> %% *** Wait for start order part *** #{desc => "await start (from tester)", cmd => fun(State) -> - receive - {start, Tester} when is_pid(Tester) -> - {ok, State#{tester => Tester}} - end + Tester = ev_await_start(), + {ok, State#{tester => Tester}} end}, #{desc => "monitor tester", cmd => fun(#{tester := Tester} = _State) -> @@ -5113,21 +5111,16 @@ sc_lc_acceptor_response_tcp(InitState) -> end}, #{desc => "announce ready (init)", cmd => fun(#{tester := Tester, sock := Sock} = _State) -> - Tester ! {ready, self(), Sock}, + ev_ready(Tester, init, Sock), ok end}, %% The actual test #{desc => "await continue (accept)", cmd => fun(#{tester := Tester} = State) -> - receive - {'DOWN', _, process, Tester, Reason} -> - ee("Unexpected DOWN regarding tester ~p: " - "~n ~p", [Tester, Reason]), - {error, {unexpected_exit, tester}}; - {continue, Tester, Timeout} -> - {ok, State#{timeout => Timeout}} - end + {ok, Timeout} = + ev_await_continue(Tester, tester, accept), + {ok, State#{timeout => Timeout}} end}, #{desc => "await connection", cmd => fun(#{sock := Sock, timeout := Timeout} = _State) -> @@ -5145,18 +5138,12 @@ sc_lc_acceptor_response_tcp(InitState) -> #{desc => "announce ready (accept timeout)", cmd => fun(#{tester := Tester}) -> Tester ! {ready, self()}, + ev_ready(Tester, accept_timeout), ok end}, #{desc => "await continue (close)", cmd => fun(#{tester := Tester} = _State) -> - receive - {'DOWN', _, process, Tester, Reason} -> - ee("Unexpected DOWN regarding tester ~p: " - "~n ~p", [Tester, Reason]), - {error, {unexpected_exit, tester}}; - {continue, Tester} -> - ok - end + ok = ev_await_continue(Tester, tester, close) end}, #{desc => "close socket", cmd => fun(#{sock := Sock} = State) -> @@ -5167,24 +5154,22 @@ sc_lc_acceptor_response_tcp(InitState) -> ERROR end end}, - #{desc => "announce ready (closed)", + #{desc => "announce ready (close)", cmd => fun(#{tester := Tester}) -> - Tester ! {ready, self()}, + ev_ready(Tester, close), ok end}, % Termination #{desc => "await terminate", cmd => fun(#{tester := Tester} = State) -> - receive - {'DOWN', _, process, Tester, Reason} -> - ee("Unexpected DOWN regarding tester ~p: " - "~n ~p", [Tester, Reason]), - {error, {unexpected_exit, tester}}; - {terminate, Tester} -> - {ok, maps:remove(tester, State)} + case ev_await_terminate(Tester, tester) of + ok -> + {ok, maps:remove(tester, State)}; + {error, _} = ERROR -> + ERROR end - end}, + end}, %% *** We are done *** #{desc => "finish", @@ -5198,10 +5183,8 @@ sc_lc_acceptor_response_tcp(InitState) -> %% *** Init part *** #{desc => "await start", cmd => fun(State) -> - receive - {start, Tester, Sock} -> - {ok, State#{tester => Tester, sock => Sock}} - end + {Tester, Sock} = ev_await_start(), + {ok, State#{tester => Tester, sock => Sock}} end}, #{desc => "monitor tester", cmd => fun(#{tester := Tester} = _State) -> @@ -5210,25 +5193,18 @@ sc_lc_acceptor_response_tcp(InitState) -> end}, #{desc => "announce ready (init)", cmd => fun(#{tester := Tester}) -> - Tester ! {ready, self()}, + ev_ready(Tester, init), ok end}, %% The actual test #{desc => "await continue (accept)", cmd => fun(#{tester := Tester} = _State) -> - receive - {'DOWN', _, process, Tester, Reason} -> - ee("Unexpected DOWN regarding tester ~p: " - "~n ~p", [Tester, Reason]), - {error, {unexpected_exit, tester}}; - {continue, Tester} -> - ok - end + ev_await_continue(Tester, tester, accept), + ok end}, #{desc => "accept", cmd => fun(#{sock := Sock} = State) -> - %% ok = socket:setopt(Sock, otp, debug, true), case socket:accept(Sock) of {error, closed} -> {ok, maps:remove(sock, State)}; @@ -5238,22 +5214,20 @@ sc_lc_acceptor_response_tcp(InitState) -> ERROR end end}, - #{desc => "announce ready (closed)", + #{desc => "announce ready (accept closed)", cmd => fun(#{tester := Tester}) -> - Tester ! {ready, self()}, + ev_ready(Tester, accept_closed), ok end}, %% Termination #{desc => "await terminate (from tester)", cmd => fun(#{tester := Tester} = State) -> - receive - {'DOWN', _, process, Tester, Reason} -> - ee("Unexpected DOWN regarding tester ~p: " - "~n ~p", [Tester, Reason]), - {error, {unexpected_exit, tester}}; - {terminate, Tester} -> - {ok, maps:remove(tester, State)} + case ev_await_terminate(Tester, tester) of + ok -> + {ok, maps:remove(tester, State)}; + {error, _} = ERROR -> + ERROR end end}, @@ -5291,73 +5265,49 @@ sc_lc_acceptor_response_tcp(InitState) -> %% Start the primary server #{desc => "order 'primary acceptor' start", cmd => fun(#{prim_acc := Pid} = _State) -> - Pid ! {start, self()}, + ev_start(Pid), ok end}, #{desc => "await 'primary acceptor' ready (init)", cmd => fun(#{prim_acc := Pid} = State) -> - receive - {'DOWN', _, process, Pid, Reason} -> - ee("Unexpected DOWN regarding prim-acc ~p: " - "~n ~p", [Pid, Reason]), - {error, {unexpected_exit, prim_acc}}; - {ready, Pid, Sock} -> - {ok, State#{sock => Sock}} - end + {ok, Sock} = ev_await_ready(Pid, prim_acc, init), + {ok, State#{sock => Sock}} end}, %% Start the secondary acceptor 1 #{desc => "order 'secondary acceptor 1' start", cmd => fun(#{sec_acc1 := Pid, sock := Sock} = _State) -> - Pid ! {start, self(), Sock}, + ev_start(Pid, Sock), ok end}, #{desc => "await 'secondary acceptor 1' ready (init)", cmd => fun(#{sec_acc1 := Pid} = _State) -> - receive - {'DOWN', _, process, Pid, Reason} -> - ee("Unexpected DOWN regarding sec-acc-1 ~p: " - "~n ~p", [Pid, Reason]), - {error, {unexpected_exit, sec_acc1}}; - {ready, Pid} -> - ok - end + ev_await_ready(Pid, sec_acc1, init), + ok end}, %% Start the secondary acceptor 2 #{desc => "order 'secondary acceptor 2' start", cmd => fun(#{sec_acc2 := Pid, sock := Sock} = _State) -> - Pid ! {start, self(), Sock}, + ev_start(Pid, Sock), ok end}, #{desc => "await 'secondary acceptor 2' ready (init)", cmd => fun(#{sec_acc2 := Pid} = _State) -> - receive - {'DOWN', _, process, Pid, Reason} -> - ee("Unexpected DOWN regarding sec-acc-2 ~p: " - "~n ~p", [Pid, Reason]), - {error, {unexpected_exit, sec_acc2}}; - {ready, Pid} -> - ok - end + ev_await_ready(Pid, sec_acc2, init), + ok end}, %% Start the secondary acceptor 3 #{desc => "order 'secondary acceptor 3' start", cmd => fun(#{sec_acc3 := Pid, sock := Sock} = _State) -> - Pid ! {start, self(), Sock}, + ev_start(Pid, Sock), ok end}, #{desc => "await 'secondary acceptor 3' ready (init)", cmd => fun(#{sec_acc3 := Pid} = _State) -> - receive - {'DOWN', _, process, Pid, Reason} -> - ee("Unexpected DOWN regarding sec-acc-3 ~p: " - "~n ~p", [Pid, Reason]), - {error, {unexpected_exit, sec_acc3}}; - {ready, Pid} -> - ok - end + ev_await_ready(Pid, sec_acc3, init), + ok end}, @@ -5368,9 +5318,9 @@ sc_lc_acceptor_response_tcp(InitState) -> %% to close the socket, which should cause the all the secondary %% server to return with error-closed. - #{desc => "order 'secondary acceptor 1' to continue", + #{desc => "order 'secondary acceptor 1' to continue (accept)", cmd => fun(#{sec_acc1 := Pid} = _State) -> - Pid ! {continue, self()}, + ev_continue(Pid, accept), ok end}, #{desc => "sleep", @@ -5378,9 +5328,9 @@ sc_lc_acceptor_response_tcp(InitState) -> ?SLEEP(?SECS(1)), ok end}, - #{desc => "order 'secondary acceptor 2' to continue", + #{desc => "order 'secondary acceptor 2' to continue (accept)", cmd => fun(#{sec_acc2 := Pid} = _State) -> - Pid ! {continue, self()}, + ev_continue(Pid, accept), ok end}, #{desc => "sleep", @@ -5388,9 +5338,9 @@ sc_lc_acceptor_response_tcp(InitState) -> ?SLEEP(?SECS(1)), ok end}, - #{desc => "order 'secondary acceptor 3' to continue", + #{desc => "order 'secondary acceptor 3' to continue (accept)", cmd => fun(#{sec_acc3 := Pid} = _State) -> - Pid ! {continue, self()}, + ev_continue(Pid, accept), ok end}, #{desc => "sleep", @@ -5400,119 +5350,81 @@ sc_lc_acceptor_response_tcp(InitState) -> end}, #{desc => "order 'primary acceptor' to continue", cmd => fun(#{prim_acc := Pid} = _State) -> - Pid ! {continue, self(), ?SECS(5)}, + ev_continue(Pid, accept, ?SECS(5)), ok end}, - #{desc => "await 'primary acceptor' ready (timeout)", + #{desc => "await 'primary acceptor' ready (accept timeout)", cmd => fun(#{prim_acc := Pid} = _State) -> - receive - {'DOWN', _, process, Pid, Reason} -> - ee("Unexpected DOWN regarding prim-acc ~p: " - "~n ~p", [Pid, Reason]), - {error, {unexpected_exit, prim_acc}}; - {ready, Pid} -> - ok - end + ev_await_ready(Pid, prim_acc, accept_timeout), + ok end}, #{desc => "order 'primary acceptor' to continue (close)", cmd => fun(#{prim_acc := Pid} = _State) -> - Pid ! {continue, self()}, + ev_continue(Pid, close), ok end}, - #{desc => "await 'primary acceptor' ready (closed)", + #{desc => "await 'primary acceptor' ready (close)", cmd => fun(#{prim_acc := Pid} = _State) -> - receive - {'DOWN', _, process, Pid, Reason} -> - ee("Unexpected DOWN regarding prim-acc ~p: " - "~n ~p", [Pid, Reason]), - {error, {unexpected_exit, prim_acc}}; - {ready, Pid} -> - ok - end + ev_await_ready(Pid, prim_acc, close), + ok end}, - #{desc => "await 'secondary acceptor 1' ready (closed)", + #{desc => "await 'secondary acceptor 1' ready (accept closed)", cmd => fun(#{sec_acc1 := Pid} = _State) -> - receive - {'DOWN', _, process, Pid, Reason} -> - ee("Unexpected DOWN regarding sec-acc-1 ~p: " - "~n ~p", [Pid, Reason]), - {error, {unexpected_exit, sec_acc1}}; - {ready, Pid} -> - ok - end + ev_await_ready(Pid, sec_acc1, accept_closed), + ok end}, - #{desc => "await 'secondary acceptor 2' ready (closed)", + #{desc => "await 'secondary acceptor 2' ready (accept closed)", cmd => fun(#{sec_acc2 := Pid} = _State) -> - receive - {'DOWN', _, process, Pid, Reason} -> - ee("Unexpected DOWN regarding sec-acc-2 ~p: " - "~n ~p", [Pid, Reason]), - {error, {unexpected_exit, sec_acc2}}; - {ready, Pid} -> - ok - end + ev_await_ready(Pid, sec_acc2, accept_closed), + ok end}, - #{desc => "await 'secondary acceptor 3' ready (closed)", + #{desc => "await 'secondary acceptor 3' ready (accept closed)", cmd => fun(#{sec_acc3 := Pid} = _State) -> - receive - {'DOWN', _, process, Pid, Reason} -> - ee("Unexpected DOWN regarding sec-acc-3 ~p: " - "~n ~p", [Pid, Reason]), - {error, {unexpected_exit, sec_acc3}}; - {ready, Pid} -> - ok - end + ev_await_ready(Pid, sec_acc3, accept_closed), + ok end}, %% Terminations #{desc => "order 'secondary acceptor 3' to terminate", cmd => fun(#{sec_acc3 := Pid} = _State) -> - Pid ! {terminate, self()}, + ev_terminate(Pid), ok end}, #{desc => "await 'secondary acceptor 3' termination", cmd => fun(#{sec_acc3 := Pid} = State) -> - receive - {'DOWN', _, process, Pid, _} -> - {ok, maps:remove(sec_acc3, State)} - end + ev_await_termination(Pid), + {ok, maps:remove(sec_acc3, State)} end}, #{desc => "order 'secondary acceptor 2' to terminate", cmd => fun(#{sec_acc2 := Pid} = _State) -> - Pid ! {terminate, self()}, + ev_terminate(Pid), ok end}, #{desc => "await 'secondary acceptor 2' termination", cmd => fun(#{sec_acc2 := Pid} = State) -> - receive - {'DOWN', _, process, Pid, _} -> - {ok, maps:remove(sec_acc2, State)} - end + ev_await_termination(Pid), + {ok, maps:remove(sec_acc2, State)} end}, #{desc => "order 'secondary acceptor 1' to terminate", cmd => fun(#{sec_acc1 := Pid} = _State) -> - Pid ! {terminate, self()}, + ev_terminate(Pid), ok end}, #{desc => "await 'secondary acceptor 1' termination", cmd => fun(#{sec_acc1 := Pid} = State) -> - receive - {'DOWN', _, process, Pid, _} -> - {ok, maps:remove(sec_acc1, State)} - end + ev_await_termination(Pid), + {ok, maps:remove(sec_acc1, State)} end}, #{desc => "order 'primary acceptor' to terminate", cmd => fun(#{prim_acc := Pid} = _State) -> - Pid ! {terminate, self()}, + ev_terminate(Pid), ok end}, #{desc => "await 'primary acceptor' termination", cmd => fun(#{prim_acc := Pid} = State) -> - receive - {'DOWN', _, process, Pid, _} -> - {ok, maps:remove(prim_acc, State)} - end + ev_await_termination(Pid), + {ok, maps:remove(prim_acc, State)} end}, -- cgit v1.2.3 From 468fd132f5b9a13af76abd2c452967132be2ffdf Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Fri, 26 Oct 2018 14:12:49 +0200 Subject: [socket-nif|test] Updated socket (remote) close test case(s) Updated the socket (remote) close test case(s) sc_rc_receive_response_tcp. Now has three (remote) client connecting. OTP-14831 --- erts/emulator/test/socket_SUITE.erl | 544 ++++++++++++++++++++++++++++++------ 1 file changed, 465 insertions(+), 79 deletions(-) diff --git a/erts/emulator/test/socket_SUITE.erl b/erts/emulator/test/socket_SUITE.erl index f399d097ad..8d96065076 100644 --- a/erts/emulator/test/socket_SUITE.erl +++ b/erts/emulator/test/socket_SUITE.erl @@ -4399,6 +4399,9 @@ sc_rc_recv_response_tcp6(_Config) when is_list(_Config) -> %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% sc_rc_receive_response_tcp(InitState) -> + %% Each connection are handled by handler processes. + %% These are created (on the fly) and handled internally + %% by the server! ServerSeq = [ %% *** Init part *** @@ -4447,21 +4450,57 @@ sc_rc_receive_response_tcp(InitState) -> ok end}, - %% The actual test - #{desc => "await continue (accept)", + %% The actual test (we expect three connections) + #{desc => "await continue (accept all three connections)", cmd => fun(#{tester := Tester} = _State) -> ev_await_continue(Tester, tester, accept) end}, - #{desc => "accept", - cmd => fun(#{lsock := LSock} = State) -> + #{desc => "accept 1", + cmd => fun(#{lsock := LSock, recv := Recv} = State) -> case socket:accept(LSock) of {ok, Sock} -> - {ok, State#{csock => Sock}}; + ei("accepted: try start handler"), + {ok, Handler} = sc_rc_tcp_handler_start(1, + Recv, + Sock), + ei("handler started"), + {ok, State#{csock1 => Sock, + handler1 => Handler}}; {error, _} = ERROR -> ERROR end end}, - #{desc => "announce ready (accept)", + #{desc => "accept 2", + cmd => fun(#{lsock := LSock, recv := Recv} = State) -> + case socket:accept(LSock) of + {ok, Sock} -> + ei("accepted: try start handler"), + {ok, Handler} = sc_rc_tcp_handler_start(2, + Recv, + Sock), + ei("handler started"), + {ok, State#{csock2 => Sock, + handler2 => Handler}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "accept 3", + cmd => fun(#{lsock := LSock, recv := Recv} = State) -> + case socket:accept(LSock) of + {ok, Sock} -> + ei("accepted: try start handler"), + {ok, Handler} = sc_rc_tcp_handler_start(3, + Recv, + Sock), + ei("handler started"), + {ok, State#{csock3 => Sock, + handler3 => Handler}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (accept all three connections)", cmd => fun(#{tester := Tester}) -> ev_ready(Tester, accept), ok @@ -4470,18 +4509,55 @@ sc_rc_receive_response_tcp(InitState) -> cmd => fun(#{tester := Tester} = _State) -> ev_await_continue(Tester, tester, recv) end}, - #{desc => "receive", - cmd => fun(#{csock := Sock, recv := Recv} = State) -> - case Recv(Sock) of - {error, closed} -> - {ok, maps:remove(csock, State)}; - {ok, _} -> - {error, unexpected_success}; - {error, _} = ERROR -> - ERROR + #{desc => "order handler 1 to receive", + cmd => fun(#{handler1 := Pid} = _State) -> + Pid ! {recv, self()}, + ok + end}, + #{desc => "order handler 2 to receive", + cmd => fun(#{handler2 := Pid} = _State) -> + Pid ! {recv, self()}, + ok + end}, + #{desc => "order handler 3 to receive", + cmd => fun(#{handler3 := Pid} = _State) -> + Pid ! {recv, self()}, + ok + end}, + #{desc => "await ready from handler 1 (recv)", + cmd => fun(#{handler1 := Pid} = _State) -> + receive + {ready, Pid, ok} -> + ok; + {ready, Pid, Result} -> + Result; + {'DOWN', _, process, Pid, Reason} -> + {error, {handler1_exit, Reason}} end end}, - #{desc => "announce ready (recv closed)", + #{desc => "await ready from handler 2 (recv)", + cmd => fun(#{handler2 := Pid} = _State) -> + receive + {ready, Pid, ok} -> + ok; + {ready, Pid, Result} -> + Result; + {'DOWN', _, process, Pid, Reason} -> + {error, {handler2_exit, Reason}} + end + end}, + #{desc => "await ready from handler 3 (recv)", + cmd => fun(#{handler3 := Pid} = _State) -> + receive + {ready, Pid, ok} -> + ok; + {ready, Pid, Result} -> + Result; + {'DOWN', _, process, Pid, Reason} -> + {error, {handler3_exit, Reason}} + end + end}, + #{desc => "announce ready (recv closed from all handlers)", cmd => fun(#{tester := Tester}) -> ev_ready(Tester, recv_closed), ok @@ -4497,6 +4573,48 @@ sc_rc_receive_response_tcp(InitState) -> ERROR end end}, + #{desc => "order handler 1 to terminate", + cmd => fun(#{handler1 := Pid} = _State) -> + Pid ! {terminate, self(), ok}, + ok + end}, + #{desc => "await handler 1 termination", + cmd => fun(#{handler1 := Pid} = State) -> + receive + {'DOWN', _, process, Pid, _} -> + State1 = maps:remove(csock1, State), + State2 = maps:remove(handler1, State1), + {ok, State2} + end + end}, + #{desc => "order handler 2 to terminate", + cmd => fun(#{handler2 := Pid} = _State) -> + Pid ! {terminate, self(), ok}, + ok + end}, + #{desc => "await handler 2 termination", + cmd => fun(#{handler2 := Pid} = State) -> + receive + {'DOWN', _, process, Pid, _} -> + State1 = maps:remove(csock2, State), + State2 = maps:remove(handler2, State1), + {ok, State2} + end + end}, + #{desc => "order handler 3 to terminate", + cmd => fun(#{handler3 := Pid} = _State) -> + Pid ! {terminate, self(), ok}, + ok + end}, + #{desc => "await handler 3 termination", + cmd => fun(#{handler3 := Pid} = State) -> + receive + {'DOWN', _, process, Pid, _} -> + State1 = maps:remove(csock3, State), + State2 = maps:remove(handler3, State1), + {ok, State2} + end + end}, #{desc => "close listen socket", cmd => fun(#{lsock := LSock} = State) -> case socket:close(LSock) of @@ -4664,18 +4782,48 @@ sc_rc_receive_response_tcp(InitState) -> TesterSeq = [ %% *** Init part *** - #{desc => "create client node", + #{desc => "create client node 1", cmd => fun(#{host := Host} = State) -> - case start_node(Host, client) of + case start_node(Host, client_1) of {ok, Node} -> - ei("client node ~p started", [Node]), - {ok, State#{client_node => Node}}; + ei("client node (1) ~p started", [Node]), + {ok, State#{client_node1 => Node}}; {error, Reason, _} -> {error, Reason} end end}, - #{desc => "monitor client node", - cmd => fun(#{client_node := Node} = _State) -> + #{desc => "monitor client node 1", + cmd => fun(#{client_node1 := Node} = _State) -> + true = erlang:monitor_node(Node, true), + ok + end}, + #{desc => "create client node 2", + cmd => fun(#{host := Host} = State) -> + case start_node(Host, client_2) of + {ok, Node} -> + ei("client node (2) ~p started", [Node]), + {ok, State#{client_node2 => Node}}; + {error, Reason, _} -> + {error, Reason} + end + end}, + #{desc => "monitor client node 2", + cmd => fun(#{client_node2 := Node} = _State) -> + true = erlang:monitor_node(Node, true), + ok + end}, + #{desc => "create client node 3", + cmd => fun(#{host := Host} = State) -> + case start_node(Host, client_3) of + {ok, Node} -> + ei("client node (3) ~p started", [Node]), + {ok, State#{client_node3 => Node}}; + {error, Reason, _} -> + {error, Reason} + end + end}, + #{desc => "monitor client node 3", + cmd => fun(#{client_node3 := Node} = _State) -> true = erlang:monitor_node(Node, true), ok end}, @@ -4684,8 +4832,18 @@ sc_rc_receive_response_tcp(InitState) -> _MRef = erlang:monitor(process, Pid), ok end}, - #{desc => "monitor client", - cmd => fun(#{client := Pid} = _State) -> + #{desc => "monitor client 1", + cmd => fun(#{client1 := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + #{desc => "monitor client 2", + cmd => fun(#{client2 := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + #{desc => "monitor client 3", + cmd => fun(#{client3 := Pid} = _State) -> _MRef = erlang:monitor(process, Pid), ok end}, @@ -4702,16 +4860,38 @@ sc_rc_receive_response_tcp(InitState) -> {ok, State#{server_sa => ServerSA}} end}, - %% Start the client - #{desc => "order client start", - cmd => fun(#{client := Pid, - client_node := Node, - server_sa := ServerSA} = _State) -> + %% Start the client(s) + #{desc => "order client 1 start", + cmd => fun(#{client1 := Pid, + client_node1 := Node, + server_sa := ServerSA} = _State) -> ev_start(Pid, {Node, ServerSA}), ok end}, - #{desc => "await client ready (init)", - cmd => fun(#{client := Pid} = _State) -> + #{desc => "await client 1 ready (init)", + cmd => fun(#{client1 := Pid} = _State) -> + ok = ev_await_ready(Pid, client, init) + end}, + #{desc => "order client 2 start", + cmd => fun(#{client2 := Pid, + client_node2 := Node, + server_sa := ServerSA} = _State) -> + ev_start(Pid, {Node, ServerSA}), + ok + end}, + #{desc => "await client 2 ready (init)", + cmd => fun(#{client2 := Pid} = _State) -> + ok = ev_await_ready(Pid, client, init) + end}, + #{desc => "order client 3 start", + cmd => fun(#{client3 := Pid, + client_node3 := Node, + server_sa := ServerSA} = _State) -> + ev_start(Pid, {Node, ServerSA}), + ok + end}, + #{desc => "await client 3 ready (init)", + cmd => fun(#{client3 := Pid} = _State) -> ok = ev_await_ready(Pid, client, init) end}, @@ -4726,26 +4906,66 @@ sc_rc_receive_response_tcp(InitState) -> ?SLEEP(?SECS(1)), ok end}, - #{desc => "order client continue (connect)", - cmd => fun(#{client := Pid} = _State) -> + #{desc => "order client 1 continue (connect)", + cmd => fun(#{client1 := Pid} = _State) -> ev_continue(Pid, connect), ok end}, - #{desc => "await server ready (accept)", - cmd => fun(#{server := Server, - client := Client} = _State) -> - ev_await_ready(Server, server, accept, - [{client, Client}]), + #{desc => "await client 1 ready (connect)", + cmd => fun(#{server := Server, + client1 := Client1, + client2 := Client2, + client3 := Client3} = _State) -> + ev_await_ready(Client1, client1, connect, + [{server, Server}, + {client2, Client2}, + {client3, Client3}]), ok end}, - #{desc => "await client ready (connect)", - cmd => fun(#{server := Server, - client := Client} = _State) -> - ev_await_ready(Client, client, connect, - [{server, Server}]), + #{desc => "order client 2 continue (connect)", + cmd => fun(#{client2 := Pid} = _State) -> + ev_continue(Pid, connect), + ok + end}, + #{desc => "await client 2 ready (connect)", + cmd => fun(#{server := Server, + client1 := Client1, + client2 := Client2, + client3 := Client3} = _State) -> + ev_await_ready(Client2, client2, connect, + [{server, Server}, + {client1, Client1}, + {client3, Client3}]), + ok + end}, + #{desc => "order client 3 continue (connect)", + cmd => fun(#{client3 := Pid} = _State) -> + ev_continue(Pid, connect), + ok + end}, + #{desc => "await client 3 ready (connect)", + cmd => fun(#{server := Server, + client1 := Client1, + client2 := Client2, + client3 := Client3} = _State) -> + ev_await_ready(Client3, client3, connect, + [{server, Server}, + {client1, Client1}, + {client2, Client2}]), + ok + end}, + #{desc => "await server ready (accept from all connections)", + cmd => fun(#{server := Server, + client1 := Client1, + client2 := Client2, + client3 := Client3} = _State) -> + ev_await_ready(Server, server, accept, + [{client1, Client1}, + {client2, Client2}, + {client3, Client3}]), ok end}, - #{desc => "order server continue (recv)", + #{desc => "order server continue (recv for all connections)", cmd => fun(#{server := Pid} = _State) -> ev_continue(Pid, recv), ok @@ -4755,36 +4975,98 @@ sc_rc_receive_response_tcp(InitState) -> ?SLEEP(?SECS(1)), ok end}, - #{desc => "order client continue (close)", - cmd => fun(#{client := Pid} = _State) -> + #{desc => "order client 1 continue (close)", + cmd => fun(#{client1 := Pid} = _State) -> ev_continue(Pid, close), ok end}, - #{desc => "await client ready (close)", - cmd => fun(#{server := Server, - client := Client} = _State) -> - ev_await_ready(Client, client, close, - [{server, Server}]), + #{desc => "await client 1 ready (close)", + cmd => fun(#{server := Server, + client1 := Client1, + client2 := Client2, + client3 := Client3} = _State) -> + ev_await_ready(Client1, client1, close, + [{server, Server}, + {client2, Client2}, + {client3, Client3}]), ok end}, - #{desc => "await server ready (closed)", - cmd => fun(#{server := Server, - client := Client} = _State) -> + #{desc => "order client 2 continue (close)", + cmd => fun(#{client2 := Pid} = _State) -> + ev_continue(Pid, close), + ok + end}, + #{desc => "await client 2 ready (close)", + cmd => fun(#{server := Server, + client1 := Client1, + client2 := Client2, + client3 := Client3} = _State) -> + ev_await_ready(Client2, client2, close, + [{server, Server}, + {client1, Client1}, + {client3, Client3}]), + ok + end}, + #{desc => "order client 3 continue (close)", + cmd => fun(#{client3 := Pid} = _State) -> + ev_continue(Pid, close), + ok + end}, + #{desc => "await client 3 ready (close)", + cmd => fun(#{server := Server, + client1 := Client1, + client2 := Client2, + client3 := Client3} = _State) -> + ev_await_ready(Client3, client1, close, + [{server, Server}, + {client1, Client1}, + {client2, Client2}]), + ok + end}, + #{desc => "await server ready (close for all connections)", + cmd => fun(#{server := Server, + client1 := Client1, + client2 := Client2, + client3 := Client3} = _State) -> ev_await_ready(Server, server, recv_closed, - [{client, Client}]), + [{client1, Client1}, + {client2, Client2}, + {client3, Client3}]), ok end}, %% Terminations - #{desc => "order client to terminate", - cmd => fun(#{client := Pid} = _State) -> + #{desc => "order client 1 to terminate", + cmd => fun(#{client1 := Pid} = _State) -> ev_terminate(Pid), ok end}, - #{desc => "await client termination", - cmd => fun(#{client := Pid} = State) -> + #{desc => "await client 1 termination", + cmd => fun(#{client1 := Pid} = State) -> ev_await_termination(Pid), - State1 = maps:remove(client, State), + State1 = maps:remove(client1, State), + {ok, State1} + end}, + #{desc => "order client 2 to terminate", + cmd => fun(#{client2 := Pid} = _State) -> + ev_terminate(Pid), + ok + end}, + #{desc => "await client 2 termination", + cmd => fun(#{client2 := Pid} = State) -> + ev_await_termination(Pid), + State1 = maps:remove(client2, State), + {ok, State1} + end}, + #{desc => "order client 3 to terminate", + cmd => fun(#{client3 := Pid} = _State) -> + ev_terminate(Pid), + ok + end}, + #{desc => "await client 3 termination", + cmd => fun(#{client3 := Pid} = State) -> + ev_await_termination(Pid), + State1 = maps:remove(client3, State), {ok, State1} end}, #{desc => "order server to terminate", @@ -4798,15 +5080,37 @@ sc_rc_receive_response_tcp(InitState) -> State1 = maps:remove(server, State), {ok, State1} end}, - #{desc => "stop client node", - cmd => fun(#{client_node := Node} = _State) -> + #{desc => "stop client node 1", + cmd => fun(#{client_node1 := Node} = _State) -> + stop_node(Node) + end}, + #{desc => "await client node 1 termination", + cmd => fun(#{client_node1 := Node} = State) -> + receive + {nodedown, Node} -> + {ok, maps:remove(client_node1, State)} + end + end}, + #{desc => "stop client node 2", + cmd => fun(#{client_node2 := Node} = _State) -> + stop_node(Node) + end}, + #{desc => "await client node 2 termination", + cmd => fun(#{client_node2 := Node} = State) -> + receive + {nodedown, Node} -> + {ok, maps:remove(client_node2, State)} + end + end}, + #{desc => "stop client node 3", + cmd => fun(#{client_node3 := Node} = _State) -> stop_node(Node) end}, - #{desc => "await client node termination", - cmd => fun(#{client_node := Node} = State) -> + #{desc => "await client node 3 termination", + cmd => fun(#{client_node3 := Node} = State) -> receive {nodedown, Node} -> - {ok, maps:remove(client_node, State)} + {ok, maps:remove(client_node3, State)} end end}, @@ -4821,32 +5125,38 @@ sc_rc_receive_response_tcp(InitState) -> ServerInitState = InitState, Server = evaluator_start("server", ServerSeq, ServerInitState), - i("start client evaluator"), + i("start client evaluator(s)"), ClientInitState = InitState, - Client = evaluator_start("client", ClientSeq, ClientInitState), + Client1 = evaluator_start("client-1", ClientSeq, ClientInitState), + Client2 = evaluator_start("client-2", ClientSeq, ClientInitState), + Client3 = evaluator_start("client-3", ClientSeq, ClientInitState), i("start 'tester' evaluator"), - TesterInitState = #{host => local_host(), - server => Server#ev.pid, - client => Client#ev.pid}, + TesterInitState = #{host => local_host(), + server => Server#ev.pid, + client1 => Client1#ev.pid, + client2 => Client2#ev.pid, + client3 => Client3#ev.pid}, Tester = evaluator_start("tester", TesterSeq, TesterInitState), i("await evaluator"), - ok = await_evaluator_finish([Server, Client, Tester]). + ok = await_evaluator_finish([Server, + Client1, Client2, Client3, + Tester]). start_node(Host, NodeName) -> - case do_start_node(Host, NodeName) of + UniqueNodeName = f("~w_~w", [NodeName, erlang:unique_integer([positive])]), + case do_start_node(Host, UniqueNodeName) of {ok, _} = OK -> OK; - {error, already_started, Node} -> - stop_node(Node), % We don't know in what state it is, so kill i t - do_start_node(Host, NodeName); - ERROR -> - ERROR + {error, Reason, _} -> + {error, Reason} end. -do_start_node(Host, NodeName) -> +do_start_node(Host, NodeName) when is_list(NodeName) -> + do_start_node(Host, list_to_atom(NodeName)); +do_start_node(Host, NodeName) when is_atom(NodeName) -> Dir = filename:dirname(code:which(?MODULE)), Flags = "-pa " ++ Dir, Opts = [{monitor_master, true}, {erl_flags, Flags}], @@ -4870,7 +5180,6 @@ local_host() -> erlang:raise(C, E, S) end. - sc_rc_tcp_client_start(Node) -> Self = self(), GL = group_leader(), @@ -4971,6 +5280,83 @@ sc_rc_tcp_client_await_terminate(Parent) -> end. +%% The handlers run on the same node as the server (the local node). + +sc_rc_tcp_handler_start(ID, Recv, Sock) -> + Self = self(), + Fun = fun() -> sc_rc_tcp_handler(ID, Self, Recv, Sock) end, + {Pid, _} = erlang:spawn_monitor(Fun), + receive + {started, Pid} -> + {ok, Pid}; + {'DOWN', _, process, Pid, Reason} -> + {error, Reason} + end. + +sc_rc_tcp_handler(ID, Parent, Recv, Sock) -> + sc_rc_tcp_handler_init(ID, Parent), + sc_rc_tcp_handler_await_recv(Parent), + RecvRes = sc_rc_tcp_handler_recv(Recv, Sock), + sc_rc_tcp_handler_announce_ready(Parent, RecvRes), + Reason = sc_rc_tcp_handler_await_terminate(Parent), + exit(Reason). + +sc_rc_tcp_handler_init(ID, Parent) -> + put(sname, f("handler-~w", [ID])), + _MRef = erlang:monitor(process, Parent), + Parent ! {started, self()}, + ei("started"), + ok. + +sc_rc_tcp_handler_await_recv(Parent) -> + ei("await recv"), + receive + {recv, Parent} -> + ok; + {'DOWN', _, process, Parent, Reason} -> + ee("received DOWN regarding parent: " + "~n ~p", [Reason]), + exit({parent, Reason}) + end. + +sc_rc_tcp_handler_recv(Recv, Sock) -> + ei("recv"), + try Recv(Sock) of + {error, closed} -> + ok; + {ok, _} -> + ei("unexpected success"), + {error, unexpected_success}; + {error, Reason} = ERROR -> + ei("receive error: " + "~n ~p", [Reason]), + ERROR + catch + C:E:S -> + ei("receive failure: " + "~n Class: ~p" + "~n Error: ~p" + "~n Stack: ~p", [C, E, S]), + {error, {recv, C, E, S}} + end. + +sc_rc_tcp_handler_announce_ready(Parent, Result) -> + ei("announce ready"), + Parent ! {ready, self(), Result}, + ok. + +sc_rc_tcp_handler_await_terminate(Parent) -> + ei("await terminate"), + receive + {terminate, Parent, Reason} -> + Reason; + {'DOWN', _, process, Parent, Reason} -> + ee("received DOWN regarding parent: " + "~n ~p", [Reason]), + {parent, Reason} + end. + + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% This test case is intended to test what happens when a socket is %% remotely closed while the process is calling the recvmsg function. -- cgit v1.2.3 From 944c0714c9342c4b72fc4433448058db4cb7861f Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Fri, 26 Oct 2018 18:07:45 +0200 Subject: [socket-nif|test] Added "proper" evaluator module Add a "proper" evaluator module and adapted a couple of test cases to use that instead. OTP-14831 --- erts/emulator/test/Makefile | 5 +- erts/emulator/test/socket_SUITE.erl | 151 +++++---- erts/emulator/test/socket_test_evaluator.erl | 487 +++++++++++++++++++++++++++ erts/emulator/test/socket_test_evaluator.hrl | 57 ++++ erts/emulator/test/socket_test_lib.erl | 72 ++++ 5 files changed, 695 insertions(+), 77 deletions(-) create mode 100644 erts/emulator/test/socket_test_evaluator.erl create mode 100644 erts/emulator/test/socket_test_evaluator.hrl create mode 100644 erts/emulator/test/socket_test_lib.erl diff --git a/erts/emulator/test/Makefile b/erts/emulator/test/Makefile index 39a3ef2a3d..22c2f24a80 100644 --- a/erts/emulator/test/Makefile +++ b/erts/emulator/test/Makefile @@ -32,6 +32,8 @@ SOCKET_MODULES = \ socket_lib \ socket_server \ socket_client \ + socket_test_lib \ + socket_test_evaluator \ socket_SUITE MODULES= \ @@ -156,6 +158,7 @@ NATIVE_MODULES= $(NATIVE:%=%_native_SUITE) NATIVE_ERL_FILES= $(NATIVE_MODULES:%=%.erl) ERL_FILES= $(MODULES:%=%.erl) +HRL_FILES= socket_test_evaluator.hrl TARGET_FILES = $(MODULES:%=$(EBIN)/%.$(EMULATOR)) SOCKET_TARGETS = $(SOCKET_MODULES:%=$(EBIN)/%.$(EMULATOR)) @@ -228,7 +231,7 @@ release_spec: release_tests_spec: make_emakefile $(INSTALL_DIR) "$(RELSYSDIR)" $(INSTALL_DATA) $(EMAKEFILE) $(TEST_SPEC_FILES) \ - $(ERL_FILES) "$(RELSYSDIR)" + $(ERL_FILES) $(HRL_FILES) "$(RELSYSDIR)" $(INSTALL_DATA) $(NO_OPT_ERL_FILES) "$(RELSYSDIR)" $(INSTALL_DATA) $(NATIVE_ERL_FILES) "$(RELSYSDIR)" chmod -R u+w "$(RELSYSDIR)" diff --git a/erts/emulator/test/socket_SUITE.erl b/erts/emulator/test/socket_SUITE.erl index 8d96065076..f4b8d94346 100644 --- a/erts/emulator/test/socket_SUITE.erl +++ b/erts/emulator/test/socket_SUITE.erl @@ -93,23 +93,26 @@ %% Tickets ]). + +-include("socket_test_evaluator.hrl"). + %% Internal exports %% -export([]). --record(ev, {name :: string(), - pid :: pid(), - mref :: reference()}). --type ev() :: #ev{}. +%% -record(ev, {name :: string(), +%% pid :: pid(), +%% mref :: reference()}). +%% -type ev() :: #ev{}. -define(MKEV(N,P,R), #ev{name = N, pid = P, mref = R}). --type initial_evaluator_state() :: map(). --type evaluator_state() :: term(). --type command_fun() :: - fun((State :: evaluator_state()) -> ok) | - fun((State :: evaluator_state()) -> {ok, evaluator_state()}) | - fun((State :: evaluator_state()) -> {error, term()}). +%% -type initial_evaluator_state() :: map(). +%% -type evaluator_state() :: map(). +%% -type command_fun() :: +%% fun((State :: evaluator_state()) -> ok) | +%% fun((State :: evaluator_state()) -> {ok, evaluator_state()}) | +%% fun((State :: evaluator_state()) -> {error, term()}). --type command() :: #{desc := string(), - cmd := command_fun()}. +%% -type command() :: #{desc := string(), +%% cmd := command_fun()}. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -126,6 +129,7 @@ -define(TT(T), ct:timetrap(T)). + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% suite() -> @@ -403,12 +407,16 @@ api_b_open_and_close(InitState) -> end}, #{desc => "validate socket close", cmd => fun({_, ok}) -> - {ok, normal}; + ok; ({_, {error, _} = ERROR}) -> ERROR - end}], - Evaluator = evaluator_start("tester", Seq, InitState), - ok = await_evaluator_finish([Evaluator]). + end}, + + %% *** We are done *** + ?FINISH_NORMAL + ], + Evaluator = ?SEV_START("tester", Seq, InitState), + ok = ?SEV_AWAIT_FINISH([Evaluator]). @@ -543,12 +551,14 @@ api_b_send_and_recv_udp(InitState) -> end}, #{desc => "close dst socket", cmd => fun(#{sock_dst := Sock}) -> - ok = socket:close(Sock), - {ok, normal} - end} + ok = socket:close(Sock) + end}, + + %% *** We are done *** + ?FINISH_NORMAL ], - Evaluator = evaluator_start("tester", Seq, InitState), - ok = await_evaluator_finish([Evaluator]). + Evaluator = ?SEV_START("tester", Seq, InitState), + ok = ?SEV_AWAIT_FINISH([Evaluator]). @@ -618,7 +628,7 @@ api_b_send_and_recv_tcp(InitState) -> %% *** Wait for start order *** #{desc => "await start (from tester)", cmd => fun(State) -> - Tester = ev_await_start(), + Tester = ?SEV_AWAIT_START(), {ok, State#{tester => Tester}} end}, #{desc => "monitor tester", @@ -658,14 +668,14 @@ api_b_send_and_recv_tcp(InitState) -> end}, #{desc => "announce ready (init)", cmd => fun(#{tester := Tester, lport := Port}) -> - ev_ready(Tester, init, Port), + ?SEV_ANNOUNCE_READY(Tester, init, Port), ok end}, %% The actual test #{desc => "await continue (accept)", cmd => fun(#{tester := Tester}) -> - ev_await_continue(Tester, tester, accept) + ?SEV_AWAIT_CONTINUE(Tester, tester, accept) end}, #{desc => "await connection", cmd => fun(#{lsock := LSock} = State) -> @@ -679,7 +689,7 @@ api_b_send_and_recv_tcp(InitState) -> end}, #{desc => "announce ready (accept)", cmd => fun(#{tester := Tester}) -> - ev_ready(Tester, accept), + ?SEV_ANNOUNCE_READY(Tester, accept), ok end}, #{desc => "await (recv) request", @@ -693,13 +703,12 @@ api_b_send_and_recv_tcp(InitState) -> end}, #{desc => "announce ready (recv request)", cmd => fun(#{tester := Tester}) -> - %% Tester ! {ready, self(), Port}, - ev_ready(Tester, recv_req), + ?SEV_ANNOUNCE_READY(Tester, recv_req), ok end}, #{desc => "await continue (with send reply)", cmd => fun(#{tester := Tester}) -> - ev_await_continue(Tester, tester, send_reply) + ?SEV_AWAIT_CONTINUE(Tester, tester, send_reply) end}, #{desc => "send reply", cmd => fun(#{csock := Sock, send := Send}) -> @@ -707,15 +716,14 @@ api_b_send_and_recv_tcp(InitState) -> end}, #{desc => "announce ready (send reply)", cmd => fun(#{tester := Tester}) -> - %% Tester ! {ready, self(), Port}, - ev_ready(Tester, send_reply), + ?SEV_ANNOUNCE_READY(Tester, send_reply), ok end}, %% *** Termination *** #{desc => "await terminate", cmd => fun(#{tester := Tester} = State) -> - case ev_await_terminate(Tester, tester) of + case ?SEV_AWAIT_TERMINATE(Tester, tester) of ok -> {ok, maps:remove(tester, State)}; {error, _} = ERROR -> @@ -732,10 +740,7 @@ api_b_send_and_recv_tcp(InitState) -> end}, %% *** We are done *** - #{desc => "finish", - cmd => fun(_) -> - {ok, normal} - end} + ?FINISH_NORMAL ], ClientSeq = @@ -743,7 +748,7 @@ api_b_send_and_recv_tcp(InitState) -> %% *** Wait for start order *** #{desc => "await start (from tester)", cmd => fun(State) -> - {Tester, Port} = ev_await_start(), + {Tester, Port} = ?SEV_AWAIT_START(), {ok, State#{tester => Tester, server_port => Port}} end}, #{desc => "monitor tester", @@ -781,14 +786,14 @@ api_b_send_and_recv_tcp(InitState) -> end}, #{desc => "announce ready (init)", cmd => fun(#{tester := Tester}) -> - ev_ready(Tester, init), + ?SEV_ANNOUNCE_READY(Tester, init), ok end}, %% *** The actual test *** #{desc => "await continue (connect)", cmd => fun(#{tester := Tester} = _State) -> - ev_await_continue(Tester, tester, connect) + ?SEV_AWAIT_CONTINUE(Tester, tester, connect) end}, #{desc => "connect to server", cmd => fun(#{sock := Sock, server_sa := SSA}) -> @@ -796,12 +801,12 @@ api_b_send_and_recv_tcp(InitState) -> end}, #{desc => "announce ready (connect)", cmd => fun(#{tester := Tester}) -> - ev_ready(Tester, connect), + ?SEV_ANNOUNCE_READY(Tester, connect), ok end}, #{desc => "await continue (send request)", cmd => fun(#{tester := Tester} = _State) -> - ev_await_continue(Tester, tester, send_req) + ?SEV_AWAIT_CONTINUE(Tester, tester, send_req) end}, #{desc => "send request (to server)", cmd => fun(#{sock := Sock, send := Send}) -> @@ -809,7 +814,7 @@ api_b_send_and_recv_tcp(InitState) -> end}, #{desc => "announce ready (send request)", cmd => fun(#{tester := Tester}) -> - ev_ready(Tester, send_req), + ?SEV_ANNOUNCE_READY(Tester, send_req), ok end}, #{desc => "await recv reply (from server)", @@ -819,14 +824,14 @@ api_b_send_and_recv_tcp(InitState) -> end}, #{desc => "announce ready (recv reply)", cmd => fun(#{tester := Tester}) -> - ev_ready(Tester, recv_reply), + ?SEV_ANNOUNCE_READY(Tester, recv_reply), ok end}, %% *** Termination *** #{desc => "await terminate", cmd => fun(#{tester := Tester} = State) -> - case ev_await_terminate(Tester, tester) of + case ?SEV_AWAIT_TERMINATE(Tester, tester) of ok -> {ok, maps:remove(tester, State)}; {error, _} = ERROR -> @@ -839,10 +844,7 @@ api_b_send_and_recv_tcp(InitState) -> end}, %% *** We are done *** - #{desc => "finish", - cmd => fun(_) -> - {ok, normal} - end} + ?FINISH_NORMAL ], TesterSeq = @@ -862,31 +864,31 @@ api_b_send_and_recv_tcp(InitState) -> %% Start the server #{desc => "order server start", cmd => fun(#{server := Pid} = _State) -> - ev_start(Pid), + ?SEV_ANNOUNCE_START(Pid), ok end}, #{desc => "await server ready (init)", cmd => fun(#{server := Pid} = State) -> - {ok, Port} = ev_await_ready(Pid, server, init), + {ok, Port} = ?SEV_AWAIT_READY(Pid, server, init), {ok, State#{server_port => Port}} end}, %% Start the client #{desc => "order client start", cmd => fun(#{client := Pid, server_port := Port} = _State) -> - ev_start(Pid, Port), + ?SEV_ANNOUNCE_START(Pid, Port), ok end}, #{desc => "await client ready (init)", cmd => fun(#{client := Pid} = _State) -> - ev_await_ready(Pid, client, init), + ?SEV_AWAIT_READY(Pid, client, init), ok end}, %% *** The actual test *** #{desc => "order server to continue (with accept)", cmd => fun(#{server := Server} = _State) -> - ev_continue(Server, accept), + ?SEV_ANNOUNCE_CONTINUE(Server, accept), ok end}, #{desc => "sleep", @@ -896,89 +898,86 @@ api_b_send_and_recv_tcp(InitState) -> end}, #{desc => "order client to continue (with connect)", cmd => fun(#{client := Client} = _State) -> - ev_continue(Client, connect), + ?SEV_ANNOUNCE_CONTINUE(Client, connect), ok end}, #{desc => "await client ready (connect)", cmd => fun(#{client := Client} = _State) -> - ev_await_ready(Client, client, connect) + ?SEV_AWAIT_READY(Client, client, connect) end}, #{desc => "await server ready (accept)", cmd => fun(#{server := Server} = _State) -> - ev_await_ready(Server, server, accept) + ?SEV_AWAIT_READY(Server, server, accept) end}, #{desc => "order client to continue (with send request)", cmd => fun(#{client := Client} = _State) -> - ev_continue(Client, send_req), + ?SEV_ANNOUNCE_CONTINUE(Client, send_req), ok end}, #{desc => "await client ready (with send request)", cmd => fun(#{client := Client} = _State) -> - ev_await_ready(Client, client, send_req) + ?SEV_AWAIT_READY(Client, client, send_req) end}, #{desc => "await server ready (request recv)", cmd => fun(#{server := Server} = _State) -> - ev_await_ready(Server, server, recv_req) + ?SEV_AWAIT_READY(Server, server, recv_req) end}, #{desc => "order server to continue (with send reply)", cmd => fun(#{server := Server} = _State) -> - ev_continue(Server, send_reply), + ?SEV_ANNOUNCE_CONTINUE(Server, send_reply), ok end}, #{desc => "await server ready (with reply sent)", cmd => fun(#{server := Server} = _State) -> - ev_await_ready(Server, server, send_reply) + ?SEV_AWAIT_READY(Server, server, send_reply) end}, #{desc => "await client ready (reply recv)", cmd => fun(#{client := Client} = _State) -> - ev_await_ready(Client, client, recv_reply) + ?SEV_AWAIT_READY(Client, client, recv_reply) end}, %% *** Termination *** #{desc => "order client to terminate", cmd => fun(#{client := Client} = _State) -> - ev_terminate(Client), + ?SEV_ANNOUNCE_TERMINATE(Client), ok end}, #{desc => "await client termination", cmd => fun(#{client := Client} = State) -> - ev_await_termination(Client), + ?SEV_AWAIT_TERMINATION(Client), State1 = maps:remove(client, State), {ok, State1} end}, #{desc => "order server to terminate", cmd => fun(#{server := Server} = _State) -> - ev_terminate(Server), + ?SEV_ANNOUNCE_TERMINATE(Server), ok end}, #{desc => "await server termination", cmd => fun(#{server := Server} = State) -> - ev_await_termination(Server), + ?SEV_AWAIT_TERMINATION(Server), State1 = maps:remove(server, State), {ok, State1} end}, %% *** We are done *** - #{desc => "finish", - cmd => fun(_) -> - {ok, normal} - end} + ?FINISH_NORMAL ], i("start server evaluator"), - Server = evaluator_start("server", ServerSeq, InitState), + Server = ?SEV_START("server", ServerSeq, InitState), i("start client evaluator"), - Client = evaluator_start("client", ClientSeq, InitState), + Client = ?SEV_START("client", ClientSeq, InitState), i("await evaluator(s)"), i("start tester evaluator"), TesterInitState = #{server => Server#ev.pid, client => Client#ev.pid}, - Tester = evaluator_start("tester", TesterSeq, TesterInitState), + Tester = ?SEV_START("tester", TesterSeq, TesterInitState), - ok = await_evaluator_finish([Server, Client, Tester]). + ok = ?SEV_AWAIT_FINISH([Server, Client, Tester]). @@ -5891,10 +5890,10 @@ which_addr2(Domain, [_|IFO]) -> %% will be used as exit reason. %% A successful command shall evaluate to ok | {ok, NewState} --spec evaluator_start(Name, Seq, Init) -> ev() when +-spec evaluator_start(Name, Seq, Init) -> socket_test_evaluator:ev() when Name :: string(), - Seq :: [command()], - Init :: initial_evaluator_state(). + Seq :: [socket_test_evaluator:command()], + Init :: socket_test_evaluator:initial_evaluator_state(). evaluator_start(Name, Seq, Init) when is_list(Name) andalso is_list(Seq) andalso (Seq =/= []) -> diff --git a/erts/emulator/test/socket_test_evaluator.erl b/erts/emulator/test/socket_test_evaluator.erl new file mode 100644 index 0000000000..8498ef6a0c --- /dev/null +++ b/erts/emulator/test/socket_test_evaluator.erl @@ -0,0 +1,487 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2018-2018. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% + +-module(socket_test_evaluator). + +%% Evaluator control functions +-export([ + start/3, + await_finish/1 + ]). + +%% Functions used by evaluators to interact with eachother +-export([ + %% Announce functions + %% (Send an announcement from one evaluator to another) + announce_start/1, announce_start/2, + announce_continue/2, announce_continue/3, + announce_ready/2, announce_ready/3, + announce_terminate/1, + + %% Await functions + %% (Wait for an announcement from another evaluator) + await_start/0, + await_continue/3, await_continue/4, + await_ready/3, await_ready/4, + await_terminate/2, await_terminate/3, + await_termination/1, await_termination/2 + ]). + +-export_type([ + ev/0, + initial_evaluator_state/0, + evaluator_state/0, + command_fun/0, + command/0 + ]). + + +-include("socket_test_evaluator.hrl"). + +-type ev() :: #ev{}. +-type initial_evaluator_state() :: map(). +-type evaluator_state() :: term(). +-type command_fun() :: + fun((State :: evaluator_state()) -> ok) | + fun((State :: evaluator_state()) -> {ok, evaluator_state()}) | + fun((State :: evaluator_state()) -> {error, term()}). + +-type command() :: #{desc := string(), + cmd := command_fun()}. + + +%% ============================================================================ + +-define(LIB, socket_test_lib). + +-define(EXTRA_NOTHING, '$nothing'). +-define(ANNOUNCEMENT_START, '$start'). +-define(ANNOUNCEMENT_READY, '$ready'). +-define(ANNOUNCEMENT_CONTINUE, '$continue'). +-define(ANNOUNCEMENT_TERMINATE, '$terminate'). + +-define(START_NAME_NONE, '$no-name'). +-define(START_SLOGAN, ?ANNOUNCEMENT_START). +-define(TERMINATE_SLOGAN, ?ANNOUNCEMENT_TERMINATE). + + +%% ============================================================================ + +-spec start(Name, Seq, Init) -> ev() when + Name :: string(), + Seq :: [command()], + Init :: initial_evaluator_state(). + +start(Name, Seq, InitState) + when is_list(Name) andalso is_list(Seq) andalso (Seq =/= []) -> + %% Make sure 'parent' is not already used + case maps:find(parent, InitState) of + {ok, _} -> + erlang:error({already_used, parent}); + error -> + InitState2 = InitState#{parent => self()}, + {Pid, MRef} = erlang:spawn_monitor( + fun() -> init(Name, Seq, InitState2) end), + #ev{name = Name, pid = Pid, mref = MRef} + end. + +init(Name, Seq, Init) -> + put(sname, Name), + loop(1, Seq, Init). + +loop(_ID, [], FinalState) -> + exit(FinalState); +loop(ID, [#{desc := Desc, + cmd := Cmd}|Cmds], State) when is_function(Cmd, 1) -> + i("evaluate command ~2w: ~s", [ID, Desc]), + try Cmd(State) of + ok -> + loop(ID + 1, Cmds, State); + {ok, NewState} -> + loop(ID + 1, Cmds, NewState); + {error, Reason} -> + e("command ~w failed: " + "~n Reason: ~p", [ID, Reason]), + exit({command_failed, ID, Reason, State}) + catch + C:E:S -> + e("command ~w crashed: " + "~n Class: ~p" + "~n Error: ~p" + "~n Call Stack: ~p", [ID, C, E, S]), + exit({command_crashed, ID, {C,E,S}, State}) + end. + + +%% ============================================================================ + +-spec await_finish(Evs) -> term() when + Evs :: [ev()]. + +await_finish(Evs) -> + await_finish(Evs, []). + +await_finish([], []) -> + ok; +await_finish([], Fails) -> + Fails; +await_finish(Evs, Fails) -> + receive + {'DOWN', _MRef, process, Pid, normal} -> + case lists:keysearch(Pid, #ev.pid, Evs) of + {value, #ev{name = Name}} -> + + + i("evaluator '~s' (~p) success", [Name, Pid]), + + + + + NewEvs = lists:keydelete(Pid, #ev.pid, Evs), + await_finish(NewEvs, Fails); + false -> + i("unknown process ~p died (normal)", [Pid]), + await_finish(Evs, Fails) + end; + {'DOWN', _MRef, process, Pid, Reason} -> + case lists:keysearch(Pid, #ev.pid, Evs) of + {value, #ev{name = Name}} -> + i("evaluator '~s' (~p) failed", [Name, Pid]), + NewEvs = lists:keydelete(Pid, #ev.pid, Evs), + await_finish(NewEvs, [{Pid, Reason}|Fails]); + false -> + i("unknown process ~p died: " + "~n ~p", [Pid, Reason]), + await_finish(Evs, Fails) + end + end. + + +%% ============================================================================ + +-spec announce_start(To) -> ok when + To :: pid(). + +announce_start(To) -> + announce(To, ?ANNOUNCEMENT_START, ?START_SLOGAN). + +-spec announce_start(To, Extra) -> ok when + To :: pid(), + Extra :: term(). + +announce_start(To, Extra) -> + announce(To, ?ANNOUNCEMENT_START, ?START_SLOGAN, Extra). + + +%% ============================================================================ + +-spec announce_continue(To, Slogan) -> ok when + To :: pid(), + Slogan :: atom(). + +announce_continue(To, Slogan) -> + announce_continue(To, Slogan, ?EXTRA_NOTHING). + +-spec announce_continue(To, Slogan, Extra) -> ok when + To :: pid(), + Slogan :: atom(), + Extra :: term(). + +announce_continue(To, Slogan, Extra) -> + announce(To, ?ANNOUNCEMENT_CONTINUE, Slogan, Extra). + + +%% ============================================================================ + +-spec announce_ready(To, Slogan) -> ok when + To :: pid(), + Slogan :: atom(). + +announce_ready(To, Slogan) -> + announce_ready(To, Slogan, ?EXTRA_NOTHING). + +-spec announce_ready(To, Slogan, Extra) -> ok when + To :: pid(), + Slogan :: atom(), + Extra :: term(). + +announce_ready(To, Slogan, Extra) -> + announce(To, ?ANNOUNCEMENT_READY, Slogan, Extra). + + +%% ============================================================================ + +-spec announce_terminate(To) -> ok when + To :: pid(). + +announce_terminate(To) -> + announce(To, ?ANNOUNCEMENT_TERMINATE, ?TERMINATE_SLOGAN). + + +%% ============================================================================ + +-spec announce(To, Announcement, Slogan) -> ok when + To :: pid(), + Announcement :: atom(), + Slogan :: atom(). + +announce(To, Announcement, Slogan) -> + announce(To, Announcement, Slogan, ?EXTRA_NOTHING). + +-spec announce(To, Announcement, Slogan, Extra) -> ok when + To :: pid(), + Announcement :: atom(), + Slogan :: atom(), + Extra :: term(). + +announce(To, Announcement, Slogan, Extra) + when is_pid(To) andalso + is_atom(Announcement) andalso + is_atom(Slogan) -> + To ! {Announcement, self(), Slogan, Extra}, + ok. + + + +%% ============================================================================ + +-spec await_start() -> Pid | {Pid, Extra} when + Pid :: pid(), + Extra :: term(). + +await_start() -> + case await(any, ?START_NAME_NONE, ?ANNOUNCEMENT_START, ?START_SLOGAN, []) of + {ok, Pid} when is_pid(Pid) -> + Pid; + {ok, {Pid, _} = OK} when is_pid(Pid) -> + OK + end. + + + +%% ============================================================================ + +-spec await_continue(From, Name, Slogan) -> ok | {ok, Extra} | {error, Reason} when + From :: pid(), + Name :: atom(), + Slogan :: atom(), + Extra :: term(), + Reason :: term(). + +await_continue(From, Name, Slogan) -> + await_continue(From, Name, Slogan, []). + +-spec await_continue(From, Name, Slogan, OtherPids) -> + ok | {ok, Extra} | {error, Reason} when + From :: pid(), + Name :: atom(), + Slogan :: atom(), + OtherPids :: [{pid(), atom()}], + Extra :: term(), + Reason :: term(). + +await_continue(From, Name, Slogan, OtherPids) + when is_pid(From) andalso + is_atom(Name) andalso + is_atom(Slogan) andalso + is_list(OtherPids) -> + await(From, Name, ?ANNOUNCEMENT_CONTINUE, Slogan, OtherPids). + + + +%% ============================================================================ + +-spec await_ready(From, Name, Slogan) -> ok | {ok, Extra} | {error, Reason} when + From :: pid(), + Name :: atom(), + Slogan :: atom(), + Extra :: term(), + Reason :: term(). + +await_ready(From, Name, Slogan) -> + await_ready(From, Name, Slogan, []). + +-spec await_ready(From, Name, Slogan, OtherPids) -> + ok | {ok, Extra} | {error, Reason} when + From :: pid(), + Name :: atom(), + Slogan :: atom(), + OtherPids :: [{pid(), atom()}], + Extra :: term(), + Reason :: term(). + +await_ready(From, Name, Slogan, OtherPids) + when is_pid(From) andalso + is_atom(Name) andalso + is_atom(Slogan) andalso + is_list(OtherPids) -> + await(From, Name, ?ANNOUNCEMENT_READY, Slogan, OtherPids). + + + +%% ============================================================================ + +-spec await_terminate(Pid, Name) -> ok | {error, Reason} when + Pid :: pid(), + Name :: atom(), + Reason :: term(). + +await_terminate(Pid, Name) when is_pid(Pid) andalso is_atom(Name) -> + await_terminate(Pid, Name, []). + +-spec await_terminate(Pid, Name, OtherPids) -> ok | {error, Reason} when + Pid :: pid(), + Name :: atom(), + OtherPids :: [{pid(), atom()}], + Reason :: term(). + +await_terminate(Pid, Name, OtherPids) -> + await(Pid, Name, ?ANNOUNCEMENT_TERMINATE, ?TERMINATE_SLOGAN, OtherPids). + + +%% ============================================================================ + +-spec await_termination(Pid) -> ok | {error, Reason} when + Pid :: pid(), + Reason :: term(). + +await_termination(Pid) when is_pid(Pid) -> + await_termination(Pid, any). + +-spec await_termination(Pid, ExpReason) -> ok | {error, Reason} when + Pid :: pid(), + ExpReason :: term(), + Reason :: term(). + +await_termination(Pid, ExpReason) -> + receive + {'DOWN', _, process, Pid, _} when (ExpReason =:= any) -> + ok; + {'DOWN', _, process, Pid, Reason} when (ExpReason =:= Reason) -> + ok; + {'DOWN', _, process, Pid, Reason} -> + {error, {unexpected_exit, ExpReason, Reason}} + end. + + +%% ============================================================================ + +%% We expect a message (announcement) from Pid, but we also watch for DOWN from +%% both Pid and OtherPids, in which case the test has failed! + +-spec await(ExpPid, Name, Announcement, Slogan, OtherPids) -> + ok | {ok, Extra} | {error, Reason} when + ExpPid :: any | pid(), + Name :: atom(), + Announcement :: atom(), + Slogan :: atom(), + OtherPids :: [{pid(), atom()}], + Extra :: term(), + Reason :: term(). + +await(ExpPid, Name, Announcement, Slogan, OtherPids) + when (is_pid(ExpPid) orelse (ExpPid =:= any)) andalso + is_atom(Name) andalso + is_atom(Announcement) andalso + is_atom(Slogan) andalso + is_list(OtherPids) -> + receive + {Announcement, Pid, Slogan, ?EXTRA_NOTHING} when (ExpPid =:= any) -> + {ok, Pid}; + {Announcement, Pid, Slogan, Extra} when (ExpPid =:= any) -> + {ok, {Pid, Extra}}; + {Announcement, Pid, Slogan, ?EXTRA_NOTHING} when (Pid =:= ExpPid) -> + ok; + {Announcement, Pid, Slogan, Extra} when (Pid =:= ExpPid) -> + {ok, Extra}; + {'DOWN', _, process, Pid, Reason} when (Pid =:= ExpPid) -> + e("Unexpected DOWN regarding ~w ~p: " + "~n ~p", [Name, Pid, Reason]), + {error, {unexpected_exit, Name}}; + {'DOWN', _, process, OtherPid, Reason} -> + case check_down(OtherPid, Reason, OtherPids) of + ok -> + i("DOWN from unknown process ~p: " + "~n ~p", [OtherPid, Reason]), + await(ExpPid, Name, Announcement, Slogan, OtherPids); + {error, _} = ERROR -> + ERROR + end + after infinity -> % For easy debugging, just change to some valid time (5000) + i("await -> timeout for msg from ~p (~w): " + "~n Announcement: ~p" + "~n Slogan: ~p" + "~nwhen" + "~n Messages: ~p", + [ExpPid, Name, Announcement, Slogan, pi(messages)]), + await(ExpPid, Name, Announcement, Slogan, OtherPids) + end. + +pi(Item) -> + pi(self(), Item). + +pi(Pid, Item) -> + {Item, Info} = process_info(Pid, Item), + Info. + +check_down(Pid, DownReason, Pids) -> + case lists:keymember(Pid, 1, Pids) of + {value, {_, Name}} -> + e("Unexpected DOWN regarding ~w ~p: " + "~n ~p", [Name, Pid, DownReason]), + {error, {unexpected_exit, Name}}; + false -> + ok + end. + + +%% ============================================================================ + +f(F, A) -> + lists:flatten(io_lib:format(F, A)). + + +%% i(F) -> +%% i(F, []). +i(F, A) -> + print("", F, A). + +%% e(F) -> +%% e(F, []). +e(F, A) -> + print(" ", F, A). + +print(Prefix, F, A) -> + %% The two prints is to get the output both in the shell (for when + %% "personal" testing is going on) and in the logs. + IDStr = + case get(sname) of + undefined -> + %% This means its not an evaluator, + %% or a named process. Instead its + %% most likely the test case itself, + %% so skip the name and the pid. + ""; + SName -> + f("[~s][~p]", [SName, self()]) + end, + FStr = f("[~s]~s ~s" ++ F, [?LIB:formated_timestamp(), IDStr, Prefix | A]), + io:format(user, FStr ++ "~n", []), + io:format(FStr, []). diff --git a/erts/emulator/test/socket_test_evaluator.hrl b/erts/emulator/test/socket_test_evaluator.hrl new file mode 100644 index 0000000000..1fb42a33b2 --- /dev/null +++ b/erts/emulator/test/socket_test_evaluator.hrl @@ -0,0 +1,57 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2018-2018. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% + +-ifndef(socket_test_evaluator). +-define(socket_test_evaluator, true). + +-define(SEV, socket_test_evaluator). + +-define(SEV_START(N, S, IS), ?SEV:start(N, S, IS)). +-define(SEV_AWAIT_FINISH(Evs), ?SEV:await_finish(Evs)). + +-define(SEV_ANNOUNCE_START(To), ?SEV:announce_start(To)). +-define(SEV_ANNOUNCE_START(To, Ex), ?SEV:announce_start(To, Ex)). +-define(SEV_ANNOUNCE_CONTINUE(To, S), ?SEV:announce_continue(To, S)). +-define(SEV_ANNOUNCE_CONTINUE(To, S, Ex), ?SEV:announce_continue(To, S, Ex)). +-define(SEV_ANNOUNCE_READY(To, S), ?SEV:announce_ready(To, S)). +-define(SEV_ANNOUNCE_READY(To, S, Ex), ?SEV:announce_ready(To, S, Ex)). +-define(SEV_ANNOUNCE_TERMINATE(To), ?SEV:announce_terminate(To)). + +-define(SEV_AWAIT_START(), ?SEV:await_start()). +-define(SEV_AWAIT_CONTINUE(F, N, S), ?SEV:await_continue(F, N, S)). +-define(SEV_AWAIT_CONTINUE(F, N, S, Ps), ?SEV:await_continue(F, N, S, Ps)). +-define(SEV_AWAIT_READY(F, N, S), ?SEV:await_ready(F, N, S)). +-define(SEV_AWAIT_READY(F, N, S, Ps), ?SEV:await_ready(F, N, S, Ps)). +-define(SEV_AWAIT_TERMINATE(F, N), ?SEV:await_terminate(F, N)). +-define(SEV_AWAIT_TERMINATE(F, N, Ps), ?SEV:await_terminate(F, N, Ps)). +-define(SEV_AWAIT_TERMINATION(P), ?SEV:await_termination(P)). +-define(SEV_AWAIT_TERMINATION(P, R), ?SEV:await_termination(P, R)). + +-record(ev, {name :: string(), + pid :: pid(), + mref :: reference()}). + +-define(FINISH_NORMAL, #{desc => "finish", + cmd => fun(_) -> + {ok, normal} + end}). + +-endif. % -ifdef(socket_test_evaluator). + diff --git a/erts/emulator/test/socket_test_lib.erl b/erts/emulator/test/socket_test_lib.erl new file mode 100644 index 0000000000..c36cc4fbfa --- /dev/null +++ b/erts/emulator/test/socket_test_lib.erl @@ -0,0 +1,72 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2018-2018. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% + +-module(socket_test_lib). + +-export([ + %% Time stuff + timestamp/0, + tdiff/2, + formated_timestamp/0, + format_timestamp/1, + + %% Skipping + not_yet_implemented/0, + skip/1 + ]). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +timestamp() -> + os:timestamp(). + + +tdiff({A1, B1, C1} = _T1x, {A2, B2, C2} = _T2x) -> + T1 = A1*1000000000+B1*1000+(C1 div 1000), + T2 = A2*1000000000+B2*1000+(C2 div 1000), + T2 - T1. + + +formated_timestamp() -> + format_timestamp(os:timestamp()). + +format_timestamp({_N1, _N2, _N3} = TS) -> + {_Date, Time} = calendar:now_to_local_time(TS), + %% {YYYY,MM,DD} = Date, + {Hour,Min,Sec} = Time, + %% FormatTS = + %% io_lib:format("~.4w-~.2.0w-~.2.0w ~.2.0w:~.2.0w:~.2.0w.~w", + %% [YYYY, MM, DD, Hour, Min, Sec, N3]), + FormatTS = io_lib:format("~.2.0w:~.2.0w:~.2.0w", [Hour, Min, Sec]), + lists:flatten(FormatTS). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +not_yet_implemented() -> + skip("not yet implemented"). + +skip(Reason) -> + throw({skip, Reason}). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -- cgit v1.2.3 From f8ce6043ff330a34644f03f5589c33ad7e57bfab Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Mon, 29 Oct 2018 14:58:53 +0100 Subject: [socket-nif|test] All test cases now using evaluator module All test cases has been updated using the new evaluator module (and their macros). OTP-14831 --- erts/emulator/test/socket_SUITE.erl | 1490 +++++++++++--------------- erts/emulator/test/socket_test_evaluator.erl | 68 +- erts/emulator/test/socket_test_evaluator.hrl | 24 +- 3 files changed, 669 insertions(+), 913 deletions(-) diff --git a/erts/emulator/test/socket_SUITE.erl b/erts/emulator/test/socket_SUITE.erl index f4b8d94346..208d8037fb 100644 --- a/erts/emulator/test/socket_SUITE.erl +++ b/erts/emulator/test/socket_SUITE.erl @@ -253,7 +253,7 @@ sc_rc_cases() -> %% traffic_cases() -> -%% []. +%% [snr_send_and_recv_chunks]. %% ticket_cases() -> @@ -413,7 +413,7 @@ api_b_open_and_close(InitState) -> end}, %% *** We are done *** - ?FINISH_NORMAL + ?SEV_FINISH_NORMAL ], Evaluator = ?SEV_START("tester", Seq, InitState), ok = ?SEV_AWAIT_FINISH([Evaluator]). @@ -555,7 +555,7 @@ api_b_send_and_recv_udp(InitState) -> end}, %% *** We are done *** - ?FINISH_NORMAL + ?SEV_FINISH_NORMAL ], Evaluator = ?SEV_START("tester", Seq, InitState), ok = ?SEV_AWAIT_FINISH([Evaluator]). @@ -681,7 +681,7 @@ api_b_send_and_recv_tcp(InitState) -> cmd => fun(#{lsock := LSock} = State) -> case socket:accept(LSock) of {ok, Sock} -> - ei("accepted: ~n ~p", [Sock]), + ?SEV_IPRINT("accepted: ~n ~p", [Sock]), {ok, State#{csock => Sock}}; {error, _} = ERROR -> ERROR @@ -740,7 +740,7 @@ api_b_send_and_recv_tcp(InitState) -> end}, %% *** We are done *** - ?FINISH_NORMAL + ?SEV_FINISH_NORMAL ], ClientSeq = @@ -844,7 +844,7 @@ api_b_send_and_recv_tcp(InitState) -> end}, %% *** We are done *** - ?FINISH_NORMAL + ?SEV_FINISH_NORMAL ], TesterSeq = @@ -962,7 +962,7 @@ api_b_send_and_recv_tcp(InitState) -> end}, %% *** We are done *** - ?FINISH_NORMAL + ?SEV_FINISH_NORMAL ], i("start server evaluator"), @@ -1239,15 +1239,15 @@ api_opt_simple_otp_options() -> i("start tcp (stream) evaluator"), InitState1 = #{domain => inet, type => stream, protocol => tcp}, - Tester1 = evaluator_start("tcp-tester", Seq, InitState1), + Tester1 = ?SEV_START("tcp-tester", Seq, InitState1), i("await tcp evaluator"), - ok = await_evaluator_finish([Tester1]), + ok = ?SEV_AWAIT_FINISH([Tester1]), i("start udp (dgram) socket"), InitState2 = #{domain => inet, type => dgram, protocol => udp}, - Tester2 = evaluator_start("udp-tester", Seq, InitState2), + Tester2 = ?SEV_START("udp-tester", Seq, InitState2), i("await udp evaluator"), - ok = await_evaluator_finish([Tester2]). + ok = ?SEV_AWAIT_FINISH([Tester2]). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -1275,12 +1275,7 @@ api_opt_simple_otp_controlling_process() -> %% *** Init part *** #{desc => "await start", cmd => fun(State) -> - %% receive - %% {start, Tester, Socket} -> - %% {ok, State#{tester => Tester, - %% sock => Socket}} - %% end - {Tester, Sock} = ev_await_start(), + {Tester, Sock} = ?SEV_AWAIT_START(), {ok, State#{tester => Tester, sock => Sock}} end}, @@ -1315,13 +1310,12 @@ api_opt_simple_otp_controlling_process() -> end}, #{desc => "announce ready (not owner)", cmd => fun(#{tester := Tester} = _State) -> - ev_ready(Tester, not_owner), - %% Tester ! {ready, self()}, + ?SEV_ANNOUNCE_READY(Tester, not_owner), ok end}, #{desc => "await continue (owner)", cmd => fun(#{tester := Tester} = _State) -> - ev_await_continue(Tester, tester, owner) + ?SEV_AWAIT_CONTINUE(Tester, tester, owner) end}, #{desc => "verify self as controlling-process", cmd => fun(#{sock := Sock} = _State) -> @@ -1352,8 +1346,7 @@ api_opt_simple_otp_controlling_process() -> end}, #{desc => "announce ready (owner)", cmd => fun(#{tester := Tester} = _State) -> - %% Tester ! {ready, self()}, - ev_ready(Tester, owner), + ?SEV_ANNOUNCE_READY(Tester, owner), ok end}, @@ -1361,23 +1354,14 @@ api_opt_simple_otp_controlling_process() -> %% *** Termination *** #{desc => "await termination", cmd => fun(#{tester := Tester} = State) -> - %% receive - %% {terminate, Tester} -> - %% State1 = maps:remove(tester, State), - %% State2 = maps:remove(sock, State1), - %% {ok, State2} - %% end - ev_await_terminate(Tester, tester), + ?SEV_AWAIT_TERMINATE(Tester, tester), State1 = maps:remove(tester, State), State2 = maps:remove(sock, State1), {ok, State2} end}, %% *** We are done *** - #{desc => "finish", - cmd => fun(_) -> - {ok, normal} - end} + ?SEV_FINISH_NORMAL ], TesterSeq = @@ -1411,17 +1395,12 @@ api_opt_simple_otp_controlling_process() -> end}, #{desc => "order (client) start", cmd => fun(#{client := Client, sock := Sock} = _State) -> - %% Client ! {start, self(), Sock}, - ev_start(Client, Sock), + ?SEV_ANNOUNCE_START(Client, Sock), ok end}, #{desc => "await (client) ready (not owner)", cmd => fun(#{client := Client} = _State) -> - %% receive - %% {ready, Client} -> - %% ok - %% end - ev_await_ready(Client, client, not_owner) + ?SEV_AWAIT_READY(Client, client, not_owner) end}, #{desc => "attempt controlling-process transfer to client", cmd => fun(#{client := Client, sock := Sock} = _State) -> @@ -1451,17 +1430,12 @@ api_opt_simple_otp_controlling_process() -> end}, #{desc => "order (client) continue (owner)", cmd => fun(#{client := Client} = _State) -> - %% Client ! {continue, self()}, - ev_continue(Client, owner), + ?SEV_ANNOUNCE_CONTINUE(Client, owner), ok end}, #{desc => "await (client) ready (2)", cmd => fun(#{client := Client} = _State) -> - %% receive - %% {ready, Client} -> - %% ok - %% end - ev_await_ready(Client, client, owner), + ?SEV_AWAIT_READY(Client, client, owner), ok end}, #{desc => "verify self as controlling-process", @@ -1480,17 +1454,12 @@ api_opt_simple_otp_controlling_process() -> %% *** Termination *** #{desc => "order (client) terminate", cmd => fun(#{client := Client} = _State) -> - %% Client ! {terminate, self()}, - ev_terminate(Client), + ?SEV_ANNOUNCE_TERMINATE(Client), ok end}, #{desc => "await client termination", cmd => fun(#{client := Client} = State) -> - %% receive - %% {'DOWN', _, process, Client, _} -> - %% {ok, maps:remove(client, State)} - %% end - ev_await_termination(Client), + ?SEV_AWAIT_TERMINATION(Client), {ok, maps:remove(client, State)} end}, #{desc => "close socket", @@ -1500,33 +1469,36 @@ api_opt_simple_otp_controlling_process() -> end}, %% *** We are done *** - #{desc => "finish", - cmd => fun(_) -> - {ok, normal} - end} + ?SEV_FINISH_NORMAL ], - i("start tcp (stream) socket"), + i("start tcp (stream) client evaluator"), ClientInitState1 = #{}, - Client1 = evaluator_start("tcp-client", ClientSeq, ClientInitState1), + Client1 = ?SEV_START("tcp-client", ClientSeq, ClientInitState1), + + i("start tcp (stream) tester evaluator"), TesterInitState1 = #{domain => inet, type => stream, protocol => tcp, client => Client1#ev.pid}, - Tester1 = evaluator_start("tcp-tester", TesterSeq, TesterInitState1), - i("await tcp evaluator"), - ok = await_evaluator_finish([Tester1, Client1]), + Tester1 = ?SEV_START("tcp-tester", TesterSeq, TesterInitState1), - i("start udp (dgram) socket"), + i("await tcp evaluator(s)"), + ok = ?SEV_AWAIT_FINISH([Tester1, Client1]), + + i("start udp (dgram) client evaluator"), ClientInitState2 = #{}, - Client2 = evaluator_start("udp-client", ClientSeq, ClientInitState2), + Client2 = ?SEV_START("udp-client", ClientSeq, ClientInitState2), + + i("start udp (dgram) tester evaluator"), TesterInitState2 = #{domain => inet, type => dgram, protocol => udp, client => Client2#ev.pid}, - Tester2 = evaluator_start("udp-tester", TesterSeq, TesterInitState2), - i("await udp evaluator"), - ok = await_evaluator_finish([Tester2, Client2]). + Tester2 = ?SEV_START("udp-tester", TesterSeq, TesterInitState2), + + i("await udp evaluator(s)"), + ok = ?SEV_AWAIT_FINISH([Tester2, Client2]). @@ -1588,7 +1560,7 @@ api_to_connect_tcp(InitState) -> %% *** Wait for start order part *** #{desc => "await start (from tester)", cmd => fun(State) -> - Tester = ev_await_start(), + Tester = ?SEV_AWAIT_START(), {ok, State#{tester => Tester}} end}, #{desc => "monitor tester", @@ -1628,14 +1600,14 @@ api_to_connect_tcp(InitState) -> end}, #{desc => "announce ready (init)", cmd => fun(#{tester := Tester, lport := Port}) -> - ev_ready(Tester, init, Port), + ?SEV_ANNOUNCE_READY(Tester, init, Port), ok end}, %% Termination #{desc => "await terminate", cmd => fun(#{tester := Tester} = State) -> - case ev_await_terminate(Tester, tester) of + case ?SEV_AWAIT_TERMINATE(Tester, tester) of ok -> {ok, maps:remove(tester, State)}; {error, _} = ERROR -> @@ -1651,10 +1623,7 @@ api_to_connect_tcp(InitState) -> end}, %% *** We are done *** - #{desc => "finish", - cmd => fun(_) -> - {ok, normal} - end} + ?SEV_FINISH_NORMAL ], TesterSeq = @@ -1729,12 +1698,12 @@ api_to_connect_tcp(InitState) -> %% *** Synchronize with the server *** #{desc => "order server start", cmd => fun(#{server := Server}) -> - ev_start(Server), + ?SEV_ANNOUNCE_START(Server), ok end}, #{desc => "await server ready (init)", cmd => fun(#{server := Server, local_sa := LSA} = State) -> - {ok, Port} = ev_await_ready(Server, server, init), + {ok, Port} = ?SEV_AWAIT_READY(Server, server, init), ServerSA = LSA#{port => Port}, {ok, State#{server_sa => ServerSA}} end}, @@ -1753,12 +1722,12 @@ api_to_connect_tcp(InitState) -> %% *** Terminate server *** #{desc => "order (server) terminate", cmd => fun(#{server := Server} = _State) -> - ev_terminate(Server), + ?SEV_ANNOUNCE_TERMINATE(Server), ok end}, #{desc => "await (server) down", cmd => fun(#{server := Server} = State) -> - ev_await_termination(Server), + ?SEV_AWAIT_TERMINATION(Server), State1 = maps:remove(server, State), State2 = maps:remove(server_sa, State1), {ok, State2} @@ -1783,22 +1752,19 @@ api_to_connect_tcp(InitState) -> end}, %% *** We are done *** - #{desc => "finish", - cmd => fun(_) -> - {ok, normal} - end} + ?SEV_FINISH_NORMAL ], i("create server evaluator"), ServerInitState = InitState, - Server = evaluator_start("server", ServerSeq, ServerInitState), + Server = ?SEV_START("server", ServerSeq, ServerInitState), i("create tester evaluator"), TesterInitState = InitState#{server => Server#ev.pid}, - Tester = evaluator_start("tester", TesterSeq, TesterInitState), + Tester = ?SEV_START("tester", TesterSeq, TesterInitState), i("await evaluator(s)"), - ok = await_evaluator_finish([Server, Tester]). + ok = ?SEV_AWAIT_FINISH([Server, Tester]). api_to_connect_tcp_await_timeout(Socks, To, ServerSA) -> @@ -1807,11 +1773,11 @@ api_to_connect_tcp_await_timeout(Socks, To, ServerSA) -> api_to_connect_tcp_await_timeout([], _To, _ServerSA, _ID) -> ?FAIL(unexpected_success); api_to_connect_tcp_await_timeout([Sock|Socks], To, ServerSA, ID) -> - ei("~w: try connect", [ID]), + ?SEV_IPRINT("~w: try connect", [ID]), Start = t(), case socket:connect(Sock, ServerSA, To) of {error, timeout} -> - ei("expected timeout (~w)", [ID]), + ?SEV_IPRINT("expected timeout (~w)", [ID]), Stop = t(), TDiff = tdiff(Start, Stop), if @@ -1821,13 +1787,13 @@ api_to_connect_tcp_await_timeout([Sock|Socks], To, ServerSA, ID) -> {error, {unexpected_timeout, TDiff, To}} end; {error, econnreset = Reason} -> - ei("failed connecting: ~p - giving up", [Reason]), + ?SEV_IPRINT("failed connecting: ~p - giving up", [Reason]), ok; {error, Reason} -> - ee("failed connecting: ~p", [Reason]), + ?SEV_EPRINT("failed connecting: ~p", [Reason]), ?FAIL({connect, Reason}); ok -> - ei("unexpected success (~w) - try next", [ID]), + ?SEV_IPRINT("unexpected success (~w) - try next", [ID]), api_to_connect_tcp_await_timeout(Socks, To, ServerSA, ID+1) end. @@ -1936,17 +1902,14 @@ api_to_accept_tcp(InitState) -> end}, %% *** We are done *** - #{desc => "finish", - cmd => fun(_) -> - {ok, normal} - end} + ?SEV_FINISH_NORMAL ], i("create tester evaluator"), - Tester = evaluator_start("tester", TesterSeq, InitState), + Tester = ?SEV_START("tester", TesterSeq, InitState), i("await evaluator"), - ok = await_evaluator_finish([Tester]). + ok = ?SEV_AWAIT_FINISH([Tester]). @@ -1994,7 +1957,7 @@ api_to_maccept_tcp(InitState) -> %% *** Init part *** #{desc => "await start", cmd => fun(State) -> - Tester = ev_await_start(), + Tester = ?SEV_AWAIT_START(), {ok, State#{tester => Tester}} end}, #{desc => "monitor tester", @@ -2032,14 +1995,14 @@ api_to_maccept_tcp(InitState) -> end}, #{desc => "announce ready (init)", cmd => fun(#{lsock := LSock, tester := Tester}) -> - ev_ready(Tester, init, LSock), + ?SEV_ANNOUNCE_READY(Tester, init, LSock), ok end}, %% *** The actual test *** #{desc => "await continue (accept)", cmd => fun(#{tester := Tester} = _State) -> - ev_await_continue(Tester, tester, accept) + ?SEV_AWAIT_CONTINUE(Tester, tester, accept) end}, #{desc => "attempt to accept (without success)", cmd => fun(#{lsock := LSock, timeout := To} = State) -> @@ -2048,8 +2011,8 @@ api_to_maccept_tcp(InitState) -> {error, timeout} -> {ok, State#{start => Start, stop => t()}}; {ok, Sock} -> - ee("Unexpected accept success: " - "~n ~p", [Sock]), + ?SEV_EPRINT("Unexpected accept success: " + "~n ~p", [Sock]), (catch socket:close(Sock)), {error, unexpected_success}; {error, _} = ERROR -> @@ -2068,14 +2031,14 @@ api_to_maccept_tcp(InitState) -> end}, #{desc => "announce ready (accept)", cmd => fun(#{tester := Tester}) -> - ev_ready(Tester, accept), + ?SEV_ANNOUNCE_READY(Tester, accept), ok end}, %% *** Terminate *** #{desc => "await terminate", cmd => fun(#{tester := Tester} = _State) -> - ev_await_terminate(Tester, tester), + ?SEV_AWAIT_TERMINATE(Tester, tester), ok end}, %% *** Close (listen) socket *** @@ -2086,10 +2049,7 @@ api_to_maccept_tcp(InitState) -> end}, %% *** We are done *** - #{desc => "finish", - cmd => fun(_) -> - {ok, normal} - end} + ?SEV_FINISH_NORMAL ], @@ -2098,7 +2058,7 @@ api_to_maccept_tcp(InitState) -> %% *** Init part *** #{desc => "await start", cmd => fun(State) -> - {Tester, LSock} = ev_await_start(), + {Tester, LSock} = ?SEV_AWAIT_START(), {ok, State#{tester => Tester, lsock => LSock}} @@ -2110,14 +2070,14 @@ api_to_maccept_tcp(InitState) -> end}, #{desc => "announce ready (init)", cmd => fun(#{tester := Tester} = _State) -> - ev_ready(Tester, init), + ?SEV_ANNOUNCE_READY(Tester, init), ok end}, %% *** The actual test part *** #{desc => "await continue (accept)", cmd => fun(#{tester := Tester} = _State) -> - ev_await_continue(Tester, tester, accept) + ?SEV_AWAIT_CONTINUE(Tester, tester, accept) end}, #{desc => "attempt to accept (without success)", cmd => fun(#{lsock := LSock, timeout := To} = State) -> @@ -2146,14 +2106,14 @@ api_to_maccept_tcp(InitState) -> end}, #{desc => "announce ready (accept)", cmd => fun(#{tester := Tester} = _State) -> - ev_ready(Tester, accept), + ?SEV_ANNOUNCE_READY(Tester, accept), ok end}, %% *** Terminate *** #{desc => "await terminate", cmd => fun(#{tester := Tester} = State) -> - case ev_await_terminate(Tester, tester) of + case ?SEV_AWAIT_TERMINATE(Tester, tester) of ok -> {ok, maps:remove(tester, State)}; {error, _} = ERROR -> @@ -2162,10 +2122,7 @@ api_to_maccept_tcp(InitState) -> end}, %% *** We are done *** - #{desc => "finish", - cmd => fun(_) -> - {ok, normal} - end} + ?SEV_FINISH_NORMAL ], @@ -2192,133 +2149,139 @@ api_to_maccept_tcp(InitState) -> %% Start the prim-acceptor #{desc => "start prim-acceptor", cmd => fun(#{prim_acceptor := Pid} = _State) -> - ev_start(Pid), + ?SEV_ANNOUNCE_START(Pid), ok end}, #{desc => "await prim-acceptor ready (init)", cmd => fun(#{prim_acceptor := Pid} = State) -> - {ok, Sock} = ev_await_ready(Pid, prim_acceptor, init), + {ok, Sock} = ?SEV_AWAIT_READY(Pid, prim_acceptor, init), {ok, State#{lsock => Sock}} end}, %% Start sec-acceptor-1 #{desc => "start sec-acceptor 1", cmd => fun(#{sec_acceptor1 := Pid, lsock := LSock} = _State) -> - ev_start(Pid, LSock), + ?SEV_ANNOUNCE_START(Pid, LSock), ok end}, #{desc => "await sec-acceptor 1 ready (init)", cmd => fun(#{sec_acceptor1 := Pid} = _State) -> - ev_await_ready(Pid, sec_acceptor1, init) + ?SEV_AWAIT_READY(Pid, sec_acceptor1, init) end}, %% Start sec-acceptor-2 #{desc => "start sec-acceptor 2", cmd => fun(#{sec_acceptor2 := Pid, lsock := LSock} = _State) -> - ev_start(Pid, LSock), + ?SEV_ANNOUNCE_START(Pid, LSock), ok end}, #{desc => "await sec-acceptor 2 ready (init)", cmd => fun(#{sec_acceptor2 := Pid} = _State) -> - ev_await_ready(Pid, sec_acceptor2, init) + ?SEV_AWAIT_READY(Pid, sec_acceptor2, init) end}, %% Activate the acceptor(s) #{desc => "active prim-acceptor", cmd => fun(#{prim_acceptor := Pid} = _State) -> - ev_continue(Pid, accept), + ?SEV_ANNOUNCE_CONTINUE(Pid, accept), ok end}, #{desc => "active sec-acceptor 1", cmd => fun(#{sec_acceptor1 := Pid} = _State) -> - ev_continue(Pid, accept), + ?SEV_ANNOUNCE_CONTINUE(Pid, accept), ok end}, #{desc => "active sec-acceptor 2", cmd => fun(#{sec_acceptor2 := Pid} = _State) -> - ev_continue(Pid, accept), + ?SEV_ANNOUNCE_CONTINUE(Pid, accept), ok end}, %% Await acceptor(s) completions #{desc => "await prim-acceptor ready (accept)", cmd => fun(#{prim_acceptor := Pid} = _State) -> - ev_await_ready(Pid, prim_acceptor, accept) + ?SEV_AWAIT_READY(Pid, prim_acceptor, accept) end}, #{desc => "await sec-acceptor 1 ready (accept)", cmd => fun(#{sec_acceptor1 := Pid} = _State) -> - ev_await_ready(Pid, sec_acceptor1, accept) + ?SEV_AWAIT_READY(Pid, sec_acceptor1, accept) end}, #{desc => "await sec-acceptor 2 ready (accept)", cmd => fun(#{sec_acceptor2 := Pid} = _State) -> - ev_await_ready(Pid, sec_acceptor2, accept) + ?SEV_AWAIT_READY(Pid, sec_acceptor2, accept) end}, %% Terminate #{desc => "order prim-acceptor to terminate", cmd => fun(#{prim_acceptor := Pid} = _State) -> - ev_terminate(Pid), + ?SEV_ANNOUNCE_TERMINATE(Pid), ok end}, #{desc => "await prim-acceptor termination", cmd => fun(#{prim_acceptor := Pid} = State) -> - ev_await_termination(Pid), - State1 = maps:remove(prim_acceptor, State), - {ok, State1} + case ?SEV_AWAIT_TERMINATION(Pid) of + ok -> + State1 = maps:remove(prim_acceptor, State), + {ok, State1}; + {error, _} = ERROR -> + ERROR + end end}, #{desc => "order sec-acceptor 1 to terminate", cmd => fun(#{sec_acceptor1 := Pid} = _State) -> - ev_terminate(Pid), + ?SEV_ANNOUNCE_TERMINATE(Pid), ok end}, #{desc => "await sec-acceptor 1 termination", cmd => fun(#{sec_acceptor1 := Pid} = State) -> - ev_await_termination(Pid), - State1 = maps:remove(sec_acceptor1, State), - {ok, State1} + case ?SEV_AWAIT_TERMINATION(Pid) of + ok -> + State1 = maps:remove(sec_acceptor1, State), + {ok, State1}; + {error, _} = ERROR -> + ERROR + end end}, #{desc => "order sec-acceptor 2 to terminate", cmd => fun(#{sec_acceptor2 := Pid} = _State) -> - ev_terminate(Pid), + ?SEV_ANNOUNCE_TERMINATE(Pid), ok end}, #{desc => "await sec-acceptor 2 termination", cmd => fun(#{sec_acceptor2 := Pid} = State) -> - ev_await_termination(Pid), - State1 = maps:remove(sec_acceptor2, State), - {ok, State1} + case ?SEV_AWAIT_TERMINATION(Pid) of + ok -> + State1 = maps:remove(sec_acceptor2, State), + {ok, State1}; + {error, _} = ERROR -> + ERROR + end end}, %% *** We are done *** - #{desc => "finish", - cmd => fun(_) -> - {ok, normal} - end} + ?SEV_FINISH_NORMAL ], i("create prim-acceptor evaluator"), PrimAInitState = InitState, - PrimAcceptor = evaluator_start("prim-acceptor", - PrimAcceptorSeq, PrimAInitState), + PrimAcceptor = ?SEV_START("prim-acceptor", PrimAcceptorSeq, PrimAInitState), i("create sec-acceptor 1 evaluator"), SecAInitState1 = maps:remove(domain, InitState), - SecAcceptor1 = evaluator_start("sec-acceptor-1", - SecAcceptorSeq, SecAInitState1), + SecAcceptor1 = ?SEV_START("sec-acceptor-1", SecAcceptorSeq, SecAInitState1), i("create sec-acceptor 2 evaluator"), SecAInitState2 = SecAInitState1, - SecAcceptor2 = evaluator_start("sec-acceptor-2", - SecAcceptorSeq, SecAInitState2), + SecAcceptor2 = ?SEV_START("sec-acceptor-2", SecAcceptorSeq, SecAInitState2), i("create tester evaluator"), TesterInitState = #{prim_acceptor => PrimAcceptor#ev.pid, sec_acceptor1 => SecAcceptor1#ev.pid, sec_acceptor2 => SecAcceptor2#ev.pid}, - Tester = evaluator_start("tester", TesterSeq, TesterInitState), + Tester = ?SEV_START("tester", TesterSeq, TesterInitState), i("await evaluator(s)"), - ok = await_evaluator_finish([PrimAcceptor, SecAcceptor1, SecAcceptor2, Tester]). + ok = ?SEV_AWAIT_FINISH([PrimAcceptor, SecAcceptor1, SecAcceptor2, Tester]). @@ -2510,7 +2473,7 @@ api_to_receive_tcp(InitState) -> %% *** Wait for start order *** #{desc => "await start (from tester)", cmd => fun(State) -> - Tester = ev_await_start(), + Tester = ?SEV_AWAIT_START(), {ok, State#{tester => Tester}} end}, #{desc => "monitor tester", @@ -2550,14 +2513,14 @@ api_to_receive_tcp(InitState) -> end}, #{desc => "announce ready (init)", cmd => fun(#{tester := Tester, lport := Port}) -> - ev_ready(Tester, init, Port), + ?SEV_ANNOUNCE_READY(Tester, init, Port), ok end}, %% *** The actual test *** #{desc => "await continue (accept and recv)", cmd => fun(#{tester := Tester}) -> - ev_await_continue(Tester, tester, accept_recv) + ?SEV_AWAIT_CONTINUE(Tester, tester, accept_recv) end}, #{desc => "attempt accept", cmd => fun(#{lsock := LSock} = State) -> @@ -2594,15 +2557,14 @@ api_to_receive_tcp(InitState) -> end}, #{desc => "announce ready (recv timeout success)", cmd => fun(#{tester := Tester} = _State) -> - %% ok - ev_ready(Tester, accept_recv), + ?SEV_ANNOUNCE_READY(Tester, accept_recv), ok end}, %% *** Termination *** #{desc => "await terminate", cmd => fun(#{tester := Tester} = State) -> - case ev_await_terminate(Tester, tester) of + case ?SEV_AWAIT_TERMINATE(Tester, tester) of ok -> {ok, maps:remove(tester, State)}; {error, _} = ERROR -> @@ -2621,10 +2583,7 @@ api_to_receive_tcp(InitState) -> end}, %% *** We are done *** - #{desc => "finish", - cmd => fun(_) -> - {ok, normal} - end} + ?SEV_FINISH_NORMAL ], ClientSeq = @@ -2632,7 +2591,7 @@ api_to_receive_tcp(InitState) -> %% *** Wait for start order part *** #{desc => "await start (from tester)", cmd => fun(State) -> - {Tester, Port} = ev_await_start(), + {Tester, Port} = ?SEV_AWAIT_START(), {ok, State#{tester => Tester, server_port => Port}} end}, @@ -2671,14 +2630,14 @@ api_to_receive_tcp(InitState) -> end}, #{desc => "announce ready (init)", cmd => fun(#{tester := Tester} = _State) -> - ev_ready(Tester, init), + ?SEV_ANNOUNCE_READY(Tester, init), ok end}, %% *** The actual test *** #{desc => "await continue (with connect)", cmd => fun(#{tester := Tester} = _State) -> - ev_await_continue(Tester, tester, connect) + ?SEV_AWAIT_CONTINUE(Tester, tester, connect) end}, #{desc => "connect", cmd => fun(#{sock := Sock, server_sa := SSA}) -> @@ -2689,7 +2648,7 @@ api_to_receive_tcp(InitState) -> %% *** Termination *** #{desc => "await terminate", cmd => fun(#{tester := Tester} = State) -> - case ev_await_terminate(Tester, tester) of + case ?SEV_AWAIT_TERMINATE(Tester, tester) of ok -> {ok, maps:remove(tester, State)}; {error, _} = ERROR -> @@ -2703,10 +2662,7 @@ api_to_receive_tcp(InitState) -> end}, %% *** We are done *** - #{desc => "finish", - cmd => fun(_) -> - {ok, normal} - end} + ?SEV_FINISH_NORMAL ], TesterSeq = @@ -2726,90 +2682,95 @@ api_to_receive_tcp(InitState) -> %% *** Activate server *** #{desc => "start server", cmd => fun(#{server := Server} = _State) -> - ev_start(Server), + ?SEV_ANNOUNCE_START(Server), ok end}, #{desc => "await server ready (init)", cmd => fun(#{server := Server} = State) -> - {ok, Port} = ev_await_ready(Server, server, init), + {ok, Port} = ?SEV_AWAIT_READY(Server, server, init), {ok, State#{server_port => Port}} end}, #{desc => "order server to continue (with accept)", cmd => fun(#{server := Server} = _State) -> - ev_continue(Server, accept_recv), + ?SEV_ANNOUNCE_CONTINUE(Server, accept_recv), ok end}, %% *** Activate client *** #{desc => "start client", cmd => fun(#{client := Client, server_port := Port} = _State) -> - ev_start(Client, Port), + ?SEV_ANNOUNCE_START(Client, Port), ok end}, #{desc => "await client ready (init)", cmd => fun(#{client := Client} = _State) -> - ev_await_ready(Client, client, init) + ?SEV_AWAIT_READY(Client, client, init) end}, %% *** The actual test *** #{desc => "order client to continue (with connect)", cmd => fun(#{client := Client} = _State) -> - ev_continue(Client, connect), + ?SEV_ANNOUNCE_CONTINUE(Client, connect), ok end}, #{desc => "await server ready (accept/recv)", cmd => fun(#{server := Server} = _State) -> - ev_await_ready(Server, server, accept_recv) + ?SEV_AWAIT_READY(Server, server, accept_recv) end}, %% *** Termination *** #{desc => "order client to terminate", cmd => fun(#{client := Client} = _State) -> - ev_terminate(Client), + ?SEV_ANNOUNCE_TERMINATE(Client), ok end}, #{desc => "await client termination", cmd => fun(#{client := Client} = State) -> - ev_await_termination(Client), - State1 = maps:remove(client, State), - {ok, State1} + case ?SEV_AWAIT_TERMINATION(Client) of + ok -> + State1 = maps:remove(client, State), + {ok, State1}; + {error, _} = ERROR -> + ERROR + end end}, #{desc => "order server to terminate", cmd => fun(#{server := Server} = _State) -> - ev_terminate(Server), + ?SEV_ANNOUNCE_TERMINATE(Server), ok end}, #{desc => "await server termination", cmd => fun(#{server := Server} = State) -> - ev_await_termination(Server), - State1 = maps:remove(server, State), - State2 = maps:remove(server_port, State1), - {ok, State2} + case ?SEV_AWAIT_TERMINATION(Server) of + ok -> + State1 = maps:remove(server, State), + State2 = maps:remove(server_port, State1), + {ok, State2}; + {error, _} = ERROR -> + ERROR + end end}, %% *** We are done *** - #{desc => "finish", - cmd => fun(_) -> - {ok, normal} - end} + ?SEV_FINISH_NORMAL ], i("start server evaluator"), ServerInitState = InitState, - Server = evaluator_start("server", ServerSeq, ServerInitState), + Server = ?SEV_START("server", ServerSeq, ServerInitState), i("start client evaluator"), ClientInitState = InitState, - Client = evaluator_start("client", ClientSeq, ClientInitState), + Client = ?SEV_START("client", ClientSeq, ClientInitState), i("start tester evaluator"), TesterInitState = #{server => Server#ev.pid, client => Client#ev.pid}, - Tester = evaluator_start("tester", TesterSeq, TesterInitState), + Tester = ?SEV_START("tester", TesterSeq, TesterInitState), i("await evaluator(s)"), - ok = await_evaluator_finish([Server, Client, Tester]). + ok = ?SEV_AWAIT_FINISH([Server, Client, Tester]). @@ -2917,17 +2878,14 @@ api_to_receive_udp(InitState) -> end}, %% *** We are done *** - #{desc => "finish", - cmd => fun(_) -> - {ok, normal} - end} + ?SEV_FINISH_NORMAL ], i("start tester evaluator"), - Tester = evaluator_start("tester", TesterSeq, InitState), + Tester = ?SEV_START("tester", TesterSeq, InitState), i("await evaluator"), - ok = await_evaluator_finish([Tester]). + ok = ?SEV_AWAIT_FINISH([Tester]). @@ -3114,16 +3072,16 @@ sc_cpe_socket_cleanup(InitState) -> %% *** Wait for start order part *** #{desc => "await start (from tester)", cmd => fun(State) -> - Tester = ev_await_start(), + Tester = ?SEV_AWAIT_START(), {ok, State#{tester => Tester}} end}, - - %% *** Init part *** #{desc => "monitor tester", cmd => fun(#{tester := Tester} = _State) -> _MRef = erlang:monitor(process, Tester), ok end}, + + %% *** Init part *** #{desc => "create socket", cmd => fun(#{domain := Domain, type := Type, @@ -3137,7 +3095,7 @@ sc_cpe_socket_cleanup(InitState) -> end}, #{desc => "announce ready (init)", cmd => fun(#{tester := Tester, sock := Sock} = _State) -> - ev_ready(Tester, init, Sock), + ?SEV_ANNOUNCE_READY(Tester, init, Sock), ok end}, @@ -3145,7 +3103,7 @@ sc_cpe_socket_cleanup(InitState) -> %% We *intentially* leave the socket "as is", no explicit close #{desc => "await terminate (from tester)", cmd => fun(#{tester := Tester} = State) -> - case ev_await_terminate(Tester, tester) of + case ?SEV_AWAIT_TERMINATE(Tester, tester) of ok -> {ok, maps:remove(tester, State)}; {error, _} = ERROR -> @@ -3154,10 +3112,7 @@ sc_cpe_socket_cleanup(InitState) -> end}, %% *** We are done *** - #{desc => "finish", - cmd => fun(_) -> - {ok, normal} - end} + ?SEV_FINISH_NORMAL ], TesterSeq = @@ -3170,12 +3125,12 @@ sc_cpe_socket_cleanup(InitState) -> end}, #{desc => "order (owner) start", cmd => fun(#{owner := Pid} = _State) -> - ev_start(Pid), + ?SEV_ANNOUNCE_START(Pid), ok end}, #{desc => "await (owner) ready", cmd => fun(#{owner := Pid} = State) -> - {ok, Sock} = ev_await_ready(Pid, owner, init), + {ok, Sock} = ?SEV_AWAIT_READY(Pid, owner, init), {ok, State#{sock => Sock}} end}, #{desc => "verify owner as controlling-process", @@ -3191,13 +3146,17 @@ sc_cpe_socket_cleanup(InitState) -> end}, #{desc => "order (owner) terminate", cmd => fun(#{owner := Pid} = _State) -> - ev_terminate(Pid), + ?SEV_ANNOUNCE_TERMINATE(Pid), ok end}, #{desc => "await (owner) termination", cmd => fun(#{owner := Pid} = _State) -> - ev_await_termination(Pid), - ok + case ?SEV_AWAIT_TERMINATION(Pid) of + ok -> + ok; + {error, _} = ERROR -> + ERROR + end end}, %% The reason we get closed, is that as long as there is a ref to %% the resource (socket), then it will not be garbage collected. @@ -3209,27 +3168,24 @@ sc_cpe_socket_cleanup(InitState) -> {error, closed} -> ok; {error, Reason} -> - ei("expected failure: ~p", [Reason]), + ?SEV_IPRINT("expected failure: ~p", [Reason]), {error, {unexpected_failure, Reason}} end end}, %% *** We are done *** - #{desc => "finish", - cmd => fun(_) -> - {ok, normal} - end} + ?SEV_FINISH_NORMAL ], i("start (socket) owner evaluator"), - #ev{pid = Pid} = Owner = evaluator_start("owner", OwnerSeq, InitState), + Owner = ?SEV_START("owner", OwnerSeq, InitState), i("start tester evaluator"), - TesterInitState = #{owner => Pid}, - Tester = evaluator_start("tester", TesterSeq, TesterInitState), + TesterInitState = #{owner => Owner#ev.pid}, + Tester = ?SEV_START("tester", TesterSeq, TesterInitState), i("await evaluator"), - ok = await_evaluator_finish([Owner, Tester]). + ok = ?SEV_AWAIT_FINISH([Owner, Tester]). @@ -3297,7 +3253,7 @@ sc_lc_receive_response_tcp(InitState) -> %% *** Wait for start order part *** #{desc => "await start (from tester)", cmd => fun(State) -> - Tester = ev_await_start(), + Tester = ?SEV_AWAIT_START(), {ok, State#{tester => Tester}} end}, #{desc => "monitor tester", @@ -3339,24 +3295,27 @@ sc_lc_receive_response_tcp(InitState) -> end}, #{desc => "announce ready (init)", cmd => fun(#{tester := Tester, lport := Port}) -> - ev_ready(Tester, init, Port), + ?SEV_ANNOUNCE_READY(Tester, init, Port), ok end}, %% The actual test #{desc => "await continue (accept)", cmd => fun(#{tester := Tester} = State) -> - {ok, {H1, H2, H3}} = - ev_await_continue(Tester, tester, accept), - {ok, State#{handler1 => H1, - handler2 => H2, - handler3 => H3}} + case ?SEV_AWAIT_CONTINUE(Tester, tester, accept) of + {ok, {H1, H2, H3}} -> + {ok, State#{handler1 => H1, + handler2 => H2, + handler3 => H3}}; + {error, _} = ERROR -> + ERROR + end end}, #{desc => "await accept", cmd => fun(#{lsock := LSock} = State) -> case socket:accept(LSock) of {ok, Sock} -> - ei("connection accepted"), + ?SEV_IPRINT("connection accepted"), {ok, State#{csock => Sock}}; {error, _} = ERROR -> ERROR @@ -3364,27 +3323,27 @@ sc_lc_receive_response_tcp(InitState) -> end}, #{desc => "announce ready (accept)", cmd => fun(#{tester := Tester}) -> - ev_ready(Tester, accept), + ?SEV_ANNOUNCE_READY(Tester, accept), ok end}, #{desc => "transfer connection to handler 1", cmd => fun(#{handler1 := Handler, csock := Sock}) -> - ev_continue(Handler, transfer, Sock), + ?SEV_ANNOUNCE_CONTINUE(Handler, transfer, Sock), ok end}, #{desc => "transfer connection to handler 2", cmd => fun(#{handler2 := Handler, csock := Sock}) -> - ev_continue(Handler, transfer, Sock), + ?SEV_ANNOUNCE_CONTINUE(Handler, transfer, Sock), ok end}, #{desc => "transfer connection to handler 3", cmd => fun(#{handler3 := Handler, csock := Sock}) -> - ev_continue(Handler, transfer, Sock), + ?SEV_ANNOUNCE_CONTINUE(Handler, transfer, Sock), ok end}, #{desc => "await continue (close)", cmd => fun(#{tester := Tester} = _State) -> - ev_await_continue(Tester, tester, close), + ?SEV_AWAIT_CONTINUE(Tester, tester, close), ok end}, #{desc => "close the connection socket", @@ -3398,14 +3357,14 @@ sc_lc_receive_response_tcp(InitState) -> end}, #{desc => "announce ready (close)", cmd => fun(#{tester := Tester}) -> - ev_ready(Tester, close), + ?SEV_ANNOUNCE_READY(Tester, close), ok end}, %% *** Terminate *** #{desc => "await terminate", cmd => fun(#{tester := Tester} = State) -> - case ev_await_terminate(Tester, tester) of + case ?SEV_AWAIT_TERMINATE(Tester, tester) of ok -> {ok, maps:remove(tester, State)}; {error, _} = ERROR -> @@ -3425,10 +3384,7 @@ sc_lc_receive_response_tcp(InitState) -> end}, %% *** We are done *** - #{desc => "finish", - cmd => fun(_) -> - {ok, normal} - end} + ?SEV_FINISH_NORMAL ], %% The point of this is to perform the recv for which we are testing the reponse @@ -3437,7 +3393,7 @@ sc_lc_receive_response_tcp(InitState) -> %% *** Wait for start order part *** #{desc => "await start (from tester)", cmd => fun(State) -> - {Tester, Acceptor} = ev_await_start(), + {Tester, Acceptor} = ?SEV_AWAIT_START(), {ok, State#{tester => Tester, acceptor => Acceptor}} end}, @@ -3453,48 +3409,51 @@ sc_lc_receive_response_tcp(InitState) -> end}, #{desc => "announce ready (init)", cmd => fun(#{tester := Tester}) -> - ev_ready(Tester, init), + ?SEV_ANNOUNCE_READY(Tester, init), ok end}, %% The actual test #{desc => "await continue (transfer)", cmd => fun(#{acceptor := Pid} = State) -> - {ok, Sock} = - ev_await_continue(Pid, acceptor, transfer), - {ok, State#{sock => Sock}} + case ?SEV_AWAIT_CONTINUE(Pid, acceptor, transfer) of + {ok, Sock} -> + {ok, State#{sock => Sock}}; + {error, _} = ERROR -> + ERROR + end end}, #{desc => "announce ready (transfer)", cmd => fun(#{tester := Tester}) -> - ev_ready(Tester, transfer), + ?SEV_ANNOUNCE_READY(Tester, transfer), ok end}, #{desc => "attempt recv (=> closed)", cmd => fun(#{sock := Sock, recv := Recv} = State) -> case Recv(Sock) of {ok, _Data} -> - ee("Unexpected data received"), + ?SEV_EPRINT("Unexpected data received"), {error, unexpected_success}; {error, closed} -> - ei("received expected 'closed' result"), + ?SEV_IPRINT("received expected 'closed' result"), State1 = maps:remove(sock, State), {ok, State1}; {error, Reason} = ERROR -> - ee("Unexpected read faulure: " - "~n ~p", [Reason]), + ?SEV_EPRINT("Unexpected read faulure: " + "~n ~p", [Reason]), ERROR end end}, #{desc => "announce ready (recv closed)", cmd => fun(#{tester := Tester}) -> - ev_ready(Tester, recv_closed), + ?SEV_ANNOUNCE_READY(Tester, recv_closed), ok end}, %% *** Terminate *** #{desc => "await terminate", cmd => fun(#{tester := Tester} = State) -> - case ev_await_terminate(Tester, tester) of + case ?SEV_AWAIT_TERMINATE(Tester, tester) of ok -> {ok, maps:remove(tester, State)}; {error, _} = ERROR -> @@ -3504,10 +3463,7 @@ sc_lc_receive_response_tcp(InitState) -> %% *** We are done *** - #{desc => "finish", - cmd => fun(_) -> - {ok, normal} - end} + ?SEV_FINISH_NORMAL ], %% The point of this is basically just to create the connection. @@ -3516,7 +3472,7 @@ sc_lc_receive_response_tcp(InitState) -> %% *** Wait for start order part *** #{desc => "await start (from tester)", cmd => fun(State) -> - Tester = ev_await_start(), + Tester = ?SEV_AWAIT_START(), {ok, State#{tester => Tester}} end}, #{desc => "monitor tester", @@ -3554,16 +3510,20 @@ sc_lc_receive_response_tcp(InitState) -> end}, #{desc => "announce ready (init)", cmd => fun(#{tester := Tester} = _State) -> - ev_ready(Tester, init), + ?SEV_ANNOUNCE_READY(Tester, init), ok end}, %% The actual test #{desc => "await continue (connect)", cmd => fun(#{tester := Tester, local_sa := LSA} = State) -> - {ok, Port} = ev_await_continue(Tester, tester, connect), - ServerSA = LSA#{port => Port}, - {ok, State#{server_sa => ServerSA}} + case ?SEV_AWAIT_CONTINUE(Tester, tester, connect) of + {ok, Port} -> + ServerSA = LSA#{port => Port}, + {ok, State#{server_sa => ServerSA}}; + {error, _} = ERROR -> + ERROR + end end}, #{desc => "connect to server", cmd => fun(#{sock := Sock, server_sa := ServerSA}) -> @@ -3571,14 +3531,14 @@ sc_lc_receive_response_tcp(InitState) -> end}, #{desc => "announce ready (connect)", cmd => fun(#{tester := Tester} = _State) -> - ev_ready(Tester, connect), + ?SEV_ANNOUNCE_READY(Tester, connect), ok end}, %% *** Terminate *** #{desc => "await terminate (from tester)", cmd => fun(#{tester := Tester} = State) -> - case ev_await_terminate(Tester, tester) of + case ?SEV_AWAIT_TERMINATE(Tester, tester) of ok -> {ok, maps:remove(tester, State)}; {error, _} = ERROR -> @@ -3592,10 +3552,7 @@ sc_lc_receive_response_tcp(InitState) -> end}, %% *** We are done *** - #{desc => "finish", - cmd => fun(_) -> - {ok, normal} - end} + ?SEV_FINISH_NORMAL ], TesterSeq = @@ -3630,53 +3587,57 @@ sc_lc_receive_response_tcp(InitState) -> %% Start the acceptor #{desc => "order acceptor start", cmd => fun(#{acceptor := Pid} = _State) -> - ev_start(Pid), + ?SEV_ANNOUNCE_START(Pid), ok end}, #{desc => "await acceptor ready (init)", cmd => fun(#{acceptor := Pid} = State) -> - {ok, Port} = ev_await_ready(Pid, acceptor, init), - {ok, State#{lport => Port}} + case ?SEV_AWAIT_READY(Pid, acceptor, init) of + {ok, Port} -> + {ok, State#{lport => Port}}; + {error, _} = ERROR -> + ERROR + end end}, %% Start the handler(s) #{desc => "order handler 1 start", cmd => fun(#{acceptor := Acceptor, handler1 := Pid} = _State) -> - ev_start(Pid, Acceptor), + ?SEV_ANNOUNCE_START(Pid, Acceptor), ok end}, #{desc => "await handler 1 ready (init)", cmd => fun(#{handler1 := Pid} = _State) -> - ev_await_ready(Pid, handler1, init) + ?SEV_AWAIT_READY(Pid, handler1, init) end}, #{desc => "order handler 2 start", cmd => fun(#{acceptor := Acceptor, handler2 := Pid} = _State) -> - ev_start(Pid, Acceptor), + ?SEV_ANNOUNCE_START(Pid, Acceptor), ok end}, #{desc => "await handler 2 ready (init)", cmd => fun(#{handler2 := Pid} = _State) -> - ev_await_ready(Pid, handler2, init) + ?SEV_AWAIT_READY(Pid, handler2, init) end}, #{desc => "order handler 3 start", cmd => fun(#{acceptor := Acceptor, handler3 := Pid} = _State) -> - ev_start(Pid, Acceptor), + ?SEV_ANNOUNCE_START(Pid, Acceptor), ok end}, #{desc => "await handler 3 ready (init)", cmd => fun(#{handler3 := Pid} = _State) -> - ev_await_ready(Pid, handler3, init) + ?SEV_AWAIT_READY(Pid, handler3, init) end}, %% Start the client #{desc => "order client start", cmd => fun(#{client := Pid} = _State) -> - ev_start(Pid), + ?SEV_ANNOUNCE_START(Pid), ok end}, #{desc => "await client ready (init)", cmd => fun(#{client := Pid} = _State) -> - ev_await_ready(Pid, client, init) + ?SEV_AWAIT_READY(Pid, client, init) end}, %% The actual test @@ -3685,143 +3646,152 @@ sc_lc_receive_response_tcp(InitState) -> handler1 := H1, handler2 := H2, handler3 := H3} = _State) -> - ev_continue(Pid, accept, {H1, H2, H3}), - ok - end}, - #{desc => "sleep", - cmd => fun(_) -> - ?SLEEP(?SECS(1)), + ?SEV_ANNOUNCE_CONTINUE(Pid, accept, {H1, H2, H3}), ok end}, + ?SEV_SLEEP(?SECS(1)), #{desc => "order client to continue (connect)", cmd => fun(#{client := Pid, lport := Port} = _State) -> - ev_continue(Pid, connect, Port), + ?SEV_ANNOUNCE_CONTINUE(Pid, connect, Port), ok end}, #{desc => "await acceptor ready (accept)", cmd => fun(#{acceptor := Pid} = _State) -> - ok = ev_await_ready(Pid, acceptor, accept) + ok = ?SEV_AWAIT_READY(Pid, acceptor, accept) end}, #{desc => "await client ready (connect)", cmd => fun(#{client := Pid} = _State) -> - ok = ev_await_ready(Pid, client, connect) + ok = ?SEV_AWAIT_READY(Pid, client, connect) end}, #{desc => "await handler 1 ready (transfer)", cmd => fun(#{handler1 := Pid} = _State) -> - ok = ev_await_ready(Pid, handler1, transfer) + ok = ?SEV_AWAIT_READY(Pid, handler1, transfer) end}, #{desc => "await handler 2 ready (transfer)", cmd => fun(#{handler2 := Pid} = _State) -> - ok = ev_await_ready(Pid, handler2, transfer) + ok = ?SEV_AWAIT_READY(Pid, handler2, transfer) end}, #{desc => "await handler 3 ready (transfer)", cmd => fun(#{handler3 := Pid} = _State) -> - ok = ev_await_ready(Pid, handler3, transfer) - end}, - #{desc => "sleep", - cmd => fun(_State) -> - ?SLEEP(?SECS(1)), - ok + ok = ?SEV_AWAIT_READY(Pid, handler3, transfer) end}, + ?SEV_SLEEP(?SECS(1)), #{desc => "order acceptor to continue (close connection socket)", cmd => fun(#{acceptor := Pid} = _State) -> - ev_continue(Pid, close), + ?SEV_ANNOUNCE_CONTINUE(Pid, close), ok end}, #{desc => "await acceptor ready (close)", cmd => fun(#{acceptor := Pid} = _State) -> - ok = ev_await_ready(Pid, acceptor, close) + ok = ?SEV_AWAIT_READY(Pid, acceptor, close) end}, #{desc => "await handler 1 ready (recv closed)", cmd => fun(#{handler1 := Pid} = _State) -> - ok = ev_await_ready(Pid, handler1, recv_closed) + ok = ?SEV_AWAIT_READY(Pid, handler1, recv_closed) end}, #{desc => "await handler 2 ready (recv closed)", cmd => fun(#{handler2 := Pid} = _State) -> - ok = ev_await_ready(Pid, handler2, recv_closed) + ok = ?SEV_AWAIT_READY(Pid, handler2, recv_closed) end}, #{desc => "await handler 3 ready (recv closed)", cmd => fun(#{handler3 := Pid} = _State) -> - ok = ev_await_ready(Pid, handler3, recv_closed) + ok = ?SEV_AWAIT_READY(Pid, handler3, recv_closed) end}, %% Terminations #{desc => "order client to terminate", cmd => fun(#{client := Pid} = _State) -> - ev_terminate(Pid), + ?SEV_ANNOUNCE_TERMINATE(Pid), ok end}, #{desc => "await client termination", cmd => fun(#{client := Pid} = State) -> - ev_await_termination(Pid), - {ok, maps:remove(client, State)} + case ?SEV_AWAIT_TERMINATION(Pid) of + ok -> + {ok, maps:remove(client, State)}; + {error, _} = ERROR -> + ERROR + end end}, #{desc => "order handler 1 to terminate", cmd => fun(#{handler1 := Pid} = _State) -> - ev_terminate(Pid), + ?SEV_ANNOUNCE_TERMINATE(Pid), ok end}, #{desc => "await handler 1 termination", cmd => fun(#{handler1 := Pid} = State) -> - ev_await_termination(Pid), - {ok, maps:remove(handler1, State)} + case ?SEV_AWAIT_TERMINATION(Pid) of + ok -> + {ok, maps:remove(handler1, State)}; + {error, _} = ERROR -> + ERROR + end end}, #{desc => "order handler 2 to terminate", cmd => fun(#{handler2 := Pid} = _State) -> - ev_terminate(Pid), + ?SEV_ANNOUNCE_TERMINATE(Pid), ok end}, #{desc => "await handler 2 termination", cmd => fun(#{handler2 := Pid} = State) -> - ev_await_termination(Pid), - {ok, maps:remove(handler2, State)} + case ?SEV_AWAIT_TERMINATION(Pid) of + ok -> + {ok, maps:remove(handler2, State)}; + {error, _} = ERROR -> + ERROR + end end}, #{desc => "order handler 3 to terminate", cmd => fun(#{handler3 := Pid} = _State) -> - ev_terminate(Pid), + ?SEV_ANNOUNCE_TERMINATE(Pid), ok end}, #{desc => "await handler 3 termination", cmd => fun(#{handler3 := Pid} = State) -> - ev_await_termination(Pid), - {ok, maps:remove(handler3, State)} + case ?SEV_AWAIT_TERMINATION(Pid) of + ok -> + {ok, maps:remove(handler3, State)}; + {error, _} = ERROR -> + ERROR + end end}, #{desc => "order acceptor to terminate", cmd => fun(#{acceptor := Pid} = _State) -> - ev_terminate(Pid), + ?SEV_ANNOUNCE_TERMINATE(Pid), ok end}, #{desc => "await acceptor termination", cmd => fun(#{acceptor := Pid} = State) -> - ev_await_termination(Pid), - {ok, maps:remove(acceptor, State)} + case ?SEV_AWAIT_TERMINATION(Pid) of + ok -> + {ok, maps:remove(acceptor, State)}; + {error, _} = ERROR -> + ERROR + end end}, %% *** We are done *** - #{desc => "finish", - cmd => fun(_) -> - {ok, normal} - end} + ?SEV_FINISH_NORMAL ], i("start acceptor evaluator"), AccInitState = InitState, - Acceptor = evaluator_start("acceptor", AcceptorSeq, AccInitState), + Acceptor = ?SEV_START("acceptor", AcceptorSeq, AccInitState), i("start handler 1 evaluator"), HandlerInitState = #{recv => maps:get(recv, InitState)}, - Handler1 = evaluator_start("handler-1", HandlerSeq, HandlerInitState), + Handler1 = ?SEV_START("handler-1", HandlerSeq, HandlerInitState), i("start handler 2 evaluator"), - Handler2 = evaluator_start("handler-2", HandlerSeq, HandlerInitState), + Handler2 = ?SEV_START("handler-2", HandlerSeq, HandlerInitState), i("start handler 3 evaluator"), - Handler3 = evaluator_start("handler-3", HandlerSeq, HandlerInitState), + Handler3 = ?SEV_START("handler-3", HandlerSeq, HandlerInitState), i("start client evaluator"), ClientInitState = InitState, - Client = evaluator_start("client", ClientSeq, ClientInitState), + Client = ?SEV_START("client", ClientSeq, ClientInitState), i("start tester evaluator"), TesterInitState = #{acceptor => Acceptor#ev.pid, @@ -3829,12 +3799,12 @@ sc_lc_receive_response_tcp(InitState) -> handler2 => Handler2#ev.pid, handler3 => Handler3#ev.pid, client => Client#ev.pid}, - Tester = evaluator_start("tester", TesterSeq, TesterInitState), + Tester = ?SEV_START("tester", TesterSeq, TesterInitState), i("await evaluator"), - ok = await_evaluator_finish([Acceptor, - Handler1, Handler2, Handler3, - Client, Tester]). + ok = ?SEV_AWAIT_FINISH([Acceptor, + Handler1, Handler2, Handler3, + Client, Tester]). @@ -3887,10 +3857,10 @@ sc_lc_recvfrom_response_udp6(_Config) when is_list(_Config) -> sc_lc_receive_response_udp(InitState) -> PrimServerSeq = [ - %% *** Init part *** + %% *** Wait for start order part *** #{desc => "await start", cmd => fun(State) -> - Tester = ev_await_start(), + Tester = ?SEV_AWAIT_START(), {ok, State#{tester => Tester}} end}, #{desc => "monitor tester", @@ -3898,6 +3868,8 @@ sc_lc_receive_response_udp(InitState) -> _MRef = erlang:monitor(process, Tester), ok end}, + + %% *** Init part *** #{desc => "local address", cmd => fun(#{domain := Domain} = State) -> LAddr = which_local_addr(Domain), @@ -3917,15 +3889,19 @@ sc_lc_receive_response_udp(InitState) -> end}, #{desc => "announce ready (init)", cmd => fun(#{tester := Tester, sock := Sock}) -> - ev_ready(Tester, init, Sock), + ?SEV_ANNOUNCE_READY(Tester, init, Sock), ok end}, %% The actual test #{desc => "await continue (recv, with timeout)", cmd => fun(#{tester := Tester} = State) -> - {ok, Timeout} = ev_await_continue(Tester, tester, recv), - {ok, State#{timeout => Timeout}} + case ?SEV_AWAIT_CONTINUE(Tester, tester, recv) of + {ok, Timeout} -> + {ok, State#{timeout => Timeout}}; + {error, _} = ERROR -> + ERROR + end end}, #{desc => "receive, with timeout", cmd => fun(#{sock := Sock, recv := Recv, timeout := Timeout}) -> @@ -3940,12 +3916,12 @@ sc_lc_receive_response_udp(InitState) -> end}, #{desc => "announce ready (recv, with timeout)", cmd => fun(#{tester := Tester}) -> - ev_ready(Tester, recv), + ?SEV_ANNOUNCE_READY(Tester, recv), ok end}, #{desc => "await continue (close)", cmd => fun(#{tester := Tester} = _State) -> - ok = ev_await_continue(Tester, tester, close) + ok = ?SEV_AWAIT_CONTINUE(Tester, tester, close) end}, #{desc => "close socket", cmd => fun(#{sock := Sock} = State) -> @@ -3958,14 +3934,14 @@ sc_lc_receive_response_udp(InitState) -> end}, #{desc => "announce ready (close)", cmd => fun(#{tester := Tester}) -> - ev_ready(Tester, close), + ?SEV_ANNOUNCE_READY(Tester, close), ok end}, %% Termination #{desc => "await terminate (from tester)", cmd => fun(#{tester := Tester} = State) -> - case ev_await_terminate(Tester, terminate) of + case ?SEV_AWAIT_TERMINATE(Tester, terminate) of ok -> {ok, maps:remove(tester, State)}; {error, _} = ERROR -> @@ -3974,10 +3950,7 @@ sc_lc_receive_response_udp(InitState) -> end}, %% *** We are done *** - #{desc => "finish", - cmd => fun(_) -> - {ok, normal} - end} + ?SEV_FINISH_NORMAL ], SecServerSeq = @@ -3985,7 +3958,7 @@ sc_lc_receive_response_udp(InitState) -> %% *** Init part *** #{desc => "await start", cmd => fun(State) -> - {Tester, Sock} = ev_await_start(), + {Tester, Sock} = ?SEV_AWAIT_START(), {ok, State#{tester => Tester, sock => Sock}} end}, #{desc => "monitor tester", @@ -3995,14 +3968,14 @@ sc_lc_receive_response_udp(InitState) -> end}, #{desc => "announce ready (init)", cmd => fun(#{tester := Tester}) -> - ev_ready(Tester, init), + ?SEV_ANNOUNCE_READY(Tester, init), ok end}, %% The actual test #{desc => "await continue (recv)", cmd => fun(#{tester := Tester} = _State) -> - ok = ev_await_continue(Tester, tester, recv) + ok = ?SEV_AWAIT_CONTINUE(Tester, tester, recv) end}, #{desc => "receive", @@ -4018,14 +3991,14 @@ sc_lc_receive_response_udp(InitState) -> end}, #{desc => "announce ready (recv closed)", cmd => fun(#{tester := Tester}) -> - ev_ready(Tester, recv_closed), + ?SEV_ANNOUNCE_READY(Tester, recv_closed), ok end}, %% Termination #{desc => "await terminate (from tester)", cmd => fun(#{tester := Tester} = State) -> - case ev_await_terminate(Tester, tester) of + case ?SEV_AWAIT_TERMINATE(Tester, tester) of ok -> {ok, maps:remove(tester, State)}; {error, _} = ERROR -> @@ -4034,10 +4007,7 @@ sc_lc_receive_response_udp(InitState) -> end}, %% *** We are done *** - #{desc => "finish", - cmd => fun(_) -> - {ok, normal} - end} + ?SEV_FINISH_NORMAL ], @@ -4068,46 +4038,46 @@ sc_lc_receive_response_udp(InitState) -> %% Start the primary server #{desc => "order 'primary server' start", cmd => fun(#{prim_server := Pid} = _State) -> - ev_start(Pid), + ?SEV_ANNOUNCE_START(Pid), ok end}, #{desc => "await 'primary server' ready (init)", cmd => fun(#{prim_server := Pid} = State) -> - {ok, Sock} = ev_await_ready(Pid, prim_server, init), + {ok, Sock} = ?SEV_AWAIT_READY(Pid, prim_server, init), {ok, State#{sock => Sock}} end}, %% Start the secondary server 1 #{desc => "order 'secondary server 1' start", cmd => fun(#{sec_server1 := Pid, sock := Sock} = _State) -> - ev_start(Pid, Sock), + ?SEV_ANNOUNCE_START(Pid, Sock), ok end}, #{desc => "await 'secondary server 1' ready (init)", cmd => fun(#{sec_server1 := Pid} = _State) -> - ok = ev_await_ready(Pid, sec_server1, init) + ok = ?SEV_AWAIT_READY(Pid, sec_server1, init) end}, %% Start the secondary server 2 #{desc => "order 'secondary server 2' start", cmd => fun(#{sec_server2 := Pid, sock := Sock} = _State) -> - ev_start(Pid, Sock), + ?SEV_ANNOUNCE_START(Pid, Sock), ok end}, #{desc => "await 'secondary server 2' ready (init)", cmd => fun(#{sec_server2 := Pid} = _State) -> - ok = ev_await_ready(Pid, sec_server2, init) + ok = ?SEV_AWAIT_READY(Pid, sec_server2, init) end}, %% Start the secondary server 3 #{desc => "order 'secondary server 3' start", cmd => fun(#{sec_server3 := Pid, sock := Sock} = _State) -> - ev_start(Pid, Sock), + ?SEV_ANNOUNCE_START(Pid, Sock), ok end}, #{desc => "await 'secondary server 3' ready (init)", cmd => fun(#{sec_server3 := Pid} = _State) -> - ok = ev_await_ready(Pid, sec_server3, init) + ok = ?SEV_AWAIT_READY(Pid, sec_server3, init) end}, @@ -4120,142 +4090,142 @@ sc_lc_receive_response_udp(InitState) -> #{desc => "order 'secondary server 1' to continue (recv)", cmd => fun(#{sec_server1 := Pid} = _State) -> - ev_continue(Pid, recv), - ok - end}, - #{desc => "sleep", - cmd => fun(_) -> - ?SLEEP(?SECS(1)), + ?SEV_ANNOUNCE_CONTINUE(Pid, recv), ok end}, + ?SEV_SLEEP(?SECS(1)), #{desc => "order 'secondary server 2' to continue (recv)", cmd => fun(#{sec_server2 := Pid} = _State) -> - ev_continue(Pid, recv), - ok - end}, - #{desc => "sleep", - cmd => fun(_) -> - ?SLEEP(?SECS(1)), + ?SEV_ANNOUNCE_CONTINUE(Pid, recv), ok end}, + ?SEV_SLEEP(?SECS(1)), #{desc => "order 'secondary server 3' to continue (recv)", cmd => fun(#{sec_server3 := Pid} = _State) -> - ev_continue(Pid, recv), - ok - end}, - #{desc => "sleep", - cmd => fun(_) -> - ?SLEEP(?SECS(1)), + ?SEV_ANNOUNCE_CONTINUE(Pid, recv), ok end}, + ?SEV_SLEEP(?SECS(1)), #{desc => "order 'primary server' to continue (recv, with timeout)", cmd => fun(#{prim_server := Pid} = _State) -> - ev_continue(Pid, recv, ?SECS(5)), + ?SEV_ANNOUNCE_CONTINUE(Pid, recv, ?SECS(5)), ok end}, #{desc => "await 'primary server' ready (recv, with timeout)", cmd => fun(#{prim_server := Pid} = _State) -> - ev_await_ready(Pid, prim_server, recv) + ?SEV_AWAIT_READY(Pid, prim_server, recv) end}, #{desc => "order 'primary server' to continue (close)", cmd => fun(#{prim_server := Pid} = _State) -> - %% Pid ! {continue, self()}, - ev_continue(Pid, close), + ?SEV_ANNOUNCE_CONTINUE(Pid, close), ok end}, #{desc => "await 'primary server' ready (close)", cmd => fun(#{prim_server := Pid} = _State) -> - ev_await_ready(Pid, prim_server, close) + ?SEV_AWAIT_READY(Pid, prim_server, close) end}, #{desc => "await 'secondary server 1' ready (closed)", cmd => fun(#{sec_server1 := Pid} = _State) -> - ev_await_ready(Pid, sec_server1, recv_closed) + ?SEV_AWAIT_READY(Pid, sec_server1, recv_closed) end}, #{desc => "await 'secondary server 2' ready (closed)", cmd => fun(#{sec_server2 := Pid} = _State) -> - ev_await_ready(Pid, sec_server2, recv_closed) + ?SEV_AWAIT_READY(Pid, sec_server2, recv_closed) end}, #{desc => "await 'secondary server 3' ready (closed)", cmd => fun(#{sec_server3 := Pid} = _State) -> - ev_await_ready(Pid, sec_server3, recv_closed) + ?SEV_AWAIT_READY(Pid, sec_server3, recv_closed) end}, %% Terminations #{desc => "order 'secondary server 3' to terminate", cmd => fun(#{sec_server3 := Pid} = _State) -> - ev_terminate(Pid), + ?SEV_ANNOUNCE_TERMINATE(Pid), ok end}, #{desc => "await 'secondary server 3' termination", cmd => fun(#{sec_server3 := Pid} = State) -> - ev_await_termination(Pid), - {ok, maps:remove(sec_server3, State)} + case ?SEV_AWAIT_TERMINATION(Pid) of + ok -> + {ok, maps:remove(sec_server3, State)}; + {error, _} = ERROR -> + ERROR + end end}, #{desc => "order 'secondary server 2' to terminate", cmd => fun(#{sec_server2 := Pid} = _State) -> - ev_terminate(Pid), + ?SEV_ANNOUNCE_TERMINATE(Pid), ok end}, #{desc => "await 'secondary server 2' termination", cmd => fun(#{sec_server2 := Pid} = State) -> - ev_await_termination(Pid), - {ok, maps:remove(sec_server2, State)} + case ?SEV_AWAIT_TERMINATION(Pid) of + ok -> + {ok, maps:remove(sec_server2, State)}; + {error, _} = ERROR -> + ERROR + end end}, #{desc => "order 'secondary server 1' to terminate", cmd => fun(#{sec_server1 := Pid} = _State) -> - ev_terminate(Pid), + ?SEV_ANNOUNCE_TERMINATE(Pid), ok end}, #{desc => "await 'secondary server 1' termination", cmd => fun(#{sec_server1 := Pid} = State) -> - ev_await_termination(Pid), - {ok, maps:remove(sec_server1, State)} + case ?SEV_AWAIT_TERMINATION(Pid) of + ok -> + {ok, maps:remove(sec_server1, State)}; + {error, _} = ERROR -> + ERROR + end end}, #{desc => "order 'primary server' to terminate", cmd => fun(#{prim_server := Pid} = _State) -> - ev_terminate(Pid), + ?SEV_ANNOUNCE_TERMINATE(Pid), ok end}, #{desc => "await 'primary server' termination", cmd => fun(#{prim_server := Pid} = State) -> - ev_await_termination(Pid), - {ok, maps:remove(prim_server, State)} + case ?SEV_AWAIT_TERMINATION(Pid) of + ok -> + {ok, maps:remove(prim_server, State)}; + {error, _} = ERROR -> + ERROR + end end}, %% *** We are done *** - #{desc => "finish", - cmd => fun(_) -> - {ok, normal} - end} + ?SEV_FINISH_NORMAL ], i("start 'primary server' evaluator"), PrimSrvInitState = InitState, - PrimServer = evaluator_start("prim-server", PrimServerSeq, PrimSrvInitState), + PrimServer = ?SEV_START("prim-server", PrimServerSeq, PrimSrvInitState), i("start 'secondary server 1' evaluator"), SecSrvInitState = #{recv => maps:get(recv, InitState)}, - SecServer1 = evaluator_start("sec-server-1", SecServerSeq, SecSrvInitState), + SecServer1 = ?SEV_START("sec-server-1", SecServerSeq, SecSrvInitState), i("start 'secondary server 2' evaluator"), - SecServer2 = evaluator_start("sec-server-2", SecServerSeq, SecSrvInitState), + SecServer2 = ?SEV_START("sec-server-2", SecServerSeq, SecSrvInitState), i("start 'secondary server 3' evaluator"), - SecServer3 = evaluator_start("sec-server-3", SecServerSeq, SecSrvInitState), + SecServer3 = ?SEV_START("sec-server-3", SecServerSeq, SecSrvInitState), i("start 'tester' evaluator"), TesterInitState = #{prim_server => PrimServer#ev.pid, sec_server1 => SecServer1#ev.pid, sec_server2 => SecServer2#ev.pid, sec_server3 => SecServer3#ev.pid}, - Tester = evaluator_start("tester", TesterSeq, TesterInitState), + Tester = ?SEV_START("tester", TesterSeq, TesterInitState), i("await evaluator"), - ok = await_evaluator_finish([PrimServer, - SecServer1, SecServer2, SecServer3, - Tester]). + ok = ?SEV_AWAIT_FINISH([PrimServer, + SecServer1, SecServer2, SecServer3, + Tester]). @@ -4403,10 +4373,10 @@ sc_rc_receive_response_tcp(InitState) -> %% by the server! ServerSeq = [ - %% *** Init part *** + %% *** Wait for start order part *** #{desc => "await start", cmd => fun(State) -> - Tester = ev_await_start(), + Tester = ?SEV_AWAIT_START(), {ok, State#{tester => Tester}} end}, #{desc => "monitor tester", @@ -4414,6 +4384,8 @@ sc_rc_receive_response_tcp(InitState) -> _MRef = erlang:monitor(process, Tester), ok end}, + + %% *** Init part *** #{desc => "which local address", cmd => fun(#{domain := Domain} = State) -> LAddr = which_local_addr(Domain), @@ -4445,24 +4417,24 @@ sc_rc_receive_response_tcp(InitState) -> #{desc => "announce ready (init)", cmd => fun(#{tester := Tester, local_sa := LSA, lport := Port}) -> ServerSA = LSA#{port => Port}, - ev_ready(Tester, init, ServerSA), + ?SEV_ANNOUNCE_READY(Tester, init, ServerSA), ok end}, %% The actual test (we expect three connections) #{desc => "await continue (accept all three connections)", cmd => fun(#{tester := Tester} = _State) -> - ev_await_continue(Tester, tester, accept) + ?SEV_ANNOUNCE_CONTINUE(Tester, tester, accept) end}, #{desc => "accept 1", cmd => fun(#{lsock := LSock, recv := Recv} = State) -> case socket:accept(LSock) of {ok, Sock} -> - ei("accepted: try start handler"), + ?SEV_IPRINT("accepted: try start handler"), {ok, Handler} = sc_rc_tcp_handler_start(1, Recv, Sock), - ei("handler started"), + ?SEV_IPRINT("handler started"), {ok, State#{csock1 => Sock, handler1 => Handler}}; {error, _} = ERROR -> @@ -4473,11 +4445,11 @@ sc_rc_receive_response_tcp(InitState) -> cmd => fun(#{lsock := LSock, recv := Recv} = State) -> case socket:accept(LSock) of {ok, Sock} -> - ei("accepted: try start handler"), + ?SEV_IPRINT("accepted: try start handler"), {ok, Handler} = sc_rc_tcp_handler_start(2, Recv, Sock), - ei("handler started"), + ?SEV_IPRINT("handler started"), {ok, State#{csock2 => Sock, handler2 => Handler}}; {error, _} = ERROR -> @@ -4488,11 +4460,11 @@ sc_rc_receive_response_tcp(InitState) -> cmd => fun(#{lsock := LSock, recv := Recv} = State) -> case socket:accept(LSock) of {ok, Sock} -> - ei("accepted: try start handler"), + ?SEV_IPRINT("accepted: try start handler"), {ok, Handler} = sc_rc_tcp_handler_start(3, Recv, Sock), - ei("handler started"), + ?SEV_IPRINT("handler started"), {ok, State#{csock3 => Sock, handler3 => Handler}}; {error, _} = ERROR -> @@ -4501,12 +4473,12 @@ sc_rc_receive_response_tcp(InitState) -> end}, #{desc => "announce ready (accept all three connections)", cmd => fun(#{tester := Tester}) -> - ev_ready(Tester, accept), + ?SEV_ANNOUNCE_READY(Tester, accept), ok end}, #{desc => "await continue (recv)", cmd => fun(#{tester := Tester} = _State) -> - ev_await_continue(Tester, tester, recv) + ?SEV_AWAIT_CONTINUE(Tester, tester, recv) end}, #{desc => "order handler 1 to receive", cmd => fun(#{handler1 := Pid} = _State) -> @@ -4558,14 +4530,14 @@ sc_rc_receive_response_tcp(InitState) -> end}, #{desc => "announce ready (recv closed from all handlers)", cmd => fun(#{tester := Tester}) -> - ev_ready(Tester, recv_closed), + ?SEV_ANNOUNCE_READY(Tester, recv_closed), ok end}, %% Termination #{desc => "await terminate (from tester)", cmd => fun(#{tester := Tester} = State) -> - case ev_await_terminate(Tester, tester) of + case ?SEV_AWAIT_TERMINATE(Tester, tester) of ok -> {ok, maps:remove(tester, State)}; {error, _} = ERROR -> @@ -4625,18 +4597,15 @@ sc_rc_receive_response_tcp(InitState) -> end}, %% *** We are done *** - #{desc => "finish", - cmd => fun(_) -> - {ok, normal} - end} + ?SEV_FINISH_NORMAL ], ClientSeq = [ - %% *** Init part *** + %% *** Wait for start order part *** #{desc => "await start", cmd => fun(State) -> - {Tester, {Node, ServerSA}} = ev_await_start(), + {Tester, {Node, ServerSA}} = ?SEV_AWAIT_START(), {ok, State#{tester => Tester, node => Node, server_sa => ServerSA}} @@ -4646,10 +4615,12 @@ sc_rc_receive_response_tcp(InitState) -> _MRef = erlang:monitor(process, Tester), ok end}, + + %% *** Init part *** #{desc => "start remote client on client node", cmd => fun(#{node := Node} = State) -> Pid = sc_rc_tcp_client_start(Node), - ei("client ~p started", [Pid]), + ?SEV_IPRINT("client ~p started", [Pid]), {ok, State#{client => Pid}} end}, #{desc => "monitor remote client", @@ -4669,18 +4640,20 @@ sc_rc_receive_response_tcp(InitState) -> {ready, Client} -> ok; {'DOWN', _, process, Tester, Reason} -> - ee("Unexpected DOWN regarding tester ~p: " - "~n ~p", [Tester, Reason]), + ?SEV_EPRINT("Unexpected DOWN regarding " + "tester ~p: " + "~n ~p", [Tester, Reason]), {error, {unexpected_exit, tester}}; {'DOWN', _, process, Client, Reason} -> - ee("Unexpected DOWN regarding client ~p: " - "~n ~p", [Client, Reason]), + ?SEV_EPRINT("Unexpected DOWN regarding " + "client ~p: " + "~n ~p", [Client, Reason]), {error, {unexpected_exit, client}} end end}, #{desc => "announce ready (init)", cmd => fun(#{tester := Tester}) -> - ev_ready(Tester, init), + ?SEV_ANNOUNCE_READY(Tester, init), ok end}, @@ -4688,8 +4661,8 @@ sc_rc_receive_response_tcp(InitState) -> #{desc => "await continue (connect)", cmd => fun(#{tester := Tester, client := Client} = _State) -> - ev_await_continue(Tester, tester, connect, - [{rclient, Client}]), + ?SEV_AWAIT_CONTINUE(Tester, tester, connect, + [{rclient, Client}]), ok end}, #{desc => "order remote client to continue (connect)", @@ -4704,25 +4677,27 @@ sc_rc_receive_response_tcp(InitState) -> {ready, Client} -> ok; {'DOWN', _, process, Tester, Reason} -> - ee("Unexpected DOWN regarding tester ~p: " - "~n ~p", [Tester, Reason]), + ?SEV_EPRINT("Unexpected DOWN regarding " + "tester ~p: " + "~n ~p", [Tester, Reason]), {error, {unexpected_exit, tester}}; {'DOWN', _, process, Client, Reason} -> - ee("Unexpected DOWN regarding client ~p: " - "~n ~p", [Client, Reason]), + ?SEV_EPRINT("Unexpected DOWN regarding " + "client ~p: " + "~n ~p", [Client, Reason]), {error, {unexpected_exit, client}} end end}, #{desc => "announce ready (connected)", cmd => fun(#{tester := Tester}) -> - ev_ready(Tester, connect), + ?SEV_ANNOUNCE_READY(Tester, connect), ok end}, #{desc => "await continue (close)", cmd => fun(#{tester := Tester, client := Client} = _State) -> - ev_await_continue(Tester, tester, close, - [{rclient, Client}]), + ?SEV_AWAIT_CONTINUE(Tester, tester, close, + [{rclient, Client}]), ok end}, #{desc => "order remote client to close", @@ -4737,26 +4712,33 @@ sc_rc_receive_response_tcp(InitState) -> {ready, Client} -> ok; {'DOWN', _, process, Tester, Reason} -> - ee("Unexpected DOWN regarding tester ~p: " - "~n ~p", [Tester, Reason]), + ?SEV_EPRINT("Unexpected DOWN regarding " + "tester ~p: " + "~n ~p", [Tester, Reason]), {error, {unexpected_exit, tester}}; {'DOWN', _, process, Client, Reason} -> - ee("Unexpected DOWN regarding client ~p: " - "~n ~p", [Client, Reason]), + ?SEV_EPRINT("Unexpected DOWN regarding " + "client ~p: " + "~n ~p", [Client, Reason]), {error, {unexpected_exit, client}} end end}, #{desc => "announce ready (close)", cmd => fun(#{tester := Tester}) -> - ev_ready(Tester, close), + ?SEV_ANNOUNCE_READY(Tester, close), ok end}, %% Termination #{desc => "await terminate (from tester)", cmd => fun(#{tester := Tester, client := Client} = State) -> - ev_await_terminate(Tester, tester, [{rclient, Client}]), - {ok, maps:remove(tester, State)} + case ?SEV_AWAIT_TERMINATE(Tester, tester, + [{rclient, Client}]) of + ok -> + {ok, maps:remove(tester, State)}; + {error, _} = ERROR -> + ERROR + end end}, #{desc => "kill remote client", cmd => fun(#{client := Client}) -> @@ -4772,10 +4754,7 @@ sc_rc_receive_response_tcp(InitState) -> end}, %% *** We are done *** - #{desc => "finish", - cmd => fun(_) -> - {ok, normal} - end} + ?SEV_FINISH_NORMAL ], TesterSeq = @@ -4785,7 +4764,8 @@ sc_rc_receive_response_tcp(InitState) -> cmd => fun(#{host := Host} = State) -> case start_node(Host, client_1) of {ok, Node} -> - ei("client node (1) ~p started", [Node]), + ?SEV_IPRINT("client node (1) ~p started", + [Node]), {ok, State#{client_node1 => Node}}; {error, Reason, _} -> {error, Reason} @@ -4800,7 +4780,8 @@ sc_rc_receive_response_tcp(InitState) -> cmd => fun(#{host := Host} = State) -> case start_node(Host, client_2) of {ok, Node} -> - ei("client node (2) ~p started", [Node]), + ?SEV_IPRINT("client node (2) ~p started", + [Node]), {ok, State#{client_node2 => Node}}; {error, Reason, _} -> {error, Reason} @@ -4815,7 +4796,8 @@ sc_rc_receive_response_tcp(InitState) -> cmd => fun(#{host := Host} = State) -> case start_node(Host, client_3) of {ok, Node} -> - ei("client node (3) ~p started", [Node]), + ?SEV_IPRINT("client node (3) ~p started", + [Node]), {ok, State#{client_node3 => Node}}; {error, Reason, _} -> {error, Reason} @@ -4850,12 +4832,12 @@ sc_rc_receive_response_tcp(InitState) -> %% Start the server #{desc => "order server start", cmd => fun(#{server := Pid} = _State) -> - ev_start(Pid), + ?SEV_ANNOUNCE_START(Pid), ok end}, #{desc => "await server ready (init)", cmd => fun(#{server := Pid} = State) -> - {ok, ServerSA} = ev_await_ready(Pid, server, init), + {ok, ServerSA} = ?SEV_AWAIT_READY(Pid, server, init), {ok, State#{server_sa => ServerSA}} end}, @@ -4864,50 +4846,46 @@ sc_rc_receive_response_tcp(InitState) -> cmd => fun(#{client1 := Pid, client_node1 := Node, server_sa := ServerSA} = _State) -> - ev_start(Pid, {Node, ServerSA}), + ?SEV_ANNOUNCE_START(Pid, {Node, ServerSA}), ok end}, #{desc => "await client 1 ready (init)", cmd => fun(#{client1 := Pid} = _State) -> - ok = ev_await_ready(Pid, client, init) + ok = ?SEV_AWAIT_READY(Pid, client1, init) end}, #{desc => "order client 2 start", cmd => fun(#{client2 := Pid, client_node2 := Node, server_sa := ServerSA} = _State) -> - ev_start(Pid, {Node, ServerSA}), + ?SEV_ANNOUNCE_START(Pid, {Node, ServerSA}), ok end}, #{desc => "await client 2 ready (init)", cmd => fun(#{client2 := Pid} = _State) -> - ok = ev_await_ready(Pid, client, init) + ok = ?SEV_AWAIT_READY(Pid, client2, init) end}, #{desc => "order client 3 start", cmd => fun(#{client3 := Pid, client_node3 := Node, server_sa := ServerSA} = _State) -> - ev_start(Pid, {Node, ServerSA}), + ?SEV_ANNOUNCE_START(Pid, {Node, ServerSA}), ok end}, #{desc => "await client 3 ready (init)", cmd => fun(#{client3 := Pid} = _State) -> - ok = ev_await_ready(Pid, client, init) + ok = ?SEV_AWAIT_READY(Pid, client3, init) end}, %% The actual test #{desc => "order server continue (accept)", cmd => fun(#{server := Pid} = _State) -> - ev_continue(Pid, accept), - ok - end}, - #{desc => "sleep", - cmd => fun(_) -> - ?SLEEP(?SECS(1)), + ?SEV_ANNOUNCE_CONTINUE(Pid, accept), ok end}, + ?SEV_SLEEP(?SECS(1)), #{desc => "order client 1 continue (connect)", cmd => fun(#{client1 := Pid} = _State) -> - ev_continue(Pid, connect), + ?SEV_ANNOUNCE_CONTINUE(Pid, connect), ok end}, #{desc => "await client 1 ready (connect)", @@ -4915,15 +4893,15 @@ sc_rc_receive_response_tcp(InitState) -> client1 := Client1, client2 := Client2, client3 := Client3} = _State) -> - ev_await_ready(Client1, client1, connect, - [{server, Server}, - {client2, Client2}, - {client3, Client3}]), + ?SEV_AWAIT_READY(Client1, client1, connect, + [{server, Server}, + {client2, Client2}, + {client3, Client3}]), ok end}, #{desc => "order client 2 continue (connect)", cmd => fun(#{client2 := Pid} = _State) -> - ev_continue(Pid, connect), + ?SEV_ANNOUNCE_CONTINUE(Pid, connect), ok end}, #{desc => "await client 2 ready (connect)", @@ -4931,15 +4909,15 @@ sc_rc_receive_response_tcp(InitState) -> client1 := Client1, client2 := Client2, client3 := Client3} = _State) -> - ev_await_ready(Client2, client2, connect, - [{server, Server}, - {client1, Client1}, - {client3, Client3}]), + ?SEV_AWAIT_READY(Client2, client2, connect, + [{server, Server}, + {client1, Client1}, + {client3, Client3}]), ok end}, #{desc => "order client 3 continue (connect)", cmd => fun(#{client3 := Pid} = _State) -> - ev_continue(Pid, connect), + ?SEV_ANNOUNCE_CONTINUE(Pid, connect), ok end}, #{desc => "await client 3 ready (connect)", @@ -4947,10 +4925,10 @@ sc_rc_receive_response_tcp(InitState) -> client1 := Client1, client2 := Client2, client3 := Client3} = _State) -> - ev_await_ready(Client3, client3, connect, - [{server, Server}, - {client1, Client1}, - {client2, Client2}]), + ?SEV_AWAIT_READY(Client3, client3, connect, + [{server, Server}, + {client1, Client1}, + {client2, Client2}]), ok end}, #{desc => "await server ready (accept from all connections)", @@ -4958,25 +4936,21 @@ sc_rc_receive_response_tcp(InitState) -> client1 := Client1, client2 := Client2, client3 := Client3} = _State) -> - ev_await_ready(Server, server, accept, - [{client1, Client1}, - {client2, Client2}, - {client3, Client3}]), + ?SEV_AWAIT_READY(Server, server, accept, + [{client1, Client1}, + {client2, Client2}, + {client3, Client3}]), ok end}, #{desc => "order server continue (recv for all connections)", cmd => fun(#{server := Pid} = _State) -> - ev_continue(Pid, recv), - ok - end}, - #{desc => "sleep", - cmd => fun(_) -> - ?SLEEP(?SECS(1)), + ?SEV_ANNOUNCE_CONTINUE(Pid, recv), ok end}, + ?SEV_SLEEP(?SECS(1)), #{desc => "order client 1 continue (close)", cmd => fun(#{client1 := Pid} = _State) -> - ev_continue(Pid, close), + ?SEV_ANNOUNCE_CONTINUE(Pid, close), ok end}, #{desc => "await client 1 ready (close)", @@ -4984,15 +4958,15 @@ sc_rc_receive_response_tcp(InitState) -> client1 := Client1, client2 := Client2, client3 := Client3} = _State) -> - ev_await_ready(Client1, client1, close, - [{server, Server}, - {client2, Client2}, - {client3, Client3}]), + ?SEV_AWAIT_READY(Client1, client1, close, + [{server, Server}, + {client2, Client2}, + {client3, Client3}]), ok end}, #{desc => "order client 2 continue (close)", cmd => fun(#{client2 := Pid} = _State) -> - ev_continue(Pid, close), + ?SEV_ANNOUNCE_CONTINUE(Pid, close), ok end}, #{desc => "await client 2 ready (close)", @@ -5000,15 +4974,15 @@ sc_rc_receive_response_tcp(InitState) -> client1 := Client1, client2 := Client2, client3 := Client3} = _State) -> - ev_await_ready(Client2, client2, close, - [{server, Server}, - {client1, Client1}, - {client3, Client3}]), + ?SEV_AWAIT_READY(Client2, client2, close, + [{server, Server}, + {client1, Client1}, + {client3, Client3}]), ok end}, #{desc => "order client 3 continue (close)", cmd => fun(#{client3 := Pid} = _State) -> - ev_continue(Pid, close), + ?SEV_ANNOUNCE_CONTINUE(Pid, close), ok end}, #{desc => "await client 3 ready (close)", @@ -5016,10 +4990,10 @@ sc_rc_receive_response_tcp(InitState) -> client1 := Client1, client2 := Client2, client3 := Client3} = _State) -> - ev_await_ready(Client3, client1, close, - [{server, Server}, - {client1, Client1}, - {client2, Client2}]), + ?SEV_AWAIT_READY(Client3, client1, close, + [{server, Server}, + {client1, Client1}, + {client2, Client2}]), ok end}, #{desc => "await server ready (close for all connections)", @@ -5027,57 +5001,73 @@ sc_rc_receive_response_tcp(InitState) -> client1 := Client1, client2 := Client2, client3 := Client3} = _State) -> - ev_await_ready(Server, server, recv_closed, - [{client1, Client1}, - {client2, Client2}, - {client3, Client3}]), + ?SEV_AWAIT_READY(Server, server, recv_closed, + [{client1, Client1}, + {client2, Client2}, + {client3, Client3}]), ok end}, %% Terminations #{desc => "order client 1 to terminate", cmd => fun(#{client1 := Pid} = _State) -> - ev_terminate(Pid), + ?SEV_ANNOUNCE_TERMINATE(Pid), ok end}, #{desc => "await client 1 termination", cmd => fun(#{client1 := Pid} = State) -> - ev_await_termination(Pid), - State1 = maps:remove(client1, State), - {ok, State1} + case ?SEV_AWAIT_TERMINATION(Pid) of + ok -> + State1 = maps:remove(client1, State), + {ok, State1}; + {error, _} = ERROR -> + ERROR + end end}, #{desc => "order client 2 to terminate", cmd => fun(#{client2 := Pid} = _State) -> - ev_terminate(Pid), + ?SEV_ANNOUNCE_TERMINATE(Pid), ok end}, #{desc => "await client 2 termination", cmd => fun(#{client2 := Pid} = State) -> - ev_await_termination(Pid), - State1 = maps:remove(client2, State), - {ok, State1} + case ?SEV_AWAIT_TERMINATION(Pid) of + ok -> + State1 = maps:remove(client2, State), + {ok, State1}; + {error, _} = ERROR -> + ERROR + end end}, #{desc => "order client 3 to terminate", cmd => fun(#{client3 := Pid} = _State) -> - ev_terminate(Pid), + ?SEV_ANNOUNCE_TERMINATE(Pid), ok end}, #{desc => "await client 3 termination", cmd => fun(#{client3 := Pid} = State) -> - ev_await_termination(Pid), - State1 = maps:remove(client3, State), - {ok, State1} + case ?SEV_AWAIT_TERMINATION(Pid) of + ok -> + State1 = maps:remove(client3, State), + {ok, State1}; + {error, _} = ERROR -> + ERROR + end end}, #{desc => "order server to terminate", cmd => fun(#{server := Pid} = _State) -> - ev_terminate(Pid), + ?SEV_ANNOUNCE_TERMINATE(Pid), ok end}, #{desc => "await server termination", cmd => fun(#{server := Pid} = State) -> - ev_await_termination(Pid), - State1 = maps:remove(server, State), - {ok, State1} + case ?SEV_AWAIT_TERMINATION(Pid) of + ok -> + State1 = maps:remove(server, State), + {ok, State1}; + {error, _} = ERROR -> + ERROR + end end}, #{desc => "stop client node 1", cmd => fun(#{client_node1 := Node} = _State) -> @@ -5114,21 +5104,18 @@ sc_rc_receive_response_tcp(InitState) -> end}, %% *** We are done *** - #{desc => "finish", - cmd => fun(_) -> - {ok, normal} - end} + ?SEV_FINISH_NORMAL ], i("start server evaluator"), ServerInitState = InitState, - Server = evaluator_start("server", ServerSeq, ServerInitState), + Server = ?SEV_START("server", ServerSeq, ServerInitState), i("start client evaluator(s)"), ClientInitState = InitState, - Client1 = evaluator_start("client-1", ClientSeq, ClientInitState), - Client2 = evaluator_start("client-2", ClientSeq, ClientInitState), - Client3 = evaluator_start("client-3", ClientSeq, ClientInitState), + Client1 = ?SEV_START("client-1", ClientSeq, ClientInitState), + Client2 = ?SEV_START("client-2", ClientSeq, ClientInitState), + Client3 = ?SEV_START("client-3", ClientSeq, ClientInitState), i("start 'tester' evaluator"), TesterInitState = #{host => local_host(), @@ -5136,12 +5123,12 @@ sc_rc_receive_response_tcp(InitState) -> client1 => Client1#ev.pid, client2 => Client2#ev.pid, client3 => Client3#ev.pid}, - Tester = evaluator_start("tester", TesterSeq, TesterInitState), + Tester = ?SEV_START("tester", TesterSeq, TesterInitState), i("await evaluator"), - ok = await_evaluator_finish([Server, - Client1, Client2, Client3, - Tester]). + ok = ?SEV_AWAIT_FINISH([Server, + Client1, Client2, Client3, + Tester]). start_node(Host, NodeName) -> @@ -5304,35 +5291,35 @@ sc_rc_tcp_handler_init(ID, Parent) -> put(sname, f("handler-~w", [ID])), _MRef = erlang:monitor(process, Parent), Parent ! {started, self()}, - ei("started"), + ?SEV_IPRINT("started"), ok. sc_rc_tcp_handler_await_recv(Parent) -> - ei("await recv"), + ?SEV_IPRINT("await recv"), receive {recv, Parent} -> ok; {'DOWN', _, process, Parent, Reason} -> - ee("received DOWN regarding parent: " + ?SEV_EPRINT("received DOWN regarding parent: " "~n ~p", [Reason]), exit({parent, Reason}) end. sc_rc_tcp_handler_recv(Recv, Sock) -> - ei("recv"), + ?SEV_IPRINT("recv"), try Recv(Sock) of {error, closed} -> ok; {ok, _} -> - ei("unexpected success"), + ?SEV_IPRINT("unexpected success"), {error, unexpected_success}; {error, Reason} = ERROR -> - ei("receive error: " + ?SEV_IPRINT("receive error: " "~n ~p", [Reason]), ERROR catch C:E:S -> - ei("receive failure: " + ?SEV_IPRINT("receive failure: " "~n Class: ~p" "~n Error: ~p" "~n Stack: ~p", [C, E, S]), @@ -5340,18 +5327,18 @@ sc_rc_tcp_handler_recv(Recv, Sock) -> end. sc_rc_tcp_handler_announce_ready(Parent, Result) -> - ei("announce ready"), + ?SEV_IPRINT("announce ready"), Parent ! {ready, self(), Result}, ok. sc_rc_tcp_handler_await_terminate(Parent) -> - ei("await terminate"), + ?SEV_IPRINT("await terminate"), receive {terminate, Parent, Reason} -> Reason; {'DOWN', _, process, Parent, Reason} -> - ee("received DOWN regarding parent: " - "~n ~p", [Reason]), + ?SEV_EPRINT("received DOWN regarding parent: " + "~n ~p", [Reason]), {parent, Reason} end. @@ -5454,7 +5441,7 @@ sc_lc_acceptor_response_tcp(InitState) -> %% *** Wait for start order part *** #{desc => "await start (from tester)", cmd => fun(State) -> - Tester = ev_await_start(), + Tester = ?SEV_AWAIT_START(), {ok, State#{tester => Tester}} end}, #{desc => "monitor tester", @@ -5496,16 +5483,19 @@ sc_lc_acceptor_response_tcp(InitState) -> end}, #{desc => "announce ready (init)", cmd => fun(#{tester := Tester, sock := Sock} = _State) -> - ev_ready(Tester, init, Sock), + ?SEV_ANNOUNCE_READY(Tester, init, Sock), ok end}, %% The actual test #{desc => "await continue (accept)", cmd => fun(#{tester := Tester} = State) -> - {ok, Timeout} = - ev_await_continue(Tester, tester, accept), - {ok, State#{timeout => Timeout}} + case ?SEV_AWAIT_CONTINUE(Tester, tester, accept) of + {ok, Timeout} -> + {ok, State#{timeout => Timeout}}; + {error, _} = ERROR -> + ERROR + end end}, #{desc => "await connection", cmd => fun(#{sock := Sock, timeout := Timeout} = _State) -> @@ -5513,7 +5503,7 @@ sc_lc_acceptor_response_tcp(InitState) -> {error, timeout} -> ok; {ok, Sock} -> - ee("unexpected success"), + ?SEV_EPRINT("unexpected success"), (catch socket:close(Sock)), {error, unexpected_success}; {error, _} = ERROR -> @@ -5522,13 +5512,12 @@ sc_lc_acceptor_response_tcp(InitState) -> end}, #{desc => "announce ready (accept timeout)", cmd => fun(#{tester := Tester}) -> - Tester ! {ready, self()}, - ev_ready(Tester, accept_timeout), + ?SEV_ANNOUNCE_READY(Tester, accept_timeout), ok end}, #{desc => "await continue (close)", cmd => fun(#{tester := Tester} = _State) -> - ok = ev_await_continue(Tester, tester, close) + ok = ?SEV_AWAIT_CONTINUE(Tester, tester, close) end}, #{desc => "close socket", cmd => fun(#{sock := Sock} = State) -> @@ -5541,14 +5530,14 @@ sc_lc_acceptor_response_tcp(InitState) -> end}, #{desc => "announce ready (close)", cmd => fun(#{tester := Tester}) -> - ev_ready(Tester, close), + ?SEV_ANNOUNCE_READY(Tester, close), ok end}, % Termination #{desc => "await terminate", cmd => fun(#{tester := Tester} = State) -> - case ev_await_terminate(Tester, tester) of + case ?SEV_AWAIT_TERMINATE(Tester, tester) of ok -> {ok, maps:remove(tester, State)}; {error, _} = ERROR -> @@ -5557,10 +5546,7 @@ sc_lc_acceptor_response_tcp(InitState) -> end}, %% *** We are done *** - #{desc => "finish", - cmd => fun(_) -> - {ok, normal} - end} + ?SEV_FINISH_NORMAL ], SecAcceptorSeq = @@ -5568,7 +5554,7 @@ sc_lc_acceptor_response_tcp(InitState) -> %% *** Init part *** #{desc => "await start", cmd => fun(State) -> - {Tester, Sock} = ev_await_start(), + {Tester, Sock} = ?SEV_AWAIT_START(), {ok, State#{tester => Tester, sock => Sock}} end}, #{desc => "monitor tester", @@ -5578,15 +5564,13 @@ sc_lc_acceptor_response_tcp(InitState) -> end}, #{desc => "announce ready (init)", cmd => fun(#{tester := Tester}) -> - ev_ready(Tester, init), - ok + ?SEV_ANNOUNCE_READY(Tester, init) end}, %% The actual test #{desc => "await continue (accept)", cmd => fun(#{tester := Tester} = _State) -> - ev_await_continue(Tester, tester, accept), - ok + ok = ?SEV_AWAIT_CONTINUE(Tester, tester, accept) end}, #{desc => "accept", cmd => fun(#{sock := Sock} = State) -> @@ -5601,14 +5585,13 @@ sc_lc_acceptor_response_tcp(InitState) -> end}, #{desc => "announce ready (accept closed)", cmd => fun(#{tester := Tester}) -> - ev_ready(Tester, accept_closed), - ok + ?SEV_ANNOUNCE_READY(Tester, accept_closed) end}, %% Termination #{desc => "await terminate (from tester)", cmd => fun(#{tester := Tester} = State) -> - case ev_await_terminate(Tester, tester) of + case ?SEV_AWAIT_TERMINATE(Tester, tester) of ok -> {ok, maps:remove(tester, State)}; {error, _} = ERROR -> @@ -5617,10 +5600,7 @@ sc_lc_acceptor_response_tcp(InitState) -> end}, %% *** We are done *** - #{desc => "finish", - cmd => fun(_) -> - {ok, normal} - end} + ?SEV_FINISH_NORMAL ], TesterSeq = @@ -5650,49 +5630,46 @@ sc_lc_acceptor_response_tcp(InitState) -> %% Start the primary server #{desc => "order 'primary acceptor' start", cmd => fun(#{prim_acc := Pid} = _State) -> - ev_start(Pid), + ?SEV_ANNOUNCE_START(Pid), ok end}, #{desc => "await 'primary acceptor' ready (init)", cmd => fun(#{prim_acc := Pid} = State) -> - {ok, Sock} = ev_await_ready(Pid, prim_acc, init), + {ok, Sock} = ?SEV_AWAIT_READY(Pid, prim_acc, init), {ok, State#{sock => Sock}} end}, %% Start the secondary acceptor 1 #{desc => "order 'secondary acceptor 1' start", cmd => fun(#{sec_acc1 := Pid, sock := Sock} = _State) -> - ev_start(Pid, Sock), + ?SEV_ANNOUNCE_START(Pid, Sock), ok end}, #{desc => "await 'secondary acceptor 1' ready (init)", cmd => fun(#{sec_acc1 := Pid} = _State) -> - ev_await_ready(Pid, sec_acc1, init), - ok + ok = ?SEV_AWAIT_READY(Pid, sec_acc1, init) end}, %% Start the secondary acceptor 2 #{desc => "order 'secondary acceptor 2' start", cmd => fun(#{sec_acc2 := Pid, sock := Sock} = _State) -> - ev_start(Pid, Sock), + ?SEV_ANNOUNCE_START(Pid, Sock), ok end}, #{desc => "await 'secondary acceptor 2' ready (init)", cmd => fun(#{sec_acc2 := Pid} = _State) -> - ev_await_ready(Pid, sec_acc2, init), - ok + ok = ?SEV_AWAIT_READY(Pid, sec_acc2, init) end}, %% Start the secondary acceptor 3 #{desc => "order 'secondary acceptor 3' start", cmd => fun(#{sec_acc3 := Pid, sock := Sock} = _State) -> - ev_start(Pid, Sock), + ?SEV_ANNOUNCE_START(Pid, Sock), ok end}, #{desc => "await 'secondary acceptor 3' ready (init)", cmd => fun(#{sec_acc3 := Pid} = _State) -> - ev_await_ready(Pid, sec_acc3, init), - ok + ok = ?SEV_AWAIT_READY(Pid, sec_acc3, init) end}, @@ -5705,145 +5682,141 @@ sc_lc_acceptor_response_tcp(InitState) -> #{desc => "order 'secondary acceptor 1' to continue (accept)", cmd => fun(#{sec_acc1 := Pid} = _State) -> - ev_continue(Pid, accept), - ok - end}, - #{desc => "sleep", - cmd => fun(_) -> - ?SLEEP(?SECS(1)), + ?SEV_ANNOUNCE_CONTINUE(Pid, accept), ok end}, + ?SEV_SLEEP(?SECS(1)), #{desc => "order 'secondary acceptor 2' to continue (accept)", cmd => fun(#{sec_acc2 := Pid} = _State) -> - ev_continue(Pid, accept), - ok - end}, - #{desc => "sleep", - cmd => fun(_) -> - ?SLEEP(?SECS(1)), + ?SEV_ANNOUNCE_CONTINUE(Pid, accept), ok end}, + ?SEV_SLEEP(?SECS(1)), #{desc => "order 'secondary acceptor 3' to continue (accept)", cmd => fun(#{sec_acc3 := Pid} = _State) -> - ev_continue(Pid, accept), - ok - end}, - #{desc => "sleep", - cmd => fun(_) -> - ?SLEEP(?SECS(1)), + ?SEV_ANNOUNCE_CONTINUE(Pid, accept), ok end}, + ?SEV_SLEEP(?SECS(1)), #{desc => "order 'primary acceptor' to continue", cmd => fun(#{prim_acc := Pid} = _State) -> - ev_continue(Pid, accept, ?SECS(5)), + ?SEV_ANNOUNCE_CONTINUE(Pid, accept, ?SECS(5)), ok end}, #{desc => "await 'primary acceptor' ready (accept timeout)", cmd => fun(#{prim_acc := Pid} = _State) -> - ev_await_ready(Pid, prim_acc, accept_timeout), - ok + ok = ?SEV_AWAIT_READY(Pid, prim_acc, accept_timeout) end}, #{desc => "order 'primary acceptor' to continue (close)", cmd => fun(#{prim_acc := Pid} = _State) -> - ev_continue(Pid, close), + ?SEV_ANNOUNCE_CONTINUE(Pid, close), ok end}, #{desc => "await 'primary acceptor' ready (close)", cmd => fun(#{prim_acc := Pid} = _State) -> - ev_await_ready(Pid, prim_acc, close), - ok + ok = ?SEV_AWAIT_READY(Pid, prim_acc, close) end}, #{desc => "await 'secondary acceptor 1' ready (accept closed)", cmd => fun(#{sec_acc1 := Pid} = _State) -> - ev_await_ready(Pid, sec_acc1, accept_closed), - ok + ok = ?SEV_AWAIT_READY(Pid, sec_acc1, accept_closed) end}, #{desc => "await 'secondary acceptor 2' ready (accept closed)", cmd => fun(#{sec_acc2 := Pid} = _State) -> - ev_await_ready(Pid, sec_acc2, accept_closed), - ok + ok = ?SEV_AWAIT_READY(Pid, sec_acc2, accept_closed) end}, #{desc => "await 'secondary acceptor 3' ready (accept closed)", cmd => fun(#{sec_acc3 := Pid} = _State) -> - ev_await_ready(Pid, sec_acc3, accept_closed), - ok + ok = ?SEV_AWAIT_READY(Pid, sec_acc3, accept_closed) end}, %% Terminations #{desc => "order 'secondary acceptor 3' to terminate", cmd => fun(#{sec_acc3 := Pid} = _State) -> - ev_terminate(Pid), + ?SEV_ANNOUNCE_TERMINATE(Pid), ok end}, #{desc => "await 'secondary acceptor 3' termination", cmd => fun(#{sec_acc3 := Pid} = State) -> - ev_await_termination(Pid), - {ok, maps:remove(sec_acc3, State)} + case ?SEV_AWAIT_TERMINATION(Pid) of + ok -> + {ok, maps:remove(sec_acc3, State)}; + {error, _} = ERROR -> + ERROR + end end}, #{desc => "order 'secondary acceptor 2' to terminate", cmd => fun(#{sec_acc2 := Pid} = _State) -> - ev_terminate(Pid), + ?SEV_ANNOUNCE_TERMINATE(Pid), ok end}, #{desc => "await 'secondary acceptor 2' termination", cmd => fun(#{sec_acc2 := Pid} = State) -> - ev_await_termination(Pid), - {ok, maps:remove(sec_acc2, State)} + case ?SEV_AWAIT_TERMINATION(Pid) of + ok -> + {ok, maps:remove(sec_acc2, State)}; + {error, _} = ERROR -> + ERROR + end end}, #{desc => "order 'secondary acceptor 1' to terminate", cmd => fun(#{sec_acc1 := Pid} = _State) -> - ev_terminate(Pid), + ?SEV_ANNOUNCE_TERMINATE(Pid), ok end}, #{desc => "await 'secondary acceptor 1' termination", cmd => fun(#{sec_acc1 := Pid} = State) -> - ev_await_termination(Pid), - {ok, maps:remove(sec_acc1, State)} + case ?SEV_AWAIT_TERMINATION(Pid) of + ok -> + {ok, maps:remove(sec_acc1, State)}; + {error, _} = ERROR -> + ERROR + end end}, #{desc => "order 'primary acceptor' to terminate", cmd => fun(#{prim_acc := Pid} = _State) -> - ev_terminate(Pid), + ?SEV_ANNOUNCE_TERMINATE(Pid), ok end}, #{desc => "await 'primary acceptor' termination", cmd => fun(#{prim_acc := Pid} = State) -> - ev_await_termination(Pid), - {ok, maps:remove(prim_acc, State)} + case ?SEV_AWAIT_TERMINATION(Pid) of + ok -> + {ok, maps:remove(prim_acc, State)}; + {error, _} = ERROR -> + ERROR + end end}, %% *** We are done *** - #{desc => "finish", - cmd => fun(_) -> - {ok, normal} - end} + ?SEV_FINISH_NORMAL ], i("start 'primary acceptor' evaluator"), PrimAccInitState = InitState, - PrimAcc = evaluator_start("prim-acceptor", PrimAcceptorSeq, PrimAccInitState), + PrimAcc = ?SEV_START("prim-acceptor", PrimAcceptorSeq, PrimAccInitState), i("start 'secondary acceptor 1' evaluator"), SecAccInitState = #{}, - SecAcc1 = evaluator_start("sec-acceptor-1", SecAcceptorSeq, SecAccInitState), + SecAcc1 = ?SEV_START("sec-acceptor-1", SecAcceptorSeq, SecAccInitState), i("start 'secondary acceptor 2' evaluator"), - SecAcc2 = evaluator_start("sec-acceptor-2", SecAcceptorSeq, SecAccInitState), + SecAcc2 = ?SEV_START("sec-acceptor-2", SecAcceptorSeq, SecAccInitState), i("start 'secondary acceptor 3' evaluator"), - SecAcc3 = evaluator_start("sec-acceptor-3", SecAcceptorSeq, SecAccInitState), + SecAcc3 = ?SEV_START("sec-acceptor-3", SecAcceptorSeq, SecAccInitState), i("start 'tester' evaluator"), TesterInitState = #{prim_acc => PrimAcc#ev.pid, sec_acc1 => SecAcc1#ev.pid, sec_acc2 => SecAcc2#ev.pid, sec_acc3 => SecAcc3#ev.pid}, - Tester = evaluator_start("tester", TesterSeq, TesterInitState), + Tester = ?SEV_START("tester", TesterSeq, TesterInitState), i("await evaluator"), - ok = await_evaluator_finish([PrimAcc, SecAcc1, SecAcc2, SecAcc3, Tester]). + ok = ?SEV_AWAIT_FINISH([PrimAcc, SecAcc1, SecAcc2, SecAcc3, Tester]). @@ -5879,229 +5852,6 @@ which_addr2(Domain, [_|IFO]) -> -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -%% An evaluator is a process that executes a command sequence. -%% A test case will consist of atleast one evaluator (one for -%% each actor). -%% The evaluator process *always* run locally. Which means that -%% it will act as a "proxy" for remote nodes in necessary. -%% When the command sequence has been processed, the final state -%% will be used as exit reason. -%% A successful command shall evaluate to ok | {ok, NewState} - --spec evaluator_start(Name, Seq, Init) -> socket_test_evaluator:ev() when - Name :: string(), - Seq :: [socket_test_evaluator:command()], - Init :: socket_test_evaluator:initial_evaluator_state(). - -evaluator_start(Name, Seq, Init) - when is_list(Name) andalso is_list(Seq) andalso (Seq =/= []) -> - Init2 = Init#{parent => self()}, - {Pid, MRef} = erlang:spawn_monitor( - fun() -> evaluator_init(Name, Seq, Init2) end), - ?MKEV(Name, Pid, MRef). - -evaluator_init(Name, Seq, Init) -> - put(sname, Name), - evaluator_loop(1, Seq, Init). - -evaluator_loop(_ID, [], FinalState) -> - exit(FinalState); -evaluator_loop(ID, [#{desc := Desc, - cmd := Cmd}|Cmds], State) when is_function(Cmd, 1) -> - ei("evaluate command ~2w: ~s", [ID, Desc]), - try Cmd(State) of - ok -> - evaluator_loop(ID + 1, Cmds, State); - {ok, NewState} -> - evaluator_loop(ID + 1, Cmds, NewState); - {error, Reason} -> - ee("command ~w failed: " - "~n Reason: ~p", [ID, Reason]), - exit({command_failed, ID, Reason, State}) - catch - C:E:S -> - ee("command ~w crashed: " - "~n Class: ~p" - "~n Error: ~p" - "~n Call Stack: ~p", [ID, C, E, S]), - exit({command_crashed, ID, {C,E,S}, State}) - end. - -await_evaluator_finish(Evs) -> - await_evaluator_finish(Evs, []). - -await_evaluator_finish([], []) -> - ok; -await_evaluator_finish([], Fails) -> - Fails; -await_evaluator_finish(Evs, Fails) -> - receive - {'DOWN', _MRef, process, Pid, normal} -> - case lists:keysearch(Pid, #ev.pid, Evs) of - {value, #ev{name = Name}} -> - i("evaluator '~s' (~p) success", [Name, Pid]), - NewEvs = lists:keydelete(Pid, #ev.pid, Evs), - await_evaluator_finish(NewEvs, Fails); - false -> - i("unknown process ~p died (normal)", [Pid]), - await_evaluator_finish(Evs, Fails) - end; - {'DOWN', _MRef, process, Pid, Reason} -> - case lists:keysearch(Pid, #ev.pid, Evs) of - {value, #ev{name = Name}} -> - i("evaluator '~s' (~p) failed", [Name, Pid]), - NewEvs = lists:keydelete(Pid, #ev.pid, Evs), - await_evaluator_finish(NewEvs, [{Pid, Reason}|Fails]); - false -> - i("unknown process ~p died: " - "~n ~p", [Pid, Reason]), - await_evaluator_finish(Evs, Fails) - end - end. - - -ei(F) -> - ei(F, []). -ei(F, A) -> - eprint("", F, A). - -ee(F) -> - ee(F, []). -ee(F, A) -> - eprint(" ", F, A). - -eprint(Prefix, F, A) -> - %% The two prints is to get the output both in the shell (for when - %% "personal" testing is going on) and in the logs. - FStr = f("[~s][~s][~p] ~s" ++ F, - [formated_timestamp(), get(sname), self(), Prefix | A]), - io:format(user, FStr ++ "~n", []), - io:format(FStr, []). - - - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -ev_start(Pid) -> - ev_announce(Pid, start, undefined). - -ev_start(Pid, Extra) -> - ev_announce(Pid, start, undefined, Extra). - -ev_continue(Pid, Slogan) -> - ev_announce(Pid, continue, Slogan). - -ev_continue(Pid, Slogan, Extra) -> - ev_announce(Pid, continue, Slogan, Extra). - -ev_ready(Pid, Slogan) -> - ev_announce(Pid, ready, Slogan). - -ev_ready(Pid, Slogan, Extra) -> - ev_announce(Pid, ready, Slogan, Extra). - -ev_terminate(Pid) -> - Pid ! {terminate, self()}. - -ev_announce(To, Tag, Slogan) -> - ev_announce(To, Tag, Slogan, undefined). - -ev_announce(To, Tag, Slogan, Extra) -> - To ! {Tag, self(), Slogan, Extra}. - -ev_await_start() -> - receive - {start, Pid, _, undefined} -> - Pid; - {start, Pid, _, Extra} -> - {Pid, Extra} - end. - -ev_await_continue(Pid, Name, Slogan) -> - ev_await_continue(Pid, Name, Slogan, []). -ev_await_continue(Pid, Name, Slogan, Pids) when is_pid(Pid) andalso is_list(Pids) -> - ev_await(Pid, Name, continue, Slogan, Pids). - -ev_await_ready(Pid, Name, Slogan) -> - ev_await_ready(Pid, Name, Slogan, []). -ev_await_ready(Pid, Name, Slogan, Pids) when is_pid(Pid) andalso is_list(Pids) -> - ev_await(Pid, Name, ready, Slogan, Pids). - -ev_await_terminate(Pid, Name) -> - ev_await_terminate(Pid, Name, []). -ev_await_terminate(Pid, Name, Pids) -> - receive - {terminate, Pid} -> - ok; - {'DOWN', _, process, Pid, Reason} -> - ee("Unexpected DOWN regarding ~w ~p: " - "~n ~p", [Name, Pid, Reason]), - {error, {unexpected_exit, Name}}; - {'DOWN', _, process, DownPid, Reason} -> - case ev_await_check_down(DownPid, Reason, Pids) of - ok -> - ei("DOWN from unknown process ~p: " - "~n ~p", [DownPid, Reason]), - ev_await_terminate(Pid, Name, Pids); - {error, _} = ERROR -> - ERROR - end - end. - -ev_await_termination(Pid) -> - receive - {'DOWN', _, process, Pid, _} -> - ok - end. - -%% We expect a message from Pid, but we also watch for DOWN from -%% both Pid and Pids, in which case the test has failed! -ev_await(Pid, Name, Tag, Slogan, Pids) -> - receive - {Tag, Pid, Slogan, undefined} -> - ok; - {Tag, Pid, Slogan, Extra} -> - {ok, Extra}; - {'DOWN', _, process, Pid, Reason} -> - ee("Unexpected DOWN regarding ~w ~p: " - "~n ~p", [Name, Pid, Reason]), - {error, {unexpected_exit, Name}}; - {'DOWN', _, process, DownPid, Reason} -> - case ev_await_check_down(DownPid, Reason, Pids) of - ok -> - ei("DOWN from unknown process ~p: " - "~n ~p", [DownPid, Reason]), - ev_await(Pid, Name, Tag, Slogan, Pids); - {error, _} = ERROR -> - ERROR - end - after infinity -> - ei("ev_await -> timeout for msg from ~p (~w): " - "~n Messages: ~p", [Pid, Name, pi(messages)]), - ev_await(Pid, Name, Tag, Slogan, Pids) - end. - -ev_await_check_down(DownPid, DownReason, Pids) -> - case lists:keymember(DownPid, 1, Pids) of - {value, {_, Name}} -> - ee("Unexpected DOWN regarding ~w ~p: " - "~n ~p", [Name, DownPid, DownReason]), - {error, {unexpected_exit, Name}}; - false -> - ok - end. - - -pi(Item) -> - pi(self(), Item). - -pi(Pid, Item) -> - {Item, Info} = process_info(Pid, Item), - Info. - - %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% sock_open(Domain, Type, Proto) -> diff --git a/erts/emulator/test/socket_test_evaluator.erl b/erts/emulator/test/socket_test_evaluator.erl index 8498ef6a0c..bad8c92dec 100644 --- a/erts/emulator/test/socket_test_evaluator.erl +++ b/erts/emulator/test/socket_test_evaluator.erl @@ -44,6 +44,12 @@ await_termination/1, await_termination/2 ]). +%% Utility functions +-export([ + iprint/2, % Info printouts + eprint/2 % Error printouts + ]). + -export_type([ ev/0, initial_evaluator_state/0, @@ -110,22 +116,22 @@ loop(_ID, [], FinalState) -> exit(FinalState); loop(ID, [#{desc := Desc, cmd := Cmd}|Cmds], State) when is_function(Cmd, 1) -> - i("evaluate command ~2w: ~s", [ID, Desc]), + iprint("evaluate command ~2w: ~s", [ID, Desc]), try Cmd(State) of ok -> loop(ID + 1, Cmds, State); {ok, NewState} -> loop(ID + 1, Cmds, NewState); {error, Reason} -> - e("command ~w failed: " - "~n Reason: ~p", [ID, Reason]), + eprint("command ~w failed: " + "~n Reason: ~p", [ID, Reason]), exit({command_failed, ID, Reason, State}) catch C:E:S -> - e("command ~w crashed: " - "~n Class: ~p" - "~n Error: ~p" - "~n Call Stack: ~p", [ID, C, E, S]), + eprint("command ~w crashed: " + "~n Class: ~p" + "~n Error: ~p" + "~n Call Stack: ~p", [ID, C, E, S]), exit({command_crashed, ID, {C,E,S}, State}) end. @@ -147,28 +153,22 @@ await_finish(Evs, Fails) -> {'DOWN', _MRef, process, Pid, normal} -> case lists:keysearch(Pid, #ev.pid, Evs) of {value, #ev{name = Name}} -> - - - i("evaluator '~s' (~p) success", [Name, Pid]), - - - - + iprint("evaluator '~s' (~p) success", [Name, Pid]), NewEvs = lists:keydelete(Pid, #ev.pid, Evs), await_finish(NewEvs, Fails); false -> - i("unknown process ~p died (normal)", [Pid]), + iprint("unknown process ~p died (normal)", [Pid]), await_finish(Evs, Fails) end; {'DOWN', _MRef, process, Pid, Reason} -> case lists:keysearch(Pid, #ev.pid, Evs) of {value, #ev{name = Name}} -> - i("evaluator '~s' (~p) failed", [Name, Pid]), + iprint("evaluator '~s' (~p) failed", [Name, Pid]), NewEvs = lists:keydelete(Pid, #ev.pid, Evs), await_finish(NewEvs, [{Pid, Reason}|Fails]); false -> - i("unknown process ~p died: " - "~n ~p", [Pid, Reason]), + iprint("unknown process ~p died: " + "~n ~p", [Pid, Reason]), await_finish(Evs, Fails) end end. @@ -412,25 +412,25 @@ await(ExpPid, Name, Announcement, Slogan, OtherPids) {Announcement, Pid, Slogan, Extra} when (Pid =:= ExpPid) -> {ok, Extra}; {'DOWN', _, process, Pid, Reason} when (Pid =:= ExpPid) -> - e("Unexpected DOWN regarding ~w ~p: " - "~n ~p", [Name, Pid, Reason]), + eprint("Unexpected DOWN regarding ~w ~p: " + "~n ~p", [Name, Pid, Reason]), {error, {unexpected_exit, Name}}; {'DOWN', _, process, OtherPid, Reason} -> case check_down(OtherPid, Reason, OtherPids) of ok -> - i("DOWN from unknown process ~p: " - "~n ~p", [OtherPid, Reason]), + iprint("DOWN from unknown process ~p: " + "~n ~p", [OtherPid, Reason]), await(ExpPid, Name, Announcement, Slogan, OtherPids); {error, _} = ERROR -> ERROR end after infinity -> % For easy debugging, just change to some valid time (5000) - i("await -> timeout for msg from ~p (~w): " - "~n Announcement: ~p" - "~n Slogan: ~p" - "~nwhen" - "~n Messages: ~p", - [ExpPid, Name, Announcement, Slogan, pi(messages)]), + iprint("await -> timeout for msg from ~p (~w): " + "~n Announcement: ~p" + "~n Slogan: ~p" + "~nwhen" + "~n Messages: ~p", + [ExpPid, Name, Announcement, Slogan, pi(messages)]), await(ExpPid, Name, Announcement, Slogan, OtherPids) end. @@ -444,8 +444,8 @@ pi(Pid, Item) -> check_down(Pid, DownReason, Pids) -> case lists:keymember(Pid, 1, Pids) of {value, {_, Name}} -> - e("Unexpected DOWN regarding ~w ~p: " - "~n ~p", [Name, Pid, DownReason]), + eprint("Unexpected DOWN regarding ~w ~p: " + "~n ~p", [Name, Pid, DownReason]), {error, {unexpected_exit, Name}}; false -> ok @@ -458,14 +458,10 @@ f(F, A) -> lists:flatten(io_lib:format(F, A)). -%% i(F) -> -%% i(F, []). -i(F, A) -> +iprint(F, A) -> print("", F, A). -%% e(F) -> -%% e(F, []). -e(F, A) -> +eprint(F, A) -> print(" ", F, A). print(Prefix, F, A) -> diff --git a/erts/emulator/test/socket_test_evaluator.hrl b/erts/emulator/test/socket_test_evaluator.hrl index 1fb42a33b2..2ebae9c852 100644 --- a/erts/emulator/test/socket_test_evaluator.hrl +++ b/erts/emulator/test/socket_test_evaluator.hrl @@ -21,6 +21,10 @@ -ifndef(socket_test_evaluator). -define(socket_test_evaluator, true). +-record(ev, {name :: string(), + pid :: pid(), + mref :: reference()}). + -define(SEV, socket_test_evaluator). -define(SEV_START(N, S, IS), ?SEV:start(N, S, IS)). @@ -44,14 +48,20 @@ -define(SEV_AWAIT_TERMINATION(P), ?SEV:await_termination(P)). -define(SEV_AWAIT_TERMINATION(P, R), ?SEV:await_termination(P, R)). --record(ev, {name :: string(), - pid :: pid(), - mref :: reference()}). +-define(SEV_IPRINT(F, A), ?SEV:iprint(F, A)). +-define(SEV_IPRINT(F), ?SEV_IPRINT(F, [])). +-define(SEV_EPRINT(F, A), ?SEV:eprint(F, A)). +-define(SEV_EPRINT(F), ?SEV_EPRINT(F, [])). --define(FINISH_NORMAL, #{desc => "finish", - cmd => fun(_) -> - {ok, normal} - end}). +-define(SEV_SLEEP(T), #{desc => "sleep", + cmd => fun(_) -> + ?SLEEP(T), + ok + end}). +-define(SEV_FINISH_NORMAL, #{desc => "finish", + cmd => fun(_) -> + {ok, normal} + end}). -endif. % -ifdef(socket_test_evaluator). -- cgit v1.2.3 From 6b9d3908c16a36f47028eaf3728826f442ffad12 Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Mon, 29 Oct 2018 18:42:16 +0100 Subject: [socket-nif|test] Make more use of the evaluator funcs Use the evaluator functions for communication between processes also for communication between "slave" processes (on other nodes). OTP-14831 --- erts/emulator/test/socket_SUITE.erl | 279 +++++++++------------------ erts/emulator/test/socket_test_evaluator.erl | 18 +- erts/emulator/test/socket_test_evaluator.hrl | 1 + 3 files changed, 103 insertions(+), 195 deletions(-) diff --git a/erts/emulator/test/socket_SUITE.erl b/erts/emulator/test/socket_SUITE.erl index 208d8037fb..0d497de7f9 100644 --- a/erts/emulator/test/socket_SUITE.erl +++ b/erts/emulator/test/socket_SUITE.erl @@ -4605,9 +4605,9 @@ sc_rc_receive_response_tcp(InitState) -> %% *** Wait for start order part *** #{desc => "await start", cmd => fun(State) -> - {Tester, {Node, ServerSA}} = ?SEV_AWAIT_START(), + {Tester, {NodeID, ServerSA}} = ?SEV_AWAIT_START(), {ok, State#{tester => Tester, - node => Node, + node_id => NodeID, server_sa => ServerSA}} end}, #{desc => "monitor tester", @@ -4617,39 +4617,42 @@ sc_rc_receive_response_tcp(InitState) -> end}, %% *** Init part *** + #{desc => "create node", + cmd => fun(#{host := Host, node_id := NodeID} = State) -> + case start_node(Host, l2a(f("client_~w", [NodeID]))) of + {ok, Node} -> + ?SEV_IPRINT("client node ~p started", [Node]), + {ok, State#{node => Node}}; + {error, Reason, _} -> + {error, Reason} + end + end}, + #{desc => "monitor client node 1", + cmd => fun(#{node := Node} = _State) -> + true = erlang:monitor_node(Node, true), + ok + end}, #{desc => "start remote client on client node", cmd => fun(#{node := Node} = State) -> Pid = sc_rc_tcp_client_start(Node), ?SEV_IPRINT("client ~p started", [Pid]), - {ok, State#{client => Pid}} + {ok, State#{rclient => Pid}} end}, #{desc => "monitor remote client", - cmd => fun(#{client := Pid}) -> + cmd => fun(#{rclient := Pid}) -> _MRef = erlang:monitor(process, Pid), ok end}, #{desc => "order remote client to start", - cmd => fun(#{client := Client, server_sa := ServerSA}) -> - Client ! {start, self(), ServerSA}, + cmd => fun(#{rclient := Client, server_sa := ServerSA}) -> + ?SEV_ANNOUNCE_START(Client, ServerSA), ok end}, #{desc => "await remote client ready", - cmd => fun(#{tester := Tester, - client := Client} = _State) -> - receive - {ready, Client} -> - ok; - {'DOWN', _, process, Tester, Reason} -> - ?SEV_EPRINT("Unexpected DOWN regarding " - "tester ~p: " - "~n ~p", [Tester, Reason]), - {error, {unexpected_exit, tester}}; - {'DOWN', _, process, Client, Reason} -> - ?SEV_EPRINT("Unexpected DOWN regarding " - "client ~p: " - "~n ~p", [Client, Reason]), - {error, {unexpected_exit, client}} - end + cmd => fun(#{tester := Tester, + rclient := Client} = _State) -> + ?SEV_AWAIT_READY(Client, rclient, init, + [{tester, Tester}]) end}, #{desc => "announce ready (init)", cmd => fun(#{tester := Tester}) -> @@ -4659,34 +4662,22 @@ sc_rc_receive_response_tcp(InitState) -> %% The actual test #{desc => "await continue (connect)", - cmd => fun(#{tester := Tester, - client := Client} = _State) -> + cmd => fun(#{tester := Tester, + rclient := Client} = _State) -> ?SEV_AWAIT_CONTINUE(Tester, tester, connect, [{rclient, Client}]), ok end}, #{desc => "order remote client to continue (connect)", - cmd => fun(#{client := Client}) -> - Client ! {continue, self()}, + cmd => fun(#{rclient := Client}) -> + ?SEV_ANNOUNCE_CONTINUE(Client, connect), ok end}, #{desc => "await client process ready (connect)", - cmd => fun(#{tester := Tester, - client := Client} = _State) -> - receive - {ready, Client} -> - ok; - {'DOWN', _, process, Tester, Reason} -> - ?SEV_EPRINT("Unexpected DOWN regarding " - "tester ~p: " - "~n ~p", [Tester, Reason]), - {error, {unexpected_exit, tester}}; - {'DOWN', _, process, Client, Reason} -> - ?SEV_EPRINT("Unexpected DOWN regarding " - "client ~p: " - "~n ~p", [Client, Reason]), - {error, {unexpected_exit, client}} - end + cmd => fun(#{tester := Tester, + rclient := Client} = _State) -> + ?SEV_AWAIT_READY(Client, rclient, connect, + [{tester, Tester}]) end}, #{desc => "announce ready (connected)", cmd => fun(#{tester := Tester}) -> @@ -4694,34 +4685,22 @@ sc_rc_receive_response_tcp(InitState) -> ok end}, #{desc => "await continue (close)", - cmd => fun(#{tester := Tester, - client := Client} = _State) -> + cmd => fun(#{tester := Tester, + rclient := Client} = _State) -> ?SEV_AWAIT_CONTINUE(Tester, tester, close, [{rclient, Client}]), ok end}, #{desc => "order remote client to close", - cmd => fun(#{client := Client}) -> - Client ! {continue, self()}, + cmd => fun(#{rclient := Client}) -> + ?SEV_ANNOUNCE_CONTINUE(Client, close), ok end}, #{desc => "await remote client ready (closed)", - cmd => fun(#{tester := Tester, - client := Client} = _State) -> - receive - {ready, Client} -> - ok; - {'DOWN', _, process, Tester, Reason} -> - ?SEV_EPRINT("Unexpected DOWN regarding " - "tester ~p: " - "~n ~p", [Tester, Reason]), - {error, {unexpected_exit, tester}}; - {'DOWN', _, process, Client, Reason} -> - ?SEV_EPRINT("Unexpected DOWN regarding " - "client ~p: " - "~n ~p", [Client, Reason]), - {error, {unexpected_exit, client}} - end + cmd => fun(#{tester := Tester, + rclient := Client} = _State) -> + ?SEV_AWAIT_READY(Client, rclient, close, + [{tester, Tester}]) end}, #{desc => "announce ready (close)", cmd => fun(#{tester := Tester}) -> @@ -4731,7 +4710,8 @@ sc_rc_receive_response_tcp(InitState) -> %% Termination #{desc => "await terminate (from tester)", - cmd => fun(#{tester := Tester, client := Client} = State) -> + cmd => fun(#{tester := Tester, + rclient := Client} = State) -> case ?SEV_AWAIT_TERMINATE(Tester, tester, [{rclient, Client}]) of ok -> @@ -4741,15 +4721,27 @@ sc_rc_receive_response_tcp(InitState) -> end end}, #{desc => "kill remote client", - cmd => fun(#{client := Client}) -> - Client ! {terminate, self(), normal}, + cmd => fun(#{rclient := Client}) -> + ?SEV_ANNOUNCE_TERMINATE(Client), ok end}, #{desc => "await remote client termination", - cmd => fun(#{client := Client} = State) -> + cmd => fun(#{rclient := Client} = State) -> + ?SEV_AWAIT_TERMINATION(Client), + State1 = maps:remove(rclient, State), + {ok, State1} + end}, + #{desc => "stop client node", + cmd => fun(#{node := Node} = _State) -> + stop_node(Node) + end}, + #{desc => "await client node termination", + cmd => fun(#{node := Node} = State) -> receive - {'DOWN', _, process, Client, _} -> - {ok, maps:remove(client, State)} + {nodedown, Node} -> + State1 = maps:remove(node_id, State), + State2 = maps:remove(node, State1), + {ok, State2} end end}, @@ -4760,54 +4752,6 @@ sc_rc_receive_response_tcp(InitState) -> TesterSeq = [ %% *** Init part *** - #{desc => "create client node 1", - cmd => fun(#{host := Host} = State) -> - case start_node(Host, client_1) of - {ok, Node} -> - ?SEV_IPRINT("client node (1) ~p started", - [Node]), - {ok, State#{client_node1 => Node}}; - {error, Reason, _} -> - {error, Reason} - end - end}, - #{desc => "monitor client node 1", - cmd => fun(#{client_node1 := Node} = _State) -> - true = erlang:monitor_node(Node, true), - ok - end}, - #{desc => "create client node 2", - cmd => fun(#{host := Host} = State) -> - case start_node(Host, client_2) of - {ok, Node} -> - ?SEV_IPRINT("client node (2) ~p started", - [Node]), - {ok, State#{client_node2 => Node}}; - {error, Reason, _} -> - {error, Reason} - end - end}, - #{desc => "monitor client node 2", - cmd => fun(#{client_node2 := Node} = _State) -> - true = erlang:monitor_node(Node, true), - ok - end}, - #{desc => "create client node 3", - cmd => fun(#{host := Host} = State) -> - case start_node(Host, client_3) of - {ok, Node} -> - ?SEV_IPRINT("client node (3) ~p started", - [Node]), - {ok, State#{client_node3 => Node}}; - {error, Reason, _} -> - {error, Reason} - end - end}, - #{desc => "monitor client node 3", - cmd => fun(#{client_node3 := Node} = _State) -> - true = erlang:monitor_node(Node, true), - ok - end}, #{desc => "monitor server", cmd => fun(#{server := Pid} = _State) -> _MRef = erlang:monitor(process, Pid), @@ -4843,10 +4787,9 @@ sc_rc_receive_response_tcp(InitState) -> %% Start the client(s) #{desc => "order client 1 start", - cmd => fun(#{client1 := Pid, - client_node1 := Node, - server_sa := ServerSA} = _State) -> - ?SEV_ANNOUNCE_START(Pid, {Node, ServerSA}), + cmd => fun(#{client1 := Pid, + server_sa := ServerSA} = _State) -> + ?SEV_ANNOUNCE_START(Pid, {1, ServerSA}), ok end}, #{desc => "await client 1 ready (init)", @@ -4854,10 +4797,9 @@ sc_rc_receive_response_tcp(InitState) -> ok = ?SEV_AWAIT_READY(Pid, client1, init) end}, #{desc => "order client 2 start", - cmd => fun(#{client2 := Pid, - client_node2 := Node, - server_sa := ServerSA} = _State) -> - ?SEV_ANNOUNCE_START(Pid, {Node, ServerSA}), + cmd => fun(#{client2 := Pid, + server_sa := ServerSA} = _State) -> + ?SEV_ANNOUNCE_START(Pid, {2, ServerSA}), ok end}, #{desc => "await client 2 ready (init)", @@ -4865,10 +4807,9 @@ sc_rc_receive_response_tcp(InitState) -> ok = ?SEV_AWAIT_READY(Pid, client2, init) end}, #{desc => "order client 3 start", - cmd => fun(#{client3 := Pid, - client_node3 := Node, - server_sa := ServerSA} = _State) -> - ?SEV_ANNOUNCE_START(Pid, {Node, ServerSA}), + cmd => fun(#{client3 := Pid, + server_sa := ServerSA} = _State) -> + ?SEV_ANNOUNCE_START(Pid, {3, ServerSA}), ok end}, #{desc => "await client 3 ready (init)", @@ -5069,39 +5010,6 @@ sc_rc_receive_response_tcp(InitState) -> ERROR end end}, - #{desc => "stop client node 1", - cmd => fun(#{client_node1 := Node} = _State) -> - stop_node(Node) - end}, - #{desc => "await client node 1 termination", - cmd => fun(#{client_node1 := Node} = State) -> - receive - {nodedown, Node} -> - {ok, maps:remove(client_node1, State)} - end - end}, - #{desc => "stop client node 2", - cmd => fun(#{client_node2 := Node} = _State) -> - stop_node(Node) - end}, - #{desc => "await client node 2 termination", - cmd => fun(#{client_node2 := Node} = State) -> - receive - {nodedown, Node} -> - {ok, maps:remove(client_node2, State)} - end - end}, - #{desc => "stop client node 3", - cmd => fun(#{client_node3 := Node} = _State) -> - stop_node(Node) - end}, - #{desc => "await client node 3 termination", - cmd => fun(#{client_node3 := Node} = State) -> - receive - {nodedown, Node} -> - {ok, maps:remove(client_node3, State)} - end - end}, %% *** We are done *** ?SEV_FINISH_NORMAL @@ -5112,14 +5020,13 @@ sc_rc_receive_response_tcp(InitState) -> Server = ?SEV_START("server", ServerSeq, ServerInitState), i("start client evaluator(s)"), - ClientInitState = InitState, + ClientInitState = InitState#{host => local_host()}, Client1 = ?SEV_START("client-1", ClientSeq, ClientInitState), Client2 = ?SEV_START("client-2", ClientSeq, ClientInitState), Client3 = ?SEV_START("client-3", ClientSeq, ClientInitState), i("start 'tester' evaluator"), - TesterInitState = #{host => local_host(), - server => Server#ev.pid, + TesterInitState = #{server => Server#ev.pid, client1 => Client1#ev.pid, client2 => Client2#ev.pid, client3 => Client3#ev.pid}, @@ -5179,13 +5086,13 @@ sc_rc_tcp_client(Parent, GL) -> Domain = maps:get(family, ServerSA), Sock = sc_rc_tcp_client_create(Domain), sc_rc_tcp_client_bind(Sock, Domain), - sc_rc_tcp_client_announce_ready(Parent), - sc_rc_tcp_client_await_continue(Parent), + sc_rc_tcp_client_announce_ready(Parent, init), + sc_rc_tcp_client_await_continue(Parent, connect), sc_rc_tcp_client_connect(Sock, ServerSA), - sc_rc_tcp_client_announce_ready(Parent), - sc_rc_tcp_client_await_continue(Parent), + sc_rc_tcp_client_announce_ready(Parent, connect), + sc_rc_tcp_client_await_continue(Parent, close), sc_rc_tcp_client_close(Sock), - sc_rc_tcp_client_announce_ready(Parent), + sc_rc_tcp_client_announce_ready(Parent, close), Reason = sc_rc_tcp_client_await_terminate(Parent), exit(Reason). @@ -5197,12 +5104,7 @@ sc_rc_tcp_client_init(Parent, GL) -> sc_rc_tcp_client_await_start(Parent) -> i("sc_rc_tcp_client_await_start -> entry"), - receive - {start, Parent, ServerSA} -> - ServerSA; - {'DOWN', _, process, Parent, _Reason} -> - init:stop() - end. + ?SEV_AWAIT_START(Parent). sc_rc_tcp_client_create(Domain) -> i("sc_rc_tcp_client_create -> entry"), @@ -5225,18 +5127,12 @@ sc_rc_tcp_client_bind(Sock, Domain) -> exit({bind, Reason}) end. -sc_rc_tcp_client_announce_ready(Parent) -> - Parent ! {ready, self()}, - ok. +sc_rc_tcp_client_announce_ready(Parent, Slogan) -> + ?SEV_ANNOUNCE_READY(Parent, Slogan). -sc_rc_tcp_client_await_continue(Parent) -> +sc_rc_tcp_client_await_continue(Parent, Slogan) -> i("sc_rc_tcp_client_await_continue -> entry"), - receive - {continue, Parent} -> - ok; - {'DOWN', _, process, Parent, _Reason} -> - init:stop() - end. + ?SEV_AWAIT_CONTINUE(Parent, parent, Slogan). sc_rc_tcp_client_connect(Sock, ServerSA) -> i("sc_rc_tcp_client_connect -> entry"), @@ -5258,11 +5154,11 @@ sc_rc_tcp_client_close(Sock) -> sc_rc_tcp_client_await_terminate(Parent) -> i("sc_rc_tcp_client_await_terminate -> entry"), - receive - {terminate, Parent, Reason} -> - Reason; - {'DOWN', _, process, Parent, _Reason} -> - init:stop() + case ?SEV_AWAIT_TERMINATE(Parent, parent) of + ok -> + ok; + {error, Reason} -> + Reason end. @@ -6047,6 +5943,9 @@ tc_which_name() -> %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +l2a(S) when is_list(S) -> + list_to_atom(S). + f(F, A) -> lists:flatten(io_lib:format(F, A)). diff --git a/erts/emulator/test/socket_test_evaluator.erl b/erts/emulator/test/socket_test_evaluator.erl index bad8c92dec..e8755a9512 100644 --- a/erts/emulator/test/socket_test_evaluator.erl +++ b/erts/emulator/test/socket_test_evaluator.erl @@ -37,7 +37,7 @@ %% Await functions %% (Wait for an announcement from another evaluator) - await_start/0, + await_start/0, await_start/1, await_continue/3, await_continue/4, await_ready/3, await_ready/4, await_terminate/2, await_terminate/3, @@ -267,15 +267,23 @@ announce(To, Announcement, Slogan, Extra) Extra :: term(). await_start() -> - case await(any, ?START_NAME_NONE, ?ANNOUNCEMENT_START, ?START_SLOGAN, []) of - {ok, Pid} when is_pid(Pid) -> + await_start(any). + +-spec await_start(Pid) -> Pid | {Pid, Extra} when + Pid :: pid(), + Extra :: term(). + +await_start(P) when is_pid(P) orelse (P =:= any) -> + case await(P, ?START_NAME_NONE, ?ANNOUNCEMENT_START, ?START_SLOGAN, []) of + {ok, Any} when is_pid(P) -> + Any; + {ok, Pid} when is_pid(Pid) andalso (P =:= any) -> Pid; - {ok, {Pid, _} = OK} when is_pid(Pid) -> + {ok, {Pid, _} = OK} when is_pid(Pid) andalso (P =:= any) -> OK end. - %% ============================================================================ -spec await_continue(From, Name, Slogan) -> ok | {ok, Extra} | {error, Reason} when diff --git a/erts/emulator/test/socket_test_evaluator.hrl b/erts/emulator/test/socket_test_evaluator.hrl index 2ebae9c852..5be49dc022 100644 --- a/erts/emulator/test/socket_test_evaluator.hrl +++ b/erts/emulator/test/socket_test_evaluator.hrl @@ -39,6 +39,7 @@ -define(SEV_ANNOUNCE_TERMINATE(To), ?SEV:announce_terminate(To)). -define(SEV_AWAIT_START(), ?SEV:await_start()). +-define(SEV_AWAIT_START(P), ?SEV:await_start(P)). -define(SEV_AWAIT_CONTINUE(F, N, S), ?SEV:await_continue(F, N, S)). -define(SEV_AWAIT_CONTINUE(F, N, S, Ps), ?SEV:await_continue(F, N, S, Ps)). -define(SEV_AWAIT_READY(F, N, S), ?SEV:await_ready(F, N, S)). -- cgit v1.2.3 From c3f18b326d27d404505865faf03c199e6e6ae34d Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Tue, 30 Oct 2018 12:12:49 +0100 Subject: [socket-nif|test] Make more use of the evaluator funcs Make more use the evaluator functions for communication between processes. Specifically, between the server and its handler processes in the sc_rc_receive_response_tcp test cases(s). OTP-14831 --- erts/emulator/test/socket_SUITE.erl | 164 +++++++++++++++++------------------- 1 file changed, 77 insertions(+), 87 deletions(-) diff --git a/erts/emulator/test/socket_SUITE.erl b/erts/emulator/test/socket_SUITE.erl index 0d497de7f9..3308b9364e 100644 --- a/erts/emulator/test/socket_SUITE.erl +++ b/erts/emulator/test/socket_SUITE.erl @@ -4431,9 +4431,7 @@ sc_rc_receive_response_tcp(InitState) -> case socket:accept(LSock) of {ok, Sock} -> ?SEV_IPRINT("accepted: try start handler"), - {ok, Handler} = sc_rc_tcp_handler_start(1, - Recv, - Sock), + Handler = sc_rc_tcp_handler_start(1, Recv, Sock), ?SEV_IPRINT("handler started"), {ok, State#{csock1 => Sock, handler1 => Handler}}; @@ -4441,14 +4439,18 @@ sc_rc_receive_response_tcp(InitState) -> ERROR end end}, + #{desc => "await handle 1 ready (init)", + cmd => fun(#{tester := Tester, + handler1 := Handler1} = _State) -> + ?SEV_AWAIT_READY(Handler1, handler1, init, + [{tester, Tester}]) + end}, #{desc => "accept 2", cmd => fun(#{lsock := LSock, recv := Recv} = State) -> case socket:accept(LSock) of {ok, Sock} -> ?SEV_IPRINT("accepted: try start handler"), - {ok, Handler} = sc_rc_tcp_handler_start(2, - Recv, - Sock), + Handler = sc_rc_tcp_handler_start(2, Recv, Sock), ?SEV_IPRINT("handler started"), {ok, State#{csock2 => Sock, handler2 => Handler}}; @@ -4456,14 +4458,20 @@ sc_rc_receive_response_tcp(InitState) -> ERROR end end}, + #{desc => "await handle 2 ready (init)", + cmd => fun(#{tester := Tester, + handler1 := Handler1, + handler2 := Handler2} = _State) -> + ?SEV_AWAIT_READY(Handler2, handler2, init, + [{tester, Tester}, + {handler1, Handler1}]) + end}, #{desc => "accept 3", cmd => fun(#{lsock := LSock, recv := Recv} = State) -> case socket:accept(LSock) of {ok, Sock} -> ?SEV_IPRINT("accepted: try start handler"), - {ok, Handler} = sc_rc_tcp_handler_start(3, - Recv, - Sock), + Handler = sc_rc_tcp_handler_start(3, Recv, Sock), ?SEV_IPRINT("handler started"), {ok, State#{csock3 => Sock, handler3 => Handler}}; @@ -4471,6 +4479,16 @@ sc_rc_receive_response_tcp(InitState) -> ERROR end end}, + #{desc => "await handle 3 ready (init)", + cmd => fun(#{tester := Tester, + handler1 := Handler1, + handler2 := Handler2, + handler3 := Handler3} = _State) -> + ?SEV_AWAIT_READY(Handler3, handler3, init, + [{tester, Tester}, + {handler1, Handler1}, + {handler2, Handler2}]) + end}, #{desc => "announce ready (accept all three connections)", cmd => fun(#{tester := Tester}) -> ?SEV_ANNOUNCE_READY(Tester, accept), @@ -4482,50 +4500,47 @@ sc_rc_receive_response_tcp(InitState) -> end}, #{desc => "order handler 1 to receive", cmd => fun(#{handler1 := Pid} = _State) -> - Pid ! {recv, self()}, + ?SEV_ANNOUNCE_CONTINUE(Pid, recv), ok end}, #{desc => "order handler 2 to receive", cmd => fun(#{handler2 := Pid} = _State) -> - Pid ! {recv, self()}, + ?SEV_ANNOUNCE_CONTINUE(Pid, recv), ok end}, #{desc => "order handler 3 to receive", cmd => fun(#{handler3 := Pid} = _State) -> - Pid ! {recv, self()}, + ?SEV_ANNOUNCE_CONTINUE(Pid, recv), ok end}, #{desc => "await ready from handler 1 (recv)", - cmd => fun(#{handler1 := Pid} = _State) -> - receive - {ready, Pid, ok} -> - ok; - {ready, Pid, Result} -> + cmd => fun(#{tester := Tester, handler1 := Pid} = _State) -> + case ?SEV_AWAIT_READY(Pid, handler1, recv, + [{tester, Tester}]) of + {ok, Result} -> Result; - {'DOWN', _, process, Pid, Reason} -> - {error, {handler1_exit, Reason}} + {error, _} = ERROR -> + ERROR end end}, #{desc => "await ready from handler 2 (recv)", - cmd => fun(#{handler2 := Pid} = _State) -> - receive - {ready, Pid, ok} -> - ok; - {ready, Pid, Result} -> + cmd => fun(#{tester := Tester, handler2 := Pid} = _State) -> + case ?SEV_AWAIT_READY(Pid, handler2, recv, + [{tester, Tester}]) of + {ok, Result} -> Result; - {'DOWN', _, process, Pid, Reason} -> - {error, {handler2_exit, Reason}} + {error, _} = ERROR -> + ERROR end end}, #{desc => "await ready from handler 3 (recv)", - cmd => fun(#{handler3 := Pid} = _State) -> - receive - {ready, Pid, ok} -> - ok; - {ready, Pid, Result} -> + cmd => fun(#{tester := Tester, handler3 := Pid} = _State) -> + case ?SEV_AWAIT_READY(Pid, handler3, recv, + [{tester, Tester}]) of + {ok, Result} -> Result; - {'DOWN', _, process, Pid, Reason} -> - {error, {handler3_exit, Reason}} + {error, _} = ERROR -> + ERROR end end}, #{desc => "announce ready (recv closed from all handlers)", @@ -4546,45 +4561,40 @@ sc_rc_receive_response_tcp(InitState) -> end}, #{desc => "order handler 1 to terminate", cmd => fun(#{handler1 := Pid} = _State) -> - Pid ! {terminate, self(), ok}, + %% Pid ! {terminate, self(), ok}, + ?SEV_ANNOUNCE_TERMINATE(Pid), ok end}, #{desc => "await handler 1 termination", cmd => fun(#{handler1 := Pid} = State) -> - receive - {'DOWN', _, process, Pid, _} -> - State1 = maps:remove(csock1, State), - State2 = maps:remove(handler1, State1), - {ok, State2} - end + ?SEV_AWAIT_TERMINATION(Pid), + State1 = maps:remove(csock1, State), + State2 = maps:remove(handler1, State1), + {ok, State2} end}, #{desc => "order handler 2 to terminate", cmd => fun(#{handler2 := Pid} = _State) -> - Pid ! {terminate, self(), ok}, + ?SEV_ANNOUNCE_TERMINATE(Pid), ok end}, #{desc => "await handler 2 termination", cmd => fun(#{handler2 := Pid} = State) -> - receive - {'DOWN', _, process, Pid, _} -> - State1 = maps:remove(csock2, State), - State2 = maps:remove(handler2, State1), - {ok, State2} - end + ?SEV_AWAIT_TERMINATION(Pid), + State1 = maps:remove(csock2, State), + State2 = maps:remove(handler2, State1), + {ok, State2} end}, #{desc => "order handler 3 to terminate", cmd => fun(#{handler3 := Pid} = _State) -> - Pid ! {terminate, self(), ok}, + ?SEV_ANNOUNCE_TERMINATE(Pid), ok end}, #{desc => "await handler 3 termination", cmd => fun(#{handler3 := Pid} = State) -> - receive - {'DOWN', _, process, Pid, _} -> - State1 = maps:remove(csock3, State), - State2 = maps:remove(handler3, State1), - {ok, State2} - end + ?SEV_AWAIT_TERMINATION(Pid), + State1 = maps:remove(csock3, State), + State2 = maps:remove(handler3, State1), + {ok, State2} end}, #{desc => "close listen socket", cmd => fun(#{lsock := LSock} = State) -> @@ -5168,38 +5178,29 @@ sc_rc_tcp_handler_start(ID, Recv, Sock) -> Self = self(), Fun = fun() -> sc_rc_tcp_handler(ID, Self, Recv, Sock) end, {Pid, _} = erlang:spawn_monitor(Fun), - receive - {started, Pid} -> - {ok, Pid}; - {'DOWN', _, process, Pid, Reason} -> - {error, Reason} - end. + Pid. sc_rc_tcp_handler(ID, Parent, Recv, Sock) -> sc_rc_tcp_handler_init(ID, Parent), - sc_rc_tcp_handler_await_recv(Parent), + sc_rc_tcp_handler_await(Parent, recv), RecvRes = sc_rc_tcp_handler_recv(Recv, Sock), - sc_rc_tcp_handler_announce_ready(Parent, RecvRes), - Reason = sc_rc_tcp_handler_await_terminate(Parent), + sc_rc_tcp_handler_announce_ready(Parent, recv, RecvRes), + Reason = sc_rc_tcp_handler_await(Parent, terminate), exit(Reason). sc_rc_tcp_handler_init(ID, Parent) -> put(sname, f("handler-~w", [ID])), _MRef = erlang:monitor(process, Parent), - Parent ! {started, self()}, ?SEV_IPRINT("started"), + ?SEV_ANNOUNCE_READY(Parent, init), ok. -sc_rc_tcp_handler_await_recv(Parent) -> - ?SEV_IPRINT("await recv"), - receive - {recv, Parent} -> - ok; - {'DOWN', _, process, Parent, Reason} -> - ?SEV_EPRINT("received DOWN regarding parent: " - "~n ~p", [Reason]), - exit({parent, Reason}) - end. +sc_rc_tcp_handler_await(Parent, terminate) -> + ?SEV_IPRINT("await terminate"), + ?SEV_AWAIT_TERMINATE(Parent, tester); +sc_rc_tcp_handler_await(Parent, Slogan) -> + ?SEV_IPRINT("await ~w", [Slogan]), + ?SEV_AWAIT_CONTINUE(Parent, parent, Slogan). sc_rc_tcp_handler_recv(Recv, Sock) -> ?SEV_IPRINT("recv"), @@ -5222,22 +5223,11 @@ sc_rc_tcp_handler_recv(Recv, Sock) -> {error, {recv, C, E, S}} end. -sc_rc_tcp_handler_announce_ready(Parent, Result) -> +sc_rc_tcp_handler_announce_ready(Parent, Slogan, Result) -> ?SEV_IPRINT("announce ready"), - Parent ! {ready, self(), Result}, + ?SEV_ANNOUNCE_READY(Parent, Slogan, Result), ok. -sc_rc_tcp_handler_await_terminate(Parent) -> - ?SEV_IPRINT("await terminate"), - receive - {terminate, Parent, Reason} -> - Reason; - {'DOWN', _, process, Parent, Reason} -> - ?SEV_EPRINT("received DOWN regarding parent: " - "~n ~p", [Reason]), - {parent, Reason} - end. - %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% This test case is intended to test what happens when a socket is -- cgit v1.2.3 From 3f46e8a184a503ead01674ee180e7222b3928712 Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Tue, 30 Oct 2018 18:26:10 +0100 Subject: [socket-nif] Add a send and receive chunks test case The send and recv test case triggered a two bugs. One was that there was no re-selecting when only a portion of the data was received (which meant that we stopped reading). Also, the wrong 'current' (writer) was reset when demonitor current reader after a successful read (which meant that future readers would never have been monitored). OTP-14831 --- erts/emulator/nifs/common/socket_nif.c | 20 +- erts/emulator/test/socket_SUITE.erl | 3058 +++++++++++++++++++++----------- erts/preloaded/ebin/socket.beam | Bin 68544 -> 68564 bytes erts/preloaded/src/socket.erl | 11 + 4 files changed, 2050 insertions(+), 1039 deletions(-) diff --git a/erts/emulator/nifs/common/socket_nif.c b/erts/emulator/nifs/common/socket_nif.c index f9eb041ad1..27395b5cf6 100644 --- a/erts/emulator/nifs/common/socket_nif.c +++ b/erts/emulator/nifs/common/socket_nif.c @@ -13479,7 +13479,7 @@ ERL_NIF_TERM recv_update_current_reader(ErlNifEnv* env, descP->currentReader.ref); } else { - descP->currentWriterP = NULL; + descP->currentReaderP = NULL; } } @@ -13744,14 +13744,23 @@ ERL_NIF_TERM recv_check_result(ErlNifEnv* env, } else { /* +++ We got only a part of what was expected +++ - * +++ => receive more later. +++ */ + * +++ => select for more more later and +++ + * +++ deliver what we got. +++ */ SSDBG( descP, ("SOCKET", "recv_check_result -> [%d] " "only part of message - expect more\r\n", toRead) ); + /* SELECT for more data */ + + SELECT(env, descP->sock, (ERL_NIF_SELECT_READ), + descP, NULL, recvRef); + cnt_inc(&descP->readByteCnt, read); - return esock_make_ok3(env, atom_false, MKBIN(env, bufP)); + data = MKBIN(env, bufP); + data = MKSBIN(env, data, 0, read); + + return esock_make_ok3(env, atom_false, data); } } } @@ -16709,7 +16718,7 @@ int esock_monitor(const char* slogan, int res; SSDBG( descP, ("SOCKET", "[%d] %s: try monitor", descP->sock, slogan) ); - // esock_dbg_printf("MONP", "[%d] %s\r\n", descP->sock, slogan); + /* esock_dbg_printf("MONP", "[%d] %s\r\n", descP->sock, slogan); */ res = enif_monitor_process(env, descP, pid, &monP->mon); if (res != 0) { @@ -16722,7 +16731,7 @@ int esock_monitor(const char* slogan, descP->sock, monP->raw[0], monP->raw[1], monP->raw[2], monP->raw[3]); - } */ + } */ return res; } @@ -16737,6 +16746,7 @@ int esock_demonitor(const char* slogan, int res; SSDBG( descP, ("SOCKET", "[%d] %s: try demonitor\r\n", descP->sock, slogan) ); + /* esock_dbg_printf("DEMONP", "[%d] %s: %u,%u,%u,%u\r\n", descP->sock, slogan, diff --git a/erts/emulator/test/socket_SUITE.erl b/erts/emulator/test/socket_SUITE.erl index 3308b9364e..7a1a362181 100644 --- a/erts/emulator/test/socket_SUITE.erl +++ b/erts/emulator/test/socket_SUITE.erl @@ -84,10 +84,11 @@ sc_rc_recv_response_tcp4/1, sc_rc_recv_response_tcp6/1, sc_rc_recvmsg_response_tcp4/1, - sc_rc_recvmsg_response_tcp6/1 + sc_rc_recvmsg_response_tcp6/1, %% Traffic - + traffic_send_and_recv_chunks_tcp4/1, + traffic_send_and_recv_chunks_tcp6/1 %% Tickets @@ -99,21 +100,6 @@ %% Internal exports %% -export([]). -%% -record(ev, {name :: string(), -%% pid :: pid(), -%% mref :: reference()}). -%% -type ev() :: #ev{}. --define(MKEV(N,P,R), #ev{name = N, pid = P, mref = R}). -%% -type initial_evaluator_state() :: map(). -%% -type evaluator_state() :: map(). -%% -type command_fun() :: -%% fun((State :: evaluator_state()) -> ok) | -%% fun((State :: evaluator_state()) -> {ok, evaluator_state()}) | -%% fun((State :: evaluator_state()) -> {error, term()}). - -%% -type command() :: #{desc := string(), -%% cmd := command_fun()}. - %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -139,7 +125,8 @@ suite() -> all() -> [ {group, api}, - {group, socket_closure} + {group, socket_closure}, + {group, traffic} %% {group, tickets} ]. @@ -151,7 +138,8 @@ groups() -> {socket_closure, [], socket_closure_cases()}, {sc_ctrl_proc_exit, [], sc_cp_exit_cases()}, {sc_local_close, [], sc_lc_cases()}, - {sc_remote_close, [], sc_rc_cases()} + {sc_remote_close, [], sc_rc_cases()}, + {traffic, [], traffic_cases()} %% {tickets, [], ticket_cases()} ]. @@ -252,8 +240,11 @@ sc_rc_cases() -> ]. -%% traffic_cases() -> -%% [snr_send_and_recv_chunks]. +traffic_cases() -> + [ + traffic_send_and_recv_chunks_tcp4, + traffic_send_and_recv_chunks_tcp6 + ]. %% ticket_cases() -> @@ -881,8 +872,7 @@ api_b_send_and_recv_tcp(InitState) -> end}, #{desc => "await client ready (init)", cmd => fun(#{client := Pid} = _State) -> - ?SEV_AWAIT_READY(Pid, client, init), - ok + ok = ?SEV_AWAIT_READY(Pid, client, init) end}, %% *** The actual test *** @@ -4315,66 +4305,59 @@ sc_lc_recvmsg_response_udp6(_Config) when is_list(_Config) -> end). + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% This test case is intended to test what happens when a socket is -%% remotely closed while the process is calling the recv function. +%% locally closed while the process is calling the accept function. +%% We test what happens with a non-controlling_process also, since we +%% git the setup anyway. %% Socket is IPv4. -%% -%% To minimize the chance of "weirdness", we should really have test cases -%% where the two sides of the connection is on different machines. But for -%% now, we will make do with different VMs on the same host. -%% -sc_rc_recv_response_tcp4(suite) -> +sc_lc_acceptor_response_tcp4(suite) -> []; -sc_rc_recv_response_tcp4(doc) -> +sc_lc_acceptor_response_tcp4(doc) -> []; -sc_rc_recv_response_tcp4(_Config) when is_list(_Config) -> - tc_try(sc_rc_recv_response_tcp4, +sc_lc_acceptor_response_tcp4(_Config) when is_list(_Config) -> + tc_try(sc_lc_acceptor_response_tcp4, fun() -> - ?TT(?SECS(30)), - Recv = fun(Sock) -> socket:recv(Sock) end, + ?TT(?SECS(10)), InitState = #{domain => inet, type => stream, - protocol => tcp, - recv => Recv}, - ok = sc_rc_receive_response_tcp(InitState) + protocol => tcp}, + ok = sc_lc_acceptor_response_tcp(InitState) end). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% This test case is intended to test what happens when a socket is -%% remotely closed while the process is calling the recv function. +%% locally closed while the process is calling the accept function. +%% We test what happens with a non-controlling_process also, since we +%% git the setup anyway. %% Socket is IPv6. -sc_rc_recv_response_tcp6(suite) -> +sc_lc_acceptor_response_tcp6(suite) -> []; -sc_rc_recv_response_tcp6(doc) -> +sc_lc_acceptor_response_tcp6(doc) -> []; -sc_rc_recv_response_tcp6(_Config) when is_list(_Config) -> - tc_try(sc_rc_recv_response_tcp6, +sc_lc_acceptor_response_tcp6(_Config) when is_list(_Config) -> + tc_try(sc_lc_acceptor_response_tcp6, fun() -> not_yet_implemented(), ?TT(?SECS(10)), - Recv = fun(Sock) -> socket:recv(Sock) end, - InitState = #{domain => inet6, + InitState = #{domain => inet, type => stream, - protocol => tcp, - recv => Recv}, - ok = sc_rc_receive_response_tcp(InitState) + protocol => tcp}, + ok = sc_lc_acceptor_response_tcp(InitState) end). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -sc_rc_receive_response_tcp(InitState) -> - %% Each connection are handled by handler processes. - %% These are created (on the fly) and handled internally - %% by the server! - ServerSeq = +sc_lc_acceptor_response_tcp(InitState) -> + PrimAcceptorSeq = [ %% *** Wait for start order part *** - #{desc => "await start", + #{desc => "await start (from tester)", cmd => fun(State) -> Tester = ?SEV_AWAIT_START(), {ok, State#{tester => Tester}} @@ -4390,167 +4373,87 @@ sc_rc_receive_response_tcp(InitState) -> cmd => fun(#{domain := Domain} = State) -> LAddr = which_local_addr(Domain), LSA = #{family => Domain, addr => LAddr}, - {ok, State#{local_sa => LSA}} + {ok, State#{lsa => LSA}} end}, - #{desc => "create listen socket", - cmd => fun(#{domain := Domain} = State) -> - case socket:open(Domain, stream, tcp) of + #{desc => "create (listen) socket", + cmd => fun(#{domain := Domain, + type := Type, + protocol := Proto} = State) -> + case socket:open(Domain, Type, Proto) of {ok, Sock} -> - {ok, State#{lsock => Sock}}; + {ok, State#{sock => Sock}}; {error, _} = ERROR -> ERROR end end}, #{desc => "bind to local address", - cmd => fun(#{lsock := LSock, local_sa := LSA} = State) -> - case socket:bind(LSock, LSA) of - {ok, Port} -> - {ok, State#{lport => Port}}; + cmd => fun(#{sock := Sock, lsa := LSA} = _State) -> + case socket:bind(Sock, LSA) of + {ok, _Port} -> + ok; {error, _} = ERROR -> ERROR end end}, #{desc => "make listen socket", - cmd => fun(#{lsock := LSock}) -> - socket:listen(LSock) + cmd => fun(#{sock := Sock}) -> + socket:listen(Sock) end}, #{desc => "announce ready (init)", - cmd => fun(#{tester := Tester, local_sa := LSA, lport := Port}) -> - ServerSA = LSA#{port => Port}, - ?SEV_ANNOUNCE_READY(Tester, init, ServerSA), + cmd => fun(#{tester := Tester, sock := Sock} = _State) -> + ?SEV_ANNOUNCE_READY(Tester, init, Sock), ok end}, - %% The actual test (we expect three connections) - #{desc => "await continue (accept all three connections)", - cmd => fun(#{tester := Tester} = _State) -> - ?SEV_ANNOUNCE_CONTINUE(Tester, tester, accept) - end}, - #{desc => "accept 1", - cmd => fun(#{lsock := LSock, recv := Recv} = State) -> - case socket:accept(LSock) of - {ok, Sock} -> - ?SEV_IPRINT("accepted: try start handler"), - Handler = sc_rc_tcp_handler_start(1, Recv, Sock), - ?SEV_IPRINT("handler started"), - {ok, State#{csock1 => Sock, - handler1 => Handler}}; - {error, _} = ERROR -> - ERROR - end - end}, - #{desc => "await handle 1 ready (init)", - cmd => fun(#{tester := Tester, - handler1 := Handler1} = _State) -> - ?SEV_AWAIT_READY(Handler1, handler1, init, - [{tester, Tester}]) - end}, - #{desc => "accept 2", - cmd => fun(#{lsock := LSock, recv := Recv} = State) -> - case socket:accept(LSock) of - {ok, Sock} -> - ?SEV_IPRINT("accepted: try start handler"), - Handler = sc_rc_tcp_handler_start(2, Recv, Sock), - ?SEV_IPRINT("handler started"), - {ok, State#{csock2 => Sock, - handler2 => Handler}}; + %% The actual test + #{desc => "await continue (accept)", + cmd => fun(#{tester := Tester} = State) -> + case ?SEV_AWAIT_CONTINUE(Tester, tester, accept) of + {ok, Timeout} -> + {ok, State#{timeout => Timeout}}; {error, _} = ERROR -> ERROR end end}, - #{desc => "await handle 2 ready (init)", - cmd => fun(#{tester := Tester, - handler1 := Handler1, - handler2 := Handler2} = _State) -> - ?SEV_AWAIT_READY(Handler2, handler2, init, - [{tester, Tester}, - {handler1, Handler1}]) - end}, - #{desc => "accept 3", - cmd => fun(#{lsock := LSock, recv := Recv} = State) -> - case socket:accept(LSock) of + #{desc => "await connection", + cmd => fun(#{sock := Sock, timeout := Timeout} = _State) -> + case socket:accept(Sock, Timeout) of + {error, timeout} -> + ok; {ok, Sock} -> - ?SEV_IPRINT("accepted: try start handler"), - Handler = sc_rc_tcp_handler_start(3, Recv, Sock), - ?SEV_IPRINT("handler started"), - {ok, State#{csock3 => Sock, - handler3 => Handler}}; + ?SEV_EPRINT("unexpected success"), + (catch socket:close(Sock)), + {error, unexpected_success}; {error, _} = ERROR -> ERROR end end}, - #{desc => "await handle 3 ready (init)", - cmd => fun(#{tester := Tester, - handler1 := Handler1, - handler2 := Handler2, - handler3 := Handler3} = _State) -> - ?SEV_AWAIT_READY(Handler3, handler3, init, - [{tester, Tester}, - {handler1, Handler1}, - {handler2, Handler2}]) - end}, - #{desc => "announce ready (accept all three connections)", + #{desc => "announce ready (accept timeout)", cmd => fun(#{tester := Tester}) -> - ?SEV_ANNOUNCE_READY(Tester, accept), + ?SEV_ANNOUNCE_READY(Tester, accept_timeout), ok end}, - #{desc => "await continue (recv)", + #{desc => "await continue (close)", cmd => fun(#{tester := Tester} = _State) -> - ?SEV_AWAIT_CONTINUE(Tester, tester, recv) - end}, - #{desc => "order handler 1 to receive", - cmd => fun(#{handler1 := Pid} = _State) -> - ?SEV_ANNOUNCE_CONTINUE(Pid, recv), - ok - end}, - #{desc => "order handler 2 to receive", - cmd => fun(#{handler2 := Pid} = _State) -> - ?SEV_ANNOUNCE_CONTINUE(Pid, recv), - ok - end}, - #{desc => "order handler 3 to receive", - cmd => fun(#{handler3 := Pid} = _State) -> - ?SEV_ANNOUNCE_CONTINUE(Pid, recv), - ok - end}, - #{desc => "await ready from handler 1 (recv)", - cmd => fun(#{tester := Tester, handler1 := Pid} = _State) -> - case ?SEV_AWAIT_READY(Pid, handler1, recv, - [{tester, Tester}]) of - {ok, Result} -> - Result; - {error, _} = ERROR -> - ERROR - end - end}, - #{desc => "await ready from handler 2 (recv)", - cmd => fun(#{tester := Tester, handler2 := Pid} = _State) -> - case ?SEV_AWAIT_READY(Pid, handler2, recv, - [{tester, Tester}]) of - {ok, Result} -> - Result; - {error, _} = ERROR -> - ERROR - end + ok = ?SEV_AWAIT_CONTINUE(Tester, tester, close) end}, - #{desc => "await ready from handler 3 (recv)", - cmd => fun(#{tester := Tester, handler3 := Pid} = _State) -> - case ?SEV_AWAIT_READY(Pid, handler3, recv, - [{tester, Tester}]) of - {ok, Result} -> - Result; + #{desc => "close socket", + cmd => fun(#{sock := Sock} = State) -> + case socket:close(Sock) of + ok -> + {ok, maps:remove(sock, State)}; {error, _} = ERROR -> ERROR end end}, - #{desc => "announce ready (recv closed from all handlers)", + #{desc => "announce ready (close)", cmd => fun(#{tester := Tester}) -> - ?SEV_ANNOUNCE_READY(Tester, recv_closed), + ?SEV_ANNOUNCE_READY(Tester, close), ok end}, - - %% Termination - #{desc => "await terminate (from tester)", + + % Termination + #{desc => "await terminate", cmd => fun(#{tester := Tester} = State) -> case ?SEV_AWAIT_TERMINATE(Tester, tester) of ok -> @@ -4559,201 +4462,60 @@ sc_rc_receive_response_tcp(InitState) -> ERROR end end}, - #{desc => "order handler 1 to terminate", - cmd => fun(#{handler1 := Pid} = _State) -> - %% Pid ! {terminate, self(), ok}, - ?SEV_ANNOUNCE_TERMINATE(Pid), - ok - end}, - #{desc => "await handler 1 termination", - cmd => fun(#{handler1 := Pid} = State) -> - ?SEV_AWAIT_TERMINATION(Pid), - State1 = maps:remove(csock1, State), - State2 = maps:remove(handler1, State1), - {ok, State2} + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + SecAcceptorSeq = + [ + %% *** Init part *** + #{desc => "await start", + cmd => fun(State) -> + {Tester, Sock} = ?SEV_AWAIT_START(), + {ok, State#{tester => Tester, sock => Sock}} end}, - #{desc => "order handler 2 to terminate", - cmd => fun(#{handler2 := Pid} = _State) -> - ?SEV_ANNOUNCE_TERMINATE(Pid), + #{desc => "monitor tester", + cmd => fun(#{tester := Tester} = _State) -> + _MRef = erlang:monitor(process, Tester), ok end}, - #{desc => "await handler 2 termination", - cmd => fun(#{handler2 := Pid} = State) -> - ?SEV_AWAIT_TERMINATION(Pid), - State1 = maps:remove(csock2, State), - State2 = maps:remove(handler2, State1), - {ok, State2} - end}, - #{desc => "order handler 3 to terminate", - cmd => fun(#{handler3 := Pid} = _State) -> - ?SEV_ANNOUNCE_TERMINATE(Pid), - ok - end}, - #{desc => "await handler 3 termination", - cmd => fun(#{handler3 := Pid} = State) -> - ?SEV_AWAIT_TERMINATION(Pid), - State1 = maps:remove(csock3, State), - State2 = maps:remove(handler3, State1), - {ok, State2} - end}, - #{desc => "close listen socket", - cmd => fun(#{lsock := LSock} = State) -> - case socket:close(LSock) of - ok -> - {ok, maps:remove(lsock, State)}; - {error, _} = ERROR -> - ERROR - end - end}, - - %% *** We are done *** - ?SEV_FINISH_NORMAL - ], - - ClientSeq = - [ - %% *** Wait for start order part *** - #{desc => "await start", - cmd => fun(State) -> - {Tester, {NodeID, ServerSA}} = ?SEV_AWAIT_START(), - {ok, State#{tester => Tester, - node_id => NodeID, - server_sa => ServerSA}} - end}, - #{desc => "monitor tester", - cmd => fun(#{tester := Tester} = _State) -> - _MRef = erlang:monitor(process, Tester), - ok - end}, - - %% *** Init part *** - #{desc => "create node", - cmd => fun(#{host := Host, node_id := NodeID} = State) -> - case start_node(Host, l2a(f("client_~w", [NodeID]))) of - {ok, Node} -> - ?SEV_IPRINT("client node ~p started", [Node]), - {ok, State#{node => Node}}; - {error, Reason, _} -> - {error, Reason} - end - end}, - #{desc => "monitor client node 1", - cmd => fun(#{node := Node} = _State) -> - true = erlang:monitor_node(Node, true), - ok - end}, - #{desc => "start remote client on client node", - cmd => fun(#{node := Node} = State) -> - Pid = sc_rc_tcp_client_start(Node), - ?SEV_IPRINT("client ~p started", [Pid]), - {ok, State#{rclient => Pid}} - end}, - #{desc => "monitor remote client", - cmd => fun(#{rclient := Pid}) -> - _MRef = erlang:monitor(process, Pid), - ok - end}, - #{desc => "order remote client to start", - cmd => fun(#{rclient := Client, server_sa := ServerSA}) -> - ?SEV_ANNOUNCE_START(Client, ServerSA), - ok - end}, - #{desc => "await remote client ready", - cmd => fun(#{tester := Tester, - rclient := Client} = _State) -> - ?SEV_AWAIT_READY(Client, rclient, init, - [{tester, Tester}]) - end}, #{desc => "announce ready (init)", cmd => fun(#{tester := Tester}) -> - ?SEV_ANNOUNCE_READY(Tester, init), - ok + ?SEV_ANNOUNCE_READY(Tester, init) end}, %% The actual test - #{desc => "await continue (connect)", - cmd => fun(#{tester := Tester, - rclient := Client} = _State) -> - ?SEV_AWAIT_CONTINUE(Tester, tester, connect, - [{rclient, Client}]), - ok - end}, - #{desc => "order remote client to continue (connect)", - cmd => fun(#{rclient := Client}) -> - ?SEV_ANNOUNCE_CONTINUE(Client, connect), - ok - end}, - #{desc => "await client process ready (connect)", - cmd => fun(#{tester := Tester, - rclient := Client} = _State) -> - ?SEV_AWAIT_READY(Client, rclient, connect, - [{tester, Tester}]) - end}, - #{desc => "announce ready (connected)", - cmd => fun(#{tester := Tester}) -> - ?SEV_ANNOUNCE_READY(Tester, connect), - ok - end}, - #{desc => "await continue (close)", - cmd => fun(#{tester := Tester, - rclient := Client} = _State) -> - ?SEV_AWAIT_CONTINUE(Tester, tester, close, - [{rclient, Client}]), - ok - end}, - #{desc => "order remote client to close", - cmd => fun(#{rclient := Client}) -> - ?SEV_ANNOUNCE_CONTINUE(Client, close), - ok + #{desc => "await continue (accept)", + cmd => fun(#{tester := Tester} = _State) -> + ok = ?SEV_AWAIT_CONTINUE(Tester, tester, accept) end}, - #{desc => "await remote client ready (closed)", - cmd => fun(#{tester := Tester, - rclient := Client} = _State) -> - ?SEV_AWAIT_READY(Client, rclient, close, - [{tester, Tester}]) + #{desc => "accept", + cmd => fun(#{sock := Sock} = State) -> + case socket:accept(Sock) of + {error, closed} -> + {ok, maps:remove(sock, State)}; + {ok, _} -> + {error, unexpected_success}; + {error, _} = ERROR -> + ERROR + end end}, - #{desc => "announce ready (close)", + #{desc => "announce ready (accept closed)", cmd => fun(#{tester := Tester}) -> - ?SEV_ANNOUNCE_READY(Tester, close), - ok + ?SEV_ANNOUNCE_READY(Tester, accept_closed) end}, %% Termination #{desc => "await terminate (from tester)", - cmd => fun(#{tester := Tester, - rclient := Client} = State) -> - case ?SEV_AWAIT_TERMINATE(Tester, tester, - [{rclient, Client}]) of + cmd => fun(#{tester := Tester} = State) -> + case ?SEV_AWAIT_TERMINATE(Tester, tester) of ok -> {ok, maps:remove(tester, State)}; {error, _} = ERROR -> ERROR end end}, - #{desc => "kill remote client", - cmd => fun(#{rclient := Client}) -> - ?SEV_ANNOUNCE_TERMINATE(Client), - ok - end}, - #{desc => "await remote client termination", - cmd => fun(#{rclient := Client} = State) -> - ?SEV_AWAIT_TERMINATION(Client), - State1 = maps:remove(rclient, State), - {ok, State1} - end}, - #{desc => "stop client node", - cmd => fun(#{node := Node} = _State) -> - stop_node(Node) - end}, - #{desc => "await client node termination", - cmd => fun(#{node := Node} = State) -> - receive - {nodedown, Node} -> - State1 = maps:remove(node_id, State), - State2 = maps:remove(node, State1), - {ok, State2} - end - end}, %% *** We are done *** ?SEV_FINISH_NORMAL @@ -4762,487 +4524,239 @@ sc_rc_receive_response_tcp(InitState) -> TesterSeq = [ %% *** Init part *** - #{desc => "monitor server", - cmd => fun(#{server := Pid} = _State) -> + #{desc => "monitor 'primary acceptor'", + cmd => fun(#{prim_acc := Pid} = _State) -> _MRef = erlang:monitor(process, Pid), ok end}, - #{desc => "monitor client 1", - cmd => fun(#{client1 := Pid} = _State) -> + #{desc => "monitor 'secondary acceptor 1'", + cmd => fun(#{sec_acc1 := Pid} = _State) -> _MRef = erlang:monitor(process, Pid), ok end}, - #{desc => "monitor client 2", - cmd => fun(#{client2 := Pid} = _State) -> + #{desc => "monitor secondary acceptor 2", + cmd => fun(#{sec_acc2 := Pid} = _State) -> _MRef = erlang:monitor(process, Pid), ok end}, - #{desc => "monitor client 3", - cmd => fun(#{client3 := Pid} = _State) -> + #{desc => "monitor secondary acceptor 3", + cmd => fun(#{sec_acc3 := Pid} = _State) -> _MRef = erlang:monitor(process, Pid), ok end}, - %% Start the server - #{desc => "order server start", - cmd => fun(#{server := Pid} = _State) -> + %% Start the primary server + #{desc => "order 'primary acceptor' start", + cmd => fun(#{prim_acc := Pid} = _State) -> ?SEV_ANNOUNCE_START(Pid), ok end}, - #{desc => "await server ready (init)", - cmd => fun(#{server := Pid} = State) -> - {ok, ServerSA} = ?SEV_AWAIT_READY(Pid, server, init), - {ok, State#{server_sa => ServerSA}} + #{desc => "await 'primary acceptor' ready (init)", + cmd => fun(#{prim_acc := Pid} = State) -> + {ok, Sock} = ?SEV_AWAIT_READY(Pid, prim_acc, init), + {ok, State#{sock => Sock}} end}, - %% Start the client(s) - #{desc => "order client 1 start", - cmd => fun(#{client1 := Pid, - server_sa := ServerSA} = _State) -> - ?SEV_ANNOUNCE_START(Pid, {1, ServerSA}), + %% Start the secondary acceptor 1 + #{desc => "order 'secondary acceptor 1' start", + cmd => fun(#{sec_acc1 := Pid, sock := Sock} = _State) -> + ?SEV_ANNOUNCE_START(Pid, Sock), ok end}, - #{desc => "await client 1 ready (init)", - cmd => fun(#{client1 := Pid} = _State) -> - ok = ?SEV_AWAIT_READY(Pid, client1, init) + #{desc => "await 'secondary acceptor 1' ready (init)", + cmd => fun(#{sec_acc1 := Pid} = _State) -> + ok = ?SEV_AWAIT_READY(Pid, sec_acc1, init) end}, - #{desc => "order client 2 start", - cmd => fun(#{client2 := Pid, - server_sa := ServerSA} = _State) -> - ?SEV_ANNOUNCE_START(Pid, {2, ServerSA}), + + %% Start the secondary acceptor 2 + #{desc => "order 'secondary acceptor 2' start", + cmd => fun(#{sec_acc2 := Pid, sock := Sock} = _State) -> + ?SEV_ANNOUNCE_START(Pid, Sock), ok end}, - #{desc => "await client 2 ready (init)", - cmd => fun(#{client2 := Pid} = _State) -> - ok = ?SEV_AWAIT_READY(Pid, client2, init) + #{desc => "await 'secondary acceptor 2' ready (init)", + cmd => fun(#{sec_acc2 := Pid} = _State) -> + ok = ?SEV_AWAIT_READY(Pid, sec_acc2, init) end}, - #{desc => "order client 3 start", - cmd => fun(#{client3 := Pid, - server_sa := ServerSA} = _State) -> - ?SEV_ANNOUNCE_START(Pid, {3, ServerSA}), + + %% Start the secondary acceptor 3 + #{desc => "order 'secondary acceptor 3' start", + cmd => fun(#{sec_acc3 := Pid, sock := Sock} = _State) -> + ?SEV_ANNOUNCE_START(Pid, Sock), ok end}, - #{desc => "await client 3 ready (init)", - cmd => fun(#{client3 := Pid} = _State) -> - ok = ?SEV_AWAIT_READY(Pid, client3, init) + #{desc => "await 'secondary acceptor 3' ready (init)", + cmd => fun(#{sec_acc3 := Pid} = _State) -> + ok = ?SEV_AWAIT_READY(Pid, sec_acc3, init) end}, + %% The actual test - #{desc => "order server continue (accept)", - cmd => fun(#{server := Pid} = _State) -> - ?SEV_ANNOUNCE_CONTINUE(Pid, accept), - ok - end}, - ?SEV_SLEEP(?SECS(1)), - #{desc => "order client 1 continue (connect)", - cmd => fun(#{client1 := Pid} = _State) -> - ?SEV_ANNOUNCE_CONTINUE(Pid, connect), - ok - end}, - #{desc => "await client 1 ready (connect)", - cmd => fun(#{server := Server, - client1 := Client1, - client2 := Client2, - client3 := Client3} = _State) -> - ?SEV_AWAIT_READY(Client1, client1, connect, - [{server, Server}, - {client2, Client2}, - {client3, Client3}]), - ok - end}, - #{desc => "order client 2 continue (connect)", - cmd => fun(#{client2 := Pid} = _State) -> - ?SEV_ANNOUNCE_CONTINUE(Pid, connect), - ok - end}, - #{desc => "await client 2 ready (connect)", - cmd => fun(#{server := Server, - client1 := Client1, - client2 := Client2, - client3 := Client3} = _State) -> - ?SEV_AWAIT_READY(Client2, client2, connect, - [{server, Server}, - {client1, Client1}, - {client3, Client3}]), - ok - end}, - #{desc => "order client 3 continue (connect)", - cmd => fun(#{client3 := Pid} = _State) -> - ?SEV_ANNOUNCE_CONTINUE(Pid, connect), - ok - end}, - #{desc => "await client 3 ready (connect)", - cmd => fun(#{server := Server, - client1 := Client1, - client2 := Client2, - client3 := Client3} = _State) -> - ?SEV_AWAIT_READY(Client3, client3, connect, - [{server, Server}, - {client1, Client1}, - {client2, Client2}]), + %% Make all the seondary servers continue, with an infinit recvfrom + %% and then the prim-server with a timed recvfrom. + %% After the prim server notifies us (about the timeout) we order it + %% to close the socket, which should cause the all the secondary + %% server to return with error-closed. + + #{desc => "order 'secondary acceptor 1' to continue (accept)", + cmd => fun(#{sec_acc1 := Pid} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Pid, accept), ok end}, - #{desc => "await server ready (accept from all connections)", - cmd => fun(#{server := Server, - client1 := Client1, - client2 := Client2, - client3 := Client3} = _State) -> - ?SEV_AWAIT_READY(Server, server, accept, - [{client1, Client1}, - {client2, Client2}, - {client3, Client3}]), + ?SEV_SLEEP(?SECS(1)), + #{desc => "order 'secondary acceptor 2' to continue (accept)", + cmd => fun(#{sec_acc2 := Pid} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Pid, accept), ok end}, - #{desc => "order server continue (recv for all connections)", - cmd => fun(#{server := Pid} = _State) -> - ?SEV_ANNOUNCE_CONTINUE(Pid, recv), + ?SEV_SLEEP(?SECS(1)), + #{desc => "order 'secondary acceptor 3' to continue (accept)", + cmd => fun(#{sec_acc3 := Pid} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Pid, accept), ok end}, ?SEV_SLEEP(?SECS(1)), - #{desc => "order client 1 continue (close)", - cmd => fun(#{client1 := Pid} = _State) -> - ?SEV_ANNOUNCE_CONTINUE(Pid, close), + #{desc => "order 'primary acceptor' to continue", + cmd => fun(#{prim_acc := Pid} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Pid, accept, ?SECS(5)), ok end}, - #{desc => "await client 1 ready (close)", - cmd => fun(#{server := Server, - client1 := Client1, - client2 := Client2, - client3 := Client3} = _State) -> - ?SEV_AWAIT_READY(Client1, client1, close, - [{server, Server}, - {client2, Client2}, - {client3, Client3}]), - ok + #{desc => "await 'primary acceptor' ready (accept timeout)", + cmd => fun(#{prim_acc := Pid} = _State) -> + ok = ?SEV_AWAIT_READY(Pid, prim_acc, accept_timeout) end}, - #{desc => "order client 2 continue (close)", - cmd => fun(#{client2 := Pid} = _State) -> + #{desc => "order 'primary acceptor' to continue (close)", + cmd => fun(#{prim_acc := Pid} = _State) -> ?SEV_ANNOUNCE_CONTINUE(Pid, close), ok end}, - #{desc => "await client 2 ready (close)", - cmd => fun(#{server := Server, - client1 := Client1, - client2 := Client2, - client3 := Client3} = _State) -> - ?SEV_AWAIT_READY(Client2, client2, close, - [{server, Server}, - {client1, Client1}, - {client3, Client3}]), - ok + #{desc => "await 'primary acceptor' ready (close)", + cmd => fun(#{prim_acc := Pid} = _State) -> + ok = ?SEV_AWAIT_READY(Pid, prim_acc, close) end}, - #{desc => "order client 3 continue (close)", - cmd => fun(#{client3 := Pid} = _State) -> - ?SEV_ANNOUNCE_CONTINUE(Pid, close), - ok + #{desc => "await 'secondary acceptor 1' ready (accept closed)", + cmd => fun(#{sec_acc1 := Pid} = _State) -> + ok = ?SEV_AWAIT_READY(Pid, sec_acc1, accept_closed) end}, - #{desc => "await client 3 ready (close)", - cmd => fun(#{server := Server, - client1 := Client1, - client2 := Client2, - client3 := Client3} = _State) -> - ?SEV_AWAIT_READY(Client3, client1, close, - [{server, Server}, - {client1, Client1}, - {client2, Client2}]), - ok + #{desc => "await 'secondary acceptor 2' ready (accept closed)", + cmd => fun(#{sec_acc2 := Pid} = _State) -> + ok = ?SEV_AWAIT_READY(Pid, sec_acc2, accept_closed) end}, - #{desc => "await server ready (close for all connections)", - cmd => fun(#{server := Server, - client1 := Client1, - client2 := Client2, - client3 := Client3} = _State) -> - ?SEV_AWAIT_READY(Server, server, recv_closed, - [{client1, Client1}, - {client2, Client2}, - {client3, Client3}]), - ok + #{desc => "await 'secondary acceptor 3' ready (accept closed)", + cmd => fun(#{sec_acc3 := Pid} = _State) -> + ok = ?SEV_AWAIT_READY(Pid, sec_acc3, accept_closed) end}, + %% Terminations - #{desc => "order client 1 to terminate", - cmd => fun(#{client1 := Pid} = _State) -> + #{desc => "order 'secondary acceptor 3' to terminate", + cmd => fun(#{sec_acc3 := Pid} = _State) -> ?SEV_ANNOUNCE_TERMINATE(Pid), ok end}, - #{desc => "await client 1 termination", - cmd => fun(#{client1 := Pid} = State) -> + #{desc => "await 'secondary acceptor 3' termination", + cmd => fun(#{sec_acc3 := Pid} = State) -> case ?SEV_AWAIT_TERMINATION(Pid) of ok -> - State1 = maps:remove(client1, State), - {ok, State1}; + {ok, maps:remove(sec_acc3, State)}; {error, _} = ERROR -> ERROR end end}, - #{desc => "order client 2 to terminate", - cmd => fun(#{client2 := Pid} = _State) -> + #{desc => "order 'secondary acceptor 2' to terminate", + cmd => fun(#{sec_acc2 := Pid} = _State) -> ?SEV_ANNOUNCE_TERMINATE(Pid), ok end}, - #{desc => "await client 2 termination", - cmd => fun(#{client2 := Pid} = State) -> + #{desc => "await 'secondary acceptor 2' termination", + cmd => fun(#{sec_acc2 := Pid} = State) -> case ?SEV_AWAIT_TERMINATION(Pid) of ok -> - State1 = maps:remove(client2, State), - {ok, State1}; + {ok, maps:remove(sec_acc2, State)}; {error, _} = ERROR -> ERROR end end}, - #{desc => "order client 3 to terminate", - cmd => fun(#{client3 := Pid} = _State) -> + #{desc => "order 'secondary acceptor 1' to terminate", + cmd => fun(#{sec_acc1 := Pid} = _State) -> ?SEV_ANNOUNCE_TERMINATE(Pid), ok end}, - #{desc => "await client 3 termination", - cmd => fun(#{client3 := Pid} = State) -> + #{desc => "await 'secondary acceptor 1' termination", + cmd => fun(#{sec_acc1 := Pid} = State) -> case ?SEV_AWAIT_TERMINATION(Pid) of ok -> - State1 = maps:remove(client3, State), - {ok, State1}; + {ok, maps:remove(sec_acc1, State)}; {error, _} = ERROR -> ERROR end end}, - #{desc => "order server to terminate", - cmd => fun(#{server := Pid} = _State) -> + #{desc => "order 'primary acceptor' to terminate", + cmd => fun(#{prim_acc := Pid} = _State) -> ?SEV_ANNOUNCE_TERMINATE(Pid), ok end}, - #{desc => "await server termination", - cmd => fun(#{server := Pid} = State) -> + #{desc => "await 'primary acceptor' termination", + cmd => fun(#{prim_acc := Pid} = State) -> case ?SEV_AWAIT_TERMINATION(Pid) of ok -> - State1 = maps:remove(server, State), - {ok, State1}; + {ok, maps:remove(prim_acc, State)}; {error, _} = ERROR -> ERROR end end}, + %% *** We are done *** ?SEV_FINISH_NORMAL ], - i("start server evaluator"), - ServerInitState = InitState, - Server = ?SEV_START("server", ServerSeq, ServerInitState), - i("start client evaluator(s)"), - ClientInitState = InitState#{host => local_host()}, - Client1 = ?SEV_START("client-1", ClientSeq, ClientInitState), - Client2 = ?SEV_START("client-2", ClientSeq, ClientInitState), - Client3 = ?SEV_START("client-3", ClientSeq, ClientInitState), + i("start 'primary acceptor' evaluator"), + PrimAccInitState = InitState, + PrimAcc = ?SEV_START("prim-acceptor", PrimAcceptorSeq, PrimAccInitState), + + i("start 'secondary acceptor 1' evaluator"), + SecAccInitState = #{}, + SecAcc1 = ?SEV_START("sec-acceptor-1", SecAcceptorSeq, SecAccInitState), + + i("start 'secondary acceptor 2' evaluator"), + SecAcc2 = ?SEV_START("sec-acceptor-2", SecAcceptorSeq, SecAccInitState), + + i("start 'secondary acceptor 3' evaluator"), + SecAcc3 = ?SEV_START("sec-acceptor-3", SecAcceptorSeq, SecAccInitState), i("start 'tester' evaluator"), - TesterInitState = #{server => Server#ev.pid, - client1 => Client1#ev.pid, - client2 => Client2#ev.pid, - client3 => Client3#ev.pid}, + TesterInitState = #{prim_acc => PrimAcc#ev.pid, + sec_acc1 => SecAcc1#ev.pid, + sec_acc2 => SecAcc2#ev.pid, + sec_acc3 => SecAcc3#ev.pid}, Tester = ?SEV_START("tester", TesterSeq, TesterInitState), i("await evaluator"), - ok = ?SEV_AWAIT_FINISH([Server, - Client1, Client2, Client3, - Tester]). + ok = ?SEV_AWAIT_FINISH([PrimAcc, SecAcc1, SecAcc2, SecAcc3, Tester]). -start_node(Host, NodeName) -> - UniqueNodeName = f("~w_~w", [NodeName, erlang:unique_integer([positive])]), - case do_start_node(Host, UniqueNodeName) of - {ok, _} = OK -> - OK; - {error, Reason, _} -> - {error, Reason} - end. -do_start_node(Host, NodeName) when is_list(NodeName) -> - do_start_node(Host, list_to_atom(NodeName)); -do_start_node(Host, NodeName) when is_atom(NodeName) -> - Dir = filename:dirname(code:which(?MODULE)), - Flags = "-pa " ++ Dir, - Opts = [{monitor_master, true}, {erl_flags, Flags}], - ct_slave:start(Host, NodeName, Opts). - +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test what happens when a socket is +%% remotely closed while the process is calling the recv function. +%% Socket is IPv4. +%% +%% To minimize the chance of "weirdness", we should really have test cases +%% where the two sides of the connection is on different machines. But for +%% now, we will make do with different VMs on the same host. +%% -stop_node(Node) -> - case ct_slave:stop(Node) of - {ok, _} -> - ok; - {error, _} = ERROR -> - ERROR - end. - -local_host() -> - try net_adm:localhost() of - Host when is_list(Host) -> - list_to_atom(Host) - catch - C:E:S -> - erlang:raise(C, E, S) - end. - -sc_rc_tcp_client_start(Node) -> - Self = self(), - GL = group_leader(), - Fun = fun() -> sc_rc_tcp_client(Self, GL) end, - erlang:spawn(Node, Fun). - - -sc_rc_tcp_client(Parent, GL) -> - sc_rc_tcp_client_init(Parent, GL), - ServerSA = sc_rc_tcp_client_await_start(Parent), - Domain = maps:get(family, ServerSA), - Sock = sc_rc_tcp_client_create(Domain), - sc_rc_tcp_client_bind(Sock, Domain), - sc_rc_tcp_client_announce_ready(Parent, init), - sc_rc_tcp_client_await_continue(Parent, connect), - sc_rc_tcp_client_connect(Sock, ServerSA), - sc_rc_tcp_client_announce_ready(Parent, connect), - sc_rc_tcp_client_await_continue(Parent, close), - sc_rc_tcp_client_close(Sock), - sc_rc_tcp_client_announce_ready(Parent, close), - Reason = sc_rc_tcp_client_await_terminate(Parent), - exit(Reason). - -sc_rc_tcp_client_init(Parent, GL) -> - i("sc_rc_tcp_client_init -> entry"), - _MRef = erlang:monitor(process, Parent), - group_leader(self(), GL), - ok. - -sc_rc_tcp_client_await_start(Parent) -> - i("sc_rc_tcp_client_await_start -> entry"), - ?SEV_AWAIT_START(Parent). - -sc_rc_tcp_client_create(Domain) -> - i("sc_rc_tcp_client_create -> entry"), - case socket:open(Domain, stream, tcp) of - {ok, Sock} -> - Sock; - {error, Reason} -> - exit({open_failed, Reason}) - end. - -sc_rc_tcp_client_bind(Sock, Domain) -> - i("sc_rc_tcp_client_bind -> entry"), - LAddr = which_local_addr(Domain), - LSA = #{family => Domain, - addr => LAddr}, - case socket:bind(Sock, LSA) of - {ok, _} -> - ok; - {error, Reason} -> - exit({bind, Reason}) - end. - -sc_rc_tcp_client_announce_ready(Parent, Slogan) -> - ?SEV_ANNOUNCE_READY(Parent, Slogan). - -sc_rc_tcp_client_await_continue(Parent, Slogan) -> - i("sc_rc_tcp_client_await_continue -> entry"), - ?SEV_AWAIT_CONTINUE(Parent, parent, Slogan). - -sc_rc_tcp_client_connect(Sock, ServerSA) -> - i("sc_rc_tcp_client_connect -> entry"), - case socket:connect(Sock, ServerSA) of - ok -> - ok; - {error, Reason} -> - exit({connect, Reason}) - end. - -sc_rc_tcp_client_close(Sock) -> - i("sc_rc_tcp_client_close -> entry"), - case socket:close(Sock) of - ok -> - ok; - {error, Reason} -> - exit({close, Reason}) - end. - -sc_rc_tcp_client_await_terminate(Parent) -> - i("sc_rc_tcp_client_await_terminate -> entry"), - case ?SEV_AWAIT_TERMINATE(Parent, parent) of - ok -> - ok; - {error, Reason} -> - Reason - end. - - -%% The handlers run on the same node as the server (the local node). - -sc_rc_tcp_handler_start(ID, Recv, Sock) -> - Self = self(), - Fun = fun() -> sc_rc_tcp_handler(ID, Self, Recv, Sock) end, - {Pid, _} = erlang:spawn_monitor(Fun), - Pid. - -sc_rc_tcp_handler(ID, Parent, Recv, Sock) -> - sc_rc_tcp_handler_init(ID, Parent), - sc_rc_tcp_handler_await(Parent, recv), - RecvRes = sc_rc_tcp_handler_recv(Recv, Sock), - sc_rc_tcp_handler_announce_ready(Parent, recv, RecvRes), - Reason = sc_rc_tcp_handler_await(Parent, terminate), - exit(Reason). - -sc_rc_tcp_handler_init(ID, Parent) -> - put(sname, f("handler-~w", [ID])), - _MRef = erlang:monitor(process, Parent), - ?SEV_IPRINT("started"), - ?SEV_ANNOUNCE_READY(Parent, init), - ok. - -sc_rc_tcp_handler_await(Parent, terminate) -> - ?SEV_IPRINT("await terminate"), - ?SEV_AWAIT_TERMINATE(Parent, tester); -sc_rc_tcp_handler_await(Parent, Slogan) -> - ?SEV_IPRINT("await ~w", [Slogan]), - ?SEV_AWAIT_CONTINUE(Parent, parent, Slogan). - -sc_rc_tcp_handler_recv(Recv, Sock) -> - ?SEV_IPRINT("recv"), - try Recv(Sock) of - {error, closed} -> - ok; - {ok, _} -> - ?SEV_IPRINT("unexpected success"), - {error, unexpected_success}; - {error, Reason} = ERROR -> - ?SEV_IPRINT("receive error: " - "~n ~p", [Reason]), - ERROR - catch - C:E:S -> - ?SEV_IPRINT("receive failure: " - "~n Class: ~p" - "~n Error: ~p" - "~n Stack: ~p", [C, E, S]), - {error, {recv, C, E, S}} - end. - -sc_rc_tcp_handler_announce_ready(Parent, Slogan, Result) -> - ?SEV_IPRINT("announce ready"), - ?SEV_ANNOUNCE_READY(Parent, Slogan, Result), - ok. - - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% This test case is intended to test what happens when a socket is -%% remotely closed while the process is calling the recvmsg function. -%% Socket is IPv4. - -sc_rc_recvmsg_response_tcp4(suite) -> +sc_rc_recv_response_tcp4(suite) -> []; -sc_rc_recvmsg_response_tcp4(doc) -> +sc_rc_recv_response_tcp4(doc) -> []; -sc_rc_recvmsg_response_tcp4(_Config) when is_list(_Config) -> - tc_try(sc_rc_recvmsg_response_tcp4, +sc_rc_recv_response_tcp4(_Config) when is_list(_Config) -> + tc_try(sc_rc_recv_response_tcp4, fun() -> ?TT(?SECS(30)), - Recv = fun(Sock) -> socket:recvmsg(Sock) end, + Recv = fun(Sock) -> socket:recv(Sock) end, InitState = #{domain => inet, type => stream, protocol => tcp, @@ -5253,19 +4767,19 @@ sc_rc_recvmsg_response_tcp4(_Config) when is_list(_Config) -> %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% This test case is intended to test what happens when a socket is -%% remotely closed while the process is calling the recvmsg function. +%% remotely closed while the process is calling the recv function. %% Socket is IPv6. -sc_rc_recvmsg_response_tcp6(suite) -> +sc_rc_recv_response_tcp6(suite) -> []; -sc_rc_recvmsg_response_tcp6(doc) -> +sc_rc_recv_response_tcp6(doc) -> []; -sc_rc_recvmsg_response_tcp6(_Config) when is_list(_Config) -> - tc_try(sc_rc_recvmsg_response_tcp6, +sc_rc_recv_response_tcp6(_Config) when is_list(_Config) -> + tc_try(sc_rc_recv_response_tcp6, fun() -> not_yet_implemented(), ?TT(?SECS(10)), - Recv = fun(Sock) -> socket:recvmsg(Sock) end, + Recv = fun(Sock) -> socket:recv(Sock) end, InitState = #{domain => inet6, type => stream, protocol => tcp, @@ -5274,401 +4788,1747 @@ sc_rc_recvmsg_response_tcp6(_Config) when is_list(_Config) -> end). -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% This test case is intended to test what happens when a socket is -%% locally closed while the process is calling the accept function. -%% We test what happens with a non-controlling_process also, since we -%% git the setup anyway. -%% Socket is IPv4. - -sc_lc_acceptor_response_tcp4(suite) -> - []; -sc_lc_acceptor_response_tcp4(doc) -> - []; -sc_lc_acceptor_response_tcp4(_Config) when is_list(_Config) -> - tc_try(sc_lc_acceptor_response_tcp4, - fun() -> - ?TT(?SECS(10)), - InitState = #{domain => inet, - type => stream, - protocol => tcp}, - ok = sc_lc_acceptor_response_tcp(InitState) - end). - - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% This test case is intended to test what happens when a socket is -%% locally closed while the process is calling the accept function. -%% We test what happens with a non-controlling_process also, since we -%% git the setup anyway. -%% Socket is IPv6. - -sc_lc_acceptor_response_tcp6(suite) -> - []; -sc_lc_acceptor_response_tcp6(doc) -> - []; -sc_lc_acceptor_response_tcp6(_Config) when is_list(_Config) -> - tc_try(sc_lc_acceptor_response_tcp6, - fun() -> - not_yet_implemented(), - ?TT(?SECS(10)), - InitState = #{domain => inet, - type => stream, - protocol => tcp}, - ok = sc_lc_acceptor_response_tcp(InitState) - end). - - %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -sc_lc_acceptor_response_tcp(InitState) -> - PrimAcceptorSeq = +sc_rc_receive_response_tcp(InitState) -> + %% Each connection are handled by handler processes. + %% These are created (on the fly) and handled internally + %% by the server! + ServerSeq = [ %% *** Wait for start order part *** - #{desc => "await start (from tester)", + #{desc => "await start", cmd => fun(State) -> Tester = ?SEV_AWAIT_START(), {ok, State#{tester => Tester}} end}, - #{desc => "monitor tester", - cmd => fun(#{tester := Tester} = _State) -> - _MRef = erlang:monitor(process, Tester), + #{desc => "monitor tester", + cmd => fun(#{tester := Tester} = _State) -> + _MRef = erlang:monitor(process, Tester), + ok + end}, + + %% *** Init part *** + #{desc => "which local address", + cmd => fun(#{domain := Domain} = State) -> + LAddr = which_local_addr(Domain), + LSA = #{family => Domain, addr => LAddr}, + {ok, State#{local_sa => LSA}} + end}, + #{desc => "create listen socket", + cmd => fun(#{domain := Domain} = State) -> + case socket:open(Domain, stream, tcp) of + {ok, Sock} -> + {ok, State#{lsock => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "bind to local address", + cmd => fun(#{lsock := LSock, local_sa := LSA} = State) -> + case socket:bind(LSock, LSA) of + {ok, Port} -> + {ok, State#{lport => Port}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "make listen socket", + cmd => fun(#{lsock := LSock}) -> + socket:listen(LSock) + end}, + #{desc => "announce ready (init)", + cmd => fun(#{tester := Tester, local_sa := LSA, lport := Port}) -> + ServerSA = LSA#{port => Port}, + ?SEV_ANNOUNCE_READY(Tester, init, ServerSA), + ok + end}, + + %% The actual test + #{desc => "await continue (accept all three connections)", + cmd => fun(#{tester := Tester} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Tester, tester, accept) + end}, + #{desc => "accept 1", + cmd => fun(#{lsock := LSock, recv := Recv} = State) -> + case socket:accept(LSock) of + {ok, Sock} -> + ?SEV_IPRINT("accepted: try start handler"), + Handler = sc_rc_tcp_handler_start(1, Recv, Sock), + ?SEV_IPRINT("handler started"), + {ok, State#{csock1 => Sock, + handler1 => Handler}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "await handle 1 ready (init)", + cmd => fun(#{tester := Tester, + handler1 := Handler1} = _State) -> + ?SEV_AWAIT_READY(Handler1, handler1, init, + [{tester, Tester}]) + end}, + #{desc => "accept 2", + cmd => fun(#{lsock := LSock, recv := Recv} = State) -> + case socket:accept(LSock) of + {ok, Sock} -> + ?SEV_IPRINT("accepted: try start handler"), + Handler = sc_rc_tcp_handler_start(2, Recv, Sock), + ?SEV_IPRINT("handler started"), + {ok, State#{csock2 => Sock, + handler2 => Handler}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "await handle 2 ready (init)", + cmd => fun(#{tester := Tester, + handler1 := Handler1, + handler2 := Handler2} = _State) -> + ?SEV_AWAIT_READY(Handler2, handler2, init, + [{tester, Tester}, + {handler1, Handler1}]) + end}, + #{desc => "accept 3", + cmd => fun(#{lsock := LSock, recv := Recv} = State) -> + case socket:accept(LSock) of + {ok, Sock} -> + ?SEV_IPRINT("accepted: try start handler"), + Handler = sc_rc_tcp_handler_start(3, Recv, Sock), + ?SEV_IPRINT("handler started"), + {ok, State#{csock3 => Sock, + handler3 => Handler}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "await handle 3 ready (init)", + cmd => fun(#{tester := Tester, + handler1 := Handler1, + handler2 := Handler2, + handler3 := Handler3} = _State) -> + ?SEV_AWAIT_READY(Handler3, handler3, init, + [{tester, Tester}, + {handler1, Handler1}, + {handler2, Handler2}]) + end}, + #{desc => "announce ready (accept all three connections)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, accept), + ok + end}, + #{desc => "await continue (recv)", + cmd => fun(#{tester := Tester} = _State) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, recv) + end}, + #{desc => "order handler 1 to receive", + cmd => fun(#{handler1 := Pid} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Pid, recv), + ok + end}, + #{desc => "order handler 2 to receive", + cmd => fun(#{handler2 := Pid} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Pid, recv), + ok + end}, + #{desc => "order handler 3 to receive", + cmd => fun(#{handler3 := Pid} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Pid, recv), + ok + end}, + #{desc => "await ready from handler 1 (recv)", + cmd => fun(#{tester := Tester, handler1 := Pid} = _State) -> + case ?SEV_AWAIT_READY(Pid, handler1, recv, + [{tester, Tester}]) of + {ok, Result} -> + Result; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "await ready from handler 2 (recv)", + cmd => fun(#{tester := Tester, handler2 := Pid} = _State) -> + case ?SEV_AWAIT_READY(Pid, handler2, recv, + [{tester, Tester}]) of + {ok, Result} -> + Result; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "await ready from handler 3 (recv)", + cmd => fun(#{tester := Tester, handler3 := Pid} = _State) -> + case ?SEV_AWAIT_READY(Pid, handler3, recv, + [{tester, Tester}]) of + {ok, Result} -> + Result; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (recv closed from all handlers)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, recv_closed), + ok + end}, + + %% Termination + #{desc => "await terminate (from tester)", + cmd => fun(#{tester := Tester} = State) -> + case ?SEV_AWAIT_TERMINATE(Tester, tester) of + ok -> + {ok, maps:remove(tester, State)}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "order handler 1 to terminate", + cmd => fun(#{handler1 := Pid} = _State) -> + %% Pid ! {terminate, self(), ok}, + ?SEV_ANNOUNCE_TERMINATE(Pid), + ok + end}, + #{desc => "await handler 1 termination", + cmd => fun(#{handler1 := Pid} = State) -> + ?SEV_AWAIT_TERMINATION(Pid), + State1 = maps:remove(csock1, State), + State2 = maps:remove(handler1, State1), + {ok, State2} + end}, + #{desc => "order handler 2 to terminate", + cmd => fun(#{handler2 := Pid} = _State) -> + ?SEV_ANNOUNCE_TERMINATE(Pid), + ok + end}, + #{desc => "await handler 2 termination", + cmd => fun(#{handler2 := Pid} = State) -> + ?SEV_AWAIT_TERMINATION(Pid), + State1 = maps:remove(csock2, State), + State2 = maps:remove(handler2, State1), + {ok, State2} + end}, + #{desc => "order handler 3 to terminate", + cmd => fun(#{handler3 := Pid} = _State) -> + ?SEV_ANNOUNCE_TERMINATE(Pid), + ok + end}, + #{desc => "await handler 3 termination", + cmd => fun(#{handler3 := Pid} = State) -> + ?SEV_AWAIT_TERMINATION(Pid), + State1 = maps:remove(csock3, State), + State2 = maps:remove(handler3, State1), + {ok, State2} + end}, + #{desc => "close listen socket", + cmd => fun(#{lsock := LSock} = State) -> + case socket:close(LSock) of + ok -> + {ok, maps:remove(lsock, State)}; + {error, _} = ERROR -> + ERROR + end + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + ClientSeq = + [ + %% *** Wait for start order part *** + #{desc => "await start", + cmd => fun(State) -> + {Tester, {NodeID, ServerSA}} = ?SEV_AWAIT_START(), + {ok, State#{tester => Tester, + node_id => NodeID, + server_sa => ServerSA}} + end}, + #{desc => "monitor tester", + cmd => fun(#{tester := Tester} = _State) -> + _MRef = erlang:monitor(process, Tester), + ok + end}, + + %% *** Init part *** + #{desc => "create node", + cmd => fun(#{host := Host, node_id := NodeID} = State) -> + case start_node(Host, l2a(f("client_~w", [NodeID]))) of + {ok, Node} -> + ?SEV_IPRINT("client node ~p started", [Node]), + {ok, State#{node => Node}}; + {error, Reason, _} -> + {error, Reason} + end + end}, + #{desc => "monitor client node 1", + cmd => fun(#{node := Node} = _State) -> + true = erlang:monitor_node(Node, true), + ok + end}, + #{desc => "start remote client on client node", + cmd => fun(#{node := Node} = State) -> + Pid = sc_rc_tcp_client_start(Node), + ?SEV_IPRINT("client ~p started", [Pid]), + {ok, State#{rclient => Pid}} + end}, + #{desc => "monitor remote client", + cmd => fun(#{rclient := Pid}) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + #{desc => "order remote client to start", + cmd => fun(#{rclient := Client, server_sa := ServerSA}) -> + ?SEV_ANNOUNCE_START(Client, ServerSA), + ok + end}, + #{desc => "await remote client ready", + cmd => fun(#{tester := Tester, + rclient := Client} = _State) -> + ?SEV_AWAIT_READY(Client, rclient, init, + [{tester, Tester}]) + end}, + #{desc => "announce ready (init)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, init), + ok + end}, + + %% The actual test + #{desc => "await continue (connect)", + cmd => fun(#{tester := Tester, + rclient := Client} = _State) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, connect, + [{rclient, Client}]), + ok + end}, + #{desc => "order remote client to continue (connect)", + cmd => fun(#{rclient := Client}) -> + ?SEV_ANNOUNCE_CONTINUE(Client, connect), + ok + end}, + #{desc => "await client process ready (connect)", + cmd => fun(#{tester := Tester, + rclient := Client} = _State) -> + ?SEV_AWAIT_READY(Client, rclient, connect, + [{tester, Tester}]) + end}, + #{desc => "announce ready (connected)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, connect), + ok + end}, + #{desc => "await continue (close)", + cmd => fun(#{tester := Tester, + rclient := Client} = _State) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, close, + [{rclient, Client}]), + ok + end}, + #{desc => "order remote client to close", + cmd => fun(#{rclient := Client}) -> + ?SEV_ANNOUNCE_CONTINUE(Client, close), + ok + end}, + #{desc => "await remote client ready (closed)", + cmd => fun(#{tester := Tester, + rclient := Client} = _State) -> + ?SEV_AWAIT_READY(Client, rclient, close, + [{tester, Tester}]) + end}, + #{desc => "announce ready (close)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, close), + ok + end}, + + %% Termination + #{desc => "await terminate (from tester)", + cmd => fun(#{tester := Tester, + rclient := Client} = State) -> + case ?SEV_AWAIT_TERMINATE(Tester, tester, + [{rclient, Client}]) of + ok -> + {ok, maps:remove(tester, State)}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "kill remote client", + cmd => fun(#{rclient := Client}) -> + ?SEV_ANNOUNCE_TERMINATE(Client), + ok + end}, + #{desc => "await remote client termination", + cmd => fun(#{rclient := Client} = State) -> + ?SEV_AWAIT_TERMINATION(Client), + State1 = maps:remove(rclient, State), + {ok, State1} + end}, + #{desc => "stop client node", + cmd => fun(#{node := Node} = _State) -> + stop_node(Node) + end}, + #{desc => "await client node termination", + cmd => fun(#{node := Node} = State) -> + receive + {nodedown, Node} -> + State1 = maps:remove(node_id, State), + State2 = maps:remove(node, State1), + {ok, State2} + end + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + TesterSeq = + [ + %% *** Init part *** + #{desc => "monitor server", + cmd => fun(#{server := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + #{desc => "monitor client 1", + cmd => fun(#{client1 := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + #{desc => "monitor client 2", + cmd => fun(#{client2 := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + #{desc => "monitor client 3", + cmd => fun(#{client3 := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + + %% Start the server + #{desc => "order server start", + cmd => fun(#{server := Pid} = _State) -> + ?SEV_ANNOUNCE_START(Pid), + ok + end}, + #{desc => "await server ready (init)", + cmd => fun(#{server := Pid} = State) -> + {ok, ServerSA} = ?SEV_AWAIT_READY(Pid, server, init), + {ok, State#{server_sa => ServerSA}} + end}, + + %% Start the client(s) + #{desc => "order client 1 start", + cmd => fun(#{client1 := Pid, + server_sa := ServerSA} = _State) -> + ?SEV_ANNOUNCE_START(Pid, {1, ServerSA}), + ok + end}, + #{desc => "await client 1 ready (init)", + cmd => fun(#{client1 := Pid} = _State) -> + ok = ?SEV_AWAIT_READY(Pid, client1, init) + end}, + #{desc => "order client 2 start", + cmd => fun(#{client2 := Pid, + server_sa := ServerSA} = _State) -> + ?SEV_ANNOUNCE_START(Pid, {2, ServerSA}), + ok + end}, + #{desc => "await client 2 ready (init)", + cmd => fun(#{client2 := Pid} = _State) -> + ok = ?SEV_AWAIT_READY(Pid, client2, init) + end}, + #{desc => "order client 3 start", + cmd => fun(#{client3 := Pid, + server_sa := ServerSA} = _State) -> + ?SEV_ANNOUNCE_START(Pid, {3, ServerSA}), + ok + end}, + #{desc => "await client 3 ready (init)", + cmd => fun(#{client3 := Pid} = _State) -> + ok = ?SEV_AWAIT_READY(Pid, client3, init) + end}, + + %% The actual test + #{desc => "order server continue (accept)", + cmd => fun(#{server := Pid} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Pid, accept), + ok + end}, + ?SEV_SLEEP(?SECS(1)), + #{desc => "order client 1 continue (connect)", + cmd => fun(#{client1 := Pid} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Pid, connect), + ok + end}, + #{desc => "await client 1 ready (connect)", + cmd => fun(#{server := Server, + client1 := Client1, + client2 := Client2, + client3 := Client3} = _State) -> + ?SEV_AWAIT_READY(Client1, client1, connect, + [{server, Server}, + {client2, Client2}, + {client3, Client3}]), + ok + end}, + #{desc => "order client 2 continue (connect)", + cmd => fun(#{client2 := Pid} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Pid, connect), + ok + end}, + #{desc => "await client 2 ready (connect)", + cmd => fun(#{server := Server, + client1 := Client1, + client2 := Client2, + client3 := Client3} = _State) -> + ?SEV_AWAIT_READY(Client2, client2, connect, + [{server, Server}, + {client1, Client1}, + {client3, Client3}]), + ok + end}, + #{desc => "order client 3 continue (connect)", + cmd => fun(#{client3 := Pid} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Pid, connect), + ok + end}, + #{desc => "await client 3 ready (connect)", + cmd => fun(#{server := Server, + client1 := Client1, + client2 := Client2, + client3 := Client3} = _State) -> + ?SEV_AWAIT_READY(Client3, client3, connect, + [{server, Server}, + {client1, Client1}, + {client2, Client2}]), + ok + end}, + #{desc => "await server ready (accept from all connections)", + cmd => fun(#{server := Server, + client1 := Client1, + client2 := Client2, + client3 := Client3} = _State) -> + ?SEV_AWAIT_READY(Server, server, accept, + [{client1, Client1}, + {client2, Client2}, + {client3, Client3}]), + ok + end}, + #{desc => "order server continue (recv for all connections)", + cmd => fun(#{server := Pid} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Pid, recv), + ok + end}, + ?SEV_SLEEP(?SECS(1)), + #{desc => "order client 1 continue (close)", + cmd => fun(#{client1 := Pid} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Pid, close), + ok + end}, + #{desc => "await client 1 ready (close)", + cmd => fun(#{server := Server, + client1 := Client1, + client2 := Client2, + client3 := Client3} = _State) -> + ?SEV_AWAIT_READY(Client1, client1, close, + [{server, Server}, + {client2, Client2}, + {client3, Client3}]), + ok + end}, + #{desc => "order client 2 continue (close)", + cmd => fun(#{client2 := Pid} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Pid, close), + ok + end}, + #{desc => "await client 2 ready (close)", + cmd => fun(#{server := Server, + client1 := Client1, + client2 := Client2, + client3 := Client3} = _State) -> + ?SEV_AWAIT_READY(Client2, client2, close, + [{server, Server}, + {client1, Client1}, + {client3, Client3}]), + ok + end}, + #{desc => "order client 3 continue (close)", + cmd => fun(#{client3 := Pid} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Pid, close), + ok + end}, + #{desc => "await client 3 ready (close)", + cmd => fun(#{server := Server, + client1 := Client1, + client2 := Client2, + client3 := Client3} = _State) -> + ?SEV_AWAIT_READY(Client3, client1, close, + [{server, Server}, + {client1, Client1}, + {client2, Client2}]), + ok + end}, + #{desc => "await server ready (close for all connections)", + cmd => fun(#{server := Server, + client1 := Client1, + client2 := Client2, + client3 := Client3} = _State) -> + ?SEV_AWAIT_READY(Server, server, recv_closed, + [{client1, Client1}, + {client2, Client2}, + {client3, Client3}]), + ok + end}, + + %% Terminations + #{desc => "order client 1 to terminate", + cmd => fun(#{client1 := Pid} = _State) -> + ?SEV_ANNOUNCE_TERMINATE(Pid), + ok + end}, + #{desc => "await client 1 termination", + cmd => fun(#{client1 := Pid} = State) -> + case ?SEV_AWAIT_TERMINATION(Pid) of + ok -> + State1 = maps:remove(client1, State), + {ok, State1}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "order client 2 to terminate", + cmd => fun(#{client2 := Pid} = _State) -> + ?SEV_ANNOUNCE_TERMINATE(Pid), + ok + end}, + #{desc => "await client 2 termination", + cmd => fun(#{client2 := Pid} = State) -> + case ?SEV_AWAIT_TERMINATION(Pid) of + ok -> + State1 = maps:remove(client2, State), + {ok, State1}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "order client 3 to terminate", + cmd => fun(#{client3 := Pid} = _State) -> + ?SEV_ANNOUNCE_TERMINATE(Pid), + ok + end}, + #{desc => "await client 3 termination", + cmd => fun(#{client3 := Pid} = State) -> + case ?SEV_AWAIT_TERMINATION(Pid) of + ok -> + State1 = maps:remove(client3, State), + {ok, State1}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "order server to terminate", + cmd => fun(#{server := Pid} = _State) -> + ?SEV_ANNOUNCE_TERMINATE(Pid), + ok + end}, + #{desc => "await server termination", + cmd => fun(#{server := Pid} = State) -> + case ?SEV_AWAIT_TERMINATION(Pid) of + ok -> + State1 = maps:remove(server, State), + {ok, State1}; + {error, _} = ERROR -> + ERROR + end + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + i("start server evaluator"), + ServerInitState = InitState, + Server = ?SEV_START("server", ServerSeq, ServerInitState), + + i("start client evaluator(s)"), + ClientInitState = InitState#{host => local_host()}, + Client1 = ?SEV_START("client-1", ClientSeq, ClientInitState), + Client2 = ?SEV_START("client-2", ClientSeq, ClientInitState), + Client3 = ?SEV_START("client-3", ClientSeq, ClientInitState), + + i("start 'tester' evaluator"), + TesterInitState = #{server => Server#ev.pid, + client1 => Client1#ev.pid, + client2 => Client2#ev.pid, + client3 => Client3#ev.pid}, + Tester = ?SEV_START("tester", TesterSeq, TesterInitState), + + i("await evaluator"), + ok = ?SEV_AWAIT_FINISH([Server, + Client1, Client2, Client3, + Tester]). + + +local_host() -> + try net_adm:localhost() of + Host when is_list(Host) -> + list_to_atom(Host) + catch + C:E:S -> + erlang:raise(C, E, S) + end. + +sc_rc_tcp_client_start(Node) -> + Self = self(), + GL = group_leader(), + Fun = fun() -> sc_rc_tcp_client(Self, GL) end, + erlang:spawn(Node, Fun). + + +sc_rc_tcp_client(Parent, GL) -> + sc_rc_tcp_client_init(Parent, GL), + ServerSA = sc_rc_tcp_client_await_start(Parent), + Domain = maps:get(family, ServerSA), + Sock = sc_rc_tcp_client_create(Domain), + sc_rc_tcp_client_bind(Sock, Domain), + sc_rc_tcp_client_announce_ready(Parent, init), + sc_rc_tcp_client_await_continue(Parent, connect), + sc_rc_tcp_client_connect(Sock, ServerSA), + sc_rc_tcp_client_announce_ready(Parent, connect), + sc_rc_tcp_client_await_continue(Parent, close), + sc_rc_tcp_client_close(Sock), + sc_rc_tcp_client_announce_ready(Parent, close), + Reason = sc_rc_tcp_client_await_terminate(Parent), + exit(Reason). + +sc_rc_tcp_client_init(Parent, GL) -> + i("sc_rc_tcp_client_init -> entry"), + _MRef = erlang:monitor(process, Parent), + group_leader(self(), GL), + ok. + +sc_rc_tcp_client_await_start(Parent) -> + i("sc_rc_tcp_client_await_start -> entry"), + ?SEV_AWAIT_START(Parent). + +sc_rc_tcp_client_create(Domain) -> + i("sc_rc_tcp_client_create -> entry"), + case socket:open(Domain, stream, tcp) of + {ok, Sock} -> + Sock; + {error, Reason} -> + exit({open_failed, Reason}) + end. + +sc_rc_tcp_client_bind(Sock, Domain) -> + i("sc_rc_tcp_client_bind -> entry"), + LAddr = which_local_addr(Domain), + LSA = #{family => Domain, + addr => LAddr}, + case socket:bind(Sock, LSA) of + {ok, _} -> + ok; + {error, Reason} -> + exit({bind, Reason}) + end. + +sc_rc_tcp_client_announce_ready(Parent, Slogan) -> + ?SEV_ANNOUNCE_READY(Parent, Slogan). + +sc_rc_tcp_client_await_continue(Parent, Slogan) -> + i("sc_rc_tcp_client_await_continue -> entry"), + ?SEV_AWAIT_CONTINUE(Parent, parent, Slogan). + +sc_rc_tcp_client_connect(Sock, ServerSA) -> + i("sc_rc_tcp_client_connect -> entry"), + case socket:connect(Sock, ServerSA) of + ok -> + ok; + {error, Reason} -> + exit({connect, Reason}) + end. + +sc_rc_tcp_client_close(Sock) -> + i("sc_rc_tcp_client_close -> entry"), + case socket:close(Sock) of + ok -> + ok; + {error, Reason} -> + exit({close, Reason}) + end. + +sc_rc_tcp_client_await_terminate(Parent) -> + i("sc_rc_tcp_client_await_terminate -> entry"), + case ?SEV_AWAIT_TERMINATE(Parent, parent) of + ok -> + ok; + {error, Reason} -> + Reason + end. + + +%% The handlers run on the same node as the server (the local node). + +sc_rc_tcp_handler_start(ID, Recv, Sock) -> + Self = self(), + Fun = fun() -> sc_rc_tcp_handler(ID, Self, Recv, Sock) end, + {Pid, _} = erlang:spawn_monitor(Fun), + Pid. + +sc_rc_tcp_handler(ID, Parent, Recv, Sock) -> + sc_rc_tcp_handler_init(ID, Parent), + sc_rc_tcp_handler_await(Parent, recv), + RecvRes = sc_rc_tcp_handler_recv(Recv, Sock), + sc_rc_tcp_handler_announce_ready(Parent, recv, RecvRes), + Reason = sc_rc_tcp_handler_await(Parent, terminate), + exit(Reason). + +sc_rc_tcp_handler_init(ID, Parent) -> + put(sname, f("handler-~w", [ID])), + _MRef = erlang:monitor(process, Parent), + ?SEV_IPRINT("started"), + ?SEV_ANNOUNCE_READY(Parent, init), + ok. + +sc_rc_tcp_handler_await(Parent, terminate) -> + ?SEV_IPRINT("await terminate"), + ?SEV_AWAIT_TERMINATE(Parent, tester); +sc_rc_tcp_handler_await(Parent, Slogan) -> + ?SEV_IPRINT("await ~w", [Slogan]), + ?SEV_AWAIT_CONTINUE(Parent, parent, Slogan). + +sc_rc_tcp_handler_recv(Recv, Sock) -> + ?SEV_IPRINT("recv"), + try Recv(Sock) of + {error, closed} -> + ok; + {ok, _} -> + ?SEV_IPRINT("unexpected success"), + {error, unexpected_success}; + {error, Reason} = ERROR -> + ?SEV_IPRINT("receive error: " + "~n ~p", [Reason]), + ERROR + catch + C:E:S -> + ?SEV_IPRINT("receive failure: " + "~n Class: ~p" + "~n Error: ~p" + "~n Stack: ~p", [C, E, S]), + {error, {recv, C, E, S}} + end. + +sc_rc_tcp_handler_announce_ready(Parent, Slogan, Result) -> + ?SEV_IPRINT("announce ready"), + ?SEV_ANNOUNCE_READY(Parent, Slogan, Result), + ok. + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test what happens when a socket is +%% remotely closed while the process is calling the recvmsg function. +%% Socket is IPv4. + +sc_rc_recvmsg_response_tcp4(suite) -> + []; +sc_rc_recvmsg_response_tcp4(doc) -> + []; +sc_rc_recvmsg_response_tcp4(_Config) when is_list(_Config) -> + tc_try(sc_rc_recvmsg_response_tcp4, + fun() -> + ?TT(?SECS(30)), + Recv = fun(Sock) -> socket:recvmsg(Sock) end, + InitState = #{domain => inet, + type => stream, + protocol => tcp, + recv => Recv}, + ok = sc_rc_receive_response_tcp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test what happens when a socket is +%% remotely closed while the process is calling the recvmsg function. +%% Socket is IPv6. + +sc_rc_recvmsg_response_tcp6(suite) -> + []; +sc_rc_recvmsg_response_tcp6(doc) -> + []; +sc_rc_recvmsg_response_tcp6(_Config) when is_list(_Config) -> + tc_try(sc_rc_recvmsg_response_tcp6, + fun() -> + not_yet_implemented(), + ?TT(?SECS(10)), + Recv = fun(Sock) -> socket:recvmsg(Sock) end, + InitState = #{domain => inet6, + type => stream, + protocol => tcp, + recv => Recv}, + ok = sc_rc_receive_response_tcp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test that the send and recv functions +%% behave as expected when sending and/or reading chunks. +%% First send data in one "big" chunk, and read it in "small" chunks. +%% Second, send in a bunch of "small" chunks, and read in one "big" chunk. +%% Socket is IPv4. + +traffic_send_and_recv_chunks_tcp4(suite) -> + []; +traffic_send_and_recv_chunks_tcp4(doc) -> + []; +traffic_send_and_recv_chunks_tcp4(_Config) when is_list(_Config) -> + tc_try(traffic_send_and_recv_chunks_tcp4, + fun() -> + ?TT(?SECS(30)), + InitState = #{domain => inet}, + ok = traffic_send_and_recv_chunks_tcp(InitState) + end). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test that the send and recv functions +%% behave as expected when sending and/or reading chunks. +%% First send data in one "big" chunk, and read it in "small" chunks. +%% Second, send in a bunch of "small" chunks, and read in one "big" chunk. +%% Socket is IPv6. + +traffic_send_and_recv_chunks_tcp6(suite) -> + []; +traffic_send_and_recv_chunks_tcp6(doc) -> + []; +traffic_send_and_recv_chunks_tcp6(_Config) when is_list(_Config) -> + tc_try(traffic_send_and_recv_chunks_tcp6, + fun() -> + not_yet_implemented(), + ?TT(?SECS(30)), + InitState = #{domain => inet6}, + ok = traffic_send_and_recv_chunks_tcp(InitState) + end). + + +traffic_send_and_recv_chunks_tcp(InitState) -> + ServerSeq = + [ + %% *** Wait for start order part *** + #{desc => "await start", + cmd => fun(State) -> + Tester = ?SEV_AWAIT_START(), + {ok, State#{tester => Tester}} + end}, + #{desc => "monitor tester", + cmd => fun(#{tester := Tester} = _State) -> + _MRef = erlang:monitor(process, Tester), + ok + end}, + + %% *** Init part *** + #{desc => "which local address", + cmd => fun(#{domain := Domain} = State) -> + LAddr = which_local_addr(Domain), + LSA = #{family => Domain, addr => LAddr}, + {ok, State#{local_sa => LSA}} + end}, + #{desc => "create listen socket", + cmd => fun(#{domain := Domain} = State) -> + case socket:open(Domain, stream, tcp) of + {ok, Sock} -> + {ok, State#{lsock => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "bind to local address", + cmd => fun(#{lsock := LSock, local_sa := LSA} = State) -> + case socket:bind(LSock, LSA) of + {ok, Port} -> + {ok, State#{lport => Port}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "make listen socket", + cmd => fun(#{lsock := LSock}) -> + socket:listen(LSock) + end}, + #{desc => "announce ready (init)", + cmd => fun(#{tester := Tester, local_sa := LSA, lport := Port}) -> + ServerSA = LSA#{port => Port}, + ?SEV_ANNOUNCE_READY(Tester, init, ServerSA), + ok + end}, + + %% The actual test + #{desc => "await continue (accept)", + cmd => fun(#{tester := Tester} = _State) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, accept) + end}, + #{desc => "accept", + cmd => fun(#{lsock := LSock} = State) -> + case socket:accept(LSock) of + {ok, Sock} -> + {ok, State#{csock => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (accept)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, accept), + ok + end}, + + #{desc => "await continue (recv-many-small)", + cmd => fun(#{tester := Tester} = _State) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, recv_many_small) + end}, + #{desc => "recv chunk 1", + cmd => fun(#{csock := Sock} = State) -> + case socket:recv(Sock, 100) of + {ok, Chunk} -> + ?SEV_IPRINT("recv of chunk 1 of ~p bytes", + [size(Chunk)]), + {ok, State#{chunks => [b2l(Chunk)]}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "recv chunk 2", + cmd => fun(#{csock := Sock, + chunks := Chunks} = State) -> + case socket:recv(Sock, 100) of + {ok, Chunk} -> + ?SEV_IPRINT("recv of chunk 2 of ~p bytes", + [size(Chunk)]), + {ok, State#{chunks => [b2l(Chunk)|Chunks]}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "recv chunk 3", + cmd => fun(#{csock := Sock, + chunks := Chunks} = State) -> + case socket:recv(Sock, 100) of + {ok, Chunk} -> + ?SEV_IPRINT("recv of chunk 3 of ~p bytes", + [size(Chunk)]), + {ok, State#{chunks => [b2l(Chunk)|Chunks]}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "recv chunk 4", + cmd => fun(#{csock := Sock, + chunks := Chunks} = State) -> + case socket:recv(Sock, 100) of + {ok, Chunk} -> + ?SEV_IPRINT("recv of chunk 4 of ~p bytes", + [size(Chunk)]), + {ok, State#{chunks => [b2l(Chunk)|Chunks]}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "recv chunk 5", + cmd => fun(#{csock := Sock, + chunks := Chunks} = State) -> + case socket:recv(Sock, 100) of + {ok, Chunk} -> + ?SEV_IPRINT("recv of chunk 5 of ~p bytes", + [size(Chunk)]), + {ok, State#{chunks => [b2l(Chunk)|Chunks]}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "recv chunk 6", + cmd => fun(#{csock := Sock, + chunks := Chunks} = State) -> + case socket:recv(Sock, 100) of + {ok, Chunk} -> + ?SEV_IPRINT("recv of chunk 6 of ~p bytes", + [size(Chunk)]), + {ok, State#{chunks => [b2l(Chunk)|Chunks]}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "recv chunk 7", + cmd => fun(#{csock := Sock, + chunks := Chunks} = State) -> + case socket:recv(Sock, 100) of + {ok, Chunk} -> + ?SEV_IPRINT("recv of chunk 7 of ~p bytes", + [size(Chunk)]), + {ok, State#{chunks => [b2l(Chunk)|Chunks]}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "recv chunk 8", + cmd => fun(#{csock := Sock, + chunks := Chunks} = State) -> + case socket:recv(Sock, 100) of + {ok, Chunk} -> + ?SEV_IPRINT("recv of chunk 8 of ~p bytes", + [size(Chunk)]), + {ok, State#{chunks => [b2l(Chunk)|Chunks]}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "recv chunk 9", + cmd => fun(#{csock := Sock, + chunks := Chunks} = State) -> + case socket:recv(Sock, 100) of + {ok, Chunk} -> + ?SEV_IPRINT("recv of chunk 9 of ~p bytes", + [size(Chunk)]), + {ok, State#{chunks => [b2l(Chunk)|Chunks]}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "recv chunk 10", + cmd => fun(#{csock := Sock, + chunks := Chunks} = State) -> + case socket:recv(Sock, 100) of + {ok, Chunk} -> + ?SEV_IPRINT("recv of chunk 10 of ~p bytes", + [size(Chunk)]), + {ok, State#{chunks => [b2l(Chunk)|Chunks]}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (recv-many-small)", + cmd => fun(#{tester := Tester, + chunks := Chunks} = State) -> + Data = lists:flatten(lists:reverse(Chunks)), + ?SEV_ANNOUNCE_READY(Tester, recv_many_small, Data), + {ok, maps:remove(chunks, State)} + end}, + + #{desc => "await continue (recv-one-big)", + cmd => fun(#{tester := Tester} = State) -> + case ?SEV_AWAIT_CONTINUE(Tester, tester, recv_one_big) of + {ok, Size} -> + {ok, State#{size => Size}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "recv (one big)", + cmd => fun(#{tester := Tester, csock := Sock, size := Size} = _State) -> + %% ok = socket:setopt(Sock, otp, debug, true), + case socket:recv(Sock, Size) of + {ok, Data} -> + %% ok = socket:setopt(Sock, otp, debug, false), + ?SEV_ANNOUNCE_READY(Tester, + recv_one_big, + b2l(Data)), + ok; + {error, _} = ERROR -> + ERROR + end + end}, + + #{desc => "await terminate (from tester)", + cmd => fun(#{tester := Tester} = State) -> + case ?SEV_AWAIT_TERMINATE(Tester, tester) of + ok -> + {ok, maps:remove(tester, State)}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "close connection socket (just in case)", + cmd => fun(#{csock := Sock} = State) -> + (catch socket:close(Sock)), + {ok, maps:remove(csock, State)} + end}, + #{desc => "close listen socket", + cmd => fun(#{lsock := Sock} = State) -> + (catch socket:close(Sock)), + {ok, maps:remove(lsock, State)} + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + ClientSeq = + [ + %% *** Wait for start order part *** + #{desc => "await start", + cmd => fun(State) -> + {Tester, ServerSA} = ?SEV_AWAIT_START(), + {ok, State#{tester => Tester, + server_sa => ServerSA}} + end}, + #{desc => "monitor tester", + cmd => fun(#{tester := Tester} = _State) -> + _MRef = erlang:monitor(process, Tester), + ok + end}, + + %% *** Init part *** + #{desc => "create node", + cmd => fun(#{host := Host} = State) -> + case start_node(Host, client) of + {ok, Node} -> + ?SEV_IPRINT("(remote) client node ~p started", + [Node]), + {ok, State#{node => Node}}; + {error, Reason, _} -> + {error, Reason} + end + end}, + #{desc => "monitor client node", + cmd => fun(#{node := Node} = _State) -> + true = erlang:monitor_node(Node, true), + ok + end}, + #{desc => "start remote client", + cmd => fun(#{node := Node} = State) -> + Pid = traffic_snr_tcp_client_start(Node), + ?SEV_IPRINT("client ~p started", [Pid]), + {ok, State#{rclient => Pid}} + end}, + #{desc => "monitor remote client", + cmd => fun(#{rclient := Pid}) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + #{desc => "order remote client to start", + cmd => fun(#{rclient := Client, server_sa := ServerSA}) -> + ?SEV_ANNOUNCE_START(Client, ServerSA), + ok + end}, + #{desc => "await remote client ready", + cmd => fun(#{tester := Tester, + rclient := Client} = _State) -> + ?SEV_AWAIT_READY(Client, rclient, init, + [{tester, Tester}]) + end}, + #{desc => "announce ready (init)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, init), + ok + end}, + + %% The actual test + #{desc => "await continue (connect)", + cmd => fun(#{tester := Tester, + rclient := Client} = _State) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, connect, + [{rclient, Client}]), + ok + end}, + #{desc => "order remote client to continue (connect)", + cmd => fun(#{rclient := Client}) -> + ?SEV_ANNOUNCE_CONTINUE(Client, connect), + ok + end}, + #{desc => "await client process ready (connect)", + cmd => fun(#{tester := Tester, + rclient := Client} = _State) -> + ?SEV_AWAIT_READY(Client, rclient, connect, + [{tester, Tester}]) + end}, + #{desc => "announce ready (connect)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, connect), + ok + end}, + + #{desc => "await continue (send-one-big)", + cmd => fun(#{tester := Tester, + rclient := Client} = State) -> + case ?SEV_AWAIT_CONTINUE(Tester, tester, + send_one_big, + [{rclient, Client}]) of + {ok, Data} -> + {ok, State#{data => Data}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "order remote client to continue (send)", + cmd => fun(#{rclient := Client, data := Data}) -> + ?SEV_ANNOUNCE_CONTINUE(Client, send, Data), + ok + end}, + #{desc => "await client process ready (send)", + cmd => fun(#{tester := Tester, + rclient := Client} = _State) -> + case ?SEV_AWAIT_READY(Client, rclient, send, + [{tester, Tester}]) of + {ok, Result} -> + Result; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (send-one-big)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, send_one_big), ok end}, - %% *** Init part *** - #{desc => "which local address", - cmd => fun(#{domain := Domain} = State) -> - LAddr = which_local_addr(Domain), - LSA = #{family => Domain, addr => LAddr}, - {ok, State#{lsa => LSA}} + #{desc => "await continue (send-many-small)", + cmd => fun(#{tester := Tester, + rclient := Client} = State) -> + case ?SEV_AWAIT_CONTINUE(Tester, tester, + send_many_small, + [{rclient, Client}]) of + {ok, Data} -> + {ok, State#{data => Data}}; + {error, _} = ERROR -> + ERROR + end end}, - #{desc => "create (listen) socket", - cmd => fun(#{domain := Domain, - type := Type, - protocol := Proto} = State) -> - case socket:open(Domain, Type, Proto) of - {ok, Sock} -> - {ok, State#{sock => Sock}}; + #{desc => "order remote client to continue (send chunk 1)", + cmd => fun(#{rclient := Client, + data := Data} = State) -> + {Chunk, RestData} = lists:split(100, Data), + %% ?SEV_IPRINT("order send of chunk 1: " + %% "~n Size: ~p" + %% "~n ~p", [length(Chunk), Chunk]), + ?SEV_ANNOUNCE_CONTINUE(Client, send, Chunk), + {ok, State#{data => RestData}} + end}, + #{desc => "await client process ready (send chunk 1)", + cmd => fun(#{tester := Tester, + rclient := Client} = _State) -> + case ?SEV_AWAIT_READY(Client, rclient, send, + [{tester, Tester}]) of + {ok, Result} -> + Result; {error, _} = ERROR -> ERROR end end}, - #{desc => "bind to local address", - cmd => fun(#{sock := Sock, lsa := LSA} = _State) -> - case socket:bind(Sock, LSA) of - {ok, _Port} -> - ok; + #{desc => "order remote client to continue (send chunk 2)", + cmd => fun(#{rclient := Client, + data := Data} = State) -> + {Chunk, RestData} = lists:split(100, Data), + %% ?SEV_IPRINT("order send of chunk 2: " + %% "~n Size: ~p" + %% "~n ~p", [length(Chunk), Chunk]), + ?SEV_ANNOUNCE_CONTINUE(Client, send, Chunk), + {ok, State#{data => RestData}} + end}, + #{desc => "await client process ready (send chunk 2)", + cmd => fun(#{tester := Tester, + rclient := Client} = _State) -> + case ?SEV_AWAIT_READY(Client, rclient, send, + [{tester, Tester}]) of + {ok, Result} -> + Result; {error, _} = ERROR -> ERROR end end}, - #{desc => "make listen socket", - cmd => fun(#{sock := Sock}) -> - socket:listen(Sock) + #{desc => "order remote client to continue (send chunk 3)", + cmd => fun(#{rclient := Client, + data := Data} = State) -> + {Chunk, RestData} = lists:split(100, Data), + %% ?SEV_IPRINT("order send of chunk 3: " + %% "~n Size: ~p" + %% "~n ~p", [length(Chunk), Chunk]), + ?SEV_ANNOUNCE_CONTINUE(Client, send, Chunk), + {ok, State#{data => RestData}} end}, - #{desc => "announce ready (init)", - cmd => fun(#{tester := Tester, sock := Sock} = _State) -> - ?SEV_ANNOUNCE_READY(Tester, init, Sock), - ok + #{desc => "await client process ready (send chunk 3)", + cmd => fun(#{tester := Tester, + rclient := Client} = _State) -> + case ?SEV_AWAIT_READY(Client, rclient, send, + [{tester, Tester}]) of + {ok, Result} -> + Result; + {error, _} = ERROR -> + ERROR + end end}, - - %% The actual test - #{desc => "await continue (accept)", - cmd => fun(#{tester := Tester} = State) -> - case ?SEV_AWAIT_CONTINUE(Tester, tester, accept) of - {ok, Timeout} -> - {ok, State#{timeout => Timeout}}; + #{desc => "order remote client to continue (send chunk 4)", + cmd => fun(#{rclient := Client, + data := Data} = State) -> + {Chunk, RestData} = lists:split(100, Data), + %% ?SEV_IPRINT("order send of chunk 4: " + %% "~n Size: ~p" + %% "~n ~p", [length(Chunk), Chunk]), + ?SEV_ANNOUNCE_CONTINUE(Client, send, Chunk), + {ok, State#{data => RestData}} + end}, + #{desc => "await client process ready (send chunk 4)", + cmd => fun(#{tester := Tester, + rclient := Client} = _State) -> + case ?SEV_AWAIT_READY(Client, rclient, send, + [{tester, Tester}]) of + {ok, Result} -> + Result; {error, _} = ERROR -> ERROR end end}, - #{desc => "await connection", - cmd => fun(#{sock := Sock, timeout := Timeout} = _State) -> - case socket:accept(Sock, Timeout) of - {error, timeout} -> - ok; - {ok, Sock} -> - ?SEV_EPRINT("unexpected success"), - (catch socket:close(Sock)), - {error, unexpected_success}; + #{desc => "order remote client to continue (send chunk 5)", + cmd => fun(#{rclient := Client, + data := Data} = State) -> + {Chunk, RestData} = lists:split(100, Data), + %% ?SEV_IPRINT("order send of chunk 5: " + %% "~n Size: ~p" + %% "~n ~p", [length(Chunk), Chunk]), + ?SEV_ANNOUNCE_CONTINUE(Client, send, Chunk), + {ok, State#{data => RestData}} + end}, + #{desc => "await client process ready (send chunk 5)", + cmd => fun(#{tester := Tester, + rclient := Client} = _State) -> + case ?SEV_AWAIT_READY(Client, rclient, send, + [{tester, Tester}]) of + {ok, Result} -> + Result; {error, _} = ERROR -> ERROR end end}, - #{desc => "announce ready (accept timeout)", - cmd => fun(#{tester := Tester}) -> - ?SEV_ANNOUNCE_READY(Tester, accept_timeout), - ok + #{desc => "order remote client to continue (send chunk 6)", + cmd => fun(#{rclient := Client, + data := Data} = State) -> + {Chunk, RestData} = lists:split(100, Data), + %% ?SEV_IPRINT("order send of chunk 6: " + %% "~n Size: ~p" + %% "~n ~p", [length(Chunk), Chunk]), + ?SEV_ANNOUNCE_CONTINUE(Client, send, Chunk), + {ok, State#{data => RestData}} end}, - #{desc => "await continue (close)", - cmd => fun(#{tester := Tester} = _State) -> - ok = ?SEV_AWAIT_CONTINUE(Tester, tester, close) + #{desc => "await client process ready (send chunk 6)", + cmd => fun(#{tester := Tester, + rclient := Client} = _State) -> + case ?SEV_AWAIT_READY(Client, rclient, send, + [{tester, Tester}]) of + {ok, Result} -> + Result; + {error, _} = ERROR -> + ERROR + end end}, - #{desc => "close socket", - cmd => fun(#{sock := Sock} = State) -> - case socket:close(Sock) of - ok -> - {ok, maps:remove(sock, State)}; + #{desc => "order remote client to continue (send chunk 7)", + cmd => fun(#{rclient := Client, + data := Data} = State) -> + {Chunk, RestData} = lists:split(100, Data), + %% ?SEV_IPRINT("order send of chunk 7: " + %% "~n Size: ~p" + %% "~n ~p", [length(Chunk), Chunk]), + ?SEV_ANNOUNCE_CONTINUE(Client, send, Chunk), + {ok, State#{data => RestData}} + end}, + #{desc => "await client process ready (send chunk 7)", + cmd => fun(#{tester := Tester, + rclient := Client} = _State) -> + case ?SEV_AWAIT_READY(Client, rclient, send, + [{tester, Tester}]) of + {ok, Result} -> + Result; {error, _} = ERROR -> ERROR end end}, - #{desc => "announce ready (close)", - cmd => fun(#{tester := Tester}) -> - ?SEV_ANNOUNCE_READY(Tester, close), - ok + #{desc => "order remote client to continue (send chunk 8)", + cmd => fun(#{rclient := Client, + data := Data} = State) -> + {Chunk, RestData} = lists:split(100, Data), + %% ?SEV_IPRINT("order send of chunk 8: " + %% "~n Size: ~p" + %% "~n ~p", [length(Chunk), Chunk]), + ?SEV_ANNOUNCE_CONTINUE(Client, send, Chunk), + {ok, State#{data => RestData}} end}, - - % Termination - #{desc => "await terminate", - cmd => fun(#{tester := Tester} = State) -> - case ?SEV_AWAIT_TERMINATE(Tester, tester) of - ok -> - {ok, maps:remove(tester, State)}; + #{desc => "await client process ready (send chunk 8)", + cmd => fun(#{tester := Tester, + rclient := Client} = _State) -> + case ?SEV_AWAIT_READY(Client, rclient, send, + [{tester, Tester}]) of + {ok, Result} -> + Result; {error, _} = ERROR -> ERROR end - end}, - - %% *** We are done *** - ?SEV_FINISH_NORMAL - ], - - SecAcceptorSeq = - [ - %% *** Init part *** - #{desc => "await start", - cmd => fun(State) -> - {Tester, Sock} = ?SEV_AWAIT_START(), - {ok, State#{tester => Tester, sock => Sock}} end}, - #{desc => "monitor tester", - cmd => fun(#{tester := Tester} = _State) -> - _MRef = erlang:monitor(process, Tester), - ok + #{desc => "order remote client to continue (send chunk 9)", + cmd => fun(#{rclient := Client, + data := Data} = State) -> + {Chunk, RestData} = lists:split(100, Data), + %% ?SEV_IPRINT("order send of chunk 9: " + %% "~n Size: ~p" + %% "~n ~p", [length(Chunk), Chunk]), + ?SEV_ANNOUNCE_CONTINUE(Client, send, Chunk), + {ok, State#{data => RestData}} end}, - #{desc => "announce ready (init)", - cmd => fun(#{tester := Tester}) -> - ?SEV_ANNOUNCE_READY(Tester, init) + #{desc => "await client process ready (send chunk 9)", + cmd => fun(#{tester := Tester, + rclient := Client} = _State) -> + case ?SEV_AWAIT_READY(Client, rclient, send, + [{tester, Tester}]) of + {ok, Result} -> + Result; + {error, _} = ERROR -> + ERROR + end end}, - - %% The actual test - #{desc => "await continue (accept)", - cmd => fun(#{tester := Tester} = _State) -> - ok = ?SEV_AWAIT_CONTINUE(Tester, tester, accept) + #{desc => "order remote client to continue (send chunk 10)", + cmd => fun(#{rclient := Client, + data := Data} = State) -> + {Chunk, []} = lists:split(100, Data), + %% ?SEV_IPRINT("order send of chunk 10: " + %% "~n Size: ~p" + %% "~n ~p", [length(Chunk), Chunk]), + ?SEV_ANNOUNCE_CONTINUE(Client, send, Chunk), + {ok, maps:remove(data, State)} end}, - #{desc => "accept", - cmd => fun(#{sock := Sock} = State) -> - case socket:accept(Sock) of - {error, closed} -> - {ok, maps:remove(sock, State)}; - {ok, _} -> - {error, unexpected_success}; + #{desc => "await client process ready (send chunk 10)", + cmd => fun(#{tester := Tester, + rclient := Client} = _State) -> + case ?SEV_AWAIT_READY(Client, rclient, send, + [{tester, Tester}]) of + {ok, Result} -> + Result; {error, _} = ERROR -> ERROR end end}, - #{desc => "announce ready (accept closed)", + #{desc => "order remote client to continue (send stop)", + cmd => fun(#{rclient := Client} = State) -> + ?SEV_ANNOUNCE_CONTINUE(Client, send, stop), + {ok, maps:remove(data, State)} + end}, + #{desc => "await client process ready (send stop)", + cmd => fun(#{tester := Tester, + rclient := Client} = _State) -> + case ?SEV_AWAIT_READY(Client, rclient, send, + [{tester, Tester}]) of + {ok, Result} -> + Result; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (send-many-small)", cmd => fun(#{tester := Tester}) -> - ?SEV_ANNOUNCE_READY(Tester, accept_closed) + ?SEV_ANNOUNCE_READY(Tester, send_many_small), + ok end}, %% Termination #{desc => "await terminate (from tester)", - cmd => fun(#{tester := Tester} = State) -> - case ?SEV_AWAIT_TERMINATE(Tester, tester) of + cmd => fun(#{tester := Tester, + rclient := Client} = State) -> + case ?SEV_AWAIT_TERMINATE(Tester, tester, + [{rclient, Client}]) of ok -> {ok, maps:remove(tester, State)}; {error, _} = ERROR -> ERROR end end}, - - %% *** We are done *** - ?SEV_FINISH_NORMAL - ], - - TesterSeq = - [ - %% *** Init part *** - #{desc => "monitor 'primary acceptor'", - cmd => fun(#{prim_acc := Pid} = _State) -> - _MRef = erlang:monitor(process, Pid), - ok - end}, - #{desc => "monitor 'secondary acceptor 1'", - cmd => fun(#{sec_acc1 := Pid} = _State) -> - _MRef = erlang:monitor(process, Pid), - ok - end}, - #{desc => "monitor secondary acceptor 2", - cmd => fun(#{sec_acc2 := Pid} = _State) -> - _MRef = erlang:monitor(process, Pid), + #{desc => "kill remote client", + cmd => fun(#{rclient := Client}) -> + ?SEV_ANNOUNCE_TERMINATE(Client), ok end}, - #{desc => "monitor secondary acceptor 3", - cmd => fun(#{sec_acc3 := Pid} = _State) -> - _MRef = erlang:monitor(process, Pid), - ok + #{desc => "await remote client termination", + cmd => fun(#{rclient := Client} = State) -> + ?SEV_AWAIT_TERMINATION(Client), + State1 = maps:remove(rclient, State), + {ok, State1} end}, - - %% Start the primary server - #{desc => "order 'primary acceptor' start", - cmd => fun(#{prim_acc := Pid} = _State) -> - ?SEV_ANNOUNCE_START(Pid), - ok + #{desc => "stop client node", + cmd => fun(#{node := Node} = _State) -> + stop_node(Node) end}, - #{desc => "await 'primary acceptor' ready (init)", - cmd => fun(#{prim_acc := Pid} = State) -> - {ok, Sock} = ?SEV_AWAIT_READY(Pid, prim_acc, init), - {ok, State#{sock => Sock}} + #{desc => "await client node termination", + cmd => fun(#{node := Node} = State) -> + receive + {nodedown, Node} -> + {ok, maps:remove(node, State)} + end end}, - %% Start the secondary acceptor 1 - #{desc => "order 'secondary acceptor 1' start", - cmd => fun(#{sec_acc1 := Pid, sock := Sock} = _State) -> - ?SEV_ANNOUNCE_START(Pid, Sock), - ok - end}, - #{desc => "await 'secondary acceptor 1' ready (init)", - cmd => fun(#{sec_acc1 := Pid} = _State) -> - ok = ?SEV_AWAIT_READY(Pid, sec_acc1, init) - end}, + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], - %% Start the secondary acceptor 2 - #{desc => "order 'secondary acceptor 2' start", - cmd => fun(#{sec_acc2 := Pid, sock := Sock} = _State) -> - ?SEV_ANNOUNCE_START(Pid, Sock), + TesterSeq = + [ + %% *** Init part *** + #{desc => "monitor server", + cmd => fun(#{server := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), ok end}, - #{desc => "await 'secondary acceptor 2' ready (init)", - cmd => fun(#{sec_acc2 := Pid} = _State) -> - ok = ?SEV_AWAIT_READY(Pid, sec_acc2, init) + #{desc => "monitor client", + cmd => fun(#{client := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), + ok end}, - %% Start the secondary acceptor 3 - #{desc => "order 'secondary acceptor 3' start", - cmd => fun(#{sec_acc3 := Pid, sock := Sock} = _State) -> - ?SEV_ANNOUNCE_START(Pid, Sock), + %% Start the server + #{desc => "order server start", + cmd => fun(#{server := Pid} = _State) -> + ?SEV_ANNOUNCE_START(Pid), ok end}, - #{desc => "await 'secondary acceptor 3' ready (init)", - cmd => fun(#{sec_acc3 := Pid} = _State) -> - ok = ?SEV_AWAIT_READY(Pid, sec_acc3, init) + #{desc => "await server ready (init)", + cmd => fun(#{server := Pid} = State) -> + {ok, ServerSA} = ?SEV_AWAIT_READY(Pid, server, init), + {ok, State#{server_sa => ServerSA}} end}, - - %% The actual test - %% Make all the seondary servers continue, with an infinit recvfrom - %% and then the prim-server with a timed recvfrom. - %% After the prim server notifies us (about the timeout) we order it - %% to close the socket, which should cause the all the secondary - %% server to return with error-closed. - - #{desc => "order 'secondary acceptor 1' to continue (accept)", - cmd => fun(#{sec_acc1 := Pid} = _State) -> - ?SEV_ANNOUNCE_CONTINUE(Pid, accept), + %% Start the client + #{desc => "order client start", + cmd => fun(#{client := Pid, + server_sa := ServerSA} = _State) -> + ?SEV_ANNOUNCE_START(Pid, ServerSA), ok end}, - ?SEV_SLEEP(?SECS(1)), - #{desc => "order 'secondary acceptor 2' to continue (accept)", - cmd => fun(#{sec_acc2 := Pid} = _State) -> - ?SEV_ANNOUNCE_CONTINUE(Pid, accept), - ok + #{desc => "await client ready (init)", + cmd => fun(#{client := Pid} = _State) -> + ok = ?SEV_AWAIT_READY(Pid, client, init) end}, - ?SEV_SLEEP(?SECS(1)), - #{desc => "order 'secondary acceptor 3' to continue (accept)", - cmd => fun(#{sec_acc3 := Pid} = _State) -> + + %% The actual test + #{desc => "order server continue (accept)", + cmd => fun(#{server := Pid} = _State) -> ?SEV_ANNOUNCE_CONTINUE(Pid, accept), ok end}, ?SEV_SLEEP(?SECS(1)), - #{desc => "order 'primary acceptor' to continue", - cmd => fun(#{prim_acc := Pid} = _State) -> - ?SEV_ANNOUNCE_CONTINUE(Pid, accept, ?SECS(5)), + #{desc => "order client continue (connect)", + cmd => fun(#{client := Pid} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Pid, connect), ok end}, - #{desc => "await 'primary acceptor' ready (accept timeout)", - cmd => fun(#{prim_acc := Pid} = _State) -> - ok = ?SEV_AWAIT_READY(Pid, prim_acc, accept_timeout) - end}, - #{desc => "order 'primary acceptor' to continue (close)", - cmd => fun(#{prim_acc := Pid} = _State) -> - ?SEV_ANNOUNCE_CONTINUE(Pid, close), + #{desc => "await server ready (accept)", + cmd => fun(#{server := Server, + client := Client} = _State) -> + ?SEV_AWAIT_READY(Server, server, accept, + [{client, Client}]), ok end}, - #{desc => "await 'primary acceptor' ready (close)", - cmd => fun(#{prim_acc := Pid} = _State) -> - ok = ?SEV_AWAIT_READY(Pid, prim_acc, close) - end}, - #{desc => "await 'secondary acceptor 1' ready (accept closed)", - cmd => fun(#{sec_acc1 := Pid} = _State) -> - ok = ?SEV_AWAIT_READY(Pid, sec_acc1, accept_closed) - end}, - #{desc => "await 'secondary acceptor 2' ready (accept closed)", - cmd => fun(#{sec_acc2 := Pid} = _State) -> - ok = ?SEV_AWAIT_READY(Pid, sec_acc2, accept_closed) + #{desc => "await client ready (connect)", + cmd => fun(#{server := Server, + client := Client} = _State) -> + ?SEV_AWAIT_READY(Client, client, connect, + [{server, Server}]) end}, - #{desc => "await 'secondary acceptor 3' ready (accept closed)", - cmd => fun(#{sec_acc3 := Pid} = _State) -> - ok = ?SEV_AWAIT_READY(Pid, sec_acc3, accept_closed) + + #{desc => "generate data", + cmd => fun(State) -> + D1 = lists:seq(1,250), + D2 = lists:duplicate(4, D1), + D3 = lists:flatten(D2), + {ok, State#{data => D3}} end}, - - %% Terminations - #{desc => "order 'secondary acceptor 3' to terminate", - cmd => fun(#{sec_acc3 := Pid} = _State) -> - ?SEV_ANNOUNCE_TERMINATE(Pid), + %% (client) Send one big and (server) recv may small + #{desc => "order server continue (recv-many-small)", + cmd => fun(#{server := Pid} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Pid, recv_many_small), ok end}, - #{desc => "await 'secondary acceptor 3' termination", - cmd => fun(#{sec_acc3 := Pid} = State) -> - case ?SEV_AWAIT_TERMINATION(Pid) of - ok -> - {ok, maps:remove(sec_acc3, State)}; + ?SEV_SLEEP(?SECS(1)), + #{desc => "order client continue (send-one-big)", + cmd => fun(#{client := Pid, data := Data} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Pid, send_one_big, Data), + ok + end}, + #{desc => "await client ready (send-one-big)", + cmd => fun(#{server := Server, + client := Client} = _State) -> + ok = ?SEV_AWAIT_READY(Client, client, send_one_big, + [{server, Server}]) + end}, + #{desc => "await server ready (recv-many-small)", + cmd => fun(#{server := Server, + client := Client, + data := Data} = _State) -> + case ?SEV_AWAIT_READY(Server, server, recv_many_small, + [{client, Client}]) of + {ok, Data} -> + ok; + {ok, OtherData} -> + {error, {mismatched_data, Data, OtherData}}; {error, _} = ERROR -> ERROR end end}, - #{desc => "order 'secondary acceptor 2' to terminate", - cmd => fun(#{sec_acc2 := Pid} = _State) -> - ?SEV_ANNOUNCE_TERMINATE(Pid), + + #{desc => "order server continue (recv-one-big)", + cmd => fun(#{server := Pid, data := Data} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Pid, recv_one_big, length(Data)), ok end}, - #{desc => "await 'secondary acceptor 2' termination", - cmd => fun(#{sec_acc2 := Pid} = State) -> - case ?SEV_AWAIT_TERMINATION(Pid) of - ok -> - {ok, maps:remove(sec_acc2, State)}; + ?SEV_SLEEP(?SECS(1)), + #{desc => "order client continue (send-many-small)", + cmd => fun(#{client := Pid, data := Data} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Pid, send_many_small, Data), + ok + end}, + #{desc => "await client ready (send-many-small)", + cmd => fun(#{server := Server, + client := Client} = _State) -> + ok = ?SEV_AWAIT_READY(Client, client, send_many_small, + [{server, Server}]) + end}, + #{desc => "await server ready (recv-one-big)", + cmd => fun(#{server := Server, + client := Client, + data := Data} = State) -> + case ?SEV_AWAIT_READY(Server, server, recv_one_big, + [{client, Client}]) of + {ok, Data} -> + {ok, maps:remove(data, State)}; + {ok, OtherData} -> + {error, {mismatched_data, Data, OtherData}}; {error, _} = ERROR -> ERROR end end}, - #{desc => "order 'secondary acceptor 1' to terminate", - cmd => fun(#{sec_acc1 := Pid} = _State) -> + + %% Terminations + #{desc => "order client to terminate", + cmd => fun(#{client := Pid} = _State) -> ?SEV_ANNOUNCE_TERMINATE(Pid), ok end}, - #{desc => "await 'secondary acceptor 1' termination", - cmd => fun(#{sec_acc1 := Pid} = State) -> + #{desc => "await client termination", + cmd => fun(#{client := Pid} = State) -> case ?SEV_AWAIT_TERMINATION(Pid) of ok -> - {ok, maps:remove(sec_acc1, State)}; + State1 = maps:remove(client, State), + {ok, State1}; {error, _} = ERROR -> ERROR end end}, - #{desc => "order 'primary acceptor' to terminate", - cmd => fun(#{prim_acc := Pid} = _State) -> + #{desc => "order server to terminate", + cmd => fun(#{server := Pid} = _State) -> ?SEV_ANNOUNCE_TERMINATE(Pid), ok end}, - #{desc => "await 'primary acceptor' termination", - cmd => fun(#{prim_acc := Pid} = State) -> + #{desc => "await server termination", + cmd => fun(#{server := Pid} = State) -> case ?SEV_AWAIT_TERMINATION(Pid) of ok -> - {ok, maps:remove(prim_acc, State)}; + State1 = maps:remove(server, State), + {ok, State1}; {error, _} = ERROR -> ERROR end @@ -5679,31 +6539,128 @@ sc_lc_acceptor_response_tcp(InitState) -> ?SEV_FINISH_NORMAL ], + i("start server evaluator"), + ServerInitState = InitState, + Server = ?SEV_START("server", ServerSeq, ServerInitState), - i("start 'primary acceptor' evaluator"), - PrimAccInitState = InitState, - PrimAcc = ?SEV_START("prim-acceptor", PrimAcceptorSeq, PrimAccInitState), - - i("start 'secondary acceptor 1' evaluator"), - SecAccInitState = #{}, - SecAcc1 = ?SEV_START("sec-acceptor-1", SecAcceptorSeq, SecAccInitState), - - i("start 'secondary acceptor 2' evaluator"), - SecAcc2 = ?SEV_START("sec-acceptor-2", SecAcceptorSeq, SecAccInitState), - - i("start 'secondary acceptor 3' evaluator"), - SecAcc3 = ?SEV_START("sec-acceptor-3", SecAcceptorSeq, SecAccInitState), + i("start client evaluator(s)"), + ClientInitState = InitState#{host => local_host()}, + Client = ?SEV_START("client", ClientSeq, ClientInitState), i("start 'tester' evaluator"), - TesterInitState = #{prim_acc => PrimAcc#ev.pid, - sec_acc1 => SecAcc1#ev.pid, - sec_acc2 => SecAcc2#ev.pid, - sec_acc3 => SecAcc3#ev.pid}, + TesterInitState = #{server => Server#ev.pid, + client => Client#ev.pid}, Tester = ?SEV_START("tester", TesterSeq, TesterInitState), i("await evaluator"), - ok = ?SEV_AWAIT_FINISH([PrimAcc, SecAcc1, SecAcc2, SecAcc3, Tester]). + ok = ?SEV_AWAIT_FINISH([Server, Client, Tester]). + + + +traffic_snr_tcp_client_start(Node) -> + Self = self(), + GL = group_leader(), + Fun = fun() -> traffic_snr_tcp_client(Self, GL) end, + erlang:spawn(Node, Fun). + +traffic_snr_tcp_client(Parent, GL) -> + {Sock, ServerSA} = traffic_snr_tcp_client_init(Parent, GL), + traffic_snr_tcp_client_announce_ready(Parent, init), + traffic_snr_tcp_client_await_continue(Parent, connect), + traffic_snr_tcp_client_connect(Sock, ServerSA), + traffic_snr_tcp_client_announce_ready(Parent, connect), + traffic_snr_tcp_client_send_loop(Parent, Sock), + Reason = traffic_snr_tcp_client_await_terminate(Parent), + traffic_snr_tcp_client_close(Sock), + exit(Reason). + + +traffic_snr_tcp_client_send_loop(Parent, Sock) -> + case ?SEV_AWAIT_CONTINUE(Parent, parent, send) of + {ok, stop} -> % Breakes the loop + ?SEV_ANNOUNCE_READY(Parent, send, ok), + ok; + {ok, Data} -> + case socket:send(Sock, Data) of + ok -> + ?SEV_ANNOUNCE_READY(Parent, send, ok), + traffic_snr_tcp_client_send_loop(Parent, Sock); + {error, Reason} = ERROR -> + ?SEV_ANNOUNCE_READY(Parent, send, ERROR), + exit({send, Reason}) + end; + {error, Reason} -> + exit({await_continue, Reason}) + end. + +traffic_snr_tcp_client_init(Parent, GL) -> + i("traffic_snr_tcp_client_init -> entry"), + _MRef = erlang:monitor(process, Parent), + group_leader(self(), GL), + ServerSA = traffic_snr_tcp_client_await_start(Parent), + Domain = maps:get(family, ServerSA), + Sock = traffic_snr_tcp_client_create(Domain), + traffic_snr_tcp_client_bind(Sock, Domain), + {Sock, ServerSA}. + +traffic_snr_tcp_client_await_start(Parent) -> + i("traffic_snr_tcp_client_await_start -> entry"), + ?SEV_AWAIT_START(Parent). + +traffic_snr_tcp_client_create(Domain) -> + i("traffic_snr_tcp_client_create -> entry"), + case socket:open(Domain, stream, tcp) of + {ok, Sock} -> + Sock; + {error, Reason} -> + exit({open_failed, Reason}) + end. + +traffic_snr_tcp_client_bind(Sock, Domain) -> + i("traffic_snr_tcp_client_bind -> entry"), + LAddr = which_local_addr(Domain), + LSA = #{family => Domain, + addr => LAddr}, + case socket:bind(Sock, LSA) of + {ok, _} -> + ok; + {error, Reason} -> + exit({bind, Reason}) + end. + +traffic_snr_tcp_client_announce_ready(Parent, Slogan) -> + ?SEV_ANNOUNCE_READY(Parent, Slogan). + +traffic_snr_tcp_client_await_continue(Parent, Slogan) -> + i("traffic_snr_tcp_client_await_continue -> entry"), + ?SEV_AWAIT_CONTINUE(Parent, parent, Slogan). + +traffic_snr_tcp_client_connect(Sock, ServerSA) -> + i("traffic_snr_tcp_client_connect -> entry"), + case socket:connect(Sock, ServerSA) of + ok -> + ok; + {error, Reason} -> + exit({connect, Reason}) + end. + +traffic_snr_tcp_client_close(Sock) -> + i("traffic_snr_tcp_client_close -> entry"), + case socket:close(Sock) of + ok -> + ok; + {error, Reason} -> + exit({close, Reason}) + end. +traffic_snr_tcp_client_await_terminate(Parent) -> + i("traffic_snr_tcp_client_await_terminate -> entry"), + case ?SEV_AWAIT_TERMINATE(Parent, parent) of + ok -> + ok; + {error, Reason} -> + Reason + end. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -5738,6 +6695,36 @@ which_addr2(Domain, [_|IFO]) -> +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +start_node(Host, NodeName) -> + UniqueNodeName = f("~w_~w", [NodeName, erlang:unique_integer([positive])]), + case do_start_node(Host, UniqueNodeName) of + {ok, _} = OK -> + OK; + {error, Reason, _} -> + {error, Reason} + end. + +do_start_node(Host, NodeName) when is_list(NodeName) -> + do_start_node(Host, list_to_atom(NodeName)); +do_start_node(Host, NodeName) when is_atom(NodeName) -> + Dir = filename:dirname(code:which(?MODULE)), + Flags = "-pa " ++ Dir, + Opts = [{monitor_master, true}, {erl_flags, Flags}], + ct_slave:start(Host, NodeName, Opts). + + +stop_node(Node) -> + case ct_slave:stop(Node) of + {ok, _} -> + ok; + {error, _} = ERROR -> + ERROR + end. + + + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% sock_open(Domain, Type, Proto) -> @@ -5936,6 +6923,9 @@ tc_which_name() -> l2a(S) when is_list(S) -> list_to_atom(S). +b2l(B) when is_binary(B) -> + binary_to_list(B). + f(F, A) -> lists:flatten(io_lib:format(F, A)). diff --git a/erts/preloaded/ebin/socket.beam b/erts/preloaded/ebin/socket.beam index a0bf156263..480f86334c 100644 Binary files a/erts/preloaded/ebin/socket.beam and b/erts/preloaded/ebin/socket.beam differ diff --git a/erts/preloaded/src/socket.erl b/erts/preloaded/src/socket.erl index acf5e18cec..5ebc2074e0 100644 --- a/erts/preloaded/src/socket.erl +++ b/erts/preloaded/src/socket.erl @@ -1716,10 +1716,14 @@ do_recv(SockRef, _OldRef, Length, EFlags, Acc, Timeout) (is_integer(Timeout) andalso (Timeout > 0)) -> TS = timestamp(Timeout), RecvRef = make_ref(), + %% p("do_recv -> try read with" + %% "~n Length: ~p", [Length]), case nif_recv(SockRef, RecvRef, Length, EFlags) of {ok, true = _Complete, Bin} when (size(Acc) =:= 0) -> + %% p("do_recv -> complete success: ~w", [size(Bin)]), {ok, Bin}; {ok, true = _Complete, Bin} -> + %% p("do_recv -> completed success: ~w (~w)", [size(Bin), size(Acc)]), {ok, <>}; %% It depends on the amount of bytes we tried to read: @@ -1728,6 +1732,7 @@ do_recv(SockRef, _OldRef, Length, EFlags, Acc, Timeout) %% > 0 - We got a part of the message and we will be notified %% when there is more to read (a select message) {ok, false = _Complete, Bin} when (Length =:= 0) -> + %% p("do_recv -> partial success: ~w", [size(Bin)]), do_recv(SockRef, RecvRef, Length, EFlags, <>, @@ -1737,6 +1742,8 @@ do_recv(SockRef, _OldRef, Length, EFlags, Acc, Timeout) %% We got the first chunk of it. %% We will be notified (select message) when there %% is more to read. + %% p("do_recv -> partial success(~w): ~w" + %% "~n ~p", [Length, size(Bin), Bin]), NewTimeout = next_timeout(TS, Timeout), receive {select, SockRef, RecvRef, ready_input} -> @@ -1756,6 +1763,8 @@ do_recv(SockRef, _OldRef, Length, EFlags, Acc, Timeout) {ok, false = _Completed, Bin} -> %% We got a chunk of it! + %% p("do_recv -> partial success(~w): ~w (~w)", + %% [Length, size(Bin), size(Acc)]), NewTimeout = next_timeout(TS, Timeout), receive {select, SockRef, RecvRef, ready_input} -> @@ -1780,6 +1789,7 @@ do_recv(SockRef, _OldRef, Length, EFlags, Acc, Timeout) {error, eagain} -> %% There is nothing just now, but we will be notified when there %% is something to read (a select message). + %% p("do_recv -> eagain(~w): ~w", [Length, size(Acc)]), NewTimeout = next_timeout(TS, Timeout), receive {select, SockRef, RecvRef, ready_input} -> @@ -3473,6 +3483,7 @@ tdiff(T1, T2) -> %% p(undefined, F, A) -> %% p("***", F, A); %% p(SName, F, A) -> +%% io:format(user,"[~s,~p] " ++ F ++ "~n", [SName, self()|A]), %% io:format("[~s,~p] " ++ F ++ "~n", [SName, self()|A]). -- cgit v1.2.3 From 0c6a8375990e491405e2282e5e038d384727f1e2 Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Thu, 1 Nov 2018 18:30:09 +0100 Subject: [sock-nif|test] Add a ping-pong test case We got some kind of send hang... --- erts/emulator/nifs/common/socket_nif.c | 20 +- erts/emulator/test/socket_SUITE.erl | 1408 +++++++++++++++++++++++++++----- 2 files changed, 1224 insertions(+), 204 deletions(-) diff --git a/erts/emulator/nifs/common/socket_nif.c b/erts/emulator/nifs/common/socket_nif.c index 27395b5cf6..389d43ee42 100644 --- a/erts/emulator/nifs/common/socket_nif.c +++ b/erts/emulator/nifs/common/socket_nif.c @@ -13314,12 +13314,6 @@ ERL_NIF_TERM send_check_result(ErlNifEnv* env, SSDBG( descP, ("SOCKET", "send_check_result -> try again\r\n") ); - /* - * SHOULD RESULT IN {error, eagain}!!!! - * - */ - written = 0; - } } @@ -13349,7 +13343,8 @@ ERL_NIF_TERM send_check_result(ErlNifEnv* env, descP, NULL, sendRef); SSDBG( descP, - ("SOCKET", "send_check_result -> not entire package written\r\n") ); + ("SOCKET", "send_check_result -> " + "not entire package written (%d of %d)\r\n", written, dataSize) ); return esock_make_ok2(env, MKI(env, written)); @@ -13687,15 +13682,26 @@ ERL_NIF_TERM recv_check_result(ErlNifEnv* env, } else if ((saveErrno == ERRNO_BLOCK) || (saveErrno == EAGAIN)) { + int sres; + SSDBG( descP, ("SOCKET", "recv_check_result -> [%d] eagain\r\n", toRead) ); if ((xres = recv_init_current_reader(env, descP, recvRef)) != NULL) return esock_make_error_str(env, xres); + SSDBG( descP, ("SOCKET", "recv_check_result -> SELECT for more\r\n") ); + + /* SELECT(env, descP->sock, (ERL_NIF_SELECT_READ), descP, NULL, recvRef); + */ + sres = enif_select(env, descP->sock, (ERL_NIF_SELECT_READ), + descP, NULL, recvRef); + + SSDBG( descP, ("SOCKET", "recv_check_result -> SELECT res: %d\r\n", sres) ); + return esock_make_error(env, esock_atom_eagain); } else { ERL_NIF_TERM res = esock_make_error_errno(env, saveErrno); diff --git a/erts/emulator/test/socket_SUITE.erl b/erts/emulator/test/socket_SUITE.erl index 7a1a362181..90e9407f6a 100644 --- a/erts/emulator/test/socket_SUITE.erl +++ b/erts/emulator/test/socket_SUITE.erl @@ -88,7 +88,19 @@ %% Traffic traffic_send_and_recv_chunks_tcp4/1, - traffic_send_and_recv_chunks_tcp6/1 + traffic_send_and_recv_chunks_tcp6/1, + traffic_ping_pong_small_send_and_recv_tcp4/1, + traffic_ping_pong_small_send_and_recv_tcp6/1, + %% traffic_ping_pong_small_sendmsg_and_recvmsg_tcp4/1, + %% traffic_ping_pong_small_sendmsg_and_recvmsg_tcp6/1, + traffic_ping_pong_medium_send_and_recv_tcp4/1, + traffic_ping_pong_medium_send_and_recv_tcp6/1, + %% traffic_ping_pong_medium_sendmsg_and_recvmsg_tcp4/1, + %% traffic_ping_pong_medium_sendmsg_and_recvmsg_tcp6/1, + traffic_ping_pong_large_send_and_recv_tcp4/1, + traffic_ping_pong_large_send_and_recv_tcp6/1%, + %% traffic_ping_pong_large_sendmsg_and_recvmsg_tcp4/1, + %% traffic_ping_pong_large_sendmsg_and_recvmsg_tcp6/1 %% Tickets @@ -115,6 +127,8 @@ -define(TT(T), ct:timetrap(T)). +-define(LIB, socket_test_lib). + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -243,7 +257,20 @@ sc_rc_cases() -> traffic_cases() -> [ traffic_send_and_recv_chunks_tcp4, - traffic_send_and_recv_chunks_tcp6 + traffic_send_and_recv_chunks_tcp6, + + traffic_ping_pong_small_send_and_recv_tcp4, + traffic_ping_pong_small_send_and_recv_tcp6, + %% traffic_ping_pong_small_sendmsg_and_recvmsg_tcp4, + %% traffic_ping_pong_small_sendmsg_and_recvmsg_tcp6, + traffic_ping_pong_medium_send_and_recv_tcp4, + traffic_ping_pong_medium_send_and_recv_tcp6, + %% traffic_ping_pong_medium_sendmsg_and_recvmsg_tcp4, + %% traffic_ping_pong_medium_sendmsg_and_recvmsg_tcp6, + traffic_ping_pong_large_send_and_recv_tcp4, + traffic_ping_pong_large_send_and_recv_tcp6%% , + %% traffic_ping_pong_large_sendmsg_and_recvmsg_tcp4, + %% traffic_ping_pong_large_sendmsg_and_recvmsg_tcp6 ]. @@ -5713,6 +5740,8 @@ traffic_send_and_recv_chunks_tcp6(_Config) when is_list(_Config) -> end). +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + traffic_send_and_recv_chunks_tcp(InitState) -> ServerSeq = [ @@ -6663,223 +6692,1189 @@ traffic_snr_tcp_client_await_terminate(Parent) -> end. -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -%% This gets the local address (not 127.0...) -%% We should really implement this using the (new) net module, -%% but until that gets the necessary functionality... -which_local_addr(Domain) -> - case inet:getifaddrs() of - {ok, IFL} -> - which_addr(Domain, IFL); - {error, Reason} -> - ?FAIL({inet, getifaddrs, Reason}) - end. - -which_addr(_Domain, []) -> - ?FAIL(no_address); -which_addr(Domain, [{Name, IFO}|_IFL]) when (Name =/= "lo") -> - which_addr2(Domain, IFO); -which_addr(Domain, [_|IFL]) -> - which_addr(Domain, IFL). - -which_addr2(_Domain, []) -> - ?FAIL(no_address); -which_addr2(inet = _Domain, [{addr, Addr}|_IFO]) when (size(Addr) =:= 4) -> - Addr; -which_addr2(inet6 = _Domain, [{addr, Addr}|_IFO]) when (size(Addr) =:= 8) -> - Addr; -which_addr2(Domain, [_|IFO]) -> - which_addr2(Domain, IFO). - - %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -start_node(Host, NodeName) -> - UniqueNodeName = f("~w_~w", [NodeName, erlang:unique_integer([positive])]), - case do_start_node(Host, UniqueNodeName) of - {ok, _} = OK -> - OK; - {error, Reason, _} -> - {error, Reason} - end. - -do_start_node(Host, NodeName) when is_list(NodeName) -> - do_start_node(Host, list_to_atom(NodeName)); -do_start_node(Host, NodeName) when is_atom(NodeName) -> - Dir = filename:dirname(code:which(?MODULE)), - Flags = "-pa " ++ Dir, - Opts = [{monitor_master, true}, {erl_flags, Flags}], - ct_slave:start(Host, NodeName, Opts). - - -stop_node(Node) -> - case ct_slave:stop(Node) of - {ok, _} -> - ok; - {error, _} = ERROR -> - ERROR - end. - +%% This test case is intended to test that the send and recv functions +%% by repeatedly sending a meassage between two entities. +%% The same basic test case is used for three different message sizes; +%% small (8 bytes), medium (8K) and large (8M). +%% The message is sent from A to B and then back again. This is +%% repeated a set number of times (more times the small the message). +%% This is the 'small' message test case, for IPv4. + +traffic_ping_pong_small_send_and_recv_tcp4(suite) -> + []; +traffic_ping_pong_small_send_and_recv_tcp4(doc) -> + []; +traffic_ping_pong_small_send_and_recv_tcp4(_Config) when is_list(_Config) -> + tc_try(traffic_ping_pong_small_send_and_recv_tcp4, + fun() -> + ?TT(?SECS(15)), + Send = fun(Sock, Data) -> socket:send(Sock, Data) end, + %% Recv = fun(Sock) -> socket:recv(Sock, 0, 5000) end, + Recv = fun(Sock) -> socket:recv(Sock) end, + InitState = #{domain => inet, + send => Send, % Send function + recv => Recv % Receive function + }, + ok = traffic_ping_pong_small_send_and_receive_tcp(InitState) + end). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -sock_open(Domain, Type, Proto) -> - try socket:open(Domain, Type, Proto) of - {ok, Socket} -> - Socket; - {error, Reason} -> - ?FAIL({open, Reason}) - catch - C:E:S -> - ?FAIL({open, C, E, S}) - end. - - -sock_bind(Sock, SockAddr) -> - try socket:bind(Sock, SockAddr) of - {ok, Port} -> - Port; - {error, Reason} -> - i("sock_bind -> error: ~p", [Reason]), - ?FAIL({bind, Reason}) - catch - C:E:S -> - i("sock_bind -> failed: ~p, ~p, ~p", [C, E, S]), - ?FAIL({bind, C, E, S}) - end. - -sock_connect(Sock, SockAddr) -> - try socket:connect(Sock, SockAddr) of - ok -> - ok; - {error, Reason} -> - ?FAIL({connect, Reason}) - catch - C:E:S -> - ?FAIL({connect, C, E, S}) - end. - -sock_sockname(Sock) -> - try socket:sockname(Sock) of - {ok, SockAddr} -> - SockAddr; - {error, Reason} -> - ?FAIL({sockname, Reason}) - catch - C:E:S -> - ?FAIL({sockname, C, E, S}) - end. - - -%% sock_listen(Sock) -> -%% sock_listen2(fun() -> socket:listen(Sock) end). - -%% sock_listen(Sock, BackLog) -> -%% sock_listen2(fun() -> socket:listen(Sock, BackLog) end). - -%% sock_listen2(Listen) -> -%% try Listen() of -%% ok -> -%% ok; -%% {error, Reason} -> -%% ?FAIL({listen, Reason}) -%% catch -%% C:E:S -> -%% ?FAIL({listen, C, E, S}) -%% end. - - -%% sock_accept(LSock) -> -%% try socket:accept(LSock) of -%% {ok, Sock} -> -%% Sock; -%% {error, Reason} -> -%% i("sock_accept -> error: ~p", [Reason]), -%% ?FAIL({accept, Reason}) -%% catch -%% C:E:S -> -%% i("sock_accept -> failed: ~p, ~p, ~p", [C, E, S]), -%% ?FAIL({accept, C, E, S}) -%% end. - - -sock_close(Sock) -> - try socket:close(Sock) of - ok -> - ok; - {error, Reason} -> - i("sock_close -> error: ~p", [Reason]), - ?FAIL({close, Reason}) - catch - C:E:S -> - i("sock_close -> failed: ~p, ~p, ~p", [C, E, S]), - ?FAIL({close, C, E, S}) - end. - +%% This test case is intended to test that the send and recv functions +%% by repeatedly sending a meassage between two entities. +%% The same basic test case is used for three different message sizes; +%% small (8 bytes), medium (8K) and large (8M). +%% The message is sent from A to B and then back again. This is +%% repeated a set number of times (more times the small the message). +%% This is the 'small' message test case, for IPv6. + +traffic_ping_pong_small_send_and_recv_tcp6(suite) -> + []; +traffic_ping_pong_small_send_and_recv_tcp6(doc) -> + []; +traffic_ping_pong_small_send_and_recv_tcp6(_Config) when is_list(_Config) -> + tc_try(traffic_ping_pong_small_send_and_recv_tcp6, + fun() -> + not_yet_implemented(), + ?TT(?SECS(30)), + Send = fun(Sock, Data) -> socket:send(Sock, Data) end, + Recv = fun(Sock) -> socket:recv(Sock) end, + InitState = #{domain => inet6, + send => Send, % Send function + recv => Recv % Receive function + }, + ok = traffic_ping_pong_small_send_and_receive_tcp(InitState) + end). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -not_yet_implemented() -> - skip("not yet implemented"). - -skip(Reason) -> - throw({skip, Reason}). +%% This test case is intended to test that the send and recv functions +%% by repeatedly sending a meassage between two entities. +%% The same basic test case is used for three different message sizes; +%% small (8 bytes), medium (8K) and large (8M). +%% The message is sent from A to B and then back again. This is +%% repeated a set number of times (more times the small the message). +%% This is the 'medium' message test case, for IPv4. + +traffic_ping_pong_medium_send_and_recv_tcp4(suite) -> + []; +traffic_ping_pong_medium_send_and_recv_tcp4(doc) -> + []; +traffic_ping_pong_medium_send_and_recv_tcp4(_Config) when is_list(_Config) -> + tc_try(traffic_ping_pong_medium_send_and_recv_tcp4, + fun() -> + %% not_yet_implemented(), + ?TT(?SECS(30)), + Send = fun(Sock, Data) -> socket:send(Sock, Data) end, + Recv = fun(Sock) -> socket:recv(Sock) end, + InitState = #{domain => inet, + send => Send, % Send function + recv => Recv % Receive function + }, + ok = traffic_ping_pong_medium_send_and_receive_tcp(InitState) + end). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test that the send and recv functions +%% by repeatedly sending a meassage between two entities. +%% The same basic test case is used for three different message sizes; +%% small (8 bytes), medium (8K) and large (8M). +%% The message is sent from A to B and then back again. This is +%% repeated a set number of times (more times the small the message). +%% This is the 'medium' message test case, for IPv6. + +traffic_ping_pong_medium_send_and_recv_tcp6(suite) -> + []; +traffic_ping_pong_medium_send_and_recv_tcp6(doc) -> + []; +traffic_ping_pong_medium_send_and_recv_tcp6(_Config) when is_list(_Config) -> + tc_try(traffic_ping_pong_medium_send_and_recv_tcp6, + fun() -> + not_yet_implemented(), + ?TT(?SECS(30)), + Send = fun(Sock, Data) -> socket:send(Sock, Data) end, + Recv = fun(Sock) -> socket:recv(Sock) end, + InitState = #{domain => inet6, + send => Send, % Send function + recv => Recv % Receive function + }, + ok = traffic_ping_pong_medium_send_and_receive_tcp(InitState) + end). -t() -> - os:timestamp(). - - -tdiff({A1, B1, C1} = _T1x, {A2, B2, C2} = _T2x) -> - T1 = A1*1000000000+B1*1000+(C1 div 1000), - T2 = A2*1000000000+B2*1000+(C2 div 1000), - T2 - T1. -formated_timestamp() -> - format_timestamp(os:timestamp()). +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test that the send and recv functions +%% by repeatedly sending a meassage between two entities. +%% The same basic test case is used for three different message sizes; +%% small (8 bytes), medium (8K) and large (8M). +%% The message is sent from A to B and then back again. This is +%% repeated a set number of times (more times the small the message). +%% This is the 'large' message test case, for IPv4. + +traffic_ping_pong_large_send_and_recv_tcp4(suite) -> + []; +traffic_ping_pong_large_send_and_recv_tcp4(doc) -> + []; +traffic_ping_pong_large_send_and_recv_tcp4(_Config) when is_list(_Config) -> + tc_try(traffic_ping_pong_large_send_and_recv_tcp4, + fun() -> + %% not_yet_implemented(), + ?TT(?MINS(5)), + Send = fun(Sock, Data) -> socket:send(Sock, Data) end, + Recv = fun(Sock) -> socket:recv(Sock) end, + InitState = #{domain => inet, + send => Send, % Send function + recv => Recv % Receive function + }, + ok = traffic_ping_pong_large_send_and_receive_tcp(InitState) + end). -format_timestamp({_N1, _N2, _N3} = TS) -> - {_Date, Time} = calendar:now_to_local_time(TS), - %% {YYYY,MM,DD} = Date, - {Hour,Min,Sec} = Time, - %% FormatTS = - %% io_lib:format("~.4w-~.2.0w-~.2.0w ~.2.0w:~.2.0w:~.2.0w.~w", - %% [YYYY, MM, DD, Hour, Min, Sec, N3]), - FormatTS = io_lib:format("~.2.0w:~.2.0w:~.2.0w", [Hour, Min, Sec]), - lists:flatten(FormatTS). - %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test that the send and recv functions +%% by repeatedly sending a meassage between two entities. +%% The same basic test case is used for three different message sizes; +%% small (8 bytes), medium (8K) and large (8M). +%% The message is sent from A to B and then back again. This is +%% repeated a set number of times (more times the small the message). +%% This is the 'large' message test case, for IPv6. + +traffic_ping_pong_large_send_and_recv_tcp6(suite) -> + []; +traffic_ping_pong_large_send_and_recv_tcp6(doc) -> + []; +traffic_ping_pong_large_send_and_recv_tcp6(_Config) when is_list(_Config) -> + tc_try(traffic_ping_pong_large_send_and_recv_tcp6, + fun() -> + not_yet_implemented(), + ?TT(?MINS(5)), + Send = fun(Sock, Data) -> socket:send(Sock, Data) end, + Recv = fun(Sock) -> socket:recv(Sock) end, + InitState = #{domain => inet6, + send => Send, % Send function + recv => Recv % Receive function + }, + ok = traffic_ping_pong_large_send_and_receive_tcp(InitState) + end). -set_tc_name(N) when is_atom(N) -> - set_tc_name(atom_to_list(N)); -set_tc_name(N) when is_list(N) -> - put(tc_name, N). -%% get_tc_name() -> -%% get(tc_name). -tc_begin(TC) -> - set_tc_name(TC), - tc_print("begin ***", - "~n----------------------------------------------------~n", ""). - -tc_end(Result) when is_list(Result) -> - tc_print("done: ~s", [Result], - "", "----------------------------------------------------~n~n"), - ok. +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +-define(SMALL, lists:seq(1, 8)). +-define(MEDIUM, lists:flatten(lists:duplicate(1024, ?SMALL))). +-define(LARGE, lists:flatten(lists:duplicate(1024, ?MEDIUM))). + +traffic_ping_pong_small_send_and_receive_tcp(InitState) -> + Msg = l2b(?SMALL), + Num = 100000, + Fun = fun(_) -> ok end, %% Fun to update the buffers: Not needed here + traffic_ping_pong_send_and_receive_tcp(InitState#{msg => Msg, + num => Num, + buf_init => Fun}). + +traffic_ping_pong_medium_send_and_receive_tcp(InitState) -> + Msg = l2b(?MEDIUM), + Num = 100000, + Fun = fun(_) -> ok end, %% Fun to update the buffers: MAYBE needed here + traffic_ping_pong_send_and_receive_tcp(InitState#{msg => Msg, + num => Num, + buf_init => Fun}). + +traffic_ping_pong_large_send_and_receive_tcp(InitState) -> + Msg = l2b(?LARGE), + Num = 10, + Fun = fun(Sock) -> + ok = socket:setopt(Sock, socket, rcvbuf, 64*1024*1024), + ok = socket:setopt(Sock, socket, sndbuf, 64*1024*1024), + %% ok = socket:setopt(Sock, otp, rcvbuf, 64*1024*1024), + %% ok = socket:setopt(Sock, otp, sndbuf, 64*1024*1024), + ok + end, %% Fun to update the buffers: NEEDED here!!! + traffic_ping_pong_send_and_receive_tcp(InitState#{msg => Msg, + num => Num, + buf_init => Fun}). + +traffic_ping_pong_send_and_receive_tcp(InitState) -> + ServerSeq = + [ + %% *** Wait for start order part *** + #{desc => "await start", + cmd => fun(State) -> + Tester = ?SEV_AWAIT_START(), + {ok, State#{tester => Tester}} + end}, + #{desc => "monitor tester", + cmd => fun(#{tester := Tester} = _State) -> + _MRef = erlang:monitor(process, Tester), + ok + end}, -tc_try(Case, Fun) when is_atom(Case) andalso is_function(Fun, 0) -> - tc_begin(Case), + %% *** Init part *** + #{desc => "which local address", + cmd => fun(#{domain := Domain} = State) -> + LAddr = which_local_addr(Domain), + LSA = #{family => Domain, addr => LAddr}, + {ok, State#{local_sa => LSA}} + end}, + #{desc => "create listen socket", + cmd => fun(#{domain := Domain} = State) -> + case socket:open(Domain, stream, tcp) of + {ok, Sock} -> + {ok, State#{lsock => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "bind to local address", + cmd => fun(#{lsock := LSock, local_sa := LSA} = State) -> + case socket:bind(LSock, LSA) of + {ok, Port} -> + {ok, State#{lport => Port}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "make listen socket", + cmd => fun(#{lsock := LSock}) -> + socket:listen(LSock) + end}, + #{desc => "announce ready (init)", + cmd => fun(#{tester := Tester, local_sa := LSA, lport := Port}) -> + ServerSA = LSA#{port => Port}, + ?SEV_ANNOUNCE_READY(Tester, init, ServerSA), + ok + end}, + + %% The actual test + #{desc => "await continue (accept)", + cmd => fun(#{tester := Tester} = _State) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, accept) + end}, + #{desc => "accept", + cmd => fun(#{lsock := LSock} = State) -> + case socket:accept(LSock) of + {ok, Sock} -> + {ok, State#{csock => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "create handler", + cmd => fun(State) -> + Handler = tpp_tcp_handler_create(), + ?SEV_IPRINT("handler created: ~p", [Handler]), + {ok, State#{handler => Handler}} + end}, + #{desc => "monitor handler", + cmd => fun(#{handler := Handler} = _State) -> + _MRef = erlang:monitor(process, Handler), + ok + end}, + #{desc => "transfer connection socket ownership to handler", + cmd => fun(#{handler := Handler, csock := Sock} = _State) -> + socket:setopt(Sock, otp, controlling_process, Handler) + end}, + #{desc => "start handler", + cmd => fun(#{handler := Handler, + csock := Sock, + buf_init := BufInit, + send := Send, + recv := Recv} = _State) -> + ?SEV_ANNOUNCE_START(Handler, + {Sock, BufInit, Send, Recv}), + ok + end}, + #{desc => "await handler ready (init)", + cmd => fun(#{tester := Tester, + handler := Handler} = State) -> + case ?SEV_AWAIT_READY(Handler, handler, init, + [{tester, Tester}]) of + ok -> + {ok, maps:remove(csock, State)}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (accept)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, accept), + ok + end}, + #{desc => "await continue (recv)", + cmd => fun(#{tester := Tester, + handler := Handler} = _State) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, recv, + [{handler, Handler}]) + end}, + #{desc => "order handler to recv", + cmd => fun(#{handler := Handler} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Handler, recv), + ok + end}, + #{desc => "await handler ready (recv)", + cmd => fun(#{tester := Tester, + handler := Handler} = State) -> + case ?SEV_AWAIT_READY(Handler, handler, recv, + [{tester, Tester}]) of + {ok, Result} -> + %% ?SEV_IPRINT("Result: ~p", [Result]), + {ok, State#{result => Result}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (recv)", + cmd => fun(#{tester := Tester, + result := Result} = State) -> + ?SEV_ANNOUNCE_READY(Tester, recv, Result), + {ok, maps:remove(result, State)} + end}, + + %% Termination + #{desc => "await terminate (from tester)", + cmd => fun(#{tester := Tester} = State) -> + case ?SEV_AWAIT_TERMINATE(Tester, tester) of + ok -> + {ok, maps:remove(tester, State)}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "stop handler", + cmd => fun(#{handler := Handler}) -> + ?SEV_ANNOUNCE_TERMINATE(Handler), + ok + end}, + #{desc => "await handler termination", + cmd => fun(#{handler := Handler} = State) -> + ?SEV_AWAIT_TERMINATION(Handler), + State1 = maps:remove(handler, State), + {ok, State1} + end}, + #{desc => "close listen socket", + cmd => fun(#{lsock := Sock} = State) -> + (catch socket:close(Sock)), + {ok, maps:remove(lsock, State)} + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + ClientSeq = + [ + %% *** Wait for start order part *** + #{desc => "await start", + cmd => fun(State) -> + {Tester, ServerSA} = ?SEV_AWAIT_START(), + {ok, State#{tester => Tester, + server_sa => ServerSA}} + end}, + #{desc => "monitor tester", + cmd => fun(#{tester := Tester} = _State) -> + _MRef = erlang:monitor(process, Tester), + ok + end}, + + %% *** Init part *** + #{desc => "create node", + cmd => fun(#{host := Host} = State) -> + case start_node(Host, client) of + {ok, Node} -> + ?SEV_IPRINT("(remote) client node ~p started", + [Node]), + {ok, State#{node => Node}}; + {error, Reason, _} -> + {error, Reason} + end + end}, + #{desc => "monitor client node", + cmd => fun(#{node := Node} = _State) -> + true = erlang:monitor_node(Node, true), + ok + end}, + #{desc => "create remote client", + cmd => fun(#{node := Node} = State) -> + Pid = tpp_tcp_client_create(Node), + ?SEV_IPRINT("remote client created: ~p", [Pid]), + {ok, State#{rclient => Pid}} + end}, + #{desc => "monitor remote client", + cmd => fun(#{rclient := Pid}) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + #{desc => "order remote client to start", + cmd => fun(#{rclient := RClient, + server_sa := ServerSA, + buf_init := BufInit, + send := Send, + recv := Recv}) -> + ?SEV_ANNOUNCE_START(RClient, + {ServerSA, BufInit, Send, Recv}), + ok + end}, + #{desc => "await remote client ready", + cmd => fun(#{tester := Tester, + rclient := RClient} = _State) -> + ?SEV_AWAIT_READY(RClient, rclient, init, + [{tester, Tester}]) + end}, + #{desc => "announce ready (init)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, init), + ok + end}, + + %% The actual test + #{desc => "await continue (connect)", + cmd => fun(#{tester := Tester, + rclient := RClient} = _State) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, connect, + [{rclient, RClient}]), + ok + end}, + #{desc => "order remote client to continue (connect)", + cmd => fun(#{rclient := RClient}) -> + ?SEV_ANNOUNCE_CONTINUE(RClient, connect), + ok + end}, + #{desc => "await remote client ready (connect)", + cmd => fun(#{tester := Tester, + rclient := RClient} = _State) -> + ?SEV_AWAIT_READY(RClient, rclient, connect, + [{tester, Tester}]) + end}, + #{desc => "announce ready (connect)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, connect), + ok + end}, + #{desc => "await continue (send)", + cmd => fun(#{tester := Tester, + rclient := RClient} = _State) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, + send, + [{rclient, RClient}]) + end}, + #{desc => "order remote client to continue (send)", + cmd => fun(#{rclient := RClient, + msg := Msg, + num := Num} = State) -> + Data = {Msg, Num}, + ?SEV_ANNOUNCE_CONTINUE(RClient, send, Data), + {ok, maps:remove(data, State)} + end}, + #{desc => "await remote client ready (send)", + cmd => fun(#{tester := Tester, + rclient := RClient} = State) -> + case ?SEV_AWAIT_READY(RClient, rclient, send, + [{tester, Tester}]) of + {ok, Result} -> + %% ?SEV_IPRINT("remote client result: " + %% "~n ~p", [Result]), + {ok, State#{result => Result}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (send)", + cmd => fun(#{tester := Tester, result := Result} = State) -> + ?SEV_ANNOUNCE_READY(Tester, send, Result), + {ok, maps:remove(result, State)} + end}, + + %% Termination + #{desc => "await terminate (from tester)", + cmd => fun(#{tester := Tester, + rclient := RClient} = State) -> + case ?SEV_AWAIT_TERMINATE(Tester, tester, + [{rclient, RClient}]) of + ok -> + {ok, maps:remove(tester, State)}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "stop remote client", + cmd => fun(#{rclient := RClient}) -> + ?SEV_ANNOUNCE_TERMINATE(RClient), + ok + end}, + #{desc => "await remote client termination", + cmd => fun(#{rclient := RClient} = State) -> + ?SEV_AWAIT_TERMINATION(RClient), + State1 = maps:remove(rclient, State), + {ok, State1} + end}, + #{desc => "stop client node", + cmd => fun(#{node := Node} = _State) -> + stop_node(Node) + end}, + #{desc => "await client node termination", + cmd => fun(#{node := Node} = State) -> + receive + {nodedown, Node} -> + {ok, maps:remove(node, State)} + end + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + TesterSeq = + [ + %% *** Init part *** + #{desc => "monitor server", + cmd => fun(#{server := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + #{desc => "monitor client", + cmd => fun(#{client := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + + %% Start the server + #{desc => "order server start", + cmd => fun(#{server := Pid} = _State) -> + ?SEV_ANNOUNCE_START(Pid), + ok + end}, + #{desc => "await server ready (init)", + cmd => fun(#{server := Pid} = State) -> + {ok, ServerSA} = ?SEV_AWAIT_READY(Pid, server, init), + {ok, State#{server_sa => ServerSA}} + end}, + + %% Start the client + #{desc => "order client start", + cmd => fun(#{client := Pid, + server_sa := ServerSA} = _State) -> + ?SEV_ANNOUNCE_START(Pid, ServerSA), + ok + end}, + #{desc => "await client ready (init)", + cmd => fun(#{client := Pid} = _State) -> + ok = ?SEV_AWAIT_READY(Pid, client, init) + end}, + + %% The actual test + #{desc => "order server continue (accept)", + cmd => fun(#{server := Pid} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Pid, accept), + ok + end}, + ?SEV_SLEEP(?SECS(1)), + #{desc => "order client continue (connect)", + cmd => fun(#{client := Pid} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Pid, connect), + ok + end}, + #{desc => "await server ready (accept)", + cmd => fun(#{server := Server, + client := Client} = _State) -> + ?SEV_AWAIT_READY(Server, server, accept, + [{client, Client}]), + ok + end}, + #{desc => "await client ready (connect)", + cmd => fun(#{server := Server, + client := Client} = _State) -> + ?SEV_AWAIT_READY(Client, client, connect, + [{server, Server}]) + end}, + #{desc => "order server continue (recv)", + cmd => fun(#{server := Pid} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Pid, recv), + ok + end}, + ?SEV_SLEEP(?SECS(1)), + #{desc => "order client continue (send)", + cmd => fun(#{client := Pid} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Pid, send), + ok + end}, + #{desc => "await client ready (send)", + cmd => fun(#{server := Server, + client := Client} = State) -> + case ?SEV_AWAIT_READY(Client, client, send, + [{server, Server}]) of + {ok, {_, _, _, _} = Result} -> + ?SEV_IPRINT("client result: " + "~n ~p", [Result]), + {ok, State#{client_result => Result}}; + {ok, BadResult} -> + ?SEV_EPRINT("client result: " + "~n ~p", [BadResult]), + {error, {invalid_client_result, BadResult}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "await server ready (recv)", + cmd => fun(#{server := Server, + client := Client, + num := Num} = State) -> + case ?SEV_AWAIT_READY(Server, server, recv, + [{client, Client}]) of + {ok, {Num, _, _, _, _} = Result} -> + ?SEV_IPRINT("server result: " + "~n ~p", [Result]), + Result2 = erlang:delete_element(1, Result), + {ok, State#{server_result => Result2}}; + {ok, BadResult} -> + ?SEV_EPRINT("bad sever result: " + "~n ~p", [BadResult]), + {error, {invalid_server_result, BadResult}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "present result", + cmd => fun(#{server_result := SRes, + client_result := CRes, + num := Num} = State) -> + {SSent, SReceived, SStart, SStop} = SRes, + {CSent, CReceived, CStart, CStop} = CRes, + STime = tdiff(SStart, SStop), + CTime = tdiff(CStart, CStop), + ?SEV_IPRINT("Results: " + "~n Server: ~w msec" + "~n ~w messages/msec exchanged" + "~n ~w bytes/msec sent" + "~n ~w bytes/msec received" + "~n Client: ~w msec" + "~n ~w messages/msec exchanged" + "~n ~w bytes/msec sent" + "~n ~w bytes/msec received", + [STime, + Num div STime, + SSent div STime, + SReceived div STime, + CTime, + Num div CTime, + CSent div CTime, + CReceived div CTime]), + State1 = maps:remove(server_result, State), + State2 = maps:remove(client_result, State), + {ok, State2} + end}, + + %% Terminations + #{desc => "order client to terminate", + cmd => fun(#{client := Pid} = _State) -> + ?SEV_ANNOUNCE_TERMINATE(Pid), + ok + end}, + #{desc => "await client termination", + cmd => fun(#{client := Pid} = State) -> + case ?SEV_AWAIT_TERMINATION(Pid) of + ok -> + State1 = maps:remove(client, State), + {ok, State1}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "order server to terminate", + cmd => fun(#{server := Pid} = _State) -> + ?SEV_ANNOUNCE_TERMINATE(Pid), + ok + end}, + #{desc => "await server termination", + cmd => fun(#{server := Pid} = State) -> + case ?SEV_AWAIT_TERMINATION(Pid) of + ok -> + State1 = maps:remove(server, State), + {ok, State1}; + {error, _} = ERROR -> + ERROR + end + end}, + + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + i("start server evaluator"), + ServerInitState = #{domain => maps:get(domain, InitState), + recv => maps:get(recv, InitState), + send => maps:get(send, InitState), + buf_init => maps:get(buf_init, InitState)}, + Server = ?SEV_START("server", ServerSeq, ServerInitState), + + i("start client evaluator(s)"), + ClientInitState = InitState#{host => local_host()}, + Client = ?SEV_START("client", ClientSeq, ClientInitState), + + i("start 'tester' evaluator"), + TesterInitState = #{server => Server#ev.pid, + client => Client#ev.pid, + num => maps:get(num, InitState)}, + Tester = ?SEV_START("tester", TesterSeq, TesterInitState), + + i("await evaluator"), + ok = ?SEV_AWAIT_FINISH([Server, Client, Tester]). + + +tpp_tcp_handler_create() -> + Self = self(), + erlang:spawn(fun() -> tpp_tcp_handler(Self) end). + +tpp_tcp_handler(Parent) -> + tpp_tcp_handler_init(Parent), + {Sock, BufInit, Send, Recv} = tpp_tcp_handler_await_start(Parent), + tpp_tcp_handler_announce_ready(Parent, init), + tpp_tcp_handler_await_continue(Parent, recv), + Result = tpp_tcp_handler_msg_exchange(Sock, BufInit, Send, Recv), + tpp_tcp_handler_announce_ready(Parent, recv, Result), + Reason = tpp_tcp_handler_await_terminate(Parent), + exit(Reason). + +tpp_tcp_handler_init(Parent) -> + put(sname, "handler"), + ?SEV_IPRINT("init"), + _MRef = erlang:monitor(process, Parent), + ok. + +tpp_tcp_handler_await_start(Parent) -> + ?SEV_IPRINT("await start"), + ?SEV_AWAIT_START(Parent). + +tpp_tcp_handler_announce_ready(Parent, Slogan) -> + ?SEV_IPRINT("announce ready (~p)", [Slogan]), + ?SEV_ANNOUNCE_READY(Parent, Slogan). +tpp_tcp_handler_announce_ready(Parent, Slogan, Extra) -> + ?SEV_IPRINT("announce ready (~p)", [Slogan]), + ?SEV_ANNOUNCE_READY(Parent, Slogan, Extra). + +tpp_tcp_handler_await_continue(Parent, Slogan) -> + ?SEV_IPRINT("await continue (~p)", [Slogan]), + case ?SEV_AWAIT_CONTINUE(Parent, parent, Slogan) of + ok -> + %% ?SEV_IPRINT("continue (~p): ok", [Slogan]), + ok; + {error, Reason} -> + ?SEV_EPRINT("continue (~p): error" + "~n ~p", [Slogan, Reason]), + exit({continue, Slogan, Reason}) + end. + +tpp_tcp_handler_await_terminate(Parent) -> + ?SEV_IPRINT("await terminate"), + case ?SEV_AWAIT_TERMINATE(Parent, parent) of + ok -> + ok; + {error, Reason} -> + Reason + end. + +tpp_tcp_handler_msg_exchange(Sock, BufInit, Send, Recv) -> + ok = BufInit(Sock), + socket:setopt(Sock, otp, debug, true), + tpp_tcp_handler_msg_exchange_loop(Sock, Send, Recv, 0, 0, 0, undefined). + +tpp_tcp_handler_msg_exchange_loop(Sock, Send, Recv, N, Sent, Received, Start) -> + %% if (N =:= 1000) -> socket:setopt(Sock, otp, debug, true); true -> ok end, + ?SEV_IPRINT("[~w] try receive", [N]), + case Recv(Sock) of + {ok, Msg} -> + NewStart = if (Start =:= undefined) -> ?LIB:timestamp(); + true -> Start end, + ?SEV_IPRINT("[~w] received - now try send", [N]), + case Send(Sock, Msg) of + ok -> + tpp_tcp_handler_msg_exchange_loop(Sock, Send, Recv, + N+1, + Sent+size(Msg), + Received+size(Msg), + NewStart); + {error, SReason} -> + ?SEV_EPRINT("send (~w): ~p", [N, SReason]), + exit({send, SReason, N}) + end; + %% {error, timeout} -> + %% ?SEV_IPRINT("timeout(~w) - try again", [N]), + %% case Send(Sock, list_to_binary("ping")) of + %% ok -> + %% exit({'ping-send', ok, N}); + %% {error, Reason} -> + %% exit({'ping-send', Reason, N}) + %% end; + {error, closed} -> + ?SEV_IPRINT("closed - we are done: ~w, ~w, ~w", [N, Sent, Received]), + Stop = ?LIB:timestamp(), + {N, Sent, Received, Start, Stop}; + {error, RReason} -> + ?SEV_EPRINT("recv (~w): ~p", [N, RReason]), + exit({recv, RReason, N}) + end. + +%% The (remote) client process + +tpp_tcp_client_create(Node) -> + Self = self(), + GL = group_leader(), + Fun = fun() -> tpp_tcp_client(Self, GL) end, + erlang:spawn(Node, Fun). + +tpp_tcp_client(Parent, GL) -> + tpp_tcp_client_init(Parent, GL), + {ServerSA, BufInit, Send, Recv} = tpp_tcp_client_await_start(Parent), + Domain = maps:get(family, ServerSA), + Sock = tpp_tcp_client_sock_open(Domain), + tpp_tcp_client_sock_bind(Sock, Domain), + tpp_tcp_client_announce_ready(Parent, init), + tpp_tcp_client_await_continue(Parent, connect), + tpp_tcp_client_sock_connect(Sock, ServerSA), + tpp_tcp_client_announce_ready(Parent, connect), + {InitMsg, Num} = tpp_tcp_client_await_continue(Parent, send), + Result = tpp_tcp_client_msg_exchange(Sock, BufInit, Send, Recv, InitMsg, Num), + tpp_tcp_client_announce_ready(Parent, send, Result), + Reason = tpp_tcp_client_await_terminate(Parent), + tpp_tcp_client_sock_close(Sock), + exit(Reason). + +tpp_tcp_client_init(Parent, GL) -> + put(sname, "rclient"), + ?SEV_IPRINT("init"), + _MRef = erlang:monitor(process, Parent), + group_leader(self(), GL), + ok. + +tpp_tcp_client_await_start(Parent) -> + ?SEV_IPRINT("await start"), + ?SEV_AWAIT_START(Parent). + +tpp_tcp_client_announce_ready(Parent, Slogan) -> + ?SEV_IPRINT("announce ready (~p)", [Slogan]), + ?SEV_ANNOUNCE_READY(Parent, Slogan). +tpp_tcp_client_announce_ready(Parent, Slogan, Extra) -> + ?SEV_IPRINT("announce ready (~p): ~p", [Slogan, Extra]), + ?SEV_ANNOUNCE_READY(Parent, Slogan, Extra). + +tpp_tcp_client_await_continue(Parent, Slogan) -> + ?SEV_IPRINT("await continue (~p)", [Slogan]), + case ?SEV_AWAIT_CONTINUE(Parent, parent, Slogan) of + ok -> + %% ?SEV_IPRINT("continue (~p): ok", [Slogan]), + ok; + {ok, Data} -> + %% ?SEV_IPRINT("continue (~p): ok with data", [Slogan]), + Data; + {error, Reason} -> + ?SEV_EPRINT("continue (~p): error" + "~n ~p", [Slogan, Reason]), + exit({continue, Slogan, Reason}) + end. + +tpp_tcp_client_await_terminate(Parent) -> + ?SEV_IPRINT("await terminate"), + case ?SEV_AWAIT_TERMINATE(Parent, parent) of + ok -> + ok; + {error, Reason} -> + Reason + end. + +tpp_tcp_client_msg_exchange(Sock, BufInit, Send, Recv, InitMsg, Num) -> + ok = BufInit(Sock), + Start = ?LIB:timestamp(), + tpp_tcp_client_msg_exchange_loop(Sock, Send, Recv, InitMsg, + Num, 0, 0, 0, Start). + +tpp_tcp_client_msg_exchange_loop(Sock, _Send, _Recv, _Msg, + Num, Num, Sent, Received, + Start) -> + Stop = ?LIB:timestamp(), + case socket:close(Sock) of + ok -> + {Sent, Received, Start, Stop}; + {error, Reason} -> + exit({failed_closing, Reason}) + end; +tpp_tcp_client_msg_exchange_loop(Sock, Send, Recv, Msg, + Num, N, Sent, Received, Start) -> + d("[~w,~w] try send", [Num,N]), + case Send(Sock, Msg) of + ok -> + d("[~w,~w] sent - no try recv", [Num,N]), + case Recv(Sock) of + {ok, NewMsg} -> + tpp_tcp_client_msg_exchange_loop(Sock, Send, Recv, + NewMsg, Num, N+1, + Sent+size(Msg), + Received+size(NewMsg), + Start); + {error, RReason} -> + ?SEV_EPRINT("recv (~w of ~w): ~p", [N, Num, RReason]), + exit({recv, RReason, N}) + end; + {error, SReason} -> + ?SEV_EPRINT("send (~w of ~w): ~p", [N, Num, SReason]), + exit({send, SReason, N}) + end. + +tpp_tcp_client_sock_open(Domain) -> + case socket:open(Domain, stream, tcp) of + {ok, Sock} -> + Sock; + {error, Reason} -> + exit({open_failed, Reason}) + end. + +tpp_tcp_client_sock_bind(Sock, Domain) -> + LAddr = which_local_addr(Domain), + LSA = #{family => Domain, + addr => LAddr}, + case socket:bind(Sock, LSA) of + {ok, _} -> + ok; + {error, Reason} -> + exit({bind, Reason}) + end. + +tpp_tcp_client_sock_connect(Sock, ServerSA) -> + case socket:connect(Sock, ServerSA) of + ok -> + ok; + {error, Reason} -> + exit({connect, Reason}) + end. + +tpp_tcp_client_sock_close(Sock) -> + case socket:close(Sock) of + ok -> + ok; + {error, Reason} -> + exit({close, Reason}) + end. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% This gets the local address (not 127.0...) +%% We should really implement this using the (new) net module, +%% but until that gets the necessary functionality... +which_local_addr(Domain) -> + case inet:getifaddrs() of + {ok, IFL} -> + which_addr(Domain, IFL); + {error, Reason} -> + ?FAIL({inet, getifaddrs, Reason}) + end. + +which_addr(_Domain, []) -> + ?FAIL(no_address); +which_addr(Domain, [{Name, IFO}|_IFL]) when (Name =/= "lo") -> + which_addr2(Domain, IFO); +which_addr(Domain, [_|IFL]) -> + which_addr(Domain, IFL). + +which_addr2(_Domain, []) -> + ?FAIL(no_address); +which_addr2(inet = _Domain, [{addr, Addr}|_IFO]) when (size(Addr) =:= 4) -> + Addr; +which_addr2(inet6 = _Domain, [{addr, Addr}|_IFO]) when (size(Addr) =:= 8) -> + Addr; +which_addr2(Domain, [_|IFO]) -> + which_addr2(Domain, IFO). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +start_node(Host, NodeName) -> + UniqueNodeName = f("~w_~w", [NodeName, erlang:system_time(millisecond)]), + case do_start_node(Host, UniqueNodeName) of + {ok, _} = OK -> + OK; + {error, Reason, _} -> + {error, Reason} + end. + +do_start_node(Host, NodeName) when is_list(NodeName) -> + do_start_node(Host, list_to_atom(NodeName)); +do_start_node(Host, NodeName) when is_atom(NodeName) -> + Dir = filename:dirname(code:which(?MODULE)), + Flags = "-pa " ++ Dir, + Opts = [{monitor_master, true}, {erl_flags, Flags}], + ct_slave:start(Host, NodeName, Opts). + + +stop_node(Node) -> + case ct_slave:stop(Node) of + {ok, _} -> + ok; + {error, _} = ERROR -> + ERROR + end. + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +sock_open(Domain, Type, Proto) -> + try socket:open(Domain, Type, Proto) of + {ok, Socket} -> + Socket; + {error, Reason} -> + ?FAIL({open, Reason}) + catch + C:E:S -> + ?FAIL({open, C, E, S}) + end. + + +sock_bind(Sock, SockAddr) -> + try socket:bind(Sock, SockAddr) of + {ok, Port} -> + Port; + {error, Reason} -> + i("sock_bind -> error: ~p", [Reason]), + ?FAIL({bind, Reason}) + catch + C:E:S -> + i("sock_bind -> failed: ~p, ~p, ~p", [C, E, S]), + ?FAIL({bind, C, E, S}) + end. + +sock_connect(Sock, SockAddr) -> + try socket:connect(Sock, SockAddr) of + ok -> + ok; + {error, Reason} -> + ?FAIL({connect, Reason}) + catch + C:E:S -> + ?FAIL({connect, C, E, S}) + end. + +sock_sockname(Sock) -> + try socket:sockname(Sock) of + {ok, SockAddr} -> + SockAddr; + {error, Reason} -> + ?FAIL({sockname, Reason}) + catch + C:E:S -> + ?FAIL({sockname, C, E, S}) + end. + + +%% sock_listen(Sock) -> +%% sock_listen2(fun() -> socket:listen(Sock) end). + +%% sock_listen(Sock, BackLog) -> +%% sock_listen2(fun() -> socket:listen(Sock, BackLog) end). + +%% sock_listen2(Listen) -> +%% try Listen() of +%% ok -> +%% ok; +%% {error, Reason} -> +%% ?FAIL({listen, Reason}) +%% catch +%% C:E:S -> +%% ?FAIL({listen, C, E, S}) +%% end. + + +%% sock_accept(LSock) -> +%% try socket:accept(LSock) of +%% {ok, Sock} -> +%% Sock; +%% {error, Reason} -> +%% i("sock_accept -> error: ~p", [Reason]), +%% ?FAIL({accept, Reason}) +%% catch +%% C:E:S -> +%% i("sock_accept -> failed: ~p, ~p, ~p", [C, E, S]), +%% ?FAIL({accept, C, E, S}) +%% end. + + +sock_close(Sock) -> + try socket:close(Sock) of + ok -> + ok; + {error, Reason} -> + i("sock_close -> error: ~p", [Reason]), + ?FAIL({close, Reason}) + catch + C:E:S -> + i("sock_close -> failed: ~p, ~p, ~p", [C, E, S]), + ?FAIL({close, C, E, S}) + end. + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +not_yet_implemented() -> + skip("not yet implemented"). + +skip(Reason) -> + throw({skip, Reason}). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +t() -> + os:timestamp(). + + +tdiff({A1, B1, C1} = _T1x, {A2, B2, C2} = _T2x) -> + T1 = A1*1000000000+B1*1000+(C1 div 1000), + T2 = A2*1000000000+B2*1000+(C2 div 1000), + T2 - T1. + + +formated_timestamp() -> + format_timestamp(os:timestamp()). + +format_timestamp({_N1, _N2, _N3} = TS) -> + {_Date, Time} = calendar:now_to_local_time(TS), + %% {YYYY,MM,DD} = Date, + {Hour,Min,Sec} = Time, + %% FormatTS = + %% io_lib:format("~.4w-~.2.0w-~.2.0w ~.2.0w:~.2.0w:~.2.0w.~w", + %% [YYYY, MM, DD, Hour, Min, Sec, N3]), + FormatTS = io_lib:format("~.2.0w:~.2.0w:~.2.0w", [Hour, Min, Sec]), + lists:flatten(FormatTS). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +set_tc_name(N) when is_atom(N) -> + set_tc_name(atom_to_list(N)); +set_tc_name(N) when is_list(N) -> + put(tc_name, N). + +%% get_tc_name() -> +%% get(tc_name). + +tc_begin(TC) -> + set_tc_name(TC), + tc_print("begin ***", + "~n----------------------------------------------------~n", ""). + +tc_end(Result) when is_list(Result) -> + tc_print("done: ~s", [Result], + "", "----------------------------------------------------~n~n"), + ok. + + +tc_try(Case, Fun) when is_atom(Case) andalso is_function(Fun, 0) -> + tc_begin(Case), try begin Fun(), @@ -6923,6 +7918,9 @@ tc_which_name() -> l2a(S) when is_list(S) -> list_to_atom(S). +l2b(L) when is_list(L) -> + list_to_binary(L). + b2l(B) when is_binary(B) -> binary_to_list(B). @@ -6953,6 +7951,22 @@ f(F, A) -> %% i(Before ++ FStr ++ After, []). +d(F, A) -> + d(get(dbg_fd), F, A). + +d(undefined, F, A) -> + [NodeNameStr|_] = string:split(atom_to_list(node()), [$@]), + DbgFileName = f("~s-dbg.txt", [NodeNameStr]), + case file:open(DbgFileName, [write]) of + {ok, FD} -> + put(dbg_fd, FD), + d(FD, F, A); + {error, Reason} -> + exit({failed_open_dbg_file, Reason}) + end; +d(FD, F, A) -> + io:format(FD, "~s~n", [f("[~s] " ++ F, [formated_timestamp()|A])]). + i(F) -> i(F, []). -- cgit v1.2.3 From d0df643ad994bbc609c52f83b814fc79624af501 Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Fri, 2 Nov 2018 14:14:48 +0100 Subject: [socket-nif] Inherit buffer sizes when accepting An "accepted" socket will inherit the parent (listen) socket's buffer sizes (rBufSz, rCtrlSz and wCtrlSz). OTP-14831 --- erts/emulator/nifs/common/socket_nif.c | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/erts/emulator/nifs/common/socket_nif.c b/erts/emulator/nifs/common/socket_nif.c index 389d43ee42..82bf8305fc 100644 --- a/erts/emulator/nifs/common/socket_nif.c +++ b/erts/emulator/nifs/common/socket_nif.c @@ -4919,6 +4919,9 @@ ERL_NIF_TERM naccept_listening(ErlNifEnv* env, accDescP->domain = descP->domain; accDescP->type = descP->type; accDescP->protocol = descP->protocol; + accDescP->rBufSz = descP->rBufSz; // Inherit buffer size + accDescP->rCtrlSz = descP->rCtrlSz; // Inherit buffer siez + accDescP->wCtrlSz = descP->wCtrlSz; // Inherit buffer size accRef = enif_make_resource(env, accDescP); enif_release_resource(accDescP); // We should really store a reference ... @@ -9549,7 +9552,17 @@ ERL_NIF_TERM nsetopt_int_opt(ErlNifEnv* env, int val; if (GET_INT(env, eVal, &val)) { - int res = socket_setopt(descP->sock, level, opt, &val, sizeof(val)); + int res; + + /* + SSDBG( descP, + ("SOCKET", "nsetopt_int_opt -> set option" + "\r\n opt: %d" + "\r\n val: %d" + "\r\n", opt, val) ); + */ + + res = socket_setopt(descP->sock, level, opt, &val, sizeof(val)); if (res != 0) result = esock_make_error_errno(env, sock_errno()); -- cgit v1.2.3 From c6767f0a6dc66971df4425c216024c47993a310b Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Fri, 2 Nov 2018 14:17:09 +0100 Subject: [socket-nif|test] Biffer init and message sizes in ping-pong case The ping-pong test case(s) now initiates the socket buffers before they are connected (server: before listen is called on the listen socket and client: before connect is called). Also, we now include a length indicator in the messages, so that we know how much to read. OTP-14831 --- erts/emulator/test/socket_SUITE.erl | 213 +++++++++++++++++++++++++----------- 1 file changed, 150 insertions(+), 63 deletions(-) diff --git a/erts/emulator/test/socket_SUITE.erl b/erts/emulator/test/socket_SUITE.erl index 90e9407f6a..c2b8a8349f 100644 --- a/erts/emulator/test/socket_SUITE.erl +++ b/erts/emulator/test/socket_SUITE.erl @@ -6712,7 +6712,7 @@ traffic_ping_pong_small_send_and_recv_tcp4(_Config) when is_list(_Config) -> ?TT(?SECS(15)), Send = fun(Sock, Data) -> socket:send(Sock, Data) end, %% Recv = fun(Sock) -> socket:recv(Sock, 0, 5000) end, - Recv = fun(Sock) -> socket:recv(Sock) end, + Recv = fun(Sock, Sz) -> socket:recv(Sock, Sz) end, InitState = #{domain => inet, send => Send, % Send function recv => Recv % Receive function @@ -6740,7 +6740,7 @@ traffic_ping_pong_small_send_and_recv_tcp6(_Config) when is_list(_Config) -> not_yet_implemented(), ?TT(?SECS(30)), Send = fun(Sock, Data) -> socket:send(Sock, Data) end, - Recv = fun(Sock) -> socket:recv(Sock) end, + Recv = fun(Sock, Sz) -> socket:recv(Sock, Sz) end, InitState = #{domain => inet6, send => Send, % Send function recv => Recv % Receive function @@ -6768,7 +6768,7 @@ traffic_ping_pong_medium_send_and_recv_tcp4(_Config) when is_list(_Config) -> %% not_yet_implemented(), ?TT(?SECS(30)), Send = fun(Sock, Data) -> socket:send(Sock, Data) end, - Recv = fun(Sock) -> socket:recv(Sock) end, + Recv = fun(Sock, Sz) -> socket:recv(Sock, Sz) end, InitState = #{domain => inet, send => Send, % Send function recv => Recv % Receive function @@ -6796,7 +6796,7 @@ traffic_ping_pong_medium_send_and_recv_tcp6(_Config) when is_list(_Config) -> not_yet_implemented(), ?TT(?SECS(30)), Send = fun(Sock, Data) -> socket:send(Sock, Data) end, - Recv = fun(Sock) -> socket:recv(Sock) end, + Recv = fun(Sock, Sz) -> socket:recv(Sock, Sz) end, InitState = #{domain => inet6, send => Send, % Send function recv => Recv % Receive function @@ -6825,7 +6825,7 @@ traffic_ping_pong_large_send_and_recv_tcp4(_Config) when is_list(_Config) -> %% not_yet_implemented(), ?TT(?MINS(5)), Send = fun(Sock, Data) -> socket:send(Sock, Data) end, - Recv = fun(Sock) -> socket:recv(Sock) end, + Recv = fun(Sock, Sz) -> socket:recv(Sock, Sz) end, InitState = #{domain => inet, send => Send, % Send function recv => Recv % Receive function @@ -6853,7 +6853,7 @@ traffic_ping_pong_large_send_and_recv_tcp6(_Config) when is_list(_Config) -> not_yet_implemented(), ?TT(?MINS(5)), Send = fun(Sock, Data) -> socket:send(Sock, Data) end, - Recv = fun(Sock) -> socket:recv(Sock) end, + Recv = fun(Sock, Sz) -> socket:recv(Sock, Sz) end, InitState = #{domain => inet6, send => Send, % Send function recv => Recv % Receive function @@ -6887,12 +6887,31 @@ traffic_ping_pong_medium_send_and_receive_tcp(InitState) -> traffic_ping_pong_large_send_and_receive_tcp(InitState) -> Msg = l2b(?LARGE), - Num = 10, + Num = 1000, Fun = fun(Sock) -> - ok = socket:setopt(Sock, socket, rcvbuf, 64*1024*1024), - ok = socket:setopt(Sock, socket, sndbuf, 64*1024*1024), - %% ok = socket:setopt(Sock, otp, rcvbuf, 64*1024*1024), - %% ok = socket:setopt(Sock, otp, sndbuf, 64*1024*1024), + %% ?SEV_IPRINT("Socket buffers (before): " + %% "~n Rcv: ~p" + %% "~n Snd: ~p", + %% [socket:getopt(Sock, socket, rcvbuf), + %% socket:getopt(Sock, socket, sndbuf)]), + {ok, RcvSz} = socket:getopt(Sock, socket, rcvbuf), + if (RcvSz < size(Msg)) -> + ok = socket:setopt(Sock, socket, rcvbuf, 1024+size(Msg)); + true -> + ok + end, + {ok, SndSz} = socket:getopt(Sock, socket, sndbuf), + if (SndSz < size(Msg)) -> + ok = socket:setopt(Sock, socket, sndbuf, 1024+size(Msg)); + true -> + ok + end, + ok = socket:setopt(Sock, otp, rcvbuf, 8*1024), + %% ?SEV_IPRINT("Socket buffers (after): " + %% "~n Rcv: ~p" + %% "~n Snd: ~p", + %% [socket:getopt(Sock, socket, rcvbuf), + %% socket:getopt(Sock, socket, sndbuf)]), ok end, %% Fun to update the buffers: NEEDED here!!! traffic_ping_pong_send_and_receive_tcp(InitState#{msg => Msg, @@ -6939,6 +6958,10 @@ traffic_ping_pong_send_and_receive_tcp(InitState) -> ERROR end end}, + #{desc => "maybe init buffers", + cmd => fun(#{lsock := LSock, buf_init := BufInit} = State) -> + BufInit(LSock) + end}, #{desc => "make listen socket", cmd => fun(#{lsock := LSock}) -> socket:listen(LSock) @@ -6982,11 +7005,9 @@ traffic_ping_pong_send_and_receive_tcp(InitState) -> #{desc => "start handler", cmd => fun(#{handler := Handler, csock := Sock, - buf_init := BufInit, send := Send, recv := Recv} = _State) -> - ?SEV_ANNOUNCE_START(Handler, - {Sock, BufInit, Send, Recv}), + ?SEV_ANNOUNCE_START(Handler, {Sock, Send, Recv}), ok end}, #{desc => "await handler ready (init)", @@ -7346,25 +7367,35 @@ traffic_ping_pong_send_and_receive_tcp(InitState) -> {CSent, CReceived, CStart, CStop} = CRes, STime = tdiff(SStart, SStop), CTime = tdiff(CStart, CStop), - ?SEV_IPRINT("Results: " + %% Note that the sizes we are counting is only + %% the "data" part of the messages. There is also + %% fixed header for each message, which of cource + %% is small for the large messages, but comparatively + %% big for the small messages! + ?SEV_IPRINT("Results: ~w messages exchanged" "~n Server: ~w msec" - "~n ~w messages/msec exchanged" + "~n ~.2f msec/message (roundtrip)" + "~n ~.2f messages/msec (roundtrip)" "~n ~w bytes/msec sent" "~n ~w bytes/msec received" "~n Client: ~w msec" - "~n ~w messages/msec exchanged" + "~n ~.2f msec/message (roundtrip)" + "~n ~.2f messages/msec (roundtrip)" "~n ~w bytes/msec sent" "~n ~w bytes/msec received", - [STime, - Num div STime, + [Num, + STime, + STime / Num, + Num / STime, SSent div STime, SReceived div STime, CTime, - Num div CTime, + CTime / Num, + Num / CTime, CSent div CTime, CReceived div CTime]), State1 = maps:remove(server_result, State), - State2 = maps:remove(client_result, State), + State2 = maps:remove(client_result, State1), {ok, State2} end}, @@ -7432,10 +7463,10 @@ tpp_tcp_handler_create() -> tpp_tcp_handler(Parent) -> tpp_tcp_handler_init(Parent), - {Sock, BufInit, Send, Recv} = tpp_tcp_handler_await_start(Parent), + {Sock, Send, Recv} = tpp_tcp_handler_await_start(Parent), tpp_tcp_handler_announce_ready(Parent, init), tpp_tcp_handler_await_continue(Parent, recv), - Result = tpp_tcp_handler_msg_exchange(Sock, BufInit, Send, Recv), + Result = tpp_tcp_handler_msg_exchange(Sock, Send, Recv), tpp_tcp_handler_announce_ready(Parent, recv, Result), Reason = tpp_tcp_handler_await_terminate(Parent), exit(Reason). @@ -7478,25 +7509,24 @@ tpp_tcp_handler_await_terminate(Parent) -> Reason end. -tpp_tcp_handler_msg_exchange(Sock, BufInit, Send, Recv) -> - ok = BufInit(Sock), - socket:setopt(Sock, otp, debug, true), +tpp_tcp_handler_msg_exchange(Sock, Send, Recv) -> + %% socket:setopt(Sock, otp, debug, true), tpp_tcp_handler_msg_exchange_loop(Sock, Send, Recv, 0, 0, 0, undefined). tpp_tcp_handler_msg_exchange_loop(Sock, Send, Recv, N, Sent, Received, Start) -> %% if (N =:= 1000) -> socket:setopt(Sock, otp, debug, true); true -> ok end, - ?SEV_IPRINT("[~w] try receive", [N]), - case Recv(Sock) of - {ok, Msg} -> + %% ?SEV_IPRINT("[~w] try receive", [N]), + case tpp_tcp_recv_req(Sock, Recv) of + {ok, Msg, RecvSz} -> NewStart = if (Start =:= undefined) -> ?LIB:timestamp(); true -> Start end, - ?SEV_IPRINT("[~w] received - now try send", [N]), - case Send(Sock, Msg) of - ok -> + %% ?SEV_IPRINT("[~w] received - now try send", [N]), + case tpp_tcp_send_rep(Sock, Send, Msg) of + {ok, SendSz} -> tpp_tcp_handler_msg_exchange_loop(Sock, Send, Recv, N+1, - Sent+size(Msg), - Received+size(Msg), + Sent+SendSz, + Received+RecvSz, NewStart); {error, SReason} -> ?SEV_EPRINT("send (~w): ~p", [N, SReason]), @@ -7531,14 +7561,14 @@ tpp_tcp_client(Parent, GL) -> tpp_tcp_client_init(Parent, GL), {ServerSA, BufInit, Send, Recv} = tpp_tcp_client_await_start(Parent), Domain = maps:get(family, ServerSA), - Sock = tpp_tcp_client_sock_open(Domain), + Sock = tpp_tcp_client_sock_open(Domain, BufInit), tpp_tcp_client_sock_bind(Sock, Domain), tpp_tcp_client_announce_ready(Parent, init), tpp_tcp_client_await_continue(Parent, connect), tpp_tcp_client_sock_connect(Sock, ServerSA), tpp_tcp_client_announce_ready(Parent, connect), {InitMsg, Num} = tpp_tcp_client_await_continue(Parent, send), - Result = tpp_tcp_client_msg_exchange(Sock, BufInit, Send, Recv, InitMsg, Num), + Result = tpp_tcp_client_msg_exchange(Sock, Send, Recv, InitMsg, Num), tpp_tcp_client_announce_ready(Parent, send, Result), Reason = tpp_tcp_client_await_terminate(Parent), tpp_tcp_client_sock_close(Sock), @@ -7586,8 +7616,7 @@ tpp_tcp_client_await_terminate(Parent) -> Reason end. -tpp_tcp_client_msg_exchange(Sock, BufInit, Send, Recv, InitMsg, Num) -> - ok = BufInit(Sock), +tpp_tcp_client_msg_exchange(Sock, Send, Recv, InitMsg, Num) -> Start = ?LIB:timestamp(), tpp_tcp_client_msg_exchange_loop(Sock, Send, Recv, InitMsg, Num, 0, 0, 0, Start). @@ -7602,18 +7631,18 @@ tpp_tcp_client_msg_exchange_loop(Sock, _Send, _Recv, _Msg, {error, Reason} -> exit({failed_closing, Reason}) end; -tpp_tcp_client_msg_exchange_loop(Sock, Send, Recv, Msg, +tpp_tcp_client_msg_exchange_loop(Sock, Send, Recv, Data, Num, N, Sent, Received, Start) -> - d("[~w,~w] try send", [Num,N]), - case Send(Sock, Msg) of - ok -> - d("[~w,~w] sent - no try recv", [Num,N]), - case Recv(Sock) of - {ok, NewMsg} -> + %% d("[~w,~w] try send", [Num,N]), + case tpp_tcp_send_req(Sock, Send, Data) of + {ok, SendSz} -> + %% d("[~w,~w] sent - no try recv", [Num,N]), + case tpp_tcp_recv_rep(Sock, Recv) of + {ok, NewData, RecvSz} -> tpp_tcp_client_msg_exchange_loop(Sock, Send, Recv, - NewMsg, Num, N+1, - Sent+size(Msg), - Received+size(NewMsg), + NewData, Num, N+1, + Sent+SendSz, + Received+RecvSz, Start); {error, RReason} -> ?SEV_EPRINT("recv (~w of ~w): ~p", [N, Num, RReason]), @@ -7624,9 +7653,10 @@ tpp_tcp_client_msg_exchange_loop(Sock, Send, Recv, Msg, exit({send, SReason, N}) end. -tpp_tcp_client_sock_open(Domain) -> +tpp_tcp_client_sock_open(Domain, BufInit) -> case socket:open(Domain, stream, tcp) of {ok, Sock} -> + ok = BufInit(Sock), Sock; {error, Reason} -> exit({open_failed, Reason}) @@ -7659,6 +7689,63 @@ tpp_tcp_client_sock_close(Sock) -> exit({close, Reason}) end. +-define(TPP_REQUEST, 1). +-define(TPP_REPLY, 2). + +tpp_tcp_recv_req(Sock, Recv) -> + tpp_tcp_recv(Sock, Recv, ?TPP_REQUEST). + +tpp_tcp_recv_rep(Sock, Recv) -> + tpp_tcp_recv(Sock, Recv, ?TPP_REPLY). + +tpp_tcp_recv(Sock, Recv, Tag) -> + case Recv(Sock, 0) of + {ok, <> = Msg} + when (Sz =:= size(Data)) -> + %% We got it all + {ok, Data, size(Msg)}; + {ok, <> = Msg} -> + Remains = Sz - size(Data), + tpp_tcp_recv(Sock, Recv, Tag, Remains, size(Msg), [Data]); + {ok, <>} -> + {error, {invalid_msg_tag, Tag}}; + {error, _} = ERROR -> + ERROR + end. + +tpp_tcp_recv(Sock, Recv, Tag, Remaining, AccSz, Acc) -> + case Recv(Sock, Remaining) of + {ok, Data} when (Remaining =:= size(Data)) -> + %% We got the rest + TotSz = AccSz + size(Data), + {ok, erlang:iolist_to_binary(lists:reverse([Data | Acc])), TotSz}; + {ok, Data} when (Remaining > size(Data)) -> + tpp_tcp_recv(Sock, Recv, Tag, + Remaining - size(Data), AccSz + size(Data), + [Data | Acc]); + {error, _} = ERROR -> + ERROR + end. + + +tpp_tcp_send_req(Sock, Send, Data) -> + tpp_tcp_send(Sock, Send, ?TPP_REQUEST, Data). + +tpp_tcp_send_rep(Sock, Send, Data) -> + tpp_tcp_send(Sock, Send, ?TPP_REPLY, Data). + +tpp_tcp_send(Sock, Send, Tag, Data) -> + DataSz = size(Data), + Msg = <>, + case Send(Sock, Msg) of + ok -> + {ok, size(Msg)}; + {error, _} = ERROR -> + ERROR + end. + + + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% This gets the local address (not 127.0...) @@ -7951,21 +8038,21 @@ f(F, A) -> %% i(Before ++ FStr ++ After, []). -d(F, A) -> - d(get(dbg_fd), F, A). +%% d(F, A) -> +%% d(get(dbg_fd), F, A). -d(undefined, F, A) -> - [NodeNameStr|_] = string:split(atom_to_list(node()), [$@]), - DbgFileName = f("~s-dbg.txt", [NodeNameStr]), - case file:open(DbgFileName, [write]) of - {ok, FD} -> - put(dbg_fd, FD), - d(FD, F, A); - {error, Reason} -> - exit({failed_open_dbg_file, Reason}) - end; -d(FD, F, A) -> - io:format(FD, "~s~n", [f("[~s] " ++ F, [formated_timestamp()|A])]). +%% d(undefined, F, A) -> +%% [NodeNameStr|_] = string:split(atom_to_list(node()), [$@]), +%% DbgFileName = f("~s-dbg.txt", [NodeNameStr]), +%% case file:open(DbgFileName, [write]) of +%% {ok, FD} -> +%% put(dbg_fd, FD), +%% d(FD, F, A); +%% {error, Reason} -> +%% exit({failed_open_dbg_file, Reason}) +%% end; +%% d(FD, F, A) -> +%% io:format(FD, "~s~n", [f("[~s] " ++ F, [formated_timestamp()|A])]). i(F) -> i(F, []). -- cgit v1.2.3 From 6fcbb97c0b7f082c4934d7765c6b63222f317ef2 Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Mon, 5 Nov 2018 14:37:39 +0100 Subject: [socket-nif] Make it possible to send (nif) debug to file Make it possible to open a file and send debug printouts to (instead of stdout) for debug printouts from the nif-code. OTP-14831 --- erts/emulator/nifs/common/socket_dbg.c | 24 +++++++++++++++++++++++- erts/emulator/nifs/common/socket_dbg.h | 8 ++++++-- 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/erts/emulator/nifs/common/socket_dbg.c b/erts/emulator/nifs/common/socket_dbg.c index dd11fbca9b..fe9135e5a0 100644 --- a/erts/emulator/nifs/common/socket_dbg.c +++ b/erts/emulator/nifs/common/socket_dbg.c @@ -36,10 +36,32 @@ #define TNAME(__T__) enif_thread_name( __T__ ) #define TSNAME() TNAME(TSELF()) +static FILE* dbgout = NULL; + static int realtime(struct timespec* tsP); static int timespec2str(char *buf, unsigned int len, struct timespec *ts); +extern +void esock_dbg_init(char* filename) +{ + if (filename != NULL) { + if (strcmp(filename, ESOCK_DBGOUT_DEFAULT) == 0) { + dbgout = stdout; + } else if (strcmp(filename, ESOCK_DBGOUT_UNIQUE) == 0) { + char template[] = "/tmp/esock-dbg-XXXXXX"; + dbgout = fdopen(mkstemp(template), "w+"); + } else { + dbgout = fopen(filename, "w+"); + } + } else { + char template[] = "/tmp/esock-dbg-XXXXXX"; + dbgout = fdopen(mkstemp(template), "w+"); + } +} + + + /* * Print a debug format string *with* both a timestamp and the * the name of the *current* thread. @@ -70,7 +92,7 @@ void esock_dbg_printf( const char* prefix, const char* format, ... ) if (res > 0) { va_start (args, format); - enif_vfprintf (stdout, f, args); + enif_vfprintf (dbgout, f, args); va_end (args); fflush(stdout); } diff --git a/erts/emulator/nifs/common/socket_dbg.h b/erts/emulator/nifs/common/socket_dbg.h index ad0fcdada9..47739b46da 100644 --- a/erts/emulator/nifs/common/socket_dbg.h +++ b/erts/emulator/nifs/common/socket_dbg.h @@ -27,6 +27,10 @@ #ifndef SOCKET_DBG_H__ #define SOCKET_DBG_H__ +/* Used when calling the init function */ +#define ESOCK_DBGOUT_DEFAULT "stdout" +#define ESOCK_DBGOUT_UNIQUE "unique" + /* Used in debug printouts */ #ifdef __WIN32__ @@ -45,7 +49,7 @@ typedef unsigned long long llu_t; } -extern -void esock_dbg_printf( const char* prefix, const char* format, ... ); +extern void esock_dbg_init(char* filename); +extern void esock_dbg_printf( const char* prefix, const char* format, ... ); #endif // SOCKET_DBG_H__ -- cgit v1.2.3 From 868950ba50185d68075e0eb14708beb5a7a5a63f Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Mon, 5 Nov 2018 14:40:14 +0100 Subject: [socket-nif] Sending when buffer is full failed When the send buffer was full (eagain), the send failed (with the rather useless return of {ok, -1}) instead of returning {error, eagain}. OTP-14831 --- erts/emulator/nifs/common/socket_nif.c | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/erts/emulator/nifs/common/socket_nif.c b/erts/emulator/nifs/common/socket_nif.c index 82bf8305fc..70a969e867 100644 --- a/erts/emulator/nifs/common/socket_nif.c +++ b/erts/emulator/nifs/common/socket_nif.c @@ -5583,7 +5583,7 @@ ERL_NIF_TERM nsendmsg(ErlNifEnv* env, if (IS_SOCKET_ERROR(written)) save_errno = sock_errno(); else - save_errno = -1; // The value does not actually matter in this case + save_errno = -1; // OK or not complete: this value should not matter in this case res = send_check_result(env, descP, written, dataSize, save_errno, sendRef); @@ -13327,7 +13327,12 @@ ERL_NIF_TERM send_check_result(ErlNifEnv* env, SSDBG( descP, ("SOCKET", "send_check_result -> try again\r\n") ); + SELECT(env, descP->sock, (ERL_NIF_SELECT_WRITE), descP, NULL, sendRef); + + return esock_make_error(env, esock_atom_eagain); + } + } /* We failed to write the *entire* packet (anything less then size @@ -13352,8 +13357,7 @@ ERL_NIF_TERM send_check_result(ErlNifEnv* env, cnt_inc(&descP->writeWaits, 1); - SELECT(env, descP->sock, (ERL_NIF_SELECT_WRITE), - descP, NULL, sendRef); + SELECT(env, descP->sock, (ERL_NIF_SELECT_WRITE), descP, NULL, sendRef); SSDBG( descP, ("SOCKET", "send_check_result -> " @@ -16736,12 +16740,12 @@ int esock_monitor(const char* slogan, { int res; - SSDBG( descP, ("SOCKET", "[%d] %s: try monitor", descP->sock, slogan) ); + SSDBG( descP, ("SOCKET", "[%d] %s: try monitor\r\n", descP->sock, slogan) ); /* esock_dbg_printf("MONP", "[%d] %s\r\n", descP->sock, slogan); */ res = enif_monitor_process(env, descP, pid, &monP->mon); if (res != 0) { - SSDBG( descP, ("SOCKET", "[%d] monitor failed: %d", descP->sock, res) ); + SSDBG( descP, ("SOCKET", "[%d] monitor failed: %d\r\n", descP->sock, res) ); // esock_dbg_printf("MONP", "[%d] failed: %d\r\n", descP->sock, res); } /* else { esock_dbg_printf("MONP", @@ -17595,6 +17599,9 @@ BOOLEAN_T extract_iow(ErlNifEnv* env, static int on_load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info) { + esock_dbg_init(ESOCK_DBGOUT_DEFAULT); + // esock_dbg_init(ESOCK_DBGOUT_UNIQUE); + data.dbg = extract_debug(env, load_info); data.iow = extract_iow(env, load_info); -- cgit v1.2.3 From f18bd2a88d7bc8519cf5db611c4c530eedecba2a Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Mon, 5 Nov 2018 14:43:07 +0100 Subject: [socket-nif] Add "partial success" to sendmsg The sendmsg function attempts to send *one message*. But its possible for the underlying software to fail to send the *entire* message. So, instead of retrying itself, as send does, the sendmsg function will now instead return with {ok, Remaining}, leaving it to the caller to decide what to do. OTP-14831 --- erts/preloaded/ebin/socket.beam | Bin 68564 -> 69380 bytes erts/preloaded/src/socket.erl | 47 +++++++++++++++++++++++++++++++++------- 2 files changed, 39 insertions(+), 8 deletions(-) diff --git a/erts/preloaded/ebin/socket.beam b/erts/preloaded/ebin/socket.beam index 480f86334c..d3bc7c7af0 100644 Binary files a/erts/preloaded/ebin/socket.beam and b/erts/preloaded/ebin/socket.beam differ diff --git a/erts/preloaded/src/socket.erl b/erts/preloaded/src/socket.erl index 5ebc2074e0..dd10aac3ff 100644 --- a/erts/preloaded/src/socket.erl +++ b/erts/preloaded/src/socket.erl @@ -1573,12 +1573,13 @@ sendmsg(Socket, MsgHdr, Timeout) sendmsg(Socket, MsgHdr, ?SOCKET_SENDMSG_FLAGS_DEFAULT, Timeout). --spec sendmsg(Socket, MsgHdr, Flags, Timeout) -> ok | {error, Reason} when - Socket :: socket(), - MsgHdr :: msghdr(), - Flags :: send_flags(), - Timeout :: timeout(), - Reason :: term(). +-spec sendmsg(Socket, MsgHdr, Flags, Timeout) -> ok | {ok, Remaining} | {error, Reason} when + Socket :: socket(), + MsgHdr :: msghdr(), + Flags :: send_flags(), + Timeout :: timeout(), + Remaining :: erlang:iovec(), + Reason :: term(). sendmsg(#socket{ref = SockRef}, #{iov := IOV} = MsgHdr, Flags, Timeout) when is_list(IOV) andalso @@ -1603,6 +1604,18 @@ do_sendmsg(SockRef, MsgHdr, EFlags, Timeout) -> %% We are done ok; + {ok, Written} when is_integer(Written) andalso (Written > 0) -> + + %% We should not retry here since the protocol may not + %% be able to handle a message being split. Leave it to + %% the caller to figure out (call again with the rest). + %% + %% We should really not need to cancel, since this is + %% accepted for sendmsg! + %% + cancel(SockRef, sendmsg, SendRef), + {ok, do_sendmsg_rest(maps:get(iov, MsgHdr), Written)}; + {error, eagain} -> receive {select, SockRef, SendRef, ready_output} -> @@ -1617,6 +1630,12 @@ do_sendmsg(SockRef, MsgHdr, EFlags, Timeout) -> ERROR end. +do_sendmsg_rest([B|IOVec], Written) when (Written >= size(B)) -> + do_sendmsg_rest(IOVec, Written - size(B)); +do_sendmsg_rest([B|IOVec], Written) -> + <<_:Written/binary, Rest/binary>> = B, + [Rest|IOVec]. + ensure_msghdr(#{ctrl := []} = M) -> ensure_msghdr(maps:remove(ctrl, M)); ensure_msghdr(#{iov := IOV} = M) when is_list(IOV) andalso (IOV =/= []) -> @@ -1625,6 +1644,8 @@ ensure_msghdr(_) -> einval(). + + %% =========================================================================== %% %% writev - write data into multiple buffers @@ -1983,10 +2004,20 @@ recvmsg(Socket, Timeout) -> Flags :: recv_flags(), Timeout :: timeout(), MsgHdr :: msghdr(), + Reason :: term() + ; (Socket, BufSz, CtrlSz) -> {ok, MsgHdr} | {error, Reason} when + Socket :: socket(), + BufSz :: non_neg_integer(), + CtrlSz :: non_neg_integer(), + MsgHdr :: msghdr(), Reason :: term(). -recvmsg(Socket, Flags, Timeout) -> - recvmsg(Socket, 0, 0, Flags, Timeout). +recvmsg(Socket, Flags, Timeout) when is_list(Flags) -> + recvmsg(Socket, 0, 0, Flags, Timeout); +recvmsg(Socket, BufSz, CtrlSz) when is_integer(BufSz) andalso is_integer(CtrlSz) -> + recvmsg(Socket, BufSz, CtrlSz, + ?SOCKET_RECV_FLAGS_DEFAULT, ?SOCKET_RECV_TIMEOUT_DEFAULT). + -spec recvmsg(Socket, BufSz, CtrlSz, -- cgit v1.2.3 From 741eb8d2d8ba606d81990ef50b2d8b261d47ec81 Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Mon, 5 Nov 2018 14:47:03 +0100 Subject: [socket-nif|doc] Improved doc for recvmsg and update for sendmsg The API for the sendmsg function has been updated to describe the possible "partial success" of {ok, Remaining}. OTP-14831 --- erts/doc/src/socket.xml | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/erts/doc/src/socket.xml b/erts/doc/src/socket.xml index 49c14869bf..ea2fde7dee 100644 --- a/erts/doc/src/socket.xml +++ b/erts/doc/src/socket.xml @@ -405,7 +405,8 @@ - + + Receive a message from a socket. @@ -416,7 +417,7 @@ which may contain the source address (if socket not connected), a list of cmsghdr_recv() (depends on what socket options have been set and what the protocol and platform supports) and - also a set of flags, providing further info about the read .

+ also a set of flags, providing further info about the read.

The BufSz argument basically defines the size of the receive buffer. By setting the value to zero (0), the configured @@ -458,6 +459,15 @@ which also contains the message to send, The MsgHdr may also contain an list of optional cmsghdr_send() (depends on what the protocol and platform supports).

+ +

Unlike the send function, + this one sends one message. + This means that if, for whatever reason, its not possible to send the + message in one go, the function will instead return with the + remaining data ({ok, Remaining}). Thereby leaving it + up to the caller to decide what to do (retry with the remaining data + of give up).

+
-- cgit v1.2.3 From fbd9c5949373c9b6292e56604885822f210f24a2 Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Mon, 5 Nov 2018 14:48:56 +0100 Subject: [socket-nif|test] Add sendmsg/recvmsg ping-pong test cases Added New ping-pong test cases using the sendmsg and recvmsg functions. OTP-14831 --- erts/emulator/test/socket_SUITE.erl | 293 +++++++++++++++++++++++++++++++++--- 1 file changed, 272 insertions(+), 21 deletions(-) diff --git a/erts/emulator/test/socket_SUITE.erl b/erts/emulator/test/socket_SUITE.erl index c2b8a8349f..2cbd45a63f 100644 --- a/erts/emulator/test/socket_SUITE.erl +++ b/erts/emulator/test/socket_SUITE.erl @@ -18,6 +18,8 @@ %% %CopyrightEnd% %% +%% ts:run(emulator, socket_SUITE, [batch]). + -module(socket_SUITE). -include_lib("common_test/include/ct.hrl"). @@ -91,16 +93,16 @@ traffic_send_and_recv_chunks_tcp6/1, traffic_ping_pong_small_send_and_recv_tcp4/1, traffic_ping_pong_small_send_and_recv_tcp6/1, - %% traffic_ping_pong_small_sendmsg_and_recvmsg_tcp4/1, - %% traffic_ping_pong_small_sendmsg_and_recvmsg_tcp6/1, + traffic_ping_pong_small_sendmsg_and_recvmsg_tcp4/1, + traffic_ping_pong_small_sendmsg_and_recvmsg_tcp6/1, traffic_ping_pong_medium_send_and_recv_tcp4/1, traffic_ping_pong_medium_send_and_recv_tcp6/1, - %% traffic_ping_pong_medium_sendmsg_and_recvmsg_tcp4/1, - %% traffic_ping_pong_medium_sendmsg_and_recvmsg_tcp6/1, + traffic_ping_pong_medium_sendmsg_and_recvmsg_tcp4/1, + traffic_ping_pong_medium_sendmsg_and_recvmsg_tcp6/1, traffic_ping_pong_large_send_and_recv_tcp4/1, - traffic_ping_pong_large_send_and_recv_tcp6/1%, - %% traffic_ping_pong_large_sendmsg_and_recvmsg_tcp4/1, - %% traffic_ping_pong_large_sendmsg_and_recvmsg_tcp6/1 + traffic_ping_pong_large_send_and_recv_tcp6/1, + traffic_ping_pong_large_sendmsg_and_recvmsg_tcp4/1, + traffic_ping_pong_large_sendmsg_and_recvmsg_tcp6/1 %% Tickets @@ -261,16 +263,16 @@ traffic_cases() -> traffic_ping_pong_small_send_and_recv_tcp4, traffic_ping_pong_small_send_and_recv_tcp6, - %% traffic_ping_pong_small_sendmsg_and_recvmsg_tcp4, - %% traffic_ping_pong_small_sendmsg_and_recvmsg_tcp6, + traffic_ping_pong_small_sendmsg_and_recvmsg_tcp4, + traffic_ping_pong_small_sendmsg_and_recvmsg_tcp6, traffic_ping_pong_medium_send_and_recv_tcp4, traffic_ping_pong_medium_send_and_recv_tcp6, - %% traffic_ping_pong_medium_sendmsg_and_recvmsg_tcp4, - %% traffic_ping_pong_medium_sendmsg_and_recvmsg_tcp6, + traffic_ping_pong_medium_sendmsg_and_recvmsg_tcp4, + traffic_ping_pong_medium_sendmsg_and_recvmsg_tcp6, traffic_ping_pong_large_send_and_recv_tcp4, - traffic_ping_pong_large_send_and_recv_tcp6%% , - %% traffic_ping_pong_large_sendmsg_and_recvmsg_tcp4, - %% traffic_ping_pong_large_sendmsg_and_recvmsg_tcp6 + traffic_ping_pong_large_send_and_recv_tcp6, + traffic_ping_pong_large_sendmsg_and_recvmsg_tcp4, + traffic_ping_pong_large_sendmsg_and_recvmsg_tcp6 ]. @@ -6749,6 +6751,83 @@ traffic_ping_pong_small_send_and_recv_tcp6(_Config) when is_list(_Config) -> end). +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test that the sendmsg and recvmsg +%% functions by repeatedly sending a meassage between two entities. +%% The same basic test case is used for three different message sizes; +%% small (8 bytes), medium (8K) and large (8M). +%% The message is sent from A to B and then back again. This is +%% repeated a set number of times (more times the small the message). +%% This is the 'small' message test case, for IPv4. + +traffic_ping_pong_small_sendmsg_and_recvmsg_tcp4(suite) -> + []; +traffic_ping_pong_small_sendmsg_and_recvmsg_tcp4(doc) -> + []; +traffic_ping_pong_small_sendmsg_and_recvmsg_tcp4(_Config) when is_list(_Config) -> + tc_try(traffic_ping_pong_small_sendmsg_and_recvmsg_tcp4, + fun() -> + ?TT(?SECS(20)), + Send = fun(Sock, Data) -> + MsgHdr = #{iov => [Data]}, + socket:sendmsg(Sock, MsgHdr) + end, + Recv = fun(Sock, Sz) -> + case socket:recvmsg(Sock, Sz, 0) of + {ok, #{addr := undefined, + iov := [Data]}} -> + {ok, Data}; + {error, _} = ERROR -> + ERROR + end + end, + InitState = #{domain => inet, + send => Send, % Send function + recv => Recv % Receive function + }, + ok = traffic_ping_pong_small_send_and_receive_tcp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test that the sendmsg and recvmsg functions +%% by repeatedly sending a meassage between two entities. +%% The same basic test case is used for three different message sizes; +%% small (8 bytes), medium (8K) and large (8M). +%% The message is sent from A to B and then back again. This is +%% repeated a set number of times (more times the small the message). +%% This is the 'small' message test case, for IPv6. + +traffic_ping_pong_small_sendmsg_and_recvmsg_tcp6(suite) -> + []; +traffic_ping_pong_small_sendmsg_and_recvmsg_tcp6(doc) -> + []; +traffic_ping_pong_small_sendmsg_and_recvmsg_tcp6(_Config) when is_list(_Config) -> + tc_try(traffic_ping_pong_small_sendmsg_and_recvmsg_tcp6, + fun() -> + not_yet_implemented(), + ?TT(?SECS(20)), + Send = fun(Sock, Data) -> + MsgHdr = #{iov => [Data]}, + socket:sendmsg(Sock, MsgHdr) + end, + Recv = fun(Sock, Sz) -> + case socket:recvmsg(Sock, Sz, 0) of + {ok, #{addr := undefined, + iov := [Data]}} -> + {ok, Data}; + {error, _} = ERROR -> + ERROR + end + end, + InitState = #{domain => inet6, + send => Send, % Send function + recv => Recv % Receive function + }, + ok = traffic_ping_pong_small_send_and_receive_tcp(InitState) + end). + + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% This test case is intended to test that the send and recv functions %% by repeatedly sending a meassage between two entities. @@ -6765,7 +6844,6 @@ traffic_ping_pong_medium_send_and_recv_tcp4(doc) -> traffic_ping_pong_medium_send_and_recv_tcp4(_Config) when is_list(_Config) -> tc_try(traffic_ping_pong_medium_send_and_recv_tcp4, fun() -> - %% not_yet_implemented(), ?TT(?SECS(30)), Send = fun(Sock, Data) -> socket:send(Sock, Data) end, Recv = fun(Sock, Sz) -> socket:recv(Sock, Sz) end, @@ -6806,6 +6884,83 @@ traffic_ping_pong_medium_send_and_recv_tcp6(_Config) when is_list(_Config) -> +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test that the sendmsg and recvmsg +%% functions by repeatedly sending a meassage between two entities. +%% The same basic test case is used for three different message sizes; +%% small (8 bytes), medium (8K) and large (8M). +%% The message is sent from A to B and then back again. This is +%% repeated a set number of times (more times the small the message). +%% This is the 'medium' message test case, for IPv4. + +traffic_ping_pong_medium_sendmsg_and_recvmsg_tcp4(suite) -> + []; +traffic_ping_pong_medium_sendmsg_and_recvmsg_tcp4(doc) -> + []; +traffic_ping_pong_medium_sendmsg_and_recvmsg_tcp4(_Config) when is_list(_Config) -> + tc_try(traffic_ping_pong_medium_sendmsg_and_recvmsg_tcp4, + fun() -> + ?TT(?SECS(20)), + Send = fun(Sock, Data) -> + MsgHdr = #{iov => [Data]}, + socket:sendmsg(Sock, MsgHdr) + end, + Recv = fun(Sock, Sz) -> + case socket:recvmsg(Sock, Sz, 0) of + {ok, #{addr := undefined, + iov := [Data]}} -> + {ok, Data}; + {error, _} = ERROR -> + ERROR + end + end, + InitState = #{domain => inet, + send => Send, % Send function + recv => Recv % Receive function + }, + ok = traffic_ping_pong_medium_send_and_receive_tcp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test that the sendmsg and recvmsg functions +%% by repeatedly sending a meassage between two entities. +%% The same basic test case is used for three different message sizes; +%% small (8 bytes), medium (8K) and large (8M). +%% The message is sent from A to B and then back again. This is +%% repeated a set number of times (more times the small the message). +%% This is the 'medium' message test case, for IPv6. + +traffic_ping_pong_medium_sendmsg_and_recvmsg_tcp6(suite) -> + []; +traffic_ping_pong_medium_sendmsg_and_recvmsg_tcp6(doc) -> + []; +traffic_ping_pong_medium_sendmsg_and_recvmsg_tcp6(_Config) when is_list(_Config) -> + tc_try(traffic_ping_pong_medium_sendmsg_and_recvmsg_tcp6, + fun() -> + not_yet_implemented(), + ?TT(?SECS(20)), + Send = fun(Sock, Data) -> + MsgHdr = #{iov => [Data]}, + socket:sendmsg(Sock, MsgHdr) + end, + Recv = fun(Sock, Sz) -> + case socket:recvmsg(Sock, Sz, 0) of + {ok, #{addr := undefined, + iov := [Data]}} -> + {ok, Data}; + {error, _} = ERROR -> + ERROR + end + end, + InitState = #{domain => inet6, + send => Send, % Send function + recv => Recv % Receive function + }, + ok = traffic_ping_pong_medium_send_and_receive_tcp(InitState) + end). + + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% This test case is intended to test that the send and recv functions %% by repeatedly sending a meassage between two entities. @@ -6863,6 +7018,87 @@ traffic_ping_pong_large_send_and_recv_tcp6(_Config) when is_list(_Config) -> +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test that the sendmsg and recvmsg +%% functions by repeatedly sending a meassage between two entities. +%% The same basic test case is used for three different message sizes; +%% small (8 bytes), medium (8K) and large (8M). +%% The message is sent from A to B and then back again. This is +%% repeated a set number of times (more times the small the message). +%% This is the 'large' message test case, for IPv4. + +traffic_ping_pong_large_sendmsg_and_recvmsg_tcp4(suite) -> + []; +traffic_ping_pong_large_sendmsg_and_recvmsg_tcp4(doc) -> + []; +traffic_ping_pong_large_sendmsg_and_recvmsg_tcp4(_Config) when is_list(_Config) -> + tc_try(traffic_ping_pong_large_sendmsg_and_recvmsg_tcp4, + fun() -> + ?TT(?SECS(30)), + Send = fun(Sock, Data) -> + MsgHdr = #{iov => [Data]}, + socket:sendmsg(Sock, MsgHdr) + end, + Recv = fun(Sock, Sz) -> + case socket:recvmsg(Sock, Sz, 0) of + {ok, #{addr := undefined, + iov := [Data]}} -> + {ok, Data}; + {error, _} = ERROR -> + ERROR + end + end, + InitState = #{domain => inet, + send => Send, % Send function + recv => Recv % Receive function + }, + ok = traffic_ping_pong_large_send_and_receive_tcp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test that the sendmsg and recvmsg functions +%% by repeatedly sending a meassage between two entities. +%% The same basic test case is used for three different message sizes; +%% small (8 bytes), medium (8K) and large (8M). +%% The message is sent from A to B and then back again. This is +%% repeated a set number of times (more times the small the message). +%% This is the 'large' message test case, for IPv6. + +traffic_ping_pong_large_sendmsg_and_recvmsg_tcp6(suite) -> + []; +traffic_ping_pong_large_sendmsg_and_recvmsg_tcp6(doc) -> + []; +traffic_ping_pong_large_sendmsg_and_recvmsg_tcp6(_Config) when is_list(_Config) -> + tc_try(traffic_ping_pong_large_sendmsg_and_recvmsg_tcp6, + fun() -> + not_yet_implemented(), + ?TT(?SECS(30)), + Send = fun(Sock, Data) when is_binary(Data) -> + MsgHdr = #{iov => [Data]}, + socket:sendmsg(Sock, MsgHdr); + (Sock, Data) when is_list(Data) -> + MsgHdr = #{iov => Data}, + socket:sendmsg(Sock, MsgHdr) + end, + Recv = fun(Sock, Sz) -> + case socket:recvmsg(Sock, Sz, 0) of + {ok, #{addr := undefined, + iov := [Data]}} -> + {ok, Data}; + {error, _} = ERROR -> + ERROR + end + end, + InitState = #{domain => inet6, + send => Send, % Send function + recv => Recv % Receive function + }, + ok = traffic_ping_pong_large_send_and_receive_tcp(InitState) + end). + + + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -define(SMALL, lists:seq(1, 8)). @@ -6959,7 +7195,7 @@ traffic_ping_pong_send_and_receive_tcp(InitState) -> end end}, #{desc => "maybe init buffers", - cmd => fun(#{lsock := LSock, buf_init := BufInit} = State) -> + cmd => fun(#{lsock := LSock, buf_init := BufInit} = _State) -> BufInit(LSock) end}, #{desc => "make listen socket", @@ -7510,11 +7746,9 @@ tpp_tcp_handler_await_terminate(Parent) -> end. tpp_tcp_handler_msg_exchange(Sock, Send, Recv) -> - %% socket:setopt(Sock, otp, debug, true), tpp_tcp_handler_msg_exchange_loop(Sock, Send, Recv, 0, 0, 0, undefined). tpp_tcp_handler_msg_exchange_loop(Sock, Send, Recv, N, Sent, Received, Start) -> - %% if (N =:= 1000) -> socket:setopt(Sock, otp, debug, true); true -> ok end, %% ?SEV_IPRINT("[~w] try receive", [N]), case tpp_tcp_recv_req(Sock, Recv) of {ok, Msg, RecvSz} -> @@ -7633,10 +7867,11 @@ tpp_tcp_client_msg_exchange_loop(Sock, _Send, _Recv, _Msg, end; tpp_tcp_client_msg_exchange_loop(Sock, Send, Recv, Data, Num, N, Sent, Received, Start) -> - %% d("[~w,~w] try send", [Num,N]), + %% d("tpp_tcp_client_msg_exchange_loop(~w,~w) try send", [Num,N]), case tpp_tcp_send_req(Sock, Send, Data) of {ok, SendSz} -> - %% d("[~w,~w] sent - no try recv", [Num,N]), + %% d("tpp_tcp_client_msg_exchange_loop(~w,~w) sent - " + %% "now try recv", [Num,N]), case tpp_tcp_recv_rep(Sock, Recv) of {ok, NewData, RecvSz} -> tpp_tcp_client_msg_exchange_loop(Sock, Send, Recv, @@ -7737,13 +7972,29 @@ tpp_tcp_send_rep(Sock, Send, Data) -> tpp_tcp_send(Sock, Send, Tag, Data) -> DataSz = size(Data), Msg = <>, + tpp_tcp_send_msg(Sock, Send, Msg, 0). + +tpp_tcp_send_msg(Sock, Send, Msg, AccSz) when is_binary(Msg) -> case Send(Sock, Msg) of ok -> - {ok, size(Msg)}; + {ok, AccSz+size(Msg)}; + {ok, Rest} -> % This is an IOVec + RestBin = list_to_binary(Rest), + tpp_tcp_send_msg(Sock, Send, RestBin, AccSz+(size(Msg)-size(RestBin))); {error, _} = ERROR -> ERROR end. + + +%% size_of_data(Data) when is_binary(Data) -> +%% size(Data); +%% size_of_data(Data) when is_list(Data) -> +%% size_of_iovec(Data, 0). +%% size_of_iovec([], Sz) -> +%% Sz; +%% size_of_iovec([B|IOVec], Sz) -> +%% size_of_iovec(IOVec, Sz+size(B)). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -- cgit v1.2.3 From 98d27d647f78e7e0c7154a5d4a007a0c0c2b43d4 Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Mon, 5 Nov 2018 15:51:51 +0100 Subject: [socket-nif|test] Some minor restructure of the ping-pong cases Some minor restructure of the ping-pong test cases in order to not have duplicate the send and receive fun's. OTP-14831 --- erts/emulator/test/socket_SUITE.erl | 295 +++++++++++++----------------------- 1 file changed, 107 insertions(+), 188 deletions(-) diff --git a/erts/emulator/test/socket_SUITE.erl b/erts/emulator/test/socket_SUITE.erl index 2cbd45a63f..3c20b6422a 100644 --- a/erts/emulator/test/socket_SUITE.erl +++ b/erts/emulator/test/socket_SUITE.erl @@ -131,6 +131,10 @@ -define(LIB, socket_test_lib). +-define(PP_SMALL, lists:seq(1, 8)). +-define(PP_MEDIUM, lists:flatten(lists:duplicate(1024, ?PP_SMALL))). +-define(PP_LARGE, lists:flatten(lists:duplicate(1024, ?PP_MEDIUM))). + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -6709,17 +6713,15 @@ traffic_ping_pong_small_send_and_recv_tcp4(suite) -> traffic_ping_pong_small_send_and_recv_tcp4(doc) -> []; traffic_ping_pong_small_send_and_recv_tcp4(_Config) when is_list(_Config) -> + Msg = l2b(?PP_SMALL), + Num = 100000, tc_try(traffic_ping_pong_small_send_and_recv_tcp4, fun() -> ?TT(?SECS(15)), - Send = fun(Sock, Data) -> socket:send(Sock, Data) end, - %% Recv = fun(Sock) -> socket:recv(Sock, 0, 5000) end, - Recv = fun(Sock, Sz) -> socket:recv(Sock, Sz) end, InitState = #{domain => inet, - send => Send, % Send function - recv => Recv % Receive function - }, - ok = traffic_ping_pong_small_send_and_receive_tcp(InitState) + msg => Msg, + num => Num}, + ok = traffic_ping_pong_send_and_recv_tcp(InitState) end). @@ -6737,17 +6739,16 @@ traffic_ping_pong_small_send_and_recv_tcp6(suite) -> traffic_ping_pong_small_send_and_recv_tcp6(doc) -> []; traffic_ping_pong_small_send_and_recv_tcp6(_Config) when is_list(_Config) -> + Msg = l2b(?PP_SMALL), + Num = 100000, tc_try(traffic_ping_pong_small_send_and_recv_tcp6, fun() -> not_yet_implemented(), - ?TT(?SECS(30)), - Send = fun(Sock, Data) -> socket:send(Sock, Data) end, - Recv = fun(Sock, Sz) -> socket:recv(Sock, Sz) end, + ?TT(?SECS(15)), InitState = #{domain => inet6, - send => Send, % Send function - recv => Recv % Receive function - }, - ok = traffic_ping_pong_small_send_and_receive_tcp(InitState) + msg => Msg, + num => Num}, + ok = traffic_ping_pong_send_and_recv_tcp(InitState) end). @@ -6765,27 +6766,15 @@ traffic_ping_pong_small_sendmsg_and_recvmsg_tcp4(suite) -> traffic_ping_pong_small_sendmsg_and_recvmsg_tcp4(doc) -> []; traffic_ping_pong_small_sendmsg_and_recvmsg_tcp4(_Config) when is_list(_Config) -> + Msg = l2b(?PP_SMALL), + Num = 100000, tc_try(traffic_ping_pong_small_sendmsg_and_recvmsg_tcp4, fun() -> ?TT(?SECS(20)), - Send = fun(Sock, Data) -> - MsgHdr = #{iov => [Data]}, - socket:sendmsg(Sock, MsgHdr) - end, - Recv = fun(Sock, Sz) -> - case socket:recvmsg(Sock, Sz, 0) of - {ok, #{addr := undefined, - iov := [Data]}} -> - {ok, Data}; - {error, _} = ERROR -> - ERROR - end - end, InitState = #{domain => inet, - send => Send, % Send function - recv => Recv % Receive function - }, - ok = traffic_ping_pong_small_send_and_receive_tcp(InitState) + msg => Msg, + num => Num}, + ok = traffic_ping_pong_sendmsg_and_recvmsg_tcp(InitState) end). @@ -6803,28 +6792,16 @@ traffic_ping_pong_small_sendmsg_and_recvmsg_tcp6(suite) -> traffic_ping_pong_small_sendmsg_and_recvmsg_tcp6(doc) -> []; traffic_ping_pong_small_sendmsg_and_recvmsg_tcp6(_Config) when is_list(_Config) -> + Msg = l2b(?PP_SMALL), + Num = 100000, tc_try(traffic_ping_pong_small_sendmsg_and_recvmsg_tcp6, fun() -> not_yet_implemented(), ?TT(?SECS(20)), - Send = fun(Sock, Data) -> - MsgHdr = #{iov => [Data]}, - socket:sendmsg(Sock, MsgHdr) - end, - Recv = fun(Sock, Sz) -> - case socket:recvmsg(Sock, Sz, 0) of - {ok, #{addr := undefined, - iov := [Data]}} -> - {ok, Data}; - {error, _} = ERROR -> - ERROR - end - end, - InitState = #{domain => inet6, - send => Send, % Send function - recv => Recv % Receive function - }, - ok = traffic_ping_pong_small_send_and_receive_tcp(InitState) + InitState = #{domain => inet, + msg => Msg, + num => Num}, + ok = traffic_ping_pong_sendmsg_and_recvmsg_tcp(InitState) end). @@ -6842,16 +6819,15 @@ traffic_ping_pong_medium_send_and_recv_tcp4(suite) -> traffic_ping_pong_medium_send_and_recv_tcp4(doc) -> []; traffic_ping_pong_medium_send_and_recv_tcp4(_Config) when is_list(_Config) -> + Msg = l2b(?PP_MEDIUM), + Num = 100000, tc_try(traffic_ping_pong_medium_send_and_recv_tcp4, fun() -> ?TT(?SECS(30)), - Send = fun(Sock, Data) -> socket:send(Sock, Data) end, - Recv = fun(Sock, Sz) -> socket:recv(Sock, Sz) end, InitState = #{domain => inet, - send => Send, % Send function - recv => Recv % Receive function - }, - ok = traffic_ping_pong_medium_send_and_receive_tcp(InitState) + msg => Msg, + num => Num}, + ok = traffic_ping_pong_send_and_recv_tcp(InitState) end). @@ -6869,17 +6845,16 @@ traffic_ping_pong_medium_send_and_recv_tcp6(suite) -> traffic_ping_pong_medium_send_and_recv_tcp6(doc) -> []; traffic_ping_pong_medium_send_and_recv_tcp6(_Config) when is_list(_Config) -> + Msg = l2b(?PP_MEDIUM), + Num = 100000, tc_try(traffic_ping_pong_medium_send_and_recv_tcp6, fun() -> not_yet_implemented(), ?TT(?SECS(30)), - Send = fun(Sock, Data) -> socket:send(Sock, Data) end, - Recv = fun(Sock, Sz) -> socket:recv(Sock, Sz) end, InitState = #{domain => inet6, - send => Send, % Send function - recv => Recv % Receive function - }, - ok = traffic_ping_pong_medium_send_and_receive_tcp(InitState) + msg => Msg, + num => Num}, + ok = traffic_ping_pong_send_and_recv_tcp(InitState) end). @@ -6898,27 +6873,15 @@ traffic_ping_pong_medium_sendmsg_and_recvmsg_tcp4(suite) -> traffic_ping_pong_medium_sendmsg_and_recvmsg_tcp4(doc) -> []; traffic_ping_pong_medium_sendmsg_and_recvmsg_tcp4(_Config) when is_list(_Config) -> + Msg = l2b(?PP_MEDIUM), + Num = 100000, tc_try(traffic_ping_pong_medium_sendmsg_and_recvmsg_tcp4, fun() -> - ?TT(?SECS(20)), - Send = fun(Sock, Data) -> - MsgHdr = #{iov => [Data]}, - socket:sendmsg(Sock, MsgHdr) - end, - Recv = fun(Sock, Sz) -> - case socket:recvmsg(Sock, Sz, 0) of - {ok, #{addr := undefined, - iov := [Data]}} -> - {ok, Data}; - {error, _} = ERROR -> - ERROR - end - end, + ?TT(?SECS(30)), InitState = #{domain => inet, - send => Send, % Send function - recv => Recv % Receive function - }, - ok = traffic_ping_pong_medium_send_and_receive_tcp(InitState) + msg => Msg, + num => Num}, + ok = traffic_ping_pong_sendmsg_and_recvmsg_tcp(InitState) end). @@ -6936,28 +6899,16 @@ traffic_ping_pong_medium_sendmsg_and_recvmsg_tcp6(suite) -> traffic_ping_pong_medium_sendmsg_and_recvmsg_tcp6(doc) -> []; traffic_ping_pong_medium_sendmsg_and_recvmsg_tcp6(_Config) when is_list(_Config) -> + Msg = l2b(?PP_MEDIUM), + Num = 100000, tc_try(traffic_ping_pong_medium_sendmsg_and_recvmsg_tcp6, fun() -> not_yet_implemented(), ?TT(?SECS(20)), - Send = fun(Sock, Data) -> - MsgHdr = #{iov => [Data]}, - socket:sendmsg(Sock, MsgHdr) - end, - Recv = fun(Sock, Sz) -> - case socket:recvmsg(Sock, Sz, 0) of - {ok, #{addr := undefined, - iov := [Data]}} -> - {ok, Data}; - {error, _} = ERROR -> - ERROR - end - end, - InitState = #{domain => inet6, - send => Send, % Send function - recv => Recv % Receive function - }, - ok = traffic_ping_pong_medium_send_and_receive_tcp(InitState) + InitState = #{domain => ine6, + msg => Msg, + num => Num}, + ok = traffic_ping_pong_sendmsg_and_recvmsg_tcp(InitState) end). @@ -6975,17 +6926,15 @@ traffic_ping_pong_large_send_and_recv_tcp4(suite) -> traffic_ping_pong_large_send_and_recv_tcp4(doc) -> []; traffic_ping_pong_large_send_and_recv_tcp4(_Config) when is_list(_Config) -> + Msg = l2b(?PP_LARGE), + Num = 1000, tc_try(traffic_ping_pong_large_send_and_recv_tcp4, fun() -> - %% not_yet_implemented(), - ?TT(?MINS(5)), - Send = fun(Sock, Data) -> socket:send(Sock, Data) end, - Recv = fun(Sock, Sz) -> socket:recv(Sock, Sz) end, + ?TT(?SECS(45)), InitState = #{domain => inet, - send => Send, % Send function - recv => Recv % Receive function - }, - ok = traffic_ping_pong_large_send_and_receive_tcp(InitState) + msg => Msg, + num => Num}, + ok = traffic_ping_pong_send_and_recv_tcp(InitState) end). @@ -7003,17 +6952,16 @@ traffic_ping_pong_large_send_and_recv_tcp6(suite) -> traffic_ping_pong_large_send_and_recv_tcp6(doc) -> []; traffic_ping_pong_large_send_and_recv_tcp6(_Config) when is_list(_Config) -> + Msg = l2b(?PP_LARGE), + Num = 1000, tc_try(traffic_ping_pong_large_send_and_recv_tcp6, fun() -> not_yet_implemented(), - ?TT(?MINS(5)), - Send = fun(Sock, Data) -> socket:send(Sock, Data) end, - Recv = fun(Sock, Sz) -> socket:recv(Sock, Sz) end, + ?TT(?SECS(45)), InitState = #{domain => inet6, - send => Send, % Send function - recv => Recv % Receive function - }, - ok = traffic_ping_pong_large_send_and_receive_tcp(InitState) + msg => Msg, + num => Num}, + ok = traffic_ping_pong_send_and_recv_tcp(InitState) end). @@ -7032,27 +6980,15 @@ traffic_ping_pong_large_sendmsg_and_recvmsg_tcp4(suite) -> traffic_ping_pong_large_sendmsg_and_recvmsg_tcp4(doc) -> []; traffic_ping_pong_large_sendmsg_and_recvmsg_tcp4(_Config) when is_list(_Config) -> + Msg = l2b(?PP_LARGE), + Num = 1000, tc_try(traffic_ping_pong_large_sendmsg_and_recvmsg_tcp4, fun() -> ?TT(?SECS(30)), - Send = fun(Sock, Data) -> - MsgHdr = #{iov => [Data]}, - socket:sendmsg(Sock, MsgHdr) - end, - Recv = fun(Sock, Sz) -> - case socket:recvmsg(Sock, Sz, 0) of - {ok, #{addr := undefined, - iov := [Data]}} -> - {ok, Data}; - {error, _} = ERROR -> - ERROR - end - end, InitState = #{domain => inet, - send => Send, % Send function - recv => Recv % Receive function - }, - ok = traffic_ping_pong_large_send_and_receive_tcp(InitState) + msg => Msg, + num => Num}, + ok = traffic_ping_pong_sendmsg_and_recvmsg_tcp(InitState) end). @@ -7070,66 +7006,55 @@ traffic_ping_pong_large_sendmsg_and_recvmsg_tcp6(suite) -> traffic_ping_pong_large_sendmsg_and_recvmsg_tcp6(doc) -> []; traffic_ping_pong_large_sendmsg_and_recvmsg_tcp6(_Config) when is_list(_Config) -> + Msg = l2b(?PP_LARGE), + Num = 1000, tc_try(traffic_ping_pong_large_sendmsg_and_recvmsg_tcp6, fun() -> not_yet_implemented(), ?TT(?SECS(30)), - Send = fun(Sock, Data) when is_binary(Data) -> - MsgHdr = #{iov => [Data]}, - socket:sendmsg(Sock, MsgHdr); - (Sock, Data) when is_list(Data) -> - MsgHdr = #{iov => Data}, - socket:sendmsg(Sock, MsgHdr) - end, - Recv = fun(Sock, Sz) -> - case socket:recvmsg(Sock, Sz, 0) of - {ok, #{addr := undefined, - iov := [Data]}} -> - {ok, Data}; - {error, _} = ERROR -> - ERROR - end - end, InitState = #{domain => inet6, - send => Send, % Send function - recv => Recv % Receive function - }, - ok = traffic_ping_pong_large_send_and_receive_tcp(InitState) + msg => Msg, + num => Num}, + ok = traffic_ping_pong_sendmsg_and_recvmsg_tcp(InitState) end). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% --define(SMALL, lists:seq(1, 8)). --define(MEDIUM, lists:flatten(lists:duplicate(1024, ?SMALL))). --define(LARGE, lists:flatten(lists:duplicate(1024, ?MEDIUM))). - -traffic_ping_pong_small_send_and_receive_tcp(InitState) -> - Msg = l2b(?SMALL), - Num = 100000, - Fun = fun(_) -> ok end, %% Fun to update the buffers: Not needed here - traffic_ping_pong_send_and_receive_tcp(InitState#{msg => Msg, - num => Num, - buf_init => Fun}). +traffic_ping_pong_send_and_recv_tcp(InitState) -> + Send = fun(Sock, Data) -> socket:send(Sock, Data) end, + Recv = fun(Sock, Sz) -> socket:recv(Sock, Sz) end, + InitState2 = InitState#{send => Send, % Send function + recv => Recv % Receive function + }, + traffic_ping_pong_send_and_receive_tcp(InitState2). + +traffic_ping_pong_sendmsg_and_recvmsg_tcp(InitState) -> + Send = fun(Sock, Data) when is_binary(Data) -> + MsgHdr = #{iov => [Data]}, + socket:sendmsg(Sock, MsgHdr); + (Sock, Data) when is_list(Data) -> %% We assume iovec... + MsgHdr = #{iov => Data}, + socket:sendmsg(Sock, MsgHdr) + end, + Recv = fun(Sock, Sz) -> + case socket:recvmsg(Sock, Sz, 0) of + {ok, #{addr := undefined, + iov := [Data]}} -> + {ok, Data}; + {error, _} = ERROR -> + ERROR + end + end, + InitState2 = InitState#{send => Send, % Send function + recv => Recv % Receive function + }, + traffic_ping_pong_send_and_receive_tcp(InitState2). -traffic_ping_pong_medium_send_and_receive_tcp(InitState) -> - Msg = l2b(?MEDIUM), - Num = 100000, - Fun = fun(_) -> ok end, %% Fun to update the buffers: MAYBE needed here - traffic_ping_pong_send_and_receive_tcp(InitState#{msg => Msg, - num => Num, - buf_init => Fun}). -traffic_ping_pong_large_send_and_receive_tcp(InitState) -> - Msg = l2b(?LARGE), - Num = 1000, +traffic_ping_pong_send_and_receive_tcp(#{msg := Msg} = InitState) -> Fun = fun(Sock) -> - %% ?SEV_IPRINT("Socket buffers (before): " - %% "~n Rcv: ~p" - %% "~n Snd: ~p", - %% [socket:getopt(Sock, socket, rcvbuf), - %% socket:getopt(Sock, socket, sndbuf)]), {ok, RcvSz} = socket:getopt(Sock, socket, rcvbuf), if (RcvSz < size(Msg)) -> ok = socket:setopt(Sock, socket, rcvbuf, 1024+size(Msg)); @@ -7142,19 +7067,11 @@ traffic_ping_pong_large_send_and_receive_tcp(InitState) -> true -> ok end, - ok = socket:setopt(Sock, otp, rcvbuf, 8*1024), - %% ?SEV_IPRINT("Socket buffers (after): " - %% "~n Rcv: ~p" - %% "~n Snd: ~p", - %% [socket:getopt(Sock, socket, rcvbuf), - %% socket:getopt(Sock, socket, sndbuf)]), - ok - end, %% Fun to update the buffers: NEEDED here!!! - traffic_ping_pong_send_and_receive_tcp(InitState#{msg => Msg, - num => Num, - buf_init => Fun}). - -traffic_ping_pong_send_and_receive_tcp(InitState) -> + ok = socket:setopt(Sock, otp, rcvbuf, 8*1024) + end, + traffic_ping_pong_send_and_receive_tcp2(InitState#{buf_init => Fun}). + +traffic_ping_pong_send_and_receive_tcp2(InitState) -> ServerSeq = [ %% *** Wait for start order part *** @@ -7705,6 +7622,7 @@ tpp_tcp_handler(Parent) -> Result = tpp_tcp_handler_msg_exchange(Sock, Send, Recv), tpp_tcp_handler_announce_ready(Parent, recv, Result), Reason = tpp_tcp_handler_await_terminate(Parent), + ?SEV_IPRINT("terminating"), exit(Reason). tpp_tcp_handler_init(Parent) -> @@ -7806,6 +7724,7 @@ tpp_tcp_client(Parent, GL) -> tpp_tcp_client_announce_ready(Parent, send, Result), Reason = tpp_tcp_client_await_terminate(Parent), tpp_tcp_client_sock_close(Sock), + ?SEV_IPRINT("terminating"), exit(Reason). tpp_tcp_client_init(Parent, GL) -> -- cgit v1.2.3 From c6e94046261a608ee536c18cf631e33191e71bb1 Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Tue, 6 Nov 2018 15:18:31 +0100 Subject: [socket-nif] Badly handled socket close for recvfrom and recvmsg When type = dgram, the functions recvfrom and recvmsg did not properly handle socket close, cuaing the caller to hang indefinitely. OTP-14831 --- erts/emulator/nifs/common/socket_nif.c | 111 +++++++++++++++++++++------------ 1 file changed, 71 insertions(+), 40 deletions(-) diff --git a/erts/emulator/nifs/common/socket_nif.c b/erts/emulator/nifs/common/socket_nif.c index 70a969e867..f657da3ace 100644 --- a/erts/emulator/nifs/common/socket_nif.c +++ b/erts/emulator/nifs/common/socket_nif.c @@ -13825,6 +13825,7 @@ ERL_NIF_TERM recvfrom_check_result(ErlNifEnv* env, /* +++ Error handling +++ */ if (saveErrno == ECONNRESET) { + ERL_NIF_TERM res = esock_make_error(env, atom_closed); /* +++ Oups - closed +++ */ @@ -13842,12 +13843,14 @@ ERL_NIF_TERM recvfrom_check_result(ErlNifEnv* env, descP->closeLocal = FALSE; descP->state = SOCKET_STATE_CLOSING; + recv_error_current_reader(env, descP, res); + SELECT(env, descP->sock, (ERL_NIF_SELECT_STOP), descP, NULL, recvRef); - return esock_make_error(env, atom_closed); + return res; } else if ((saveErrno == ERRNO_BLOCK) || (saveErrno == EAGAIN)) { @@ -13862,12 +13865,15 @@ ERL_NIF_TERM recvfrom_check_result(ErlNifEnv* env, return esock_make_error(env, esock_atom_eagain); } else { + ERL_NIF_TERM res = esock_make_error_errno(env, saveErrno); SSDBG( descP, ("SOCKET", "recvfrom_check_result -> errno: %d\r\n", saveErrno) ); - return esock_make_error_errno(env, saveErrno); + recv_error_current_reader(env, descP, res); + + return res; } } else { @@ -13894,6 +13900,8 @@ ERL_NIF_TERM recvfrom_check_result(ErlNifEnv* env, data = MKSBIN(env, data, 0, read); } + recv_update_current_reader(env, descP); + return esock_make_ok2(env, MKT2(env, eSockAddr, data)); } @@ -13957,6 +13965,7 @@ ERL_NIF_TERM recvmsg_check_result(ErlNifEnv* env, /* +++ Error handling +++ */ if (saveErrno == ECONNRESET) { + ERL_NIF_TERM res = esock_make_error(env, atom_closed); /* +++ Oups - closed +++ */ @@ -13974,12 +13983,14 @@ ERL_NIF_TERM recvmsg_check_result(ErlNifEnv* env, descP->closeLocal = FALSE; descP->state = SOCKET_STATE_CLOSING; + recv_error_current_reader(env, descP, res); + SELECT(env, descP->sock, (ERL_NIF_SELECT_STOP), descP, NULL, recvRef); - return esock_make_error(env, atom_closed); + return res; } else if ((saveErrno == ERRNO_BLOCK) || (saveErrno == EAGAIN)) { @@ -13995,12 +14006,15 @@ ERL_NIF_TERM recvmsg_check_result(ErlNifEnv* env, return esock_make_error(env, esock_atom_eagain); } else { + ERL_NIF_TERM res = esock_make_error_errno(env, saveErrno); SSDBG( descP, ("SOCKET", "recvmsg_check_result -> errno: %d\r\n", saveErrno) ); - return esock_make_error_errno(env, saveErrno); + recv_error_current_reader(env, descP, res); + + return res; } } else { @@ -14029,6 +14043,8 @@ ERL_NIF_TERM recvmsg_check_result(ErlNifEnv* env, "recvmsg_check_result -> " "(msghdr) encode failed: %s\r\n", xres) ); + recv_update_current_reader(env, descP); + return esock_make_error_str(env, xres); } else { @@ -14037,6 +14053,8 @@ ERL_NIF_TERM recvmsg_check_result(ErlNifEnv* env, "recvmsg_check_result -> " "(msghdr) encode ok: %T\r\n", eMsgHdr) ); + recv_update_current_reader(env, descP); + return esock_make_ok2(env, eMsgHdr); } @@ -16913,18 +16931,22 @@ void socket_stop(ErlNifEnv* env, void* obj, int fd, int is_direct_call) SSDBG( descP, ("SOCKET", "socket_stop -> handle current writer\r\n") ); if (!compare_pids(env, &descP->closerPid, - &descP->currentWriter.pid) && - send_msg_nif_abort(env, - descP->currentWriter.ref, - atom_closed, - &descP->currentWriter.pid) != NULL) { - /* Shall we really do this? - * This happens if the controlling process has been killed! - */ - esock_warning_msg("Failed sending abort (%T) message to " - "current writer %T\r\n", - descP->currentWriter.ref, - descP->currentWriter.pid); + &descP->currentWriter.pid)) { + SSDBG( descP, ("SOCKET", "socket_stop -> " + "send abort message to current writer %T\r\n", + descP->currentWriter.pid) ); + if (send_msg_nif_abort(env, + descP->currentWriter.ref, + atom_closed, + &descP->currentWriter.pid) != NULL) { + /* Shall we really do this? + * This happens if the controlling process has been killed! + */ + esock_warning_msg("Failed sending abort (%T) message to " + "current writer %T\r\n", + descP->currentWriter.ref, + descP->currentWriter.pid); + } } /* And also deal with the waiting writers (in the same way) */ @@ -16948,18 +16970,22 @@ void socket_stop(ErlNifEnv* env, void* obj, int fd, int is_direct_call) SSDBG( descP, ("SOCKET", "socket_stop -> handle current reader\r\n") ); if (!compare_pids(env, &descP->closerPid, - &descP->currentReader.pid) && - send_msg_nif_abort(env, - descP->currentReader.ref, - atom_closed, - &descP->currentReader.pid) != NULL) { - /* Shall we really do this? - * This happens if the controlling process has been killed! - */ - esock_warning_msg("Failed sending abort (%T) message to " - "current reader %T\r\n", - descP->currentReader.ref, - descP->currentReader.pid); + &descP->currentReader.pid)) { + SSDBG( descP, ("SOCKET", "socket_stop -> " + "send abort message to current reader %T\r\n", + descP->currentReader.pid) ); + if (send_msg_nif_abort(env, + descP->currentReader.ref, + atom_closed, + &descP->currentReader.pid) != NULL) { + /* Shall we really do this? + * This happens if the controlling process has been killed! + */ + esock_warning_msg("Failed sending abort (%T) message to " + "current reader %T\r\n", + descP->currentReader.ref, + descP->currentReader.pid); + } } /* And also deal with the waiting readers (in the same way) */ @@ -16982,18 +17008,22 @@ void socket_stop(ErlNifEnv* env, void* obj, int fd, int is_direct_call) SSDBG( descP, ("SOCKET", "socket_stop -> handle current acceptor\r\n") ); if (!compare_pids(env, &descP->closerPid, - &descP->currentAcceptor.pid) && - send_msg_nif_abort(env, - descP->currentAcceptor.ref, - atom_closed, - &descP->currentAcceptor.pid) != NULL) { - /* Shall we really do this? - * This happens if the controlling process has been killed! - */ - esock_warning_msg("Failed sending abort (%T) message to " - "current acceptor %T\r\n", - descP->currentAcceptor.ref, - descP->currentAcceptor.pid); + &descP->currentAcceptor.pid)) { + SSDBG( descP, ("SOCKET", "socket_stop -> " + "send abort message to current acceptor %T\r\n", + descP->currentWriter.pid) ); + if (send_msg_nif_abort(env, + descP->currentAcceptor.ref, + atom_closed, + &descP->currentAcceptor.pid) != NULL) { + /* Shall we really do this? + * This happens if the controlling process has been killed! + */ + esock_warning_msg("Failed sending abort (%T) message to " + "current acceptor %T\r\n", + descP->currentAcceptor.ref, + descP->currentAcceptor.pid); + } } /* And also deal with the waiting acceptors (in the same way) */ @@ -17105,6 +17135,7 @@ void inform_waiting_procs(ErlNifEnv* env, currentP->data.ref, reason, ¤tP->data.pid)) ); + DEMONP("inform_waiting_procs -> current 'request'", env, descP, ¤tP->data.mon); nextP = currentP->nextP; -- cgit v1.2.3 From e3e607ac76dc308da3ac24364477d48da0dc23bd Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Tue, 6 Nov 2018 15:22:09 +0100 Subject: [socket-nif|test] Add UDP ping-pong test cases Added ping-pong test cases for UDP, small and medium, using the sendto/recvfrom and sendmsg/recvmsg functions. OTP-14831 --- erts/emulator/test/socket_SUITE.erl | 1451 ++++++++++++++++++++++++++++++----- erts/preloaded/ebin/socket.beam | Bin 69380 -> 69392 bytes erts/preloaded/src/socket.erl | 8 + 3 files changed, 1272 insertions(+), 187 deletions(-) diff --git a/erts/emulator/test/socket_SUITE.erl b/erts/emulator/test/socket_SUITE.erl index 3c20b6422a..4a381843a9 100644 --- a/erts/emulator/test/socket_SUITE.erl +++ b/erts/emulator/test/socket_SUITE.erl @@ -91,19 +91,30 @@ %% Traffic traffic_send_and_recv_chunks_tcp4/1, traffic_send_and_recv_chunks_tcp6/1, + traffic_ping_pong_small_send_and_recv_tcp4/1, traffic_ping_pong_small_send_and_recv_tcp6/1, - traffic_ping_pong_small_sendmsg_and_recvmsg_tcp4/1, - traffic_ping_pong_small_sendmsg_and_recvmsg_tcp6/1, traffic_ping_pong_medium_send_and_recv_tcp4/1, traffic_ping_pong_medium_send_and_recv_tcp6/1, - traffic_ping_pong_medium_sendmsg_and_recvmsg_tcp4/1, - traffic_ping_pong_medium_sendmsg_and_recvmsg_tcp6/1, traffic_ping_pong_large_send_and_recv_tcp4/1, traffic_ping_pong_large_send_and_recv_tcp6/1, + + traffic_ping_pong_small_sendto_and_recvfrom_udp4/1, + traffic_ping_pong_small_sendto_and_recvfrom_udp6/1, + traffic_ping_pong_medium_sendto_and_recvfrom_udp4/1, + traffic_ping_pong_medium_sendto_and_recvfrom_udp6/1, + + traffic_ping_pong_small_sendmsg_and_recvmsg_tcp4/1, + traffic_ping_pong_small_sendmsg_and_recvmsg_tcp6/1, + traffic_ping_pong_medium_sendmsg_and_recvmsg_tcp4/1, + traffic_ping_pong_medium_sendmsg_and_recvmsg_tcp6/1, traffic_ping_pong_large_sendmsg_and_recvmsg_tcp4/1, - traffic_ping_pong_large_sendmsg_and_recvmsg_tcp6/1 + traffic_ping_pong_large_sendmsg_and_recvmsg_tcp6/1, + traffic_ping_pong_small_sendmsg_and_recvmsg_udp4/1, + traffic_ping_pong_small_sendmsg_and_recvmsg_udp6/1, + traffic_ping_pong_medium_sendmsg_and_recvmsg_udp4/1, + traffic_ping_pong_medium_sendmsg_and_recvmsg_udp6/1 %% Tickets ]). @@ -131,9 +142,13 @@ -define(LIB, socket_test_lib). --define(PP_SMALL, lists:seq(1, 8)). --define(PP_MEDIUM, lists:flatten(lists:duplicate(1024, ?PP_SMALL))). --define(PP_LARGE, lists:flatten(lists:duplicate(1024, ?PP_MEDIUM))). +-define(TPP_SMALL, lists:seq(1, 8)). +-define(TPP_MEDIUM, lists:flatten(lists:duplicate(1024, ?TPP_SMALL))). +-define(TPP_LARGE, lists:flatten(lists:duplicate(1024, ?TPP_MEDIUM))). + +-define(TPP_SMALL_NUM, 100000). +-define(TPP_MEDIUM_NUM, 100000). +-define(TPP_LARGE_NUM, 1000). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -267,16 +282,27 @@ traffic_cases() -> traffic_ping_pong_small_send_and_recv_tcp4, traffic_ping_pong_small_send_and_recv_tcp6, - traffic_ping_pong_small_sendmsg_and_recvmsg_tcp4, - traffic_ping_pong_small_sendmsg_and_recvmsg_tcp6, traffic_ping_pong_medium_send_and_recv_tcp4, traffic_ping_pong_medium_send_and_recv_tcp6, - traffic_ping_pong_medium_sendmsg_and_recvmsg_tcp4, - traffic_ping_pong_medium_sendmsg_and_recvmsg_tcp6, traffic_ping_pong_large_send_and_recv_tcp4, traffic_ping_pong_large_send_and_recv_tcp6, + + traffic_ping_pong_small_sendto_and_recvfrom_udp4, + traffic_ping_pong_small_sendto_and_recvfrom_udp6, + traffic_ping_pong_medium_sendto_and_recvfrom_udp4, + traffic_ping_pong_medium_sendto_and_recvfrom_udp6, + + traffic_ping_pong_small_sendmsg_and_recvmsg_tcp4, + traffic_ping_pong_small_sendmsg_and_recvmsg_tcp6, + traffic_ping_pong_medium_sendmsg_and_recvmsg_tcp4, + traffic_ping_pong_medium_sendmsg_and_recvmsg_tcp6, traffic_ping_pong_large_sendmsg_and_recvmsg_tcp4, - traffic_ping_pong_large_sendmsg_and_recvmsg_tcp6 + traffic_ping_pong_large_sendmsg_and_recvmsg_tcp6, + + traffic_ping_pong_small_sendmsg_and_recvmsg_udp4, + traffic_ping_pong_small_sendmsg_and_recvmsg_udp6, + traffic_ping_pong_medium_sendmsg_and_recvmsg_udp4, + traffic_ping_pong_medium_sendmsg_and_recvmsg_udp6 ]. @@ -6713,8 +6739,8 @@ traffic_ping_pong_small_send_and_recv_tcp4(suite) -> traffic_ping_pong_small_send_and_recv_tcp4(doc) -> []; traffic_ping_pong_small_send_and_recv_tcp4(_Config) when is_list(_Config) -> - Msg = l2b(?PP_SMALL), - Num = 100000, + Msg = l2b(?TPP_SMALL), + Num = ?TPP_SMALL_NUM, tc_try(traffic_ping_pong_small_send_and_recv_tcp4, fun() -> ?TT(?SECS(15)), @@ -6739,8 +6765,8 @@ traffic_ping_pong_small_send_and_recv_tcp6(suite) -> traffic_ping_pong_small_send_and_recv_tcp6(doc) -> []; traffic_ping_pong_small_send_and_recv_tcp6(_Config) when is_list(_Config) -> - Msg = l2b(?PP_SMALL), - Num = 100000, + Msg = l2b(?TPP_SMALL), + Num = ?TPP_SMALL_NUM, tc_try(traffic_ping_pong_small_send_and_recv_tcp6, fun() -> not_yet_implemented(), @@ -6753,58 +6779,59 @@ traffic_ping_pong_small_send_and_recv_tcp6(_Config) when is_list(_Config) -> %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% This test case is intended to test that the sendmsg and recvmsg -%% functions by repeatedly sending a meassage between two entities. +%% This test case is intended to test that the send and recv functions +%% by repeatedly sending a meassage between two entities. %% The same basic test case is used for three different message sizes; %% small (8 bytes), medium (8K) and large (8M). %% The message is sent from A to B and then back again. This is %% repeated a set number of times (more times the small the message). -%% This is the 'small' message test case, for IPv4. +%% This is the 'medium' message test case, for IPv4. -traffic_ping_pong_small_sendmsg_and_recvmsg_tcp4(suite) -> +traffic_ping_pong_medium_send_and_recv_tcp4(suite) -> []; -traffic_ping_pong_small_sendmsg_and_recvmsg_tcp4(doc) -> +traffic_ping_pong_medium_send_and_recv_tcp4(doc) -> []; -traffic_ping_pong_small_sendmsg_and_recvmsg_tcp4(_Config) when is_list(_Config) -> - Msg = l2b(?PP_SMALL), - Num = 100000, - tc_try(traffic_ping_pong_small_sendmsg_and_recvmsg_tcp4, +traffic_ping_pong_medium_send_and_recv_tcp4(_Config) when is_list(_Config) -> + Msg = l2b(?TPP_MEDIUM), + Num = ?TPP_MEDIUM_NUM, + tc_try(traffic_ping_pong_medium_send_and_recv_tcp4, fun() -> - ?TT(?SECS(20)), + ?TT(?SECS(30)), InitState = #{domain => inet, msg => Msg, num => Num}, - ok = traffic_ping_pong_sendmsg_and_recvmsg_tcp(InitState) + ok = traffic_ping_pong_send_and_recv_tcp(InitState) end). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% This test case is intended to test that the sendmsg and recvmsg functions +%% This test case is intended to test that the send and recv functions %% by repeatedly sending a meassage between two entities. %% The same basic test case is used for three different message sizes; %% small (8 bytes), medium (8K) and large (8M). %% The message is sent from A to B and then back again. This is %% repeated a set number of times (more times the small the message). -%% This is the 'small' message test case, for IPv6. +%% This is the 'medium' message test case, for IPv6. -traffic_ping_pong_small_sendmsg_and_recvmsg_tcp6(suite) -> +traffic_ping_pong_medium_send_and_recv_tcp6(suite) -> []; -traffic_ping_pong_small_sendmsg_and_recvmsg_tcp6(doc) -> +traffic_ping_pong_medium_send_and_recv_tcp6(doc) -> []; -traffic_ping_pong_small_sendmsg_and_recvmsg_tcp6(_Config) when is_list(_Config) -> - Msg = l2b(?PP_SMALL), - Num = 100000, - tc_try(traffic_ping_pong_small_sendmsg_and_recvmsg_tcp6, +traffic_ping_pong_medium_send_and_recv_tcp6(_Config) when is_list(_Config) -> + Msg = l2b(?TPP_MEDIUM), + Num = ?TPP_MEDIUM_NUM, + tc_try(traffic_ping_pong_medium_send_and_recv_tcp6, fun() -> not_yet_implemented(), - ?TT(?SECS(20)), - InitState = #{domain => inet, + ?TT(?SECS(30)), + InitState = #{domain => inet6, msg => Msg, num => Num}, - ok = traffic_ping_pong_sendmsg_and_recvmsg_tcp(InitState) + ok = traffic_ping_pong_send_and_recv_tcp(InitState) end). + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% This test case is intended to test that the send and recv functions %% by repeatedly sending a meassage between two entities. @@ -6812,18 +6839,18 @@ traffic_ping_pong_small_sendmsg_and_recvmsg_tcp6(_Config) when is_list(_Config) %% small (8 bytes), medium (8K) and large (8M). %% The message is sent from A to B and then back again. This is %% repeated a set number of times (more times the small the message). -%% This is the 'medium' message test case, for IPv4. +%% This is the 'large' message test case, for IPv4. -traffic_ping_pong_medium_send_and_recv_tcp4(suite) -> +traffic_ping_pong_large_send_and_recv_tcp4(suite) -> []; -traffic_ping_pong_medium_send_and_recv_tcp4(doc) -> +traffic_ping_pong_large_send_and_recv_tcp4(doc) -> []; -traffic_ping_pong_medium_send_and_recv_tcp4(_Config) when is_list(_Config) -> - Msg = l2b(?PP_MEDIUM), - Num = 100000, - tc_try(traffic_ping_pong_medium_send_and_recv_tcp4, +traffic_ping_pong_large_send_and_recv_tcp4(_Config) when is_list(_Config) -> + Msg = l2b(?TPP_LARGE), + Num = ?TPP_LARGE_NUM, + tc_try(traffic_ping_pong_large_send_and_recv_tcp4, fun() -> - ?TT(?SECS(30)), + ?TT(?SECS(45)), InitState = #{domain => inet, msg => Msg, num => Num}, @@ -6838,19 +6865,19 @@ traffic_ping_pong_medium_send_and_recv_tcp4(_Config) when is_list(_Config) -> %% small (8 bytes), medium (8K) and large (8M). %% The message is sent from A to B and then back again. This is %% repeated a set number of times (more times the small the message). -%% This is the 'medium' message test case, for IPv6. +%% This is the 'large' message test case, for IPv6. -traffic_ping_pong_medium_send_and_recv_tcp6(suite) -> +traffic_ping_pong_large_send_and_recv_tcp6(suite) -> []; -traffic_ping_pong_medium_send_and_recv_tcp6(doc) -> +traffic_ping_pong_large_send_and_recv_tcp6(doc) -> []; -traffic_ping_pong_medium_send_and_recv_tcp6(_Config) when is_list(_Config) -> - Msg = l2b(?PP_MEDIUM), - Num = 100000, - tc_try(traffic_ping_pong_medium_send_and_recv_tcp6, +traffic_ping_pong_large_send_and_recv_tcp6(_Config) when is_list(_Config) -> + Msg = l2b(?TPP_LARGE), + Num = ?TPP_LARGE_NUM, + tc_try(traffic_ping_pong_large_send_and_recv_tcp6, fun() -> not_yet_implemented(), - ?TT(?SECS(30)), + ?TT(?SECS(45)), InitState = #{domain => inet6, msg => Msg, num => Num}, @@ -6859,6 +6886,112 @@ traffic_ping_pong_medium_send_and_recv_tcp6(_Config) when is_list(_Config) -> +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test that the sendto and recvfrom +%% functions by repeatedly sending a meassage between two entities. +%% The same basic test case is used for two different message sizes; +%% small (8 bytes) and medium (8K). +%% The message is sent from A to B and then back again. This is +%% repeated a set number of times (more times the small the message). +%% This is the 'small' message test case, for IPv4. + +traffic_ping_pong_small_sendto_and_recvfrom_udp4(suite) -> + []; +traffic_ping_pong_small_sendto_and_recvfrom_udp4(doc) -> + []; +traffic_ping_pong_small_sendto_and_recvfrom_udp4(_Config) when is_list(_Config) -> + Msg = l2b(?TPP_SMALL), + Num = ?TPP_SMALL_NUM, + tc_try(traffic_ping_pong_small_sendto_and_recvfrom_udp4, + fun() -> + ?TT(?SECS(45)), + InitState = #{domain => inet, + msg => Msg, + num => Num}, + ok = traffic_ping_pong_sendto_and_recvfrom_udp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test that the sendto and recvfrom +%% functions by repeatedly sending a meassage between two entities. +%% The same basic test case is used for two different message sizes; +%% small (8 bytes) and medium (8K). +%% The message is sent from A to B and then back again. This is +%% repeated a set number of times (more times the small the message). +%% This is the 'small' message test case, for IPv6. + +traffic_ping_pong_small_sendto_and_recvfrom_udp6(suite) -> + []; +traffic_ping_pong_small_sendto_and_recvfrom_udp6(doc) -> + []; +traffic_ping_pong_small_sendto_and_recvfrom_udp6(_Config) when is_list(_Config) -> + Msg = l2b(?TPP_SMALL), + Num = ?TPP_SMALL_NUM, + tc_try(traffic_ping_pong_small_sendto_and_recvfrom_udp6, + fun() -> + ?TT(?SECS(45)), + InitState = #{domain => inet, + msg => Msg, + num => Num}, + ok = traffic_ping_pong_sendto_and_recvfrom_udp(InitState) + end). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test that the sendto and recvfrom +%% functions by repeatedly sending a meassage between two entities. +%% The same basic test case is used for two different message sizes; +%% small (8 bytes) and medium (8K). +%% The message is sent from A to B and then back again. This is +%% repeated a set number of times (more times the small the message). +%% This is the 'medium' message test case, for IPv4. + +traffic_ping_pong_medium_sendto_and_recvfrom_udp4(suite) -> + []; +traffic_ping_pong_medium_sendto_and_recvfrom_udp4(doc) -> + []; +traffic_ping_pong_medium_sendto_and_recvfrom_udp4(_Config) when is_list(_Config) -> + Msg = l2b(?TPP_MEDIUM), + Num = ?TPP_MEDIUM_NUM, + tc_try(traffic_ping_pong_medium_sendto_and_recvfrom_udp4, + fun() -> + ?TT(?SECS(45)), + InitState = #{domain => inet, + msg => Msg, + num => Num}, + ok = traffic_ping_pong_sendto_and_recvfrom_udp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test that the sendto and recvfrom +%% functions by repeatedly sending a meassage between two entities. +%% The same basic test case is used for two different message sizes; +%% small (8 bytes) and medium (8K). +%% The message is sent from A to B and then back again. This is +%% repeated a set number of times (more times the small the message). +%% This is the 'medium' message test case, for IPv6. + +traffic_ping_pong_medium_sendto_and_recvfrom_udp6(suite) -> + []; +traffic_ping_pong_medium_sendto_and_recvfrom_udp6(doc) -> + []; +traffic_ping_pong_medium_sendto_and_recvfrom_udp6(_Config) when is_list(_Config) -> + Msg = l2b(?TPP_MEDIUM), + Num = ?TPP_MEDIUM_NUM, + tc_try(traffic_ping_pong_medium_sendto_and_recvfrom_udp6, + fun() -> + ?TT(?SECS(45)), + InitState = #{domain => inet, + msg => Msg, + num => Num}, + ok = traffic_ping_pong_sendto_and_recvfrom_udp(InitState) + end). + + + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% This test case is intended to test that the sendmsg and recvmsg %% functions by repeatedly sending a meassage between two entities. @@ -6866,18 +6999,18 @@ traffic_ping_pong_medium_send_and_recv_tcp6(_Config) when is_list(_Config) -> %% small (8 bytes), medium (8K) and large (8M). %% The message is sent from A to B and then back again. This is %% repeated a set number of times (more times the small the message). -%% This is the 'medium' message test case, for IPv4. +%% This is the 'small' message test case, for IPv4. -traffic_ping_pong_medium_sendmsg_and_recvmsg_tcp4(suite) -> +traffic_ping_pong_small_sendmsg_and_recvmsg_tcp4(suite) -> []; -traffic_ping_pong_medium_sendmsg_and_recvmsg_tcp4(doc) -> +traffic_ping_pong_small_sendmsg_and_recvmsg_tcp4(doc) -> []; -traffic_ping_pong_medium_sendmsg_and_recvmsg_tcp4(_Config) when is_list(_Config) -> - Msg = l2b(?PP_MEDIUM), - Num = 100000, - tc_try(traffic_ping_pong_medium_sendmsg_and_recvmsg_tcp4, +traffic_ping_pong_small_sendmsg_and_recvmsg_tcp4(_Config) when is_list(_Config) -> + Msg = l2b(?TPP_SMALL), + Num = ?TPP_SMALL_NUM, + tc_try(traffic_ping_pong_small_sendmsg_and_recvmsg_tcp4, fun() -> - ?TT(?SECS(30)), + ?TT(?SECS(20)), InitState = #{domain => inet, msg => Msg, num => Num}, @@ -6892,20 +7025,20 @@ traffic_ping_pong_medium_sendmsg_and_recvmsg_tcp4(_Config) when is_list(_Config) %% small (8 bytes), medium (8K) and large (8M). %% The message is sent from A to B and then back again. This is %% repeated a set number of times (more times the small the message). -%% This is the 'medium' message test case, for IPv6. +%% This is the 'small' message test case, for IPv6. -traffic_ping_pong_medium_sendmsg_and_recvmsg_tcp6(suite) -> +traffic_ping_pong_small_sendmsg_and_recvmsg_tcp6(suite) -> []; -traffic_ping_pong_medium_sendmsg_and_recvmsg_tcp6(doc) -> +traffic_ping_pong_small_sendmsg_and_recvmsg_tcp6(doc) -> []; -traffic_ping_pong_medium_sendmsg_and_recvmsg_tcp6(_Config) when is_list(_Config) -> - Msg = l2b(?PP_MEDIUM), - Num = 100000, - tc_try(traffic_ping_pong_medium_sendmsg_and_recvmsg_tcp6, +traffic_ping_pong_small_sendmsg_and_recvmsg_tcp6(_Config) when is_list(_Config) -> + Msg = l2b(?TPP_SMALL), + Num = ?TPP_SMALL_NUM, + tc_try(traffic_ping_pong_small_sendmsg_and_recvmsg_tcp6, fun() -> not_yet_implemented(), ?TT(?SECS(20)), - InitState = #{domain => ine6, + InitState = #{domain => inet, msg => Msg, num => Num}, ok = traffic_ping_pong_sendmsg_and_recvmsg_tcp(InitState) @@ -6913,59 +7046,58 @@ traffic_ping_pong_medium_sendmsg_and_recvmsg_tcp6(_Config) when is_list(_Config) %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% This test case is intended to test that the send and recv functions -%% by repeatedly sending a meassage between two entities. +%% This test case is intended to test that the sendmsg and recvmsg +%% functions by repeatedly sending a meassage between two entities. %% The same basic test case is used for three different message sizes; %% small (8 bytes), medium (8K) and large (8M). %% The message is sent from A to B and then back again. This is %% repeated a set number of times (more times the small the message). -%% This is the 'large' message test case, for IPv4. +%% This is the 'medium' message test case, for IPv4. -traffic_ping_pong_large_send_and_recv_tcp4(suite) -> +traffic_ping_pong_medium_sendmsg_and_recvmsg_tcp4(suite) -> []; -traffic_ping_pong_large_send_and_recv_tcp4(doc) -> +traffic_ping_pong_medium_sendmsg_and_recvmsg_tcp4(doc) -> []; -traffic_ping_pong_large_send_and_recv_tcp4(_Config) when is_list(_Config) -> - Msg = l2b(?PP_LARGE), - Num = 1000, - tc_try(traffic_ping_pong_large_send_and_recv_tcp4, +traffic_ping_pong_medium_sendmsg_and_recvmsg_tcp4(_Config) when is_list(_Config) -> + Msg = l2b(?TPP_MEDIUM), + Num = ?TPP_MEDIUM_NUM, + tc_try(traffic_ping_pong_medium_sendmsg_and_recvmsg_tcp4, fun() -> - ?TT(?SECS(45)), + ?TT(?SECS(30)), InitState = #{domain => inet, msg => Msg, num => Num}, - ok = traffic_ping_pong_send_and_recv_tcp(InitState) + ok = traffic_ping_pong_sendmsg_and_recvmsg_tcp(InitState) end). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% This test case is intended to test that the send and recv functions +%% This test case is intended to test that the sendmsg and recvmsg functions %% by repeatedly sending a meassage between two entities. %% The same basic test case is used for three different message sizes; %% small (8 bytes), medium (8K) and large (8M). %% The message is sent from A to B and then back again. This is %% repeated a set number of times (more times the small the message). -%% This is the 'large' message test case, for IPv6. +%% This is the 'medium' message test case, for IPv6. -traffic_ping_pong_large_send_and_recv_tcp6(suite) -> +traffic_ping_pong_medium_sendmsg_and_recvmsg_tcp6(suite) -> []; -traffic_ping_pong_large_send_and_recv_tcp6(doc) -> +traffic_ping_pong_medium_sendmsg_and_recvmsg_tcp6(doc) -> []; -traffic_ping_pong_large_send_and_recv_tcp6(_Config) when is_list(_Config) -> - Msg = l2b(?PP_LARGE), - Num = 1000, - tc_try(traffic_ping_pong_large_send_and_recv_tcp6, +traffic_ping_pong_medium_sendmsg_and_recvmsg_tcp6(_Config) when is_list(_Config) -> + Msg = l2b(?TPP_MEDIUM), + Num = ?TPP_MEDIUM_NUM, + tc_try(traffic_ping_pong_medium_sendmsg_and_recvmsg_tcp6, fun() -> not_yet_implemented(), - ?TT(?SECS(45)), - InitState = #{domain => inet6, + ?TT(?SECS(20)), + InitState = #{domain => ine6, msg => Msg, num => Num}, - ok = traffic_ping_pong_send_and_recv_tcp(InitState) + ok = traffic_ping_pong_sendmsg_and_recvmsg_tcp(InitState) end). - %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% This test case is intended to test that the sendmsg and recvmsg %% functions by repeatedly sending a meassage between two entities. @@ -6980,8 +7112,8 @@ traffic_ping_pong_large_sendmsg_and_recvmsg_tcp4(suite) -> traffic_ping_pong_large_sendmsg_and_recvmsg_tcp4(doc) -> []; traffic_ping_pong_large_sendmsg_and_recvmsg_tcp4(_Config) when is_list(_Config) -> - Msg = l2b(?PP_LARGE), - Num = 1000, + Msg = l2b(?TPP_LARGE), + Num = ?TPP_LARGE_NUM, tc_try(traffic_ping_pong_large_sendmsg_and_recvmsg_tcp4, fun() -> ?TT(?SECS(30)), @@ -7006,8 +7138,8 @@ traffic_ping_pong_large_sendmsg_and_recvmsg_tcp6(suite) -> traffic_ping_pong_large_sendmsg_and_recvmsg_tcp6(doc) -> []; traffic_ping_pong_large_sendmsg_and_recvmsg_tcp6(_Config) when is_list(_Config) -> - Msg = l2b(?PP_LARGE), - Num = 1000, + Msg = l2b(?TPP_LARGE), + Num = ?TPP_LARGE_NUM, tc_try(traffic_ping_pong_large_sendmsg_and_recvmsg_tcp6, fun() -> not_yet_implemented(), @@ -7021,40 +7153,149 @@ traffic_ping_pong_large_sendmsg_and_recvmsg_tcp6(_Config) when is_list(_Config) %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test that the sendmsg and recvmsg +%% functions by repeatedly sending a meassage between two entities. +%% The same basic test case is used for three different message sizes; +%% small (8 bytes) and medium (8K). +%% The message is sent from A to B and then back again. This is +%% repeated a set number of times (more times the small the message). +%% This is the 'small' message test case, for IPv4. -traffic_ping_pong_send_and_recv_tcp(InitState) -> - Send = fun(Sock, Data) -> socket:send(Sock, Data) end, - Recv = fun(Sock, Sz) -> socket:recv(Sock, Sz) end, - InitState2 = InitState#{send => Send, % Send function - recv => Recv % Receive function - }, - traffic_ping_pong_send_and_receive_tcp(InitState2). - -traffic_ping_pong_sendmsg_and_recvmsg_tcp(InitState) -> - Send = fun(Sock, Data) when is_binary(Data) -> - MsgHdr = #{iov => [Data]}, - socket:sendmsg(Sock, MsgHdr); - (Sock, Data) when is_list(Data) -> %% We assume iovec... - MsgHdr = #{iov => Data}, - socket:sendmsg(Sock, MsgHdr) - end, - Recv = fun(Sock, Sz) -> - case socket:recvmsg(Sock, Sz, 0) of - {ok, #{addr := undefined, - iov := [Data]}} -> - {ok, Data}; - {error, _} = ERROR -> - ERROR - end - end, - InitState2 = InitState#{send => Send, % Send function - recv => Recv % Receive function - }, - traffic_ping_pong_send_and_receive_tcp(InitState2). +traffic_ping_pong_small_sendmsg_and_recvmsg_udp4(suite) -> + []; +traffic_ping_pong_small_sendmsg_and_recvmsg_udp4(doc) -> + []; +traffic_ping_pong_small_sendmsg_and_recvmsg_udp4(_Config) when is_list(_Config) -> + Msg = l2b(?TPP_SMALL), + Num = ?TPP_SMALL_NUM, + tc_try(traffic_ping_pong_small_sendmsg_and_recvmsg_udp4, + fun() -> + ?TT(?SECS(20)), + InitState = #{domain => inet, + msg => Msg, + num => Num}, + ok = traffic_ping_pong_sendmsg_and_recvmsg_udp(InitState) + end). -traffic_ping_pong_send_and_receive_tcp(#{msg := Msg} = InitState) -> - Fun = fun(Sock) -> +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test that the sendmsg and recvmsg functions +%% by repeatedly sending a meassage between two entities. +%% The same basic test case is used for three different message sizes; +%% small (8 bytes) and medium (8K). +%% The message is sent from A to B and then back again. This is +%% repeated a set number of times (more times the small the message). +%% This is the 'small' message test case, for IPv6. + +traffic_ping_pong_small_sendmsg_and_recvmsg_udp6(suite) -> + []; +traffic_ping_pong_small_sendmsg_and_recvmsg_udp6(doc) -> + []; +traffic_ping_pong_small_sendmsg_and_recvmsg_udp6(_Config) when is_list(_Config) -> + Msg = l2b(?TPP_SMALL), + Num = ?TPP_SMALL_NUM, + tc_try(traffic_ping_pong_small_sendmsg_and_recvmsg_udp6, + fun() -> + not_yet_implemented(), + ?TT(?SECS(20)), + InitState = #{domain => inet, + msg => Msg, + num => Num}, + ok = traffic_ping_pong_sendmsg_and_recvmsg_udp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test that the sendmsg and recvmsg +%% functions by repeatedly sending a meassage between two entities. +%% The same basic test case is used for three different message sizes; +%% small (8 bytes) and medium (8K). +%% The message is sent from A to B and then back again. This is +%% repeated a set number of times (more times the small the message). +%% This is the 'medium' message test case, for IPv4. + +traffic_ping_pong_medium_sendmsg_and_recvmsg_udp4(suite) -> + []; +traffic_ping_pong_medium_sendmsg_and_recvmsg_udp4(doc) -> + []; +traffic_ping_pong_medium_sendmsg_and_recvmsg_udp4(_Config) when is_list(_Config) -> + Msg = l2b(?TPP_MEDIUM), + Num = ?TPP_MEDIUM_NUM, + tc_try(traffic_ping_pong_medium_sendmsg_and_recvmsg_udp4, + fun() -> + ?TT(?SECS(30)), + InitState = #{domain => inet, + msg => Msg, + num => Num}, + ok = traffic_ping_pong_sendmsg_and_recvmsg_udp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test that the sendmsg and recvmsg +%% functions by repeatedly sending a meassage between two entities. +%% The same basic test case is used for three different message sizes; +%% small (8 bytes) and medium (8K). +%% The message is sent from A to B and then back again. This is +%% repeated a set number of times (more times the small the message). +%% This is the 'medium' message test case, for IPv6. + +traffic_ping_pong_medium_sendmsg_and_recvmsg_udp6(suite) -> + []; +traffic_ping_pong_medium_sendmsg_and_recvmsg_udp6(doc) -> + []; +traffic_ping_pong_medium_sendmsg_and_recvmsg_udp6(_Config) when is_list(_Config) -> + Msg = l2b(?TPP_MEDIUM), + Num = ?TPP_MEDIUM_NUM, + tc_try(traffic_ping_pong_medium_sendmsg_and_recvmsg_udp6, + fun() -> + not_yet_implemented(), + ?TT(?SECS(20)), + InitState = #{domain => ine6, + msg => Msg, + num => Num}, + ok = traffic_ping_pong_sendmsg_and_recvmsg_udp(InitState) + end). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Ping-Pong for TCP + +traffic_ping_pong_send_and_recv_tcp(InitState) -> + Send = fun(Sock, Data) -> socket:send(Sock, Data) end, + Recv = fun(Sock, Sz) -> socket:recv(Sock, Sz) end, + InitState2 = InitState#{send => Send, % Send function + recv => Recv % Receive function + }, + traffic_ping_pong_send_and_receive_tcp(InitState2). + +traffic_ping_pong_sendmsg_and_recvmsg_tcp(InitState) -> + Send = fun(Sock, Data) when is_binary(Data) -> + MsgHdr = #{iov => [Data]}, + socket:sendmsg(Sock, MsgHdr); + (Sock, Data) when is_list(Data) -> %% We assume iovec... + MsgHdr = #{iov => Data}, + socket:sendmsg(Sock, MsgHdr) + end, + Recv = fun(Sock, Sz) -> + case socket:recvmsg(Sock, Sz, 0) of + {ok, #{addr := undefined, + iov := [Data]}} -> + {ok, Data}; + {error, _} = ERROR -> + ERROR + end + end, + InitState2 = InitState#{send => Send, % Send function + recv => Recv % Receive function + }, + traffic_ping_pong_send_and_receive_tcp(InitState2). + + +traffic_ping_pong_send_and_receive_tcp(#{msg := Msg} = InitState) -> + Fun = fun(Sock) -> {ok, RcvSz} = socket:getopt(Sock, socket, rcvbuf), if (RcvSz < size(Msg)) -> ok = socket:setopt(Sock, socket, rcvbuf, 1024+size(Msg)); @@ -7918,58 +8159,894 @@ tpp_tcp_send_msg(Sock, Send, Msg, AccSz) when is_binary(Msg) -> %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% This gets the local address (not 127.0...) -%% We should really implement this using the (new) net module, -%% but until that gets the necessary functionality... -which_local_addr(Domain) -> - case inet:getifaddrs() of - {ok, IFL} -> - which_addr(Domain, IFL); - {error, Reason} -> - ?FAIL({inet, getifaddrs, Reason}) - end. +%% Ping-Pong for UDP -which_addr(_Domain, []) -> - ?FAIL(no_address); -which_addr(Domain, [{Name, IFO}|_IFL]) when (Name =/= "lo") -> - which_addr2(Domain, IFO); -which_addr(Domain, [_|IFL]) -> - which_addr(Domain, IFL). +traffic_ping_pong_sendto_and_recvfrom_udp(InitState) -> + Send = fun(Sock, Data, Dest) -> + socket:sendto(Sock, Data, Dest) + end, + Recv = fun(Sock, Sz) -> + socket:recvfrom(Sock, Sz) + end, + InitState2 = InitState#{send => Send, % Send function + recv => Recv % Receive function + }, + traffic_ping_pong_send_and_receive_udp(InitState2). -which_addr2(_Domain, []) -> - ?FAIL(no_address); -which_addr2(inet = _Domain, [{addr, Addr}|_IFO]) when (size(Addr) =:= 4) -> - Addr; -which_addr2(inet6 = _Domain, [{addr, Addr}|_IFO]) when (size(Addr) =:= 8) -> - Addr; -which_addr2(Domain, [_|IFO]) -> - which_addr2(Domain, IFO). - +traffic_ping_pong_sendmsg_and_recvmsg_udp(InitState) -> + Send = fun(Sock, Data, Dest) when is_binary(Data) -> + MsgHdr = #{addr => Dest, iov => [Data]}, + socket:sendmsg(Sock, MsgHdr); + (Sock, Data, Dest) when is_list(Data) -> %% We assume iovec... + MsgHdr = #{addr => Dest, iov => Data}, + socket:sendmsg(Sock, MsgHdr) + end, + Recv = fun(Sock, Sz) -> + case socket:recvmsg(Sock, Sz, 0) of + {ok, #{addr := Source, + iov := [Data]}} -> + {ok, {Source, Data}}; + {error, _} = ERROR -> + ERROR + end + end, + InitState2 = InitState#{send => Send, % Send function + recv => Recv % Receive function + }, + traffic_ping_pong_send_and_receive_udp(InitState2). -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +traffic_ping_pong_send_and_receive_udp(#{msg := Msg} = InitState) -> + Fun = fun(Sock) -> + {ok, RcvSz} = socket:getopt(Sock, socket, rcvbuf), + if (RcvSz < size(Msg)) -> + ok = socket:setopt(Sock, socket, rcvbuf, 1024+size(Msg)); + true -> + ok + end, + {ok, SndSz} = socket:getopt(Sock, socket, sndbuf), + if (SndSz < size(Msg)) -> + ok = socket:setopt(Sock, socket, sndbuf, 1024+size(Msg)); + true -> + ok + end, + {ok, OtpRcvBuf} = socket:getopt(Sock, otp, rcvbuf), + if + (OtpRcvBuf < size(Msg)) -> + ok = socket:setopt(Sock, otp, rcvbuf, 1024+size(Msg)); + true -> + ok + end + end, + traffic_ping_pong_send_and_receive_udp2(InitState#{buf_init => Fun}). -start_node(Host, NodeName) -> - UniqueNodeName = f("~w_~w", [NodeName, erlang:system_time(millisecond)]), - case do_start_node(Host, UniqueNodeName) of - {ok, _} = OK -> - OK; - {error, Reason, _} -> - {error, Reason} - end. +traffic_ping_pong_send_and_receive_udp2(InitState) -> + ServerSeq = + [ + %% *** Wait for start order part *** + #{desc => "await start", + cmd => fun(State) -> + Tester = ?SEV_AWAIT_START(), + {ok, State#{tester => Tester}} + end}, + #{desc => "monitor tester", + cmd => fun(#{tester := Tester} = _State) -> + _MRef = erlang:monitor(process, Tester), + ok + end}, -do_start_node(Host, NodeName) when is_list(NodeName) -> - do_start_node(Host, list_to_atom(NodeName)); -do_start_node(Host, NodeName) when is_atom(NodeName) -> - Dir = filename:dirname(code:which(?MODULE)), - Flags = "-pa " ++ Dir, - Opts = [{monitor_master, true}, {erl_flags, Flags}], - ct_slave:start(Host, NodeName, Opts). + %% *** Init part *** + #{desc => "which local address", + cmd => fun(#{domain := Domain} = State) -> + LAddr = which_local_addr(Domain), + LSA = #{family => Domain, addr => LAddr}, + {ok, State#{local_sa => LSA}} + end}, + #{desc => "create listen socket", + cmd => fun(#{domain := Domain} = State) -> + case socket:open(Domain, dgram, udp) of + {ok, Sock} -> + {ok, State#{sock => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "bind to local address", + cmd => fun(#{sock := Sock, local_sa := LSA} = State) -> + case socket:bind(Sock, LSA) of + {ok, Port} -> + {ok, State#{port => Port}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "maybe init buffers", + cmd => fun(#{sock := Sock, buf_init := BufInit} = _State) -> + BufInit(Sock) + end}, + #{desc => "create handler", + cmd => fun(State) -> + Handler = tpp_udp_server_handler_create(), + ?SEV_IPRINT("handler created: ~p", [Handler]), + {ok, State#{handler => Handler}} + end}, + #{desc => "monitor handler", + cmd => fun(#{handler := Handler} = _State) -> + _MRef = erlang:monitor(process, Handler), + ok + end}, + #{desc => "start handler", + cmd => fun(#{handler := Handler, + sock := Sock, + send := Send, + recv := Recv} = _State) -> + ?SEV_ANNOUNCE_START(Handler, {Sock, Send, Recv}), + ok + end}, + #{desc => "await handler ready (init)", + cmd => fun(#{tester := Tester, + handler := Handler} = State) -> + case ?SEV_AWAIT_READY(Handler, handler, init, + [{tester, Tester}]) of + ok -> + {ok, maps:remove(csock, State)}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (init)", + cmd => fun(#{tester := Tester, local_sa := LSA, port := Port}) -> + ServerSA = LSA#{port => Port}, + ?SEV_ANNOUNCE_READY(Tester, init, ServerSA), + ok + end}, + %% The actual test + #{desc => "await continue (recv)", + cmd => fun(#{tester := Tester, + handler := Handler} = _State) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, recv, + [{handler, Handler}]) + end}, + #{desc => "order handler to recv", + cmd => fun(#{handler := Handler} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Handler, recv), + ok + end}, + #{desc => "await continue (close)", + cmd => fun(#{tester := Tester, + handler := Handler} = _State) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, close, + [{handler, Handler}]) + end}, -stop_node(Node) -> - case ct_slave:stop(Node) of - {ok, _} -> + ?SEV_SLEEP(?SECS(1)), + + #{desc => "close socket", + cmd => fun(#{sock := Sock} = State) -> + %% socket:setopt(Sock, otp, debug, true), + case socket:close(Sock) of + ok -> + {ok, maps:remove(sock, State)}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (close)", + cmd => fun(#{tester := Tester} = _State) -> + ?SEV_ANNOUNCE_READY(Tester, close), + ok + end}, + #{desc => "await handler ready (recv)", + cmd => fun(#{tester := Tester, + handler := Handler} = State) -> + case ?SEV_AWAIT_READY(Handler, handler, recv, + [{tester, Tester}]) of + {ok, Result} -> + %% ?SEV_IPRINT("Result: ~p", [Result]), + {ok, State#{result => Result}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (recv)", + cmd => fun(#{tester := Tester, + result := Result} = State) -> + ?SEV_ANNOUNCE_READY(Tester, recv, Result), + {ok, maps:remove(result, State)} + end}, + + %% Termination + #{desc => "await terminate (from tester)", + cmd => fun(#{tester := Tester} = State) -> + case ?SEV_AWAIT_TERMINATE(Tester, tester) of + ok -> + {ok, maps:remove(tester, State)}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "stop handler", + cmd => fun(#{handler := Handler}) -> + ?SEV_ANNOUNCE_TERMINATE(Handler), + ok + end}, + #{desc => "await handler termination", + cmd => fun(#{handler := Handler} = State) -> + ?SEV_AWAIT_TERMINATION(Handler), + State1 = maps:remove(handler, State), + {ok, State1} + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + ClientSeq = + [ + %% *** Wait for start order part *** + #{desc => "await start", + cmd => fun(State) -> + {Tester, ServerSA} = ?SEV_AWAIT_START(), + {ok, State#{tester => Tester, + server_sa => ServerSA}} + end}, + #{desc => "monitor tester", + cmd => fun(#{tester := Tester} = _State) -> + _MRef = erlang:monitor(process, Tester), + ok + end}, + + %% *** Init part *** + #{desc => "create node", + cmd => fun(#{host := Host} = State) -> + case start_node(Host, client) of + {ok, Node} -> + ?SEV_IPRINT("(remote) client node ~p started", + [Node]), + {ok, State#{node => Node}}; + {error, Reason, _} -> + {error, Reason} + end + end}, + #{desc => "monitor client node", + cmd => fun(#{node := Node} = _State) -> + true = erlang:monitor_node(Node, true), + ok + end}, + #{desc => "create (remote) handler", + cmd => fun(#{node := Node} = State) -> + Pid = tpp_udp_client_handler_create(Node), + ?SEV_IPRINT("handler created: ~p", [Pid]), + {ok, State#{handler => Pid}} + end}, + #{desc => "monitor remote handler", + cmd => fun(#{handler := Pid}) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + #{desc => "order remote handler to start", + cmd => fun(#{handler := Handler, + server_sa := ServerSA, + buf_init := BufInit, + send := Send, + recv := Recv}) -> + ?SEV_ANNOUNCE_START(Handler, + {ServerSA, BufInit, Send, Recv}), + ok + end}, + #{desc => "await (remote) handler ready", + cmd => fun(#{tester := Tester, + handler := Handler} = _State) -> + ?SEV_AWAIT_READY(Handler, handler, init, + [{tester, Tester}]) + end}, + #{desc => "announce ready (init)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, init), + ok + end}, + + %% The actual test + #{desc => "await continue (send)", + cmd => fun(#{tester := Tester, + handler := Handler} = _State) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, + send, + [{handler, Handler}]) + end}, + #{desc => "order handler to continue (send)", + cmd => fun(#{handler := Handler, + msg := Msg, + num := Num} = State) -> + Data = {Msg, Num}, + ?SEV_ANNOUNCE_CONTINUE(Handler, send, Data), + {ok, maps:remove(data, State)} + end}, + #{desc => "await remote handler ready (send)", + cmd => fun(#{tester := Tester, + handler := Handler} = State) -> + case ?SEV_AWAIT_READY(Handler, handler, send, + [{tester, Tester}]) of + {ok, Result} -> + %% ?SEV_IPRINT("remote client result: " + %% "~n ~p", [Result]), + {ok, State#{result => Result}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (send)", + cmd => fun(#{tester := Tester, result := Result} = State) -> + ?SEV_ANNOUNCE_READY(Tester, send, Result), + {ok, maps:remove(result, State)} + end}, + + %% Termination + #{desc => "await terminate (from tester)", + cmd => fun(#{tester := Tester, + handler := Handler} = State) -> + case ?SEV_AWAIT_TERMINATE(Tester, tester, + [{handler, Handler}]) of + ok -> + {ok, maps:remove(tester, State)}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "stop (remote) handler", + cmd => fun(#{handler := Handler}) -> + ?SEV_ANNOUNCE_TERMINATE(Handler), + ok + end}, + #{desc => "await (remote) handler termination", + cmd => fun(#{handler := Handler} = State) -> + ?SEV_AWAIT_TERMINATION(Handler), + State1 = maps:remove(handler, State), + {ok, State1} + end}, + #{desc => "stop client node", + cmd => fun(#{node := Node} = _State) -> + stop_node(Node) + end}, + #{desc => "await client node termination", + cmd => fun(#{node := Node} = State) -> + receive + {nodedown, Node} -> + {ok, maps:remove(node, State)} + end + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + TesterSeq = + [ + %% *** Init part *** + #{desc => "monitor server", + cmd => fun(#{server := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + #{desc => "monitor client", + cmd => fun(#{client := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + + %% Start the server + #{desc => "order server start", + cmd => fun(#{server := Pid} = _State) -> + ?SEV_ANNOUNCE_START(Pid), + ok + end}, + #{desc => "await server ready (init)", + cmd => fun(#{server := Pid} = State) -> + {ok, ServerSA} = ?SEV_AWAIT_READY(Pid, server, init), + {ok, State#{server_sa => ServerSA}} + end}, + + %% Start the client + #{desc => "order client start", + cmd => fun(#{client := Pid, + server_sa := ServerSA} = _State) -> + ?SEV_ANNOUNCE_START(Pid, ServerSA), + ok + end}, + #{desc => "await client ready (init)", + cmd => fun(#{client := Pid} = _State) -> + ok = ?SEV_AWAIT_READY(Pid, client, init) + end}, + + %% The actual test + #{desc => "order server continue (recv)", + cmd => fun(#{server := Pid} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Pid, recv), + ok + end}, + ?SEV_SLEEP(?SECS(1)), + #{desc => "order client continue (send)", + cmd => fun(#{client := Pid} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Pid, send), + ok + end}, + #{desc => "await client ready (send)", + cmd => fun(#{server := Server, + client := Client} = State) -> + case ?SEV_AWAIT_READY(Client, client, send, + [{server, Server}]) of + {ok, {_, _, _, _} = Result} -> + ?SEV_IPRINT("client result: " + "~n ~p", [Result]), + {ok, State#{client_result => Result}}; + {ok, BadResult} -> + ?SEV_EPRINT("client result: " + "~n ~p", [BadResult]), + {error, {invalid_client_result, BadResult}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "order server continue (close)", + cmd => fun(#{server := Pid} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Pid, close), + ok + end}, + #{desc => "await server ready (close)", + cmd => fun(#{server := Pid} = _State) -> + ok = ?SEV_AWAIT_READY(Pid, server, close) + end}, + %% Because of the way we control the server, there is no real + %% point in collecting statistics from it (the time will include + %% our communication with it). + #{desc => "await server ready (recv)", + cmd => fun(#{server := Server, + client := Client} = _State) -> + case ?SEV_AWAIT_READY(Server, server, recv, + [{client, Client}]) of + {ok, _Result} -> + ok; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "present result", + cmd => fun(#{client_result := CRes, + num := Num} = State) -> + {CSent, CReceived, CStart, CStop} = CRes, + CTime = tdiff(CStart, CStop), + %% Note that the sizes we are counting is only + %% the "data" part of the messages. There is also + %% fixed header for each message, which of cource + %% is small for the large messages, but comparatively + %% big for the small messages! + ?SEV_IPRINT("Results: ~w messages exchanged" + "~n Client: ~w msec" + "~n ~.2f msec/message (roundtrip)" + "~n ~.2f messages/msec (roundtrip)" + "~n ~w bytes/msec sent" + "~n ~w bytes/msec received", + [Num, + CTime, + CTime / Num, + Num / CTime, + CSent div CTime, + CReceived div CTime]), + State1 = maps:remove(client_result, State), + {ok, State1} + end}, + + %% Terminations + #{desc => "order client to terminate", + cmd => fun(#{client := Pid} = _State) -> + ?SEV_ANNOUNCE_TERMINATE(Pid), + ok + end}, + #{desc => "await client termination", + cmd => fun(#{client := Pid} = State) -> + case ?SEV_AWAIT_TERMINATION(Pid) of + ok -> + State1 = maps:remove(client, State), + {ok, State1}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "order server to terminate", + cmd => fun(#{server := Pid} = _State) -> + ?SEV_ANNOUNCE_TERMINATE(Pid), + ok + end}, + #{desc => "await server termination", + cmd => fun(#{server := Pid} = State) -> + case ?SEV_AWAIT_TERMINATION(Pid) of + ok -> + State1 = maps:remove(server, State), + {ok, State1}; + {error, _} = ERROR -> + ERROR + end + end}, + + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + i("start server evaluator"), + ServerInitState = #{domain => maps:get(domain, InitState), + recv => maps:get(recv, InitState), + send => maps:get(send, InitState), + buf_init => maps:get(buf_init, InitState)}, + Server = ?SEV_START("server", ServerSeq, ServerInitState), + + i("start client evaluator(s)"), + ClientInitState = InitState#{host => local_host()}, + Client = ?SEV_START("client", ClientSeq, ClientInitState), + + i("start 'tester' evaluator"), + TesterInitState = #{server => Server#ev.pid, + client => Client#ev.pid, + num => maps:get(num, InitState)}, + Tester = ?SEV_START("tester", TesterSeq, TesterInitState), + + i("await evaluator"), + ok = ?SEV_AWAIT_FINISH([Server, Client, Tester]). + + + +%% Server side handler process +%% We don't actually need a separate process for this socket, +%% but we do it anyway to simplify the sequence. +tpp_udp_server_handler_create() -> + Self = self(), + erlang:spawn(fun() -> tpp_udp_server_handler(Self) end). + +tpp_udp_server_handler(Parent) -> + tpp_udp_server_handler_init(Parent), + {Sock, Send, Recv} = tpp_udp_handler_await_start(Parent), + tpp_udp_handler_announce_ready(Parent, init), + tpp_udp_handler_await_continue(Parent, recv), + Result = tpp_udp_server_handler_msg_exchange(Sock, Send, Recv), + tpp_udp_handler_announce_ready(Parent, recv, Result), + Reason = tpp_udp_handler_await_terminate(Parent), + ?SEV_IPRINT("terminating"), + exit(Reason). + +tpp_udp_server_handler_init(Parent) -> + put(sname, "shandler"), + ?SEV_IPRINT("init"), + _MRef = erlang:monitor(process, Parent), + ok. + +tpp_udp_server_handler_msg_exchange(Sock, Send, Recv) -> + tpp_udp_server_handler_msg_exchange_loop(Sock, Send, Recv, 0, 0, 0, undefined). + +tpp_udp_server_handler_msg_exchange_loop(Sock, Send, Recv, + N, Sent, Received, Start) -> + %% ?SEV_IPRINT("[~w] try receive", [N]), + %% if + %% (N =:= (?TPP_SMALL_NUM-2)) -> + %% ?SEV_IPRINT("[~w] try receive", [N]), + %% socket:setopt(Sock, otp, debug, true); + %% true -> ok + %% end, + case tpp_udp_recv_req(Sock, Recv) of + {ok, Msg, RecvSz, From} -> + NewStart = if (Start =:= undefined) -> ?LIB:timestamp(); + true -> Start end, + %% ?SEV_IPRINT("[~w] received - now try send", [N]), + case tpp_udp_send_rep(Sock, Send, Msg, From) of + {ok, SendSz} -> + tpp_udp_server_handler_msg_exchange_loop(Sock, Send, Recv, + N+1, + Sent+SendSz, + Received+RecvSz, + NewStart); + {error, SReason} -> + ?SEV_EPRINT("send (~w): ~p", [N, SReason]), + exit({send, SReason, N}) + end; + %% {error, timeout} -> + %% ?SEV_IPRINT("timeout(~w) - try again", [N]), + %% case Send(Sock, list_to_binary("ping")) of + %% ok -> + %% exit({'ping-send', ok, N}); + %% {error, Reason} -> + %% exit({'ping-send', Reason, N}) + %% end; + {error, closed} -> + ?SEV_IPRINT("closed - we are done: ~w, ~w, ~w", [N, Sent, Received]), + Stop = ?LIB:timestamp(), + {N, Sent, Received, Start, Stop}; + {error, RReason} -> + ?SEV_EPRINT("recv (~w): ~p", [N, RReason]), + exit({recv, RReason, N}) + end. + + +%% The (remote) client side handler process + +tpp_udp_client_handler_create(Node) -> + Self = self(), + GL = group_leader(), + Fun = fun() -> tpp_udp_client_handler(Self, GL) end, + erlang:spawn(Node, Fun). + +tpp_udp_client_handler(Parent, GL) -> + tpp_udp_client_handler_init(Parent, GL), + {ServerSA, BufInit, Send, Recv} = tpp_udp_handler_await_start(Parent), + Domain = maps:get(family, ServerSA), + Sock = tpp_udp_sock_open(Domain, BufInit), + tpp_udp_sock_bind(Sock, Domain), + tpp_udp_handler_announce_ready(Parent, init), + {InitMsg, Num} = tpp_udp_handler_await_continue(Parent, send), + Result = tpp_udp_client_handler_msg_exchange(Sock, ServerSA, + Send, Recv, InitMsg, Num), + tpp_udp_handler_announce_ready(Parent, send, Result), + Reason = tpp_udp_handler_await_terminate(Parent), + tpp_udp_sock_close(Sock), + ?SEV_IPRINT("terminating"), + exit(Reason). + +tpp_udp_client_handler_init(Parent, GL) -> + put(sname, "chandler"), + ?SEV_IPRINT("init"), + _MRef = erlang:monitor(process, Parent), + group_leader(self(), GL), + ok. + +tpp_udp_client_handler_msg_exchange(Sock, ServerSA, Send, Recv, InitMsg, Num) -> + Start = ?LIB:timestamp(), + tpp_udp_client_handler_msg_exchange_loop(Sock, ServerSA, Send, Recv, InitMsg, + Num, 0, 0, 0, Start). + +tpp_udp_client_handler_msg_exchange_loop(_Sock, _Dest, _Send, _Recv, _Msg, + Num, Num, Sent, Received, + Start) -> + Stop = ?LIB:timestamp(), + {Sent, Received, Start, Stop}; +tpp_udp_client_handler_msg_exchange_loop(Sock, Dest, Send, Recv, Data, + Num, N, Sent, Received, Start) -> + %% d("tpp_udp_client_handler_msg_exchange_loop(~w,~w) try send", [Num,N]), + case tpp_udp_send_req(Sock, Send, Data, Dest) of + {ok, SendSz} -> + %% d("tpp_tcp_client_msg_exchange_loop(~w,~w) sent - " + %% "now try recv", [Num,N]), + case tpp_udp_recv_rep(Sock, Recv) of + {ok, NewData, RecvSz, Dest} -> + tpp_udp_client_handler_msg_exchange_loop(Sock, Dest, Send, Recv, + NewData, Num, N+1, + Sent+SendSz, + Received+RecvSz, + Start); + {error, RReason} -> + ?SEV_EPRINT("recv (~w of ~w): ~p", [N, Num, RReason]), + exit({recv, RReason, N}) + end; + {error, SReason} -> + ?SEV_EPRINT("send (~w of ~w): ~p", [N, Num, SReason]), + exit({send, SReason, N}) + end. + + +tpp_udp_recv_req(Sock, Recv) -> + tpp_udp_recv(Sock, Recv, ?TPP_REQUEST). + +tpp_udp_recv_rep(Sock, Recv) -> + tpp_udp_recv(Sock, Recv, ?TPP_REPLY). + +tpp_udp_recv(Sock, Recv, Tag) -> + case Recv(Sock, 0) of + {ok, {Source, <> = Msg}} + when (Sz =:= size(Data)) -> + %% We got it all + %% ?SEV_IPRINT("tpp_udp_recv -> got all: " + %% "~n Source: ~p" + %% "~n Tag: ~p" + %% "~n Sz: ~p" + %% "~n size(Data): ~p", [Source, Tag, Sz, size(Data)]), + {ok, Data, size(Msg), Source}; + {ok, {Source, <> = Msg}} -> + %% ?SEV_IPRINT("tpp_udp_recv -> got part: " + %% "~n Source: ~p" + %% "~n Tag: ~p" + %% "~n Sz: ~p" + %% "~n size(Data): ~p", [Source, Tag, Sz, size(Data)]), + Remains = Sz - size(Data), + tpp_tcp_recv(Sock, Source, Recv, Tag, Remains, size(Msg), [Data]); + {ok, {_, <>}} -> + {error, {invalid_msg_tag, Tag}}; + {error, _} = ERROR -> + ERROR + end. + +%% We match against Source since we only communicate with one peer +tpp_tcp_recv(Sock, Source, Recv, Tag, Remaining, AccSz, Acc) -> + %% ?SEV_IPRINT("tpp_tcp_recv -> entry with" + %% "~n Tag: ~p" + %% "~n Remaining: ~p" + %% "~n AccSz: ~p" + %% "~n RcvBuf: ~p" + %% "~n SndBuf: ~p", + %% [Tag, Remaining, AccSz, + %% socket:getopt(Sock, socket, rcvbuf), + %% socket:getopt(Sock, socket, sndbuf)]), + case Recv(Sock, Remaining) of + {ok, {Source, Data}} when (Remaining =:= size(Data)) -> + %% ?SEV_IPRINT("tpp_udp_recv -> got rest: " + %% "~n Source: ~p" + %% "~n size(Data): ~p", [Source, size(Data)]), + %% We got the rest + TotSz = AccSz + size(Data), + {ok, + erlang:iolist_to_binary(lists:reverse([Data | Acc])), + TotSz, Source}; + {ok, {Source, Data}} when (Remaining > size(Data)) -> + %% ?SEV_IPRINT("tpp_udp_recv -> got part of rest: " + %% "~n Source: ~p" + %% "~n size(Data): ~p", [Source, size(Data)]), + tpp_tcp_recv(Sock, Source, Recv, Tag, + Remaining - size(Data), AccSz + size(Data), + [Data | Acc]); + {error, _} = ERROR -> + ERROR + end. + + +tpp_udp_send_req(Sock, Send, Data, Dest) -> + tpp_udp_send(Sock, Send, ?TPP_REQUEST, Data, Dest). + +tpp_udp_send_rep(Sock, Send, Data, Dest) -> + tpp_udp_send(Sock, Send, ?TPP_REPLY, Data, Dest). + +tpp_udp_send(Sock, Send, Tag, Data, Dest) -> + DataSz = size(Data), + Msg = <>, + tpp_udp_send_msg(Sock, Send, Msg, Dest, 0). + +tpp_udp_send_msg(Sock, Send, Msg, Dest, AccSz) when is_binary(Msg) -> + %% d("tpp_udp_send_msg -> entry with" + %% "~n size(Msg): ~p" + %% "~n Dest: ~p" + %% "~n AccSz: ~p" + %% "~n RcvBuf: ~p" + %% "~n SndBuf: ~p", + %% [size(Msg), Dest, AccSz, + %% socket:getopt(Sock, socket, rcvbuf), + %% socket:getopt(Sock, socket, sndbuf)]), + case Send(Sock, Msg, Dest) of + ok -> + {ok, AccSz+size(Msg)}; + {ok, Rest} -> % This is an IOVec + RestBin = list_to_binary(Rest), + tpp_udp_send_msg(Sock, Send, RestBin, Dest, + AccSz+(size(Msg)-size(RestBin))); + {error, _} = ERROR -> + ERROR + end. + + +tpp_udp_handler_await_start(Parent) -> + ?SEV_IPRINT("await start"), + ?SEV_AWAIT_START(Parent). + +tpp_udp_handler_announce_ready(Parent, Slogan) -> + ?SEV_IPRINT("announce ready (~p)", [Slogan]), + ?SEV_ANNOUNCE_READY(Parent, Slogan). +tpp_udp_handler_announce_ready(Parent, Slogan, Extra) -> + ?SEV_IPRINT("announce ready (~p)", [Slogan]), + ?SEV_ANNOUNCE_READY(Parent, Slogan, Extra). + +tpp_udp_handler_await_continue(Parent, Slogan) -> + ?SEV_IPRINT("await continue (~p)", [Slogan]), + case ?SEV_AWAIT_CONTINUE(Parent, parent, Slogan) of + ok -> + ?SEV_IPRINT("continue (~p): ok", [Slogan]), + ok; + {ok, Data} -> + ?SEV_IPRINT("continue (~p): ok with data", [Slogan]), + Data; + {error, Reason} -> + ?SEV_EPRINT("continue (~p): error" + "~n ~p", [Slogan, Reason]), + exit({continue, Slogan, Reason}) + end. + +tpp_udp_handler_await_terminate(Parent) -> + ?SEV_IPRINT("await terminate"), + case ?SEV_AWAIT_TERMINATE(Parent, parent) of + ok -> + ok; + {error, Reason} -> + Reason + end. + + +tpp_udp_sock_open(Domain, BufInit) -> + case socket:open(Domain, dgram, udp) of + {ok, Sock} -> + ok = BufInit(Sock), + Sock; + {error, Reason} -> + exit({open_failed, Reason}) + end. + +tpp_udp_sock_bind(Sock, Domain) -> + LAddr = which_local_addr(Domain), + LSA = #{family => Domain, + addr => LAddr}, + case socket:bind(Sock, LSA) of + {ok, _} -> + ok; + {error, Reason} -> + exit({bind, Reason}) + end. + +tpp_udp_sock_close(Sock) -> + case socket:close(Sock) of + ok -> + ok; + {error, Reason} -> + exit({close, Reason}) + end. + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% This gets the local address (not 127.0...) +%% We should really implement this using the (new) net module, +%% but until that gets the necessary functionality... +which_local_addr(Domain) -> + case inet:getifaddrs() of + {ok, IFL} -> + which_addr(Domain, IFL); + {error, Reason} -> + ?FAIL({inet, getifaddrs, Reason}) + end. + +which_addr(_Domain, []) -> + ?FAIL(no_address); +which_addr(Domain, [{Name, IFO}|_IFL]) when (Name =/= "lo") -> + which_addr2(Domain, IFO); +which_addr(Domain, [_|IFL]) -> + which_addr(Domain, IFL). + +which_addr2(_Domain, []) -> + ?FAIL(no_address); +which_addr2(inet = _Domain, [{addr, Addr}|_IFO]) when (size(Addr) =:= 4) -> + Addr; +which_addr2(inet6 = _Domain, [{addr, Addr}|_IFO]) when (size(Addr) =:= 8) -> + Addr; +which_addr2(Domain, [_|IFO]) -> + which_addr2(Domain, IFO). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +start_node(Host, NodeName) -> + UniqueNodeName = f("~w_~w", [NodeName, erlang:system_time(millisecond)]), + case do_start_node(Host, UniqueNodeName) of + {ok, _} = OK -> + OK; + {error, Reason, _} -> + {error, Reason} + end. + +do_start_node(Host, NodeName) when is_list(NodeName) -> + do_start_node(Host, list_to_atom(NodeName)); +do_start_node(Host, NodeName) when is_atom(NodeName) -> + Dir = filename:dirname(code:which(?MODULE)), + Flags = "-pa " ++ Dir, + Opts = [{monitor_master, true}, {erl_flags, Flags}], + ct_slave:start(Host, NodeName, Opts). + + +stop_node(Node) -> + case ct_slave:stop(Node) of + {ok, _} -> ok; {error, _} = ERROR -> ERROR @@ -8208,21 +9285,21 @@ f(F, A) -> %% i(Before ++ FStr ++ After, []). -%% d(F, A) -> -%% d(get(dbg_fd), F, A). +d(F, A) -> + d(get(dbg_fd), F, A). -%% d(undefined, F, A) -> -%% [NodeNameStr|_] = string:split(atom_to_list(node()), [$@]), -%% DbgFileName = f("~s-dbg.txt", [NodeNameStr]), -%% case file:open(DbgFileName, [write]) of -%% {ok, FD} -> -%% put(dbg_fd, FD), -%% d(FD, F, A); -%% {error, Reason} -> -%% exit({failed_open_dbg_file, Reason}) -%% end; -%% d(FD, F, A) -> -%% io:format(FD, "~s~n", [f("[~s] " ++ F, [formated_timestamp()|A])]). +d(undefined, F, A) -> + [NodeNameStr|_] = string:split(atom_to_list(node()), [$@]), + DbgFileName = f("~s-dbg.txt", [NodeNameStr]), + case file:open(DbgFileName, [write]) of + {ok, FD} -> + put(dbg_fd, FD), + d(FD, F, A); + {error, Reason} -> + exit({failed_open_dbg_file, Reason}) + end; +d(FD, F, A) -> + io:format(FD, "~s~n", [f("[~s] " ++ F, [formated_timestamp()|A])]). i(F) -> i(F, []). diff --git a/erts/preloaded/ebin/socket.beam b/erts/preloaded/ebin/socket.beam index d3bc7c7af0..25046e6aad 100644 Binary files a/erts/preloaded/ebin/socket.beam and b/erts/preloaded/ebin/socket.beam differ diff --git a/erts/preloaded/src/socket.erl b/erts/preloaded/src/socket.erl index dd10aac3ff..a40692881b 100644 --- a/erts/preloaded/src/socket.erl +++ b/erts/preloaded/src/socket.erl @@ -1958,6 +1958,7 @@ do_recvfrom(SockRef, BufSz, EFlags, Timeout) -> next_timeout(TS, Timeout)); {nif_abort, RecvRef, Reason} -> + %% p("received nif-abort: ~p", [Reason]), {error, Reason} after NewTimeout -> @@ -1970,6 +1971,13 @@ do_recvfrom(SockRef, BufSz, EFlags, Timeout) -> end. +%% pi(Item) -> +%% pi(self(), Item). + +%% pi(Pid, Item) -> +%% {Item, Info} = process_info(Pid, Item), +%% Info. + %% --------------------------------------------------------------------------- %% -- cgit v1.2.3 From 691854f4a4ef200158fd018f6d881343658099f8 Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Thu, 15 Nov 2018 20:23:01 +0100 Subject: [socket-nif|test] Add test case for socket close Added a socket close (actually shutdown(write)) for recv and recvmsg for tcp. OTP-14831 --- erts/emulator/test/socket_SUITE.erl | 1374 +++++++++++++++++++++++++++++------ 1 file changed, 1146 insertions(+), 228 deletions(-) diff --git a/erts/emulator/test/socket_SUITE.erl b/erts/emulator/test/socket_SUITE.erl index 7a1a362181..815cc1e624 100644 --- a/erts/emulator/test/socket_SUITE.erl +++ b/erts/emulator/test/socket_SUITE.erl @@ -71,6 +71,7 @@ sc_cpe_socket_cleanup_tcp6/1, sc_cpe_socket_cleanup_udp4/1, sc_cpe_socket_cleanup_udp6/1, + sc_lc_recv_response_tcp4/1, sc_lc_recv_response_tcp6/1, sc_lc_recvfrom_response_udp4/1, @@ -81,16 +82,21 @@ sc_lc_recvmsg_response_udp6/1, sc_lc_acceptor_response_tcp4/1, sc_lc_acceptor_response_tcp6/1, + sc_rc_recv_response_tcp4/1, sc_rc_recv_response_tcp6/1, sc_rc_recvmsg_response_tcp4/1, sc_rc_recvmsg_response_tcp6/1, + sc_rs_recv_send_shutdown_receive_tcp4/1, + sc_rs_recv_send_shutdown_receive_tcp6/1, + sc_rs_recvmsg_send_shutdown_receive_tcp4/1, + sc_rs_recvmsg_send_shutdown_receive_tcp6/1, + %% Traffic traffic_send_and_recv_chunks_tcp4/1, traffic_send_and_recv_chunks_tcp6/1 - %% Tickets ]). @@ -106,6 +112,7 @@ -define(BASIC_REQ, <<"hejsan">>). -define(BASIC_REP, <<"hoppsan">>). +-define(DATA, <<"HOPPSAN">>). % Temporary -define(FAIL(R), exit(R)). -define(SLEEP(T), receive after T -> ok end). @@ -139,6 +146,7 @@ groups() -> {sc_ctrl_proc_exit, [], sc_cp_exit_cases()}, {sc_local_close, [], sc_lc_cases()}, {sc_remote_close, [], sc_rc_cases()}, + {sc_remote_shutdown, [], sc_rs_cases()}, {traffic, [], traffic_cases()} %% {tickets, [], ticket_cases()} ]. @@ -192,13 +200,14 @@ api_op_with_timeout_cases() -> api_to_recvmsg_tcp6 ]. -%% These cases tests what happens when the socket is closed, locally or -%% remotely. +%% These cases tests what happens when the socket is closed/shutdown, +%% locally or remotely. socket_closure_cases() -> [ {group, sc_ctrl_proc_exit}, {group, sc_local_close}, - {group, sc_remote_close} + {group, sc_remote_close}, + {group, sc_remote_shutdown} ]. %% These cases are all about socket cleanup after the controlling process @@ -239,6 +248,17 @@ sc_rc_cases() -> sc_rc_recvmsg_response_tcp6 ]. +%% These cases tests what happens when the socket is shutdown/closed remotely +%% after writing and reading is ongoing. +sc_rs_cases() -> + [ + sc_rs_recv_send_shutdown_receive_tcp4, + sc_rs_recv_send_shutdown_receive_tcp6, + + sc_rs_recvmsg_send_shutdown_receive_tcp4, + sc_rs_recvmsg_send_shutdown_receive_tcp6 + ]. + traffic_cases() -> [ @@ -453,9 +473,9 @@ api_b_sendmsg_and_recvmsg_udp4(_Config) when is_list(_Config) -> %% type => tos, %% data => reliability}, %% CMsgHdrs = [CMsgHdr], - MsgHdr = #{addr => Dest, - %% ctrl => CMsgHdrs, - iov => [Data]}, + MsgHdr = #{addr => Dest, + %% ctrl => CMsgHdrs, + iov => [Data]}, socket:sendmsg(Sock, MsgHdr) end, Recv = fun(Sock) -> @@ -4862,7 +4882,7 @@ sc_rc_receive_response_tcp(InitState) -> ERROR end end}, - #{desc => "await handle 1 ready (init)", + #{desc => "await handler 1 ready (init)", cmd => fun(#{tester := Tester, handler1 := Handler1} = _State) -> ?SEV_AWAIT_READY(Handler1, handler1, init, @@ -4881,7 +4901,7 @@ sc_rc_receive_response_tcp(InitState) -> ERROR end end}, - #{desc => "await handle 2 ready (init)", + #{desc => "await handler 2 ready (init)", cmd => fun(#{tester := Tester, handler1 := Handler1, handler2 := Handler2} = _State) -> @@ -4902,7 +4922,7 @@ sc_rc_receive_response_tcp(InitState) -> ERROR end end}, - #{desc => "await handle 3 ready (init)", + #{desc => "await handler 3 ready (init)", cmd => fun(#{tester := Tester, handler1 := Handler1, handler2 := Handler2, @@ -4971,7 +4991,7 @@ sc_rc_receive_response_tcp(InitState) -> ?SEV_ANNOUNCE_READY(Tester, recv_closed), ok end}, - + %% Termination #{desc => "await terminate (from tester)", cmd => fun(#{tester := Tester} = State) -> @@ -5471,15 +5491,6 @@ sc_rc_receive_response_tcp(InitState) -> Tester]). -local_host() -> - try net_adm:localhost() of - Host when is_list(Host) -> - list_to_atom(Host) - catch - C:E:S -> - erlang:raise(C, E, S) - end. - sc_rc_tcp_client_start(Node) -> Self = self(), GL = group_leader(), @@ -5609,17 +5620,17 @@ sc_rc_tcp_handler_recv(Recv, Sock) -> {error, unexpected_success}; {error, Reason} = ERROR -> ?SEV_IPRINT("receive error: " - "~n ~p", [Reason]), + "~n ~p", [Reason]), ERROR catch C:E:S -> ?SEV_IPRINT("receive failure: " - "~n Class: ~p" - "~n Error: ~p" - "~n Stack: ~p", [C, E, S]), + "~n Class: ~p" + "~n Error: ~p" + "~n Stack: ~p", [C, E, S]), {error, {recv, C, E, S}} end. - + sc_rc_tcp_handler_announce_ready(Parent, Slogan, Result) -> ?SEV_IPRINT("announce ready"), ?SEV_ANNOUNCE_READY(Parent, Slogan, Result), @@ -5672,48 +5683,77 @@ sc_rc_recvmsg_response_tcp6(_Config) when is_list(_Config) -> %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% This test case is intended to test that the send and recv functions -%% behave as expected when sending and/or reading chunks. -%% First send data in one "big" chunk, and read it in "small" chunks. -%% Second, send in a bunch of "small" chunks, and read in one "big" chunk. +%% This test case is intended to test what happens when a socket is +%% remotely closed while the process is calling the recv function. +%% The remote client sends data, then shutdown(write) and then the +%% reader attempts a recv. %% Socket is IPv4. +%% +%% To minimize the chance of "weirdness", we should really have test cases +%% where the two sides of the connection is on different machines. But for +%% now, we will make do with different VMs on the same host. +%% -traffic_send_and_recv_chunks_tcp4(suite) -> +sc_rs_recv_send_shutdown_receive_tcp4(suite) -> []; -traffic_send_and_recv_chunks_tcp4(doc) -> +sc_rs_recv_send_shutdown_receive_tcp4(doc) -> []; -traffic_send_and_recv_chunks_tcp4(_Config) when is_list(_Config) -> - tc_try(traffic_send_and_recv_chunks_tcp4, +sc_rs_recv_send_shutdown_receive_tcp4(_Config) when is_list(_Config) -> + tc_try(sc_rs_recv_send_shutdown_receive_tcp4, fun() -> ?TT(?SECS(30)), - InitState = #{domain => inet}, - ok = traffic_send_and_recv_chunks_tcp(InitState) + MsgData = ?DATA, + Recv = fun(Sock) -> + socket:recv(Sock) + end, + Send = fun(Sock, Data) -> + socket:send(Sock, Data) + end, + InitState = #{domain => inet, + recv => Recv, + send => Send, + data => MsgData}, + ok = sc_rs_send_shutdown_receive_tcp(InitState) end). - %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% This test case is intended to test that the send and recv functions -%% behave as expected when sending and/or reading chunks. -%% First send data in one "big" chunk, and read it in "small" chunks. -%% Second, send in a bunch of "small" chunks, and read in one "big" chunk. +%% This test case is intended to test what happens when a socket is +%% remotely closed while the process is calling the recv function. +%% The remote client sends data, then shutdown(write) and then the +%% reader attempts a recv. %% Socket is IPv6. -traffic_send_and_recv_chunks_tcp6(suite) -> +sc_rs_recv_send_shutdown_receive_tcp6(suite) -> []; -traffic_send_and_recv_chunks_tcp6(doc) -> +sc_rs_recv_send_shutdown_receive_tcp6(doc) -> []; -traffic_send_and_recv_chunks_tcp6(_Config) when is_list(_Config) -> - tc_try(traffic_send_and_recv_chunks_tcp6, +sc_rs_recv_send_shutdown_receive_tcp6(_Config) when is_list(_Config) -> + tc_try(sc_rs_recv_send_shutdown_receive_tcp6, fun() -> not_yet_implemented(), - ?TT(?SECS(30)), - InitState = #{domain => inet6}, - ok = traffic_send_and_recv_chunks_tcp(InitState) + ?TT(?SECS(10)), + MsgData = ?DATA, + Recv = fun(Sock) -> + socket:recv(Sock) + end, + Send = fun(Sock, Data) -> + socket:send(Sock, Data) + end, + InitState = #{domain => inet6, + recv => Recv, + send => Send, + data => MsgData}, + ok = sc_rs_send_shutdown_receive_tcp(InitState) end). -traffic_send_and_recv_chunks_tcp(InitState) -> +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +sc_rs_send_shutdown_receive_tcp(InitState) -> + %% The connection is handled by a handler processes. + %% This are created (on the fly) and handled internally + %% by the server! ServerSeq = [ %% *** Wait for start order part *** @@ -5731,6 +5771,7 @@ traffic_send_and_recv_chunks_tcp(InitState) -> %% *** Init part *** #{desc => "which local address", cmd => fun(#{domain := Domain} = State) -> + i("get local address for ~p", [Domain]), LAddr = which_local_addr(Domain), LSA = #{family => Domain, addr => LAddr}, {ok, State#{local_sa => LSA}} @@ -5767,178 +5808,86 @@ traffic_send_and_recv_chunks_tcp(InitState) -> %% The actual test #{desc => "await continue (accept)", cmd => fun(#{tester := Tester} = _State) -> - ?SEV_AWAIT_CONTINUE(Tester, tester, accept) + ?SEV_ANNOUNCE_CONTINUE(Tester, tester, accept) end}, #{desc => "accept", - cmd => fun(#{lsock := LSock} = State) -> + cmd => fun(#{lsock := LSock, recv := Recv} = State) -> case socket:accept(LSock) of {ok, Sock} -> - {ok, State#{csock => Sock}}; + ?SEV_IPRINT("accepted: try start handler"), + Handler = + sc_rs_tcp_handler_start(Recv, Sock), + ?SEV_IPRINT("handler started"), + {ok, State#{csock => Sock, + handler => Handler}}; {error, _} = ERROR -> ERROR end end}, + #{desc => "await handler ready (init)", + cmd => fun(#{tester := Tester, + handler := Handler} = _State) -> + ?SEV_AWAIT_READY(Handler, handler, init, + [{tester, Tester}]) + end}, #{desc => "announce ready (accept)", cmd => fun(#{tester := Tester}) -> ?SEV_ANNOUNCE_READY(Tester, accept), ok end}, - #{desc => "await continue (recv-many-small)", + #{desc => "await continue (first recv)", cmd => fun(#{tester := Tester} = _State) -> - ?SEV_AWAIT_CONTINUE(Tester, tester, recv_many_small) - end}, - #{desc => "recv chunk 1", - cmd => fun(#{csock := Sock} = State) -> - case socket:recv(Sock, 100) of - {ok, Chunk} -> - ?SEV_IPRINT("recv of chunk 1 of ~p bytes", - [size(Chunk)]), - {ok, State#{chunks => [b2l(Chunk)]}}; - {error, _} = ERROR -> - ERROR - end - end}, - #{desc => "recv chunk 2", - cmd => fun(#{csock := Sock, - chunks := Chunks} = State) -> - case socket:recv(Sock, 100) of - {ok, Chunk} -> - ?SEV_IPRINT("recv of chunk 2 of ~p bytes", - [size(Chunk)]), - {ok, State#{chunks => [b2l(Chunk)|Chunks]}}; - {error, _} = ERROR -> - ERROR - end - end}, - #{desc => "recv chunk 3", - cmd => fun(#{csock := Sock, - chunks := Chunks} = State) -> - case socket:recv(Sock, 100) of - {ok, Chunk} -> - ?SEV_IPRINT("recv of chunk 3 of ~p bytes", - [size(Chunk)]), - {ok, State#{chunks => [b2l(Chunk)|Chunks]}}; - {error, _} = ERROR -> - ERROR - end - end}, - #{desc => "recv chunk 4", - cmd => fun(#{csock := Sock, - chunks := Chunks} = State) -> - case socket:recv(Sock, 100) of - {ok, Chunk} -> - ?SEV_IPRINT("recv of chunk 4 of ~p bytes", - [size(Chunk)]), - {ok, State#{chunks => [b2l(Chunk)|Chunks]}}; - {error, _} = ERROR -> - ERROR - end - end}, - #{desc => "recv chunk 5", - cmd => fun(#{csock := Sock, - chunks := Chunks} = State) -> - case socket:recv(Sock, 100) of - {ok, Chunk} -> - ?SEV_IPRINT("recv of chunk 5 of ~p bytes", - [size(Chunk)]), - {ok, State#{chunks => [b2l(Chunk)|Chunks]}}; - {error, _} = ERROR -> - ERROR - end - end}, - #{desc => "recv chunk 6", - cmd => fun(#{csock := Sock, - chunks := Chunks} = State) -> - case socket:recv(Sock, 100) of - {ok, Chunk} -> - ?SEV_IPRINT("recv of chunk 6 of ~p bytes", - [size(Chunk)]), - {ok, State#{chunks => [b2l(Chunk)|Chunks]}}; - {error, _} = ERROR -> - ERROR - end - end}, - #{desc => "recv chunk 7", - cmd => fun(#{csock := Sock, - chunks := Chunks} = State) -> - case socket:recv(Sock, 100) of - {ok, Chunk} -> - ?SEV_IPRINT("recv of chunk 7 of ~p bytes", - [size(Chunk)]), - {ok, State#{chunks => [b2l(Chunk)|Chunks]}}; - {error, _} = ERROR -> - ERROR - end + ?SEV_AWAIT_CONTINUE(Tester, tester, recv) end}, - #{desc => "recv chunk 8", - cmd => fun(#{csock := Sock, - chunks := Chunks} = State) -> - case socket:recv(Sock, 100) of - {ok, Chunk} -> - ?SEV_IPRINT("recv of chunk 8 of ~p bytes", - [size(Chunk)]), - {ok, State#{chunks => [b2l(Chunk)|Chunks]}}; - {error, _} = ERROR -> - ERROR - end + #{desc => "order handler to receive (first)", + cmd => fun(#{handler := Pid} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Pid, recv), + ok end}, - #{desc => "recv chunk 9", - cmd => fun(#{csock := Sock, - chunks := Chunks} = State) -> - case socket:recv(Sock, 100) of - {ok, Chunk} -> - ?SEV_IPRINT("recv of chunk 9 of ~p bytes", - [size(Chunk)]), - {ok, State#{chunks => [b2l(Chunk)|Chunks]}}; + #{desc => "await ready from handler (first recv)", + cmd => fun(#{tester := Tester, handler := Pid} = _State) -> + case ?SEV_AWAIT_READY(Pid, handler, recv, + [{tester, Tester}]) of + {ok, Result} -> + ?SEV_IPRINT("first recv: ~p", [Result]), + ok; {error, _} = ERROR -> ERROR end end}, - #{desc => "recv chunk 10", - cmd => fun(#{csock := Sock, - chunks := Chunks} = State) -> - case socket:recv(Sock, 100) of - {ok, Chunk} -> - ?SEV_IPRINT("recv of chunk 10 of ~p bytes", - [size(Chunk)]), - {ok, State#{chunks => [b2l(Chunk)|Chunks]}}; - {error, _} = ERROR -> - ERROR - end + #{desc => "announce ready (first recv)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, recv), + ok end}, - #{desc => "announce ready (recv-many-small)", - cmd => fun(#{tester := Tester, - chunks := Chunks} = State) -> - Data = lists:flatten(lists:reverse(Chunks)), - ?SEV_ANNOUNCE_READY(Tester, recv_many_small, Data), - {ok, maps:remove(chunks, State)} + #{desc => "await continue (second recv)", + cmd => fun(#{tester := Tester} = _State) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, recv) end}, - - #{desc => "await continue (recv-one-big)", - cmd => fun(#{tester := Tester} = State) -> - case ?SEV_AWAIT_CONTINUE(Tester, tester, recv_one_big) of - {ok, Size} -> - {ok, State#{size => Size}}; - {error, _} = ERROR -> - ERROR - end + #{desc => "order handler to receive (second)", + cmd => fun(#{handler := Pid} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Pid, recv), + ok end}, - #{desc => "recv (one big)", - cmd => fun(#{tester := Tester, csock := Sock, size := Size} = _State) -> - %% ok = socket:setopt(Sock, otp, debug, true), - case socket:recv(Sock, Size) of - {ok, Data} -> - %% ok = socket:setopt(Sock, otp, debug, false), - ?SEV_ANNOUNCE_READY(Tester, - recv_one_big, - b2l(Data)), + #{desc => "await ready from handler (second recv)", + cmd => fun(#{tester := Tester, handler := Pid} = _State) -> + case ?SEV_AWAIT_READY(Pid, handler, recv, + [{tester, Tester}]) of + {ok, Result} -> + ?SEV_IPRINT("second recv: ~p", [Result]), ok; {error, _} = ERROR -> ERROR end end}, + #{desc => "announce ready (second recv)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, recv), + ok + end}, + %% Termination #{desc => "await terminate (from tester)", cmd => fun(#{tester := Tester} = State) -> case ?SEV_AWAIT_TERMINATE(Tester, tester) of @@ -5948,15 +5897,26 @@ traffic_send_and_recv_chunks_tcp(InitState) -> ERROR end end}, - #{desc => "close connection socket (just in case)", - cmd => fun(#{csock := Sock} = State) -> - (catch socket:close(Sock)), - {ok, maps:remove(csock, State)} + #{desc => "order handler to terminate", + cmd => fun(#{handler := Pid} = _State) -> + ?SEV_ANNOUNCE_TERMINATE(Pid), + ok + end}, + #{desc => "await handler termination", + cmd => fun(#{handler := Pid} = State) -> + ?SEV_AWAIT_TERMINATION(Pid), + State1 = maps:remove(csock, State), + State2 = maps:remove(handler, State1), + {ok, State2} end}, #{desc => "close listen socket", - cmd => fun(#{lsock := Sock} = State) -> - (catch socket:close(Sock)), - {ok, maps:remove(lsock, State)} + cmd => fun(#{lsock := LSock} = State) -> + case socket:close(LSock) of + ok -> + {ok, maps:remove(lsock, State)}; + {error, _} = ERROR -> + ERROR + end end}, %% *** We are done *** @@ -5983,7 +5943,7 @@ traffic_send_and_recv_chunks_tcp(InitState) -> cmd => fun(#{host := Host} = State) -> case start_node(Host, client) of {ok, Node} -> - ?SEV_IPRINT("(remote) client node ~p started", + ?SEV_IPRINT("client node ~p started", [Node]), {ok, State#{node => Node}}; {error, Reason, _} -> @@ -5995,9 +5955,10 @@ traffic_send_and_recv_chunks_tcp(InitState) -> true = erlang:monitor_node(Node, true), ok end}, - #{desc => "start remote client", - cmd => fun(#{node := Node} = State) -> - Pid = traffic_snr_tcp_client_start(Node), + #{desc => "start remote client on client node", + cmd => fun(#{node := Node, + send := Send} = State) -> + Pid = sc_rs_tcp_client_start(Node, Send), ?SEV_IPRINT("client ~p started", [Pid]), {ok, State#{rclient => Pid}} end}, @@ -6039,7 +6000,7 @@ traffic_send_and_recv_chunks_tcp(InitState) -> #{desc => "await client process ready (connect)", cmd => fun(#{tester := Tester, rclient := Client} = _State) -> - ?SEV_AWAIT_READY(Client, rclient, connect, + ?SEV_AWAIT_READY(Client, rclient, connect, [{tester, Tester}]) end}, #{desc => "announce ready (connect)", @@ -6048,51 +6009,998 @@ traffic_send_and_recv_chunks_tcp(InitState) -> ok end}, - #{desc => "await continue (send-one-big)", + #{desc => "await continue (send)", cmd => fun(#{tester := Tester, rclient := Client} = State) -> - case ?SEV_AWAIT_CONTINUE(Tester, tester, - send_one_big, + case ?SEV_AWAIT_CONTINUE(Tester, tester, send, [{rclient, Client}]) of {ok, Data} -> - {ok, State#{data => Data}}; + {ok, State#{rclient_data => Data}}; {error, _} = ERROR -> ERROR end end}, - #{desc => "order remote client to continue (send)", - cmd => fun(#{rclient := Client, data := Data}) -> + #{desc => "order remote client to send", + cmd => fun(#{rclient := Client, + rclient_data := Data}) -> ?SEV_ANNOUNCE_CONTINUE(Client, send, Data), ok end}, - #{desc => "await client process ready (send)", + #{desc => "await remote client ready (closed)", cmd => fun(#{tester := Tester, rclient := Client} = _State) -> - case ?SEV_AWAIT_READY(Client, rclient, send, - [{tester, Tester}]) of - {ok, Result} -> - Result; - {error, _} = ERROR -> - ERROR - end + ?SEV_AWAIT_READY(Client, rclient, send, + [{tester, Tester}]) end}, - #{desc => "announce ready (send-one-big)", + #{desc => "announce ready (send)", cmd => fun(#{tester := Tester}) -> - ?SEV_ANNOUNCE_READY(Tester, send_one_big), + ?SEV_ANNOUNCE_READY(Tester, send), ok end}, - #{desc => "await continue (send-many-small)", + + #{desc => "await continue (shutdown)", cmd => fun(#{tester := Tester, - rclient := Client} = State) -> - case ?SEV_AWAIT_CONTINUE(Tester, tester, - send_many_small, - [{rclient, Client}]) of - {ok, Data} -> - {ok, State#{data => Data}}; - {error, _} = ERROR -> - ERROR - end + rclient := Client} = _State) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, shutdown, + [{rclient, Client}]), + ok + end}, + #{desc => "order remote client to shutdown", + cmd => fun(#{rclient := Client}) -> + ?SEV_ANNOUNCE_CONTINUE(Client, shutdown), + ok + end}, + #{desc => "await remote client ready (shiutdown)", + cmd => fun(#{tester := Tester, + rclient := Client} = _State) -> + ?SEV_AWAIT_READY(Client, rclient, shutdown, + [{tester, Tester}]) + end}, + #{desc => "announce ready (shutdown)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, shutdown), + ok + end}, + + #{desc => "await continue (close)", + cmd => fun(#{tester := Tester, + rclient := Client} = _State) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, close, + [{rclient, Client}]), + ok + end}, + #{desc => "order remote client to close", + cmd => fun(#{rclient := Client}) -> + ?SEV_ANNOUNCE_CONTINUE(Client, close), + ok + end}, + #{desc => "await remote client ready (closed)", + cmd => fun(#{tester := Tester, + rclient := Client} = _State) -> + ?SEV_AWAIT_READY(Client, rclient, close, + [{tester, Tester}]) + end}, + #{desc => "announce ready (close)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, close), + ok + end}, + + %% Termination + #{desc => "await terminate (from tester)", + cmd => fun(#{tester := Tester, + rclient := Client} = State) -> + case ?SEV_AWAIT_TERMINATE(Tester, tester, + [{rclient, Client}]) of + ok -> + {ok, maps:remove(tester, State)}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "kill remote client", + cmd => fun(#{rclient := Client}) -> + ?SEV_ANNOUNCE_TERMINATE(Client), + ok + end}, + #{desc => "await remote client termination", + cmd => fun(#{rclient := Client} = State) -> + ?SEV_AWAIT_TERMINATION(Client), + State1 = maps:remove(rclient, State), + {ok, State1} + end}, + #{desc => "stop client node", + cmd => fun(#{node := Node} = _State) -> + stop_node(Node) + end}, + #{desc => "await client node termination", + cmd => fun(#{node := Node} = State) -> + receive + {nodedown, Node} -> + State1 = maps:remove(node_id, State), + State2 = maps:remove(node, State1), + {ok, State2} + end + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + TesterSeq = + [ + %% *** Init part *** + #{desc => "monitor server", + cmd => fun(#{server := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + #{desc => "monitor client", + cmd => fun(#{client := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + + %% Start the server + #{desc => "order server start", + cmd => fun(#{server := Pid} = _State) -> + ?SEV_ANNOUNCE_START(Pid), + ok + end}, + #{desc => "await server ready (init)", + cmd => fun(#{server := Pid} = State) -> + {ok, ServerSA} = ?SEV_AWAIT_READY(Pid, server, init), + {ok, State#{server_sa => ServerSA}} + end}, + + %% Start the client(s) + #{desc => "order client start", + cmd => fun(#{client := Pid, + server_sa := ServerSA} = _State) -> + ?SEV_ANNOUNCE_START(Pid, ServerSA), + ok + end}, + #{desc => "await client ready (init)", + cmd => fun(#{client := Pid} = _State) -> + ok = ?SEV_AWAIT_READY(Pid, client, init) + end}, + + %% The actual test + #{desc => "order server continue (accept)", + cmd => fun(#{server := Pid} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Pid, accept), + ok + end}, + ?SEV_SLEEP(?SECS(1)), + #{desc => "order client continue (connect)", + cmd => fun(#{client := Pid} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Pid, connect), + ok + end}, + #{desc => "await client ready (connect)", + cmd => fun(#{server := Server, + client := Client} = _State) -> + ?SEV_AWAIT_READY(Client, client, connect, + [{server, Server}]), + ok + end}, + #{desc => "await server ready (accept)", + cmd => fun(#{server := Server, + client := Client} = _State) -> + ?SEV_AWAIT_READY(Server, server, accept, + [{client, Client}]), + ok + end}, + + #{desc => "order client continue (send)", + cmd => fun(#{client := Pid, + data := Data} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Pid, send, Data), + ok + end}, + #{desc => "await client ready (send)", + cmd => fun(#{server := Server, + client := Client} = _State) -> + ?SEV_AWAIT_READY(Client, client, send, + [{server, Server}]), + ok + end}, + + #{desc => "order client continue (shutdown)", + cmd => fun(#{client := Pid} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Pid, shutdown), + ok + end}, + #{desc => "await client ready (shutdown)", + cmd => fun(#{server := Server, + client := Client} = _State) -> + ?SEV_AWAIT_READY(Client, client, shutdown, + [{server, Server}]), + ok + end}, + + #{desc => "order server continue (first recv)", + cmd => fun(#{server := Pid} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Pid, recv), + ok + end}, + #{desc => "await server ready (first recv)", + cmd => fun(#{server := Server, + client := Client} = _State) -> + ?SEV_AWAIT_READY(Server, server, recv, + [{client, Client}]), + ok + end}, + + #{desc => "order server continue (second recv)", + cmd => fun(#{server := Pid} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Pid, recv), + ok + end}, + #{desc => "await server ready (second recv)", + cmd => fun(#{server := Server, + client := Client} = _State) -> + ?SEV_AWAIT_READY(Server, server, recv, + [{client, Client}]), + ok + end}, + + ?SEV_SLEEP(?SECS(1)), + + #{desc => "order client continue (close)", + cmd => fun(#{client := Pid} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Pid, close), + ok + end}, + #{desc => "await client ready (close)", + cmd => fun(#{server := Server, + client := Client} = _State) -> + ?SEV_AWAIT_READY(Client, client, close, + [{server, Server}]), + ok + end}, + + %% Terminations + #{desc => "order client to terminate", + cmd => fun(#{client := Pid} = _State) -> + ?SEV_ANNOUNCE_TERMINATE(Pid), + ok + end}, + #{desc => "await client termination", + cmd => fun(#{client := Pid} = State) -> + case ?SEV_AWAIT_TERMINATION(Pid) of + ok -> + State1 = maps:remove(client, State), + {ok, State1}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "order server to terminate", + cmd => fun(#{server := Pid} = _State) -> + ?SEV_ANNOUNCE_TERMINATE(Pid), + ok + end}, + #{desc => "await server termination", + cmd => fun(#{server := Pid} = State) -> + case ?SEV_AWAIT_TERMINATION(Pid) of + ok -> + State1 = maps:remove(server, State), + {ok, State1}; + {error, _} = ERROR -> + ERROR + end + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + i("start server evaluator"), + ServerInitState = #{domain => maps:get(domain, InitState), + recv => maps:get(recv, InitState)}, + Server = ?SEV_START("server", ServerSeq, ServerInitState), + + i("start client evaluator"), + ClientInitState = #{host => local_host(), + domain => maps:get(domain, InitState), + send => maps:get(send, InitState)}, + Client = ?SEV_START("client", ClientSeq, ClientInitState), + + i("start 'tester' evaluator"), + TesterInitState = #{server => Server#ev.pid, + client => Client#ev.pid, + data => maps:get(data, InitState)}, + Tester = ?SEV_START("tester", TesterSeq, TesterInitState), + + i("await evaluator"), + ok = ?SEV_AWAIT_FINISH([Server, Client, Tester]). + + +sc_rs_tcp_client_start(Node, Send) -> + Self = self(), + GL = group_leader(), + Fun = fun() -> sc_rs_tcp_client(Self, Send, GL) end, + erlang:spawn(Node, Fun). + + +sc_rs_tcp_client(Parent, Send, GL) -> + sc_rs_tcp_client_init(Parent, GL), + ServerSA = sc_rs_tcp_client_await_start(Parent), + Domain = maps:get(family, ServerSA), + Sock = sc_rs_tcp_client_create(Domain), + sc_rs_tcp_client_bind(Sock, Domain), + sc_rs_tcp_client_announce_ready(Parent, init), + sc_rs_tcp_client_await_continue(Parent, connect), + sc_rs_tcp_client_connect(Sock, ServerSA), + sc_rs_tcp_client_announce_ready(Parent, connect), + Data = sc_rs_tcp_client_await_continue(Parent, send), + sc_rs_tcp_client_send(Sock, Send, Data), + sc_rs_tcp_client_announce_ready(Parent, send), + sc_rs_tcp_client_await_continue(Parent, shutdown), + sc_rs_tcp_client_shutdown(Sock), + sc_rs_tcp_client_announce_ready(Parent, shutdown), + sc_rs_tcp_client_await_continue(Parent, close), + sc_rs_tcp_client_close(Sock), + sc_rs_tcp_client_announce_ready(Parent, close), + Reason = sc_rs_tcp_client_await_terminate(Parent), + exit(Reason). + +sc_rs_tcp_client_init(Parent, GL) -> + i("sc_rs_tcp_client_init -> entry"), + _MRef = erlang:monitor(process, Parent), + group_leader(self(), GL), + ok. + +sc_rs_tcp_client_await_start(Parent) -> + i("sc_rs_tcp_client_await_start -> entry"), + ?SEV_AWAIT_START(Parent). + +sc_rs_tcp_client_create(Domain) -> + i("sc_rs_tcp_client_create -> entry"), + case socket:open(Domain, stream, tcp) of + {ok, Sock} -> + Sock; + {error, Reason} -> + exit({open_failed, Reason}) + end. + +sc_rs_tcp_client_bind(Sock, Domain) -> + i("sc_rs_tcp_client_bind -> entry"), + LAddr = which_local_addr(Domain), + LSA = #{family => Domain, + addr => LAddr}, + case socket:bind(Sock, LSA) of + {ok, _} -> + ok; + {error, Reason} -> + exit({bind, Reason}) + end. + +sc_rs_tcp_client_announce_ready(Parent, Slogan) -> + ?SEV_ANNOUNCE_READY(Parent, Slogan). + +sc_rs_tcp_client_await_continue(Parent, Slogan) -> + i("sc_rs_tcp_client_await_continue -> entry"), + case ?SEV_AWAIT_CONTINUE(Parent, parent, Slogan) of + ok -> + ok; + {ok, Extra} -> + Extra; + {error, Reason} -> + exit({await_continue, Slogan, Reason}) + end. + + +sc_rs_tcp_client_connect(Sock, ServerSA) -> + i("sc_rs_tcp_client_connect -> entry"), + case socket:connect(Sock, ServerSA) of + ok -> + ok; + {error, Reason} -> + exit({connect, Reason}) + end. + +sc_rs_tcp_client_send(Sock, Send, Data) -> + i("sc_rs_tcp_client_send -> entry"), + case Send(Sock, Data) of + ok -> + ok; + {error, Reason} -> + exit({send, Reason}) + end. + +sc_rs_tcp_client_shutdown(Sock) -> + i("sc_rs_tcp_client_shutdown -> entry"), + case socket:shutdown(Sock, write) of + ok -> + ok; + {error, Reason} -> + exit({shutdown, Reason}) + end. + +sc_rs_tcp_client_close(Sock) -> + i("sc_rs_tcp_client_close -> entry"), + case socket:close(Sock) of + ok -> + ok; + {error, Reason} -> + exit({close, Reason}) + end. + +sc_rs_tcp_client_await_terminate(Parent) -> + i("sc_rs_tcp_client_await_terminate -> entry"), + case ?SEV_AWAIT_TERMINATE(Parent, parent) of + ok -> + ok; + {error, Reason} -> + Reason + end. + + +%% The handlers run on the same node as the server (the local node). + +sc_rs_tcp_handler_start(Recv, Sock) -> + Self = self(), + Fun = fun() -> sc_rs_tcp_handler(Self, Recv, Sock) end, + {Pid, _} = erlang:spawn_monitor(Fun), + Pid. + +sc_rs_tcp_handler(Parent, Recv, Sock) -> + sc_rs_tcp_handler_init(Parent), + sc_rs_tcp_handler_await(Parent, recv), + ok = sc_rs_tcp_handler_recv(Recv, Sock, true), + sc_rs_tcp_handler_announce_ready(Parent, recv, received), + sc_rs_tcp_handler_await(Parent, recv), + ok = sc_rs_tcp_handler_recv(Recv, Sock, false), + sc_rs_tcp_handler_announce_ready(Parent, recv, closed), + Reason = sc_rs_tcp_handler_await(Parent, terminate), + exit(Reason). + +sc_rs_tcp_handler_init(Parent) -> + put(sname, "handler"), + _MRef = erlang:monitor(process, Parent), + ?SEV_IPRINT("started"), + ?SEV_ANNOUNCE_READY(Parent, init), + ok. + +sc_rs_tcp_handler_await(Parent, terminate) -> + ?SEV_IPRINT("await terminate"), + ?SEV_AWAIT_TERMINATE(Parent, tester); +sc_rs_tcp_handler_await(Parent, Slogan) -> + ?SEV_IPRINT("await ~w", [Slogan]), + ?SEV_AWAIT_CONTINUE(Parent, parent, Slogan). + +%% This hould actually work - we leave it for now +sc_rs_tcp_handler_recv(Recv, Sock, First) -> + ?SEV_IPRINT("recv"), + try Recv(Sock) of + {ok, _} when (First =:= true) -> + ok; + {error, closed} when (First =:= false) -> + ok; + {ok, _} -> + ?SEV_IPRINT("unexpected success"), + {error, unexpected_success}; + {error, Reason} = ERROR -> + ?SEV_IPRINT("receive error: " + "~n ~p", [Reason]), + ERROR + catch + C:E:S -> + ?SEV_IPRINT("receive failure: " + "~n Class: ~p" + "~n Error: ~p" + "~n Stack: ~p", [C, E, S]), + {error, {recv, C, E, S}} + end. + +sc_rs_tcp_handler_announce_ready(Parent, Slogan, Result) -> + ?SEV_IPRINT("announce ready"), + ?SEV_ANNOUNCE_READY(Parent, Slogan, Result), + ok. + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test what happens when a socket is +%% remotely closed while the process is calling the recvmsg function. +%% The remote client sends data, then shutdown(write) and then the +%% reader attempts a recv. +%% Socket is IPv4. + +sc_rs_recvmsg_send_shutdown_receive_tcp4(suite) -> + []; +sc_rs_recvmsg_send_shutdown_receive_tcp4(doc) -> + []; +sc_rs_recvmsg_send_shutdown_receive_tcp4(_Config) when is_list(_Config) -> + tc_try(sc_rs_recvmsg_send_shutdown_receive_tcp4, + fun() -> + ?TT(?SECS(30)), + MsgData = ?DATA, + Recv = fun(Sock) -> + case socket:recvmsg(Sock) of + {ok, #{addr := undefined, + iov := [Data]}} -> + {ok, Data}; + {error, _} = ERROR -> + ERROR + end + end, + Send = fun(Sock, Data) when is_binary(Data) -> + MsgHdr = #{iov => [Data]}, + socket:sendmsg(Sock, MsgHdr) + end, + InitState = #{domain => inet, + type => stream, + protocol => tcp, + recv => Recv, + send => Send, + data => MsgData}, + ok = sc_rs_send_shutdown_receive_tcp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test what happens when a socket is +%% remotely closed while the process is calling the recvmsg function. +%% The remote client sends data, then shutdown(write) and then the +%% reader attempts a recv. +%% Socket is IPv6. + +sc_rs_recvmsg_send_shutdown_receive_tcp6(suite) -> + []; +sc_rs_recvmsg_send_shutdown_receive_tcp6(doc) -> + []; +sc_rs_recvmsg_send_shutdown_receive_tcp6(_Config) when is_list(_Config) -> + tc_try(sc_rs_recvmsg_send_shutdown_receive_tcp6, + fun() -> + not_yet_implemented(), + ?TT(?SECS(10)), + MsgData = ?DATA, + Recv = fun(Sock) -> + case socket:recvmsg(Sock) of + {ok, #{addr := undefined, + iov := [Data]}} -> + {ok, Data}; + {error, _} = ERROR -> + ERROR + end + end, + Send = fun(Sock, Data) when is_binary(Data) -> + MsgHdr = #{iov => [Data]}, + socket:sendmsg(Sock, MsgHdr) + end, + InitState = #{domain => inet6, + type => stream, + protocol => tcp, + recv => Recv, + send => Send, + data => MsgData}, + ok = sc_rs_send_shutdown_receive_tcp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test that the send and recv functions +%% behave as expected when sending and/or reading chunks. +%% First send data in one "big" chunk, and read it in "small" chunks. +%% Second, send in a bunch of "small" chunks, and read in one "big" chunk. +%% Socket is IPv4. + +traffic_send_and_recv_chunks_tcp4(suite) -> + []; +traffic_send_and_recv_chunks_tcp4(doc) -> + []; +traffic_send_and_recv_chunks_tcp4(_Config) when is_list(_Config) -> + tc_try(traffic_send_and_recv_chunks_tcp4, + fun() -> + ?TT(?SECS(30)), + InitState = #{domain => inet}, + ok = traffic_send_and_recv_chunks_tcp(InitState) + end). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test that the send and recv functions +%% behave as expected when sending and/or reading chunks. +%% First send data in one "big" chunk, and read it in "small" chunks. +%% Second, send in a bunch of "small" chunks, and read in one "big" chunk. +%% Socket is IPv6. + +traffic_send_and_recv_chunks_tcp6(suite) -> + []; +traffic_send_and_recv_chunks_tcp6(doc) -> + []; +traffic_send_and_recv_chunks_tcp6(_Config) when is_list(_Config) -> + tc_try(traffic_send_and_recv_chunks_tcp6, + fun() -> + not_yet_implemented(), + ?TT(?SECS(30)), + InitState = #{domain => inet6}, + ok = traffic_send_and_recv_chunks_tcp(InitState) + end). + + +traffic_send_and_recv_chunks_tcp(InitState) -> + ServerSeq = + [ + %% *** Wait for start order part *** + #{desc => "await start", + cmd => fun(State) -> + Tester = ?SEV_AWAIT_START(), + {ok, State#{tester => Tester}} + end}, + #{desc => "monitor tester", + cmd => fun(#{tester := Tester} = _State) -> + _MRef = erlang:monitor(process, Tester), + ok + end}, + + %% *** Init part *** + #{desc => "which local address", + cmd => fun(#{domain := Domain} = State) -> + LAddr = which_local_addr(Domain), + LSA = #{family => Domain, addr => LAddr}, + {ok, State#{local_sa => LSA}} + end}, + #{desc => "create listen socket", + cmd => fun(#{domain := Domain} = State) -> + case socket:open(Domain, stream, tcp) of + {ok, Sock} -> + {ok, State#{lsock => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "bind to local address", + cmd => fun(#{lsock := LSock, local_sa := LSA} = State) -> + case socket:bind(LSock, LSA) of + {ok, Port} -> + {ok, State#{lport => Port}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "make listen socket", + cmd => fun(#{lsock := LSock}) -> + socket:listen(LSock) + end}, + #{desc => "announce ready (init)", + cmd => fun(#{tester := Tester, local_sa := LSA, lport := Port}) -> + ServerSA = LSA#{port => Port}, + ?SEV_ANNOUNCE_READY(Tester, init, ServerSA), + ok + end}, + + %% The actual test + #{desc => "await continue (accept)", + cmd => fun(#{tester := Tester} = _State) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, accept) + end}, + #{desc => "accept", + cmd => fun(#{lsock := LSock} = State) -> + case socket:accept(LSock) of + {ok, Sock} -> + {ok, State#{csock => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (accept)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, accept), + ok + end}, + + #{desc => "await continue (recv-many-small)", + cmd => fun(#{tester := Tester} = _State) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, recv_many_small) + end}, + #{desc => "recv chunk 1", + cmd => fun(#{csock := Sock} = State) -> + case socket:recv(Sock, 100) of + {ok, Chunk} -> + ?SEV_IPRINT("recv of chunk 1 of ~p bytes", + [size(Chunk)]), + {ok, State#{chunks => [b2l(Chunk)]}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "recv chunk 2", + cmd => fun(#{csock := Sock, + chunks := Chunks} = State) -> + case socket:recv(Sock, 100) of + {ok, Chunk} -> + ?SEV_IPRINT("recv of chunk 2 of ~p bytes", + [size(Chunk)]), + {ok, State#{chunks => [b2l(Chunk)|Chunks]}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "recv chunk 3", + cmd => fun(#{csock := Sock, + chunks := Chunks} = State) -> + case socket:recv(Sock, 100) of + {ok, Chunk} -> + ?SEV_IPRINT("recv of chunk 3 of ~p bytes", + [size(Chunk)]), + {ok, State#{chunks => [b2l(Chunk)|Chunks]}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "recv chunk 4", + cmd => fun(#{csock := Sock, + chunks := Chunks} = State) -> + case socket:recv(Sock, 100) of + {ok, Chunk} -> + ?SEV_IPRINT("recv of chunk 4 of ~p bytes", + [size(Chunk)]), + {ok, State#{chunks => [b2l(Chunk)|Chunks]}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "recv chunk 5", + cmd => fun(#{csock := Sock, + chunks := Chunks} = State) -> + case socket:recv(Sock, 100) of + {ok, Chunk} -> + ?SEV_IPRINT("recv of chunk 5 of ~p bytes", + [size(Chunk)]), + {ok, State#{chunks => [b2l(Chunk)|Chunks]}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "recv chunk 6", + cmd => fun(#{csock := Sock, + chunks := Chunks} = State) -> + case socket:recv(Sock, 100) of + {ok, Chunk} -> + ?SEV_IPRINT("recv of chunk 6 of ~p bytes", + [size(Chunk)]), + {ok, State#{chunks => [b2l(Chunk)|Chunks]}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "recv chunk 7", + cmd => fun(#{csock := Sock, + chunks := Chunks} = State) -> + case socket:recv(Sock, 100) of + {ok, Chunk} -> + ?SEV_IPRINT("recv of chunk 7 of ~p bytes", + [size(Chunk)]), + {ok, State#{chunks => [b2l(Chunk)|Chunks]}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "recv chunk 8", + cmd => fun(#{csock := Sock, + chunks := Chunks} = State) -> + case socket:recv(Sock, 100) of + {ok, Chunk} -> + ?SEV_IPRINT("recv of chunk 8 of ~p bytes", + [size(Chunk)]), + {ok, State#{chunks => [b2l(Chunk)|Chunks]}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "recv chunk 9", + cmd => fun(#{csock := Sock, + chunks := Chunks} = State) -> + case socket:recv(Sock, 100) of + {ok, Chunk} -> + ?SEV_IPRINT("recv of chunk 9 of ~p bytes", + [size(Chunk)]), + {ok, State#{chunks => [b2l(Chunk)|Chunks]}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "recv chunk 10", + cmd => fun(#{csock := Sock, + chunks := Chunks} = State) -> + case socket:recv(Sock, 100) of + {ok, Chunk} -> + ?SEV_IPRINT("recv of chunk 10 of ~p bytes", + [size(Chunk)]), + {ok, State#{chunks => [b2l(Chunk)|Chunks]}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (recv-many-small)", + cmd => fun(#{tester := Tester, + chunks := Chunks} = State) -> + Data = lists:flatten(lists:reverse(Chunks)), + ?SEV_ANNOUNCE_READY(Tester, recv_many_small, Data), + {ok, maps:remove(chunks, State)} + end}, + + #{desc => "await continue (recv-one-big)", + cmd => fun(#{tester := Tester} = State) -> + case ?SEV_AWAIT_CONTINUE(Tester, tester, recv_one_big) of + {ok, Size} -> + {ok, State#{size => Size}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "recv (one big)", + cmd => fun(#{tester := Tester, csock := Sock, size := Size} = _State) -> + case socket:recv(Sock, Size) of + {ok, Data} -> + ?SEV_ANNOUNCE_READY(Tester, + recv_one_big, + b2l(Data)), + ok; + {error, _} = ERROR -> + ERROR + end + end}, + + #{desc => "await terminate (from tester)", + cmd => fun(#{tester := Tester} = State) -> + case ?SEV_AWAIT_TERMINATE(Tester, tester) of + ok -> + {ok, maps:remove(tester, State)}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "close connection socket (just in case)", + cmd => fun(#{csock := Sock} = State) -> + (catch socket:close(Sock)), + {ok, maps:remove(csock, State)} + end}, + #{desc => "close listen socket", + cmd => fun(#{lsock := Sock} = State) -> + (catch socket:close(Sock)), + {ok, maps:remove(lsock, State)} + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + ClientSeq = + [ + %% *** Wait for start order part *** + #{desc => "await start", + cmd => fun(State) -> + {Tester, ServerSA} = ?SEV_AWAIT_START(), + {ok, State#{tester => Tester, + server_sa => ServerSA}} + end}, + #{desc => "monitor tester", + cmd => fun(#{tester := Tester} = _State) -> + _MRef = erlang:monitor(process, Tester), + ok + end}, + + %% *** Init part *** + #{desc => "create node", + cmd => fun(#{host := Host} = State) -> + case start_node(Host, client) of + {ok, Node} -> + ?SEV_IPRINT("(remote) client node ~p started", + [Node]), + {ok, State#{node => Node}}; + {error, Reason, _} -> + {error, Reason} + end + end}, + #{desc => "monitor client node", + cmd => fun(#{node := Node} = _State) -> + true = erlang:monitor_node(Node, true), + ok + end}, + #{desc => "start remote client", + cmd => fun(#{node := Node} = State) -> + Pid = traffic_snr_tcp_client_start(Node), + ?SEV_IPRINT("client ~p started", [Pid]), + {ok, State#{rclient => Pid}} + end}, + #{desc => "monitor remote client", + cmd => fun(#{rclient := Pid}) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + #{desc => "order remote client to start", + cmd => fun(#{rclient := Client, server_sa := ServerSA}) -> + ?SEV_ANNOUNCE_START(Client, ServerSA), + ok + end}, + #{desc => "await remote client ready", + cmd => fun(#{tester := Tester, + rclient := Client} = _State) -> + ?SEV_AWAIT_READY(Client, rclient, init, + [{tester, Tester}]) + end}, + #{desc => "announce ready (init)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, init), + ok + end}, + + %% The actual test + #{desc => "await continue (connect)", + cmd => fun(#{tester := Tester, + rclient := Client} = _State) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, connect, + [{rclient, Client}]), + ok + end}, + #{desc => "order remote client to continue (connect)", + cmd => fun(#{rclient := Client}) -> + ?SEV_ANNOUNCE_CONTINUE(Client, connect), + ok + end}, + #{desc => "await client process ready (connect)", + cmd => fun(#{tester := Tester, + rclient := Client} = _State) -> + ?SEV_AWAIT_READY(Client, rclient, connect, + [{tester, Tester}]) + end}, + #{desc => "announce ready (connect)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, connect), + ok + end}, + + #{desc => "await continue (send-one-big)", + cmd => fun(#{tester := Tester, + rclient := Client} = State) -> + case ?SEV_AWAIT_CONTINUE(Tester, tester, + send_one_big, + [{rclient, Client}]) of + {ok, Data} -> + {ok, State#{data => Data}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "order remote client to continue (send)", + cmd => fun(#{rclient := Client, data := Data}) -> + ?SEV_ANNOUNCE_CONTINUE(Client, send, Data), + ok + end}, + #{desc => "await client process ready (send)", + cmd => fun(#{tester := Tester, + rclient := Client} = _State) -> + case ?SEV_AWAIT_READY(Client, rclient, send, + [{tester, Tester}]) of + {ok, Result} -> + Result; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (send-one-big)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, send_one_big), + ok + end}, + + #{desc => "await continue (send-many-small)", + cmd => fun(#{tester := Tester, + rclient := Client} = State) -> + case ?SEV_AWAIT_CONTINUE(Tester, tester, + send_many_small, + [{rclient, Client}]) of + {ok, Data} -> + {ok, State#{data => Data}}; + {error, _} = ERROR -> + ERROR + end end}, #{desc => "order remote client to continue (send chunk 1)", cmd => fun(#{rclient := Client, @@ -6666,6 +7574,16 @@ traffic_snr_tcp_client_await_terminate(Parent) -> %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +local_host() -> + try net_adm:localhost() of + Host when is_list(Host) -> + list_to_atom(Host) + catch + C:E:S -> + erlang:raise(C, E, S) + end. + + %% This gets the local address (not 127.0...) %% We should really implement this using the (new) net module, %% but until that gets the necessary functionality... -- cgit v1.2.3 From 03b6d593970e1783cdbb82729edb0ba27eb29ae4 Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Thu, 15 Nov 2018 23:02:43 +0100 Subject: [socket-nif|test] Attempt to "fix" the api_to_connect_tcpX test cases Attempt to fix the the api_to_connect_tcp[4|6] test cases. On some linux versions (Ubuntu 14), the backlog seems to not work. No luck... OTP-14831 --- erts/emulator/test/socket_SUITE.erl | 444 ++++++++++++++++++++++++++++-------- 1 file changed, 348 insertions(+), 96 deletions(-) diff --git a/erts/emulator/test/socket_SUITE.erl b/erts/emulator/test/socket_SUITE.erl index 815cc1e624..9acf334633 100644 --- a/erts/emulator/test/socket_SUITE.erl +++ b/erts/emulator/test/socket_SUITE.erl @@ -1532,7 +1532,10 @@ api_to_connect_tcp4(_Config) when is_list(_Config) -> tc_try(api_to_connect_tcp4, fun() -> ?TT(?SECS(10)), - InitState = #{domain => inet, timeout => 5000}, + InitState = #{domain => inet, + backlog => 1, + timeout => 5000, + connect_limit => 3}, ok = api_to_connect_tcp(InitState) end). @@ -1550,7 +1553,10 @@ api_to_connect_tcp6(_Config) when is_list(_Config) -> fun() -> not_yet_implemented(), ?TT(?SECS(10)), - InitState = #{domain => inet6, timeout => 5000}, + InitState = #{domain => inet6, + backlog => 1, + timeout => 5000, + connect_limit => 3}, ok = api_to_connect_tcp(InitState) end). @@ -1570,8 +1576,9 @@ api_to_connect_tcp(InitState) -> %% *** Wait for start order part *** #{desc => "await start (from tester)", cmd => fun(State) -> - Tester = ?SEV_AWAIT_START(), - {ok, State#{tester => Tester}} + {Tester, Backlog} = ?SEV_AWAIT_START(), + {ok, State#{tester => Tester, + backlog => Backlog}} end}, #{desc => "monitor tester", cmd => fun(#{tester := Tester} = _State) -> @@ -1605,8 +1612,8 @@ api_to_connect_tcp(InitState) -> end end}, #{desc => "make listen socket (with backlog = 1)", - cmd => fun(#{lsock := LSock}) -> - socket:listen(LSock, 1) + cmd => fun(#{lsock := LSock, backlog := Backlog}) -> + socket:listen(LSock, Backlog) end}, #{desc => "announce ready (init)", cmd => fun(#{tester := Tester, lport := Port}) -> @@ -1636,79 +1643,178 @@ api_to_connect_tcp(InitState) -> ?SEV_FINISH_NORMAL ], - TesterSeq = + ClientSeq = [ - %% *** Init part *** - #{desc => "monitor server", - cmd => fun(#{server := Server} = _State) -> - _MRef = erlang:monitor(process, Server), + %% *** Wait for start order part *** + #{desc => "await start", + cmd => fun(State) -> + {Tester, ServerSA} = ?SEV_AWAIT_START(), + {ok, State#{tester => Tester, + server_sa => ServerSA}} + end}, + #{desc => "monitor tester", + cmd => fun(#{tester := Tester} = _State) -> + _MRef = erlang:monitor(process, Tester), ok end}, + + %% *** Init part *** #{desc => "which local address", cmd => fun(#{domain := Domain} = State) -> LAddr = which_local_addr(Domain), LSA = #{family => Domain, addr => LAddr}, {ok, State#{local_sa => LSA}} end}, - #{desc => "create socket 1", - cmd => fun(#{domain := Domain} = State) -> - case socket:open(Domain, stream, tcp) of - {ok, Sock} -> - {ok, State#{sock1 => Sock}}; - {error, _} = ERROR -> - ERROR + #{desc => "create node", + cmd => fun(#{host := Host} = State) -> + case start_node(Host, client) of + {ok, Node} -> + ?SEV_IPRINT("client node ~p started", + [Node]), + {ok, State#{node => Node}}; + {error, Reason, _} -> + {error, Reason} end end}, - #{desc => "create socket 2", - cmd => fun(#{domain := Domain} = State) -> - case socket:open(Domain, stream, tcp) of - {ok, Sock} -> - {ok, State#{sock2 => Sock}}; - {error, _} = ERROR -> - ERROR - end + #{desc => "monitor client node", + cmd => fun(#{node := Node} = _State) -> + true = erlang:monitor_node(Node, true), + ok end}, - #{desc => "create socket 3", - cmd => fun(#{domain := Domain} = State) -> - case socket:open(Domain, stream, tcp) of - {ok, Sock} -> - {ok, State#{sock3 => Sock}}; + #{desc => "start remote client on client node", + cmd => fun(#{node := Node} = State) -> + Pid = api_toc_tcp_client_start(Node), + ?SEV_IPRINT("remote client ~p started", [Pid]), + {ok, State#{rclient => Pid}} + end}, + #{desc => "monitor remote client", + cmd => fun(#{rclient := Pid}) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + #{desc => "order remote client to start", + cmd => fun(#{rclient := Client, + server_sa := ServerSA}) -> + ?SEV_ANNOUNCE_START(Client, ServerSA), + ok + end}, + #{desc => "await remote client ready", + cmd => fun(#{tester := Tester, + rclient := Client} = _State) -> + ?SEV_AWAIT_READY(Client, rclient, init, + [{tester, Tester}]) + end}, + #{desc => "announce ready (init)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, init), + ok + end}, + + %% The actual test + #{desc => "await continue (connect)", + cmd => fun(#{tester := Tester, + rclient := Client} = State) -> + case ?SEV_AWAIT_CONTINUE(Tester, tester, connect, + [{rclient, Client}]) of + {ok, {ConTimeout, ConLimit}} -> + {ok, State#{connect_timeout => ConTimeout, + connect_limit => ConLimit}}; {error, _} = ERROR -> ERROR end end}, - #{desc => "bind socket 1 to local address", - cmd => fun(#{sock1 := Sock, local_sa := LSA} = _State) -> - case socket:bind(Sock, LSA) of - {ok, _} -> - ok; + #{desc => "order remote client to continue (connect)", + cmd => fun(#{rclient := RClient, + connect_timeout := ConTimeout, + connect_limit := ConLimit}) -> + ?SEV_ANNOUNCE_CONTINUE(RClient, connect, + {ConTimeout, ConLimit}), + ok + end}, + #{desc => "await remote client ready (connect)", + cmd => fun(#{tester := Tester, + rclient := RClient} = State) -> + case ?SEV_AWAIT_READY(RClient, rclient, connect, + [{tester, Tester}]) of + {ok, ok = _Result} -> + {ok, maps:remove(connect_limit, State)}; + {ok, Result} -> + Result; {error, _} = ERROR -> ERROR end end}, - #{desc => "bind socket 2 to local address", - cmd => fun(#{sock2 := Sock, local_sa := LSA} = _State) -> - case socket:bind(Sock, LSA) of - {ok, _} -> - ok; + + #{desc => "announce ready (connect)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, connect), + ok + end}, + + %% Termination + #{desc => "await terminate (from tester)", + cmd => fun(#{tester := Tester, + rclient := RClient} = State) -> + case ?SEV_AWAIT_TERMINATE(Tester, tester, + [{rclient, RClient}]) of + ok -> + {ok, maps:remove(tester, State)}; {error, _} = ERROR -> ERROR end end}, - #{desc => "bind socket 3 to local address", - cmd => fun(#{sock3 := Sock, local_sa := LSA} = _State) -> - case socket:bind(Sock, LSA) of - {ok, _} -> - ok; - {error, _} = ERROR -> - ERROR + #{desc => "kill remote client", + cmd => fun(#{rclient := Client}) -> + ?SEV_ANNOUNCE_TERMINATE(Client), + ok + end}, + #{desc => "await remote client termination", + cmd => fun(#{rclient := Client} = State) -> + ?SEV_AWAIT_TERMINATION(Client), + State1 = maps:remove(rclient, State), + {ok, State1} + end}, + #{desc => "stop client node", + cmd => fun(#{node := Node} = _State) -> + stop_node(Node) + end}, + #{desc => "await client node termination", + cmd => fun(#{node := Node} = State) -> + receive + {nodedown, Node} -> + State1 = maps:remove(node_id, State), + State2 = maps:remove(node, State1), + {ok, State2} end end}, - %% *** Synchronize with the server *** + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + TesterSeq = + [ + %% *** Init part *** + #{desc => "monitor server", + cmd => fun(#{server := Server} = _State) -> + _MRef = erlang:monitor(process, Server), + ok + end}, + #{desc => "monitor client", + cmd => fun(#{client := Client} = _State) -> + _MRef = erlang:monitor(process, Client), + ok + end}, + #{desc => "which local address", + cmd => fun(#{domain := Domain} = State) -> + LAddr = which_local_addr(Domain), + LSA = #{family => Domain, addr => LAddr}, + {ok, State#{local_sa => LSA}} + end}, #{desc => "order server start", - cmd => fun(#{server := Server}) -> - ?SEV_ANNOUNCE_START(Server), + cmd => fun(#{server := Server, + backlog := Backlog}) -> + ?SEV_ANNOUNCE_START(Server, Backlog), ok end}, #{desc => "await server ready (init)", @@ -1717,95 +1823,241 @@ api_to_connect_tcp(InitState) -> ServerSA = LSA#{port => Port}, {ok, State#{server_sa => ServerSA}} end}, + #{desc => "order client start", + cmd => fun(#{client := Client, + server_sa := ServerSA}) -> + ?SEV_ANNOUNCE_START(Client, ServerSA), + ok + end}, + #{desc => "await client ready (init)", + cmd => fun(#{client := Client} = _State) -> + ?SEV_AWAIT_READY(Client, client, init), + ok + end}, - %% *** Connect sequence *** - #{desc => "order (server) start", - cmd => fun(#{sock1 := Sock1, - sock2 := Sock2, - sock3 := Sock3, - server_sa := SSA, - timeout := To}) -> - Socks = [Sock1, Sock2, Sock3], - api_to_connect_tcp_await_timeout(Socks, To, SSA) + %% The actual test + %% The server does nothing (this is the point), no accept, + %% the client tries to connect. + #{desc => "order client continue (connect)", + cmd => fun(#{client := Client, + timeout := Timeout, + connect_limit := ConLimit} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Client, connect, + {Timeout, ConLimit}), + ok end}, + #{desc => "await client ready (connect)", + cmd => fun(#{server := Server, + client := Client} = _State) -> + case ?SEV_AWAIT_READY(Client, client, connect, + [{server, Server}]) of + {ok, _} -> + ok; + {error, _} = ERROR -> + ERROR + end + end}, + %% *** Terminate server *** - #{desc => "order (server) terminate", + #{desc => "order client terminate", + cmd => fun(#{client := Client} = _State) -> + ?SEV_ANNOUNCE_TERMINATE(Client), + ok + end}, + #{desc => "await client down", + cmd => fun(#{client := Client} = State) -> + ?SEV_AWAIT_TERMINATION(Client), + State1 = maps:remove(client, State), + {ok, State1} + end}, + #{desc => "order server terminate", cmd => fun(#{server := Server} = _State) -> ?SEV_ANNOUNCE_TERMINATE(Server), ok end}, - #{desc => "await (server) down", + #{desc => "await server down", cmd => fun(#{server := Server} = State) -> ?SEV_AWAIT_TERMINATION(Server), State1 = maps:remove(server, State), State2 = maps:remove(server_sa, State1), {ok, State2} end}, - #{desc => "close socket 3", - cmd => fun(#{sock3 := Sock} = State) -> - sock_close(Sock), - {ok, maps:remove(sock3, State)} - - end}, - #{desc => "close socket 2", - cmd => fun(#{sock2 := Sock} = State) -> - sock_close(Sock), - {ok, maps:remove(sock2, State)} - - end}, - #{desc => "close socket 1", - cmd => fun(#{sock1 := Sock} = State) -> - sock_close(Sock), - {ok, maps:remove(sock1, State)} - - end}, %% *** We are done *** ?SEV_FINISH_NORMAL ], i("create server evaluator"), - ServerInitState = InitState, + ServerInitState = #{domain => maps:get(domain, InitState)}, Server = ?SEV_START("server", ServerSeq, ServerInitState), + i("create client evaluator"), + ClientInitState = #{host => local_host(), + domain => maps:get(domain, InitState)}, + Client = ?SEV_START("client", ClientSeq, ClientInitState), + i("create tester evaluator"), - TesterInitState = InitState#{server => Server#ev.pid}, + TesterInitState = InitState#{server => Server#ev.pid, + client => Client#ev.pid}, Tester = ?SEV_START("tester", TesterSeq, TesterInitState), i("await evaluator(s)"), - ok = ?SEV_AWAIT_FINISH([Server, Tester]). + ok = ?SEV_AWAIT_FINISH([Server, Client, Tester]). + + +api_toc_tcp_client_start(Node) -> + Self = self(), + GL = group_leader(), + Fun = fun() -> api_toc_tcp_client(Self, GL) end, + erlang:spawn(Node, Fun). + +api_toc_tcp_client(Parent, GL) -> + api_toc_tcp_client_init(Parent, GL), + ServerSA = api_toc_tcp_client_await_start(Parent), + Domain = maps:get(family, ServerSA), + api_toc_tcp_client_announce_ready(Parent, init), + {To, ConLimit} = api_toc_tcp_client_await_continue(Parent, connect), + Result = api_to_connect_tcp_await_timeout(To, ServerSA, Domain, ConLimit), + api_toc_tcp_client_announce_ready(Parent, connect, Result), + Reason = sc_rs_tcp_client_await_terminate(Parent), + exit(Reason). + +api_toc_tcp_client_init(Parent, GL) -> + %% i("api_toc_tcp_client_init -> entry"), + _MRef = erlang:monitor(process, Parent), + group_leader(self(), GL), + ok. + +api_toc_tcp_client_await_start(Parent) -> + %% i("api_toc_tcp_client_await_start -> entry"), + ?SEV_AWAIT_START(Parent). + +api_toc_tcp_client_announce_ready(Parent, Slogan) -> + ?SEV_ANNOUNCE_READY(Parent, Slogan). +api_toc_tcp_client_announce_ready(Parent, Slogan, Result) -> + ?SEV_ANNOUNCE_READY(Parent, Slogan, Result). +api_toc_tcp_client_await_continue(Parent, Slogan) -> + %% i("api_toc_tcp_client_await_continue -> entry"), + case ?SEV_AWAIT_CONTINUE(Parent, parent, Slogan) of + ok -> + ok; + {ok, Extra} -> + Extra; + {error, Reason} -> + exit({await_continue, Slogan, Reason}) + end. -api_to_connect_tcp_await_timeout(Socks, To, ServerSA) -> - api_to_connect_tcp_await_timeout(Socks, To, ServerSA, 1). +api_toc_tcp_client_await_terminate(Parent) -> + %% i("api_toc_tcp_client_await_terminate -> entry"), + case ?SEV_AWAIT_TERMINATE(Parent, parent) of + ok -> + ok; + {error, Reason} -> + Reason + end. -api_to_connect_tcp_await_timeout([], _To, _ServerSA, _ID) -> - ?FAIL(unexpected_success); -api_to_connect_tcp_await_timeout([Sock|Socks], To, ServerSA, ID) -> - ?SEV_IPRINT("~w: try connect", [ID]), +api_to_connect_tcp_await_timeout(To, ServerSA, Domain, ConLimit) -> + LAddr = which_local_addr(Domain), + LSA = #{family => Domain, + addr => LAddr}, + NewSock = fun() -> + S = case socket:open(Domain, stream, tcp) of + {ok, Sock} -> + Sock; + {error, OReason} -> + ?FAIL({open, OReason}) + end, + case socket:bind(S, LSA) of + {ok, _} -> + S; + {error, BReason} -> + ?FAIL({bind, BReason}) + end + end, + api_to_connect_tcp_await_timeout(1, ConLimit, To, ServerSA, NewSock, []). + +api_to_connect_tcp_await_timeout(ID, ConLimit, _To, _ServerSA, _NewSock, Acc) + when (ID > ConLimit) -> + api_to_connect_tcp_await_timeout3(Acc), + {error, {connect_limit_reached, ID, ConLimit}}; +api_to_connect_tcp_await_timeout(ID, ConLimit, To, ServerSA, NewSock, Acc) -> + case api_to_connect_tcp_await_timeout2(ID, To, ServerSA, NewSock) of + ok -> + %% ?SEV_IPRINT("success when number of socks: ~w", [length(Acc)]), + api_to_connect_tcp_await_timeout3(Acc), + ok; + {ok, Sock} -> + %% ?SEV_IPRINT("~w: unexpected success (connect)", [ID]), + api_to_connect_tcp_await_timeout(ID+1, ConLimit, + To, ServerSA, NewSock, + [Sock|Acc]); + {error, _} = ERROR -> + ERROR + end. + +api_to_connect_tcp_await_timeout2(ID, To, ServerSA, NewSock) -> + Sock = NewSock(), + %% ?SEV_IPRINT("~w: try connect", [ID]), Start = t(), case socket:connect(Sock, ServerSA, To) of {error, timeout} -> - ?SEV_IPRINT("expected timeout (~w)", [ID]), Stop = t(), TDiff = tdiff(Start, Stop), if (TDiff >= To) -> + (catch socket:close(Sock)), ok; true -> - {error, {unexpected_timeout, TDiff, To}} + (catch socket:close(Sock)), + ?FAIL({unexpected_timeout, TDiff, To}) end; - {error, econnreset = Reason} -> - ?SEV_IPRINT("failed connecting: ~p - giving up", [Reason]), + {error, econnreset = _Reason} -> + (catch socket:close(Sock)), ok; {error, Reason} -> - ?SEV_EPRINT("failed connecting: ~p", [Reason]), + (catch socket:close(Sock)), ?FAIL({connect, Reason}); ok -> - ?SEV_IPRINT("unexpected success (~w) - try next", [ID]), - api_to_connect_tcp_await_timeout(Socks, To, ServerSA, ID+1) + {ok, Sock} end. + +api_to_connect_tcp_await_timeout3([]) -> + ok; +api_to_connect_tcp_await_timeout3([Sock|Socka]) -> + (catch socket:close(Sock)), + api_to_connect_tcp_await_timeout3(Socka). + +%% api_to_connect_tcp_await_timeout(Socks, To, ServerSA) -> +%% api_to_connect_tcp_await_timeout(Socks, To, ServerSA, 1). + +%% api_to_connect_tcp_await_timeout([], _To, _ServerSA, _ID) -> +%% ?FAIL(unexpected_success); +%% api_to_connect_tcp_await_timeout([Sock|Socks], To, ServerSA, ID) -> +%% ?SEV_IPRINT("~w: try connect", [ID]), +%% Start = t(), +%% case socket:connect(Sock, ServerSA, To) of +%% {error, timeout} -> +%% ?SEV_IPRINT("expected timeout (~w)", [ID]), +%% Stop = t(), +%% TDiff = tdiff(Start, Stop), +%% if +%% (TDiff >= To) -> +%% ok; +%% true -> +%% {error, {unexpected_timeout, TDiff, To}} +%% end; +%% {error, econnreset = Reason} -> +%% ?SEV_IPRINT("failed connecting: ~p - giving up", [Reason]), +%% ok; +%% {error, Reason} -> +%% ?SEV_EPRINT("failed connecting: ~p", [Reason]), +%% ?FAIL({connect, Reason}); +%% ok -> +%% ?SEV_IPRINT("unexpected success (~w) - try next", [ID]), +%% api_to_connect_tcp_await_timeout(Socks, To, ServerSA, ID+1) +%% end. -- cgit v1.2.3 From b7e5753c4e2eb22d231f2b64291ba7d1aec4d444 Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Mon, 26 Nov 2018 18:31:49 +0100 Subject: [socket-nif] Conditional use of sctp struct field Added config checks for the sctp struct field: sctp_event_subscribe.sctp_sender_dry_event Also, if-def'ed the code accordingly. OTP-14831 --- erts/configure.in | 3 ++- erts/emulator/nifs/common/socket_nif.c | 9 ++++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/erts/configure.in b/erts/configure.in index d211b81878..79a1944a03 100644 --- a/erts/configure.in +++ b/erts/configure.in @@ -1594,7 +1594,8 @@ if test x"$ac_cv_header_netinet_sctp_h" = x"yes"; then struct sctp_paddrparams.spp_sackdelay, struct sctp_paddrparams.spp_flags, struct sctp_remote_error.sre_data, - struct sctp_send_failed.ssf_data], [], [], + struct sctp_send_failed.ssf_data, + struct sctp_event_subscribe.sctp_sender_dry_event], [], [], [#if HAVE_SYS_SOCKET_H #include #endif diff --git a/erts/emulator/nifs/common/socket_nif.c b/erts/emulator/nifs/common/socket_nif.c index f657da3ace..cc58918f43 100644 --- a/erts/emulator/nifs/common/socket_nif.c +++ b/erts/emulator/nifs/common/socket_nif.c @@ -9225,7 +9225,10 @@ ERL_NIF_TERM nsetopt_lvl_sctp_events(ErlNifEnv* env, ERL_NIF_TERM result; ERL_NIF_TERM eDataIn, eAssoc, eAddr, eSndFailure; ERL_NIF_TERM ePeerError, eShutdown, ePartialDelivery; - ERL_NIF_TERM eAdaptLayer, eAuth, eSndDry; + ERL_NIF_TERM eAdaptLayer, eAuth; +#if defined(HAVE_STRUCT_SCTP_EVENT_SUBSCRIBE_SCTP_SENDER_DRY_EVENT) + ERL_NIF_TERM eSndDry; +#endif struct sctp_event_subscribe events; int res; size_t sz; @@ -9273,8 +9276,10 @@ ERL_NIF_TERM nsetopt_lvl_sctp_events(ErlNifEnv* env, if (!GET_MAP_VAL(env, eVal, atom_authentication, &eAuth)) return esock_make_error(env, esock_atom_einval); +#if defined(HAVE_STRUCT_SCTP_EVENT_SUBSCRIBE_SCTP_SENDER_DRY_EVENT) if (!GET_MAP_VAL(env, eVal, atom_sender_dry, &eSndDry)) return esock_make_error(env, esock_atom_einval); +#endif SSDBG( descP, ("SOCKET", "nsetopt_lvl_sctp_events -> decode attributes\r\n") ); @@ -9288,7 +9293,9 @@ ERL_NIF_TERM nsetopt_lvl_sctp_events(ErlNifEnv* env, events.sctp_partial_delivery_event = esock_decode_bool(ePartialDelivery); events.sctp_adaptation_layer_event = esock_decode_bool(eAdaptLayer); events.sctp_authentication_event = esock_decode_bool(eAuth); +#if defined(HAVE_STRUCT_SCTP_EVENT_SUBSCRIBE_SCTP_SENDER_DRY_EVENT) events.sctp_sender_dry_event = esock_decode_bool(eSndDry); +#endif SSDBG( descP, ("SOCKET", "nsetopt_lvl_sctp_events -> set events option\r\n") ); -- cgit v1.2.3 From cdc39e8040e5817bb15c2d035c48cec812047035 Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Tue, 27 Nov 2018 11:59:28 +0100 Subject: [socket-nif] Conditional use of sctp struct field Added config checks for the sctp struct field: sctp_event_subscribe.sctp_authentication_event Also, if-def'ed the code accordingly. If-def'ed code to handle the (non-) existence of IP_PMTUDISC_PROBE and IPV6_PMTUDISC_PROBE for the IP and IPv6 MTU_DISCOVER options. OTP-14831 --- erts/configure.in | 1 + erts/emulator/nifs/common/socket_nif.c | 12 ++++++++++++ 2 files changed, 13 insertions(+) diff --git a/erts/configure.in b/erts/configure.in index 79a1944a03..e6e734728c 100644 --- a/erts/configure.in +++ b/erts/configure.in @@ -1595,6 +1595,7 @@ if test x"$ac_cv_header_netinet_sctp_h" = x"yes"; then struct sctp_paddrparams.spp_flags, struct sctp_remote_error.sre_data, struct sctp_send_failed.ssf_data, + struct sctp_event_subscribe.sctp_authentication_event, struct sctp_event_subscribe.sctp_sender_dry_event], [], [], [#if HAVE_SYS_SOCKET_H #include diff --git a/erts/emulator/nifs/common/socket_nif.c b/erts/emulator/nifs/common/socket_nif.c index cc58918f43..cdd9f0fb15 100644 --- a/erts/emulator/nifs/common/socket_nif.c +++ b/erts/emulator/nifs/common/socket_nif.c @@ -9273,8 +9273,10 @@ ERL_NIF_TERM nsetopt_lvl_sctp_events(ErlNifEnv* env, if (!GET_MAP_VAL(env, eVal, atom_adaptation_layer, &eAdaptLayer)) return esock_make_error(env, esock_atom_einval); +#if defined(HAVE_STRUCT_SCTP_EVENT_SUBSCRIBE_SCTP_AUTHENTICATION_EVENT) if (!GET_MAP_VAL(env, eVal, atom_authentication, &eAuth)) return esock_make_error(env, esock_atom_einval); +#endif #if defined(HAVE_STRUCT_SCTP_EVENT_SUBSCRIBE_SCTP_SENDER_DRY_EVENT) if (!GET_MAP_VAL(env, eVal, atom_sender_dry, &eSndDry)) @@ -9292,7 +9294,9 @@ ERL_NIF_TERM nsetopt_lvl_sctp_events(ErlNifEnv* env, events.sctp_shutdown_event = esock_decode_bool(eShutdown); events.sctp_partial_delivery_event = esock_decode_bool(ePartialDelivery); events.sctp_adaptation_layer_event = esock_decode_bool(eAdaptLayer); +#if defined(HAVE_STRUCT_SCTP_EVENT_SUBSCRIBE_SCTP_AUTHENTICATION_EVENT) events.sctp_authentication_event = esock_decode_bool(eAuth); +#endif #if defined(HAVE_STRUCT_SCTP_EVENT_SUBSCRIBE_SCTP_SENDER_DRY_EVENT) events.sctp_sender_dry_event = esock_decode_bool(eSndDry); #endif @@ -15418,8 +15422,10 @@ char* decode_ip_pmtudisc(ErlNifEnv* env, ERL_NIF_TERM eVal, int* val) *val = IP_PMTUDISC_DONT; } else if (COMPARE(eVal, atom_do) == 0) { *val = IP_PMTUDISC_DO; +#if defined(IP_PMTUDISC_PROBE) } else if (COMPARE(eVal, atom_probe) == 0) { *val = IP_PMTUDISC_PROBE; +#endif } else { *val = -1; res = ESOCK_STR_EINVAL; @@ -15469,8 +15475,10 @@ char* decode_ipv6_pmtudisc(ErlNifEnv* env, ERL_NIF_TERM eVal, int* val) *val = IPV6_PMTUDISC_DONT; } else if (COMPARE(eVal, atom_do) == 0) { *val = IPV6_PMTUDISC_DO; +#if defined(IPV6_PMTUDISC_PROBE) } else if (COMPARE(eVal, atom_probe) == 0) { *val = IPV6_PMTUDISC_PROBE; +#endif } else { *val = -1; res = ESOCK_STR_EINVAL; @@ -15523,9 +15531,11 @@ void encode_ip_pmtudisc(ErlNifEnv* env, int val, ERL_NIF_TERM* eVal) *eVal = atom_do; break; +#if defined(IP_PMTUDISC_PROBE) case IP_PMTUDISC_PROBE: *eVal = atom_probe; break; +#endif default: *eVal = MKI(env, val); @@ -15565,9 +15575,11 @@ void encode_ipv6_pmtudisc(ErlNifEnv* env, int val, ERL_NIF_TERM* eVal) *eVal = atom_do; break; +#if defined(IPV6_PMTUDISC_PROBE) case IPV6_PMTUDISC_PROBE: *eVal = atom_probe; break; +#endif default: *eVal = MKI(env, val); -- cgit v1.2.3 From 8d66078550e372f134d11afdf1075aa386195b6b Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Thu, 22 Nov 2018 18:01:12 +0100 Subject: [socket-nif|test] Fixed the test suite The ping-pong merge had some problems... --- erts/emulator/test/socket_SUITE.erl | 82 ++++++++++++++++--------------------- 1 file changed, 35 insertions(+), 47 deletions(-) diff --git a/erts/emulator/test/socket_SUITE.erl b/erts/emulator/test/socket_SUITE.erl index f2aace40de..71e32f8e95 100644 --- a/erts/emulator/test/socket_SUITE.erl +++ b/erts/emulator/test/socket_SUITE.erl @@ -1980,7 +1980,7 @@ api_toc_tcp_client(Parent, GL) -> {To, ConLimit} = api_toc_tcp_client_await_continue(Parent, connect), Result = api_to_connect_tcp_await_timeout(To, ServerSA, Domain, ConLimit), api_toc_tcp_client_announce_ready(Parent, connect, Result), - Reason = sc_rs_tcp_client_await_terminate(Parent), + Reason = api_toc_tcp_client_await_terminate(Parent), exit(Reason). api_toc_tcp_client_init(Parent, GL) -> @@ -2057,7 +2057,7 @@ api_to_connect_tcp_await_timeout(ID, ConLimit, To, ServerSA, NewSock, Acc) -> ERROR end. -api_to_connect_tcp_await_timeout2(ID, To, ServerSA, NewSock) -> +api_to_connect_tcp_await_timeout2(_ID, To, ServerSA, NewSock) -> Sock = NewSock(), %% ?SEV_IPRINT("~w: try connect", [ID]), Start = t(), @@ -10155,37 +10155,6 @@ tpp_udp_sock_close(Sock) -> end. -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -%% This gets the local address (not 127.0...) -%% We should really implement this using the (new) net module, -%% but until that gets the necessary functionality... -which_local_addr(Domain) -> - case inet:getifaddrs() of - {ok, IFL} -> - which_addr(Domain, IFL); - {error, Reason} -> - ?FAIL({inet, getifaddrs, Reason}) - end. - -which_addr(_Domain, []) -> - ?FAIL(no_address); -which_addr(Domain, [{Name, IFO}|_IFL]) when (Name =/= "lo") -> - which_addr2(Domain, IFO); -which_addr(Domain, [_|IFL]) -> - which_addr(Domain, IFL). - -which_addr2(_Domain, []) -> - ?FAIL(no_address); -which_addr2(inet = _Domain, [{addr, Addr}|_IFO]) when (size(Addr) =:= 4) -> - Addr; -which_addr2(inet6 = _Domain, [{addr, Addr}|_IFO]) when (size(Addr) =:= 8) -> - Addr; -which_addr2(Domain, [_|IFO]) -> - which_addr2(Domain, IFO). - - - %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% start_node(Host, NodeName) -> @@ -10335,6 +10304,25 @@ which_local_addr(Domain) -> ?FAIL({inet, getifaddrs, Reason}) end. +which_addr(_Domain, []) -> + ?FAIL(no_address); +which_addr(Domain, [{Name, IFO}|_IFL]) when (Name =/= "lo") -> + which_addr2(Domain, IFO); +which_addr(Domain, [_|IFL]) -> + which_addr(Domain, IFL). + +which_addr2(_Domain, []) -> + ?FAIL(no_address); +which_addr2(inet = _Domain, [{addr, Addr}|_IFO]) when (size(Addr) =:= 4) -> + Addr; +which_addr2(inet6 = _Domain, [{addr, Addr}|_IFO]) when (size(Addr) =:= 8) -> + Addr; +which_addr2(Domain, [_|IFO]) -> + which_addr2(Domain, IFO). + + + + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -10470,21 +10458,21 @@ f(F, A) -> %% i(Before ++ FStr ++ After, []). -d(F, A) -> - d(get(dbg_fd), F, A). +%% d(F, A) -> +%% d(get(dbg_fd), F, A). -d(undefined, F, A) -> - [NodeNameStr|_] = string:split(atom_to_list(node()), [$@]), - DbgFileName = f("~s-dbg.txt", [NodeNameStr]), - case file:open(DbgFileName, [write]) of - {ok, FD} -> - put(dbg_fd, FD), - d(FD, F, A); - {error, Reason} -> - exit({failed_open_dbg_file, Reason}) - end; -d(FD, F, A) -> - io:format(FD, "~s~n", [f("[~s] " ++ F, [formated_timestamp()|A])]). +%% d(undefined, F, A) -> +%% [NodeNameStr|_] = string:split(atom_to_list(node()), [$@]), +%% DbgFileName = f("~s-dbg.txt", [NodeNameStr]), +%% case file:open(DbgFileName, [write]) of +%% {ok, FD} -> +%% put(dbg_fd, FD), +%% d(FD, F, A); +%% {error, Reason} -> +%% exit({failed_open_dbg_file, Reason}) +%% end; +%% d(FD, F, A) -> +%% io:format(FD, "~s~n", [f("[~s] " ++ F, [formated_timestamp()|A])]). i(F) -> i(F, []). -- cgit v1.2.3 From c4e29690e5a57004f494d35ae7d5b9e00f624d76 Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Mon, 26 Nov 2018 12:33:09 +0100 Subject: [socket-nif] Increased the default read buffer size The default read buffer size was 2K and has now been increased to 8K. OTP-14831 --- erts/emulator/nifs/common/socket_nif.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erts/emulator/nifs/common/socket_nif.c b/erts/emulator/nifs/common/socket_nif.c index f657da3ace..cc11bb159f 100644 --- a/erts/emulator/nifs/common/socket_nif.c +++ b/erts/emulator/nifs/common/socket_nif.c @@ -436,7 +436,7 @@ static void (*esock_sctp_freepaddrs)(struct sockaddr *addrs) = NULL; #define SOCKET_RECV_FLAG_LOW SOCKET_RECV_FLAG_CMSG_CLOEXEC #define SOCKET_RECV_FLAG_HIGH SOCKET_RECV_FLAG_TRUNC -#define SOCKET_RECV_BUFFER_SIZE_DEFAULT 2048 +#define SOCKET_RECV_BUFFER_SIZE_DEFAULT 8192 #define SOCKET_RECV_CTRL_BUFFER_SIZE_DEFAULT 1024 #define SOCKET_SEND_CTRL_BUFFER_SIZE_DEFAULT 1024 -- cgit v1.2.3 From 151bb05cd61987723f8de9f0c7ac71b4b5430307 Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Thu, 22 Nov 2018 18:00:26 +0100 Subject: [socket-nif] Fixed two minor problems with socket close and down * Socket close callback Only demonitor the closer if its local (close) and its not a direct call. There was a spurious warning message. * Down callback Only process this event if the socket is not closed or closing. OTP-14831 --- erts/emulator/nifs/common/socket_nif.c | 279 +++++++++++++++++++-------------- 1 file changed, 157 insertions(+), 122 deletions(-) diff --git a/erts/emulator/nifs/common/socket_nif.c b/erts/emulator/nifs/common/socket_nif.c index cc11bb159f..ee1aa61c35 100644 --- a/erts/emulator/nifs/common/socket_nif.c +++ b/erts/emulator/nifs/common/socket_nif.c @@ -775,7 +775,7 @@ typedef struct { ErlNifEnv* env; /* +++ Controller (owner) process +++ */ - ErlNifPid ctrlPid; + ErlNifPid ctrlPid; // ErlNifMonitor ctrlMon; ESockMonitor ctrlMon; @@ -5232,7 +5232,6 @@ ERL_NIF_TERM nsend(ErlNifEnv* env, else save_errno = -1; // The value does not actually matter in this case - return send_check_result(env, descP, written, sndDataP->size, save_errno, sendRef); @@ -6185,8 +6184,10 @@ ERL_NIF_TERM nclose(ErlNifEnv* env, int type = descP->type; int protocol = descP->protocol; - SSDBG( descP, ("SOCKET", "nclose -> [%d] entry (0x%lX, 0x%lX, 0x%lX)\r\n", + SSDBG( descP, ("SOCKET", + "nclose -> [%d] entry (0x%lX, 0x%lX, 0x%lX, 0x%lX)\r\n", descP->sock, + descP->state, descP->currentWriterP, descP->currentReaderP, descP->currentAcceptorP) ); @@ -6246,7 +6247,6 @@ ERL_NIF_TERM nclose(ErlNifEnv* env, SSDBG( descP, ("SOCKET", "nclose -> [%d] stop was called\r\n", descP->sock) ); dec_socket(domain, type, protocol); - DEMONP("nclose -> closer", env, descP, &descP->closerMon); reply = esock_atom_ok; } else if (selectRes & ERL_NIF_SELECT_STOP_SCHEDULED) { /* The stop callback function has been *scheduled* which means that we @@ -6284,8 +6284,9 @@ ERL_NIF_TERM nclose(ErlNifEnv* env, SSDBG( descP, ("SOCKET", "nclose -> [%d] done when: " + "\r\n state: 0x%lX" "\r\n reply: %T" - "\r\n", descP->sock, reply) ); + "\r\n", descP->sock, descP->state, reply) ); return reply; } @@ -16901,8 +16902,29 @@ void socket_stop(ErlNifEnv* env, void* obj, int fd, int is_direct_call) MLOCK(descP->accMtx); MLOCK(descP->closeMtx); - SSDBG( descP, ("SOCKET", "socket_stop -> all mutex(s) locked\r\n") ); - + SSDBG( descP, ("SOCKET", "socket_stop -> " + "[%d, %T] all mutex(s) locked when counters:" + "\r\n writePkgCnt: %u" + "\r\n writeByteCnt: %u" + "\r\n writeTries: %u" + "\r\n writeWaits: %u" + "\r\n writeFails: %u" + "\r\n readPkgCnt: %u" + "\r\n readByteCnt: %u" + "\r\n readTries: %u" + "\r\n readWaits: %u" + "\r\n", + descP->sock, descP->ctrlPid, + descP->writePkgCnt, + descP->writeByteCnt, + descP->writeTries, + descP->writeWaits, + descP->writeFails, + descP->readPkgCnt, + descP->readByteCnt, + descP->readTries, + descP->readWaits) ); + descP->state = SOCKET_STATE_CLOSING; // Just in case...??? descP->isReadable = FALSE; descP->isWritable = FALSE; @@ -17044,7 +17066,8 @@ void socket_stop(ErlNifEnv* env, void* obj, int fd, int is_direct_call) * */ - if (descP->closeLocal) { + if (descP->closeLocal && + !is_direct_call) { /* +++ send close message to the waiting process +++ * @@ -17054,13 +17077,18 @@ void socket_stop(ErlNifEnv* env, void* obj, int fd, int is_direct_call) * * WHAT HAPPENS IF THE RECEIVER HAS DIED IN THE MEANTIME???? * + * Also, we should really *always* use a tag unique to this + * (nif-) module. Some like (in this case): + * + * {'$socket', close, CloseRef} + * * */ - send_msg(env, MKT2(env, atom_close, descP->closeRef), &descP->closerPid); + send_msg(env, + MKT2(env, atom_close, descP->closeRef), &descP->closerPid); - DEMONP("socket_stop -> closer", - env, descP, &descP->closerMon); + DEMONP("socket_stop -> closer", env, descP, &descP->closerMon); } else { @@ -17199,137 +17227,144 @@ void socket_down(ErlNifEnv* env, SSDBG( descP, ("SOCKET", "socket_down -> OTHER mon\r\n") ); } */ - - if (compare_pids(env, &descP->ctrlPid, pid)) { - int selectRes; - - /* We don't bother with the queue cleanup here - - * we leave it to the stop callback function. - */ - - SSDBG( descP, - ("SOCKET", "socket_down -> controlling process exit\r\n") ); - descP->state = SOCKET_STATE_CLOSING; - descP->closeLocal = TRUE; - descP->closerPid = *pid; - descP->closerMon = (ESockMonitor) *mon; - descP->closeRef = MKREF(env); // Do we really need this in this case? - - /* - esock_dbg_printf("DOWN", - "[%d] select stop %u,%u,%u,%d\r\n", - descP->sock, - monP->raw[0], monP->raw[1], - monP->raw[2], monP->raw[3]); - */ + if (!IS_CLOSED(descP)) { + if (compare_pids(env, &descP->ctrlPid, pid)) { + int selectRes; - selectRes = enif_select(env, descP->sock, (ERL_NIF_SELECT_STOP), - descP, NULL, descP->closeRef); + /* We don't bother with the queue cleanup here - + * we leave it to the stop callback function. + */ - if (selectRes & ERL_NIF_SELECT_STOP_CALLED) { - /* We are done - wwe can finalize (socket close) directly */ SSDBG( descP, - ("SOCKET", "socket_down -> [%d] stop called\r\n", descP->sock) ); - dec_socket(descP->domain, descP->type, descP->protocol); - descP->state = SOCKET_STATE_CLOSED; - - /* And finally close the socket. - * Since we close the socket because of an exiting owner, - * we do not need to wait for buffers to sync (linger). - * If the owner wish to ensure the buffer are written, - * it should have closed teh socket explicitly... - */ - if (sock_close(descP->sock) != 0) { - int save_errno = sock_errno(); - - esock_warning_msg("Failed closing socket for terminating " - "controlling process: " - "\r\n Controlling Process: %T" - "\r\n Descriptor: %d" - "\r\n Errno: %d" - "\r\n", pid, descP->sock, save_errno); - } - sock_close_event(descP->event); + ("SOCKET", "socket_down -> controlling process exit\r\n") ); + + descP->state = SOCKET_STATE_CLOSING; + descP->closeLocal = TRUE; + descP->closerPid = *pid; + descP->closerMon = (ESockMonitor) *mon; + descP->closeRef = MKREF(env); // Do we really need this in this case? - descP->sock = INVALID_SOCKET; - descP->event = INVALID_EVENT; + /* + esock_dbg_printf("DOWN", + "[%d] select stop %u,%u,%u,%d\r\n", + descP->sock, + monP->raw[0], monP->raw[1], + monP->raw[2], monP->raw[3]); + */ - descP->state = SOCKET_STATE_CLOSED; + selectRes = enif_select(env, descP->sock, (ERL_NIF_SELECT_STOP), + descP, NULL, descP->closeRef); + + if (selectRes & ERL_NIF_SELECT_STOP_CALLED) { + /* We are done - we can finalize (socket close) directly */ + SSDBG( descP, + ("SOCKET", + "socket_down -> [%d] stop called\r\n", descP->sock) ); + dec_socket(descP->domain, descP->type, descP->protocol); + descP->state = SOCKET_STATE_CLOSED; + + /* And finally close the socket. + * Since we close the socket because of an exiting owner, + * we do not need to wait for buffers to sync (linger). + * If the owner wish to ensure the buffer are written, + * it should have closed the socket explicitly... + */ + if (sock_close(descP->sock) != 0) { + int save_errno = sock_errno(); + + esock_warning_msg("Failed closing socket for terminating " + "controlling process: " + "\r\n Controlling Process: %T" + "\r\n Descriptor: %d" + "\r\n Errno: %d" + "\r\n", pid, descP->sock, save_errno); + } + sock_close_event(descP->event); - } else if (selectRes & ERL_NIF_SELECT_STOP_SCHEDULED) { - /* The stop callback function has been *scheduled* which means that - * "should" wait for it to complete. But since we are in a callback - * (down) function, we cannot... - * So, we must close the socket - */ - SSDBG( descP, - ("SOCKET", - "socket_down -> [%d] stop scheduled\r\n", descP->sock) ); - dec_socket(descP->domain, descP->type, descP->protocol); + descP->sock = INVALID_SOCKET; + descP->event = INVALID_EVENT; - /* And now what? We can't wait for the stop function here... - * So, we simply close it here and leave the rest of the "close" - * for later (when the stop function actually gets called... - */ + descP->state = SOCKET_STATE_CLOSED; - if (sock_close(descP->sock) != 0) { - int save_errno = sock_errno(); - - esock_warning_msg("Failed closing socket for terminating " - "controlling process: " + } else if (selectRes & ERL_NIF_SELECT_STOP_SCHEDULED) { + /* The stop callback function has been *scheduled* which means + * that "should" wait for it to complete. But since we are in + * a callback (down) function, we cannot... + * So, we must close the socket + */ + SSDBG( descP, + ("SOCKET", + "socket_down -> [%d] stop scheduled\r\n", + descP->sock) ); + dec_socket(descP->domain, descP->type, descP->protocol); + + /* And now what? We can't wait for the stop function here... + * So, we simply close it here and leave the rest of the "close" + * for later (when the stop function actually gets called... + */ + + if (sock_close(descP->sock) != 0) { + int save_errno = sock_errno(); + + esock_warning_msg("Failed closing socket for terminating " + "controlling process: " + "\r\n Controlling Process: %T" + "\r\n Descriptor: %d" + "\r\n Errno: %d" + "\r\n", pid, descP->sock, save_errno); + } + sock_close_event(descP->event); + + } else { + + /* + * + * + * WE SHOULD REALLY HAVE A WAY TO CLOBBER THE SOCKET, + * SO WE DON'T LET STUFF LEAK. + * NOW, BECAUSE WE FAILED TO SELECT, WE CANNOT FINISH + * THE CLOSE, WHAT TO DO? ABORT? + * + * + */ + esock_warning_msg("Failed selecting stop when handling down " + "of controlling process: " + "\r\n Select Res: %d" "\r\n Controlling Process: %T" "\r\n Descriptor: %d" - "\r\n Errno: %d" - "\r\n", pid, descP->sock, save_errno); + "\r\n Monitor: %u.%u.%u.%u" + "\r\n", selectRes, pid, descP->sock, + monP->raw[0], monP->raw[1], + monP->raw[2], monP->raw[3]); } - sock_close_event(descP->event); - + } else { - /* - * + /* check all operation queue(s): acceptor, writer and reader. * - * WE SHOULD REALLY HAVE A WAY TO CLOBBER THE SOCKET, - * SO WE DON'T LET STUFF LEAK. - * NOW, BECAUSE WE FAILED TO SELECT, WE CANNOT FINISH - * THE CLOSE, WHAT TO DO? ABORT? + * Is it really any point in doing this if the socket is closed? * - * */ - esock_warning_msg("Failed selecting stop when handling down " - "of controlling process: " - "\r\n Select Res: %d" - "\r\n Controlling Process: %T" - "\r\n Descriptor: %d" - "\r\n Monitor: %u.%u.%u.%u" - "\r\n", selectRes, pid, descP->sock, - monP->raw[0], monP->raw[1], - monP->raw[2], monP->raw[3]); - } - - } else { - - /* check all operation queue(s): acceptor, writer and reader. */ - - SSDBG( descP, ("SOCKET", "socket_down -> other process term\r\n") ); + SSDBG( descP, ("SOCKET", "socket_down -> other process term\r\n") ); - MLOCK(descP->accMtx); - if (descP->currentAcceptorP != NULL) - socket_down_acceptor(env, descP, pid); - MUNLOCK(descP->accMtx); - - MLOCK(descP->writeMtx); - if (descP->currentWriterP != NULL) - socket_down_writer(env, descP, pid); - MUNLOCK(descP->writeMtx); - - MLOCK(descP->readMtx); - if (descP->currentReaderP != NULL) - socket_down_reader(env, descP, pid); - MUNLOCK(descP->readMtx); + MLOCK(descP->accMtx); + if (descP->currentAcceptorP != NULL) + socket_down_acceptor(env, descP, pid); + MUNLOCK(descP->accMtx); + + MLOCK(descP->writeMtx); + if (descP->currentWriterP != NULL) + socket_down_writer(env, descP, pid); + MUNLOCK(descP->writeMtx); + MLOCK(descP->readMtx); + if (descP->currentReaderP != NULL) + socket_down_reader(env, descP, pid); + MUNLOCK(descP->readMtx); + + } } /* -- cgit v1.2.3 From 94d8e2f1bf9508656f5b9b2c2c644128a9bdfb57 Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Wed, 5 Dec 2018 10:51:19 +0100 Subject: [socket-nif|test] Added the proper time-test code --- erts/emulator/test/Makefile | 13 +- erts/emulator/test/socket_test_ttest.hrl | 32 + erts/emulator/test/socket_test_ttest_client.hrl | 141 +++++ .../emulator/test/socket_test_ttest_tcp_client.erl | 617 +++++++++++++++++++ .../test/socket_test_ttest_tcp_client_gen.erl | 45 ++ .../test/socket_test_ttest_tcp_client_socket.erl | 45 ++ erts/emulator/test/socket_test_ttest_tcp_gen.erl | 123 ++++ .../emulator/test/socket_test_ttest_tcp_server.erl | 678 +++++++++++++++++++++ .../test/socket_test_ttest_tcp_server_gen.erl | 34 ++ .../test/socket_test_ttest_tcp_server_socket.erl | 34 ++ .../emulator/test/socket_test_ttest_tcp_socket.erl | 345 +++++++++++ 11 files changed, 2106 insertions(+), 1 deletion(-) create mode 100644 erts/emulator/test/socket_test_ttest.hrl create mode 100644 erts/emulator/test/socket_test_ttest_client.hrl create mode 100644 erts/emulator/test/socket_test_ttest_tcp_client.erl create mode 100644 erts/emulator/test/socket_test_ttest_tcp_client_gen.erl create mode 100644 erts/emulator/test/socket_test_ttest_tcp_client_socket.erl create mode 100644 erts/emulator/test/socket_test_ttest_tcp_gen.erl create mode 100644 erts/emulator/test/socket_test_ttest_tcp_server.erl create mode 100644 erts/emulator/test/socket_test_ttest_tcp_server_gen.erl create mode 100644 erts/emulator/test/socket_test_ttest_tcp_server_socket.erl create mode 100644 erts/emulator/test/socket_test_ttest_tcp_socket.erl diff --git a/erts/emulator/test/Makefile b/erts/emulator/test/Makefile index 22c2f24a80..a910588381 100644 --- a/erts/emulator/test/Makefile +++ b/erts/emulator/test/Makefile @@ -34,6 +34,14 @@ SOCKET_MODULES = \ socket_client \ socket_test_lib \ socket_test_evaluator \ + socket_test_ttest_tcp_gen \ + socket_test_ttest_tcp_socket \ + socket_test_ttest_tcp_client \ + socket_test_ttest_tcp_client_gen \ + socket_test_ttest_tcp_client_socket \ + socket_test_ttest_tcp_server \ + socket_test_ttest_tcp_server_gen \ + socket_test_ttest_tcp_server_socket \ socket_SUITE MODULES= \ @@ -158,7 +166,10 @@ NATIVE_MODULES= $(NATIVE:%=%_native_SUITE) NATIVE_ERL_FILES= $(NATIVE_MODULES:%=%.erl) ERL_FILES= $(MODULES:%=%.erl) -HRL_FILES= socket_test_evaluator.hrl +HRL_FILES= \ + socket_test_evaluator.hrl \ + socket_test_ttest.hrl \ + socket_test_ttest_client.hrl TARGET_FILES = $(MODULES:%=$(EBIN)/%.$(EMULATOR)) SOCKET_TARGETS = $(SOCKET_MODULES:%=$(EBIN)/%.$(EMULATOR)) diff --git a/erts/emulator/test/socket_test_ttest.hrl b/erts/emulator/test/socket_test_ttest.hrl new file mode 100644 index 0000000000..1a004a9a7a --- /dev/null +++ b/erts/emulator/test/socket_test_ttest.hrl @@ -0,0 +1,32 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2018-2018. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% + +-ifndef(socket_test_ttest). +-define(socket_test_ttest, true). + +-define(TTEST_TAG, 42). +-define(TTEST_TYPE_REQUEST, 101). +-define(TTEST_TYPE_REPLY, 102). + +-define(SECS(I), timer:seconds(I)). + +-define(SLEEP(T), receive after T -> ok end). + +-endif. % -ifdef(socket_test_ttest). diff --git a/erts/emulator/test/socket_test_ttest_client.hrl b/erts/emulator/test/socket_test_ttest_client.hrl new file mode 100644 index 0000000000..84e736cc34 --- /dev/null +++ b/erts/emulator/test/socket_test_ttest_client.hrl @@ -0,0 +1,141 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2018-2018. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% + +-ifndef(socket_test_ttest_client). +-define(socket_test_ttest_client, true). + +-define(MSG_ID_DEFAULT, 2). +-define(RUNTIME_DEFAULT, ?SECS(10)). +-define(MAX_ID, 16#FFFFFFFF). + +-define(MSG_DATA1, <<"This is test data 0123456789 0123456789 0123456789">>). +-define(MSG_DATA2, <<"This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789">>). +-define(MSG_DATA3, <<"This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789">>). + + +-endif. % -ifdef(socket_test_ttest_client). diff --git a/erts/emulator/test/socket_test_ttest_tcp_client.erl b/erts/emulator/test/socket_test_ttest_tcp_client.erl new file mode 100644 index 0000000000..1bd1bc54e9 --- /dev/null +++ b/erts/emulator/test/socket_test_ttest_tcp_client.erl @@ -0,0 +1,617 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2018-2018. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% + +%% ========================================================================== +%% +%% This is the "simple" client using gen_tcp. The client is supposed to be +%% as simple as possible in order to incur as little overhead as possible. +%% +%% There are three ways to run the client: active, passive or active-once. +%% +%% The client is the entity that controls the test, timing and counting. +%% +%% ========================================================================== +%% +%% Before the actual test starts, the client performs a "warmup". +%% The warmup has two functions. First, to ensure that everything is "loaded" +%% and, second, to calculate an approximate roundtrip time, in order to +%% "know" how many iterations we should make (to run for the expected time). +%% This is not intended to be exact, but just to ensure that all tests take +%% approx the same time to run. +%% +%% ========================================================================== + +-module(socket_test_ttest_tcp_client). + +-export([ + start_monitor/4, start_monitor/5, start_monitor/7, + stop/1 + ]). + +-include_lib("kernel/include/inet.hrl"). +-include("socket_test_ttest.hrl"). +-include("socket_test_ttest_client.hrl"). + +-define(RECV_TIMEOUT, 10000). +-define(MAX_OUTSTANDING_DEFAULT_1, 100). +-define(MAX_OUTSTANDING_DEFAULT_2, 10). +-define(MAX_OUTSTANDING_DEFAULT_3, 3). + + +-type active() :: once | boolean(). +-type msg_id() :: 1..3. +-type max_outstanding() :: pos_integer(). +-type runtime() :: pos_integer(). + + +%% ========================================================================== + +-spec start_monitor(Mod, Active, Addr, Port) -> term() when + Mod :: atom(), + Active :: active(), + Addr :: inet:ip_address(), + Port :: inet:port_number(). + +%% RunTime is in number of ms. +start_monitor(Mod, Active, Addr, Port) -> + start_monitor(Mod, Active, Addr, Port, ?MSG_ID_DEFAULT). + +-spec start_monitor(Mod, Active, Addr, Port, MsgID) -> term() when + Mod :: atom(), + Active :: active(), + Addr :: inet:ip_address(), + Port :: inet:port_number(), + MsgID :: msg_id(). + +%% RunTime is in number of ms. +start_monitor(Mod, Active, Addr, Port, 1 = MsgID) -> + start_monitor(Mod, Active, Addr, Port, MsgID, + ?MAX_OUTSTANDING_DEFAULT_1, ?RUNTIME_DEFAULT); +start_monitor(Mod, Active, Addr, Port, 2 = MsgID) -> + start_monitor(Mod, Active, Addr, Port, MsgID, + ?MAX_OUTSTANDING_DEFAULT_2, ?RUNTIME_DEFAULT); +start_monitor(Mod, Active, Addr, Port, 3 = MsgID) -> + start_monitor(Mod, Active, Addr, Port, MsgID, + ?MAX_OUTSTANDING_DEFAULT_3, ?RUNTIME_DEFAULT). + +-spec start_monitor(Mod, + Active, + Addr, + Port, + MsgID, + MaxOutstanding, + RunTime) -> term() when + Mod :: atom(), + Active :: active(), + Addr :: inet:ip_address(), + Port :: inet:port_number(), + MsgID :: msg_id(), + MaxOutstanding :: max_outstanding(), + RunTime :: runtime(). + +%% RunTime is in number of ms. +start_monitor(Mod, Active, Addr, Port, + MsgID, MaxOutstanding, RunTime) + when is_atom(Mod) andalso + (is_boolean(Active) orelse (Active =:= once)) andalso + is_tuple(Addr) andalso + (is_integer(Port) andalso (Port > 0)) andalso + (is_integer(MsgID) andalso (MsgID >= 1) andalso (MsgID =< 3)) andalso + (is_integer(MaxOutstanding) andalso (MaxOutstanding > 0)) andalso + (is_integer(RunTime) andalso (RunTime > 0)) -> + Self = self(), + ClientInit = fun() -> put(sname, "client"), + init(Self, + Mod, Active, Addr, Port, + MsgID, MaxOutstanding, RunTime) + end, + {Pid, MRef} = spawn_monitor(ClientInit), + receive + {?MODULE, Pid, ok} -> + erlang:demonitor(MRef, [flush]), + {ok, {Pid, MRef}}; + + {?MODULE, Pid, {error, _} = ERROR} -> + erlang:demonitor(MRef, [flush]), + ERROR; + + {'DOWN', MRef, process, Pid, normal} -> + ok; + {'DOWN', MRef, process, Pid, Reason} -> + {error, {exit, Reason}} + + end. + + +%% We should not normally stop this (it terminates when its done). +stop(Pid) when is_pid(Pid) -> + req(Pid, stop). + + +%% ========================================================================== + +init(Parent, Mod, Active, Addr, Port, + MsgID, MaxOutstanding, RunTime) -> + i("init -> entry with" + "~n Parent: ~p" + "~n Mod: ~p" + "~n Active: ~p" + "~n Addr: ~s" + "~n Port: ~p" + "~n Msg ID: ~p (=> 16 + ~w bytes)" + "~n Max Outstanding: ~p" + "~n (Suggested) Run Time: ~p ms", + [Parent, + Mod, Active, inet:ntoa(Addr), Port, + MsgID, size(which_msg_data(MsgID)), MaxOutstanding, RunTime]), + case Mod:connect(Addr, Port) of + {ok, Sock} -> + i("init -> connected"), + Parent ! {?MODULE, self(), ok}, + initial_activation(Mod, Sock, Active), + Results = loop(#{slogan => run, + runtime => RunTime, + start => t(), + parent => Parent, + mod => Mod, + sock => Sock, + active => Active, + msg_data => which_msg_data(MsgID), + outstanding => 0, + max_outstanding => MaxOutstanding, + sid => 1, + rid => 1, + scnt => 0, + rcnt => 0, + bcnt => 0, + num => undefined, + acc => <<>>}), + present_results(Results), + (catch Mod:close(Sock)), + exit(normal); + {error, Reason} -> + i("init -> connect failed: ~p", [Reason]), + exit({connect, Reason}) + end. + +which_msg_data(1) -> ?MSG_DATA1; +which_msg_data(2) -> ?MSG_DATA2; +which_msg_data(3) -> ?MSG_DATA3. + + +present_results(#{status := ok, + runtime := RunTime, + bcnt := ByteCnt, + cnt := NumIterations}) -> + i("Results: " + "~n Run Time: ~s" + "~n ByteCnt: ~s" + "~n NumIterations: ~s", + [format_time(RunTime), + if ((ByteCnt =:= 0) orelse (RunTime =:= 0)) -> + f("~w, ~w", [ByteCnt, RunTime]); + true -> + f("~p => ~p byte / ms", [ByteCnt, ByteCnt div RunTime]) + end, + if (RunTime =:= 0) -> + "-"; + true -> + f("~p => ~p iterations / ms", + [NumIterations, NumIterations div RunTime]) + end]), + ok; +present_results(#{status := Failure, + runtime := RunTime, + sid := SID, + rid := RID, + scnt := SCnt, + rcnt := RCnt, + bcnt := BCnt, + num := Num}) -> + i("Time Test failed: " + "~n ~p" + "~n" + "~nwhen" + "~n" + "~n Run Time: ~s" + "~n Send ID: ~p" + "~n Recv ID: ~p" + "~n Send Count: ~p" + "~n Recv Count: ~p" + "~n Byte Count: ~p" + "~n Num Iterations: ~p", + [Failure, + format_time(RunTime), + SID, RID, SCnt, RCnt, BCnt, Num]). + + + +loop(#{runtime := RunTime} = State) -> + erlang:start_timer(RunTime, self(), stop), + try do_loop(State) + catch + throw:Results -> + Results + end. + +do_loop(State) -> + do_loop( handle_message( msg_exchange(State) ) ). + +msg_exchange(#{rcnt := Num, num := Num} = State) -> + %% i("we are done"), + finish(ok, State); +msg_exchange(#{scnt := Num, num := Num} = State) -> + %% We are done sending more requests - now we will just await + %% the replies for the (still) outstanding replies. + %% i("we have sent all requests - (only) wait for replies"), + msg_exchange( recv_reply(State) ); +msg_exchange(#{outstanding := Outstanding, + max_outstanding := MaxOutstanding} = State) + when (Outstanding < MaxOutstanding) -> + %% i("send the (initial) requests (~w, ~w)", [Outstanding, MaxOutstanding]), + msg_exchange( send_request(State) ); +msg_exchange(State) -> + send_request( recv_reply(State) ). + + +finish(ok, + #{start := Start, bcnt := BCnt, num := Num}) -> + Stop = t(), + throw(#{status => ok, + runtime => tdiff(Start, Stop), + bcnt => BCnt, + cnt => Num}); +finish(Reason, + #{start := Start, + sid := SID, rid := RID, + scnt := SCnt, rcnt := RCnt, bcnt := BCnt, + num := Num}) -> + Stop = t(), + throw(#{status => Reason, + runtime => tdiff(Start, Stop), + sid => SID, + rid => RID, + scnt => SCnt, + rcnt => RCnt, + bcnt => BCnt, + num => Num}). + +send_request(#{mod := Mod, + sock := Sock, + sid := ID, + scnt := Cnt, + outstanding := Outstanding, + max_outstanding := MaxOutstanding, + msg_data := Data} = State) + when (MaxOutstanding > Outstanding) -> + %% i("send request -> entry when" + %% "~n ID: ~p" + %% "~n Cnt: ~p" + %% "~n Outstanding: ~p" + %% "~n MaxOutstanding: ~p", [ID, Cnt, Outstanding, MaxOutstanding]), + SZ = size(Data), + Req = <>, + case Mod:send(Sock, Req) of + ok -> + %% i("~w bytes sent", [size(Req)]), + State#{sid => next_id(ID), + scnt => Cnt + 1, + outstanding => Outstanding + 1}; + {error, Reason} -> + e("Failed sending request: ~p", [Reason]), + exit({send, Reason}) + end; +send_request(State) -> + State. + + + +recv_reply(#{mod := Mod, + sock := Sock, + rid := ID, + active := false, + bcnt := BCnt, + rcnt := Cnt, + outstanding := Outstanding} = State) -> + %% i("recv-reply(false) -> entry with" + %% "~n (R)ID: ~p" + %% "~n (R)Cnt: ~p" + %% "~n BCnt: ~p" + %% "~n Outstanding: ~p", [ID, Cnt, BCnt, Outstanding]), + case recv_reply_message1(Mod, Sock, ID) of + {ok, MsgSz} -> + State#{rid => next_id(ID), + bcnt => BCnt + MsgSz, + rcnt => Cnt + 1, + outstanding => Outstanding - 1}; + + {error, timeout} -> + i("recv_reply(false) -> error: timeout"), + State; + + {error, Reason} -> + finish(Reason, State) + end; +recv_reply(#{mod := Mod, + sock := Sock, + rid := ID, + active := Active, + bcnt := BCnt, + scnt := SCnt, + rcnt := RCnt, + outstanding := Outstanding, + acc := Acc} = State) -> + %% i("recv-reply(~w) -> entry with" + %% "~n (R)ID: ~p" + %% "~n RCnt: ~p" + %% "~n BCnt: ~p" + %% "~n Outstanding: ~p", [Active, ID, RCnt, BCnt, Outstanding]), + case recv_reply_message2(Mod, Sock, ID, Acc) of + {ok, {MsgSz, NewAcc}} when is_integer(MsgSz) andalso is_binary(NewAcc) -> + maybe_activate(Mod, Sock, Active), + State#{rid => next_id(ID), + bcnt => BCnt + MsgSz, + rcnt => RCnt + 1, + outstanding => Outstanding - 1, + acc => NewAcc}; + + ok -> + State; + + {error, stop} -> + i("recv_reply(~w) -> stop", [Active]), + %% This will have the effect that no more requests are sent... + State#{num => SCnt, stop_started => t()}; + + {error, timeout} -> + i("recv_reply(~w) -> error: timeout", [Active]), + State; + + {error, Reason} -> + finish(Reason, State) + end. + + +%% This function reads exactly one (reply) message. No more no less. +recv_reply_message1(Mod, Sock, ID) -> + %% i("recv_reply_message1 -> entry with" + %% "~n ID: ~w", [ID]), + case Mod:recv(Sock, 4*4, ?RECV_TIMEOUT) of + {ok, <> = Hdr} -> + %% Receive the ping-pong reply boby + %% i("recv_reply_message1 -> try read body" + %% "~n ID: ~w", [ID]), + case Mod:recv(Sock, SZ, ?RECV_TIMEOUT) of + {ok, Data} when (size(Data) =:= SZ) -> + {ok, size(Hdr) + size(Data)}; + {error, Reason2} -> + i("recv_reply_message1 -> body error: " + "~n ~p: ~p", [Reason2]), + {error, {recv_hdr, Reason2}} + end; + + {ok, <>} -> + {error, {invalid_hdr, + {?TTEST_TAG, BadTag}, + {?TTEST_TYPE_REPLY, BadType}, + {ID, BadID}, + BadSZ}}; + {ok, _InvHdr} -> + {error, invalid_hdr}; + + {error, Reason1} -> + i("recv_reply_message1 -> hdr error: " + "~n ~p", [Reason1]), + {error, {recv_hdr, Reason1}} + end. + + +%% This function first attempts to process the data we have already +%% accumulated. If that is not enough for a (complete) reply, it +%% will attempt to receive more. +recv_reply_message2(Mod, Sock, ID, Acc) -> + %% i("recv_reply_message2 -> entry with" + %% "~n ID: ~w", [ID]), + case process_acc_data(ID, Acc) of + ok -> + %% No or insufficient data, so get more + recv_reply_message3(Mod, Sock, ID, Acc); + + {ok, _} = OK -> % We already had a reply accumulated - no need to read more + OK; + + {error, _} = ERROR -> + ERROR + end. + +%% This function receives a "chunk" of data, then it tries to extract +%% one (reply) message from the accumulated and new data (combined). +recv_reply_message3(_Mod, Sock, ID, Acc) -> + receive + {timeout, _TRef, stop} -> + %% i("stop - when messages: ~p", [process_info(self(), messages)]), + {error, stop}; + + {TagClosed, Sock} when (TagClosed =:= tcp_closed) orelse + (TagClosed =:= socket_closed) -> + {error, closed}; + + {TagErr, Sock, Reason} when (TagErr =:= tcp_error) orelse + (TagErr =:= socket_error) -> + {error, Reason}; + + {Tag, Sock, Msg} when (Tag =:= tcp) orelse + (Tag =:= socket) -> + %% i("recv_reply_message3 -> got ~w byte message", [size(Msg)]), + process_acc_data(ID, <>) + + %% after ?RECV_TIMEOUT -> + %% {error, timeout} + end. + + +process_acc_data(ID, <>) when (SZ =< size(Data)) -> + %% i("process_acc_data -> entry with" + %% "~n ID: ~w" + %% "~n SZ: ~w", [ID, SZ]), + <<_Body:SZ/binary, Rest/binary>> = Data, + {ok, {4*4+SZ, Rest}}; +process_acc_data(ID, <>) + when ((BadTag =/= ?TTEST_TAG) orelse + (BadType =/= ?TTEST_TYPE_REPLY) orelse + (BadID =/= ID)) -> + {error, {invalid_hdr, + {?TTEST_TAG, BadTag}, + {?TTEST_TYPE_REPLY, BadType}, + {ID, BadID}, + BadSZ}}; +%% Not enough for an entire (reply) message +process_acc_data(_ID, _Data) -> + ok. + + +handle_message(#{parent := Parent, sock := Sock, scnt := SCnt} = State) -> + receive + {timeout, _TRef, stop} -> + i("stop"), + %% This will have the effect that no more requests are sent... + State#{num => SCnt, stop_started => t()}; + + {?MODULE, Ref, Parent, stop} -> + %% This *aborts* the test + reply(Parent, Ref, ok), + exit(normal); + + %% Only when active + {TagClosed, Sock, Reason} when (TagClosed =:= tcp_closed) orelse + (TagClosed =:= socket_closed) -> + %% We should never get this (unless the server crashed) + exit({closed, Reason}); + + %% Only when active + {TagErr, Sock, Reason} when (TagErr =:= tcp_error) orelse + (TagErr =:= socket_error) -> + exit({error, Reason}) + + after 0 -> + State + end. + + +initial_activation(_Mod, _Sock, false = _Active) -> + ok; +initial_activation(Mod, Sock, Active) -> + Mod:active(Sock, Active). + + +maybe_activate(Mod, Sock, once = Active) -> + Mod:active(Sock, Active); +maybe_activate(_, _, _) -> + ok. + + +%% ========================================================================== + +req(Pid, Req) -> + Ref = make_ref(), + Pid ! {?MODULE, Ref, Pid, Req}, + receive + {'EXIT', Pid, Reason} -> + {error, {exit, Reason}}; + {?MODULE, Ref, Reply} -> + Reply + end. + +reply(Pid, Ref, Reply) -> + Pid ! {?MODULE, Ref, Reply}. + + +%% ========================================================================== + +next_id(ID) when (ID < ?MAX_ID) -> + ID + 1; +next_id(_) -> + 1. + + +%% ========================================================================== + +t() -> + os:timestamp(). + +tdiff({A1, B1, C1} = _T1x, {A2, B2, C2} = _T2x) -> + T1 = A1*1000000000+B1*1000+(C1 div 1000), + T2 = A2*1000000000+B2*1000+(C2 div 1000), + T2 - T1. + +formated_timestamp() -> + format_timestamp(os:timestamp()). + +format_timestamp({_N1, _N2, N3} = TS) -> + {_Date, Time} = calendar:now_to_local_time(TS), + {Hour,Min,Sec} = Time, + FormatTS = io_lib:format("~.2.0w:~.2.0w:~.2.0w.4~w", + [Hour, Min, Sec, round(N3/1000)]), + lists:flatten(FormatTS). + +%% Time is always in number os ms (milli seconds) +format_time(T) -> + f("~p", [T]). + + +%% ========================================================================== + +f(F, A) -> + lists:flatten(io_lib:format(F, A)). + +%% e(F) -> +%% i(" " ++ F). + +e(F, A) -> + p(get(sname), " " ++ F, A). + +i(F) -> + i(F, []). + +i(F, A) -> + p(get(sname), " " ++ F, A). + +p(undefined, F, A) -> + p("- ", F, A); +p(Prefix, F, A) -> + io:format("[~s, ~s] " ++ F ++ "~n", [formated_timestamp(), Prefix |A]). diff --git a/erts/emulator/test/socket_test_ttest_tcp_client_gen.erl b/erts/emulator/test/socket_test_ttest_tcp_client_gen.erl new file mode 100644 index 0000000000..4f6152a4b1 --- /dev/null +++ b/erts/emulator/test/socket_test_ttest_tcp_client_gen.erl @@ -0,0 +1,45 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2018-2018. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% + +-module(socket_test_ttest_tcp_client_gen). + +-export([ + start_monitor/3, start_monitor/4, start_monitor/6, + stop/1 + ]). + +-define(TRANSPORT_MOD, socket_test_ttest_tcp_gen). + +start_monitor(Active, Addr, Port) -> + socket_test_ttest_tcp_client:start_monitor(?TRANSPORT_MOD, + Active, Addr, Port). + +start_monitor(Active, Addr, Port, MsgID) -> + socket_test_ttest_tcp_client:start_monitor(?TRANSPORT_MOD, + Active, Addr, Port, + MsgID). + +start_monitor(Active, Addr, Port, MsgID, MaxOutstanding, RunTime) -> + socket_test_ttest_tcp_client:start_monitor(?TRANSPORT_MOD, + Active, Addr, Port, + MsgID, MaxOutstanding, RunTime). + +stop(Pid) -> + socket_test_ttest_tcp_client:stop(Pid). diff --git a/erts/emulator/test/socket_test_ttest_tcp_client_socket.erl b/erts/emulator/test/socket_test_ttest_tcp_client_socket.erl new file mode 100644 index 0000000000..0d2ec38876 --- /dev/null +++ b/erts/emulator/test/socket_test_ttest_tcp_client_socket.erl @@ -0,0 +1,45 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2018-2018. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% + +-module(socket_test_ttest_tcp_client_socket). + +-export([ + start_monitor/3, start_monitor/4, start_monitor/6, + stop/1 + ]). + +-define(TRANSPORT_MOD, socket_test_ttest_tcp_socket). + +start_monitor(Active, Addr, Port) -> + socket_test_ttest_tcp_client:start_monitor(?TRANSPORT_MOD, + Active, Addr, Port). + +start_monitor(Active, Addr, Port, MsgID) -> + socket_test_ttest_tcp_client:start_monitor(?TRANSPORT_MOD, + Active, Addr, Port, + MsgID). + +start_monitor(Active, Addr, Port, MsgID, MaxOutstanding, RunTime) -> + socket_test_ttest_tcp_client:start_monitor(?TRANSPORT_MOD, + Active, Addr, Port, + MsgID, MaxOutstanding, RunTime). + +stop(Pid) -> + socket_test_ttest_client:stop(Pid). diff --git a/erts/emulator/test/socket_test_ttest_tcp_gen.erl b/erts/emulator/test/socket_test_ttest_tcp_gen.erl new file mode 100644 index 0000000000..de8822157d --- /dev/null +++ b/erts/emulator/test/socket_test_ttest_tcp_gen.erl @@ -0,0 +1,123 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2018-2018. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% + +-module(socket_test_ttest_tcp_gen). + +-export([ + accept/1, accept/2, + active/2, + close/1, + connect/2, + controlling_process/2, + listen/0, listen/1, + peername/1, + port/1, + recv/2, recv/3, + send/2, + shutdown/2, + sockname/1 + ]). + + +%% ========================================================================== + +accept(Sock) -> + case gen_tcp:accept(Sock) of + {ok, NewSock} -> + {ok, NewSock}; + {error, _} = ERROR -> + ERROR + end. + +accept(Sock, Timeout) -> + case gen_tcp:accept(Sock, Timeout) of + {ok, NewSock} -> + {ok, NewSock}; + {error, _} = ERROR -> + ERROR + end. + + +active(Sock, NewActive) + when (is_boolean(NewActive) orelse (NewActive =:= once)) -> + inet:setopts(Sock, [{active, NewActive}]). + + +close(Sock) -> + gen_tcp:close(Sock). + + +connect(Addr, Port) -> + Opts = [binary, {packet, raw}, {active, false}], + case gen_tcp:connect(Addr, Port, Opts) of + {ok, Sock} -> + {ok, Sock}; + {error, _} = ERROR -> + ERROR + end. + +controlling_process(Sock, NewPid) -> + gen_tcp:controlling_process(Sock, NewPid). + + +%% Create a listen socket +listen() -> + listen(0). + +listen(Port) when is_integer(Port) andalso (Port >= 0) -> + Opts = [binary, {ip, {0,0,0,0}}, {packet, raw}, {active, false}], + case gen_tcp:listen(Port, Opts) of + {ok, Sock} -> + {ok, Sock}; + {error, _} = ERROR -> + ERROR + end. + + +peername(Sock) -> + inet:peername(Sock). + + +port(Sock) -> + inet:port(Sock). + + +recv(Sock, Length) -> + gen_tcp:recv(Sock, Length). +recv(Sock, Length, Timeout) -> + gen_tcp:recv(Sock, Length, Timeout). + + +send(Sock, Data) -> + gen_tcp:send(Sock, Data). + + +shutdown(Sock, How) -> + gen_tcp:shutdown(Sock, How). + + +sockname(Sock) -> + inet:sockname(Sock). + + +%% ========================================================================== + + + diff --git a/erts/emulator/test/socket_test_ttest_tcp_server.erl b/erts/emulator/test/socket_test_ttest_tcp_server.erl new file mode 100644 index 0000000000..cb503a1feb --- /dev/null +++ b/erts/emulator/test/socket_test_ttest_tcp_server.erl @@ -0,0 +1,678 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2018-2018. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% + +%% ========================================================================== +%% +%% This is the "simple" server using gen_tcp. The server is supposed to be +%% as simple as possible in order to incur as little overhead as possible. +%% +%% There are three ways to run the server: active, passive or active-once. +%% +%% The server does only two things; accept connnections and then reply +%% to requests (actually the handler(s) does that). No timing or counting. +%% That is all done by the clients. +%% +%% ========================================================================== + +-module(socket_test_ttest_tcp_server). + +-export([ + start_monitor/2, + stop/1 + ]). + +-include_lib("kernel/include/inet.hrl"). +-include("socket_test_ttest.hrl"). + +-define(ACC_TIMEOUT, 10000). +-define(RECV_TIMEOUT, 10000). + + +%% ========================================================================== + +start_monitor(Mod, Active) + when is_atom(Mod) andalso is_boolean(Active) orelse (Active =:= once) -> + Self = self(), + ServerInit = fun() -> put(sname, "server"), server_init(Self, Mod, Active) end, + {Pid, MRef} = spawn_monitor(ServerInit), + receive + {'DOWN', MRef, process, Pid, normal} -> + ok; + {'DOWN', MRef, process, Pid, Reason} -> + {error, {exit, Reason}}; + + {?MODULE, Pid, {ok, Port}} -> + erlang:demonitor(MRef, [flush]), + {ok, {Pid, MRef, Port}}; + {?MODULE, Pid, {error, _} = ERROR} -> + erlang:demonitor(MRef, [flush]), + ERROR + end. + + +stop(Pid) when is_pid(Pid) -> + req(Pid, stop). + + +%% ========================================================================== + +server_init(Parent, Mod, Active) -> + i("init -> entry with" + "~n Parent: ~p" + "~n Mod: ~p" + "~n Active: ~p", [Parent, Mod, Active]), + case Mod:listen(0) of + {ok, LSock} -> + case Mod:port(LSock) of + {ok, Port} = OK -> + Addr = which_addr(), % This is just for convenience + i("init -> listening on:" + "~n Addr: ~p (~s)" + "~n Port: ~w" + "~n", [Addr, inet:ntoa(Addr), Port]), + Parent ! {?MODULE, self(), OK}, + server_loop(#{parent => Parent, + mod => Mod, + active => Active, + lsock => LSock, + handlers => [], + %% Accumulation + runtime => 0, + mcnt => 0, + bcnt => 0, + hcnt => 0 + }); + {error, PReason} -> + (catch Mod:close(LSock)), + exit({port, PReason}) + end; + {error, LReason} -> + exit({listen, LReason}) + end. + + +server_loop(State) -> + %% i("loop -> enter"), + server_loop( server_handle_message( server_accept(State) ) ). + +server_accept(#{mod := Mod, + active := Active, + lsock := LSock, + handlers := Handlers} = State) -> + + %% i("server-accept -> entry with" + %% "~n Mod: ~p" + %% "~n Active: ~p" + %% "~n LSock: ~p", [Mod, Active, LSock]), + + case Mod:accept(LSock, ?ACC_TIMEOUT) of + {ok, Sock} -> + %% i("server-accept -> accepted: " + %% "~n Sock: ~p", [Sock]), + i("accepted connection from ~s", + [case Mod:peername(Sock) of + {ok, Peer} -> + format_peername(Peer); + {error, _} -> + "-" + end]), + {Pid, _} = handler_start(), + i("handler ~p started -> try transfer socket control", [Pid]), + case Mod:controlling_process(Sock, Pid) of + ok -> + i("server-accept: handler ~p started", [Pid]), + handler_continue(Pid, Mod, Sock, Active), + Handlers2 = [Pid | Handlers], + State#{handlers => Handlers2}; + {error, CPReason} -> + (catch Mod:close(Sock)), + (catch Mod:close(LSock)), + exit({controlling_process, CPReason}) + end; + {error, timeout} -> + State; + {error, AReason} -> + (catch Mod:close(LSock)), + exit({accept, AReason}) + end. + +format_peername({Addr, Port}) -> + case inet:gethostbyaddr(Addr) of + {ok, #hostent{h_name = N}} -> + f("~s (~s:~w)", [N, inet:ntoa(Addr), Port]); + {error, _} -> + f("~p, ~p", [Addr, Port]) + end. + +server_handle_message(#{parent := Parent, handlers := H} = State) -> + %% i("server_handle_message -> enter"), + receive + {?MODULE, Ref, Parent, stop} -> + reply(Parent, Ref, ok), + lists:foreach(fun(P) -> handler_stop(P) end, H), + exit(normal); + + {'DOWN', _MRef, process, Pid, Reason} -> + server_handle_down(Pid, Reason, State) + + after 0 -> + State + end. + + +server_handle_down(Pid, Reason, #{handlers := Handlers} = State) -> + case lists:delete(Pid, Handlers) of + Handlers -> + i("unknown process ~p died", [Pid]), + State; + Handlers2 -> + server_handle_handler_down(Pid, Reason, State#{handlers => Handlers2}) + end. + + +server_handle_handler_down(Pid, + {done, RunTime, MCnt, BCnt}, + #{runtime := AccRunTime, + mcnt := AccMCnt, + bcnt := AccBCnt, + hcnt := AccHCnt} = State) -> + AccRunTime2 = AccRunTime + RunTime, + AccMCnt2 = AccMCnt + MCnt, + AccBCnt2 = AccBCnt + BCnt, + AccHCnt2 = AccHCnt + 1, + i("handler ~p (~w) done => accumulated results: " + "~n Run Time: ~s ms" + "~n Message Count: ~s" + "~n Byte Count: ~s", + [Pid, AccHCnt2, + format_time(AccRunTime2), + if (AccRunTime2 > 0) -> + f("~w => ~w (~w) msgs / ms", + [AccMCnt2, + AccMCnt2 div AccRunTime2, + (AccMCnt2 div AccHCnt2) div AccRunTime2]); + true -> + f("~w", [AccMCnt2]) + end, + if (AccRunTime2 > 0) -> + f("~w => ~w (~w) bytes / ms", + [AccBCnt2, + AccBCnt2 div AccRunTime2, + (AccBCnt2 div AccHCnt2) div AccRunTime2]); + true -> + f("~w", [AccBCnt2]) + end]), + State#{runtime => AccRunTime2, + mcnt => AccMCnt2, + bcnt => AccBCnt2, + hcnt => AccHCnt2}; +server_handle_handler_down(Pid, Reason, State) -> + i("handler ~p terminated: " + "~n ~p", [Pid, Reason]), + State. + + + +%% ========================================================================== + +handler_start() -> + Self = self(), + HandlerInit = fun() -> put(sname, "handler"), handler_init(Self) end, + spawn_monitor(HandlerInit). + +handler_continue(Pid, Mod, Sock, Active) -> + req(Pid, {continue, Mod, Sock, Active}). + +handler_stop(Pid) -> + req(Pid, stop). + +handler_init(Parent) -> + i("starting"), + receive + {?MODULE, Ref, Parent, {continue, Mod, Sock, Active}} -> + i("received continue"), + reply(Parent, Ref, ok), + handler_initial_activation(Mod, Sock, Active), + handler_loop(#{parent => Parent, + mod => Mod, + sock => Sock, + active => Active, + start => t(), + mcnt => 0, + bcnt => 0, + last_reply => none, + acc => <<>>}) + + after 5000 -> + i("timeout when message queue: " + "~n ~p" + "~nwhen" + "~n Parent: ~p", [process_info(self(), messages), Parent]), + handler_init(Parent) + end. + +handler_loop(State) -> + %% i("handler-loop"), + handler_loop( handler_handle_message( handler_recv_message(State) ) ). + +%% When passive, we read *one* request and then attempt to reply to it. +handler_recv_message(#{mod := Mod, + sock := Sock, + active := false, + mcnt := MCnt, + bcnt := BCnt} = State) -> + %% i("handler_recv_message(false) -> entry"), + case handler_recv_message2(Mod, Sock) of + {ok, {MsgSz, ID, Body}} -> + handler_send_reply(Mod, Sock, ID, Body), + State#{mcnt => MCnt + 1, + bcnt => BCnt + MsgSz, + last_reply => ID}; + {error, closed} -> + handler_done(State); + {error, timeout} -> + State + end; + + +%% When "active" (once or true), we receive one data "message", which may +%% contain any number of requests or only part of a request. Then we +%% process this data together with whatever we had "accumulated" from +%% prevous messages. Each request will be extracted and replied to. If +%% there is some data left, not enough for a complete request, we store +%% this in 'acc' (accumulate it). +handler_recv_message(#{mod := Mod, + sock := Sock, + active := Active, + mcnt := MCnt, + bcnt := BCnt, + last_reply := LID, + acc := Acc} = State) -> + %% i("handler_recv_message(~w) -> entry", [Active]), + case handler_recv_message3(Mod, Sock, Acc, LID) of + {ok, {MCnt2, BCnt2, LID2}, NewAcc} -> + handler_maybe_activate(Mod, Sock, Active), + State#{mcnt => MCnt + MCnt2, + bcnt => BCnt + BCnt2, + last_reply => LID2, + acc => NewAcc}; + + {error, closed} -> + if + (size(Acc) =:= 0) -> + handler_done(State); + true -> + e("client done with partial message: " + "~n Last Reply Sent: ~w" + "~n Message Count: ~w" + "~n Byte Count: ~w" + "~n Partial Message: ~w bytes", + [LID, MCnt, BCnt, size(Acc)]), + exit({closed_with_partial_message, LID}) + end; + + {error, timeout} -> + State + end. + +handler_process_data(Acc, Mod, Sock, LID) -> + handler_process_data(Acc, Mod, Sock, 0, 0, LID). + +%% Extract each complete request, one at a time. +handler_process_data(<>, + Mod, Sock, + MCnt, BCnt, _LID) when (size(Rest) >= SZ) -> + <> = Rest, + case handler_send_reply(Mod, Sock, ID, Body) of + ok -> + handler_process_data(Rest2, Mod, Sock, MCnt+1, BCnt+SZ, ID); + {error, _} = ERROR -> + ERROR + end; +handler_process_data(Data, _Mod, _Sock, MCnt, BCnt, LID) -> + {ok, {MCnt, BCnt, LID}, Data}. + + +handler_recv_message2(Mod, Sock) -> + %% i("handler_recv_message2 -> entry"), + case Mod:recv(Sock, 4*4, ?RECV_TIMEOUT) of + {ok, <> = Hdr} -> + %% i("handler_recv_message2 -> got request header: " + %% "~n ID: ~p" + %% "~n SZ: ~p", [ID, SZ]), + case Mod:recv(Sock, SZ, ?RECV_TIMEOUT) of + {ok, Body} when (SZ =:= size(Body)) -> + %% i("handler_recv_message2 -> got body"), + {ok, {size(Hdr) + size(Body), ID, Body}}; + {error, BReason} -> + e("failed reading body (~w) of message ~w:" + "~n ~p", [SZ, ID, BReason]), + exit({recv, body, ID, SZ, BReason}) + end; + {error, timeout} = ERROR -> + i("handler_recv_message2 -> timeout"), + ERROR; + {error, closed} = ERROR -> + ERROR; + {error, HReason} -> + e("failed reading header of message:" + "~n ~p", [HReason]), + exit({recv, header, HReason}) + end. + + +handler_recv_message3(Mod, Sock, Acc, LID) -> + receive + {TagClosed, Sock} when (TagClosed =:= tcp_closed) orelse + (TagClosed =:= socket_closed) -> + {error, closed}; + + {TagErr, Sock, Reason} when (TagErr =:= tcp_error) orelse + (TagErr =:= socket_error) -> + {error, Reason}; + + {Tag, Sock, Msg} when (Tag =:= tcp) orelse + (Tag =:= socket) -> + handler_process_data(<>, Mod, Sock, LID) + + after ?RECV_TIMEOUT -> + {error, timeout} + end. + + + +%% %% Socket in passive mode => we need to read explicitly +%% handler_loop(#{sock := Sock, active := false} = State, MCnt, BCnt) -> +%% %% i("try recv msg header"), +%% MsgSz = +%% case gen_tcp:recv(Sock, 4*4) of +%% {ok, <> = Hdr} -> +%% %% i("msg ~w header received (data sz is ~w bytes)", [ID, SZ]), +%% case gen_tcp:recv(Sock, SZ) of +%% {ok, Data} when (size(Data) =:= SZ) -> +%% handler_send_reply(Sock, ID, Data), +%% size(Hdr)+SZ; +%% {ok, InvData} -> +%% i("invalid data"), +%% (catch gen_tcp:close(Sock)), +%% exit({invalid_data, SZ, size(InvData)}); +%% {error, Reason2} -> +%% i("Data read failed: ~p", [Reason2]), +%% (catch gen_tcp:close(Sock)), +%% exit({recv_data, Reason2}) +%% end; +%% {ok, _InvHdr} -> +%% (catch gen_tcp:close(Sock)), +%% exit(invalid_hdr); +%% {error, closed} -> +%% i("we are done: " +%% "~n Message Count: ~p" +%% "~n Byte Count: ~p", [MCnt, BCnt]), +%% exit(normal); +%% {error, Reason1} -> +%% i("Header read failed: ~p", [Reason1]), +%% (catch gen_tcp:close(Sock)), +%% exit({recv_hdr, Reason1}) +%% end, +%% handler_loop(State, MCnt+1, BCnt+MsgSz); + +%% %% Socket in active mode (once | true) => data messages arrive +%% handler_loop(#{sock := Sock, active := Active} = State, MCnt, BCnt) -> +%% %% i("await msg"), +%% try handler_recv_request(Sock, Active) of +%% {ID, Data, MsgSz} -> +%% %% i("msg ~w received (data sz is ~w bytes)", [ID, size(Data)]), +%% handler_send_reply(Sock, ID, Data), +%% handler_maybe_activate(Sock, Active), +%% handler_loop(State, MCnt+1, BCnt+MsgSz) +%% catch +%% throw:tcp_closed -> +%% i("we are done: " +%% "~n Message Count: ~p" +%% "~n Byte Count: ~p", [MCnt, BCnt]), +%% exit(normal); +%% throw:{tcp_error, Reason} -> +%% i(" TCP error ~p when: " +%% "~n Message Count: ~p" +%% "~n Byte Count: ~p", [Reason, MCnt, BCnt]), +%% exit({tcp_error, Reason}) +%% end. +%% %% {ID, Data, MsgSz} = handler_recv_request(Sock, Active), +%% %% i("msg ~w received (data sz is ~w bytes)", [ID, size(Data)]), +%% %% handler_send_reply(Sock, ID, Data), +%% %% handler_maybe_activate(Sock, Active), +%% %% handler_loop(State, MCnt+1, BCnt+MsgSz). + + +%% handler_recv_request(Sock, Active) -> +%% %% In theory we should also be ready for a partial header, +%% %% but I can't be bothered... +%% receive +%% {tcp_closed, Sock} -> +%% throw(tcp_closed); +%% {tcp_error, Sock, Reason} -> +%% throw({tcp_error, Reason}); +%% {tcp, Sock, <> = Msg} when (size(Data) =:= SZ) -> +%% %% i("[complete] msg ~w received (data sz is ~w bytes)", [ID, SZ]), +%% {ID, Data, size(Msg)}; +%% {tcp, Sock, <> = Msg} when (size(Data) < SZ) -> +%% %% i("[incomplete] msg ~w received (data sz is ~w bytes)", [ID, SZ]), +%% handler_recv_request_data(Sock, Active, ID, SZ, Data, size(Msg)) +%% end. + +%% handler_recv_request_data(Sock, Active, ID, SZ, AccData, AccMsgSz) -> +%% handler_maybe_activate(Sock, Active), +%% receive +%% {tcp_closed, Sock} -> +%% %% i("we are done (incomplete data)"), +%% throw(tcp_closed); +%% {tcp_error, Sock, Reason} -> +%% throw({tcp_error, Reason}); +%% {tcp, Sock, Data} when (SZ =:= (size(AccData) + size(Data))) -> +%% %% i("[complete] received the remaining data (~w bytes) for msg ~w", +%% %% [size(Data), ID]), +%% {ID, <>, AccMsgSz+size(Data)}; +%% {tcp, Sock, Data} -> +%% %% i("[incomplete] received ~w bytes of data for for msg ~w", +%% %% [size(Data), ID]), +%% handler_recv_request_data(Sock, Active, ID, SZ, +%% <>, +%% AccMsgSz+size(Data)) +%% end. + +handler_send_reply(Mod, Sock, ID, Data) -> + SZ = size(Data), + Msg = <>, + %% i("handler-send-reply -> try send reply ~w: ~w bytes", [ID, size(Msg)]), + case Mod:send(Sock, Msg) of + ok -> + %% i("handler-send-reply -> reply ~w (~w bytes) sent", [ID, size(Msg)]), + ok; + {error, Reason} -> + (catch Mod:close(Sock)), + exit({send, Reason}) + end. + + +handler_done(State) -> + handler_done(State, t()). + +handler_done(#{start := Start, + mod := Mod, + sock := Sock, + mcnt := MCnt, + bcnt := BCnt}, Stop) -> + (catch Mod:close(Sock)), + exit({done, tdiff(Start, Stop), MCnt, BCnt}). + + +handler_handle_message(#{parent := Parent} = State) -> + receive + {'EXIT', Parent, Reason} -> + exit({parent_exit, Reason}) + after 0 -> + State + end. + + +handler_initial_activation(_Mod, _Sock, false = _Active) -> + ok; +handler_initial_activation(Mod, Sock, Active) -> + Mod:active(Sock, Active). + + +handler_maybe_activate(Mod, Sock, once = Active) -> + Mod:active(Sock, Active); +handler_maybe_activate(_, _, _) -> + ok. + + + +%% ========================================================================== + +which_addr() -> + case inet:getifaddrs() of + {ok, IfAddrs} -> + which_addrs(inet, IfAddrs); + {error, Reason} -> + exit({getifaddrs, Reason}) + end. + +which_addrs(_Family, []) -> + exit({getifaddrs, not_found}); +which_addrs(Family, [{"lo", _} | IfAddrs]) -> + %% Skip + which_addrs(Family, IfAddrs); +which_addrs(Family, [{"docker" ++ _, _} | IfAddrs]) -> + %% Skip docker + which_addrs(Family, IfAddrs); +which_addrs(Family, [{"br-" ++ _, _} | IfAddrs]) -> + %% Skip docker + which_addrs(Family, IfAddrs); +which_addrs(Family, [{"en" ++ _, IfOpts} | IfAddrs]) -> + %% Maybe take this one + case which_addr(Family, IfOpts) of + {ok, Addr} -> + Addr; + error -> + which_addrs(Family, IfAddrs) + end; +which_addrs(Family, [{_IfName, IfOpts} | IfAddrs]) -> + case which_addr(Family, IfOpts) of + {ok, Addr} -> + Addr; + error -> + which_addrs(Family, IfAddrs) + end. + +which_addr(_, []) -> + error; +which_addr(inet, [{addr, Addr}|_]) + when is_tuple(Addr) andalso (size(Addr) =:= 4) -> + {ok, Addr}; +which_addr(inet6, [{addr, Addr}|_]) + when is_tuple(Addr) andalso (size(Addr) =:= 8) -> + {ok, Addr}; +which_addr(Family, [_|IfOpts]) -> + which_addr(Family, IfOpts). + + +%% ========================================================================== + +req(Pid, Req) -> + Ref = make_ref(), + Pid ! {?MODULE, Ref, self(), Req}, + receive + {'EXIT', Pid, Reason} -> + {error, {exit, Reason}}; + {?MODULE, Ref, Reply} -> + Reply + end. + +reply(Pid, Ref, Reply) -> + Pid ! {?MODULE, Ref, Reply}. + + +%% ========================================================================== + +t() -> + os:timestamp(). + +tdiff({A1, B1, C1} = _T1x, {A2, B2, C2} = _T2x) -> + T1 = A1*1000000000+B1*1000+(C1 div 1000), + T2 = A2*1000000000+B2*1000+(C2 div 1000), + T2 - T1. + +formated_timestamp() -> + format_timestamp(os:timestamp()). + +format_timestamp({_N1, _N2, N3} = TS) -> + {_Date, Time} = calendar:now_to_local_time(TS), + {Hour,Min,Sec} = Time, + FormatTS = io_lib:format("~.2.0w:~.2.0w:~.2.0w.4~w", + [Hour, Min, Sec, round(N3/1000)]), + lists:flatten(FormatTS). + +%% Time is always in number os ms (milli seconds) +format_time(T) -> + f("~p", [T]). + + +%% ========================================================================== + +f(F, A) -> + lists:flatten(io_lib:format(F, A)). + +e(F, A) -> + p(get(sname), " " ++ F, A). + +i(F) -> + i(F, []). + +i(F, A) -> + p(get(sname), " " ++ F, A). + +p(undefined, F, A) -> + p("- ", F, A); +p(Prefix, F, A) -> + io:format("[~s, ~s] " ++ F ++ "~n", [formated_timestamp(), Prefix |A]). + diff --git a/erts/emulator/test/socket_test_ttest_tcp_server_gen.erl b/erts/emulator/test/socket_test_ttest_tcp_server_gen.erl new file mode 100644 index 0000000000..53a9dc7d2a --- /dev/null +++ b/erts/emulator/test/socket_test_ttest_tcp_server_gen.erl @@ -0,0 +1,34 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2018-2018. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% + +-module(socket_test_ttest_tcp_server_gen). + +-export([ + start_monitor/1, + stop/1 + ]). + +-define(TRANSPORT_MOD, socket_test_ttest_tcp_gen). + +start_monitor(Active) -> + socket_test_ttest_tcp_server:start_monitor(?TRANSPORT_MOD, Active). + +stop(Pid) -> + socket_test_ttest_tcp_server:stop(Pid). diff --git a/erts/emulator/test/socket_test_ttest_tcp_server_socket.erl b/erts/emulator/test/socket_test_ttest_tcp_server_socket.erl new file mode 100644 index 0000000000..de9df857fe --- /dev/null +++ b/erts/emulator/test/socket_test_ttest_tcp_server_socket.erl @@ -0,0 +1,34 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2018-2018. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% + +-module(socket_test_ttest_tcp_server_socket). + +-export([ + start_monitor/1, + stop/1 + ]). + +-define(TRANSPORT_MOD, socket_test_ttest_tcp_socket). + +start_monitor(Active) -> + socket_test_ttest_tcp_server:start_monitor(?TRANSPORT_MOD, Active). + +stop(Pid) -> + socket_test_ttest_tcp_server:stop(Pid). diff --git a/erts/emulator/test/socket_test_ttest_tcp_socket.erl b/erts/emulator/test/socket_test_ttest_tcp_socket.erl new file mode 100644 index 0000000000..12d9e052d7 --- /dev/null +++ b/erts/emulator/test/socket_test_ttest_tcp_socket.erl @@ -0,0 +1,345 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2018-2018. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% + +-module(socket_test_ttest_tcp_socket). + +-export([ + accept/1, accept/2, + active/2, + close/1, + connect/2, + controlling_process/2, + listen/0, listen/1, + port/1, + peername/1, + recv/2, recv/3, + send/2, + shutdown/2, + sockname/1 + ]). + + +-define(READER_RECV_TIMEOUT, 1000). + + +%% ========================================================================== + +accept(#{sock := LSock}) -> + case socket:accept(LSock) of + {ok, Sock} -> + Self = self(), + Reader = spawn(fun() -> reader_init(Self, Sock, false) end), + {ok, #{sock => Sock, reader => Reader}}; + {error, _} = ERROR -> + ERROR + end. + +accept(#{sock := LSock}, Timeout) -> + case socket:accept(LSock, Timeout) of + {ok, Sock} -> + Self = self(), + Reader = spawn(fun() -> reader_init(Self, Sock, false) end), + {ok, #{sock => Sock, reader => Reader}}; + {error, _} = ERROR -> + ERROR + end. + + +active(#{reader := Pid}, NewActive) + when (is_boolean(NewActive) orelse (NewActive =:= once)) -> + Pid ! {?MODULE, active, NewActive}, + ok. + + +close(#{sock := Sock, reader := Pid}) -> + Pid ! {?MODULE, stop}, + socket:close(Sock). + +%% Create a socket and connect it to a peer +connect(Addr, Port) -> + try + begin + Sock = + case socket:open(inet, stream, tcp) of + {ok, S} -> + S; + {error, OReason} -> + throw({error, {open, OReason}}) + end, + case socket:bind(Sock, any) of + {ok, _} -> + ok; + {error, BReason} -> + (catch socket:close(Sock)), + throw({error, {bind, BReason}}) + end, + SA = #{family => inet, + addr => Addr, + port => Port}, + case socket:connect(Sock, SA) of + ok -> + ok; + {error, CReason} -> + (catch socket:close(Sock)), + throw({error, {connect, CReason}}) + end, + Self = self(), + Reader = spawn(fun() -> reader_init(Self, Sock, false) end), + {ok, #{sock => Sock, reader => Reader}} + end + catch + throw:ERROR:_ -> + ERROR + end. + + +controlling_process(#{sock := Sock, reader := Pid}, NewPid) -> + case socket:setopt(Sock, otp, controlling_process, NewPid) of + ok -> + Pid ! {?MODULE, self(), controlling_process, NewPid}, + receive + {?MODULE, Pid, controlling_process} -> + ok + end; + {error, _} = ERROR -> + ERROR + end. + + +%% Create a listen socket +listen() -> + listen(0). + +listen(Port) when is_integer(Port) andalso (Port >= 0) -> + try + begin + Sock = case socket:open(inet, stream, tcp) of + {ok, S} -> + S; + {error, OReason} -> + throw({error, {open, OReason}}) + end, + SA = #{family => inet, + port => Port}, + case socket:bind(Sock, SA) of + {ok, _} -> + ok; + {error, BReason} -> + (catch socket:close(Sock)), + throw({error, {bind, BReason}}) + end, + case socket:listen(Sock) of + ok -> + ok; + {error, LReason} -> + (catch socket:close(Sock)), + throw({error, {listen, LReason}}) + end, + {ok, #{sock => Sock}} + end + catch + throw:{error, Reason}:_ -> + {error, Reason} + end. + + +port(#{sock := Sock}) -> + case socket:sockname(Sock) of + {ok, #{port := Port}} -> + {ok, Port}; + {error, _} = ERROR -> + ERROR + end. + + +peername(#{sock := Sock}) -> + case socket:peername(Sock) of + {ok, #{addr := Addr, port := Port}} -> + {ok, {Addr, Port}}; + {error, _} = ERROR -> + ERROR + end. + + +recv(#{sock := Sock}, Length) -> + socket:recv(Sock, Length). +recv(#{sock := Sock}, Length, Timeout) -> + socket:recv(Sock, Length, Timeout). + + +send(#{sock := Sock}, Length) -> + socket:send(Sock, Length). + + +shutdown(#{sock := Sock}, How) -> + socket:shutdown(Sock, How). + + +sockname(#{sock := Sock}) -> + case socket:sockname(Sock) of + {ok, #{addr := Addr, port := Port}} -> + {ok, {Addr, Port}}; + {error, _} = ERROR -> + ERROR + end. + + +%% ========================================================================== + +reader_init(ControllingProcess, Sock, Active) + when is_pid(ControllingProcess) andalso + (is_boolean(Active) orelse (Active =:= once)) -> + MRef = erlang:monitor(process, ControllingProcess), + reader_loop(#{ctrl_proc => ControllingProcess, + ctrl_proc_mref => MRef, + active => Active, + sock => Sock}). + + +%% Never read +reader_loop(#{active := false, + ctrl_proc := Pid} = State) -> + receive + {?MODULE, stop} -> + exit(normal); + + {?MODULE, Pid, controlling_process, NewPid} -> + MRef = maps:get(ctrl_proc_mref, State), + erlang:demonitor(MRef, [flush]), + NewMRef = erlang:monitor(process, NewPid), + Pid ! {?MODULE, self(), controlling_process}, + reader_loop(State#{ctrl_proc => NewPid, + ctrl_proc_mref => NewMRef}); + + {?MODULE, active, NewActive} -> + reader_loop(State#{active => NewActive}); + + {'DOWN', MRef, process, Pid, Reason} -> + case maps:get(ctrl_proc_mref, State) of + MRef when (Reason =:= normal) -> + exit(normal); + MRef -> + exit({controlling_process, Reason}); + _ -> + reader_loop(State) + end + end; + +%% Read *once* and then change to false +reader_loop(#{active := once, + sock := Sock, + ctrl_proc := Pid} = State) -> + case socket:recv(Sock, 0, ?READER_RECV_TIMEOUT) of + {ok, Data} -> + Pid ! {socket, #{sock => Sock, reader => self()}, Data}, + reader_loop(State#{active => false}); + {error, timeout} -> + receive + {?MODULE, stop} -> + exit(normal); + + {?MODULE, Pid, controlling_process, NewPid} -> + MRef = maps:get(ctrl_proc_mref, State), + erlang:demonitor(MRef, [flush]), + MRef = erlang:monitor(process, NewPid), + Pid ! {?MODULE, self(), controlling_process}, + reader_loop(State#{ctrl_proc => NewPid, + ctrl_proc_mref => MRef}); + + {?MODULE, active, NewActive} -> + reader_loop(State#{active => NewActive}); + + {'DOWN', MRef, process, Pid, Reason} -> + case maps:get(ctrl_proc_mref, State) of + MRef when (Reason =:= normal) -> + exit(normal); + MRef -> + exit({controlling_process, Reason}); + _ -> + reader_loop(State) + end + after 0 -> + reader_loop(State) + end; + + {error, closed} -> + Pid ! {socket_closed, #{sock => Sock, reader => self()}}, + exit(normal); + + {error, Reason} -> + Pid ! {socket_error, #{sock => Sock, reader => self()}, Reason}, + exit(Reason) + end; + +%% Read and forward data continuously +reader_loop(#{active := true, + sock := Sock, + ctrl_proc := Pid} = State) -> + case socket:recv(Sock, 0, ?READER_RECV_TIMEOUT) of + {ok, Data} -> + Pid ! {socket, #{sock => Sock, reader => self()}, Data}, + reader_loop(State); + {error, timeout} -> + receive + {?MODULE, stop} -> + exit(normal); + + {?MODULE, Pid, controlling_process, NewPid} -> + MRef = maps:get(ctrl_proc_mref, State), + erlang:demonitor(MRef, [flush]), + MRef = erlang:monitor(process, NewPid), + Pid ! {?MODULE, self(), controlling_process}, + reader_loop(State#{ctrl_proc => NewPid, + ctrl_proc_mref => MRef}); + + {?MODULE, active, NewActive} -> + reader_loop(State#{active => NewActive}); + + {'DOWN', MRef, process, Pid, Reason} -> + case maps:get(ctrl_proc_mref, State) of + MRef when (Reason =:= normal) -> + exit(normal); + MRef -> + exit({controlling_process, Reason}); + _ -> + reader_loop(State) + end + after 0 -> + reader_loop(State) + end; + + {error, closed} -> + Pid ! {socket_closed, #{sock => Sock, reader => self()}}, + exit(normal); + + {error, Reason} -> + Pid ! {socket_error, #{sock => Sock, reader => self()}, Reason}, + exit(Reason) + end. + + + + + + +%% ========================================================================== + + + -- cgit v1.2.3 From 7923c2c7ce4deba75d845501eb96ddce07dc5ea0 Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Thu, 6 Dec 2018 12:40:37 +0100 Subject: [socket-nif] Valgrind: plugged memory leaks in nif_recv Running valgrind, found a couple of cases when allocated binaries where not released. Mostly when the recv failed for some reason. Also clear and free the environment associated with the socket (resource) (in the _dtor callback function). OTP-14831 --- erts/emulator/nifs/common/socket_int.h | 1 + erts/emulator/nifs/common/socket_nif.c | 41 ++++++++++++++++++++++++++++------ 2 files changed, 35 insertions(+), 7 deletions(-) diff --git a/erts/emulator/nifs/common/socket_int.h b/erts/emulator/nifs/common/socket_int.h index 7d223b8259..ec17e45f25 100644 --- a/erts/emulator/nifs/common/socket_int.h +++ b/erts/emulator/nifs/common/socket_int.h @@ -378,6 +378,7 @@ extern ERL_NIF_TERM esock_atom_einval; #define ALLOC_BIN(SZ, BP) enif_alloc_binary((SZ), (BP)) #define REALLOC_BIN(SZ, BP) enif_realloc_binary((SZ), (BP)) +#define FREE_BIN(BP) enif_release_binary((BP)) #endif // SOCKET_INT_H__ diff --git a/erts/emulator/nifs/common/socket_nif.c b/erts/emulator/nifs/common/socket_nif.c index 3da895e644..57ce92d77e 100644 --- a/erts/emulator/nifs/common/socket_nif.c +++ b/erts/emulator/nifs/common/socket_nif.c @@ -4233,8 +4233,13 @@ ERL_NIF_TERM nopen(ErlNifEnv* env, descP->type = type; descP->protocol = protocol; + /* + * Should we keep track of sockets (resources) in some way? + * Doing it here will require mutex to ensure data integrity, + * which will be costly. Send it somewhere? + */ res = enif_make_resource(env, descP); - enif_release_resource(descP); // We should really store a reference ... + enif_release_resource(descP); /* Keep track of the creator * This should not be a problem but just in case @@ -13592,6 +13597,8 @@ ERL_NIF_TERM recv_check_result(ErlNifEnv* env, recv_error_current_reader(env, descP, res); + FREE_BIN(bufP); + return res; } @@ -13633,6 +13640,9 @@ ERL_NIF_TERM recv_check_result(ErlNifEnv* env, if ((xres = recv_init_current_reader(env, descP, recvRef)) != NULL) return esock_make_error_str(env, xres); + /* This transfers "ownership" of the *allocated* binary to an + * erlang term (no need for an explicit free). + */ data = MKBIN(env, bufP); SSDBG( descP, @@ -13664,6 +13674,9 @@ ERL_NIF_TERM recv_check_result(ErlNifEnv* env, recv_update_current_reader(env, descP); + /* This transfers "ownership" of the *allocated* binary to an + * erlang term (no need for an explicit free). + */ data = MKBIN(env, bufP); return esock_make_ok3(env, atom_true, data); @@ -13707,6 +13720,8 @@ ERL_NIF_TERM recv_check_result(ErlNifEnv* env, (ERL_NIF_SELECT_STOP), descP, NULL, recvRef); + FREE_BIN(bufP); + return res; } else if ((saveErrno == ERRNO_BLOCK) || @@ -13721,16 +13736,14 @@ ERL_NIF_TERM recv_check_result(ErlNifEnv* env, SSDBG( descP, ("SOCKET", "recv_check_result -> SELECT for more\r\n") ); - /* - SELECT(env, descP->sock, (ERL_NIF_SELECT_READ), - descP, NULL, recvRef); - */ - sres = enif_select(env, descP->sock, (ERL_NIF_SELECT_READ), descP, NULL, recvRef); - SSDBG( descP, ("SOCKET", "recv_check_result -> SELECT res: %d\r\n", sres) ); + SSDBG( descP, + ("SOCKET", "recv_check_result -> SELECT res: %d\r\n", sres) ); + FREE_BIN(bufP); + return esock_make_error(env, esock_atom_eagain); } else { ERL_NIF_TERM res = esock_make_error_errno(env, saveErrno); @@ -13740,6 +13753,8 @@ ERL_NIF_TERM recv_check_result(ErlNifEnv* env, recv_error_current_reader(env, descP, res); + FREE_BIN(bufP); + return res; } @@ -13768,6 +13783,9 @@ ERL_NIF_TERM recv_check_result(ErlNifEnv* env, recv_update_current_reader(env, descP); + /* This transfers "ownership" of the *allocated* binary to an + * erlang term (no need for an explicit free). + */ data = MKBIN(env, bufP); data = MKSBIN(env, data, 0, read); @@ -13792,6 +13810,9 @@ ERL_NIF_TERM recv_check_result(ErlNifEnv* env, cnt_inc(&descP->readByteCnt, read); + /* This transfers "ownership" of the *allocated* binary to an + * erlang term (no need for an explicit free). + */ data = MKBIN(env, bufP); data = MKSBIN(env, data, 0, read); @@ -15713,6 +15734,7 @@ SocketDescriptor* alloc_descriptor(SOCKET sock, HANDLE event) if ((descP = enif_alloc_resource(sockets, sizeof(SocketDescriptor))) != NULL) { char buf[64]; /* Buffer used for building the mutex name */ + // This needs to be released when the socket is closed! descP->env = enif_alloc_env(); sprintf(buf, "socket[w,%d]", sock); @@ -16870,10 +16892,15 @@ void socket_dtor(ErlNifEnv* env, void* obj) { SocketDescriptor* descP = (SocketDescriptor*) obj; + enif_clear_env(descP->env); + enif_free_env(descP->env); + descP->env = NULL; + MDESTROY(descP->writeMtx); MDESTROY(descP->readMtx); MDESTROY(descP->accMtx); MDESTROY(descP->closeMtx); + } -- cgit v1.2.3 From c5378517cccf29d1708c71a0949664605743b478 Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Thu, 6 Dec 2018 14:24:06 +0100 Subject: [socket-nif] Valgrind: plugged memory leaks in nif_[recvfromsock, (ERL_NIF_SELECT_READ), descP, NULL, recvRef); + FREE_BIN(bufP); + return esock_make_error(env, esock_atom_eagain); + } else { ERL_NIF_TERM res = esock_make_error_errno(env, saveErrno); @@ -13906,6 +13911,8 @@ ERL_NIF_TERM recvfrom_check_result(ErlNifEnv* env, recv_error_current_reader(env, descP, res); + FREE_BIN(bufP); + return res; } @@ -13983,6 +13990,9 @@ ERL_NIF_TERM recvmsg_check_result(ErlNifEnv* env, * *We* do never actually try to read 0 bytes from a stream socket! */ + + FREE_BIN(dataBufP); FREE_BIN(ctrlBufP); + return esock_make_error(env, atom_closed); } @@ -14023,6 +14033,8 @@ ERL_NIF_TERM recvmsg_check_result(ErlNifEnv* env, (ERL_NIF_SELECT_STOP), descP, NULL, recvRef); + FREE_BIN(dataBufP); FREE_BIN(ctrlBufP); + return res; } else if ((saveErrno == ERRNO_BLOCK) || @@ -14037,7 +14049,10 @@ ERL_NIF_TERM recvmsg_check_result(ErlNifEnv* env, SELECT(env, descP->sock, (ERL_NIF_SELECT_READ), descP, NULL, recvRef); + FREE_BIN(dataBufP); FREE_BIN(ctrlBufP); + return esock_make_error(env, esock_atom_eagain); + } else { ERL_NIF_TERM res = esock_make_error_errno(env, saveErrno); @@ -14047,6 +14062,8 @@ ERL_NIF_TERM recvmsg_check_result(ErlNifEnv* env, recv_error_current_reader(env, descP, res); + FREE_BIN(dataBufP); FREE_BIN(ctrlBufP); + return res; } @@ -14078,6 +14095,8 @@ ERL_NIF_TERM recvmsg_check_result(ErlNifEnv* env, recv_update_current_reader(env, descP); + FREE_BIN(dataBufP); FREE_BIN(ctrlBufP); + return esock_make_error_str(env, xres); } else { -- cgit v1.2.3 From d0c3f79d22b4778f66ac1d8a2fc03e736f42e973 Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Fri, 7 Dec 2018 18:40:24 +0100 Subject: [socket-nif|test] ttest improvements Added a ttest lib module for some common functions. Added a process (server handler and reader processes) stats printouts. So far only used by the server. There is still a "leak". Its a term leak. Some of the functions take a ref as argument (recv, send and accept for instance). This is stored internally, by way of a call to the enif_make_copy, in order to be used later in a select call. Its not "released" though, until the environment is released, which happens when the socket dtor callback function is called. Possible solution: We need to keep "temporary" environments (one for each of the queues), which we can clear (basically we need two, one that is currently used for new ref's and one for the old ref's). OTP-14831 --- erts/emulator/test/Makefile | 1 + erts/emulator/test/socket_test_ttest_lib.erl | 125 +++++++ .../emulator/test/socket_test_ttest_tcp_client.erl | 261 ++++++-------- .../test/socket_test_ttest_tcp_client_socket.erl | 15 +- .../emulator/test/socket_test_ttest_tcp_server.erl | 391 +++++++++------------ .../test/socket_test_ttest_tcp_server_socket.erl | 9 +- .../emulator/test/socket_test_ttest_tcp_socket.erl | 129 +++++-- 7 files changed, 508 insertions(+), 423 deletions(-) create mode 100644 erts/emulator/test/socket_test_ttest_lib.erl diff --git a/erts/emulator/test/Makefile b/erts/emulator/test/Makefile index a910588381..09bfe6f104 100644 --- a/erts/emulator/test/Makefile +++ b/erts/emulator/test/Makefile @@ -34,6 +34,7 @@ SOCKET_MODULES = \ socket_client \ socket_test_lib \ socket_test_evaluator \ + socket_test_ttest_lib \ socket_test_ttest_tcp_gen \ socket_test_ttest_tcp_socket \ socket_test_ttest_tcp_client \ diff --git a/erts/emulator/test/socket_test_ttest_lib.erl b/erts/emulator/test/socket_test_ttest_lib.erl new file mode 100644 index 0000000000..71679bc6d6 --- /dev/null +++ b/erts/emulator/test/socket_test_ttest_lib.erl @@ -0,0 +1,125 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2018-2018. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% + +-module(socket_test_ttest_lib). + +-compile({no_auto_import, [error/2]}). + +-export([ + t/0, tdiff/2, + formated_timestamp/0, format_timestamp/1, + format_time/1, + + formated_process_stats/1, formated_process_stats/2, + + format/2, + error/1, error/2, + info/1, info/2 + ]). + +%% ========================================================================== + +t() -> + os:timestamp(). + +tdiff({A1, B1, C1} = _T1x, {A2, B2, C2} = _T2x) -> + T1 = A1*1000000000+B1*1000+(C1 div 1000), + T2 = A2*1000000000+B2*1000+(C2 div 1000), + T2 - T1. + +formated_timestamp() -> + format_timestamp(os:timestamp()). + +format_timestamp({_N1, _N2, N3} = TS) -> + {_Date, Time} = calendar:now_to_local_time(TS), + {Hour,Min,Sec} = Time, + FormatTS = io_lib:format("~.2.0w:~.2.0w:~.2.0w.4~w", + [Hour, Min, Sec, round(N3/1000)]), + lists:flatten(FormatTS). + +%% Time is always in number os ms (milli seconds) +%% At some point, we should convert this to a more readable format... +format_time(T) -> + format("~p", [T]). + + +formated_process_stats(Pid) -> + formated_process_stats("", Pid). + +formated_process_stats(Prefix, Pid) when is_list(Prefix) andalso is_pid(Pid) -> + try + begin + TotHeapSz = pi(Pid, total_heap_size), + HeapSz = pi(Pid, heap_size), + StackSz = pi(Pid, stack_size), + Reds = pi(Pid, reductions), + GCInfo = pi(Pid, garbage_collection), + MinBinVHeapSz = proplists:get_value(min_bin_vheap_size, GCInfo), + MinHeapSz = proplists:get_value(min_heap_size, GCInfo), + MinGCS = proplists:get_value(minor_gcs, GCInfo), + format("~n ~sTotal Heap Size: ~p" + "~n ~sHeap Size: ~p" + "~n ~sStack Size: ~p" + "~n ~sReductions: ~p" + "~n ~s[GC] Min Bin VHeap Size: ~p" + "~n ~s[GC] Min Heap Size: ~p" + "~n ~s[GC] Minor GCS: ~p", + [Prefix, TotHeapSz, + Prefix, HeapSz, + Prefix, StackSz, + Prefix, Reds, + Prefix, MinBinVHeapSz, + Prefix, MinHeapSz, + Prefix, MinGCS]) + end + catch + _:_:_ -> + "" + end. + + +pi(Pid, Item) -> + {Item, Info} = process_info(Pid, Item), + Info. + + + +%% ========================================================================== + +format(F, A) -> + lists:flatten(io_lib:format(F, A)). + +error(F) -> + error(F, []). + +error(F, A) -> + print(get(sname), " " ++ F, A). + +info(F) -> + info(F, []). + +info(F, A) -> + print(get(sname), " " ++ F, A). + +print(undefined, F, A) -> + print("- ", F, A); +print(Prefix, F, A) -> + io:format("[~s, ~s] " ++ F ++ "~n", [formated_timestamp(), Prefix |A]). + diff --git a/erts/emulator/test/socket_test_ttest_tcp_client.erl b/erts/emulator/test/socket_test_ttest_tcp_client.erl index 1bd1bc54e9..9c43c41841 100644 --- a/erts/emulator/test/socket_test_ttest_tcp_client.erl +++ b/erts/emulator/test/socket_test_ttest_tcp_client.erl @@ -54,6 +54,14 @@ -define(MAX_OUTSTANDING_DEFAULT_2, 10). -define(MAX_OUTSTANDING_DEFAULT_3, 3). +-define(LIB, socket_test_ttest_lib). +-define(I(F), ?LIB:info(F)). +-define(I(F,A), ?LIB:info(F, A)). +-define(E(F,A), ?LIB:error(F, A)). +-define(F(F,A), ?LIB:format(F, A)). +-define(FORMAT_TIME(T), ?LIB:format_time(T)). +-define(T(), ?LIB:t()). +-define(TDIFF(T1,T2), ?LIB:tdiff(T1, T2)). -type active() :: once | boolean(). -type msg_id() :: 1..3. @@ -63,42 +71,28 @@ %% ========================================================================== --spec start_monitor(Mod, Active, Addr, Port) -> term() when - Mod :: atom(), - Active :: active(), - Addr :: inet:ip_address(), - Port :: inet:port_number(). +start_monitor(Transport, Active, Addr, Port) -> + start_monitor(Transport, Active, Addr, Port, ?MSG_ID_DEFAULT). %% RunTime is in number of ms. -start_monitor(Mod, Active, Addr, Port) -> - start_monitor(Mod, Active, Addr, Port, ?MSG_ID_DEFAULT). - --spec start_monitor(Mod, Active, Addr, Port, MsgID) -> term() when - Mod :: atom(), - Active :: active(), - Addr :: inet:ip_address(), - Port :: inet:port_number(), - MsgID :: msg_id(). - -%% RunTime is in number of ms. -start_monitor(Mod, Active, Addr, Port, 1 = MsgID) -> - start_monitor(Mod, Active, Addr, Port, MsgID, +start_monitor(Transport, Active, Addr, Port, 1 = MsgID) -> + start_monitor(Transport, Active, Addr, Port, MsgID, ?MAX_OUTSTANDING_DEFAULT_1, ?RUNTIME_DEFAULT); -start_monitor(Mod, Active, Addr, Port, 2 = MsgID) -> - start_monitor(Mod, Active, Addr, Port, MsgID, +start_monitor(Transport, Active, Addr, Port, 2 = MsgID) -> + start_monitor(Transport, Active, Addr, Port, MsgID, ?MAX_OUTSTANDING_DEFAULT_2, ?RUNTIME_DEFAULT); -start_monitor(Mod, Active, Addr, Port, 3 = MsgID) -> - start_monitor(Mod, Active, Addr, Port, MsgID, +start_monitor(Transport, Active, Addr, Port, 3 = MsgID) -> + start_monitor(Transport, Active, Addr, Port, MsgID, ?MAX_OUTSTANDING_DEFAULT_3, ?RUNTIME_DEFAULT). --spec start_monitor(Mod, +-spec start_monitor(Transport, Active, Addr, Port, MsgID, MaxOutstanding, RunTime) -> term() when - Mod :: atom(), + Transport :: atom() | tuple(), Active :: active(), Addr :: inet:ip_address(), Port :: inet:port_number(), @@ -107,9 +101,9 @@ start_monitor(Mod, Active, Addr, Port, 3 = MsgID) -> RunTime :: runtime(). %% RunTime is in number of ms. -start_monitor(Mod, Active, Addr, Port, +start_monitor(Transport, Active, Addr, Port, MsgID, MaxOutstanding, RunTime) - when is_atom(Mod) andalso + when (is_atom(Transport) orelse is_tuple(Transport)) andalso (is_boolean(Active) orelse (Active =:= once)) andalso is_tuple(Addr) andalso (is_integer(Port) andalso (Port > 0)) andalso @@ -119,7 +113,7 @@ start_monitor(Mod, Active, Addr, Port, Self = self(), ClientInit = fun() -> put(sname, "client"), init(Self, - Mod, Active, Addr, Port, + Transport, Active, Addr, Port, MsgID, MaxOutstanding, RunTime) end, {Pid, MRef} = spawn_monitor(ClientInit), @@ -147,28 +141,29 @@ stop(Pid) when is_pid(Pid) -> %% ========================================================================== -init(Parent, Mod, Active, Addr, Port, +init(Parent, Transport, Active, Addr, Port, MsgID, MaxOutstanding, RunTime) -> - i("init -> entry with" - "~n Parent: ~p" - "~n Mod: ~p" - "~n Active: ~p" - "~n Addr: ~s" - "~n Port: ~p" - "~n Msg ID: ~p (=> 16 + ~w bytes)" - "~n Max Outstanding: ~p" - "~n (Suggested) Run Time: ~p ms", - [Parent, - Mod, Active, inet:ntoa(Addr), Port, - MsgID, size(which_msg_data(MsgID)), MaxOutstanding, RunTime]), - case Mod:connect(Addr, Port) of + ?I("init with" + "~n Parent: ~p" + "~n Transport: ~p" + "~n Active: ~p" + "~n Addr: ~s" + "~n Port: ~p" + "~n Msg ID: ~p (=> 16 + ~w bytes)" + "~n Max Outstanding: ~p" + "~n (Suggested) Run Time: ~p ms", + [Parent, + Transport, Active, inet:ntoa(Addr), Port, + MsgID, size(which_msg_data(MsgID)), MaxOutstanding, RunTime]), + {Mod, Connect} = process_transport(Transport), + case Connect(Addr, Port) of {ok, Sock} -> - i("init -> connected"), + ?I("connected"), Parent ! {?MODULE, self(), ok}, initial_activation(Mod, Sock, Active), Results = loop(#{slogan => run, runtime => RunTime, - start => t(), + start => ?T(), parent => Parent, mod => Mod, sock => Sock, @@ -187,10 +182,16 @@ init(Parent, Mod, Active, Addr, Port, (catch Mod:close(Sock)), exit(normal); {error, Reason} -> - i("init -> connect failed: ~p", [Reason]), + ?E("connect failed: ~p", [Reason]), exit({connect, Reason}) end. +process_transport(Mod) when is_atom(Mod) -> + {Mod, fun(A, P) -> Mod:connect(A, P) end}; +process_transport({Mod, Opts}) -> + {Mod, fun(A, P) -> Mod:connect(A, P, Opts) end}. + + which_msg_data(1) -> ?MSG_DATA1; which_msg_data(2) -> ?MSG_DATA2; which_msg_data(3) -> ?MSG_DATA3. @@ -200,21 +201,21 @@ present_results(#{status := ok, runtime := RunTime, bcnt := ByteCnt, cnt := NumIterations}) -> - i("Results: " - "~n Run Time: ~s" - "~n ByteCnt: ~s" - "~n NumIterations: ~s", - [format_time(RunTime), - if ((ByteCnt =:= 0) orelse (RunTime =:= 0)) -> - f("~w, ~w", [ByteCnt, RunTime]); + ?I("Results: " + "~n Run Time: ~s" + "~n ByteCnt: ~s" + "~n NumIterations: ~s", + [?FORMAT_TIME(RunTime), + if ((ByteCnt =:= 0) orelse (RunTime =:= 0)) -> + ?F("~w, ~w", [ByteCnt, RunTime]); true -> - f("~p => ~p byte / ms", [ByteCnt, ByteCnt div RunTime]) + ?F("~p => ~p byte / ms", [ByteCnt, ByteCnt div RunTime]) end, if (RunTime =:= 0) -> "-"; true -> - f("~p => ~p iterations / ms", - [NumIterations, NumIterations div RunTime]) + ?F("~p => ~p iterations / ms", + [NumIterations, NumIterations div RunTime]) end]), ok; present_results(#{status := Failure, @@ -225,21 +226,21 @@ present_results(#{status := Failure, rcnt := RCnt, bcnt := BCnt, num := Num}) -> - i("Time Test failed: " - "~n ~p" - "~n" - "~nwhen" - "~n" - "~n Run Time: ~s" - "~n Send ID: ~p" - "~n Recv ID: ~p" - "~n Send Count: ~p" - "~n Recv Count: ~p" - "~n Byte Count: ~p" - "~n Num Iterations: ~p", - [Failure, - format_time(RunTime), - SID, RID, SCnt, RCnt, BCnt, Num]). + ?I("Time Test failed: " + "~n ~p" + "~n" + "~nwhen" + "~n" + "~n Run Time: ~s" + "~n Send ID: ~p" + "~n Recv ID: ~p" + "~n Send Count: ~p" + "~n Recv Count: ~p" + "~n Byte Count: ~p" + "~n Num Iterations: ~p", + [Failure, + ?FORMAT_TIME(RunTime), + SID, RID, SCnt, RCnt, BCnt, Num]). @@ -255,17 +256,14 @@ do_loop(State) -> do_loop( handle_message( msg_exchange(State) ) ). msg_exchange(#{rcnt := Num, num := Num} = State) -> - %% i("we are done"), finish(ok, State); msg_exchange(#{scnt := Num, num := Num} = State) -> %% We are done sending more requests - now we will just await %% the replies for the (still) outstanding replies. - %% i("we have sent all requests - (only) wait for replies"), msg_exchange( recv_reply(State) ); msg_exchange(#{outstanding := Outstanding, max_outstanding := MaxOutstanding} = State) when (Outstanding < MaxOutstanding) -> - %% i("send the (initial) requests (~w, ~w)", [Outstanding, MaxOutstanding]), msg_exchange( send_request(State) ); msg_exchange(State) -> send_request( recv_reply(State) ). @@ -273,9 +271,9 @@ msg_exchange(State) -> finish(ok, #{start := Start, bcnt := BCnt, num := Num}) -> - Stop = t(), + Stop = ?T(), throw(#{status => ok, - runtime => tdiff(Start, Stop), + runtime => ?TDIFF(Start, Stop), bcnt => BCnt, cnt => Num}); finish(Reason, @@ -283,9 +281,9 @@ finish(Reason, sid := SID, rid := RID, scnt := SCnt, rcnt := RCnt, bcnt := BCnt, num := Num}) -> - Stop = t(), + Stop = ?T(), throw(#{status => Reason, - runtime => tdiff(Start, Stop), + runtime => ?TDIFF(Start, Stop), sid => SID, rid => RID, scnt => SCnt, @@ -301,11 +299,6 @@ send_request(#{mod := Mod, max_outstanding := MaxOutstanding, msg_data := Data} = State) when (MaxOutstanding > Outstanding) -> - %% i("send request -> entry when" - %% "~n ID: ~p" - %% "~n Cnt: ~p" - %% "~n Outstanding: ~p" - %% "~n MaxOutstanding: ~p", [ID, Cnt, Outstanding, MaxOutstanding]), SZ = size(Data), Req = <>, case Mod:send(Sock, Req) of ok -> - %% i("~w bytes sent", [size(Req)]), State#{sid => next_id(ID), scnt => Cnt + 1, outstanding => Outstanding + 1}; {error, Reason} -> - e("Failed sending request: ~p", [Reason]), + ?E("Failed sending request: ~p", [Reason]), exit({send, Reason}) end; send_request(State) -> @@ -334,11 +326,6 @@ recv_reply(#{mod := Mod, bcnt := BCnt, rcnt := Cnt, outstanding := Outstanding} = State) -> - %% i("recv-reply(false) -> entry with" - %% "~n (R)ID: ~p" - %% "~n (R)Cnt: ~p" - %% "~n BCnt: ~p" - %% "~n Outstanding: ~p", [ID, Cnt, BCnt, Outstanding]), case recv_reply_message1(Mod, Sock, ID) of {ok, MsgSz} -> State#{rid => next_id(ID), @@ -347,7 +334,7 @@ recv_reply(#{mod := Mod, outstanding => Outstanding - 1}; {error, timeout} -> - i("recv_reply(false) -> error: timeout"), + ?I("receive timeout"), State; {error, Reason} -> @@ -362,11 +349,6 @@ recv_reply(#{mod := Mod, rcnt := RCnt, outstanding := Outstanding, acc := Acc} = State) -> - %% i("recv-reply(~w) -> entry with" - %% "~n (R)ID: ~p" - %% "~n RCnt: ~p" - %% "~n BCnt: ~p" - %% "~n Outstanding: ~p", [Active, ID, RCnt, BCnt, Outstanding]), case recv_reply_message2(Mod, Sock, ID, Acc) of {ok, {MsgSz, NewAcc}} when is_integer(MsgSz) andalso is_binary(NewAcc) -> maybe_activate(Mod, Sock, Active), @@ -380,12 +362,12 @@ recv_reply(#{mod := Mod, State; {error, stop} -> - i("recv_reply(~w) -> stop", [Active]), + ?I("receive [~w] -> stop", [Active]), %% This will have the effect that no more requests are sent... - State#{num => SCnt, stop_started => t()}; + State#{num => SCnt, stop_started => ?T()}; {error, timeout} -> - i("recv_reply(~w) -> error: timeout", [Active]), + ?I("receive[~w] -> timeout", [Active]), State; {error, Reason} -> @@ -395,23 +377,19 @@ recv_reply(#{mod := Mod, %% This function reads exactly one (reply) message. No more no less. recv_reply_message1(Mod, Sock, ID) -> - %% i("recv_reply_message1 -> entry with" - %% "~n ID: ~w", [ID]), case Mod:recv(Sock, 4*4, ?RECV_TIMEOUT) of {ok, <> = Hdr} -> %% Receive the ping-pong reply boby - %% i("recv_reply_message1 -> try read body" - %% "~n ID: ~w", [ID]), case Mod:recv(Sock, SZ, ?RECV_TIMEOUT) of {ok, Data} when (size(Data) =:= SZ) -> {ok, size(Hdr) + size(Data)}; {error, Reason2} -> - i("recv_reply_message1 -> body error: " - "~n ~p: ~p", [Reason2]), - {error, {recv_hdr, Reason2}} + ?E("Failed reading body: " + "~n ~p: ~p", [Reason2]), + {error, {recv_body, Reason2}} end; {ok, < {error, invalid_hdr}; {error, Reason1} -> - i("recv_reply_message1 -> hdr error: " + ?E("Feiled reading header: " "~n ~p", [Reason1]), {error, {recv_hdr, Reason1}} end. @@ -437,8 +415,6 @@ recv_reply_message1(Mod, Sock, ID) -> %% accumulated. If that is not enough for a (complete) reply, it %% will attempt to receive more. recv_reply_message2(Mod, Sock, ID, Acc) -> - %% i("recv_reply_message2 -> entry with" - %% "~n ID: ~w", [ID]), case process_acc_data(ID, Acc) of ok -> %% No or insufficient data, so get more @@ -456,7 +432,6 @@ recv_reply_message2(Mod, Sock, ID, Acc) -> recv_reply_message3(_Mod, Sock, ID, Acc) -> receive {timeout, _TRef, stop} -> - %% i("stop - when messages: ~p", [process_info(self(), messages)]), {error, stop}; {TagClosed, Sock} when (TagClosed =:= tcp_closed) orelse @@ -469,7 +444,6 @@ recv_reply_message3(_Mod, Sock, ID, Acc) -> {Tag, Sock, Msg} when (Tag =:= tcp) orelse (Tag =:= socket) -> - %% i("recv_reply_message3 -> got ~w byte message", [size(Msg)]), process_acc_data(ID, <>) %% after ?RECV_TIMEOUT -> @@ -482,9 +456,6 @@ process_acc_data(ID, <>) when (SZ =< size(Data)) -> - %% i("process_acc_data -> entry with" - %% "~n ID: ~w" - %% "~n SZ: ~w", [ID, SZ]), <<_Body:SZ/binary, Rest/binary>> = Data, {ok, {4*4+SZ, Rest}}; process_acc_data(ID, < handle_message(#{parent := Parent, sock := Sock, scnt := SCnt} = State) -> receive {timeout, _TRef, stop} -> - i("stop"), + ?I("STOP"), %% This will have the effect that no more requests are sent... - State#{num => SCnt, stop_started => t()}; + State#{num => SCnt, stop_started => ?T()}; {?MODULE, Ref, Parent, stop} -> %% This *aborts* the test @@ -571,47 +542,47 @@ next_id(_) -> %% ========================================================================== -t() -> - os:timestamp(). +%% t() -> +%% os:timestamp(). -tdiff({A1, B1, C1} = _T1x, {A2, B2, C2} = _T2x) -> - T1 = A1*1000000000+B1*1000+(C1 div 1000), - T2 = A2*1000000000+B2*1000+(C2 div 1000), - T2 - T1. +%% tdiff({A1, B1, C1} = _T1x, {A2, B2, C2} = _T2x) -> +%% T1 = A1*1000000000+B1*1000+(C1 div 1000), +%% T2 = A2*1000000000+B2*1000+(C2 div 1000), +%% T2 - T1. -formated_timestamp() -> - format_timestamp(os:timestamp()). +%% formated_timestamp() -> +%% format_timestamp(os:timestamp()). -format_timestamp({_N1, _N2, N3} = TS) -> - {_Date, Time} = calendar:now_to_local_time(TS), - {Hour,Min,Sec} = Time, - FormatTS = io_lib:format("~.2.0w:~.2.0w:~.2.0w.4~w", - [Hour, Min, Sec, round(N3/1000)]), - lists:flatten(FormatTS). +%% format_timestamp({_N1, _N2, N3} = TS) -> +%% {_Date, Time} = calendar:now_to_local_time(TS), +%% {Hour,Min,Sec} = Time, +%% FormatTS = io_lib:format("~.2.0w:~.2.0w:~.2.0w.4~w", +%% [Hour, Min, Sec, round(N3/1000)]), +%% lists:flatten(FormatTS). -%% Time is always in number os ms (milli seconds) -format_time(T) -> - f("~p", [T]). +%% %% Time is always in number os ms (milli seconds) +%% format_time(T) -> +%% f("~p", [T]). %% ========================================================================== -f(F, A) -> - lists:flatten(io_lib:format(F, A)). +%% f(F, A) -> +%% lists:flatten(io_lib:format(F, A)). -%% e(F) -> -%% i(" " ++ F). +%% %% e(F) -> +%% %% i(" " ++ F). -e(F, A) -> - p(get(sname), " " ++ F, A). +%% e(F, A) -> +%% p(get(sname), " " ++ F, A). -i(F) -> - i(F, []). +%% i(F) -> +%% i(F, []). -i(F, A) -> - p(get(sname), " " ++ F, A). +%% i(F, A) -> +%% p(get(sname), " " ++ F, A). -p(undefined, F, A) -> - p("- ", F, A); -p(Prefix, F, A) -> - io:format("[~s, ~s] " ++ F ++ "~n", [formated_timestamp(), Prefix |A]). +%% p(undefined, F, A) -> +%% p("- ", F, A); +%% p(Prefix, F, A) -> +%% io:format("[~s, ~s] " ++ F ++ "~n", [formated_timestamp(), Prefix |A]). diff --git a/erts/emulator/test/socket_test_ttest_tcp_client_socket.erl b/erts/emulator/test/socket_test_ttest_tcp_client_socket.erl index 0d2ec38876..ef980e8d32 100644 --- a/erts/emulator/test/socket_test_ttest_tcp_client_socket.erl +++ b/erts/emulator/test/socket_test_ttest_tcp_client_socket.erl @@ -21,23 +21,24 @@ -module(socket_test_ttest_tcp_client_socket). -export([ - start_monitor/3, start_monitor/4, start_monitor/6, + start_monitor/4, start_monitor/5, start_monitor/7, stop/1 ]). -define(TRANSPORT_MOD, socket_test_ttest_tcp_socket). +-define(MOD(M), {?TRANSPORT_MOD, #{method => Method}}). -start_monitor(Active, Addr, Port) -> - socket_test_ttest_tcp_client:start_monitor(?TRANSPORT_MOD, +start_monitor(Method, Active, Addr, Port) -> + socket_test_ttest_tcp_client:start_monitor(?MOD(Method), Active, Addr, Port). -start_monitor(Active, Addr, Port, MsgID) -> - socket_test_ttest_tcp_client:start_monitor(?TRANSPORT_MOD, +start_monitor(Method, Active, Addr, Port, MsgID) -> + socket_test_ttest_tcp_client:start_monitor(?MOD(Method), Active, Addr, Port, MsgID). -start_monitor(Active, Addr, Port, MsgID, MaxOutstanding, RunTime) -> - socket_test_ttest_tcp_client:start_monitor(?TRANSPORT_MOD, +start_monitor(Method, Active, Addr, Port, MsgID, MaxOutstanding, RunTime) -> + socket_test_ttest_tcp_client:start_monitor(?MOD(Method), Active, Addr, Port, MsgID, MaxOutstanding, RunTime). diff --git a/erts/emulator/test/socket_test_ttest_tcp_server.erl b/erts/emulator/test/socket_test_ttest_tcp_server.erl index cb503a1feb..b248b063a3 100644 --- a/erts/emulator/test/socket_test_ttest_tcp_server.erl +++ b/erts/emulator/test/socket_test_ttest_tcp_server.erl @@ -44,13 +44,25 @@ -define(ACC_TIMEOUT, 10000). -define(RECV_TIMEOUT, 10000). +-define(LIB, socket_test_ttest_lib). +-define(I(F), ?LIB:info(F)). +-define(I(F,A), ?LIB:info(F, A)). +-define(E(F,A), ?LIB:error(F, A)). +-define(F(F,A), ?LIB:format(F, A)). +-define(FORMAT_TIME(T), ?LIB:format_time(T)). +-define(T(), ?LIB:t()). +-define(TDIFF(T1,T2), ?LIB:tdiff(T1, T2)). + %% ========================================================================== -start_monitor(Mod, Active) - when is_atom(Mod) andalso is_boolean(Active) orelse (Active =:= once) -> +start_monitor(Transport, Active) + when (is_atom(Transport) orelse is_tuple(Transport)) andalso + (is_boolean(Active) orelse (Active =:= once)) -> Self = self(), - ServerInit = fun() -> put(sname, "server"), server_init(Self, Mod, Active) end, + ServerInit = fun() -> put(sname, "server"), + server_init(Self, Transport, Active) + end, {Pid, MRef} = spawn_monitor(ServerInit), receive {'DOWN', MRef, process, Pid, normal} -> @@ -73,26 +85,28 @@ stop(Pid) when is_pid(Pid) -> %% ========================================================================== -server_init(Parent, Mod, Active) -> - i("init -> entry with" - "~n Parent: ~p" - "~n Mod: ~p" - "~n Active: ~p", [Parent, Mod, Active]), - case Mod:listen(0) of +server_init(Parent, Transport, Active) -> + ?I("init -> entry with" + "~n Parent: ~p" + "~n Transport: ~p" + "~n Active: ~p", [Parent, Transport, Active]), + {Mod, Listen, StatsInterval} = process_transport(Transport, Active), + case Listen(0) of {ok, LSock} -> case Mod:port(LSock) of {ok, Port} = OK -> Addr = which_addr(), % This is just for convenience - i("init -> listening on:" - "~n Addr: ~p (~s)" - "~n Port: ~w" - "~n", [Addr, inet:ntoa(Addr), Port]), + ?I("init -> listening on:" + "~n Addr: ~p (~s)" + "~n Port: ~w" + "~n", [Addr, inet:ntoa(Addr), Port]), Parent ! {?MODULE, self(), OK}, - server_loop(#{parent => Parent, - mod => Mod, - active => Active, - lsock => LSock, - handlers => [], + server_loop(#{parent => Parent, + mod => Mod, + active => Active, + lsock => LSock, + handlers => [], + stats_interval => StatsInterval, %% Accumulation runtime => 0, mcnt => 0, @@ -107,37 +121,40 @@ server_init(Parent, Mod, Active) -> exit({listen, LReason}) end. +process_transport(Mod, _) when is_atom(Mod) -> + {Mod, fun(Port) -> Mod:listen(Port) end, infinity}; +process_transport({Mod, #{stats_interval := T} = Opts}, Active) + when (Active =/= false) -> + {Mod, fun(Port) -> Mod:listen(Port, Opts#{stats_to => self()}) end, T}; +process_transport({Mod, #{stats_interval := T} = Opts}, _Active) -> + {Mod, fun(Port) -> Mod:listen(Port, Opts) end, T}; +process_transport({Mod, Opts}, _Active) -> + {Mod, fun(Port) -> Mod:listen(Port, Opts) end, infinity}. + + server_loop(State) -> - %% i("loop -> enter"), server_loop( server_handle_message( server_accept(State) ) ). server_accept(#{mod := Mod, active := Active, lsock := LSock, handlers := Handlers} = State) -> - - %% i("server-accept -> entry with" - %% "~n Mod: ~p" - %% "~n Active: ~p" - %% "~n LSock: ~p", [Mod, Active, LSock]), - case Mod:accept(LSock, ?ACC_TIMEOUT) of {ok, Sock} -> - %% i("server-accept -> accepted: " - %% "~n Sock: ~p", [Sock]), - i("accepted connection from ~s", - [case Mod:peername(Sock) of - {ok, Peer} -> - format_peername(Peer); - {error, _} -> - "-" - end]), + ?I("accepted connection from ~s", + [case Mod:peername(Sock) of + {ok, Peer} -> + format_peername(Peer); + {error, _} -> + "-" + end]), {Pid, _} = handler_start(), - i("handler ~p started -> try transfer socket control", [Pid]), + ?I("handler ~p started -> try transfer socket control", [Pid]), case Mod:controlling_process(Sock, Pid) of ok -> - i("server-accept: handler ~p started", [Pid]), + maybe_start_stats_timer(State, Pid), + ?I("server-accept: handler ~p started", [Pid]), handler_continue(Pid, Mod, Sock, Active), Handlers2 = [Pid | Handlers], State#{handlers => Handlers2}; @@ -156,14 +173,31 @@ server_accept(#{mod := Mod, format_peername({Addr, Port}) -> case inet:gethostbyaddr(Addr) of {ok, #hostent{h_name = N}} -> - f("~s (~s:~w)", [N, inet:ntoa(Addr), Port]); + ?F("~s (~s:~w)", [N, inet:ntoa(Addr), Port]); {error, _} -> - f("~p, ~p", [Addr, Port]) + ?F("~p, ~p", [Addr, Port]) end. - + +maybe_start_stats_timer(#{active := Active, stats_interval := Time}, Handler) + when (Active =/= false) andalso (is_integer(Time) andalso (Time > 0)) -> + start_stats_timer(Time, "handler", Handler); +maybe_start_stats_timer(_, _) -> + ok. + +start_stats_timer(Time, ProcStr, Pid) -> + erlang:start_timer(Time, self(), {stats, Time, ProcStr, Pid}). + server_handle_message(#{parent := Parent, handlers := H} = State) -> - %% i("server_handle_message -> enter"), receive + {timeout, _TRef, {stats, Interval, ProcStr, Pid}} -> + case server_handle_stats(ProcStr, Pid) of + ok -> + start_stats_timer(Interval, ProcStr, Pid); + skip -> + ok + end, + State; + {?MODULE, Ref, Parent, stop} -> reply(Parent, Ref, ok), lists:foreach(fun(P) -> handler_stop(P) end, H), @@ -176,11 +210,20 @@ server_handle_message(#{parent := Parent, handlers := H} = State) -> State end. +server_handle_stats(ProcStr, Pid) -> + case ?LIB:formated_process_stats(Pid) of + "" -> + skip; + FormatedStats -> + ?I("Statistics for ~s ~p:~s", [ProcStr, Pid, FormatedStats]), + ok + end. + server_handle_down(Pid, Reason, #{handlers := Handlers} = State) -> case lists:delete(Pid, Handlers) of Handlers -> - i("unknown process ~p died", [Pid]), + ?I("unknown process ~p died", [Pid]), State; Handlers2 -> server_handle_handler_down(Pid, Reason, State#{handlers => Handlers2}) @@ -197,35 +240,35 @@ server_handle_handler_down(Pid, AccMCnt2 = AccMCnt + MCnt, AccBCnt2 = AccBCnt + BCnt, AccHCnt2 = AccHCnt + 1, - i("handler ~p (~w) done => accumulated results: " - "~n Run Time: ~s ms" - "~n Message Count: ~s" - "~n Byte Count: ~s", - [Pid, AccHCnt2, - format_time(AccRunTime2), - if (AccRunTime2 > 0) -> - f("~w => ~w (~w) msgs / ms", - [AccMCnt2, - AccMCnt2 div AccRunTime2, - (AccMCnt2 div AccHCnt2) div AccRunTime2]); - true -> - f("~w", [AccMCnt2]) - end, - if (AccRunTime2 > 0) -> - f("~w => ~w (~w) bytes / ms", - [AccBCnt2, - AccBCnt2 div AccRunTime2, - (AccBCnt2 div AccHCnt2) div AccRunTime2]); - true -> - f("~w", [AccBCnt2]) - end]), + ?I("handler ~p (~w) done => accumulated results: " + "~n Run Time: ~s ms" + "~n Message Count: ~s" + "~n Byte Count: ~s", + [Pid, AccHCnt2, + ?FORMAT_TIME(AccRunTime2), + if (AccRunTime2 > 0) -> + ?F("~w => ~w (~w) msgs / ms", + [AccMCnt2, + AccMCnt2 div AccRunTime2, + (AccMCnt2 div AccHCnt2) div AccRunTime2]); + true -> + ?F("~w", [AccMCnt2]) + end, + if (AccRunTime2 > 0) -> + ?F("~w => ~w (~w) bytes / ms", + [AccBCnt2, + AccBCnt2 div AccRunTime2, + (AccBCnt2 div AccHCnt2) div AccRunTime2]); + true -> + ?F("~w", [AccBCnt2]) + end]), State#{runtime => AccRunTime2, mcnt => AccMCnt2, bcnt => AccBCnt2, hcnt => AccHCnt2}; server_handle_handler_down(Pid, Reason, State) -> - i("handler ~p terminated: " - "~n ~p", [Pid, Reason]), + ?I("handler ~p terminated: " + "~n ~p", [Pid, Reason]), State. @@ -244,32 +287,31 @@ handler_stop(Pid) -> req(Pid, stop). handler_init(Parent) -> - i("starting"), + ?I("starting"), receive {?MODULE, Ref, Parent, {continue, Mod, Sock, Active}} -> - i("received continue"), + ?I("received continue"), reply(Parent, Ref, ok), handler_initial_activation(Mod, Sock, Active), handler_loop(#{parent => Parent, mod => Mod, sock => Sock, active => Active, - start => t(), + start => ?T(), mcnt => 0, bcnt => 0, last_reply => none, acc => <<>>}) after 5000 -> - i("timeout when message queue: " - "~n ~p" - "~nwhen" - "~n Parent: ~p", [process_info(self(), messages), Parent]), + ?I("timeout when message queue: " + "~n ~p" + "~nwhen" + "~n Parent: ~p", [process_info(self(), messages), Parent]), handler_init(Parent) end. handler_loop(State) -> - %% i("handler-loop"), handler_loop( handler_handle_message( handler_recv_message(State) ) ). %% When passive, we read *one* request and then attempt to reply to it. @@ -278,7 +320,6 @@ handler_recv_message(#{mod := Mod, active := false, mcnt := MCnt, bcnt := BCnt} = State) -> - %% i("handler_recv_message(false) -> entry"), case handler_recv_message2(Mod, Sock) of {ok, {MsgSz, ID, Body}} -> handler_send_reply(Mod, Sock, ID, Body), @@ -305,7 +346,6 @@ handler_recv_message(#{mod := Mod, bcnt := BCnt, last_reply := LID, acc := Acc} = State) -> - %% i("handler_recv_message(~w) -> entry", [Active]), case handler_recv_message3(Mod, Sock, Acc, LID) of {ok, {MCnt2, BCnt2, LID2}, NewAcc} -> handler_maybe_activate(Mod, Sock, Active), @@ -319,12 +359,12 @@ handler_recv_message(#{mod := Mod, (size(Acc) =:= 0) -> handler_done(State); true -> - e("client done with partial message: " - "~n Last Reply Sent: ~w" - "~n Message Count: ~w" - "~n Byte Count: ~w" - "~n Partial Message: ~w bytes", - [LID, MCnt, BCnt, size(Acc)]), + ?E("client done with partial message: " + "~n Last Reply Sent: ~w" + "~n Message Count: ~w" + "~n Byte Count: ~w" + "~n Partial Message: ~w bytes", + [LID, MCnt, BCnt, size(Acc)]), exit({closed_with_partial_message, LID}) end; @@ -355,32 +395,27 @@ handler_process_data(Data, _Mod, _Sock, MCnt, BCnt, LID) -> handler_recv_message2(Mod, Sock) -> - %% i("handler_recv_message2 -> entry"), case Mod:recv(Sock, 4*4, ?RECV_TIMEOUT) of {ok, <> = Hdr} -> - %% i("handler_recv_message2 -> got request header: " - %% "~n ID: ~p" - %% "~n SZ: ~p", [ID, SZ]), case Mod:recv(Sock, SZ, ?RECV_TIMEOUT) of {ok, Body} when (SZ =:= size(Body)) -> - %% i("handler_recv_message2 -> got body"), {ok, {size(Hdr) + size(Body), ID, Body}}; {error, BReason} -> - e("failed reading body (~w) of message ~w:" - "~n ~p", [SZ, ID, BReason]), + ?E("failed reading body (~w) of message ~w:" + "~n ~p", [SZ, ID, BReason]), exit({recv, body, ID, SZ, BReason}) end; {error, timeout} = ERROR -> - i("handler_recv_message2 -> timeout"), + ?I("timeout"), ERROR; {error, closed} = ERROR -> ERROR; {error, HReason} -> - e("failed reading header of message:" - "~n ~p", [HReason]), + ?E("Failed reading header of message:" + "~n ~p", [HReason]), exit({recv, header, HReason}) end. @@ -405,116 +440,6 @@ handler_recv_message3(Mod, Sock, Acc, LID) -> -%% %% Socket in passive mode => we need to read explicitly -%% handler_loop(#{sock := Sock, active := false} = State, MCnt, BCnt) -> -%% %% i("try recv msg header"), -%% MsgSz = -%% case gen_tcp:recv(Sock, 4*4) of -%% {ok, <> = Hdr} -> -%% %% i("msg ~w header received (data sz is ~w bytes)", [ID, SZ]), -%% case gen_tcp:recv(Sock, SZ) of -%% {ok, Data} when (size(Data) =:= SZ) -> -%% handler_send_reply(Sock, ID, Data), -%% size(Hdr)+SZ; -%% {ok, InvData} -> -%% i("invalid data"), -%% (catch gen_tcp:close(Sock)), -%% exit({invalid_data, SZ, size(InvData)}); -%% {error, Reason2} -> -%% i("Data read failed: ~p", [Reason2]), -%% (catch gen_tcp:close(Sock)), -%% exit({recv_data, Reason2}) -%% end; -%% {ok, _InvHdr} -> -%% (catch gen_tcp:close(Sock)), -%% exit(invalid_hdr); -%% {error, closed} -> -%% i("we are done: " -%% "~n Message Count: ~p" -%% "~n Byte Count: ~p", [MCnt, BCnt]), -%% exit(normal); -%% {error, Reason1} -> -%% i("Header read failed: ~p", [Reason1]), -%% (catch gen_tcp:close(Sock)), -%% exit({recv_hdr, Reason1}) -%% end, -%% handler_loop(State, MCnt+1, BCnt+MsgSz); - -%% %% Socket in active mode (once | true) => data messages arrive -%% handler_loop(#{sock := Sock, active := Active} = State, MCnt, BCnt) -> -%% %% i("await msg"), -%% try handler_recv_request(Sock, Active) of -%% {ID, Data, MsgSz} -> -%% %% i("msg ~w received (data sz is ~w bytes)", [ID, size(Data)]), -%% handler_send_reply(Sock, ID, Data), -%% handler_maybe_activate(Sock, Active), -%% handler_loop(State, MCnt+1, BCnt+MsgSz) -%% catch -%% throw:tcp_closed -> -%% i("we are done: " -%% "~n Message Count: ~p" -%% "~n Byte Count: ~p", [MCnt, BCnt]), -%% exit(normal); -%% throw:{tcp_error, Reason} -> -%% i(" TCP error ~p when: " -%% "~n Message Count: ~p" -%% "~n Byte Count: ~p", [Reason, MCnt, BCnt]), -%% exit({tcp_error, Reason}) -%% end. -%% %% {ID, Data, MsgSz} = handler_recv_request(Sock, Active), -%% %% i("msg ~w received (data sz is ~w bytes)", [ID, size(Data)]), -%% %% handler_send_reply(Sock, ID, Data), -%% %% handler_maybe_activate(Sock, Active), -%% %% handler_loop(State, MCnt+1, BCnt+MsgSz). - - -%% handler_recv_request(Sock, Active) -> -%% %% In theory we should also be ready for a partial header, -%% %% but I can't be bothered... -%% receive -%% {tcp_closed, Sock} -> -%% throw(tcp_closed); -%% {tcp_error, Sock, Reason} -> -%% throw({tcp_error, Reason}); -%% {tcp, Sock, <> = Msg} when (size(Data) =:= SZ) -> -%% %% i("[complete] msg ~w received (data sz is ~w bytes)", [ID, SZ]), -%% {ID, Data, size(Msg)}; -%% {tcp, Sock, <> = Msg} when (size(Data) < SZ) -> -%% %% i("[incomplete] msg ~w received (data sz is ~w bytes)", [ID, SZ]), -%% handler_recv_request_data(Sock, Active, ID, SZ, Data, size(Msg)) -%% end. - -%% handler_recv_request_data(Sock, Active, ID, SZ, AccData, AccMsgSz) -> -%% handler_maybe_activate(Sock, Active), -%% receive -%% {tcp_closed, Sock} -> -%% %% i("we are done (incomplete data)"), -%% throw(tcp_closed); -%% {tcp_error, Sock, Reason} -> -%% throw({tcp_error, Reason}); -%% {tcp, Sock, Data} when (SZ =:= (size(AccData) + size(Data))) -> -%% %% i("[complete] received the remaining data (~w bytes) for msg ~w", -%% %% [size(Data), ID]), -%% {ID, <>, AccMsgSz+size(Data)}; -%% {tcp, Sock, Data} -> -%% %% i("[incomplete] received ~w bytes of data for for msg ~w", -%% %% [size(Data), ID]), -%% handler_recv_request_data(Sock, Active, ID, SZ, -%% <>, -%% AccMsgSz+size(Data)) -%% end. - handler_send_reply(Mod, Sock, ID, Data) -> SZ = size(Data), Msg = < ID:32, SZ:32, Data/binary>>, - %% i("handler-send-reply -> try send reply ~w: ~w bytes", [ID, size(Msg)]), case Mod:send(Sock, Msg) of ok -> - %% i("handler-send-reply -> reply ~w (~w bytes) sent", [ID, size(Msg)]), ok; {error, Reason} -> (catch Mod:close(Sock)), @@ -534,7 +457,7 @@ handler_send_reply(Mod, Sock, ID, Data) -> handler_done(State) -> - handler_done(State, t()). + handler_done(State, ?T()). handler_done(#{start := Start, mod := Mod, @@ -542,7 +465,7 @@ handler_done(#{start := Start, mcnt := MCnt, bcnt := BCnt}, Stop) -> (catch Mod:close(Sock)), - exit({done, tdiff(Start, Stop), MCnt, BCnt}). + exit({done, ?TDIFF(Start, Stop), MCnt, BCnt}). handler_handle_message(#{parent := Parent} = State) -> @@ -634,45 +557,45 @@ reply(Pid, Ref, Reply) -> %% ========================================================================== -t() -> - os:timestamp(). +%% t() -> +%% os:timestamp(). -tdiff({A1, B1, C1} = _T1x, {A2, B2, C2} = _T2x) -> - T1 = A1*1000000000+B1*1000+(C1 div 1000), - T2 = A2*1000000000+B2*1000+(C2 div 1000), - T2 - T1. +%% tdiff({A1, B1, C1} = _T1x, {A2, B2, C2} = _T2x) -> +%% T1 = A1*1000000000+B1*1000+(C1 div 1000), +%% T2 = A2*1000000000+B2*1000+(C2 div 1000), +%% T2 - T1. -formated_timestamp() -> - format_timestamp(os:timestamp()). +%% formated_timestamp() -> +%% format_timestamp(os:timestamp()). -format_timestamp({_N1, _N2, N3} = TS) -> - {_Date, Time} = calendar:now_to_local_time(TS), - {Hour,Min,Sec} = Time, - FormatTS = io_lib:format("~.2.0w:~.2.0w:~.2.0w.4~w", - [Hour, Min, Sec, round(N3/1000)]), - lists:flatten(FormatTS). +%% format_timestamp({_N1, _N2, N3} = TS) -> +%% {_Date, Time} = calendar:now_to_local_time(TS), +%% {Hour,Min,Sec} = Time, +%% FormatTS = io_lib:format("~.2.0w:~.2.0w:~.2.0w.4~w", +%% [Hour, Min, Sec, round(N3/1000)]), +%% lists:flatten(FormatTS). -%% Time is always in number os ms (milli seconds) -format_time(T) -> - f("~p", [T]). +%% %% Time is always in number os ms (milli seconds) +%% format_time(T) -> +%% f("~p", [T]). %% ========================================================================== -f(F, A) -> - lists:flatten(io_lib:format(F, A)). +%% f(F, A) -> +%% lists:flatten(io_lib:format(F, A)). -e(F, A) -> - p(get(sname), " " ++ F, A). +%% e(F, A) -> +%% p(get(sname), " " ++ F, A). -i(F) -> - i(F, []). +%% i(F) -> +%% i(F, []). -i(F, A) -> - p(get(sname), " " ++ F, A). +%% i(F, A) -> +%% p(get(sname), " " ++ F, A). -p(undefined, F, A) -> - p("- ", F, A); -p(Prefix, F, A) -> - io:format("[~s, ~s] " ++ F ++ "~n", [formated_timestamp(), Prefix |A]). +%% p(undefined, F, A) -> +%% p("- ", F, A); +%% p(Prefix, F, A) -> +%% io:format("[~s, ~s] " ++ F ++ "~n", [formated_timestamp(), Prefix |A]). diff --git a/erts/emulator/test/socket_test_ttest_tcp_server_socket.erl b/erts/emulator/test/socket_test_ttest_tcp_server_socket.erl index de9df857fe..d62f4d86a6 100644 --- a/erts/emulator/test/socket_test_ttest_tcp_server_socket.erl +++ b/erts/emulator/test/socket_test_ttest_tcp_server_socket.erl @@ -21,14 +21,17 @@ -module(socket_test_ttest_tcp_server_socket). -export([ - start_monitor/1, + start_monitor/2, stop/1 ]). -define(TRANSPORT_MOD, socket_test_ttest_tcp_socket). +%% -define(MOD(M), {?TRANSPORT_MOD, #{method => M, +%% stats_interval => 10000}}). +-define(MOD(M), {?TRANSPORT_MOD, #{method => M}}). -start_monitor(Active) -> - socket_test_ttest_tcp_server:start_monitor(?TRANSPORT_MOD, Active). +start_monitor(Method, Active) -> + socket_test_ttest_tcp_server:start_monitor(?MOD(Method), Active). stop(Pid) -> socket_test_ttest_tcp_server:stop(Pid). diff --git a/erts/emulator/test/socket_test_ttest_tcp_socket.erl b/erts/emulator/test/socket_test_ttest_tcp_socket.erl index 12d9e052d7..f314d01a63 100644 --- a/erts/emulator/test/socket_test_ttest_tcp_socket.erl +++ b/erts/emulator/test/socket_test_ttest_tcp_socket.erl @@ -24,9 +24,9 @@ accept/1, accept/2, active/2, close/1, - connect/2, + connect/2, connect/3, controlling_process/2, - listen/0, listen/1, + listen/0, listen/1, listen/2, port/1, peername/1, recv/2, recv/3, @@ -38,25 +38,41 @@ -define(READER_RECV_TIMEOUT, 1000). +-define(DATA_MSG(Sock, Method, Data), + {socket, + #{sock => Sock, reader => self(), method => Method}, + Data}). + +-define(CLOSED_MSG(Sock, Method), + {socket_closed, + #{sock => Sock, reader => self(), method => Method}}). + +-define(ERROR_MSG(Sock, Method, Reason), + {socket_error, + #{sock => Sock, reader => self(), method => Method}, + Reason}). + %% ========================================================================== -accept(#{sock := LSock}) -> +accept(#{sock := LSock, opts := #{method := Method} = Opts}) -> case socket:accept(LSock) of {ok, Sock} -> Self = self(), - Reader = spawn(fun() -> reader_init(Self, Sock, false) end), - {ok, #{sock => Sock, reader => Reader}}; + Reader = spawn(fun() -> reader_init(Self, Sock, false, Method) end), + maybe_start_stats_timer(Opts, Reader), + {ok, #{sock => Sock, reader => Reader, method => Method}}; {error, _} = ERROR -> ERROR end. -accept(#{sock := LSock}, Timeout) -> +accept(#{sock := LSock, opts := #{method := Method} = Opts}, Timeout) -> case socket:accept(LSock, Timeout) of {ok, Sock} -> Self = self(), - Reader = spawn(fun() -> reader_init(Self, Sock, false) end), - {ok, #{sock => Sock, reader => Reader}}; + Reader = spawn(fun() -> reader_init(Self, Sock, false, Method) end), + maybe_start_stats_timer(Opts, Reader), + {ok, #{sock => Sock, reader => Reader, method => Method}}; {error, _} = ERROR -> ERROR end. @@ -74,6 +90,9 @@ close(#{sock := Sock, reader := Pid}) -> %% Create a socket and connect it to a peer connect(Addr, Port) -> + connect(Addr, Port, #{method => plain}). + +connect(Addr, Port, #{method := Method} = Opts) -> try begin Sock = @@ -100,9 +119,10 @@ connect(Addr, Port) -> (catch socket:close(Sock)), throw({error, {connect, CReason}}) end, - Self = self(), - Reader = spawn(fun() -> reader_init(Self, Sock, false) end), - {ok, #{sock => Sock, reader => Reader}} + Self = self(), + Reader = spawn(fun() -> reader_init(Self, Sock, false, Method) end), + maybe_start_stats_timer(Opts, Reader), + {ok, #{sock => Sock, reader => Reader, method => Method}} end catch throw:ERROR:_ -> @@ -110,6 +130,16 @@ connect(Addr, Port) -> end. +maybe_start_stats_timer(#{stats_to := Pid, + stats_interval := T}, + Reader) when is_pid(Pid) -> + erlang:start_timer(T, Pid, {stats, T, "reader", Reader}); +maybe_start_stats_timer(O, _) -> + io:format("NO STATS: " + "~n ~p" + "~n", [O]), + ok. + controlling_process(#{sock := Sock, reader := Pid}, NewPid) -> case socket:setopt(Sock, otp, controlling_process, NewPid) of ok -> @@ -125,9 +155,13 @@ controlling_process(#{sock := Sock, reader := Pid}, NewPid) -> %% Create a listen socket listen() -> - listen(0). + listen(0, #{method => plain}). -listen(Port) when is_integer(Port) andalso (Port >= 0) -> +listen(Port) -> + listen(Port, #{method => plain}). +listen(Port, #{method := Method} = Opts) + when (is_integer(Port) andalso (Port >= 0)) andalso + ((Method =:= plain) orelse (Method =:= msg)) -> try begin Sock = case socket:open(inet, stream, tcp) of @@ -152,7 +186,7 @@ listen(Port) when is_integer(Port) andalso (Port >= 0) -> (catch socket:close(Sock)), throw({error, {listen, LReason}}) end, - {ok, #{sock => Sock}} + {ok, #{sock => Sock, opts => Opts}} end catch throw:{error, Reason}:_ -> @@ -178,14 +212,31 @@ peername(#{sock := Sock}) -> end. -recv(#{sock := Sock}, Length) -> - socket:recv(Sock, Length). -recv(#{sock := Sock}, Length, Timeout) -> - socket:recv(Sock, Length, Timeout). +recv(#{sock := Sock, method := plain}, Length) -> + socket:recv(Sock, Length); +recv(#{sock := Sock, method := msg}, Length) -> + case socket:recvmsg(Sock, Length, 0, [], infinity) of + {ok, #{iov := [Bin]}} -> + {ok, Bin}; + {error, _} = ERROR -> + ERROR + end. + +recv(#{sock := Sock, method := plain}, Length, Timeout) -> + socket:recv(Sock, Length, Timeout); +recv(#{sock := Sock, method := msg}, Length, Timeout) -> + case socket:recvmsg(Sock, Length, 0, [], Timeout) of + {ok, #{iov := [Bin]}} -> + {ok, Bin}; + {error, _} = ERROR -> + ERROR + end. -send(#{sock := Sock}, Length) -> - socket:send(Sock, Length). +send(#{sock := Sock, method := plain}, Bin) -> + socket:send(Sock, Bin); +send(#{sock := Sock, method := msg}, Bin) -> + socket:sendmsg(Sock, #{iov => [Bin]}). shutdown(#{sock := Sock}, How) -> @@ -203,14 +254,16 @@ sockname(#{sock := Sock}) -> %% ========================================================================== -reader_init(ControllingProcess, Sock, Active) +reader_init(ControllingProcess, Sock, Active, Method) when is_pid(ControllingProcess) andalso - (is_boolean(Active) orelse (Active =:= once)) -> + (is_boolean(Active) orelse (Active =:= once)) andalso + ((Method =:= plain) orelse (Method =:= msg)) -> MRef = erlang:monitor(process, ControllingProcess), reader_loop(#{ctrl_proc => ControllingProcess, ctrl_proc_mref => MRef, active => Active, - sock => Sock}). + sock => Sock, + method => Method}). %% Never read @@ -245,10 +298,11 @@ reader_loop(#{active := false, %% Read *once* and then change to false reader_loop(#{active := once, sock := Sock, + method := Method, ctrl_proc := Pid} = State) -> - case socket:recv(Sock, 0, ?READER_RECV_TIMEOUT) of + case do_recv(Method, Sock) of {ok, Data} -> - Pid ! {socket, #{sock => Sock, reader => self()}, Data}, + Pid ! ?DATA_MSG(Sock, Method, Data), reader_loop(State#{active => false}); {error, timeout} -> receive @@ -280,21 +334,22 @@ reader_loop(#{active := once, end; {error, closed} -> - Pid ! {socket_closed, #{sock => Sock, reader => self()}}, + Pid ! ?CLOSED_MSG(Sock, Method), exit(normal); {error, Reason} -> - Pid ! {socket_error, #{sock => Sock, reader => self()}, Reason}, + Pid ! ?ERROR_MSG(Sock, Method, Reason), exit(Reason) end; %% Read and forward data continuously reader_loop(#{active := true, sock := Sock, + method := Method, ctrl_proc := Pid} = State) -> - case socket:recv(Sock, 0, ?READER_RECV_TIMEOUT) of + case do_recv(Method, Sock) of {ok, Data} -> - Pid ! {socket, #{sock => Sock, reader => self()}, Data}, + Pid ! ?DATA_MSG(Sock, Method, Data), reader_loop(State); {error, timeout} -> receive @@ -326,20 +381,26 @@ reader_loop(#{active := true, end; {error, closed} -> - Pid ! {socket_closed, #{sock => Sock, reader => self()}}, + Pid ! ?CLOSED_MSG(Sock, Method), exit(normal); {error, Reason} -> - Pid ! {socket_error, #{sock => Sock, reader => self()}, Reason}, + Pid ! ?ERROR_MSG(Sock, Method, Reason), exit(Reason) end. - +do_recv(plain, Sock) -> + socket:recv(Sock, 0, ?READER_RECV_TIMEOUT); +do_recv(msg, Sock) -> + case socket:recvmsg(Sock, 0, 0, [], ?READER_RECV_TIMEOUT) of + {ok, #{iov := [Bin]}} -> + {ok, Bin}; + {error, _} = ERROR -> + ERROR + end. %% ========================================================================== - - -- cgit v1.2.3 From 342d35f457c15a9cea426e8ca83bfd52b0ec2f2e Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Wed, 12 Dec 2018 18:37:55 +0100 Subject: [socket-nif] Message interface between socket.erl and nif updated Previously the "message interface" between the functions in socket.erl and the nif-code (socket_nif.c) was "ad hoc". This has now been changed so that we have a unified message {'$socket', SockRef | undefined, Tag, Info} This also has the added advantage of preparing the code for when we start using the new select-fucntions (with which its possible to specify your own message). This will be used in order to get around our eterm "leak" (we will use a simple counter, maintained in the nif, instead of the [Recv|Send|Acc]Ref we generate in the erlang code today. OTP-14831 --- erts/emulator/nifs/common/socket_int.h | 3 + erts/emulator/nifs/common/socket_nif.c | 314 ++++++++++++++++++++++----------- erts/preloaded/ebin/socket.beam | Bin 69392 -> 70272 bytes erts/preloaded/src/socket.erl | 34 ++-- 4 files changed, 236 insertions(+), 115 deletions(-) diff --git a/erts/emulator/nifs/common/socket_int.h b/erts/emulator/nifs/common/socket_int.h index ec17e45f25..0f973855ae 100644 --- a/erts/emulator/nifs/common/socket_int.h +++ b/erts/emulator/nifs/common/socket_int.h @@ -103,6 +103,7 @@ typedef unsigned int BOOLEAN_T; /* ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ * "Global" atoms */ +extern ERL_NIF_TERM esock_atom_abort; extern ERL_NIF_TERM esock_atom_accept; extern ERL_NIF_TERM esock_atom_acceptconn; extern ERL_NIF_TERM esock_atom_acceptfilter; @@ -126,6 +127,7 @@ extern ERL_NIF_TERM esock_atom_block_source; extern ERL_NIF_TERM esock_atom_broadcast; extern ERL_NIF_TERM esock_atom_busy_poll; extern ERL_NIF_TERM esock_atom_checksum; +extern ERL_NIF_TERM esock_atom_close; extern ERL_NIF_TERM esock_atom_connect; extern ERL_NIF_TERM esock_atom_congestion; extern ERL_NIF_TERM esock_atom_context; @@ -269,6 +271,7 @@ extern ERL_NIF_TERM esock_atom_sndbufforce; extern ERL_NIF_TERM esock_atom_sndlowat; extern ERL_NIF_TERM esock_atom_sndtimeo; extern ERL_NIF_TERM esock_atom_socket; +extern ERL_NIF_TERM esock_atom_socket_tag; extern ERL_NIF_TERM esock_atom_spec_dst; extern ERL_NIF_TERM esock_atom_status; extern ERL_NIF_TERM esock_atom_stream; diff --git a/erts/emulator/nifs/common/socket_nif.c b/erts/emulator/nifs/common/socket_nif.c index 1e0533535c..80903c487f 100644 --- a/erts/emulator/nifs/common/socket_nif.c +++ b/erts/emulator/nifs/common/socket_nif.c @@ -760,24 +760,24 @@ typedef struct { typedef struct { /* +++ The actual socket +++ */ - SOCKET sock; - HANDLE event; + SOCKET sock; + HANDLE event; /* +++ Stuff "about" the socket +++ */ - int domain; - int type; - int protocol; + int domain; + int type; + int protocol; - unsigned int state; - SocketAddress remote; - unsigned int addrLen; + unsigned int state; + SocketAddress remote; + unsigned int addrLen; - ErlNifEnv* env; + ErlNifEnv* env; /* +++ Controller (owner) process +++ */ - ErlNifPid ctrlPid; - // ErlNifMonitor ctrlMon; - ESockMonitor ctrlMon; + ErlNifPid ctrlPid; + // ErlNifMonitor ctrlMon; + ESockMonitor ctrlMon; /* +++ Write stuff +++ */ ErlNifMutex* writeMtx; @@ -996,11 +996,13 @@ static ERL_NIF_TERM naccept(ErlNifEnv* env, ERL_NIF_TERM ref); static ERL_NIF_TERM nsend(ErlNifEnv* env, SocketDescriptor* descP, + ERL_NIF_TERM sockRef, ERL_NIF_TERM sendRef, ErlNifBinary* dataP, int flags); static ERL_NIF_TERM nsendto(ErlNifEnv* env, SocketDescriptor* descP, + ERL_NIF_TERM sockRef, ERL_NIF_TERM sendRef, ErlNifBinary* dataP, int flags, @@ -1008,21 +1010,25 @@ static ERL_NIF_TERM nsendto(ErlNifEnv* env, unsigned int toAddrLen); static ERL_NIF_TERM nsendmsg(ErlNifEnv* env, SocketDescriptor* descP, + ERL_NIF_TERM sockRef, ERL_NIF_TERM sendRef, ERL_NIF_TERM eMsgHdr, int flags); static ERL_NIF_TERM nrecv(ErlNifEnv* env, SocketDescriptor* descP, + ERL_NIF_TERM sendRef, ERL_NIF_TERM recvRef, int len, int flags); static ERL_NIF_TERM nrecvfrom(ErlNifEnv* env, SocketDescriptor* descP, + ERL_NIF_TERM sockRef, ERL_NIF_TERM recvRef, uint16_t bufSz, int flags); static ERL_NIF_TERM nrecvmsg(ErlNifEnv* env, SocketDescriptor* descP, + ERL_NIF_TERM sockRef, ERL_NIF_TERM recvRef, uint16_t bufLen, uint16_t ctrlLen, @@ -1986,6 +1992,7 @@ static ERL_NIF_TERM send_check_result(ErlNifEnv* env, ssize_t written, ssize_t dataSize, int saveErrno, + ERL_NIF_TERM sockRef, ERL_NIF_TERM sendRef); static BOOLEAN_T recv_check_reader(ErlNifEnv* env, SocketDescriptor* descP, @@ -1998,6 +2005,7 @@ static ERL_NIF_TERM recv_update_current_reader(ErlNifEnv* env, SocketDescriptor* descP); static void recv_error_current_reader(ErlNifEnv* env, SocketDescriptor* descP, + ERL_NIF_TERM sockRef, ERL_NIF_TERM reason); static ERL_NIF_TERM recv_check_result(ErlNifEnv* env, SocketDescriptor* descP, @@ -2005,6 +2013,7 @@ static ERL_NIF_TERM recv_check_result(ErlNifEnv* env, int toRead, int saveErrno, ErlNifBinary* bufP, + ERL_NIF_TERM sockRef, ERL_NIF_TERM recvRef); static ERL_NIF_TERM recvfrom_check_result(ErlNifEnv* env, SocketDescriptor* descP, @@ -2013,6 +2022,7 @@ static ERL_NIF_TERM recvfrom_check_result(ErlNifEnv* env, ErlNifBinary* bufP, SocketAddress* fromAddrP, unsigned int fromAddrLen, + ERL_NIF_TERM sockRef, ERL_NIF_TERM recvRef); static ERL_NIF_TERM recvmsg_check_result(ErlNifEnv* env, SocketDescriptor* descP, @@ -2021,6 +2031,7 @@ static ERL_NIF_TERM recvmsg_check_result(ErlNifEnv* env, struct msghdr* msgHdrP, ErlNifBinary* dataBufP, ErlNifBinary* ctrlBufP, + ERL_NIF_TERM sockRef, ERL_NIF_TERM recvRef); static ERL_NIF_TERM nfinalize_connection(ErlNifEnv* env, @@ -2310,22 +2321,22 @@ static void socket_down_reader(ErlNifEnv* env, SocketDescriptor* descP, const ErlNifPid* pid); -/* -static char* send_msg_error_closed(ErlNifEnv* env, +static char* esock_send_close_msg(ErlNifEnv* env, + ERL_NIF_TERM closeRef, + ErlNifPid* pid); +static char* esock_send_abort_msg(ErlNifEnv* env, + ERL_NIF_TERM sockRef, + ERL_NIF_TERM recvRef, + ERL_NIF_TERM reason, + ErlNifPid* pid); +static char* esock_send_socket_msg(ErlNifEnv* env, + ERL_NIF_TERM sockRef, + ERL_NIF_TERM tag, + ERL_NIF_TERM info, ErlNifPid* pid); -*/ -/* -static char* send_msg_error(ErlNifEnv* env, - ERL_NIF_TERM reason, +static char* esock_send_msg(ErlNifEnv* env, + ERL_NIF_TERM msg, ErlNifPid* pid); -*/ -static char* send_msg_nif_abort(ErlNifEnv* env, - ERL_NIF_TERM ref, - ERL_NIF_TERM reason, - ErlNifPid* pid); -static char* send_msg(ErlNifEnv* env, - ERL_NIF_TERM msg, - ErlNifPid* pid); static BOOLEAN_T extract_debug(ErlNifEnv* env, ERL_NIF_TERM map); @@ -2394,7 +2405,7 @@ static char str_max_rxt[] = "max_rxt"; static char str_min[] = "min"; static char str_mode[] = "mode"; static char str_multiaddr[] = "multiaddr"; -static char str_nif_abort[] = "nif_abort"; +// static char str_nif_abort[] = "nif_abort"; static char str_null[] = "null"; static char str_num_dlocal[] = "num_domain_local"; static char str_num_dinet[] = "num_domain_inet"; @@ -2436,6 +2447,7 @@ static char str_exsend[] = "exsend"; // failed send /* *** "Global" Atoms *** */ +ERL_NIF_TERM esock_atom_abort; ERL_NIF_TERM esock_atom_accept; ERL_NIF_TERM esock_atom_acceptconn; ERL_NIF_TERM esock_atom_acceptfilter; @@ -2459,6 +2471,7 @@ ERL_NIF_TERM esock_atom_block_source; ERL_NIF_TERM esock_atom_broadcast; ERL_NIF_TERM esock_atom_busy_poll; ERL_NIF_TERM esock_atom_checksum; +ERL_NIF_TERM esock_atom_close; ERL_NIF_TERM esock_atom_connect; ERL_NIF_TERM esock_atom_congestion; ERL_NIF_TERM esock_atom_context; @@ -2598,6 +2611,7 @@ ERL_NIF_TERM esock_atom_seqpacket; ERL_NIF_TERM esock_atom_setfib; ERL_NIF_TERM esock_atom_set_peer_primary_addr; ERL_NIF_TERM esock_atom_socket; +ERL_NIF_TERM esock_atom_socket_tag; ERL_NIF_TERM esock_atom_sndbuf; ERL_NIF_TERM esock_atom_sndbufforce; ERL_NIF_TERM esock_atom_sndlowat; @@ -2665,7 +2679,7 @@ static ERL_NIF_TERM atom_max_rxt; static ERL_NIF_TERM atom_min; static ERL_NIF_TERM atom_mode; static ERL_NIF_TERM atom_multiaddr; -static ERL_NIF_TERM atom_nif_abort; +// static ERL_NIF_TERM atom_nif_abort; static ERL_NIF_TERM atom_null; static ERL_NIF_TERM atom_num_dinet; static ERL_NIF_TERM atom_num_dinet6; @@ -4929,7 +4943,7 @@ ERL_NIF_TERM naccept_listening(ErlNifEnv* env, accDescP->wCtrlSz = descP->wCtrlSz; // Inherit buffer size accRef = enif_make_resource(env, accDescP); - enif_release_resource(accDescP); // We should really store a reference ... + enif_release_resource(accDescP); accDescP->ctrlPid = caller; if (MONP("naccept_listening -> ctrl", @@ -5143,7 +5157,7 @@ ERL_NIF_TERM nif_send(ErlNifEnv* env, const ERL_NIF_TERM argv[]) { SocketDescriptor* descP; - ERL_NIF_TERM sendRef; + ERL_NIF_TERM sockRef, sendRef; ErlNifBinary sndData; unsigned int eflags; int flags; @@ -5154,13 +5168,17 @@ ERL_NIF_TERM nif_send(ErlNifEnv* env, /* Extract arguments and perform preliminary validation */ if ((argc != 4) || - !enif_get_resource(env, argv[0], sockets, (void**) &descP) || !GET_BIN(env, argv[2], &sndData) || !GET_UINT(env, argv[3], &eflags)) { return enif_make_badarg(env); } + sockRef = argv[0]; // We need this in case we send in case we send abort sendRef = argv[1]; + if (!enif_get_resource(env, sockRef, sockets, (void**) &descP)) { + return enif_make_badarg(env); + } + if (IS_CLOSED(descP) || IS_CLOSING(descP)) return esock_make_error(env, atom_closed); @@ -5170,7 +5188,7 @@ ERL_NIF_TERM nif_send(ErlNifEnv* env, "\r\n SendRef: %T" "\r\n Size of data: %d" "\r\n eFlags: %d" - "\r\n", descP->sock, argv[0], sendRef, sndData.size, eflags) ); + "\r\n", descP->sock, sockRef, sendRef, sndData.size, eflags) ); if (!IS_CONNECTED(descP)) return esock_make_error(env, atom_enotconn); @@ -5193,7 +5211,7 @@ ERL_NIF_TERM nif_send(ErlNifEnv* env, * time we do. */ - res = nsend(env, descP, sendRef, &sndData, flags); + res = nsend(env, descP, sockRef, sendRef, &sndData, flags); MUNLOCK(descP->writeMtx); @@ -5211,6 +5229,7 @@ ERL_NIF_TERM nif_send(ErlNifEnv* env, static ERL_NIF_TERM nsend(ErlNifEnv* env, SocketDescriptor* descP, + ERL_NIF_TERM sockRef, ERL_NIF_TERM sendRef, ErlNifBinary* sndDataP, int flags) @@ -5238,7 +5257,8 @@ ERL_NIF_TERM nsend(ErlNifEnv* env, save_errno = -1; // The value does not actually matter in this case return send_check_result(env, descP, - written, sndDataP->size, save_errno, sendRef); + written, sndDataP->size, save_errno, + sockRef, sendRef); } @@ -5264,7 +5284,7 @@ ERL_NIF_TERM nif_sendto(ErlNifEnv* env, const ERL_NIF_TERM argv[]) { SocketDescriptor* descP; - ERL_NIF_TERM sendRef; + ERL_NIF_TERM sockRef, sendRef; ErlNifBinary sndData; unsigned int eflags; int flags; @@ -5284,9 +5304,14 @@ ERL_NIF_TERM nif_sendto(ErlNifEnv* env, !GET_UINT(env, argv[4], &eflags)) { return enif_make_badarg(env); } + sockRef = argv[0]; // We need this in case we send in case we send abort sendRef = argv[1]; eSockAddr = argv[3]; + if (!enif_get_resource(env, sockRef, sockets, (void**) &descP)) { + return enif_make_badarg(env); + } + if (IS_CLOSED(descP) || IS_CLOSING(descP)) return esock_make_error(env, atom_closed); @@ -5298,7 +5323,7 @@ ERL_NIF_TERM nif_sendto(ErlNifEnv* env, "\r\n eSockAddr: %T" "\r\n eflags: %d" "\r\n", - descP->sock, argv[0], sendRef, sndData.size, eSockAddr, eflags) ); + descP->sock, sockRef, sendRef, sndData.size, eSockAddr, eflags) ); /* THIS TEST IS NOT CORRECT!!! */ if (!IS_OPEN(descP)) { @@ -5320,7 +5345,7 @@ ERL_NIF_TERM nif_sendto(ErlNifEnv* env, MLOCK(descP->writeMtx); - res = nsendto(env, descP, sendRef, &sndData, flags, + res = nsendto(env, descP, sockRef, sendRef, &sndData, flags, &remoteAddr, remoteAddrLen); MUNLOCK(descP->writeMtx); @@ -5336,6 +5361,7 @@ ERL_NIF_TERM nif_sendto(ErlNifEnv* env, static ERL_NIF_TERM nsendto(ErlNifEnv* env, SocketDescriptor* descP, + ERL_NIF_TERM sockRef, ERL_NIF_TERM sendRef, ErlNifBinary* dataP, int flags, @@ -5372,7 +5398,8 @@ ERL_NIF_TERM nsendto(ErlNifEnv* env, else save_errno = -1; // The value does not actually matter in this case - return send_check_result(env, descP, written, dataP->size, save_errno, sendRef); + return send_check_result(env, descP, written, dataP->size, save_errno, + sockRef, sendRef); } @@ -5395,7 +5422,7 @@ ERL_NIF_TERM nif_sendmsg(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { - ERL_NIF_TERM res, sendRef, eMsgHdr; + ERL_NIF_TERM res, sockRef, sendRef, eMsgHdr; SocketDescriptor* descP; unsigned int eflags; int flags; @@ -5405,14 +5432,18 @@ ERL_NIF_TERM nif_sendmsg(ErlNifEnv* env, /* Extract arguments and perform preliminary validation */ if ((argc != 4) || - !enif_get_resource(env, argv[0], sockets, (void**) &descP) || !IS_MAP(env, argv[2]) || !GET_UINT(env, argv[3], &eflags)) { return enif_make_badarg(env); } + sockRef = argv[0]; // We need this in case we send in case we send abort sendRef = argv[1]; eMsgHdr = argv[2]; + if (!enif_get_resource(env, sockRef, sockets, (void**) &descP)) { + return enif_make_badarg(env); + } + if (IS_CLOSED(descP) || IS_CLOSING(descP)) return esock_make_error(env, atom_closed); @@ -5433,7 +5464,7 @@ ERL_NIF_TERM nif_sendmsg(ErlNifEnv* env, MLOCK(descP->writeMtx); - res = nsendmsg(env, descP, sendRef, eMsgHdr, flags); + res = nsendmsg(env, descP, sockRef, sendRef, eMsgHdr, flags); MUNLOCK(descP->writeMtx); @@ -5449,6 +5480,7 @@ ERL_NIF_TERM nif_sendmsg(ErlNifEnv* env, static ERL_NIF_TERM nsendmsg(ErlNifEnv* env, SocketDescriptor* descP, + ERL_NIF_TERM sockRef, ERL_NIF_TERM sendRef, ERL_NIF_TERM eMsgHdr, int flags) @@ -5589,7 +5621,8 @@ ERL_NIF_TERM nsendmsg(ErlNifEnv* env, else save_errno = -1; // OK or not complete: this value should not matter in this case - res = send_check_result(env, descP, written, dataSize, save_errno, sendRef); + res = send_check_result(env, descP, written, dataSize, save_errno, + sockRef, sendRef); FREE(iovBins); FREE(iov); @@ -5691,20 +5724,24 @@ ERL_NIF_TERM nif_recv(ErlNifEnv* env, const ERL_NIF_TERM argv[]) { SocketDescriptor* descP; - ERL_NIF_TERM recvRef; + ERL_NIF_TERM sockRef, recvRef; int len; unsigned int eflags; int flags; ERL_NIF_TERM res; if ((argc != 4) || - !enif_get_resource(env, argv[0], sockets, (void**) &descP) || !GET_INT(env, argv[2], &len) || !GET_UINT(env, argv[3], &eflags)) { return enif_make_badarg(env); } + sockRef = argv[0]; // We need this in case we send in case we send abort recvRef = argv[1]; + if (!enif_get_resource(env, sockRef, sockets, (void**) &descP)) { + return enif_make_badarg(env); + } + if (IS_CLOSED(descP) || IS_CLOSING(descP)) return esock_make_error(env, atom_closed); @@ -5729,7 +5766,7 @@ ERL_NIF_TERM nif_recv(ErlNifEnv* env, * time we do. */ - res = nrecv(env, descP, recvRef, len, flags); + res = nrecv(env, descP, sockRef, recvRef, len, flags); MUNLOCK(descP->readMtx); @@ -5746,6 +5783,7 @@ ERL_NIF_TERM nif_recv(ErlNifEnv* env, static ERL_NIF_TERM nrecv(ErlNifEnv* env, SocketDescriptor* descP, + ERL_NIF_TERM sockRef, ERL_NIF_TERM recvRef, int len, int flags) @@ -5794,6 +5832,7 @@ ERL_NIF_TERM nrecv(ErlNifEnv* env, read, len, save_errno, &buf, + sockRef, recvRef); } @@ -5829,7 +5868,7 @@ ERL_NIF_TERM nif_recvfrom(ErlNifEnv* env, const ERL_NIF_TERM argv[]) { SocketDescriptor* descP; - ERL_NIF_TERM recvRef; + ERL_NIF_TERM sockRef, recvRef; unsigned int bufSz; unsigned int eflags; int flags; @@ -5840,12 +5879,16 @@ ERL_NIF_TERM nif_recvfrom(ErlNifEnv* env, /* Extract arguments and perform preliminary validation */ if ((argc != 4) || - !enif_get_resource(env, argv[0], sockets, (void**) &descP) || !GET_UINT(env, argv[2], &bufSz) || !GET_UINT(env, argv[3], &eflags)) { return enif_make_badarg(env); } - recvRef = argv[1]; + sockRef = argv[0]; // We need this in case we send in case we send abort + recvRef = argv[1]; + + if (!enif_get_resource(env, sockRef, sockets, (void**) &descP)) { + return enif_make_badarg(env); + } if (IS_CLOSED(descP) || IS_CLOSING(descP)) return esock_make_error(env, atom_closed); @@ -5883,7 +5926,7 @@ ERL_NIF_TERM nif_recvfrom(ErlNifEnv* env, * */ - res = nrecvfrom(env, descP, recvRef, bufSz, flags); + res = nrecvfrom(env, descP, sockRef, recvRef, bufSz, flags); MUNLOCK(descP->readMtx); @@ -5900,6 +5943,7 @@ ERL_NIF_TERM nif_recvfrom(ErlNifEnv* env, static ERL_NIF_TERM nrecvfrom(ErlNifEnv* env, SocketDescriptor* descP, + ERL_NIF_TERM sockRef, ERL_NIF_TERM recvRef, uint16_t len, int flags) @@ -5951,6 +5995,7 @@ ERL_NIF_TERM nrecvfrom(ErlNifEnv* env, save_errno, &buf, &fromAddr, addrLen, + sockRef, recvRef); } @@ -5990,7 +6035,7 @@ ERL_NIF_TERM nif_recvmsg(ErlNifEnv* env, const ERL_NIF_TERM argv[]) { SocketDescriptor* descP; - ERL_NIF_TERM recvRef; + ERL_NIF_TERM sockRef, recvRef; unsigned int bufSz; unsigned int ctrlSz; unsigned int eflags; @@ -6002,14 +6047,18 @@ ERL_NIF_TERM nif_recvmsg(ErlNifEnv* env, /* Extract arguments and perform preliminary validation */ if ((argc != 5) || - !enif_get_resource(env, argv[0], sockets, (void**) &descP) || !GET_UINT(env, argv[2], &bufSz) || !GET_UINT(env, argv[3], &ctrlSz) || !GET_UINT(env, argv[4], &eflags)) { return enif_make_badarg(env); } - recvRef = argv[1]; + sockRef = argv[0]; // We need this in case we send in case we send abort + recvRef = argv[1]; + if (!enif_get_resource(env, sockRef, sockets, (void**) &descP)) { + return enif_make_badarg(env); + } + if (IS_CLOSED(descP) || IS_CLOSING(descP)) return esock_make_error(env, atom_closed); @@ -6047,7 +6096,7 @@ ERL_NIF_TERM nif_recvmsg(ErlNifEnv* env, * */ - res = nrecvmsg(env, descP, recvRef, bufSz, ctrlSz, flags); + res = nrecvmsg(env, descP, sockRef, recvRef, bufSz, ctrlSz, flags); MUNLOCK(descP->readMtx); @@ -6064,6 +6113,7 @@ ERL_NIF_TERM nif_recvmsg(ErlNifEnv* env, static ERL_NIF_TERM nrecvmsg(ErlNifEnv* env, SocketDescriptor* descP, + ERL_NIF_TERM sockRef, ERL_NIF_TERM recvRef, uint16_t bufLen, uint16_t ctrlLen, @@ -6144,6 +6194,7 @@ ERL_NIF_TERM nrecvmsg(ErlNifEnv* env, &msgHdr, data, // Needed for iov encode &ctrl, // Needed for ctrl header encode + sockRef, recvRef); } @@ -13254,6 +13305,7 @@ ERL_NIF_TERM send_check_result(ErlNifEnv* env, ssize_t written, ssize_t dataSize, int saveErrno, + ERL_NIF_TERM sockRef, ERL_NIF_TERM sendRef) { SSDBG( descP, @@ -13330,7 +13382,7 @@ ERL_NIF_TERM send_check_result(ErlNifEnv* env, while (writer_pop(env, descP, &pid, &mon, &ref)) { SSDBG( descP, ("SOCKET", "send_check_result -> abort %T\r\n", pid) ); - send_msg_nif_abort(env, ref, res, &pid); + esock_send_abort_msg(env, sockRef, ref, res, &pid); DEMONP("send_check_result -> pop'ed writer", env, descP, &mon); } @@ -13527,6 +13579,7 @@ ERL_NIF_TERM recv_update_current_reader(ErlNifEnv* env, static void recv_error_current_reader(ErlNifEnv* env, SocketDescriptor* descP, + ERL_NIF_TERM sockRef, ERL_NIF_TERM reason) { if (descP->currentReaderP != NULL) { @@ -13541,7 +13594,7 @@ void recv_error_current_reader(ErlNifEnv* env, while (reader_pop(env, descP, &pid, &mon, &ref)) { SSDBG( descP, ("SOCKET", "recv_error_current_reader -> abort %T\r\n", pid) ); - send_msg_nif_abort(env, ref, reason, &pid); + esock_send_abort_msg(env, sockRef, ref, reason, &pid); DEMONP("recv_error_current_reader -> pop'ed reader", env, descP, &mon); } @@ -13561,6 +13614,7 @@ ERL_NIF_TERM recv_check_result(ErlNifEnv* env, int toRead, int saveErrno, ErlNifBinary* bufP, + ERL_NIF_TERM sockRef, ERL_NIF_TERM recvRef) { char* xres; @@ -13595,7 +13649,7 @@ ERL_NIF_TERM recv_check_result(ErlNifEnv* env, * We must also notify any waiting readers! */ - recv_error_current_reader(env, descP, res); + recv_error_current_reader(env, descP, sockRef, res); FREE_BIN(bufP); @@ -13713,7 +13767,7 @@ ERL_NIF_TERM recv_check_result(ErlNifEnv* env, descP->closeLocal = FALSE; descP->state = SOCKET_STATE_CLOSING; - recv_error_current_reader(env, descP, res); + recv_error_current_reader(env, descP, sockRef, res); SELECT(env, descP->sock, @@ -13751,7 +13805,7 @@ ERL_NIF_TERM recv_check_result(ErlNifEnv* env, SSDBG( descP, ("SOCKET", "recv_check_result -> [%d] errno: %d\r\n", toRead, saveErrno) ); - recv_error_current_reader(env, descP, res); + recv_error_current_reader(env, descP, sockRef, res); FREE_BIN(bufP); @@ -13835,6 +13889,7 @@ ERL_NIF_TERM recvfrom_check_result(ErlNifEnv* env, ErlNifBinary* bufP, SocketAddress* fromAddrP, unsigned int fromAddrLen, + ERL_NIF_TERM sockRef, ERL_NIF_TERM recvRef) { char* xres; @@ -13876,7 +13931,7 @@ ERL_NIF_TERM recvfrom_check_result(ErlNifEnv* env, descP->closeLocal = FALSE; descP->state = SOCKET_STATE_CLOSING; - recv_error_current_reader(env, descP, res); + recv_error_current_reader(env, descP, sockRef, res); SELECT(env, descP->sock, @@ -13909,7 +13964,7 @@ ERL_NIF_TERM recvfrom_check_result(ErlNifEnv* env, ("SOCKET", "recvfrom_check_result -> errno: %d\r\n", saveErrno) ); - recv_error_current_reader(env, descP, res); + recv_error_current_reader(env, descP, sockRef, res); FREE_BIN(bufP); @@ -13962,6 +14017,7 @@ ERL_NIF_TERM recvmsg_check_result(ErlNifEnv* env, struct msghdr* msgHdrP, ErlNifBinary* dataBufP, ErlNifBinary* ctrlBufP, + ERL_NIF_TERM sockRef, ERL_NIF_TERM recvRef) { @@ -14026,7 +14082,7 @@ ERL_NIF_TERM recvmsg_check_result(ErlNifEnv* env, descP->closeLocal = FALSE; descP->state = SOCKET_STATE_CLOSING; - recv_error_current_reader(env, descP, res); + recv_error_current_reader(env, descP, sockRef, res); SELECT(env, descP->sock, @@ -14060,7 +14116,7 @@ ERL_NIF_TERM recvmsg_check_result(ErlNifEnv* env, ("SOCKET", "recvmsg_check_result -> errno: %d\r\n", saveErrno) ); - recv_error_current_reader(env, descP, res); + recv_error_current_reader(env, descP, sockRef, res); FREE_BIN(dataBufP); FREE_BIN(ctrlBufP); @@ -16350,37 +16406,89 @@ char* send_msg_error(ErlNifEnv* env, */ -/* Send an (nif-) abort message to the specified process: +/* Send an close message to the specified process: * A message in the form: * - * {nif_abort, Ref, Reason} + * {'$socket', SockRef, close, CloseRef} * - * This message is for processes that are waiting in the + * This message is for processes that is waiting in the + * erlang API (close-) function for the socket to be "closed" + * (actually that the 'stop' callback function has been called). + */ +static +char* esock_send_close_msg(ErlNifEnv* env, + ERL_NIF_TERM closeRef, + ErlNifPid* pid) +{ + return esock_send_socket_msg(env, + esock_atom_undefined, + esock_atom_close, closeRef, pid); +} + + +/* Send an abort message to the specified process: + * A message in the form: + * + * {'$socket', SockRef, abort, {RecvRef, Reason}} + * + * This message is for processes that is waiting in the * erlang API functions for a select message. */ static -char* send_msg_nif_abort(ErlNifEnv* env, - ERL_NIF_TERM ref, - ERL_NIF_TERM reason, - ErlNifPid* pid) +char* esock_send_abort_msg(ErlNifEnv* env, + ERL_NIF_TERM sockRef, + ERL_NIF_TERM recvRef, + ERL_NIF_TERM reason, + ErlNifPid* pid) { - ERL_NIF_TERM msg = MKT3(env, atom_nif_abort, ref, reason); + ERL_NIF_TERM info = MKT2(env, recvRef, reason); - return send_msg(env, msg, pid); + /* + esock_dbg_printf("SEND MSG", + "try send abort message to %T:\r\n", + "\r\n sockRef: %T" + "\r\n recvRef: %T" + "\r\n reason: %T" + "\r\n", *pid, sockRef, recvRef, reason); + */ + + return esock_send_socket_msg(env, sockRef, esock_atom_abort, info, pid); +} + + +/* *** esock_send_socket_msg *** + * + * This function sends a general purpose socket message to an erlang + * process. A general 'socket' message has the form: + * + * {'$socket', SockRef, Tag, Info} + * + */ + +static +char* esock_send_socket_msg(ErlNifEnv* env, + ERL_NIF_TERM sockRef, + ERL_NIF_TERM tag, + ERL_NIF_TERM info, + ErlNifPid* pid) +{ + ERL_NIF_TERM msg = MKT4(env, esock_atom_socket_tag, sockRef, tag, info); + + return esock_send_msg(env, msg, pid); } /* Send a message to the specified process. */ static -char* send_msg(ErlNifEnv* env, - ERL_NIF_TERM msg, - ErlNifPid* pid) +char* esock_send_msg(ErlNifEnv* env, + ERL_NIF_TERM msg, + ErlNifPid* pid) { - if (!enif_send(env, pid, NULL, msg)) - return str_exsend; - else - return NULL; + if (!enif_send(env, pid, NULL, msg)) + return str_exsend; + else + return NULL; } @@ -17022,10 +17130,11 @@ void socket_stop(ErlNifEnv* env, void* obj, int fd, int is_direct_call) SSDBG( descP, ("SOCKET", "socket_stop -> " "send abort message to current writer %T\r\n", descP->currentWriter.pid) ); - if (send_msg_nif_abort(env, - descP->currentWriter.ref, - atom_closed, - &descP->currentWriter.pid) != NULL) { + if (esock_send_abort_msg(env, + esock_atom_undefined, + descP->currentWriter.ref, + atom_closed, + &descP->currentWriter.pid) != NULL) { /* Shall we really do this? * This happens if the controlling process has been killed! */ @@ -17061,10 +17170,11 @@ void socket_stop(ErlNifEnv* env, void* obj, int fd, int is_direct_call) SSDBG( descP, ("SOCKET", "socket_stop -> " "send abort message to current reader %T\r\n", descP->currentReader.pid) ); - if (send_msg_nif_abort(env, - descP->currentReader.ref, - atom_closed, - &descP->currentReader.pid) != NULL) { + if (esock_send_abort_msg(env, + esock_atom_undefined, + descP->currentReader.ref, + atom_closed, + &descP->currentReader.pid) != NULL) { /* Shall we really do this? * This happens if the controlling process has been killed! */ @@ -17099,10 +17209,11 @@ void socket_stop(ErlNifEnv* env, void* obj, int fd, int is_direct_call) SSDBG( descP, ("SOCKET", "socket_stop -> " "send abort message to current acceptor %T\r\n", descP->currentWriter.pid) ); - if (send_msg_nif_abort(env, - descP->currentAcceptor.ref, - atom_closed, - &descP->currentAcceptor.pid) != NULL) { + if (esock_send_abort_msg(env, + esock_atom_undefined, + descP->currentAcceptor.ref, + atom_closed, + &descP->currentAcceptor.pid) != NULL) { /* Shall we really do this? * This happens if the controlling process has been killed! */ @@ -17145,13 +17256,12 @@ void socket_stop(ErlNifEnv* env, void* obj, int fd, int is_direct_call) * Also, we should really *always* use a tag unique to this * (nif-) module. Some like (in this case): * - * {'$socket', close, CloseRef} + * {'$socket', undefined, close, CloseRef} * * */ - - send_msg(env, - MKT2(env, atom_close, descP->closeRef), &descP->closerPid); + + esock_send_close_msg(env, descP->closeRef, &descP->closerPid); DEMONP("socket_stop -> closer", env, descP, &descP->closerMon); @@ -17221,13 +17331,14 @@ void inform_waiting_procs(ErlNifEnv* env, */ SSDBG( descP, - ("SOCKET", "inform_waiting_procs -> abort %T (%T)\r\n", + ("SOCKET", "inform_waiting_procs -> abort request %T (from %T)\r\n", currentP->data.ref, currentP->data.pid) ); - ESOCK_ASSERT( (NULL == send_msg_nif_abort(env, - currentP->data.ref, - reason, - ¤tP->data.pid)) ); + ESOCK_ASSERT( (NULL == esock_send_abort_msg(env, + esock_atom_undefined, + currentP->data.ref, + reason, + ¤tP->data.pid)) ); DEMONP("inform_waiting_procs -> current 'request'", env, descP, ¤tP->data.mon); @@ -17783,7 +17894,7 @@ int on_load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info) atom_min = MKA(env, str_min); atom_mode = MKA(env, str_mode); atom_multiaddr = MKA(env, str_multiaddr); - atom_nif_abort = MKA(env, str_nif_abort); + // atom_nif_abort = MKA(env, str_nif_abort); atom_null = MKA(env, str_null); atom_num_dinet = MKA(env, str_num_dinet); atom_num_dinet6 = MKA(env, str_num_dinet6); @@ -17813,6 +17924,7 @@ int on_load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info) atom_want = MKA(env, str_want); /* Global atom(s) */ + esock_atom_abort = MKA(env, "abort"); esock_atom_accept = MKA(env, "accept"); esock_atom_acceptconn = MKA(env, "acceptconn"); esock_atom_acceptfilter = MKA(env, "acceptfilter"); @@ -17836,6 +17948,7 @@ int on_load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info) esock_atom_broadcast = MKA(env, "broadcast"); esock_atom_busy_poll = MKA(env, "busy_poll"); esock_atom_checksum = MKA(env, "checksum"); + esock_atom_close = MKA(env, "close"); esock_atom_connect = MKA(env, "connect"); esock_atom_congestion = MKA(env, "congestion"); esock_atom_context = MKA(env, "context"); @@ -17979,6 +18092,7 @@ int on_load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info) esock_atom_sndlowat = MKA(env, "sndlowat"); esock_atom_sndtimeo = MKA(env, "sndtimeo"); esock_atom_socket = MKA(env, "socket"); + esock_atom_socket_tag = MKA(env, "$socket"); esock_atom_spec_dst = MKA(env, "spec_dst"); esock_atom_status = MKA(env, "status"); esock_atom_stream = MKA(env, "stream"); diff --git a/erts/preloaded/ebin/socket.beam b/erts/preloaded/ebin/socket.beam index 25046e6aad..ddd50fdefa 100644 Binary files a/erts/preloaded/ebin/socket.beam and b/erts/preloaded/ebin/socket.beam differ diff --git a/erts/preloaded/src/socket.erl b/erts/preloaded/src/socket.erl index a40692881b..2e295a91ae 100644 --- a/erts/preloaded/src/socket.erl +++ b/erts/preloaded/src/socket.erl @@ -1331,7 +1331,7 @@ do_accept(LSockRef, Timeout) -> {select, LSockRef, AccRef, ready_input} -> do_accept(LSockRef, next_timeout(TS, Timeout)); - {nif_abort, AccRef, Reason} -> + {'$socket', _, abort, {AccRef, Reason}} -> {error, Reason} after NewTimeout -> @@ -1408,7 +1408,7 @@ do_send(SockRef, Data, EFlags, Timeout) -> do_send(SockRef, Data, EFlags, next_timeout(TS, Timeout)); - {nif_abort, SendRef, Reason} -> + {'$socket', _, abort, {SendRef, Reason}} -> {error, Reason} after NewTimeout -> @@ -1421,7 +1421,7 @@ do_send(SockRef, Data, EFlags, Timeout) -> do_send(SockRef, Data, EFlags, next_timeout(TS, Timeout)); - {nif_abort, SendRef, Reason} -> + {'$socket', _, abort, {SendRef, Reason}} -> {error, Reason} after Timeout -> @@ -1513,7 +1513,7 @@ do_sendto(SockRef, Data, Dest, EFlags, Timeout) -> do_sendto(SockRef, Data, Dest, EFlags, next_timeout(TS, Timeout)); - {nif_abort, SendRef, Reason} -> + {'$socket', _, abort, {SendRef, Reason}} -> {error, Reason} after Timeout -> @@ -1525,7 +1525,11 @@ do_sendto(SockRef, Data, Dest, EFlags, Timeout) -> receive {select, SockRef, SendRef, ready_output} -> do_sendto(SockRef, Data, Dest, EFlags, - next_timeout(TS, Timeout)) + next_timeout(TS, Timeout)); + + {'$socket', _, abort, {SendRef, Reason}} -> + {error, Reason} + after Timeout -> cancel(SockRef, sendto, SendRef), {error, timeout} @@ -1773,10 +1777,9 @@ do_recv(SockRef, _OldRef, Length, EFlags, Acc, Timeout) Bin, next_timeout(TS, Timeout)); - {nif_abort, RecvRef, Reason} -> + {'$socket', _, abort, {RecvRef, Reason}} -> {error, Reason} - after NewTimeout -> cancel(SockRef, recv, RecvRef), {error, {timeout, Acc}} @@ -1794,10 +1797,9 @@ do_recv(SockRef, _OldRef, Length, EFlags, Acc, Timeout) <>, next_timeout(TS, Timeout)); - {nif_abort, RecvRef, Reason} -> + {'$socket', _, abort, {RecvRef, Reason}} -> {error, Reason} - after NewTimeout -> cancel(SockRef, recv, RecvRef), {error, {timeout, Acc}} @@ -1805,6 +1807,7 @@ do_recv(SockRef, _OldRef, Length, EFlags, Acc, Timeout) %% We return with the accumulated binary (if its non-empty) {error, eagain} when (Length =:= 0) andalso (size(Acc) > 0) -> + %% CAN WE REALLY DO THIS? THE NIF HAS SELECTED!! OR? {ok, Acc}; {error, eagain} -> @@ -1819,7 +1822,7 @@ do_recv(SockRef, _OldRef, Length, EFlags, Acc, Timeout) Acc, next_timeout(TS, Timeout)); - {nif_abort, RecvRef, Reason} -> + {'$socket', _, abort, {RecvRef, Reason}} -> {error, Reason} after NewTimeout -> @@ -1850,7 +1853,8 @@ do_recv(SockRef, RecvRef, 0 = _Length, _Eflags, Acc, _Timeout) -> %% any waiting reader. cancel(SockRef, recv, RecvRef), {ok, Acc}; -do_recv(_SockRef, _RecvRef, _Length, _EFlags, Acc, _Timeout) when (size(Acc) > 0) -> +do_recv(_SockRef, _RecvRef, _Length, _EFlags, Acc, _Timeout) + when (size(Acc) > 0) -> {error, {timeout, Acc}}; do_recv(_SockRef, _RecvRef, _Length, _EFlags, _Acc, _Timeout) -> {error, timeout}. @@ -1957,8 +1961,7 @@ do_recvfrom(SockRef, BufSz, EFlags, Timeout) -> do_recvfrom(SockRef, BufSz, EFlags, next_timeout(TS, Timeout)); - {nif_abort, RecvRef, Reason} -> - %% p("received nif-abort: ~p", [Reason]), + {'$socket', _, abort, {RecvRef, Reason}} -> {error, Reason} after NewTimeout -> @@ -2062,7 +2065,7 @@ do_recvmsg(SockRef, BufSz, CtrlSz, EFlags, Timeout) -> do_recvmsg(SockRef, BufSz, CtrlSz, EFlags, next_timeout(TS, Timeout)); - {nif_abort, RecvRef, Reason} -> + {'$socket', _, abort, {RecvRef, Reason}} -> {error, Reason} after NewTimeout -> @@ -2107,7 +2110,8 @@ do_close(SockRef) -> {ok, CloseRef} -> %% We must wait receive - {close, CloseRef} -> + {'$socket', _, close, CloseRef} -> +%% {close, CloseRef} -> %% %% %% WHAT HAPPENS IF THIS PROCESS IS KILLED -- cgit v1.2.3 From 73eae9c9c3e458d6d6cffb0cdbfb6ce80a340be1 Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Wed, 12 Dec 2018 18:39:34 +0100 Subject: [socket-nif|test] Correct the (api) connect timeout test case Assumed the wrong success value ({ok, _} instead of ok) for the api-to-connect-tcp test case. OTP-14831 --- erts/emulator/test/socket_SUITE.erl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erts/emulator/test/socket_SUITE.erl b/erts/emulator/test/socket_SUITE.erl index 71e32f8e95..779cc80ad6 100644 --- a/erts/emulator/test/socket_SUITE.erl +++ b/erts/emulator/test/socket_SUITE.erl @@ -1911,7 +1911,7 @@ api_to_connect_tcp(InitState) -> client := Client} = _State) -> case ?SEV_AWAIT_READY(Client, client, connect, [{server, Server}]) of - {ok, _} -> + ok -> ok; {error, _} = ERROR -> ERROR @@ -3670,6 +3670,7 @@ sc_lc_receive_response_tcp(InitState) -> end}, #{desc => "close the connection socket", cmd => fun(#{csock := Sock} = State) -> + %% ok = socket:setopt(Sock, otp, debug, true), case socket:close(Sock) of ok -> {ok, maps:remove(csock, State)}; -- cgit v1.2.3 From aed92219aacf962a9d6c71ea448809fe2c9acae6 Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Thu, 13 Dec 2018 16:38:52 +0100 Subject: [socket-nif|test] Add a "global" logger Added a global logger that make it possible to log from the slave nodes (with "ease"). Also "fixed" the test case that failed on "older" linux (Ubuntu 14.04). For now we let it skip instead (we should really check the OS version). Also corrected a couple of (ping-pong) cases for which the buffer adjustments did not work. OTP-14831 --- erts/emulator/test/Makefile | 1 + erts/emulator/test/socket_SUITE.erl | 198 +++++++++++++-------------- erts/emulator/test/socket_test_evaluator.erl | 10 +- erts/emulator/test/socket_test_lib.erl | 10 +- erts/emulator/test/socket_test_logger.erl | 99 ++++++++++++++ 5 files changed, 208 insertions(+), 110 deletions(-) create mode 100644 erts/emulator/test/socket_test_logger.erl diff --git a/erts/emulator/test/Makefile b/erts/emulator/test/Makefile index 09bfe6f104..300270a10e 100644 --- a/erts/emulator/test/Makefile +++ b/erts/emulator/test/Makefile @@ -33,6 +33,7 @@ SOCKET_MODULES = \ socket_server \ socket_client \ socket_test_lib \ + socket_test_logger \ socket_test_evaluator \ socket_test_ttest_lib \ socket_test_ttest_tcp_gen \ diff --git a/erts/emulator/test/socket_SUITE.erl b/erts/emulator/test/socket_SUITE.erl index 779cc80ad6..57f562f24d 100644 --- a/erts/emulator/test/socket_SUITE.erl +++ b/erts/emulator/test/socket_SUITE.erl @@ -149,6 +149,7 @@ -define(TT(T), ct:timetrap(T)). -define(LIB, socket_test_lib). +-define(LOGGER, socket_test_logger). -define(TPP_SMALL, lists:seq(1, 8)). -define(TPP_MEDIUM, lists:flatten(lists:duplicate(1024, ?TPP_SMALL))). @@ -335,15 +336,19 @@ traffic_cases() -> %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% init_per_suite(Config) -> + ?LOGGER:start(), Config. end_per_suite(_) -> + ?LOGGER:stop(), ok. init_per_testcase(_TC, Config) -> + ?LOGGER:start(), Config. end_per_testcase(_TC, Config) -> + ?LOGGER:stop(), Config. @@ -1798,6 +1803,10 @@ api_to_connect_tcp(InitState) -> [{tester, Tester}]) of {ok, ok = _Result} -> {ok, maps:remove(connect_limit, State)}; + {ok, {error, {connect_limit_reached,R,L}}} -> + {skip, + ?LIB:f("Connect limit reached ~w: ~w", + [L, R])}; {ok, Result} -> Result; {error, _} = ERROR -> @@ -1968,25 +1977,25 @@ api_to_connect_tcp(InitState) -> api_toc_tcp_client_start(Node) -> Self = self(), - GL = group_leader(), - Fun = fun() -> api_toc_tcp_client(Self, GL) end, + Fun = fun() -> api_toc_tcp_client(Self) end, erlang:spawn(Node, Fun). -api_toc_tcp_client(Parent, GL) -> - api_toc_tcp_client_init(Parent, GL), +api_toc_tcp_client(Parent) -> + api_toc_tcp_client_init(Parent), ServerSA = api_toc_tcp_client_await_start(Parent), Domain = maps:get(family, ServerSA), api_toc_tcp_client_announce_ready(Parent, init), {To, ConLimit} = api_toc_tcp_client_await_continue(Parent, connect), Result = api_to_connect_tcp_await_timeout(To, ServerSA, Domain, ConLimit), + ?SEV_IPRINT("result: ~p", [Result]), api_toc_tcp_client_announce_ready(Parent, connect, Result), Reason = api_toc_tcp_client_await_terminate(Parent), exit(Reason). -api_toc_tcp_client_init(Parent, GL) -> +api_toc_tcp_client_init(Parent) -> + put(sname, "rclient"), %% i("api_toc_tcp_client_init -> entry"), _MRef = erlang:monitor(process, Parent), - group_leader(self(), GL), ok. api_toc_tcp_client_await_start(Parent) -> @@ -3710,7 +3719,8 @@ sc_lc_receive_response_tcp(InitState) -> ?SEV_FINISH_NORMAL ], - %% The point of this is to perform the recv for which we are testing the reponse + %% The point of this is to perform the recv for which + %% we are testing the reponse. HandlerSeq = [ %% *** Wait for start order part *** @@ -3758,7 +3768,8 @@ sc_lc_receive_response_tcp(InitState) -> ?SEV_EPRINT("Unexpected data received"), {error, unexpected_success}; {error, closed} -> - ?SEV_IPRINT("received expected 'closed' result"), + ?SEV_IPRINT("received expected 'closed' " + "result"), State1 = maps:remove(sock, State), {ok, State1}; {error, Reason} = ERROR -> @@ -5806,13 +5817,12 @@ sc_rc_receive_response_tcp(InitState) -> sc_rc_tcp_client_start(Node) -> Self = self(), - GL = group_leader(), - Fun = fun() -> sc_rc_tcp_client(Self, GL) end, + Fun = fun() -> sc_rc_tcp_client(Self) end, erlang:spawn(Node, Fun). -sc_rc_tcp_client(Parent, GL) -> - sc_rc_tcp_client_init(Parent, GL), +sc_rc_tcp_client(Parent) -> + sc_rc_tcp_client_init(Parent), ServerSA = sc_rc_tcp_client_await_start(Parent), Domain = maps:get(family, ServerSA), Sock = sc_rc_tcp_client_create(Domain), @@ -5825,12 +5835,13 @@ sc_rc_tcp_client(Parent, GL) -> sc_rc_tcp_client_close(Sock), sc_rc_tcp_client_announce_ready(Parent, close), Reason = sc_rc_tcp_client_await_terminate(Parent), + ?SEV_IPRINT("terminate"), exit(Reason). -sc_rc_tcp_client_init(Parent, GL) -> - i("sc_rc_tcp_client_init -> entry"), +sc_rc_tcp_client_init(Parent) -> + put(sname, "rclient"), + ?SEV_IPRINT("init"), _MRef = erlang:monitor(process, Parent), - group_leader(self(), GL), ok. sc_rc_tcp_client_await_start(Parent) -> @@ -6633,13 +6644,12 @@ sc_rs_send_shutdown_receive_tcp(InitState) -> sc_rs_tcp_client_start(Node, Send) -> Self = self(), - GL = group_leader(), - Fun = fun() -> sc_rs_tcp_client(Self, Send, GL) end, + Fun = fun() -> sc_rs_tcp_client(Self, Send) end, erlang:spawn(Node, Fun). -sc_rs_tcp_client(Parent, Send, GL) -> - sc_rs_tcp_client_init(Parent, GL), +sc_rs_tcp_client(Parent, Send) -> + sc_rs_tcp_client_init(Parent), ServerSA = sc_rs_tcp_client_await_start(Parent), Domain = maps:get(family, ServerSA), Sock = sc_rs_tcp_client_create(Domain), @@ -6658,12 +6668,13 @@ sc_rs_tcp_client(Parent, Send, GL) -> sc_rs_tcp_client_close(Sock), sc_rs_tcp_client_announce_ready(Parent, close), Reason = sc_rs_tcp_client_await_terminate(Parent), + ?SEV_IPRINT("terminate"), exit(Reason). -sc_rs_tcp_client_init(Parent, GL) -> - i("sc_rs_tcp_client_init -> entry"), +sc_rs_tcp_client_init(Parent) -> + put(sname, "rclient"), + ?SEV_IPRINT("init"), _MRef = erlang:monitor(process, Parent), - group_leader(self(), GL), ok. sc_rs_tcp_client_await_start(Parent) -> @@ -7782,12 +7793,11 @@ traffic_send_and_recv_chunks_tcp(InitState) -> traffic_snr_tcp_client_start(Node) -> Self = self(), - GL = group_leader(), - Fun = fun() -> traffic_snr_tcp_client(Self, GL) end, + Fun = fun() -> traffic_snr_tcp_client(Self) end, erlang:spawn(Node, Fun). -traffic_snr_tcp_client(Parent, GL) -> - {Sock, ServerSA} = traffic_snr_tcp_client_init(Parent, GL), +traffic_snr_tcp_client(Parent) -> + {Sock, ServerSA} = traffic_snr_tcp_client_init(Parent), traffic_snr_tcp_client_announce_ready(Parent, init), traffic_snr_tcp_client_await_continue(Parent, connect), traffic_snr_tcp_client_connect(Sock, ServerSA), @@ -7816,10 +7826,10 @@ traffic_snr_tcp_client_send_loop(Parent, Sock) -> exit({await_continue, Reason}) end. -traffic_snr_tcp_client_init(Parent, GL) -> - i("traffic_snr_tcp_client_init -> entry"), +traffic_snr_tcp_client_init(Parent) -> + put(sname, "rclient"), + ?SEV_IPRINT("init"), _MRef = erlang:monitor(process, Parent), - group_leader(self(), GL), ServerSA = traffic_snr_tcp_client_await_start(Parent), Domain = maps:get(family, ServerSA), Sock = traffic_snr_tcp_client_create(Domain), @@ -8146,8 +8156,9 @@ traffic_ping_pong_medium_sendto_and_recvfrom_udp6(_Config) when is_list(_Config) Num = ?TPP_MEDIUM_NUM, tc_try(traffic_ping_pong_medium_sendto_and_recvfrom_udp6, fun() -> + not_yet_implemented(), ?TT(?SECS(45)), - InitState = #{domain => inet, + InitState = #{domain => inet6, msg => Msg, num => Num}, ok = traffic_ping_pong_sendto_and_recvfrom_udp(InitState) @@ -8201,7 +8212,7 @@ traffic_ping_pong_small_sendmsg_and_recvmsg_tcp6(_Config) when is_list(_Config) fun() -> not_yet_implemented(), ?TT(?SECS(20)), - InitState = #{domain => inet, + InitState = #{domain => inet6, msg => Msg, num => Num}, ok = traffic_ping_pong_sendmsg_and_recvmsg_tcp(InitState) @@ -8471,6 +8482,9 @@ traffic_ping_pong_send_and_receive_tcp(#{msg := Msg} = InitState) -> true -> ok end, + + + ok = socket:setopt(Sock, otp, rcvbuf, 8*1024) end, traffic_ping_pong_send_and_receive_tcp2(InitState#{buf_init => Fun}). @@ -9109,12 +9123,11 @@ tpp_tcp_handler_msg_exchange_loop(Sock, Send, Recv, N, Sent, Received, Start) -> tpp_tcp_client_create(Node) -> Self = self(), - GL = group_leader(), - Fun = fun() -> tpp_tcp_client(Self, GL) end, + Fun = fun() -> tpp_tcp_client(Self) end, erlang:spawn(Node, Fun). -tpp_tcp_client(Parent, GL) -> - tpp_tcp_client_init(Parent, GL), +tpp_tcp_client(Parent) -> + tpp_tcp_client_init(Parent), {ServerSA, BufInit, Send, Recv} = tpp_tcp_client_await_start(Parent), Domain = maps:get(family, ServerSA), Sock = tpp_tcp_client_sock_open(Domain, BufInit), @@ -9131,11 +9144,10 @@ tpp_tcp_client(Parent, GL) -> ?SEV_IPRINT("terminating"), exit(Reason). -tpp_tcp_client_init(Parent, GL) -> +tpp_tcp_client_init(Parent) -> put(sname, "rclient"), ?SEV_IPRINT("init"), _MRef = erlang:monitor(process, Parent), - group_leader(self(), GL), ok. tpp_tcp_client_await_start(Parent) -> @@ -9362,20 +9374,23 @@ traffic_ping_pong_sendmsg_and_recvmsg_udp(InitState) -> traffic_ping_pong_send_and_receive_udp(#{msg := Msg} = InitState) -> Fun = fun(Sock) -> {ok, RcvSz} = socket:getopt(Sock, socket, rcvbuf), - if (RcvSz < size(Msg)) -> + if (RcvSz =< (8+size(Msg))) -> + i("adjust socket rcvbuf buffer size"), ok = socket:setopt(Sock, socket, rcvbuf, 1024+size(Msg)); true -> ok end, {ok, SndSz} = socket:getopt(Sock, socket, sndbuf), - if (SndSz < size(Msg)) -> + if (SndSz =< (8+size(Msg))) -> + i("adjust socket sndbuf buffer size"), ok = socket:setopt(Sock, socket, sndbuf, 1024+size(Msg)); true -> ok end, {ok, OtpRcvBuf} = socket:getopt(Sock, otp, rcvbuf), if - (OtpRcvBuf < size(Msg)) -> + (OtpRcvBuf =< (8+size(Msg))) -> + i("adjust otp rcvbuf buffer size"), ok = socket:setopt(Sock, otp, rcvbuf, 1024+size(Msg)); true -> ok @@ -9837,6 +9852,7 @@ traffic_ping_pong_send_and_receive_udp2(InitState) -> ?SEV_FINISH_NORMAL ], + i("start server evaluator"), ServerInitState = #{domain => maps:get(domain, InitState), recv => maps:get(recv, InitState), @@ -9884,7 +9900,8 @@ tpp_udp_server_handler_init(Parent) -> ok. tpp_udp_server_handler_msg_exchange(Sock, Send, Recv) -> - tpp_udp_server_handler_msg_exchange_loop(Sock, Send, Recv, 0, 0, 0, undefined). + tpp_udp_server_handler_msg_exchange_loop(Sock, Send, Recv, + 0, 0, 0, undefined). tpp_udp_server_handler_msg_exchange_loop(Sock, Send, Recv, N, Sent, Received, Start) -> @@ -9920,7 +9937,8 @@ tpp_udp_server_handler_msg_exchange_loop(Sock, Send, Recv, %% exit({'ping-send', Reason, N}) %% end; {error, closed} -> - ?SEV_IPRINT("closed - we are done: ~w, ~w, ~w", [N, Sent, Received]), + ?SEV_IPRINT("closed - we are done: ~w, ~w, ~w", + [N, Sent, Received]), Stop = ?LIB:timestamp(), {N, Sent, Received, Start, Stop}; {error, RReason} -> @@ -9933,36 +9951,45 @@ tpp_udp_server_handler_msg_exchange_loop(Sock, Send, Recv, tpp_udp_client_handler_create(Node) -> Self = self(), - GL = group_leader(), - Fun = fun() -> tpp_udp_client_handler(Self, GL) end, + Fun = fun() -> put(sname, "chandler"), tpp_udp_client_handler(Self) end, erlang:spawn(Node, Fun). -tpp_udp_client_handler(Parent, GL) -> - tpp_udp_client_handler_init(Parent, GL), +tpp_udp_client_handler(Parent) -> + tpp_udp_client_handler_init(Parent), + ?SEV_IPRINT("await start command"), {ServerSA, BufInit, Send, Recv} = tpp_udp_handler_await_start(Parent), + ?SEV_IPRINT("start command with" + "~n ServerSA: ~p", [ServerSA]), Domain = maps:get(family, ServerSA), Sock = tpp_udp_sock_open(Domain, BufInit), tpp_udp_sock_bind(Sock, Domain), + ?SEV_IPRINT("announce ready", []), tpp_udp_handler_announce_ready(Parent, init), {InitMsg, Num} = tpp_udp_handler_await_continue(Parent, send), + ?SEV_IPRINT("received continue with" + "~n Num: ~p", [Num]), Result = tpp_udp_client_handler_msg_exchange(Sock, ServerSA, Send, Recv, InitMsg, Num), + ?SEV_IPRINT("ready"), tpp_udp_handler_announce_ready(Parent, send, Result), + ?SEV_IPRINT("await terminate"), Reason = tpp_udp_handler_await_terminate(Parent), + ?SEV_IPRINT("terminate with ~p", [Reason]), tpp_udp_sock_close(Sock), ?SEV_IPRINT("terminating"), exit(Reason). -tpp_udp_client_handler_init(Parent, GL) -> +tpp_udp_client_handler_init(Parent) -> put(sname, "chandler"), ?SEV_IPRINT("init"), _MRef = erlang:monitor(process, Parent), - group_leader(self(), GL), ok. -tpp_udp_client_handler_msg_exchange(Sock, ServerSA, Send, Recv, InitMsg, Num) -> +tpp_udp_client_handler_msg_exchange(Sock, ServerSA, + Send, Recv, InitMsg, Num) -> Start = ?LIB:timestamp(), - tpp_udp_client_handler_msg_exchange_loop(Sock, ServerSA, Send, Recv, InitMsg, + tpp_udp_client_handler_msg_exchange_loop(Sock, ServerSA, + Send, Recv, InitMsg, Num, 0, 0, 0, Start). tpp_udp_client_handler_msg_exchange_loop(_Sock, _Dest, _Send, _Recv, _Msg, @@ -9970,16 +9997,14 @@ tpp_udp_client_handler_msg_exchange_loop(_Sock, _Dest, _Send, _Recv, _Msg, Start) -> Stop = ?LIB:timestamp(), {Sent, Received, Start, Stop}; -tpp_udp_client_handler_msg_exchange_loop(Sock, Dest, Send, Recv, Data, +tpp_udp_client_handler_msg_exchange_loop(Sock, Dest, Send, Recv, Data, Num, N, Sent, Received, Start) -> - %% d("tpp_udp_client_handler_msg_exchange_loop(~w,~w) try send", [Num,N]), case tpp_udp_send_req(Sock, Send, Data, Dest) of {ok, SendSz} -> - %% d("tpp_tcp_client_msg_exchange_loop(~w,~w) sent - " - %% "now try recv", [Num,N]), case tpp_udp_recv_rep(Sock, Recv) of {ok, NewData, RecvSz, Dest} -> - tpp_udp_client_handler_msg_exchange_loop(Sock, Dest, Send, Recv, + tpp_udp_client_handler_msg_exchange_loop(Sock, Dest, + Send, Recv, NewData, Num, N+1, Sent+SendSz, Received+RecvSz, @@ -10009,55 +10034,17 @@ tpp_udp_recv(Sock, Recv, Tag) -> %% "~n Source: ~p" %% "~n Tag: ~p" %% "~n Sz: ~p" - %% "~n size(Data): ~p", [Source, Tag, Sz, size(Data)]), + %% "~n size(Data): ~p", + %% [Source, Tag, Sz, size(Data)]), {ok, Data, size(Msg), Source}; - {ok, {Source, <> = Msg}} -> - %% ?SEV_IPRINT("tpp_udp_recv -> got part: " - %% "~n Source: ~p" - %% "~n Tag: ~p" - %% "~n Sz: ~p" - %% "~n size(Data): ~p", [Source, Tag, Sz, size(Data)]), - Remains = Sz - size(Data), - tpp_tcp_recv(Sock, Source, Recv, Tag, Remains, size(Msg), [Data]); + {ok, {_Source, <>}} -> + {error, {invalid_msg, Sz, size(Data)}}; {ok, {_, <>}} -> {error, {invalid_msg_tag, Tag}}; {error, _} = ERROR -> ERROR end. -%% We match against Source since we only communicate with one peer -tpp_tcp_recv(Sock, Source, Recv, Tag, Remaining, AccSz, Acc) -> - %% ?SEV_IPRINT("tpp_tcp_recv -> entry with" - %% "~n Tag: ~p" - %% "~n Remaining: ~p" - %% "~n AccSz: ~p" - %% "~n RcvBuf: ~p" - %% "~n SndBuf: ~p", - %% [Tag, Remaining, AccSz, - %% socket:getopt(Sock, socket, rcvbuf), - %% socket:getopt(Sock, socket, sndbuf)]), - case Recv(Sock, Remaining) of - {ok, {Source, Data}} when (Remaining =:= size(Data)) -> - %% ?SEV_IPRINT("tpp_udp_recv -> got rest: " - %% "~n Source: ~p" - %% "~n size(Data): ~p", [Source, size(Data)]), - %% We got the rest - TotSz = AccSz + size(Data), - {ok, - erlang:iolist_to_binary(lists:reverse([Data | Acc])), - TotSz, Source}; - {ok, {Source, Data}} when (Remaining > size(Data)) -> - %% ?SEV_IPRINT("tpp_udp_recv -> got part of rest: " - %% "~n Source: ~p" - %% "~n size(Data): ~p", [Source, size(Data)]), - tpp_tcp_recv(Sock, Source, Recv, Tag, - Remaining - size(Data), AccSz + size(Data), - [Data | Acc]); - {error, _} = ERROR -> - ERROR - end. - - tpp_udp_send_req(Sock, Send, Data, Dest) -> tpp_udp_send(Sock, Send, ?TPP_REQUEST, Data, Dest). @@ -10070,15 +10057,6 @@ tpp_udp_send(Sock, Send, Tag, Data, Dest) -> tpp_udp_send_msg(Sock, Send, Msg, Dest, 0). tpp_udp_send_msg(Sock, Send, Msg, Dest, AccSz) when is_binary(Msg) -> - %% d("tpp_udp_send_msg -> entry with" - %% "~n size(Msg): ~p" - %% "~n Dest: ~p" - %% "~n AccSz: ~p" - %% "~n RcvBuf: ~p" - %% "~n SndBuf: ~p", - %% [size(Msg), Dest, AccSz, - %% socket:getopt(Sock, socket, rcvbuf), - %% socket:getopt(Sock, socket, sndbuf)]), case Send(Sock, Msg, Dest) of ok -> {ok, AccSz+size(Msg)}; @@ -10162,6 +10140,14 @@ start_node(Host, NodeName) -> UniqueNodeName = f("~w_~w", [NodeName, erlang:system_time(millisecond)]), case do_start_node(Host, UniqueNodeName) of {ok, _} = OK -> + global:sync(), + %% i("Node ~p started: " + %% "~n Nodes: ~p" + %% "~n Logger: ~p" + %% "~n Global Names: ~p", + %% [NodeName, nodes(), + %% global:whereis_name(socket_test_logger), + %% global:registered_names()]), OK; {error, Reason, _} -> {error, Reason} diff --git a/erts/emulator/test/socket_test_evaluator.erl b/erts/emulator/test/socket_test_evaluator.erl index e8755a9512..48fb6a027e 100644 --- a/erts/emulator/test/socket_test_evaluator.erl +++ b/erts/emulator/test/socket_test_evaluator.erl @@ -76,6 +76,7 @@ %% ============================================================================ -define(LIB, socket_test_lib). +-define(LOGGER, socket_test_logger). -define(EXTRA_NOTHING, '$nothing'). -define(ANNOUNCEMENT_START, '$start'). @@ -122,6 +123,8 @@ loop(ID, [#{desc := Desc, loop(ID + 1, Cmds, State); {ok, NewState} -> loop(ID + 1, Cmds, NewState); + {skip, Reason} -> + exit({skip, Reason}); {error, Reason} -> eprint("command ~w failed: " "~n Reason: ~p", [ID, Reason]), @@ -160,6 +163,8 @@ await_finish(Evs, Fails) -> iprint("unknown process ~p died (normal)", [Pid]), await_finish(Evs, Fails) end; + {'DOWN', _MRef, process, Pid, {skip, Reason}} -> + ?LIB:skip(Reason); {'DOWN', _MRef, process, Pid, Reason} -> case lists:keysearch(Pid, #ev.pid, Evs) of {value, #ev{name = Name}} -> @@ -486,6 +491,5 @@ print(Prefix, F, A) -> SName -> f("[~s][~p]", [SName, self()]) end, - FStr = f("[~s]~s ~s" ++ F, [?LIB:formated_timestamp(), IDStr, Prefix | A]), - io:format(user, FStr ++ "~n", []), - io:format(FStr, []). + ?LOGGER:format("[~s]~s ~s" ++ F, + [?LIB:formated_timestamp(), IDStr, Prefix | A]). diff --git a/erts/emulator/test/socket_test_lib.erl b/erts/emulator/test/socket_test_lib.erl index c36cc4fbfa..f55f338ef9 100644 --- a/erts/emulator/test/socket_test_lib.erl +++ b/erts/emulator/test/socket_test_lib.erl @@ -27,13 +27,15 @@ formated_timestamp/0, format_timestamp/1, + %% String and format + f/2, + %% Skipping not_yet_implemented/0, skip/1 ]). - %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% timestamp() -> @@ -60,6 +62,12 @@ format_timestamp({_N1, _N2, _N3} = TS) -> lists:flatten(FormatTS). +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +f(F, A) -> + lists:flatten(io_lib:format(F, A)). + + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% not_yet_implemented() -> diff --git a/erts/emulator/test/socket_test_logger.erl b/erts/emulator/test/socket_test_logger.erl new file mode 100644 index 0000000000..5996bbe855 --- /dev/null +++ b/erts/emulator/test/socket_test_logger.erl @@ -0,0 +1,99 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2018-2018. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% + +-module(socket_test_logger). + +-export([ + start/0, + stop/0, + format/2 + ]). + + +-define(LIB, socket_test_lib). +-define(LOGGER, ?MODULE). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +start() -> + case global:whereis_name(?LOGGER) of + Pid when is_pid(Pid) -> + ok; + undefined -> + Self = self(), + Pid = spawn_link(fun() -> init(Self) end), + yes = global:register_name(?LOGGER, Pid), + ok + end. + + +stop() -> + case global:whereis_name(?LOGGER) of + undefined -> + ok; + Pid when is_pid(Pid) -> + global:unregister_name(?LOGGER), + Pid ! {?LOGGER, '$logger', stop}, + ok + end. + + +format(F, []) -> + do_format(F); +format(F, A) -> + do_format(?LIB:f(F, A)). + +do_format(Msg) -> + case global:whereis_name(?LOGGER) of + undefined -> + ok; + Pid when is_pid(Pid) -> + Pid ! {?MODULE, '$logger', {msg, Msg}}, + ok + end. + +init(Parent) -> + put(sname, "logger"), + print("[~s][logger] starting~n", [?LIB:formated_timestamp()]), + loop(#{parent => Parent}). + +loop(#{parent := Parent} = State) -> + receive + {'EXIT', Parent, _} -> + print("[~s][logger] parent exit~n", [?LIB:formated_timestamp()]), + exit(normal); + + {?MODULE, '$logger', stop} -> + print("[~s][logger] stopping~n", [?LIB:formated_timestamp()]), + exit(normal); + + {?MODULE, '$logger', {msg, Msg}} -> + print(Msg), + loop(State) + end. + + +print(F, A) -> + print(?LIB:f(F, A)). + +print(Str) -> + io:format(user, Str ++ "~n", []), + io:format(Str, []). -- cgit v1.2.3 From 6781913e975e93a4a29d14e14794aae4526de9f7 Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Fri, 14 Dec 2018 19:14:43 +0100 Subject: [socket-nif|test] Add test case based on the ttest modules Added a (first) test case based on the ttest modules. OTP-14831 --- erts/emulator/test/socket_SUITE.erl | 574 ++++++++++++++++++++- erts/emulator/test/socket_test_evaluator.erl | 19 + erts/emulator/test/socket_test_lib.erl | 18 + erts/emulator/test/socket_test_logger.erl | 16 +- .../emulator/test/socket_test_ttest_tcp_client.erl | 182 +++++-- .../test/socket_test_ttest_tcp_client_gen.erl | 21 +- .../test/socket_test_ttest_tcp_client_socket.erl | 22 +- .../emulator/test/socket_test_ttest_tcp_server.erl | 80 ++- .../test/socket_test_ttest_tcp_server_gen.erl | 11 +- .../test/socket_test_ttest_tcp_server_socket.erl | 10 +- .../emulator/test/socket_test_ttest_tcp_socket.erl | 5 +- 11 files changed, 844 insertions(+), 114 deletions(-) diff --git a/erts/emulator/test/socket_SUITE.erl b/erts/emulator/test/socket_SUITE.erl index 57f562f24d..72fea0a40a 100644 --- a/erts/emulator/test/socket_SUITE.erl +++ b/erts/emulator/test/socket_SUITE.erl @@ -121,7 +121,17 @@ traffic_ping_pong_small_sendmsg_and_recvmsg_udp4/1, traffic_ping_pong_small_sendmsg_and_recvmsg_udp6/1, traffic_ping_pong_medium_sendmsg_and_recvmsg_udp4/1, - traffic_ping_pong_medium_sendmsg_and_recvmsg_udp6/1 + traffic_ping_pong_medium_sendmsg_and_recvmsg_udp6/1, + + %% Time Test + ttest_sgenf_cgenf_small_tcp4/1, + ttest_sgenf_cgenf_small_tcp6/1%% , + + %% ttest_sgenf_cgenf_medium_tcp4/1, + %% ttest_sgenf_cgenf_medium_tcp6/1, + + %% ttest_sgenf_cgenf_large_tcp4/1, + %% ttest_sgenf_cgenf_large_tcp6/1 %% Tickets ]). @@ -148,8 +158,9 @@ -define(TT(T), ct:timetrap(T)). --define(LIB, socket_test_lib). --define(LOGGER, socket_test_logger). +-define(LIB, socket_test_lib). +-define(TTEST_LIB, socket_test_ttest_lib). +-define(LOGGER, socket_test_logger). -define(TPP_SMALL, lists:seq(1, 8)). -define(TPP_MEDIUM, lists:flatten(lists:duplicate(1024, ?TPP_SMALL))). @@ -170,7 +181,8 @@ all() -> [ {group, api}, {group, socket_closure}, - {group, traffic} + {group, traffic}, + {group, ttest} %% {group, tickets} ]. @@ -184,7 +196,8 @@ groups() -> {sc_local_close, [], sc_lc_cases()}, {sc_remote_close, [], sc_rc_cases()}, {sc_remote_shutdown, [], sc_rs_cases()}, - {traffic, [], traffic_cases()} + {traffic, [], traffic_cases()}, + {ttest, [], ttest_cases()} %% {tickets, [], ticket_cases()} ]. @@ -328,6 +341,19 @@ traffic_cases() -> ]. +ttest_cases() -> + [ + ttest_sgenf_cgenf_small_tcp4, + ttest_sgenf_cgenf_small_tcp6%% , + + %% ttest_sgenf_cgenf_medium_tcp4, + %% ttest_sgenf_cgenf_medium_tcp6, + + %% ttest_sgenf_cgenf_large_tcp4, + %% ttest_sgenf_cgenf_large_tcp6 + ]. + + %% ticket_cases() -> %% []. @@ -8482,9 +8508,6 @@ traffic_ping_pong_send_and_receive_tcp(#{msg := Msg} = InitState) -> true -> ok end, - - - ok = socket:setopt(Sock, otp, rcvbuf, 8*1024) end, traffic_ping_pong_send_and_receive_tcp2(InitState#{buf_init => Fun}). @@ -10134,6 +10157,539 @@ tpp_udp_sock_close(Sock) -> end. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. + +ttest_sgenf_cgenf_small_tcp4(suite) -> + []; +ttest_sgenf_cgenf_small_tcp4(doc) -> + []; +ttest_sgenf_cgenf_small_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgenf_cgenf_small_tcp4, + inet, + gen, false, + gen, false, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% + +ttest_sgenf_cgenf_small_tcp6(suite) -> + []; +ttest_sgenf_cgenf_small_tcp6(doc) -> + []; +ttest_sgenf_cgenf_small_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgenf_cgenf_small_tcp6, + inet6, + gen, false, + gen, false, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +ttest_tcp(TC, + Domain, + ServerMod, ServerActive, + ClientMod, ClientActive, + MsgID, MaxOutstanding) -> + Runtime = ?SECS(60), + tc_try(TC, + fun() -> + if (Domain =/= inet) -> not_yet_implemented(); true -> ok end, + ?TT(Runtime + ?SECS(60)), + InitState = #{domain => Domain, + msg_id => MsgID, + max_outstanding => MaxOutstanding, + runtime => Runtime, + server_mod => ServerMod, + server_active => ServerActive, + client_mod => ClientMod, + client_active => ClientActive}, + ok = ttest_tcp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% This test is run with the following combinations: +%% (this does not take the message size into consideration) +%% server(gen, false) - client(gen, false) +%% server(gen, false) - client(gen, once) +%% server(gen, false) - client(gen, true) +%% server(gen, false) - client(sock, false) +%% server(gen, false) - client(sock, once) +%% server(gen, false) - client(sock, true) +%% server(gen, once) - client(gen, false) +%% server(gen, once) - client(gen, once) +%% server(gen, once) - client(gen, true) +%% server(gen, once) - client(sock, false) +%% server(gen, once) - client(sock, once) +%% server(gen, once) - client(sock, true) +%% server(gen, true) - client(gen, false) +%% server(gen, true) - client(gen, once) +%% server(gen, true) - client(gen, true) +%% server(gen, true) - client(sock, false) +%% server(gen, true) - client(sock, once) +%% server(gen, true) - client(sock, true) +%% server(sock, false) - client(gen, false) +%% server(sock, false) - client(gen, once) +%% server(sock, false) - client(gen, true) +%% server(sock, false) - client(sock, false) +%% server(sock, false) - client(sock, once) +%% server(sock, false) - client(sock, true) +%% server(sock, once) - client(gen, false) +%% server(sock, once) - client(gen, once) +%% server(sock, once) - client(gen, true) +%% server(sock, once) - client(sock, false) +%% server(sock, once) - client(sock, once) +%% server(sock, once) - client(sock, true) +%% server(sock, true) - client(gen, false) +%% server(sock, true) - client(gen, once) +%% server(sock, true) - client(gen, true) +%% server(sock, true) - client(sock, false) +%% server(sock, true) - client(sock, once) +%% server(sock, true) - client(sock, true) + +ttest_tcp(InitState) -> + ServerSeq = + [ + %% *** Wait for start order part *** + #{desc => "await start", + cmd => fun(State) -> + Tester = ?SEV_AWAIT_START(), + {ok, State#{tester => Tester}} + end}, + #{desc => "monitor tester", + cmd => fun(#{tester := Tester} = _State) -> + _MRef = erlang:monitor(process, Tester), + ok + end}, + + + %% *** Init part *** + #{desc => "create node", + cmd => fun(#{host := Host} = State) -> + case start_node(Host, server) of + {ok, Node} -> + {ok, State#{node => Node}}; + {error, Reason, _} -> + {error, Reason} + end + end}, + #{desc => "monitor server node", + cmd => fun(#{node := Node} = _State) -> + true = erlang:monitor_node(Node, true), + ok + end}, + #{desc => "start ttest (remote) server", + cmd => fun(#{mod := Mod, + active := Active, + node := Node} = State) -> + case ttest_tcp_server_start(Node, Mod, Active) of + {ok, {{Pid, _MRef}, {Addr, Port}}} -> + {ok, State#{rserver => Pid, + addr => Addr, + port => Port}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (init)", + cmd => fun(#{tester := Tester, + addr := Addr, + port := Port}) -> + ?SEV_ANNOUNCE_READY(Tester, init, {Addr, Port}), + ok + end}, + + + %% *** Termination *** + #{desc => "await terminate (from tester)", + cmd => fun(#{tester := Tester, + rserver := RServer} = State) -> + case ?SEV_AWAIT_TERMINATE(Tester, tester, + [{rserver, RServer}]) of + ok -> + {ok, maps:remove(tester, State)}; + {error, _} = ERROR -> + ERROR + end + end}, + %% The remote server is in a accept, with a timeout of 5 seconds, + %% so may have to wait a bit... + #{desc => "order (remote) ttest server terminate", + cmd => fun(#{node := _Node, + rserver := RServer}) -> + ttest_tcp_server_stop(RServer), + ok + end}, + #{desc => "await ttest (remote) server termination", + cmd => fun(#{rserver := RServer} = State) -> + ?SEV_AWAIT_TERMINATION(RServer), + State1 = maps:remove(rserver, State), + {ok, State1} + end}, + #{desc => "stop (server) node", + cmd => fun(#{node := Node} = _State) -> + stop_node(Node) + end}, + #{desc => "await (server) node termination", + cmd => fun(#{node := Node} = State) -> + receive + {nodedown, Node} -> + {ok, maps:remove(node, State)} + end + end}, + + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + ClientSeq = + [ + %% *** Wait for start order part *** + #{desc => "await start", + cmd => fun(State) -> + {Tester, {ServerAddr, ServerPort}} = ?SEV_AWAIT_START(), + {ok, State#{tester => Tester, + server_addr => ServerAddr, + server_port => ServerPort}} + end}, + #{desc => "monitor tester", + cmd => fun(#{tester := Tester} = _State) -> + _MRef = erlang:monitor(process, Tester), + ok + end}, + + + %% *** Init part *** + #{desc => "create node", + cmd => fun(#{host := Host} = State) -> + case start_node(Host, client) of + {ok, Node} -> + {ok, State#{node => Node}}; + {error, Reason, _} -> + {error, Reason} + end + end}, + #{desc => "monitor client node", + cmd => fun(#{node := Node} = _State) -> + true = erlang:monitor_node(Node, true), + ok + end}, + #{desc => "announce ready (init)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, init), + ok + end}, + + + %% The actual test + #{desc => "await continue (ttest)", + cmd => fun(#{tester := Tester} = _State) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, ttest), + ok + end}, + #{desc => "start ttest (remote) client", + cmd => fun(#{node := Node, + mod := Mod, + active := Active, + msg_id := MsgID, + max_outstanding := MaxOutstanding, + runtime := RunTime, + server_addr := Addr, + server_port := Port} = State) -> + Self = self(), + Notify = + fun(Result) -> + ?SEV_ANNOUNCE_READY(Self, ttest, Result) + end, + case ttest_tcp_client_start(Node, Notify, + Mod, Active, + Addr, Port, + MsgID, MaxOutstanding, + RunTime) of + {ok, {Pid, _MRef}} -> + {ok, State#{rclient => Pid}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "await ttest ready", + cmd => fun(#{tester := Tester, + rclient := RClient} = State) -> + %% TTestResult = ?SEV_AWAIT_READY(RClient, rclient, ttest, + %% [{tester, Tester}]), + case ?SEV_AWAIT_READY(RClient, rclient, ttest, + [{tester, Tester}]) of + {ok, Result} -> + {ok, State#{result => Result}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "await ttest (remote) client termination", + cmd => fun(#{rclient := RClient} = State) -> + ?SEV_AWAIT_TERMINATION(RClient), + State1 = maps:remove(rclient, State), + {ok, State1} + end}, + #{desc => "announce ready (ttest)", + cmd => fun(#{tester := Tester, + result := Result} = State) -> + ?SEV_ANNOUNCE_READY(Tester, ttest, Result), + {ok, maps:remove(result, State)} + end}, + + + %% *** Termination *** + #{desc => "await terminate (from tester)", + cmd => fun(#{tester := Tester} = State) -> + case ?SEV_AWAIT_TERMINATE(Tester, tester) of + ok -> + {ok, maps:remove(tester, State)}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "stop (client) node", + cmd => fun(#{node := Node} = _State) -> + stop_node(Node) + end}, + #{desc => "await (client) node termination", + cmd => fun(#{node := Node} = State) -> + receive + {nodedown, Node} -> + {ok, maps:remove(node, State)} + end + end}, + + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + TesterSeq = + [ + %% *** Init part *** + #{desc => "monitor server", + cmd => fun(#{server := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + #{desc => "monitor client", + cmd => fun(#{client := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + + %% Start the server + #{desc => "order server start", + cmd => fun(#{server := Pid} = _State) -> + ?SEV_ANNOUNCE_START(Pid), + ok + end}, + #{desc => "await server ready (init)", + cmd => fun(#{server := Pid} = State) -> + {ok, {Addr, Port}} = ?SEV_AWAIT_READY(Pid, server, init), + {ok, State#{server_addr => Addr, + server_port => Port}} + end}, + + + %% Start the client + #{desc => "order client start", + cmd => fun(#{client := Pid, + server_addr := Addr, + server_port := Port} = _State) -> + ?SEV_ANNOUNCE_START(Pid, {Addr, Port}), + ok + end}, + #{desc => "await client ready (init)", + cmd => fun(#{client := Client} = _State) -> + ok = ?SEV_AWAIT_READY(Client, client, init) + end}, + + %% The actual test + #{desc => "order client continue (ttest)", + cmd => fun(#{client := Client} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Client, ttest), + ok + end}, + #{desc => "await client ready (ttest)", + cmd => fun(#{server := Server, + client := Client} = State) -> + case ?SEV_AWAIT_READY(Client, client, ttest, + [{server, Server}]) of + {ok, Result} -> + {ok, State#{result => Result}}; + {error, _} = ERROR -> + ERROR + end + end}, + + + %% *** Terminate server *** + #{desc => "order client terminate", + cmd => fun(#{client := Client} = _State) -> + ?SEV_ANNOUNCE_TERMINATE(Client), + ok + end}, + #{desc => "await client down", + cmd => fun(#{client := Client} = State) -> + ?SEV_AWAIT_TERMINATION(Client), + State1 = maps:remove(client, State), + {ok, State1} + end}, + #{desc => "order server terminate", + cmd => fun(#{server := Server} = _State) -> + ?SEV_ANNOUNCE_TERMINATE(Server), + ok + end}, + #{desc => "await server down", + cmd => fun(#{server := Server} = _State) -> + ?SEV_AWAIT_TERMINATION(Server), + ok + end}, + + + %% Present the results + #{desc => "present the results", + cmd => fun(#{result := Result} = State) -> + case Result of + #{status := ok, + runtime := RunTime, + cnt := Cnt, + bcnt := BCnt} -> + ?SEV_IPRINT( + "TTest results: " + "~n Run Time: ~s" + "~n Byte Count: ~s" + "~n Number of message exchanges: ~s" + "~n~n", + [ + ?TTEST_LIB:format_time(RunTime), + if ((BCnt =:= 0) orelse (RunTime =:= 0)) -> + ?TTEST_LIB:format("~w, ~w", + [BCnt, RunTime]); + true -> + ?TTEST_LIB:format("~p => ~p byte / ms", + [BCnt, BCnt div RunTime]) + end, + if (RunTime =:= 0) -> + "-"; + true -> + ?TTEST_LIB:format("~p => ~p iterations / ms", + [Cnt, Cnt div RunTime]) + end + ]), + {ok, maps:remove(result, State)}; + + #{status := Failure, + runtime := RunTime, + sid := SID, + rid := RID, + scnt := SCnt, + rcnt := RCnt, + bcnt := BCnt, + num := Num} -> + ?SEV_EPRINT("Time Test failed: " + "~n ~p" + "~n" + "~nwhen" + "~n" + "~n Run Time: ~s" + "~n Send ID: ~p" + "~n Recv ID: ~p" + "~n Send Count: ~p" + "~n Recv Count: ~p" + "~n Byte Count: ~p" + "~n Num Iterations: ~p", + [Failure, + ?TTEST_LIB:format_time(RunTime), + SID, RID, SCnt, RCnt, BCnt, Num]), + {error, Failure} + end + end}, + + %% This is just so that the printout above shall have time to come + %% out before then end of the test case. + ?SEV_SLEEP(?SECS(1)), + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + i("start server evaluator"), + ServerInitState = #{host => local_host(), + domain => maps:get(domain, InitState), + mod => maps:get(server_mod, InitState), + active => maps:get(server_active, InitState)}, + Server = ?SEV_START("server", ServerSeq, ServerInitState), + + i("start client evaluator"), + ClientInitState = #{host => local_host(), + domain => maps:get(domain, InitState), + mod => maps:get(client_mod, InitState), + active => maps:get(client_active, InitState), + msg_id => maps:get(msg_id, InitState), + max_outstanding => maps:get(max_outstanding, InitState), + runtime => maps:get(runtime, InitState)}, + Client = ?SEV_START("client", ClientSeq, ClientInitState), + + i("start 'tester' evaluator"), + TesterInitState = #{server => Server#ev.pid, + client => Client#ev.pid}, + Tester = ?SEV_START("tester", TesterSeq, TesterInitState), + + i("await evaluator(s)"), + ok = ?SEV_AWAIT_FINISH([Server, Client, Tester]). + + + +ttest_tcp_server_start(Node, gen, Active) -> + Transport = socket_test_ttest_tcp_gen, + socket_test_ttest_tcp_server:start_monitor(Node, Transport, Active); +ttest_tcp_server_start(Node, sock, Active) -> + TransportMod = socket_test_ttest_tcp_socket, + Transport = {TransportMod, #{method => plain}}, + socket_test_ttest_tcp_server:start_monitor(Node, Transport, Active). + +ttest_tcp_server_stop(Pid) -> + socket_test_ttest_tcp_server:stop(Pid). + +ttest_tcp_client_start(Node, + Notify, + gen, + Active, Addr, Port, MsgID, MaxOutstanding, RunTime) -> + Transport = socket_test_ttest_tcp_gen, + socket_test_ttest_tcp_client:start_monitor(Node, + Notify, + Transport, + Active, + Addr, Port, + MsgID, MaxOutstanding, RunTime); +ttest_tcp_client_start(Node, + Notify, + sock, + Active, Addr, Port, MsgID, MaxOutstanding, RunTime) -> + TransportMod = socket_test_ttest_tcp_socket, + Transport = {TransportMod, #{method => plain}}, + socket_test_ttest_tcp_client:start_monitor(Node, + Notify, + Transport, + Active, + Addr, Port, + MsgID, MaxOutstanding, RunTime). + + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% start_node(Host, NodeName) -> @@ -10406,7 +10962,7 @@ tc_which_name() -> Name end. - + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% l2a(S) when is_list(S) -> diff --git a/erts/emulator/test/socket_test_evaluator.erl b/erts/emulator/test/socket_test_evaluator.erl index 48fb6a027e..deea7e5d36 100644 --- a/erts/emulator/test/socket_test_evaluator.erl +++ b/erts/emulator/test/socket_test_evaluator.erl @@ -153,6 +153,7 @@ await_finish([], Fails) -> Fails; await_finish(Evs, Fails) -> receive + %% Successfull termination of evaluator {'DOWN', _MRef, process, Pid, normal} -> case lists:keysearch(Pid, #ev.pid, Evs) of {value, #ev{name = Name}} -> @@ -163,8 +164,20 @@ await_finish(Evs, Fails) -> iprint("unknown process ~p died (normal)", [Pid]), await_finish(Evs, Fails) end; + + %% The evaluator can skip the teat case: {'DOWN', _MRef, process, Pid, {skip, Reason}} -> + case lists:keysearch(Pid, #ev.pid, Evs) of + {value, #ev{name = Name}} -> + iprint("evaluator '~s' (~p) issued SKIP: " + "~n ~p", [Name, Pid, Reason]); + false -> + iprint("unknown process ~p issued SKIP: " + "~n ~p", [Pid, Reason]) + end, ?LIB:skip(Reason); + + %% Evaluator failed {'DOWN', _MRef, process, Pid, Reason} -> case lists:keysearch(Pid, #ev.pid, Evs) of {value, #ev{name = Name}} -> @@ -260,6 +273,12 @@ announce(To, Announcement, Slogan, Extra) when is_pid(To) andalso is_atom(Announcement) andalso is_atom(Slogan) -> + %% iprint("announce -> entry with: " + %% "~n To: ~p" + %% "~n Announcement: ~p" + %% "~n Slogan: ~p" + %% "~n Extra: ~p", + %% [To, Announcement, Slogan, Extra]), To ! {Announcement, self(), Slogan, Extra}, ok. diff --git a/erts/emulator/test/socket_test_lib.erl b/erts/emulator/test/socket_test_lib.erl index f55f338ef9..4e65c4f3c0 100644 --- a/erts/emulator/test/socket_test_lib.erl +++ b/erts/emulator/test/socket_test_lib.erl @@ -21,6 +21,8 @@ -module(socket_test_lib). -export([ + pi/1, pi/2, pi/3, + %% Time stuff timestamp/0, tdiff/2, @@ -36,6 +38,22 @@ ]). +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +pi(Item) when is_atom(Item) -> + pi(self(), Item). + +pi(Pid, Item) when is_pid(Pid) andalso is_atom(Item) -> + {Item, Info} = process_info(Pid, Item), + Info; +pi(Node, Pid) when is_pid(Pid) -> + rpc:call(Node, erlang, process_info, [Pid]). + +pi(Node, Pid, Item) when is_pid(Pid) andalso is_atom(Item) -> + rpc:call(Node, erlang, process_info, [Pid, Item]). + + + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% timestamp() -> diff --git a/erts/emulator/test/socket_test_logger.erl b/erts/emulator/test/socket_test_logger.erl index 5996bbe855..e42251158f 100644 --- a/erts/emulator/test/socket_test_logger.erl +++ b/erts/emulator/test/socket_test_logger.erl @@ -95,5 +95,17 @@ print(F, A) -> print(?LIB:f(F, A)). print(Str) -> - io:format(user, Str ++ "~n", []), - io:format(Str, []). + try + begin + io:format(user, Str ++ "~n", []), + io:format(Str, []) + end + catch + _:_:_ -> + io:format(user, + "~nFailed Format message:" + "~n~p~n", [Str]), + io:format("~nFailed Format message:" + "~n~p~n", [Str]) + end. + diff --git a/erts/emulator/test/socket_test_ttest_tcp_client.erl b/erts/emulator/test/socket_test_ttest_tcp_client.erl index 9c43c41841..180c280fb0 100644 --- a/erts/emulator/test/socket_test_ttest_tcp_client.erl +++ b/erts/emulator/test/socket_test_ttest_tcp_client.erl @@ -41,10 +41,19 @@ -module(socket_test_ttest_tcp_client). -export([ - start_monitor/4, start_monitor/5, start_monitor/7, + %% These are for the test suite + start_monitor/6, start_monitor/7, start_monitor/9, + + %% These are for starting in a shell when run "manually" + start/4, start/5, start/7, stop/1 ]). +%% Internal exports +-export([ + do_start/9 + ]). + -include_lib("kernel/include/inet.hrl"). -include("socket_test_ttest.hrl"). -include("socket_test_ttest_client.hrl"). @@ -71,66 +80,135 @@ %% ========================================================================== -start_monitor(Transport, Active, Addr, Port) -> - start_monitor(Transport, Active, Addr, Port, ?MSG_ID_DEFAULT). - -%% RunTime is in number of ms. -start_monitor(Transport, Active, Addr, Port, 1 = MsgID) -> - start_monitor(Transport, Active, Addr, Port, MsgID, - ?MAX_OUTSTANDING_DEFAULT_1, ?RUNTIME_DEFAULT); -start_monitor(Transport, Active, Addr, Port, 2 = MsgID) -> - start_monitor(Transport, Active, Addr, Port, MsgID, - ?MAX_OUTSTANDING_DEFAULT_2, ?RUNTIME_DEFAULT); -start_monitor(Transport, Active, Addr, Port, 3 = MsgID) -> - start_monitor(Transport, Active, Addr, Port, MsgID, - ?MAX_OUTSTANDING_DEFAULT_3, ?RUNTIME_DEFAULT). - --spec start_monitor(Transport, - Active, - Addr, - Port, - MsgID, - MaxOutstanding, - RunTime) -> term() when +start_monitor(Node, Notify, Transport, Active, Addr, Port) -> + start_monitor(Node, Notify, Transport, Active, Addr, Port, ?MSG_ID_DEFAULT). + +start_monitor(Node, Notify, Transport, Active, Addr, Port, 1 = MsgID) -> + start_monitor(Node, Notify, Transport, Active, Addr, Port, MsgID, + ?MAX_OUTSTANDING_DEFAULT_1, ?RUNTIME_DEFAULT); +start_monitor(Node, Notify, Transport, Active, Addr, Port, 2 = MsgID) -> + start_monitor(Node, Notify, Transport, Active, Addr, Port, MsgID, + ?MAX_OUTSTANDING_DEFAULT_2, ?RUNTIME_DEFAULT); +start_monitor(Node, Notify, Transport, Active, Addr, Port, 3 = MsgID) -> + start_monitor(Node, Notify, Transport, Active, Addr, Port, MsgID, + ?MAX_OUTSTANDING_DEFAULT_3, ?RUNTIME_DEFAULT). + +start_monitor(Node, Notify, Transport, Active, Addr, Port, + MsgID, MaxOutstanding, RunTime) + when (Node =/= node()) -> + ?I("start_monitor -> entry with" + "~n Node: ~p" + "~n Transport: ~p" + "~n Active: ~p" + "~n Addr: ~p" + "~n Port: ~p" + "~n MsgID: ~p" + "~n MaxOutstanding: ~p" + "~n RunTime: ~p", + [Node, Transport, Active, Addr, Port, MsgID, MaxOutstanding, RunTime]), + Args = [self(), Notify, + Transport, Active, Addr, Port, MsgID, MaxOutstanding, RunTime], + case rpc:call(Node, ?MODULE, do_start, Args) of + {badrpc, _} = Reason -> + {error, Reason}; + {ok, Pid} when is_pid(Pid) -> + MRef = erlang:monitor(process, Pid), + {ok, {Pid, MRef}}; + {error, _} = ERROR -> + ERROR + end; +start_monitor(_, Notify, Transport, Active, Addr, Port, + MsgID, MaxOutstanding, RunTime) -> + case do_start(self(), Notify, + Transport, Active, Addr, Port, + MsgID, MaxOutstanding, RunTime) of + {ok, Pid} -> + MRef = erlang:monitor(process, Pid), + {ok, {Pid, MRef}}; + {error, _} = ERROR -> + ERROR + end. + + +start(Transport, Active, Addr, Port) -> + start(Transport, Active, Addr, Port, ?MSG_ID_DEFAULT). + +start(Transport, Active, Addr, Port, 1 = MsgID) -> + start(Transport, Active, Addr, Port, MsgID, + ?MAX_OUTSTANDING_DEFAULT_1, ?RUNTIME_DEFAULT); +start(Transport, Active, Addr, Port, 2 = MsgID) -> + start(Transport, Active, Addr, Port, MsgID, + ?MAX_OUTSTANDING_DEFAULT_2, ?RUNTIME_DEFAULT); +start(Transport, Active, Addr, Port, 3 = MsgID) -> + start(Transport, Active, Addr, Port, MsgID, + ?MAX_OUTSTANDING_DEFAULT_3, ?RUNTIME_DEFAULT). + +start(Transport, Active, Addr, Port, MsgID, MaxOutstanding, RunTime) -> + Notify = fun(R) -> present_results(R) end, + do_start(self(), Notify, + Transport, Active, Addr, Port, MsgID, MaxOutstanding, RunTime). + + +-spec do_start(Parent, + Notify, + Transport, + Active, + Addr, + Port, + MsgID, + MaxOutstanding, + RunTime) -> {ok, Pid} | {error, Reason} when + Parent :: pid(), + Notify :: function(), Transport :: atom() | tuple(), Active :: active(), Addr :: inet:ip_address(), Port :: inet:port_number(), MsgID :: msg_id(), MaxOutstanding :: max_outstanding(), - RunTime :: runtime(). - -%% RunTime is in number of ms. -start_monitor(Transport, Active, Addr, Port, - MsgID, MaxOutstanding, RunTime) - when (is_atom(Transport) orelse is_tuple(Transport)) andalso + RunTime :: runtime(), + Pid :: pid(), + Reason :: term(). + +do_start(Parent, Notify, + Transport, Active, Addr, Port, MsgID, MaxOutstanding, RunTime) + when is_pid(Parent) andalso + is_function(Notify) andalso + (is_atom(Transport) orelse is_tuple(Transport)) andalso (is_boolean(Active) orelse (Active =:= once)) andalso is_tuple(Addr) andalso (is_integer(Port) andalso (Port > 0)) andalso (is_integer(MsgID) andalso (MsgID >= 1) andalso (MsgID =< 3)) andalso (is_integer(MaxOutstanding) andalso (MaxOutstanding > 0)) andalso (is_integer(RunTime) andalso (RunTime > 0)) -> - Self = self(), - ClientInit = fun() -> put(sname, "client"), - init(Self, - Transport, Active, Addr, Port, - MsgID, MaxOutstanding, RunTime) - end, - {Pid, MRef} = spawn_monitor(ClientInit), + ?I("do_start -> entry with" + "~n Parent: ~p" + "~n Transport: ~p" + "~n Active: ~p" + "~n Addr: ~p" + "~n Port: ~p" + "~n MsgID: ~p" + "~n MaxOutstanding: ~p" + "~n RunTime: ~p", + [Parent, Transport, Active, Addr, Port, MsgID, MaxOutstanding, RunTime]), + Starter = self(), + Init = fun() -> put(sname, "client"), + init(Starter, + Parent, + Notify, + Transport, Active, Addr, Port, + MsgID, MaxOutstanding, RunTime) + end, + {Pid, MRef} = spawn_monitor(Init), receive + {'DOWN', MRef, process, Pid, Reason} -> + {error, Reason}; {?MODULE, Pid, ok} -> - erlang:demonitor(MRef, [flush]), - {ok, {Pid, MRef}}; - + erlang:demonitor(MRef), + {ok, Pid}; {?MODULE, Pid, {error, _} = ERROR} -> - erlang:demonitor(MRef, [flush]), - ERROR; - - {'DOWN', MRef, process, Pid, normal} -> - ok; - {'DOWN', MRef, process, Pid, Reason} -> - {error, {exit, Reason}} - + erlang:demonitor(MRef, [flush]), + ERROR end. @@ -141,9 +219,12 @@ stop(Pid) when is_pid(Pid) -> %% ========================================================================== -init(Parent, Transport, Active, Addr, Port, +init(Starter, + Parent, Notify, + Transport, Active, Addr, Port, MsgID, MaxOutstanding, RunTime) -> ?I("init with" + "~n Starter: ~p" "~n Parent: ~p" "~n Transport: ~p" "~n Active: ~p" @@ -152,14 +233,15 @@ init(Parent, Transport, Active, Addr, Port, "~n Msg ID: ~p (=> 16 + ~w bytes)" "~n Max Outstanding: ~p" "~n (Suggested) Run Time: ~p ms", - [Parent, + [Starter, + Parent, Transport, Active, inet:ntoa(Addr), Port, MsgID, size(which_msg_data(MsgID)), MaxOutstanding, RunTime]), {Mod, Connect} = process_transport(Transport), case Connect(Addr, Port) of {ok, Sock} -> ?I("connected"), - Parent ! {?MODULE, self(), ok}, + Starter ! {?MODULE, self(), ok}, initial_activation(Mod, Sock, Active), Results = loop(#{slogan => run, runtime => RunTime, @@ -178,7 +260,7 @@ init(Parent, Transport, Active, Addr, Port, bcnt => 0, num => undefined, acc => <<>>}), - present_results(Results), + Notify(Results), (catch Mod:close(Sock)), exit(normal); {error, Reason} -> diff --git a/erts/emulator/test/socket_test_ttest_tcp_client_gen.erl b/erts/emulator/test/socket_test_ttest_tcp_client_gen.erl index 4f6152a4b1..33eebfb10f 100644 --- a/erts/emulator/test/socket_test_ttest_tcp_client_gen.erl +++ b/erts/emulator/test/socket_test_ttest_tcp_client_gen.erl @@ -21,25 +21,22 @@ -module(socket_test_ttest_tcp_client_gen). -export([ - start_monitor/3, start_monitor/4, start_monitor/6, + start/3, start/4, start/6, stop/1 ]). -define(TRANSPORT_MOD, socket_test_ttest_tcp_gen). -start_monitor(Active, Addr, Port) -> - socket_test_ttest_tcp_client:start_monitor(?TRANSPORT_MOD, - Active, Addr, Port). +start(Active, Addr, Port) -> + socket_test_ttest_tcp_client:start(?TRANSPORT_MOD, Active, Addr, Port). -start_monitor(Active, Addr, Port, MsgID) -> - socket_test_ttest_tcp_client:start_monitor(?TRANSPORT_MOD, - Active, Addr, Port, - MsgID). +start(Active, Addr, Port, MsgID) -> + socket_test_ttest_tcp_client:start(?TRANSPORT_MOD, Active, Addr, Port, MsgID). -start_monitor(Active, Addr, Port, MsgID, MaxOutstanding, RunTime) -> - socket_test_ttest_tcp_client:start_monitor(?TRANSPORT_MOD, - Active, Addr, Port, - MsgID, MaxOutstanding, RunTime). +start(Active, Addr, Port, MsgID, MaxOutstanding, RunTime) -> + socket_test_ttest_tcp_client:start(?TRANSPORT_MOD, + Active, Addr, Port, + MsgID, MaxOutstanding, RunTime). stop(Pid) -> socket_test_ttest_tcp_client:stop(Pid). diff --git a/erts/emulator/test/socket_test_ttest_tcp_client_socket.erl b/erts/emulator/test/socket_test_ttest_tcp_client_socket.erl index ef980e8d32..c0a3302ecd 100644 --- a/erts/emulator/test/socket_test_ttest_tcp_client_socket.erl +++ b/erts/emulator/test/socket_test_ttest_tcp_client_socket.erl @@ -21,26 +21,24 @@ -module(socket_test_ttest_tcp_client_socket). -export([ - start_monitor/4, start_monitor/5, start_monitor/7, + start/4, start/5, start/7, stop/1 ]). -define(TRANSPORT_MOD, socket_test_ttest_tcp_socket). -define(MOD(M), {?TRANSPORT_MOD, #{method => Method}}). -start_monitor(Method, Active, Addr, Port) -> - socket_test_ttest_tcp_client:start_monitor(?MOD(Method), - Active, Addr, Port). +start(Method, Active, Addr, Port) -> + socket_test_ttest_tcp_client:start_monitor(?MOD(Method), Active, Addr, Port). -start_monitor(Method, Active, Addr, Port, MsgID) -> - socket_test_ttest_tcp_client:start_monitor(?MOD(Method), - Active, Addr, Port, - MsgID). +start(Method, Active, Addr, Port, MsgID) -> + socket_test_ttest_tcp_client:start(?MOD(Method), + Active, Addr, Port, MsgID). -start_monitor(Method, Active, Addr, Port, MsgID, MaxOutstanding, RunTime) -> - socket_test_ttest_tcp_client:start_monitor(?MOD(Method), - Active, Addr, Port, - MsgID, MaxOutstanding, RunTime). +start(Method, Active, Addr, Port, MsgID, MaxOutstanding, RunTime) -> + socket_test_ttest_tcp_client:start(?MOD(Method), + Active, Addr, Port, + MsgID, MaxOutstanding, RunTime). stop(Pid) -> socket_test_ttest_client:stop(Pid). diff --git a/erts/emulator/test/socket_test_ttest_tcp_server.erl b/erts/emulator/test/socket_test_ttest_tcp_server.erl index b248b063a3..257bb18592 100644 --- a/erts/emulator/test/socket_test_ttest_tcp_server.erl +++ b/erts/emulator/test/socket_test_ttest_tcp_server.erl @@ -34,15 +34,25 @@ -module(socket_test_ttest_tcp_server). -export([ - start_monitor/2, + %% This are for the test suite + start_monitor/3, + + %% This are for starting in a shell when run "manually" + start/2, + stop/1 ]). +%% Internal exports +-export([ + do_start/3 + ]). + -include_lib("kernel/include/inet.hrl"). -include("socket_test_ttest.hrl"). --define(ACC_TIMEOUT, 10000). --define(RECV_TIMEOUT, 10000). +-define(ACC_TIMEOUT, 5000). +-define(RECV_TIMEOUT, 5000). -define(LIB, socket_test_ttest_lib). -define(I(F), ?LIB:info(F)). @@ -56,28 +66,51 @@ %% ========================================================================== -start_monitor(Transport, Active) - when (is_atom(Transport) orelse is_tuple(Transport)) andalso +start_monitor(Node, Transport, Active) when (Node =/= node()) -> + case rpc:call(Node, ?MODULE, do_start, [self(), Transport, Active]) of + {badrpc, _} = Reason -> + {error, Reason}; + {ok, {Pid, AddrPort}} -> + MRef = erlang:monitor(process, Pid), + {ok, {{Pid, MRef}, AddrPort}}; + {error, _} = ERROR -> + ERROR + end; +start_monitor(_, Transport, Active) -> + case do_start(self(), Transport, Active) of + {ok, {Pid, AddrPort}} -> + MRef = erlang:monitor(process, Pid), + {ok, {{Pid, MRef}, AddrPort}}; + {error, _} = ERROR -> + ERROR + end. + + + +start(Transport, Active) -> + do_start(self(), Transport, Active). + + +do_start(Parent, Transport, Active) + when is_pid(Parent) andalso + (is_atom(Transport) orelse is_tuple(Transport)) andalso (is_boolean(Active) orelse (Active =:= once)) -> - Self = self(), - ServerInit = fun() -> put(sname, "server"), - server_init(Self, Transport, Active) - end, + Starter = self(), + ServerInit = fun() -> put(sname, "server"), + server_init(Starter, Parent, Transport, Active) + end, {Pid, MRef} = spawn_monitor(ServerInit), receive - {'DOWN', MRef, process, Pid, normal} -> - ok; {'DOWN', MRef, process, Pid, Reason} -> - {error, {exit, Reason}}; - - {?MODULE, Pid, {ok, Port}} -> - erlang:demonitor(MRef, [flush]), - {ok, {Pid, MRef, Port}}; + {error, Reason}; + {?MODULE, Pid, {ok, AddrPort}} -> + erlang:demonitor(MRef), + {ok, {Pid, AddrPort}}; {?MODULE, Pid, {error, _} = ERROR} -> - erlang:demonitor(MRef, [flush]), + erlang:demonitor(MRef, [flush]), ERROR end. - + stop(Pid) when is_pid(Pid) -> req(Pid, stop). @@ -85,22 +118,23 @@ stop(Pid) when is_pid(Pid) -> %% ========================================================================== -server_init(Parent, Transport, Active) -> +server_init(Starter, Parent, Transport, Active) -> ?I("init -> entry with" - "~n Parent: ~p" + "~n Starter: ~p" + "~n Parent: ~p" "~n Transport: ~p" - "~n Active: ~p", [Parent, Transport, Active]), + "~n Active: ~p", [Starter, Parent, Transport, Active]), {Mod, Listen, StatsInterval} = process_transport(Transport, Active), case Listen(0) of {ok, LSock} -> case Mod:port(LSock) of - {ok, Port} = OK -> + {ok, Port} -> Addr = which_addr(), % This is just for convenience ?I("init -> listening on:" "~n Addr: ~p (~s)" "~n Port: ~w" "~n", [Addr, inet:ntoa(Addr), Port]), - Parent ! {?MODULE, self(), OK}, + Starter ! {?MODULE, self(), {ok, {Addr, Port}}}, server_loop(#{parent => Parent, mod => Mod, active => Active, diff --git a/erts/emulator/test/socket_test_ttest_tcp_server_gen.erl b/erts/emulator/test/socket_test_ttest_tcp_server_gen.erl index 53a9dc7d2a..425e3d74af 100644 --- a/erts/emulator/test/socket_test_ttest_tcp_server_gen.erl +++ b/erts/emulator/test/socket_test_ttest_tcp_server_gen.erl @@ -28,7 +28,16 @@ -define(TRANSPORT_MOD, socket_test_ttest_tcp_gen). start_monitor(Active) -> - socket_test_ttest_tcp_server:start_monitor(?TRANSPORT_MOD, Active). + case socket_test_ttest_tcp_server:start_monitor(node(), + ?TRANSPORT_MOD, + Active) of + {ok, {Pid, AddrPort}} -> + MRef = erlang:monitor(process, Pid), + {ok, {Pid, MRef, AddrPort}}; + {error, _} = ERROR -> + ERROR + end. + stop(Pid) -> socket_test_ttest_tcp_server:stop(Pid). diff --git a/erts/emulator/test/socket_test_ttest_tcp_server_socket.erl b/erts/emulator/test/socket_test_ttest_tcp_server_socket.erl index d62f4d86a6..c78c7111ce 100644 --- a/erts/emulator/test/socket_test_ttest_tcp_server_socket.erl +++ b/erts/emulator/test/socket_test_ttest_tcp_server_socket.erl @@ -31,7 +31,15 @@ -define(MOD(M), {?TRANSPORT_MOD, #{method => M}}). start_monitor(Method, Active) -> - socket_test_ttest_tcp_server:start_monitor(?MOD(Method), Active). + case socket_test_ttest_tcp_server:start_monitor(node(), + ?MOD(Method), + Active) of + {ok, {Pid, AddrPort}} -> + MRef = erlang:monitor(process, Pid), + {ok, {Pid, MRef, AddrPort}}; + {error, _} = ERROR -> + ERROR + end. stop(Pid) -> socket_test_ttest_tcp_server:stop(Pid). diff --git a/erts/emulator/test/socket_test_ttest_tcp_socket.erl b/erts/emulator/test/socket_test_ttest_tcp_socket.erl index f314d01a63..9af25c5f50 100644 --- a/erts/emulator/test/socket_test_ttest_tcp_socket.erl +++ b/erts/emulator/test/socket_test_ttest_tcp_socket.erl @@ -134,10 +134,7 @@ maybe_start_stats_timer(#{stats_to := Pid, stats_interval := T}, Reader) when is_pid(Pid) -> erlang:start_timer(T, Pid, {stats, T, "reader", Reader}); -maybe_start_stats_timer(O, _) -> - io:format("NO STATS: " - "~n ~p" - "~n", [O]), +maybe_start_stats_timer(_O, _) -> ok. controlling_process(#{sock := Sock, reader := Pid}, NewPid) -> -- cgit v1.2.3 From 2dceec3057ff298cf6a7ff6af5a5a1b5f8b96e61 Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Tue, 18 Dec 2018 15:21:43 +0100 Subject: [socket-nif|test] Add more ttest (gen_tcp) test cases Added a number of ttest test cases with transport gen_tcp. Server with transport = gen_tcp, active = false and client using transport = gen_tcp (and active = false, once and true). Also "fixed" the gen_tcp socket buffer size (default size was way to small for the "large" messages). OTP-14831 --- erts/emulator/test/socket_SUITE.erl | 466 ++++++++++++++++++++- .../emulator/test/socket_test_ttest_tcp_client.erl | 9 +- erts/emulator/test/socket_test_ttest_tcp_gen.erl | 19 +- .../emulator/test/socket_test_ttest_tcp_server.erl | 21 +- .../test/socket_test_ttest_tcp_server_gen.erl | 20 +- .../test/socket_test_ttest_tcp_server_socket.erl | 20 +- .../emulator/test/socket_test_ttest_tcp_socket.erl | 11 + 7 files changed, 518 insertions(+), 48 deletions(-) diff --git a/erts/emulator/test/socket_SUITE.erl b/erts/emulator/test/socket_SUITE.erl index 72fea0a40a..11d1f1ad5f 100644 --- a/erts/emulator/test/socket_SUITE.erl +++ b/erts/emulator/test/socket_SUITE.erl @@ -32,7 +32,7 @@ %% Test cases -export([ - %% API Basic + %% *** API Basic *** api_b_open_and_close_udp4/1, api_b_open_and_close_tcp4/1, api_b_sendto_and_recvfrom_udp4/1, @@ -40,11 +40,11 @@ api_b_send_and_recv_tcp4/1, api_b_sendmsg_and_recvmsg_tcp4/1, - %% API Options + %% *** API Options *** api_opt_simple_otp_options/1, api_opt_simple_otp_controlling_process/1, - %% API Operation Timeout + %% *** API Operation Timeout *** api_to_connect_tcp4/1, api_to_connect_tcp6/1, api_to_accept_tcp4/1, @@ -68,7 +68,7 @@ api_to_recvmsg_tcp4/1, api_to_recvmsg_tcp6/1, - %% Socket Closure + %% *** Socket Closure *** sc_cpe_socket_cleanup_tcp4/1, sc_cpe_socket_cleanup_tcp6/1, sc_cpe_socket_cleanup_udp4/1, @@ -95,7 +95,7 @@ sc_rs_recvmsg_send_shutdown_receive_tcp4/1, sc_rs_recvmsg_send_shutdown_receive_tcp6/1, - %% Traffic + %% *** Traffic *** traffic_send_and_recv_chunks_tcp4/1, traffic_send_and_recv_chunks_tcp6/1, @@ -123,15 +123,62 @@ traffic_ping_pong_medium_sendmsg_and_recvmsg_udp4/1, traffic_ping_pong_medium_sendmsg_and_recvmsg_udp6/1, - %% Time Test + %% *** Time Test *** + %% Server: transport = gen_tcp, active = false + %% Client: transport = gen_tcp ttest_sgenf_cgenf_small_tcp4/1, - ttest_sgenf_cgenf_small_tcp6/1%% , + ttest_sgenf_cgenf_small_tcp6/1, + ttest_sgenf_cgenf_medium_tcp4/1, + ttest_sgenf_cgenf_medium_tcp6/1, + ttest_sgenf_cgenf_large_tcp4/1, + ttest_sgenf_cgenf_large_tcp6/1, - %% ttest_sgenf_cgenf_medium_tcp4/1, - %% ttest_sgenf_cgenf_medium_tcp6/1, + ttest_sgenf_cgeno_small_tcp4/1, + ttest_sgenf_cgeno_small_tcp6/1, + ttest_sgenf_cgeno_medium_tcp4/1, + ttest_sgenf_cgeno_medium_tcp6/1, + ttest_sgenf_cgeno_large_tcp4/1, + ttest_sgenf_cgeno_large_tcp6/1, - %% ttest_sgenf_cgenf_large_tcp4/1, - %% ttest_sgenf_cgenf_large_tcp6/1 + ttest_sgenf_cgent_small_tcp4/1, + ttest_sgenf_cgent_small_tcp6/1, + ttest_sgenf_cgent_medium_tcp4/1, + ttest_sgenf_cgent_medium_tcp6/1, + ttest_sgenf_cgent_large_tcp4/1, + ttest_sgenf_cgent_large_tcp6/1 + + %% Server: transport = gen_tcp, active = false + %% Client: transport = socket(tcp) + + %% Server: transport = gen_tcp, active = once + %% Client: transport = gen_tcp + + %% Server: transport = gen_tcp, active = once + %% Client: transport = socket(tcp) + + %% Server: transport = gen_tcp, active = true + %% Client: transport = gen_tcp + + %% Server: transport = gen_tcp, active = true + %% Client: transport = socket(tcp) + + %% Server: transport = socket(tcp), active = false + %% Client: transport = gen_tcp + + %% Server: transport = socket(tcp), active = false + %% Client: transport = socket(tcp) + + %% Server: transport = socket(tcp), active = once + %% Client: transport = gen_tcp + + %% Server: transport = socket(tcp), active = once + %% Client: transport = socket(tcp) + + %% Server: transport = socket(tcp), active = true + %% Client: transport = gen_tcp + + %% Server: transport = socket(tcp), active = true + %% Client: transport = socket(tcp) %% Tickets ]). @@ -343,14 +390,38 @@ traffic_cases() -> ttest_cases() -> [ + %% Server: transport = gen_tcp, active = false + %% Client: transport = gen_tcp, active = false ttest_sgenf_cgenf_small_tcp4, - ttest_sgenf_cgenf_small_tcp6%% , + ttest_sgenf_cgenf_small_tcp6, + + ttest_sgenf_cgenf_medium_tcp4, + ttest_sgenf_cgenf_medium_tcp6, + + ttest_sgenf_cgenf_large_tcp4, + ttest_sgenf_cgenf_large_tcp6, + + %% Server: transport = gen_tcp, active = false + %% Client: transport = gen_tcp, active = once + ttest_sgenf_cgeno_small_tcp4, + ttest_sgenf_cgeno_small_tcp6, - %% ttest_sgenf_cgenf_medium_tcp4, - %% ttest_sgenf_cgenf_medium_tcp6, + ttest_sgenf_cgeno_medium_tcp4, + ttest_sgenf_cgeno_medium_tcp6, - %% ttest_sgenf_cgenf_large_tcp4, - %% ttest_sgenf_cgenf_large_tcp6 + ttest_sgenf_cgeno_large_tcp4, + ttest_sgenf_cgeno_large_tcp6, + + %% Server: transport = gen_tcp, active = false + %% Client: transport = gen_tcp, active = true + ttest_sgenf_cgent_small_tcp4, + ttest_sgenf_cgent_small_tcp6, + + ttest_sgenf_cgent_medium_tcp4, + ttest_sgenf_cgent_medium_tcp6, + + ttest_sgenf_cgent_large_tcp4, + ttest_sgenf_cgent_large_tcp6 ]. @@ -10161,6 +10232,11 @@ tpp_udp_sock_close(Sock) -> %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% This test case uses the time test (ttest) utility to implement a %% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = false +%% Client: Transport = gen_tcp, Active = false +%% Message Size: small (=1) +%% Domain: inet +%% ttest_sgenf_cgenf_small_tcp4(suite) -> []; @@ -10176,6 +10252,12 @@ ttest_sgenf_cgenf_small_tcp4(_Config) when is_list(_Config) -> %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = false +%% Client: Transport = gen_tcp, Active = false +%% Message Size: small (=1) +%% Domain: inet6 %% ttest_sgenf_cgenf_small_tcp6(suite) -> @@ -10191,6 +10273,358 @@ ttest_sgenf_cgenf_small_tcp6(_Config) when is_list(_Config) -> +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = false +%% Client: Transport = gen_tcp, Active = false +%% Message Size: medium (=2) +%% Domain: inet +%% + +ttest_sgenf_cgenf_medium_tcp4(suite) -> + []; +ttest_sgenf_cgenf_medium_tcp4(doc) -> + []; +ttest_sgenf_cgenf_medium_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgenf_cgenf_medium_tcp4, + inet, + gen, false, + gen, false, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = false +%% Client: Transport = gen_tcp, Active = false +%% Message Size: medium (=2) +%% Domain: inet6 +%% + +ttest_sgenf_cgenf_medium_tcp6(suite) -> + []; +ttest_sgenf_cgenf_medium_tcp6(doc) -> + []; +ttest_sgenf_cgenf_medium_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgenf_cgenf_medium_tcp6, + inet6, + gen, false, + gen, false, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = false +%% Client: Transport = gen_tcp, Active = false +%% Message Size: large (=3) +%% Domain: inet +%% + +ttest_sgenf_cgenf_large_tcp4(suite) -> + []; +ttest_sgenf_cgenf_large_tcp4(doc) -> + []; +ttest_sgenf_cgenf_large_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgenf_cgenf_large_tcp4, + inet, + gen, false, + gen, false, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = false +%% Client: Transport = gen_tcp, Active = false +%% Message Size: large (=3) +%% Domain: inet6 +%% + +ttest_sgenf_cgenf_large_tcp6(suite) -> + []; +ttest_sgenf_cgenf_large_tcp6(doc) -> + []; +ttest_sgenf_cgenf_large_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgenf_cgenf_large_tcp6, + inet6, + gen, false, + gen, false, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = false +%% Client: Transport = gen_tcp, Active = once +%% Message Size: small (=1) +%% Domain: inet +%% + +ttest_sgenf_cgeno_small_tcp4(suite) -> + []; +ttest_sgenf_cgeno_small_tcp4(doc) -> + []; +ttest_sgenf_cgeno_small_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgenf_cgeno_small_tcp4, + inet, + gen, false, + gen, once, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = false +%% Client: Transport = gen_tcp, Active = once +%% Message Size: small (=1) +%% Domain: inet6 +%% + +ttest_sgenf_cgeno_small_tcp6(suite) -> + []; +ttest_sgenf_cgeno_small_tcp6(doc) -> + []; +ttest_sgenf_cgeno_small_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgenf_cgeno_small_tcp6, + inet6, + gen, false, + gen, once, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = false +%% Client: Transport = gen_tcp, Active = once +%% Message Size: medium (=2) +%% Domain: inet +%% + +ttest_sgenf_cgeno_medium_tcp4(suite) -> + []; +ttest_sgenf_cgeno_medium_tcp4(doc) -> + []; +ttest_sgenf_cgeno_medium_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgenf_cgeno_medium_tcp4, + inet, + gen, false, + gen, once, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = false +%% Client: Transport = gen_tcp, Active = once +%% Message Size: medium (=2) +%% Domain: inet6 +%% + +ttest_sgenf_cgeno_medium_tcp6(suite) -> + []; +ttest_sgenf_cgeno_medium_tcp6(doc) -> + []; +ttest_sgenf_cgeno_medium_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgenf_cgeno_medium_tcp6, + inet6, + gen, false, + gen, once, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = false +%% Client: Transport = gen_tcp, Active = once +%% Message Size: large (=3) +%% Domain: inet +%% + +ttest_sgenf_cgeno_large_tcp4(suite) -> + []; +ttest_sgenf_cgeno_large_tcp4(doc) -> + []; +ttest_sgenf_cgeno_large_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgenf_cgeno_large_tcp4, + inet, + gen, false, + gen, once, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = false +%% Client: Transport = gen_tcp, Active = once +%% Message Size: large (=3) +%% Domain: inet6 +%% + +ttest_sgenf_cgeno_large_tcp6(suite) -> + []; +ttest_sgenf_cgeno_large_tcp6(doc) -> + []; +ttest_sgenf_cgeno_large_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgenf_cgeno_large_tcp6, + inet6, + gen, false, + gen, once, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = false +%% Client: Transport = gen_tcp, Active = true +%% Message Size: small (=1) +%% Domain: inet +%% + +ttest_sgenf_cgent_small_tcp4(suite) -> + []; +ttest_sgenf_cgent_small_tcp4(doc) -> + []; +ttest_sgenf_cgent_small_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgenf_cgent_small_tcp4, + inet, + gen, false, + gen, true, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = false +%% Client: Transport = gen_tcp, Active = true +%% Message Size: small (=1) +%% Domain: inet6 +%% + +ttest_sgenf_cgent_small_tcp6(suite) -> + []; +ttest_sgenf_cgent_small_tcp6(doc) -> + []; +ttest_sgenf_cgent_small_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgenf_cgeno_small_tcp6, + inet6, + gen, false, + gen, true, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = false +%% Client: Transport = gen_tcp, Active = true +%% Message Size: medium (=2) +%% Domain: inet +%% + +ttest_sgenf_cgent_medium_tcp4(suite) -> + []; +ttest_sgenf_cgent_medium_tcp4(doc) -> + []; +ttest_sgenf_cgent_medium_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgenf_cgent_medium_tcp4, + inet, + gen, false, + gen, true, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = false +%% Client: Transport = gen_tcp, Active = true +%% Message Size: medium (=2) +%% Domain: inet6 +%% + +ttest_sgenf_cgent_medium_tcp6(suite) -> + []; +ttest_sgenf_cgent_medium_tcp6(doc) -> + []; +ttest_sgenf_cgent_medium_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgenf_cgent_medium_tcp6, + inet6, + gen, false, + gen, true, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = false +%% Client: Transport = gen_tcp, Active = true +%% Message Size: large (=3) +%% Domain: inet +%% + +ttest_sgenf_cgent_large_tcp4(suite) -> + []; +ttest_sgenf_cgent_large_tcp4(doc) -> + []; +ttest_sgenf_cgent_large_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgenf_cgent_large_tcp4, + inet, + gen, false, + gen, true, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = false +%% Client: Transport = gen_tcp, Active = true +%% Message Size: large (=3) +%% Domain: inet6 +%% + +ttest_sgenf_cgent_large_tcp6(suite) -> + []; +ttest_sgenf_cgent_large_tcp6(doc) -> + []; +ttest_sgenf_cgent_large_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgenf_cgent_large_tcp6, + inet6, + gen, false, + gen, true, + 3, 2). + + + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% ttest_tcp(TC, diff --git a/erts/emulator/test/socket_test_ttest_tcp_client.erl b/erts/emulator/test/socket_test_ttest_tcp_client.erl index 180c280fb0..cfd16496cc 100644 --- a/erts/emulator/test/socket_test_ttest_tcp_client.erl +++ b/erts/emulator/test/socket_test_ttest_tcp_client.erl @@ -528,8 +528,13 @@ recv_reply_message3(_Mod, Sock, ID, Acc) -> (Tag =:= socket) -> process_acc_data(ID, <>) - %% after ?RECV_TIMEOUT -> - %% {error, timeout} + after ?RECV_TIMEOUT -> + ?I("timeout when" + "~n ID: ~p" + "~n size(Acc): ~p", + [ID, size(Acc)]), + %% {error, timeout} + recv_reply_message3(_Mod, Sock, ID, Acc) end. diff --git a/erts/emulator/test/socket_test_ttest_tcp_gen.erl b/erts/emulator/test/socket_test_ttest_tcp_gen.erl index de8822157d..604408c489 100644 --- a/erts/emulator/test/socket_test_ttest_tcp_gen.erl +++ b/erts/emulator/test/socket_test_ttest_tcp_gen.erl @@ -36,6 +36,20 @@ ]). +%% ========================================================================== + +%% getopt(Sock, Opt) when is_atom(Opt) -> +%% case inet:getopts(Sock, [Opt]) of +%% {ok, [{Opt, Value}]} -> +%% {ok, Value}; +%% {error, _} = ERROR -> +%% ERROR +%% end. + +%% setopt(Sock, Opt, Value) when is_atom(Opt) -> +%% inet:setopts(Sock, [{Opt, Value}]). + + %% ========================================================================== accept(Sock) -> @@ -65,7 +79,7 @@ close(Sock) -> connect(Addr, Port) -> - Opts = [binary, {packet, raw}, {active, false}], + Opts = [binary, {packet, raw}, {active, false}, {buffer, 32*1024}], case gen_tcp:connect(Addr, Port, Opts) of {ok, Sock} -> {ok, Sock}; @@ -82,7 +96,8 @@ listen() -> listen(0). listen(Port) when is_integer(Port) andalso (Port >= 0) -> - Opts = [binary, {ip, {0,0,0,0}}, {packet, raw}, {active, false}], + Opts = [binary, {ip, {0,0,0,0}}, {packet, raw}, {active, false}, + {buffer, 32*1024}], case gen_tcp:listen(Port, Opts) of {ok, Sock} -> {ok, Sock}; diff --git a/erts/emulator/test/socket_test_ttest_tcp_server.erl b/erts/emulator/test/socket_test_ttest_tcp_server.erl index 257bb18592..15f65d56b6 100644 --- a/erts/emulator/test/socket_test_ttest_tcp_server.erl +++ b/erts/emulator/test/socket_test_ttest_tcp_server.erl @@ -349,11 +349,12 @@ handler_loop(State) -> handler_loop( handler_handle_message( handler_recv_message(State) ) ). %% When passive, we read *one* request and then attempt to reply to it. -handler_recv_message(#{mod := Mod, - sock := Sock, - active := false, - mcnt := MCnt, - bcnt := BCnt} = State) -> +handler_recv_message(#{mod := Mod, + sock := Sock, + active := false, + mcnt := MCnt, + bcnt := BCnt, + last_reply := LID} = State) -> case handler_recv_message2(Mod, Sock) of {ok, {MsgSz, ID, Body}} -> handler_send_reply(Mod, Sock, ID, Body), @@ -363,6 +364,10 @@ handler_recv_message(#{mod := Mod, {error, closed} -> handler_done(State); {error, timeout} -> + ?I("timeout when: " + "~n MCnt: ~p" + "~n BCnt: ~p" + "~n LID: ~p", [MCnt, BCnt, LID]), State end; @@ -403,6 +408,11 @@ handler_recv_message(#{mod := Mod, end; {error, timeout} -> + ?I("timeout when: " + "~n MCnt: ~p" + "~n BCnt: ~p" + "~n LID: ~p" + "~n size(Acc): ~p", [MCnt, BCnt, LID, size(Acc)]), State end. @@ -443,7 +453,6 @@ handler_recv_message2(Mod, Sock) -> exit({recv, body, ID, SZ, BReason}) end; {error, timeout} = ERROR -> - ?I("timeout"), ERROR; {error, closed} = ERROR -> ERROR; diff --git a/erts/emulator/test/socket_test_ttest_tcp_server_gen.erl b/erts/emulator/test/socket_test_ttest_tcp_server_gen.erl index 425e3d74af..b1b31f5158 100644 --- a/erts/emulator/test/socket_test_ttest_tcp_server_gen.erl +++ b/erts/emulator/test/socket_test_ttest_tcp_server_gen.erl @@ -21,22 +21,20 @@ -module(socket_test_ttest_tcp_server_gen). -export([ - start_monitor/1, + start/1, stop/1 ]). -define(TRANSPORT_MOD, socket_test_ttest_tcp_gen). -start_monitor(Active) -> - case socket_test_ttest_tcp_server:start_monitor(node(), - ?TRANSPORT_MOD, - Active) of - {ok, {Pid, AddrPort}} -> - MRef = erlang:monitor(process, Pid), - {ok, {Pid, MRef, AddrPort}}; - {error, _} = ERROR -> - ERROR - end. +start(Active) -> + socket_test_ttest_tcp_server:start(?TRANSPORT_MOD, Active). + %% {ok, {Pid, AddrPort}} -> + %% MRef = erlang:monitor(process, Pid), + %% {ok, {Pid, MRef, AddrPort}}; + %% {error, _} = ERROR -> + %% ERROR + %% end. stop(Pid) -> diff --git a/erts/emulator/test/socket_test_ttest_tcp_server_socket.erl b/erts/emulator/test/socket_test_ttest_tcp_server_socket.erl index c78c7111ce..b7ea1e8e93 100644 --- a/erts/emulator/test/socket_test_ttest_tcp_server_socket.erl +++ b/erts/emulator/test/socket_test_ttest_tcp_server_socket.erl @@ -21,7 +21,7 @@ -module(socket_test_ttest_tcp_server_socket). -export([ - start_monitor/2, + start/2, stop/1 ]). @@ -30,16 +30,14 @@ %% stats_interval => 10000}}). -define(MOD(M), {?TRANSPORT_MOD, #{method => M}}). -start_monitor(Method, Active) -> - case socket_test_ttest_tcp_server:start_monitor(node(), - ?MOD(Method), - Active) of - {ok, {Pid, AddrPort}} -> - MRef = erlang:monitor(process, Pid), - {ok, {Pid, MRef, AddrPort}}; - {error, _} = ERROR -> - ERROR - end. +start(Method, Active) -> + socket_test_ttest_tcp_server:start(?MOD(Method), Active). + %% {ok, {Pid, AddrPort}} -> + %% MRef = erlang:monitor(process, Pid), + %% {ok, {Pid, MRef, AddrPort}}; + %% {error, _} = ERROR -> + %% ERROR + %% end. stop(Pid) -> socket_test_ttest_tcp_server:stop(Pid). diff --git a/erts/emulator/test/socket_test_ttest_tcp_socket.erl b/erts/emulator/test/socket_test_ttest_tcp_socket.erl index 9af25c5f50..0ae2412e4c 100644 --- a/erts/emulator/test/socket_test_ttest_tcp_socket.erl +++ b/erts/emulator/test/socket_test_ttest_tcp_socket.erl @@ -53,6 +53,17 @@ Reason}). +%% ========================================================================== + +%% This does not really work. Its just a placeholder for the time beeing... + +%% getopt(Sock, Opt) when is_atom(Opt) -> +%% socket:getopt(Sock, socket, Opt). + +%% setopt(Sock, Opt, Value) when is_atom(Opt) -> +%% socket:setopts(Sock, socket, Opt, Value). + + %% ========================================================================== accept(#{sock := LSock, opts := #{method := Method} = Opts}) -> -- cgit v1.2.3 From 9fa8a82995cfb6a7f8f2721a52e99b8557893310 Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Tue, 18 Dec 2018 16:42:40 +0100 Subject: [socket-nif|test] Add more ttest (gen_tcp and socket) test cases Added a number of ttest test cases with transport gen_tcp and socket. Server with transport = gen_tcp, active = false and client using transport = socket(tcp) (and active = false, once and true). Also a bit of "grouping". OTP-14831 --- erts/emulator/test/socket_SUITE.erl | 519 +++++++++++++++++++++++++++++++++++- 1 file changed, 509 insertions(+), 10 deletions(-) diff --git a/erts/emulator/test/socket_SUITE.erl b/erts/emulator/test/socket_SUITE.erl index 11d1f1ad5f..8a6e1eb3e4 100644 --- a/erts/emulator/test/socket_SUITE.erl +++ b/erts/emulator/test/socket_SUITE.erl @@ -145,10 +145,30 @@ ttest_sgenf_cgent_medium_tcp4/1, ttest_sgenf_cgent_medium_tcp6/1, ttest_sgenf_cgent_large_tcp4/1, - ttest_sgenf_cgent_large_tcp6/1 + ttest_sgenf_cgent_large_tcp6/1, %% Server: transport = gen_tcp, active = false %% Client: transport = socket(tcp) + ttest_sgenf_csockf_small_tcp4/1, + ttest_sgenf_csockf_small_tcp6/1, + ttest_sgenf_csockf_medium_tcp4/1, + ttest_sgenf_csockf_medium_tcp6/1, + ttest_sgenf_csockf_large_tcp4/1, + ttest_sgenf_csockf_large_tcp6/1, + + ttest_sgenf_csocko_small_tcp4/1, + ttest_sgenf_csocko_small_tcp6/1, + ttest_sgenf_csocko_medium_tcp4/1, + ttest_sgenf_csocko_medium_tcp6/1, + ttest_sgenf_csocko_large_tcp4/1, + ttest_sgenf_csocko_large_tcp6/1, + + ttest_sgenf_csockt_small_tcp4/1, + ttest_sgenf_csockt_small_tcp6/1, + ttest_sgenf_csockt_medium_tcp4/1, + ttest_sgenf_csockt_medium_tcp6/1, + ttest_sgenf_csockt_large_tcp4/1, + ttest_sgenf_csockt_large_tcp6/1 %% Server: transport = gen_tcp, active = once %% Client: transport = gen_tcp @@ -229,7 +249,10 @@ all() -> {group, api}, {group, socket_closure}, {group, traffic}, - {group, ttest} + {group, ttest}, + {group, ttest_sgenf_cgen}, + {group, ttest_sgenf_csock} + %% {group, tickets} ]. @@ -244,7 +267,16 @@ groups() -> {sc_remote_close, [], sc_rc_cases()}, {sc_remote_shutdown, [], sc_rs_cases()}, {traffic, [], traffic_cases()}, - {ttest, [], ttest_cases()} + {ttest, [], ttest_cases()}, + {ttest_sgenf_cgen, [], ttest_sgenf_cgen_cases()}, + {ttest_sgenf_cgenf, [], ttest_sgenf_cgenf_cases()}, + {ttest_sgenf_cgeno, [], ttest_sgenf_cgeno_cases()}, + {ttest_sgenf_cgent, [], ttest_sgenf_cgent_cases()}, + {ttest_sgenf_csock, [], ttest_sgenf_csock_cases()}, + {ttest_sgenf_csockf, [], ttest_sgenf_csockf_cases()}, + {ttest_sgenf_csocko, [], ttest_sgenf_csocko_cases()}, + {ttest_sgenf_csockt, [], ttest_sgenf_csockt_cases()} + %% {tickets, [], ticket_cases()} ]. @@ -391,7 +423,28 @@ traffic_cases() -> ttest_cases() -> [ %% Server: transport = gen_tcp, active = false - %% Client: transport = gen_tcp, active = false + %% Client: transport = gen_tcp (active = false, once and true) + {group, ttest_sgenf_cgen}, + + %% Server: transport = gen_tcp, active = false + %% Client: transport = socket(tcp) (active = false, once and true) + {group, ttest_sgenf_cgen} + ]. + + +%% Server: transport = gen_tcp, active = false +%% Client: transport = gen_tcp +ttest_sgenf_cgen_cases() -> + [ + {group, ttest_sgenf_cgenf}, + {group, ttest_sgenf_cgeno}, + {group, ttest_sgenf_cgent} + ]. + +%% Server: transport = gen_tcp, active = false +%% Client: transport = gen_tcp, active = false +ttest_sgenf_cgenf_cases() -> + [ ttest_sgenf_cgenf_small_tcp4, ttest_sgenf_cgenf_small_tcp6, @@ -399,10 +452,13 @@ ttest_cases() -> ttest_sgenf_cgenf_medium_tcp6, ttest_sgenf_cgenf_large_tcp4, - ttest_sgenf_cgenf_large_tcp6, + ttest_sgenf_cgenf_large_tcp6 + ]. - %% Server: transport = gen_tcp, active = false - %% Client: transport = gen_tcp, active = once +%% Server: transport = gen_tcp, active = false +%% Client: transport = gen_tcp, active = once +ttest_sgenf_cgeno_cases() -> + [ ttest_sgenf_cgeno_small_tcp4, ttest_sgenf_cgeno_small_tcp6, @@ -410,10 +466,13 @@ ttest_cases() -> ttest_sgenf_cgeno_medium_tcp6, ttest_sgenf_cgeno_large_tcp4, - ttest_sgenf_cgeno_large_tcp6, + ttest_sgenf_cgeno_large_tcp6 + ]. - %% Server: transport = gen_tcp, active = false - %% Client: transport = gen_tcp, active = true +%% Server: transport = gen_tcp, active = false +%% Client: transport = gen_tcp, active = true +ttest_sgenf_cgent_cases() -> + [ ttest_sgenf_cgent_small_tcp4, ttest_sgenf_cgent_small_tcp6, @@ -424,6 +483,50 @@ ttest_cases() -> ttest_sgenf_cgent_large_tcp6 ]. +%% Server: transport = gen_tcp, active = false +%% Client: transport = socket(tcp) +ttest_sgenf_csock_cases() -> + [ + {group, ttest_sgenf_csockf}, + {group, ttest_sgenf_csocko}, + {group, ttest_sgenf_csockt} + ]. + +ttest_sgenf_csockf_cases() -> + [ + ttest_sgenf_csockf_small_tcp4, + ttest_sgenf_csockf_small_tcp6, + + ttest_sgenf_csockf_medium_tcp4, + ttest_sgenf_csockf_medium_tcp6, + + ttest_sgenf_csockf_large_tcp4, + ttest_sgenf_csockf_large_tcp6 + ]. + +ttest_sgenf_csocko_cases() -> + [ + ttest_sgenf_csocko_small_tcp4, + ttest_sgenf_csocko_small_tcp6, + + ttest_sgenf_csocko_medium_tcp4, + ttest_sgenf_csocko_medium_tcp6, + + ttest_sgenf_csocko_large_tcp4, + ttest_sgenf_csocko_large_tcp6 + ]. + +ttest_sgenf_csockt_cases() -> + [ + ttest_sgenf_csockt_small_tcp4, + ttest_sgenf_csockt_small_tcp6, + + ttest_sgenf_csockt_medium_tcp4, + ttest_sgenf_csockt_medium_tcp6, + + ttest_sgenf_csockt_large_tcp4, + ttest_sgenf_csockt_large_tcp6 + ]. %% ticket_cases() -> %% []. @@ -10625,6 +10728,402 @@ ttest_sgenf_cgent_large_tcp6(_Config) when is_list(_Config) -> +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = false +%% Client: Transport = socket(tcp), Active = false +%% Message Size: small (=1) +%% Domain: inet +%% + +ttest_sgenf_csockf_small_tcp4(suite) -> + []; +ttest_sgenf_csockf_small_tcp4(doc) -> + []; +ttest_sgenf_csockf_small_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgenf_csockf_small_tcp4, + inet, + gen, false, + sock, false, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = false +%% Client: Transport = socket(tcp), Active = false +%% Message Size: small (=1) +%% Domain: inet6 +%% + +ttest_sgenf_csockf_small_tcp6(suite) -> + []; +ttest_sgenf_csockf_small_tcp6(doc) -> + []; +ttest_sgenf_csockf_small_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgenf_csockf_small_tcp6, + inet6, + gen, false, + sock, false, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = false +%% Client: Transport = socket(tcp), Active = false +%% Message Size: medium (=2) +%% Domain: inet +%% + +ttest_sgenf_csockf_medium_tcp4(suite) -> + []; +ttest_sgenf_csockf_medium_tcp4(doc) -> + []; +ttest_sgenf_csockf_medium_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgenf_csockf_medium_tcp4, + inet, + gen, false, + sock, false, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = false +%% Client: Transport = socket(tcp), Active = false +%% Message Size: medium (=2) +%% Domain: inet6 +%% + +ttest_sgenf_csockf_medium_tcp6(suite) -> + []; +ttest_sgenf_csockf_medium_tcp6(doc) -> + []; +ttest_sgenf_csockf_medium_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgenf_csockf_medium_tcp6, + inet6, + gen, false, + sock, false, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = false +%% Client: Transport = socket(tcp), Active = false +%% Message Size: large (=3) +%% Domain: inet +%% + +ttest_sgenf_csockf_large_tcp4(suite) -> + []; +ttest_sgenf_csockf_large_tcp4(doc) -> + []; +ttest_sgenf_csockf_large_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgenf_csockf_large_tcp4, + inet, + gen, false, + sock, false, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = false +%% Client: Transport = socket(tcp), Active = false +%% Message Size: large (=3) +%% Domain: inet6 +%% + +ttest_sgenf_csockf_large_tcp6(suite) -> + []; +ttest_sgenf_csockf_large_tcp6(doc) -> + []; +ttest_sgenf_csockf_large_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgenf_csockf_large_tcp6, + inet6, + gen, false, + sock, false, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = false +%% Client: Transport = socket(tcp), Active = once +%% Message Size: small (=1) +%% Domain: inet +%% + +ttest_sgenf_csocko_small_tcp4(suite) -> + []; +ttest_sgenf_csocko_small_tcp4(doc) -> + []; +ttest_sgenf_csocko_small_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgenf_csocko_small_tcp4, + inet, + gen, false, + sock, once, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = false +%% Client: Transport = socket(tcp), Active = once +%% Message Size: small (=1) +%% Domain: inet6 +%% + +ttest_sgenf_csocko_small_tcp6(suite) -> + []; +ttest_sgenf_csocko_small_tcp6(doc) -> + []; +ttest_sgenf_csocko_small_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgenf_csocko_small_tcp6, + inet6, + gen, false, + sock, once, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = false +%% Client: Transport = socket(tcp), Active = once +%% Message Size: medium (=2) +%% Domain: inet +%% + +ttest_sgenf_csocko_medium_tcp4(suite) -> + []; +ttest_sgenf_csocko_medium_tcp4(doc) -> + []; +ttest_sgenf_csocko_medium_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgenf_csocko_medium_tcp4, + inet, + gen, false, + sock, once, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = false +%% Client: Transport = socket(tcp), Active = once +%% Message Size: medium (=2) +%% Domain: inet6 +%% + +ttest_sgenf_csocko_medium_tcp6(suite) -> + []; +ttest_sgenf_csocko_medium_tcp6(doc) -> + []; +ttest_sgenf_csocko_medium_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgenf_csocko_medium_tcp6, + inet6, + gen, false, + sock, once, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = false +%% Client: Transport = socket(tcp), Active = once +%% Message Size: large (=3) +%% Domain: inet +%% + +ttest_sgenf_csocko_large_tcp4(suite) -> + []; +ttest_sgenf_csocko_large_tcp4(doc) -> + []; +ttest_sgenf_csocko_large_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgenf_csocko_large_tcp4, + inet, + gen, false, + sock, once, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = false +%% Client: Transport = socket(tcp), Active = once +%% Message Size: large (=3) +%% Domain: inet6 +%% + +ttest_sgenf_csocko_large_tcp6(suite) -> + []; +ttest_sgenf_csocko_large_tcp6(doc) -> + []; +ttest_sgenf_csocko_large_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgenf_csocko_large_tcp6, + inet6, + gen, false, + sock, once, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = false +%% Client: Transport = socket(tcp), Active = true +%% Message Size: small (=1) +%% Domain: inet +%% + +ttest_sgenf_csockt_small_tcp4(suite) -> + []; +ttest_sgenf_csockt_small_tcp4(doc) -> + []; +ttest_sgenf_csockt_small_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgenf_csockt_small_tcp4, + inet, + gen, false, + sock, true, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = false +%% Client: Transport = socket(tcp), Active = true +%% Message Size: small (=1) +%% Domain: inet6 +%% + +ttest_sgenf_csockt_small_tcp6(suite) -> + []; +ttest_sgenf_csockt_small_tcp6(doc) -> + []; +ttest_sgenf_csockt_small_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgenf_csocko_small_tcp6, + inet6, + gen, false, + sock, true, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = false +%% Client: Transport = socket(tcp), Active = true +%% Message Size: medium (=2) +%% Domain: inet +%% + +ttest_sgenf_csockt_medium_tcp4(suite) -> + []; +ttest_sgenf_csockt_medium_tcp4(doc) -> + []; +ttest_sgenf_csockt_medium_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgenf_csockt_medium_tcp4, + inet, + gen, false, + sock, true, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = false +%% Client: Transport = socket(tcp), Active = true +%% Message Size: medium (=2) +%% Domain: inet6 +%% + +ttest_sgenf_csockt_medium_tcp6(suite) -> + []; +ttest_sgenf_csockt_medium_tcp6(doc) -> + []; +ttest_sgenf_csockt_medium_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgenf_csockt_medium_tcp6, + inet6, + gen, false, + sock, true, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = false +%% Client: Transport = socket(tcp), Active = true +%% Message Size: large (=3) +%% Domain: inet +%% + +ttest_sgenf_csockt_large_tcp4(suite) -> + []; +ttest_sgenf_csockt_large_tcp4(doc) -> + []; +ttest_sgenf_csockt_large_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgenf_csockt_large_tcp4, + inet, + gen, false, + sock, true, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = false +%% Client: Transport = socket(tcp), Active = true +%% Message Size: large (=3) +%% Domain: inet6 +%% + +ttest_sgenf_csockt_large_tcp6(suite) -> + []; +ttest_sgenf_csockt_large_tcp6(doc) -> + []; +ttest_sgenf_csockt_large_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgenf_csockt_large_tcp6, + inet6, + gen, false, + sock, true, + 3, 2). + + + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% ttest_tcp(TC, -- cgit v1.2.3 From 04e458b0caa911a9988174ea3a359351f56cc4a5 Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Tue, 18 Dec 2018 17:13:26 +0100 Subject: [socket-nif|test] Add more ttest (gen_tcp) test cases Added a number of ttest test cases with transport gen_tcp. Server with transport = gen_tcp, active = once and client using transport = gen_tcp (and active = false, once and true). Also a bit of client and server cleanup (remove and cleanup up some printouts). OTP-14831 --- erts/emulator/test/socket_SUITE.erl | 484 ++++++++++++++++++++- .../emulator/test/socket_test_ttest_tcp_client.erl | 46 +- .../emulator/test/socket_test_ttest_tcp_server.erl | 8 +- 3 files changed, 504 insertions(+), 34 deletions(-) diff --git a/erts/emulator/test/socket_SUITE.erl b/erts/emulator/test/socket_SUITE.erl index 8a6e1eb3e4..fee7c59178 100644 --- a/erts/emulator/test/socket_SUITE.erl +++ b/erts/emulator/test/socket_SUITE.erl @@ -168,10 +168,30 @@ ttest_sgenf_csockt_medium_tcp4/1, ttest_sgenf_csockt_medium_tcp6/1, ttest_sgenf_csockt_large_tcp4/1, - ttest_sgenf_csockt_large_tcp6/1 + ttest_sgenf_csockt_large_tcp6/1, %% Server: transport = gen_tcp, active = once %% Client: transport = gen_tcp + ttest_sgeno_cgenf_small_tcp4/1, + ttest_sgeno_cgenf_small_tcp6/1, + ttest_sgeno_cgenf_medium_tcp4/1, + ttest_sgeno_cgenf_medium_tcp6/1, + ttest_sgeno_cgenf_large_tcp4/1, + ttest_sgeno_cgenf_large_tcp6/1, + + ttest_sgeno_cgeno_small_tcp4/1, + ttest_sgeno_cgeno_small_tcp6/1, + ttest_sgeno_cgeno_medium_tcp4/1, + ttest_sgeno_cgeno_medium_tcp6/1, + ttest_sgeno_cgeno_large_tcp4/1, + ttest_sgeno_cgeno_large_tcp6/1, + + ttest_sgeno_cgent_small_tcp4/1, + ttest_sgeno_cgent_small_tcp6/1, + ttest_sgeno_cgent_medium_tcp4/1, + ttest_sgeno_cgent_medium_tcp6/1, + ttest_sgeno_cgent_large_tcp4/1, + ttest_sgeno_cgent_large_tcp6/1 %% Server: transport = gen_tcp, active = once %% Client: transport = socket(tcp) @@ -251,7 +271,8 @@ all() -> {group, traffic}, {group, ttest}, {group, ttest_sgenf_cgen}, - {group, ttest_sgenf_csock} + {group, ttest_sgenf_csock}, + {group, ttest_sgeno_cgen} %% {group, tickets} ]. @@ -275,7 +296,11 @@ groups() -> {ttest_sgenf_csock, [], ttest_sgenf_csock_cases()}, {ttest_sgenf_csockf, [], ttest_sgenf_csockf_cases()}, {ttest_sgenf_csocko, [], ttest_sgenf_csocko_cases()}, - {ttest_sgenf_csockt, [], ttest_sgenf_csockt_cases()} + {ttest_sgenf_csockt, [], ttest_sgenf_csockt_cases()}, + {ttest_sgeno_cgen, [], ttest_sgeno_cgen_cases()}, + {ttest_sgeno_cgenf, [], ttest_sgeno_cgenf_cases()}, + {ttest_sgeno_cgeno, [], ttest_sgeno_cgeno_cases()}, + {ttest_sgeno_cgent, [], ttest_sgeno_cgent_cases()} %% {tickets, [], ticket_cases()} ]. @@ -428,7 +453,11 @@ ttest_cases() -> %% Server: transport = gen_tcp, active = false %% Client: transport = socket(tcp) (active = false, once and true) - {group, ttest_sgenf_cgen} + {group, ttest_sgenf_cgen}, + + %% Server: transport = gen_tcp, active = once + %% Client: transport = gen_tcp (active = false, once and true) + {group, ttest_sgeno_cgen} ]. @@ -528,6 +557,57 @@ ttest_sgenf_csockt_cases() -> ttest_sgenf_csockt_large_tcp6 ]. +%% Server: transport = gen_tcp, active = once +%% Client: transport = gen_tcp +ttest_sgeno_cgen_cases() -> + [ + {group, ttest_sgeno_cgenf}, + {group, ttest_sgeno_cgeno}, + {group, ttest_sgeno_cgent} + ]. + +%% Server: transport = gen_tcp, active = once +%% Client: transport = gen_tcp, active = false +ttest_sgeno_cgenf_cases() -> + [ + ttest_sgeno_cgenf_small_tcp4, + ttest_sgeno_cgenf_small_tcp6, + + ttest_sgeno_cgenf_medium_tcp4, + ttest_sgeno_cgenf_medium_tcp6, + + ttest_sgeno_cgenf_large_tcp4, + ttest_sgeno_cgenf_large_tcp6 + ]. + +%% Server: transport = gen_tcp, active = once +%% Client: transport = gen_tcp, active = once +ttest_sgeno_cgeno_cases() -> + [ + ttest_sgeno_cgeno_small_tcp4, + ttest_sgeno_cgeno_small_tcp6, + + ttest_sgeno_cgeno_medium_tcp4, + ttest_sgeno_cgeno_medium_tcp6, + + ttest_sgeno_cgeno_large_tcp4, + ttest_sgeno_cgeno_large_tcp6 + ]. + +%% Server: transport = gen_tcp, active = once +%% Client: transport = gen_tcp, active = true +ttest_sgeno_cgent_cases() -> + [ + ttest_sgeno_cgent_small_tcp4, + ttest_sgeno_cgent_small_tcp6, + + ttest_sgeno_cgent_medium_tcp4, + ttest_sgeno_cgent_medium_tcp6, + + ttest_sgeno_cgent_large_tcp4, + ttest_sgeno_cgent_large_tcp6 + ]. + %% ticket_cases() -> %% []. @@ -11124,6 +11204,402 @@ ttest_sgenf_csockt_large_tcp6(_Config) when is_list(_Config) -> +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = once +%% Client: Transport = gen_tcp, Active = false +%% Message Size: small (=1) +%% Domain: inet +%% + +ttest_sgeno_cgenf_small_tcp4(suite) -> + []; +ttest_sgeno_cgenf_small_tcp4(doc) -> + []; +ttest_sgeno_cgenf_small_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgeno_cgenf_small_tcp4, + inet, + gen, once, + gen, false, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = once +%% Client: Transport = gen_tcp, Active = false +%% Message Size: small (=1) +%% Domain: inet6 +%% + +ttest_sgeno_cgenf_small_tcp6(suite) -> + []; +ttest_sgeno_cgenf_small_tcp6(doc) -> + []; +ttest_sgeno_cgenf_small_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgeno_cgenf_small_tcp6, + inet6, + gen, once, + gen, false, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = once +%% Client: Transport = gen_tcp, Active = false +%% Message Size: medium (=2) +%% Domain: inet +%% + +ttest_sgeno_cgenf_medium_tcp4(suite) -> + []; +ttest_sgeno_cgenf_medium_tcp4(doc) -> + []; +ttest_sgeno_cgenf_medium_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgeno_cgenf_medium_tcp4, + inet, + gen, once, + gen, false, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = once +%% Client: Transport = gen_tcp, Active = false +%% Message Size: medium (=2) +%% Domain: inet6 +%% + +ttest_sgeno_cgenf_medium_tcp6(suite) -> + []; +ttest_sgeno_cgenf_medium_tcp6(doc) -> + []; +ttest_sgeno_cgenf_medium_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgeno_cgenf_medium_tcp6, + inet6, + gen, once, + gen, false, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = once +%% Client: Transport = gen_tcp, Active = false +%% Message Size: large (=3) +%% Domain: inet +%% + +ttest_sgeno_cgenf_large_tcp4(suite) -> + []; +ttest_sgeno_cgenf_large_tcp4(doc) -> + []; +ttest_sgeno_cgenf_large_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgeno_cgenf_large_tcp4, + inet, + gen, once, + gen, false, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = once +%% Client: Transport = gen_tcp, Active = false +%% Message Size: large (=3) +%% Domain: inet6 +%% + +ttest_sgeno_cgenf_large_tcp6(suite) -> + []; +ttest_sgeno_cgenf_large_tcp6(doc) -> + []; +ttest_sgeno_cgenf_large_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgeno_cgenf_large_tcp6, + inet6, + gen, once, + gen, false, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = once +%% Client: Transport = gen_tcp, Active = once +%% Message Size: small (=1) +%% Domain: inet +%% + +ttest_sgeno_cgeno_small_tcp4(suite) -> + []; +ttest_sgeno_cgeno_small_tcp4(doc) -> + []; +ttest_sgeno_cgeno_small_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgeno_cgeno_small_tcp4, + inet, + gen, once, + gen, once, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = once +%% Client: Transport = gen_tcp, Active = once +%% Message Size: small (=1) +%% Domain: inet6 +%% + +ttest_sgeno_cgeno_small_tcp6(suite) -> + []; +ttest_sgeno_cgeno_small_tcp6(doc) -> + []; +ttest_sgeno_cgeno_small_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgeno_cgeno_small_tcp6, + inet6, + gen, once, + gen, once, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = once +%% Client: Transport = gen_tcp, Active = once +%% Message Size: medium (=2) +%% Domain: inet +%% + +ttest_sgeno_cgeno_medium_tcp4(suite) -> + []; +ttest_sgeno_cgeno_medium_tcp4(doc) -> + []; +ttest_sgeno_cgeno_medium_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgeno_cgeno_medium_tcp4, + inet, + gen, once, + gen, once, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = once +%% Client: Transport = gen_tcp, Active = once +%% Message Size: medium (=2) +%% Domain: inet6 +%% + +ttest_sgeno_cgeno_medium_tcp6(suite) -> + []; +ttest_sgeno_cgeno_medium_tcp6(doc) -> + []; +ttest_sgeno_cgeno_medium_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgeno_cgeno_medium_tcp6, + inet6, + gen, once, + gen, once, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = once +%% Client: Transport = gen_tcp, Active = once +%% Message Size: large (=3) +%% Domain: inet +%% + +ttest_sgeno_cgeno_large_tcp4(suite) -> + []; +ttest_sgeno_cgeno_large_tcp4(doc) -> + []; +ttest_sgeno_cgeno_large_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgeno_cgeno_large_tcp4, + inet, + gen, once, + gen, once, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = once +%% Client: Transport = gen_tcp, Active = once +%% Message Size: large (=3) +%% Domain: inet6 +%% + +ttest_sgeno_cgeno_large_tcp6(suite) -> + []; +ttest_sgeno_cgeno_large_tcp6(doc) -> + []; +ttest_sgeno_cgeno_large_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgeno_cgeno_large_tcp6, + inet6, + gen, once, + gen, once, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = once +%% Client: Transport = gen_tcp, Active = true +%% Message Size: small (=1) +%% Domain: inet +%% + +ttest_sgeno_cgent_small_tcp4(suite) -> + []; +ttest_sgeno_cgent_small_tcp4(doc) -> + []; +ttest_sgeno_cgent_small_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgeno_cgent_small_tcp4, + inet, + gen, once, + gen, true, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = once +%% Client: Transport = gen_tcp, Active = true +%% Message Size: small (=1) +%% Domain: inet6 +%% + +ttest_sgeno_cgent_small_tcp6(suite) -> + []; +ttest_sgeno_cgent_small_tcp6(doc) -> + []; +ttest_sgeno_cgent_small_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgeno_cgeno_small_tcp6, + inet6, + gen, once, + gen, true, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = once +%% Client: Transport = gen_tcp, Active = true +%% Message Size: medium (=2) +%% Domain: inet +%% + +ttest_sgeno_cgent_medium_tcp4(suite) -> + []; +ttest_sgeno_cgent_medium_tcp4(doc) -> + []; +ttest_sgeno_cgent_medium_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgeno_cgent_medium_tcp4, + inet, + gen, once, + gen, true, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = once +%% Client: Transport = gen_tcp, Active = true +%% Message Size: medium (=2) +%% Domain: inet6 +%% + +ttest_sgeno_cgent_medium_tcp6(suite) -> + []; +ttest_sgeno_cgent_medium_tcp6(doc) -> + []; +ttest_sgeno_cgent_medium_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgeno_cgent_medium_tcp6, + inet6, + gen, once, + gen, true, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = once +%% Client: Transport = gen_tcp, Active = true +%% Message Size: large (=3) +%% Domain: inet +%% + +ttest_sgeno_cgent_large_tcp4(suite) -> + []; +ttest_sgeno_cgent_large_tcp4(doc) -> + []; +ttest_sgeno_cgent_large_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgeno_cgent_large_tcp4, + inet, + gen, once, + gen, true, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = once +%% Client: Transport = gen_tcp, Active = true +%% Message Size: large (=3) +%% Domain: inet6 +%% + +ttest_sgeno_cgent_large_tcp6(suite) -> + []; +ttest_sgeno_cgent_large_tcp6(doc) -> + []; +ttest_sgeno_cgent_large_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgeno_cgent_large_tcp6, + inet6, + gen, once, + gen, true, + 3, 2). + + + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% ttest_tcp(TC, diff --git a/erts/emulator/test/socket_test_ttest_tcp_client.erl b/erts/emulator/test/socket_test_ttest_tcp_client.erl index cfd16496cc..bf34a8c2ab 100644 --- a/erts/emulator/test/socket_test_ttest_tcp_client.erl +++ b/erts/emulator/test/socket_test_ttest_tcp_client.erl @@ -96,16 +96,16 @@ start_monitor(Node, Notify, Transport, Active, Addr, Port, 3 = MsgID) -> start_monitor(Node, Notify, Transport, Active, Addr, Port, MsgID, MaxOutstanding, RunTime) when (Node =/= node()) -> - ?I("start_monitor -> entry with" - "~n Node: ~p" - "~n Transport: ~p" - "~n Active: ~p" - "~n Addr: ~p" - "~n Port: ~p" - "~n MsgID: ~p" - "~n MaxOutstanding: ~p" - "~n RunTime: ~p", - [Node, Transport, Active, Addr, Port, MsgID, MaxOutstanding, RunTime]), + %% ?I("start_monitor -> entry with" + %% "~n Node: ~p" + %% "~n Transport: ~p" + %% "~n Active: ~p" + %% "~n Addr: ~p" + %% "~n Port: ~p" + %% "~n MsgID: ~p" + %% "~n MaxOutstanding: ~p" + %% "~n RunTime: ~p", + %% [Node, Transport, Active, Addr, Port, MsgID, MaxOutstanding, RunTime]), Args = [self(), Notify, Transport, Active, Addr, Port, MsgID, MaxOutstanding, RunTime], case rpc:call(Node, ?MODULE, do_start, Args) of @@ -181,16 +181,16 @@ do_start(Parent, Notify, (is_integer(MsgID) andalso (MsgID >= 1) andalso (MsgID =< 3)) andalso (is_integer(MaxOutstanding) andalso (MaxOutstanding > 0)) andalso (is_integer(RunTime) andalso (RunTime > 0)) -> - ?I("do_start -> entry with" - "~n Parent: ~p" - "~n Transport: ~p" - "~n Active: ~p" - "~n Addr: ~p" - "~n Port: ~p" - "~n MsgID: ~p" - "~n MaxOutstanding: ~p" - "~n RunTime: ~p", - [Parent, Transport, Active, Addr, Port, MsgID, MaxOutstanding, RunTime]), + %% ?I("do_start -> entry with" + %% "~n Parent: ~p" + %% "~n Transport: ~p" + %% "~n Active: ~p" + %% "~n Addr: ~p" + %% "~n Port: ~p" + %% "~n MsgID: ~p" + %% "~n MaxOutstanding: ~p" + %% "~n RunTime: ~p", + %% [Parent, Transport, Active, Addr, Port, MsgID, MaxOutstanding, RunTime]), Starter = self(), Init = fun() -> put(sname, "client"), init(Starter, @@ -224,8 +224,6 @@ init(Starter, Transport, Active, Addr, Port, MsgID, MaxOutstanding, RunTime) -> ?I("init with" - "~n Starter: ~p" - "~n Parent: ~p" "~n Transport: ~p" "~n Active: ~p" "~n Addr: ~s" @@ -233,9 +231,7 @@ init(Starter, "~n Msg ID: ~p (=> 16 + ~w bytes)" "~n Max Outstanding: ~p" "~n (Suggested) Run Time: ~p ms", - [Starter, - Parent, - Transport, Active, inet:ntoa(Addr), Port, + [Transport, Active, inet:ntoa(Addr), Port, MsgID, size(which_msg_data(MsgID)), MaxOutstanding, RunTime]), {Mod, Connect} = process_transport(Transport), case Connect(Addr, Port) of diff --git a/erts/emulator/test/socket_test_ttest_tcp_server.erl b/erts/emulator/test/socket_test_ttest_tcp_server.erl index 15f65d56b6..7506e7f932 100644 --- a/erts/emulator/test/socket_test_ttest_tcp_server.erl +++ b/erts/emulator/test/socket_test_ttest_tcp_server.erl @@ -119,18 +119,16 @@ stop(Pid) when is_pid(Pid) -> %% ========================================================================== server_init(Starter, Parent, Transport, Active) -> - ?I("init -> entry with" - "~n Starter: ~p" - "~n Parent: ~p" + ?I("init with" "~n Transport: ~p" - "~n Active: ~p", [Starter, Parent, Transport, Active]), + "~n Active: ~p", [Transport, Active]), {Mod, Listen, StatsInterval} = process_transport(Transport, Active), case Listen(0) of {ok, LSock} -> case Mod:port(LSock) of {ok, Port} -> Addr = which_addr(), % This is just for convenience - ?I("init -> listening on:" + ?I("listening on:" "~n Addr: ~p (~s)" "~n Port: ~w" "~n", [Addr, inet:ntoa(Addr), Port]), -- cgit v1.2.3 From 453f5b597d42147321f6973b28d7b921fa628852 Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Tue, 18 Dec 2018 17:55:32 +0100 Subject: [socket-nif|test] Add more ttest (gen_tcp and socket) test cases Added a number of ttest test cases with transport gen_tcp and socket. Server with transport = gen_tcp, active = once and client using transport = socket(tcp) (and active = false, once and true). Also fixed a counting error in the server (forgot to include the header, 16 bytes, in the byte count). OTP-14831 --- erts/emulator/test/socket_SUITE.erl | 481 ++++++++++++++++++++- .../emulator/test/socket_test_ttest_tcp_server.erl | 2 +- 2 files changed, 477 insertions(+), 6 deletions(-) diff --git a/erts/emulator/test/socket_SUITE.erl b/erts/emulator/test/socket_SUITE.erl index fee7c59178..5bd8816ee7 100644 --- a/erts/emulator/test/socket_SUITE.erl +++ b/erts/emulator/test/socket_SUITE.erl @@ -191,10 +191,30 @@ ttest_sgeno_cgent_medium_tcp4/1, ttest_sgeno_cgent_medium_tcp6/1, ttest_sgeno_cgent_large_tcp4/1, - ttest_sgeno_cgent_large_tcp6/1 + ttest_sgeno_cgent_large_tcp6/1, %% Server: transport = gen_tcp, active = once %% Client: transport = socket(tcp) + ttest_sgeno_csockf_small_tcp4/1, + ttest_sgeno_csockf_small_tcp6/1, + ttest_sgeno_csockf_medium_tcp4/1, + ttest_sgeno_csockf_medium_tcp6/1, + ttest_sgeno_csockf_large_tcp4/1, + ttest_sgeno_csockf_large_tcp6/1, + + ttest_sgeno_csocko_small_tcp4/1, + ttest_sgeno_csocko_small_tcp6/1, + ttest_sgeno_csocko_medium_tcp4/1, + ttest_sgeno_csocko_medium_tcp6/1, + ttest_sgeno_csocko_large_tcp4/1, + ttest_sgeno_csocko_large_tcp6/1, + + ttest_sgeno_csockt_small_tcp4/1, + ttest_sgeno_csockt_small_tcp6/1, + ttest_sgeno_csockt_medium_tcp4/1, + ttest_sgeno_csockt_medium_tcp6/1, + ttest_sgeno_csockt_large_tcp4/1, + ttest_sgeno_csockt_large_tcp6/1 %% Server: transport = gen_tcp, active = true %% Client: transport = gen_tcp @@ -272,7 +292,8 @@ all() -> {group, ttest}, {group, ttest_sgenf_cgen}, {group, ttest_sgenf_csock}, - {group, ttest_sgeno_cgen} + {group, ttest_sgeno_cgen}, + {group, ttest_sgeno_csock} %% {group, tickets} ]. @@ -300,7 +321,11 @@ groups() -> {ttest_sgeno_cgen, [], ttest_sgeno_cgen_cases()}, {ttest_sgeno_cgenf, [], ttest_sgeno_cgenf_cases()}, {ttest_sgeno_cgeno, [], ttest_sgeno_cgeno_cases()}, - {ttest_sgeno_cgent, [], ttest_sgeno_cgent_cases()} + {ttest_sgeno_cgent, [], ttest_sgeno_cgent_cases()}, + {ttest_sgeno_csock, [], ttest_sgeno_csock_cases()}, + {ttest_sgeno_csockf, [], ttest_sgeno_csockf_cases()}, + {ttest_sgeno_csocko, [], ttest_sgeno_csocko_cases()}, + {ttest_sgeno_csockt, [], ttest_sgeno_csockt_cases()} %% {tickets, [], ticket_cases()} ]. @@ -453,11 +478,16 @@ ttest_cases() -> %% Server: transport = gen_tcp, active = false %% Client: transport = socket(tcp) (active = false, once and true) - {group, ttest_sgenf_cgen}, + {group, ttest_sgenf_csock}, %% Server: transport = gen_tcp, active = once %% Client: transport = gen_tcp (active = false, once and true) - {group, ttest_sgeno_cgen} + {group, ttest_sgeno_cgen}, + + %% Server: transport = gen_tcp, active = once + %% Client: transport = socket(tcp) (active = false, once and true) + {group, ttest_sgeno_csock} + ]. @@ -608,6 +638,51 @@ ttest_sgeno_cgent_cases() -> ttest_sgeno_cgent_large_tcp6 ]. +%% Server: transport = gen_tcp, active = once +%% Client: transport = socket(tcp) +ttest_sgeno_csock_cases() -> + [ + {group, ttest_sgeno_csockf}, + {group, ttest_sgeno_csocko}, + {group, ttest_sgeno_csockt} + ]. + +ttest_sgeno_csockf_cases() -> + [ + ttest_sgeno_csockf_small_tcp4, + ttest_sgeno_csockf_small_tcp6, + + ttest_sgeno_csockf_medium_tcp4, + ttest_sgeno_csockf_medium_tcp6, + + ttest_sgeno_csockf_large_tcp4, + ttest_sgeno_csockf_large_tcp6 + ]. + +ttest_sgeno_csocko_cases() -> + [ + ttest_sgeno_csocko_small_tcp4, + ttest_sgeno_csocko_small_tcp6, + + ttest_sgeno_csocko_medium_tcp4, + ttest_sgeno_csocko_medium_tcp6, + + ttest_sgeno_csocko_large_tcp4, + ttest_sgeno_csocko_large_tcp6 + ]. + +ttest_sgeno_csockt_cases() -> + [ + ttest_sgeno_csockt_small_tcp4, + ttest_sgeno_csockt_small_tcp6, + + ttest_sgeno_csockt_medium_tcp4, + ttest_sgeno_csockt_medium_tcp6, + + ttest_sgeno_csockt_large_tcp4, + ttest_sgeno_csockt_large_tcp6 + ]. + %% ticket_cases() -> %% []. @@ -11600,6 +11675,402 @@ ttest_sgeno_cgent_large_tcp6(_Config) when is_list(_Config) -> +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = once +%% Client: Transport = socket(tcp), Active = false +%% Message Size: small (=1) +%% Domain: inet +%% + +ttest_sgeno_csockf_small_tcp4(suite) -> + []; +ttest_sgeno_csockf_small_tcp4(doc) -> + []; +ttest_sgeno_csockf_small_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgeno_csockf_small_tcp4, + inet, + gen, once, + sock, false, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = once +%% Client: Transport = socket(tcp), Active = false +%% Message Size: small (=1) +%% Domain: inet6 +%% + +ttest_sgeno_csockf_small_tcp6(suite) -> + []; +ttest_sgeno_csockf_small_tcp6(doc) -> + []; +ttest_sgeno_csockf_small_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgeno_csockf_small_tcp6, + inet6, + gen, once, + sock, false, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = once +%% Client: Transport = socket(tcp), Active = false +%% Message Size: medium (=2) +%% Domain: inet +%% + +ttest_sgeno_csockf_medium_tcp4(suite) -> + []; +ttest_sgeno_csockf_medium_tcp4(doc) -> + []; +ttest_sgeno_csockf_medium_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgeno_csockf_medium_tcp4, + inet, + gen, once, + sock, false, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = once +%% Client: Transport = socket(tcp), Active = false +%% Message Size: medium (=2) +%% Domain: inet6 +%% + +ttest_sgeno_csockf_medium_tcp6(suite) -> + []; +ttest_sgeno_csockf_medium_tcp6(doc) -> + []; +ttest_sgeno_csockf_medium_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgeno_csockf_medium_tcp6, + inet6, + gen, once, + sock, false, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = once +%% Client: Transport = socket(tcp), Active = false +%% Message Size: large (=3) +%% Domain: inet +%% + +ttest_sgeno_csockf_large_tcp4(suite) -> + []; +ttest_sgeno_csockf_large_tcp4(doc) -> + []; +ttest_sgeno_csockf_large_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgeno_csockf_large_tcp4, + inet, + gen, once, + sock, false, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = once +%% Client: Transport = socket(tcp), Active = false +%% Message Size: large (=3) +%% Domain: inet6 +%% + +ttest_sgeno_csockf_large_tcp6(suite) -> + []; +ttest_sgeno_csockf_large_tcp6(doc) -> + []; +ttest_sgeno_csockf_large_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgeno_csockf_large_tcp6, + inet6, + gen, once, + sock, false, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = once +%% Client: Transport = socket(tcp), Active = once +%% Message Size: small (=1) +%% Domain: inet +%% + +ttest_sgeno_csocko_small_tcp4(suite) -> + []; +ttest_sgeno_csocko_small_tcp4(doc) -> + []; +ttest_sgeno_csocko_small_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgeno_csocko_small_tcp4, + inet, + gen, once, + sock, once, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = once +%% Client: Transport = socket(tcp), Active = once +%% Message Size: small (=1) +%% Domain: inet6 +%% + +ttest_sgeno_csocko_small_tcp6(suite) -> + []; +ttest_sgeno_csocko_small_tcp6(doc) -> + []; +ttest_sgeno_csocko_small_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgeno_csocko_small_tcp6, + inet6, + gen, once, + sock, once, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = once +%% Client: Transport = socket(tcp), Active = once +%% Message Size: medium (=2) +%% Domain: inet +%% + +ttest_sgeno_csocko_medium_tcp4(suite) -> + []; +ttest_sgeno_csocko_medium_tcp4(doc) -> + []; +ttest_sgeno_csocko_medium_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgeno_csocko_medium_tcp4, + inet, + gen, once, + sock, once, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = once +%% Client: Transport = socket(tcp), Active = once +%% Message Size: medium (=2) +%% Domain: inet6 +%% + +ttest_sgeno_csocko_medium_tcp6(suite) -> + []; +ttest_sgeno_csocko_medium_tcp6(doc) -> + []; +ttest_sgeno_csocko_medium_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgeno_csocko_medium_tcp6, + inet6, + gen, once, + sock, once, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = once +%% Client: Transport = socket(tcp), Active = once +%% Message Size: large (=3) +%% Domain: inet +%% + +ttest_sgeno_csocko_large_tcp4(suite) -> + []; +ttest_sgeno_csocko_large_tcp4(doc) -> + []; +ttest_sgeno_csocko_large_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgeno_csocko_large_tcp4, + inet, + gen, once, + sock, once, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = once +%% Client: Transport = socket(tcp), Active = once +%% Message Size: large (=3) +%% Domain: inet6 +%% + +ttest_sgeno_csocko_large_tcp6(suite) -> + []; +ttest_sgeno_csocko_large_tcp6(doc) -> + []; +ttest_sgeno_csocko_large_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgeno_csocko_large_tcp6, + inet6, + gen, once, + sock, once, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = once +%% Client: Transport = socket(tcp), Active = true +%% Message Size: small (=1) +%% Domain: inet +%% + +ttest_sgeno_csockt_small_tcp4(suite) -> + []; +ttest_sgeno_csockt_small_tcp4(doc) -> + []; +ttest_sgeno_csockt_small_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgeno_csockt_small_tcp4, + inet, + gen, once, + sock, true, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = once +%% Client: Transport = socket(tcp), Active = true +%% Message Size: small (=1) +%% Domain: inet6 +%% + +ttest_sgeno_csockt_small_tcp6(suite) -> + []; +ttest_sgeno_csockt_small_tcp6(doc) -> + []; +ttest_sgeno_csockt_small_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgeno_csocko_small_tcp6, + inet6, + gen, once, + sock, true, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = once +%% Client: Transport = socket(tcp), Active = true +%% Message Size: medium (=2) +%% Domain: inet +%% + +ttest_sgeno_csockt_medium_tcp4(suite) -> + []; +ttest_sgeno_csockt_medium_tcp4(doc) -> + []; +ttest_sgeno_csockt_medium_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgeno_csockt_medium_tcp4, + inet, + gen, once, + sock, true, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = once +%% Client: Transport = socket(tcp), Active = true +%% Message Size: medium (=2) +%% Domain: inet6 +%% + +ttest_sgeno_csockt_medium_tcp6(suite) -> + []; +ttest_sgeno_csockt_medium_tcp6(doc) -> + []; +ttest_sgeno_csockt_medium_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgeno_csockt_medium_tcp6, + inet6, + gen, once, + sock, true, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = once +%% Client: Transport = socket(tcp), Active = true +%% Message Size: large (=3) +%% Domain: inet +%% + +ttest_sgeno_csockt_large_tcp4(suite) -> + []; +ttest_sgeno_csockt_large_tcp4(doc) -> + []; +ttest_sgeno_csockt_large_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgeno_csockt_large_tcp4, + inet, + gen, once, + sock, true, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = once +%% Client: Transport = socket(tcp), Active = true +%% Message Size: large (=3) +%% Domain: inet6 +%% + +ttest_sgeno_csockt_large_tcp6(suite) -> + []; +ttest_sgeno_csockt_large_tcp6(doc) -> + []; +ttest_sgeno_csockt_large_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgeno_csockt_large_tcp6, + inet6, + gen, once, + sock, true, + 3, 2). + + + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% ttest_tcp(TC, diff --git a/erts/emulator/test/socket_test_ttest_tcp_server.erl b/erts/emulator/test/socket_test_ttest_tcp_server.erl index 7506e7f932..e8d626e3d8 100644 --- a/erts/emulator/test/socket_test_ttest_tcp_server.erl +++ b/erts/emulator/test/socket_test_ttest_tcp_server.erl @@ -428,7 +428,7 @@ handler_process_data(<> = Rest, case handler_send_reply(Mod, Sock, ID, Body) of ok -> - handler_process_data(Rest2, Mod, Sock, MCnt+1, BCnt+SZ, ID); + handler_process_data(Rest2, Mod, Sock, MCnt+1, BCnt+16+SZ, ID); {error, _} = ERROR -> ERROR end; -- cgit v1.2.3 From aca866077bb611ed4c993e3d0dcf7f127570bce7 Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Wed, 19 Dec 2018 11:03:25 +0100 Subject: [socket-nif|test] Add more ttest (gen_tcp) test cases Added a number of ttest test cases with transport gen_tcp. Server with transport = gen_tcp, active = true and client using transport = gen_tcp (and active = false, once and true). OTP-14831 --- erts/emulator/test/socket_SUITE.erl | 484 +++++++++++++++++++++++++++++++++++- 1 file changed, 480 insertions(+), 4 deletions(-) diff --git a/erts/emulator/test/socket_SUITE.erl b/erts/emulator/test/socket_SUITE.erl index 5bd8816ee7..aea1c1fc54 100644 --- a/erts/emulator/test/socket_SUITE.erl +++ b/erts/emulator/test/socket_SUITE.erl @@ -214,10 +214,30 @@ ttest_sgeno_csockt_medium_tcp4/1, ttest_sgeno_csockt_medium_tcp6/1, ttest_sgeno_csockt_large_tcp4/1, - ttest_sgeno_csockt_large_tcp6/1 + ttest_sgeno_csockt_large_tcp6/1, %% Server: transport = gen_tcp, active = true %% Client: transport = gen_tcp + ttest_sgent_cgenf_small_tcp4/1, + ttest_sgent_cgenf_small_tcp6/1, + ttest_sgent_cgenf_medium_tcp4/1, + ttest_sgent_cgenf_medium_tcp6/1, + ttest_sgent_cgenf_large_tcp4/1, + ttest_sgent_cgenf_large_tcp6/1, + + ttest_sgent_cgeno_small_tcp4/1, + ttest_sgent_cgeno_small_tcp6/1, + ttest_sgent_cgeno_medium_tcp4/1, + ttest_sgent_cgeno_medium_tcp6/1, + ttest_sgent_cgeno_large_tcp4/1, + ttest_sgent_cgeno_large_tcp6/1, + + ttest_sgent_cgent_small_tcp4/1, + ttest_sgent_cgent_small_tcp6/1, + ttest_sgent_cgent_medium_tcp4/1, + ttest_sgent_cgent_medium_tcp6/1, + ttest_sgent_cgent_large_tcp4/1, + ttest_sgent_cgent_large_tcp6/1 %% Server: transport = gen_tcp, active = true %% Client: transport = socket(tcp) @@ -293,7 +313,8 @@ all() -> {group, ttest_sgenf_cgen}, {group, ttest_sgenf_csock}, {group, ttest_sgeno_cgen}, - {group, ttest_sgeno_csock} + {group, ttest_sgeno_csock}, + {group, ttest_sgent_cgen} %% {group, tickets} ]. @@ -325,7 +346,11 @@ groups() -> {ttest_sgeno_csock, [], ttest_sgeno_csock_cases()}, {ttest_sgeno_csockf, [], ttest_sgeno_csockf_cases()}, {ttest_sgeno_csocko, [], ttest_sgeno_csocko_cases()}, - {ttest_sgeno_csockt, [], ttest_sgeno_csockt_cases()} + {ttest_sgeno_csockt, [], ttest_sgeno_csockt_cases()}, + {ttest_sgent_cgen, [], ttest_sgent_cgen_cases()}, + {ttest_sgent_cgenf, [], ttest_sgent_cgenf_cases()}, + {ttest_sgent_cgeno, [], ttest_sgent_cgeno_cases()}, + {ttest_sgent_cgent, [], ttest_sgent_cgent_cases()} %% {tickets, [], ticket_cases()} ]. @@ -486,7 +511,11 @@ ttest_cases() -> %% Server: transport = gen_tcp, active = once %% Client: transport = socket(tcp) (active = false, once and true) - {group, ttest_sgeno_csock} + {group, ttest_sgeno_csock}, + + %% Server: transport = gen_tcp, active = true + %% Client: transport = gen_tcp (active = false, once and true) + {group, ttest_sgent_cgen} ]. @@ -683,6 +712,57 @@ ttest_sgeno_csockt_cases() -> ttest_sgeno_csockt_large_tcp6 ]. +%% Server: transport = gen_tcp, active = true +%% Client: transport = gen_tcp +ttest_sgent_cgen_cases() -> + [ + {group, ttest_sgent_cgenf}, + {group, ttest_sgent_cgeno}, + {group, ttest_sgent_cgent} + ]. + +%% Server: transport = gen_tcp, active = true +%% Client: transport = gen_tcp, active = false +ttest_sgent_cgenf_cases() -> + [ + ttest_sgent_cgenf_small_tcp4, + ttest_sgent_cgenf_small_tcp6, + + ttest_sgent_cgenf_medium_tcp4, + ttest_sgent_cgenf_medium_tcp6, + + ttest_sgent_cgenf_large_tcp4, + ttest_sgent_cgenf_large_tcp6 + ]. + +%% Server: transport = gen_tcp, active = true +%% Client: transport = gen_tcp, active = once +ttest_sgent_cgeno_cases() -> + [ + ttest_sgent_cgeno_small_tcp4, + ttest_sgent_cgeno_small_tcp6, + + ttest_sgent_cgeno_medium_tcp4, + ttest_sgent_cgeno_medium_tcp6, + + ttest_sgent_cgeno_large_tcp4, + ttest_sgent_cgeno_large_tcp6 + ]. + +%% Server: transport = gen_tcp, active = true +%% Client: transport = gen_tcp, active = true +ttest_sgent_cgent_cases() -> + [ + ttest_sgent_cgent_small_tcp4, + ttest_sgent_cgent_small_tcp6, + + ttest_sgent_cgent_medium_tcp4, + ttest_sgent_cgent_medium_tcp6, + + ttest_sgent_cgent_large_tcp4, + ttest_sgent_cgent_large_tcp6 + ]. + %% ticket_cases() -> %% []. @@ -12071,6 +12151,402 @@ ttest_sgeno_csockt_large_tcp6(_Config) when is_list(_Config) -> +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = true +%% Client: Transport = gen_tcp, Active = false +%% Message Size: small (=1) +%% Domain: inet +%% + +ttest_sgent_cgenf_small_tcp4(suite) -> + []; +ttest_sgent_cgenf_small_tcp4(doc) -> + []; +ttest_sgent_cgenf_small_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgent_cgenf_small_tcp4, + inet, + gen, true, + gen, false, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = true +%% Client: Transport = gen_tcp, Active = false +%% Message Size: small (=1) +%% Domain: inet6 +%% + +ttest_sgent_cgenf_small_tcp6(suite) -> + []; +ttest_sgent_cgenf_small_tcp6(doc) -> + []; +ttest_sgent_cgenf_small_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgent_cgenf_small_tcp6, + inet6, + gen, true, + gen, false, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = true +%% Client: Transport = gen_tcp, Active = false +%% Message Size: medium (=2) +%% Domain: inet +%% + +ttest_sgent_cgenf_medium_tcp4(suite) -> + []; +ttest_sgent_cgenf_medium_tcp4(doc) -> + []; +ttest_sgent_cgenf_medium_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgent_cgenf_medium_tcp4, + inet, + gen, true, + gen, false, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = true +%% Client: Transport = gen_tcp, Active = false +%% Message Size: medium (=2) +%% Domain: inet6 +%% + +ttest_sgent_cgenf_medium_tcp6(suite) -> + []; +ttest_sgent_cgenf_medium_tcp6(doc) -> + []; +ttest_sgent_cgenf_medium_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgent_cgenf_medium_tcp6, + inet6, + gen, true, + gen, false, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = true +%% Client: Transport = gen_tcp, Active = false +%% Message Size: large (=3) +%% Domain: inet +%% + +ttest_sgent_cgenf_large_tcp4(suite) -> + []; +ttest_sgent_cgenf_large_tcp4(doc) -> + []; +ttest_sgent_cgenf_large_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgent_cgenf_large_tcp4, + inet, + gen, true, + gen, false, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = true +%% Client: Transport = gen_tcp, Active = false +%% Message Size: large (=3) +%% Domain: inet6 +%% + +ttest_sgent_cgenf_large_tcp6(suite) -> + []; +ttest_sgent_cgenf_large_tcp6(doc) -> + []; +ttest_sgent_cgenf_large_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgent_cgenf_large_tcp6, + inet6, + gen, true, + gen, false, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = true +%% Client: Transport = gen_tcp, Active = once +%% Message Size: small (=1) +%% Domain: inet +%% + +ttest_sgent_cgeno_small_tcp4(suite) -> + []; +ttest_sgent_cgeno_small_tcp4(doc) -> + []; +ttest_sgent_cgeno_small_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgent_cgeno_small_tcp4, + inet, + gen, true, + gen, once, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = true +%% Client: Transport = gen_tcp, Active = once +%% Message Size: small (=1) +%% Domain: inet6 +%% + +ttest_sgent_cgeno_small_tcp6(suite) -> + []; +ttest_sgent_cgeno_small_tcp6(doc) -> + []; +ttest_sgent_cgeno_small_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgent_cgeno_small_tcp6, + inet6, + gen, true, + gen, once, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = true +%% Client: Transport = gen_tcp, Active = once +%% Message Size: medium (=2) +%% Domain: inet +%% + +ttest_sgent_cgeno_medium_tcp4(suite) -> + []; +ttest_sgent_cgeno_medium_tcp4(doc) -> + []; +ttest_sgent_cgeno_medium_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgent_cgeno_medium_tcp4, + inet, + gen, true, + gen, once, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = true +%% Client: Transport = gen_tcp, Active = once +%% Message Size: medium (=2) +%% Domain: inet6 +%% + +ttest_sgent_cgeno_medium_tcp6(suite) -> + []; +ttest_sgent_cgeno_medium_tcp6(doc) -> + []; +ttest_sgent_cgeno_medium_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgent_cgeno_medium_tcp6, + inet6, + gen, true, + gen, once, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = true +%% Client: Transport = gen_tcp, Active = once +%% Message Size: large (=3) +%% Domain: inet +%% + +ttest_sgent_cgeno_large_tcp4(suite) -> + []; +ttest_sgent_cgeno_large_tcp4(doc) -> + []; +ttest_sgent_cgeno_large_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgent_cgeno_large_tcp4, + inet, + gen, true, + gen, once, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = true +%% Client: Transport = gen_tcp, Active = once +%% Message Size: large (=3) +%% Domain: inet6 +%% + +ttest_sgent_cgeno_large_tcp6(suite) -> + []; +ttest_sgent_cgeno_large_tcp6(doc) -> + []; +ttest_sgent_cgeno_large_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgent_cgeno_large_tcp6, + inet6, + gen, true, + gen, once, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = true +%% Client: Transport = gen_tcp, Active = true +%% Message Size: small (=1) +%% Domain: inet +%% + +ttest_sgent_cgent_small_tcp4(suite) -> + []; +ttest_sgent_cgent_small_tcp4(doc) -> + []; +ttest_sgent_cgent_small_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgent_cgent_small_tcp4, + inet, + gen, true, + gen, true, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = true +%% Client: Transport = gen_tcp, Active = true +%% Message Size: small (=1) +%% Domain: inet6 +%% + +ttest_sgent_cgent_small_tcp6(suite) -> + []; +ttest_sgent_cgent_small_tcp6(doc) -> + []; +ttest_sgent_cgent_small_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgent_cgeno_small_tcp6, + inet6, + gen, true, + gen, true, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = true +%% Client: Transport = gen_tcp, Active = true +%% Message Size: medium (=2) +%% Domain: inet +%% + +ttest_sgent_cgent_medium_tcp4(suite) -> + []; +ttest_sgent_cgent_medium_tcp4(doc) -> + ["Server(gen,true), Client(gen,true), Domain=inet, msg=medium"]; +ttest_sgent_cgent_medium_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgent_cgent_medium_tcp4, + inet, + gen, true, + gen, true, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = true +%% Client: Transport = gen_tcp, Active = true +%% Message Size: medium (=2) +%% Domain: inet6 +%% + +ttest_sgent_cgent_medium_tcp6(suite) -> + []; +ttest_sgent_cgent_medium_tcp6(doc) -> + ["Server(gen,true), Client(gen,true), Domain=inet6, msg=medium"]; +ttest_sgent_cgent_medium_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgent_cgent_medium_tcp6, + inet6, + gen, true, + gen, true, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = true +%% Client: Transport = gen_tcp, Active = true +%% Message Size: large (=3) +%% Domain: inet +%% + +ttest_sgent_cgent_large_tcp4(suite) -> + []; +ttest_sgent_cgent_large_tcp4(doc) -> + ["Server(gen,true), Client(gen,true), Domain=inet, msg=large"]; +ttest_sgent_cgent_large_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgent_cgent_large_tcp4, + inet, + gen, true, + gen, true, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = true +%% Client: Transport = gen_tcp, Active = true +%% Message Size: large (=3) +%% Domain: inet6 +%% + +ttest_sgent_cgent_large_tcp6(suite) -> + []; +ttest_sgent_cgent_large_tcp6(doc) -> + ["Server(gen,true), Client(gen,true), Domain=inet6, msg=large"]; +ttest_sgent_cgent_large_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgent_cgent_large_tcp6, + inet6, + gen, true, + gen, true, + 3, 2). + + + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% ttest_tcp(TC, -- cgit v1.2.3 From e5d21513fde6b69a38d4eab8ab57ec7ac661c0df Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Wed, 19 Dec 2018 11:35:07 +0100 Subject: [socket-nif|test] Add more ttest (gen_tcp and socket) test cases Added a number of ttest test cases with transport gen_tcp and socket. Server with transport = gen_tcp, active = true and client using transport = socket(tcp) (and active = false, once and true). OTP-14831 --- erts/emulator/test/socket_SUITE.erl | 478 +++++++++++++++++++++++++++++++++++- 1 file changed, 474 insertions(+), 4 deletions(-) diff --git a/erts/emulator/test/socket_SUITE.erl b/erts/emulator/test/socket_SUITE.erl index aea1c1fc54..79fb37dc98 100644 --- a/erts/emulator/test/socket_SUITE.erl +++ b/erts/emulator/test/socket_SUITE.erl @@ -237,10 +237,30 @@ ttest_sgent_cgent_medium_tcp4/1, ttest_sgent_cgent_medium_tcp6/1, ttest_sgent_cgent_large_tcp4/1, - ttest_sgent_cgent_large_tcp6/1 + ttest_sgent_cgent_large_tcp6/1, %% Server: transport = gen_tcp, active = true %% Client: transport = socket(tcp) + ttest_sgent_csockf_small_tcp4/1, + ttest_sgent_csockf_small_tcp6/1, + ttest_sgent_csockf_medium_tcp4/1, + ttest_sgent_csockf_medium_tcp6/1, + ttest_sgent_csockf_large_tcp4/1, + ttest_sgent_csockf_large_tcp6/1, + + ttest_sgent_csocko_small_tcp4/1, + ttest_sgent_csocko_small_tcp6/1, + ttest_sgent_csocko_medium_tcp4/1, + ttest_sgent_csocko_medium_tcp6/1, + ttest_sgent_csocko_large_tcp4/1, + ttest_sgent_csocko_large_tcp6/1, + + ttest_sgent_csockt_small_tcp4/1, + ttest_sgent_csockt_small_tcp6/1, + ttest_sgent_csockt_medium_tcp4/1, + ttest_sgent_csockt_medium_tcp6/1, + ttest_sgent_csockt_large_tcp4/1, + ttest_sgent_csockt_large_tcp6/1 %% Server: transport = socket(tcp), active = false %% Client: transport = gen_tcp @@ -314,7 +334,8 @@ all() -> {group, ttest_sgenf_csock}, {group, ttest_sgeno_cgen}, {group, ttest_sgeno_csock}, - {group, ttest_sgent_cgen} + {group, ttest_sgent_cgen}, + {group, ttest_sgent_csock} %% {group, tickets} ]. @@ -350,7 +371,11 @@ groups() -> {ttest_sgent_cgen, [], ttest_sgent_cgen_cases()}, {ttest_sgent_cgenf, [], ttest_sgent_cgenf_cases()}, {ttest_sgent_cgeno, [], ttest_sgent_cgeno_cases()}, - {ttest_sgent_cgent, [], ttest_sgent_cgent_cases()} + {ttest_sgent_cgent, [], ttest_sgent_cgent_cases()}, + {ttest_sgent_csock, [], ttest_sgent_csock_cases()}, + {ttest_sgent_csockf, [], ttest_sgent_csockf_cases()}, + {ttest_sgent_csocko, [], ttest_sgent_csocko_cases()}, + {ttest_sgent_csockt, [], ttest_sgent_csockt_cases()} %% {tickets, [], ticket_cases()} ]. @@ -515,7 +540,11 @@ ttest_cases() -> %% Server: transport = gen_tcp, active = true %% Client: transport = gen_tcp (active = false, once and true) - {group, ttest_sgent_cgen} + {group, ttest_sgent_cgen}, + + %% Server: transport = gen_tcp, active = true + %% Client: transport = socket(tcp) (active = false, once and true) + {group, ttest_sgent_csock} ]. @@ -763,6 +792,51 @@ ttest_sgent_cgent_cases() -> ttest_sgent_cgent_large_tcp6 ]. +%% Server: transport = gen_tcp, active = true +%% Client: transport = socket(tcp) +ttest_sgent_csock_cases() -> + [ + {group, ttest_sgent_csockf}, + {group, ttest_sgent_csocko}, + {group, ttest_sgent_csockt} + ]. + +ttest_sgent_csockf_cases() -> + [ + ttest_sgent_csockf_small_tcp4, + ttest_sgent_csockf_small_tcp6, + + ttest_sgent_csockf_medium_tcp4, + ttest_sgent_csockf_medium_tcp6, + + ttest_sgent_csockf_large_tcp4, + ttest_sgent_csockf_large_tcp6 + ]. + +ttest_sgent_csocko_cases() -> + [ + ttest_sgent_csocko_small_tcp4, + ttest_sgent_csocko_small_tcp6, + + ttest_sgent_csocko_medium_tcp4, + ttest_sgent_csocko_medium_tcp6, + + ttest_sgent_csocko_large_tcp4, + ttest_sgent_csocko_large_tcp6 + ]. + +ttest_sgent_csockt_cases() -> + [ + ttest_sgent_csockt_small_tcp4, + ttest_sgent_csockt_small_tcp6, + + ttest_sgent_csockt_medium_tcp4, + ttest_sgent_csockt_medium_tcp6, + + ttest_sgent_csockt_large_tcp4, + ttest_sgent_csockt_large_tcp6 + ]. + %% ticket_cases() -> %% []. @@ -12547,6 +12621,402 @@ ttest_sgent_cgent_large_tcp6(_Config) when is_list(_Config) -> +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = true +%% Client: Transport = socket(tcp), Active = false +%% Message Size: small (=1) +%% Domain: inet +%% + +ttest_sgent_csockf_small_tcp4(suite) -> + []; +ttest_sgent_csockf_small_tcp4(doc) -> + []; +ttest_sgent_csockf_small_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgent_csockf_small_tcp4, + inet, + gen, true, + sock, false, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = true +%% Client: Transport = socket(tcp), Active = false +%% Message Size: small (=1) +%% Domain: inet6 +%% + +ttest_sgent_csockf_small_tcp6(suite) -> + []; +ttest_sgent_csockf_small_tcp6(doc) -> + []; +ttest_sgent_csockf_small_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgent_csockf_small_tcp6, + inet6, + gen, true, + sock, false, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = true +%% Client: Transport = socket(tcp), Active = false +%% Message Size: medium (=2) +%% Domain: inet +%% + +ttest_sgent_csockf_medium_tcp4(suite) -> + []; +ttest_sgent_csockf_medium_tcp4(doc) -> + []; +ttest_sgent_csockf_medium_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgent_csockf_medium_tcp4, + inet, + gen, true, + sock, false, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = true +%% Client: Transport = socket(tcp), Active = false +%% Message Size: medium (=2) +%% Domain: inet6 +%% + +ttest_sgent_csockf_medium_tcp6(suite) -> + []; +ttest_sgent_csockf_medium_tcp6(doc) -> + []; +ttest_sgent_csockf_medium_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgent_csockf_medium_tcp6, + inet6, + gen, true, + sock, false, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = true +%% Client: Transport = socket(tcp), Active = false +%% Message Size: large (=3) +%% Domain: inet +%% + +ttest_sgent_csockf_large_tcp4(suite) -> + []; +ttest_sgent_csockf_large_tcp4(doc) -> + []; +ttest_sgent_csockf_large_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgent_csockf_large_tcp4, + inet, + gen, true, + sock, false, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = true +%% Client: Transport = socket(tcp), Active = false +%% Message Size: large (=3) +%% Domain: inet6 +%% + +ttest_sgent_csockf_large_tcp6(suite) -> + []; +ttest_sgent_csockf_large_tcp6(doc) -> + []; +ttest_sgent_csockf_large_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgent_csockf_large_tcp6, + inet6, + gen, true, + sock, false, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = true +%% Client: Transport = socket(tcp), Active = once +%% Message Size: small (=1) +%% Domain: inet +%% + +ttest_sgent_csocko_small_tcp4(suite) -> + []; +ttest_sgent_csocko_small_tcp4(doc) -> + []; +ttest_sgent_csocko_small_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgent_csocko_small_tcp4, + inet, + gen, true, + sock, once, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = true +%% Client: Transport = socket(tcp), Active = once +%% Message Size: small (=1) +%% Domain: inet6 +%% + +ttest_sgent_csocko_small_tcp6(suite) -> + []; +ttest_sgent_csocko_small_tcp6(doc) -> + []; +ttest_sgent_csocko_small_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgent_csocko_small_tcp6, + inet6, + gen, true, + sock, once, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = true +%% Client: Transport = socket(tcp), Active = once +%% Message Size: medium (=2) +%% Domain: inet +%% + +ttest_sgent_csocko_medium_tcp4(suite) -> + []; +ttest_sgent_csocko_medium_tcp4(doc) -> + []; +ttest_sgent_csocko_medium_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgent_csocko_medium_tcp4, + inet, + gen, true, + sock, once, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = true +%% Client: Transport = socket(tcp), Active = once +%% Message Size: medium (=2) +%% Domain: inet6 +%% + +ttest_sgent_csocko_medium_tcp6(suite) -> + []; +ttest_sgent_csocko_medium_tcp6(doc) -> + []; +ttest_sgent_csocko_medium_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgent_csocko_medium_tcp6, + inet6, + gen, true, + sock, once, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = true +%% Client: Transport = socket(tcp), Active = once +%% Message Size: large (=3) +%% Domain: inet +%% + +ttest_sgent_csocko_large_tcp4(suite) -> + []; +ttest_sgent_csocko_large_tcp4(doc) -> + []; +ttest_sgent_csocko_large_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgent_csocko_large_tcp4, + inet, + gen, true, + sock, once, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = true +%% Client: Transport = socket(tcp), Active = once +%% Message Size: large (=3) +%% Domain: inet6 +%% + +ttest_sgent_csocko_large_tcp6(suite) -> + []; +ttest_sgent_csocko_large_tcp6(doc) -> + []; +ttest_sgent_csocko_large_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgent_csocko_large_tcp6, + inet6, + gen, true, + sock, once, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = true +%% Client: Transport = socket(tcp), Active = true +%% Message Size: small (=1) +%% Domain: inet +%% + +ttest_sgent_csockt_small_tcp4(suite) -> + []; +ttest_sgent_csockt_small_tcp4(doc) -> + []; +ttest_sgent_csockt_small_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgent_csockt_small_tcp4, + inet, + gen, true, + sock, true, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = true +%% Client: Transport = socket(tcp), Active = true +%% Message Size: small (=1) +%% Domain: inet6 +%% + +ttest_sgent_csockt_small_tcp6(suite) -> + []; +ttest_sgent_csockt_small_tcp6(doc) -> + []; +ttest_sgent_csockt_small_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgent_csocko_small_tcp6, + inet6, + gen, true, + sock, true, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = true +%% Client: Transport = socket(tcp), Active = true +%% Message Size: medium (=2) +%% Domain: inet +%% + +ttest_sgent_csockt_medium_tcp4(suite) -> + []; +ttest_sgent_csockt_medium_tcp4(doc) -> + []; +ttest_sgent_csockt_medium_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgent_csockt_medium_tcp4, + inet, + gen, true, + sock, true, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = true +%% Client: Transport = socket(tcp), Active = true +%% Message Size: medium (=2) +%% Domain: inet6 +%% + +ttest_sgent_csockt_medium_tcp6(suite) -> + []; +ttest_sgent_csockt_medium_tcp6(doc) -> + []; +ttest_sgent_csockt_medium_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgent_csockt_medium_tcp6, + inet6, + gen, true, + sock, true, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = true +%% Client: Transport = socket(tcp), Active = true +%% Message Size: large (=3) +%% Domain: inet +%% + +ttest_sgent_csockt_large_tcp4(suite) -> + []; +ttest_sgent_csockt_large_tcp4(doc) -> + []; +ttest_sgent_csockt_large_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgent_csockt_large_tcp4, + inet, + gen, true, + sock, true, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = true +%% Client: Transport = socket(tcp), Active = true +%% Message Size: large (=3) +%% Domain: inet6 +%% + +ttest_sgent_csockt_large_tcp6(suite) -> + []; +ttest_sgent_csockt_large_tcp6(doc) -> + []; +ttest_sgent_csockt_large_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgent_csockt_large_tcp6, + inet6, + gen, true, + sock, true, + 3, 2). + + + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% ttest_tcp(TC, -- cgit v1.2.3 From 2aa290ae65c2d391fc178a30bdc9bef3bd6549ff Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Wed, 19 Dec 2018 12:37:03 +0100 Subject: [socket-nif|test] Add more ttest (socket and gen_tcp) test cases Added a number of ttest test cases with transport socket and gen_tcp. Server with transport = socket(tcp), active = false and client using transport = gen_tcp (and active = false, once and true). OTP-14831 --- erts/emulator/test/socket_SUITE.erl | 484 +++++++++++++++++++++++++++++++++++- 1 file changed, 480 insertions(+), 4 deletions(-) diff --git a/erts/emulator/test/socket_SUITE.erl b/erts/emulator/test/socket_SUITE.erl index 79fb37dc98..bac9cf4434 100644 --- a/erts/emulator/test/socket_SUITE.erl +++ b/erts/emulator/test/socket_SUITE.erl @@ -260,10 +260,30 @@ ttest_sgent_csockt_medium_tcp4/1, ttest_sgent_csockt_medium_tcp6/1, ttest_sgent_csockt_large_tcp4/1, - ttest_sgent_csockt_large_tcp6/1 + ttest_sgent_csockt_large_tcp6/1, %% Server: transport = socket(tcp), active = false %% Client: transport = gen_tcp + ttest_ssockf_cgenf_small_tcp4/1, + ttest_ssockf_cgenf_small_tcp6/1, + ttest_ssockf_cgenf_medium_tcp4/1, + ttest_ssockf_cgenf_medium_tcp6/1, + ttest_ssockf_cgenf_large_tcp4/1, + ttest_ssockf_cgenf_large_tcp6/1, + + ttest_ssockf_cgeno_small_tcp4/1, + ttest_ssockf_cgeno_small_tcp6/1, + ttest_ssockf_cgeno_medium_tcp4/1, + ttest_ssockf_cgeno_medium_tcp6/1, + ttest_ssockf_cgeno_large_tcp4/1, + ttest_ssockf_cgeno_large_tcp6/1, + + ttest_ssockf_cgent_small_tcp4/1, + ttest_ssockf_cgent_small_tcp6/1, + ttest_ssockf_cgent_medium_tcp4/1, + ttest_ssockf_cgent_medium_tcp6/1, + ttest_ssockf_cgent_large_tcp4/1, + ttest_ssockf_cgent_large_tcp6/1 %% Server: transport = socket(tcp), active = false %% Client: transport = socket(tcp) @@ -335,7 +355,8 @@ all() -> {group, ttest_sgeno_cgen}, {group, ttest_sgeno_csock}, {group, ttest_sgent_cgen}, - {group, ttest_sgent_csock} + {group, ttest_sgent_csock}, + {group, ttest_ssockf_cgen} %% {group, tickets} ]. @@ -375,7 +396,11 @@ groups() -> {ttest_sgent_csock, [], ttest_sgent_csock_cases()}, {ttest_sgent_csockf, [], ttest_sgent_csockf_cases()}, {ttest_sgent_csocko, [], ttest_sgent_csocko_cases()}, - {ttest_sgent_csockt, [], ttest_sgent_csockt_cases()} + {ttest_sgent_csockt, [], ttest_sgent_csockt_cases()}, + {ttest_ssockf_cgen, [], ttest_ssockf_cgen_cases()}, + {ttest_ssockf_cgenf, [], ttest_ssockf_cgenf_cases()}, + {ttest_ssockf_cgeno, [], ttest_ssockf_cgeno_cases()}, + {ttest_ssockf_cgent, [], ttest_ssockf_cgent_cases()} %% {tickets, [], ticket_cases()} ]. @@ -544,7 +569,11 @@ ttest_cases() -> %% Server: transport = gen_tcp, active = true %% Client: transport = socket(tcp) (active = false, once and true) - {group, ttest_sgent_csock} + {group, ttest_sgent_csock}, + + %% Server: transport = socket(tcp), active = false + %% Client: transport = gen_tcp (active = false, once and true) + {group, ttest_ssockf_cgen} ]. @@ -837,6 +866,57 @@ ttest_sgent_csockt_cases() -> ttest_sgent_csockt_large_tcp6 ]. +%% Server: transport = socket(tcp), active = false +%% Client: transport = gen_tcp +ttest_ssockf_cgen_cases() -> + [ + {group, ttest_ssockf_cgenf}, + {group, ttest_ssockf_cgeno}, + {group, ttest_ssockf_cgent} + ]. + +%% Server: transport = socket(tcp), active = false +%% Client: transport = gen_tcp, active = false +ttest_ssockf_cgenf_cases() -> + [ + ttest_ssockf_cgenf_small_tcp4, + ttest_ssockf_cgenf_small_tcp6, + + ttest_ssockf_cgenf_medium_tcp4, + ttest_ssockf_cgenf_medium_tcp6, + + ttest_ssockf_cgenf_large_tcp4, + ttest_ssockf_cgenf_large_tcp6 + ]. + +%% Server: transport = socket(tcp), active = false +%% Client: transport = gen_tcp, active = once +ttest_ssockf_cgeno_cases() -> + [ + ttest_ssockf_cgeno_small_tcp4, + ttest_ssockf_cgeno_small_tcp6, + + ttest_ssockf_cgeno_medium_tcp4, + ttest_ssockf_cgeno_medium_tcp6, + + ttest_ssockf_cgeno_large_tcp4, + ttest_ssockf_cgeno_large_tcp6 + ]. + +%% Server: transport = socket(tcp), active = false +%% Client: transport = gen_tcp, active = true +ttest_ssockf_cgent_cases() -> + [ + ttest_ssockf_cgent_small_tcp4, + ttest_ssockf_cgent_small_tcp6, + + ttest_ssockf_cgent_medium_tcp4, + ttest_ssockf_cgent_medium_tcp6, + + ttest_ssockf_cgent_large_tcp4, + ttest_ssockf_cgent_large_tcp6 + ]. + %% ticket_cases() -> %% []. @@ -13017,6 +13097,402 @@ ttest_sgent_csockt_large_tcp6(_Config) when is_list(_Config) -> +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = false +%% Client: Transport = gen_tcp, Active = false +%% Message Size: small (=1) +%% Domain: inet +%% + +ttest_ssockf_cgenf_small_tcp4(suite) -> + []; +ttest_ssockf_cgenf_small_tcp4(doc) -> + []; +ttest_ssockf_cgenf_small_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssockf_cgenf_small_tcp4, + inet, + sock, false, + gen, false, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = false +%% Client: Transport = gen_tcp, Active = false +%% Message Size: small (=1) +%% Domain: inet6 +%% + +ttest_ssockf_cgenf_small_tcp6(suite) -> + []; +ttest_ssockf_cgenf_small_tcp6(doc) -> + []; +ttest_ssockf_cgenf_small_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssockf_cgenf_small_tcp6, + inet6, + sock, false, + gen, false, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = false +%% Client: Transport = gen_tcp, Active = false +%% Message Size: medium (=2) +%% Domain: inet +%% + +ttest_ssockf_cgenf_medium_tcp4(suite) -> + []; +ttest_ssockf_cgenf_medium_tcp4(doc) -> + []; +ttest_ssockf_cgenf_medium_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssockf_cgenf_medium_tcp4, + inet, + sock, false, + gen, false, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = false +%% Client: Transport = gen_tcp, Active = false +%% Message Size: medium (=2) +%% Domain: inet6 +%% + +ttest_ssockf_cgenf_medium_tcp6(suite) -> + []; +ttest_ssockf_cgenf_medium_tcp6(doc) -> + []; +ttest_ssockf_cgenf_medium_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssockf_cgenf_medium_tcp6, + inet6, + sock, false, + gen, false, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = false +%% Client: Transport = gen_tcp, Active = false +%% Message Size: large (=3) +%% Domain: inet +%% + +ttest_ssockf_cgenf_large_tcp4(suite) -> + []; +ttest_ssockf_cgenf_large_tcp4(doc) -> + []; +ttest_ssockf_cgenf_large_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssockf_cgenf_large_tcp4, + inet, + sock, false, + gen, false, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = false +%% Client: Transport = gen_tcp, Active = false +%% Message Size: large (=3) +%% Domain: inet6 +%% + +ttest_ssockf_cgenf_large_tcp6(suite) -> + []; +ttest_ssockf_cgenf_large_tcp6(doc) -> + []; +ttest_ssockf_cgenf_large_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssockf_cgenf_large_tcp6, + inet6, + sock, false, + gen, false, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = false +%% Client: Transport = gen_tcp, Active = once +%% Message Size: small (=1) +%% Domain: inet +%% + +ttest_ssockf_cgeno_small_tcp4(suite) -> + []; +ttest_ssockf_cgeno_small_tcp4(doc) -> + []; +ttest_ssockf_cgeno_small_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssockf_cgeno_small_tcp4, + inet, + sock, false, + gen, once, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = false +%% Client: Transport = gen_tcp, Active = once +%% Message Size: small (=1) +%% Domain: inet6 +%% + +ttest_ssockf_cgeno_small_tcp6(suite) -> + []; +ttest_ssockf_cgeno_small_tcp6(doc) -> + []; +ttest_ssockf_cgeno_small_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssockf_cgeno_small_tcp6, + inet6, + gen, false, + gen, once, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = false +%% Client: Transport = gen_tcp, Active = once +%% Message Size: medium (=2) +%% Domain: inet +%% + +ttest_ssockf_cgeno_medium_tcp4(suite) -> + []; +ttest_ssockf_cgeno_medium_tcp4(doc) -> + []; +ttest_ssockf_cgeno_medium_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssockf_cgeno_medium_tcp4, + inet, + sock, false, + gen, once, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = false +%% Client: Transport = gen_tcp, Active = once +%% Message Size: medium (=2) +%% Domain: inet6 +%% + +ttest_ssockf_cgeno_medium_tcp6(suite) -> + []; +ttest_ssockf_cgeno_medium_tcp6(doc) -> + []; +ttest_ssockf_cgeno_medium_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssockf_cgeno_medium_tcp6, + inet6, + gen, false, + gen, once, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = false +%% Client: Transport = gen_tcp, Active = once +%% Message Size: large (=3) +%% Domain: inet +%% + +ttest_ssockf_cgeno_large_tcp4(suite) -> + []; +ttest_ssockf_cgeno_large_tcp4(doc) -> + []; +ttest_ssockf_cgeno_large_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssockf_cgeno_large_tcp4, + inet, + sock, false, + gen, once, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = false +%% Client: Transport = gen_tcp, Active = once +%% Message Size: large (=3) +%% Domain: inet6 +%% + +ttest_ssockf_cgeno_large_tcp6(suite) -> + []; +ttest_ssockf_cgeno_large_tcp6(doc) -> + []; +ttest_ssockf_cgeno_large_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssockf_cgeno_large_tcp6, + inet6, + gen, false, + gen, once, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = false +%% Client: Transport = gen_tcp, Active = true +%% Message Size: small (=1) +%% Domain: inet +%% + +ttest_ssockf_cgent_small_tcp4(suite) -> + []; +ttest_ssockf_cgent_small_tcp4(doc) -> + []; +ttest_ssockf_cgent_small_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssockf_cgent_small_tcp4, + inet, + sock, false, + gen, true, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = false +%% Client: Transport = gen_tcp, Active = true +%% Message Size: small (=1) +%% Domain: inet6 +%% + +ttest_ssockf_cgent_small_tcp6(suite) -> + []; +ttest_ssockf_cgent_small_tcp6(doc) -> + []; +ttest_ssockf_cgent_small_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssockf_cgeno_small_tcp6, + inet6, + gen, false, + gen, true, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = false +%% Client: Transport = gen_tcp, Active = true +%% Message Size: medium (=2) +%% Domain: inet +%% + +ttest_ssockf_cgent_medium_tcp4(suite) -> + []; +ttest_ssockf_cgent_medium_tcp4(doc) -> + []; +ttest_ssockf_cgent_medium_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssockf_cgent_medium_tcp4, + inet, + sock, false, + gen, true, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = false +%% Client: Transport = gen_tcp, Active = true +%% Message Size: medium (=2) +%% Domain: inet6 +%% + +ttest_ssockf_cgent_medium_tcp6(suite) -> + []; +ttest_ssockf_cgent_medium_tcp6(doc) -> + []; +ttest_ssockf_cgent_medium_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssockf_cgent_medium_tcp6, + inet6, + sock, false, + gen, true, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = false +%% Client: Transport = gen_tcp, Active = true +%% Message Size: large (=3) +%% Domain: inet +%% + +ttest_ssockf_cgent_large_tcp4(suite) -> + []; +ttest_ssockf_cgent_large_tcp4(doc) -> + []; +ttest_ssockf_cgent_large_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssockf_cgent_large_tcp4, + inet, + sock, false, + gen, true, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = false +%% Client: Transport = gen_tcp, Active = true +%% Message Size: large (=3) +%% Domain: inet6 +%% + +ttest_ssockf_cgent_large_tcp6(suite) -> + []; +ttest_ssockf_cgent_large_tcp6(doc) -> + []; +ttest_ssockf_cgent_large_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssockf_cgent_large_tcp6, + inet6, + sock, false, + gen, true, + 3, 2). + + + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% ttest_tcp(TC, -- cgit v1.2.3 From 74ebdcf1fdd7021f00349a936692f183889232d9 Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Wed, 19 Dec 2018 13:50:47 +0100 Subject: [socket-nif|test] Add more ttest (socket) test cases Added a number of ttest test cases with transport socket. Server with transport = socket(tcp), active = false and client using transport = socket(tcp) (and active = false, once and true). OTP-14831 --- erts/emulator/test/socket_SUITE.erl | 489 +++++++++++++++++++++++++++++++++++- 1 file changed, 482 insertions(+), 7 deletions(-) diff --git a/erts/emulator/test/socket_SUITE.erl b/erts/emulator/test/socket_SUITE.erl index bac9cf4434..3597f9c481 100644 --- a/erts/emulator/test/socket_SUITE.erl +++ b/erts/emulator/test/socket_SUITE.erl @@ -283,10 +283,30 @@ ttest_ssockf_cgent_medium_tcp4/1, ttest_ssockf_cgent_medium_tcp6/1, ttest_ssockf_cgent_large_tcp4/1, - ttest_ssockf_cgent_large_tcp6/1 + ttest_ssockf_cgent_large_tcp6/1, %% Server: transport = socket(tcp), active = false %% Client: transport = socket(tcp) + ttest_ssockf_csockf_small_tcp4/1, + ttest_ssockf_csockf_small_tcp6/1, + ttest_ssockf_csockf_medium_tcp4/1, + ttest_ssockf_csockf_medium_tcp6/1, + ttest_ssockf_csockf_large_tcp4/1, + ttest_ssockf_csockf_large_tcp6/1, + + ttest_ssockf_csocko_small_tcp4/1, + ttest_ssockf_csocko_small_tcp6/1, + ttest_ssockf_csocko_medium_tcp4/1, + ttest_ssockf_csocko_medium_tcp6/1, + ttest_ssockf_csocko_large_tcp4/1, + ttest_ssockf_csocko_large_tcp6/1, + + ttest_ssockf_csockt_small_tcp4/1, + ttest_ssockf_csockt_small_tcp6/1, + ttest_ssockf_csockt_medium_tcp4/1, + ttest_ssockf_csockt_medium_tcp6/1, + ttest_ssockf_csockt_large_tcp4/1, + ttest_ssockf_csockt_large_tcp6/1 %% Server: transport = socket(tcp), active = once %% Client: transport = gen_tcp @@ -400,7 +420,11 @@ groups() -> {ttest_ssockf_cgen, [], ttest_ssockf_cgen_cases()}, {ttest_ssockf_cgenf, [], ttest_ssockf_cgenf_cases()}, {ttest_ssockf_cgeno, [], ttest_ssockf_cgeno_cases()}, - {ttest_ssockf_cgent, [], ttest_ssockf_cgent_cases()} + {ttest_ssockf_cgent, [], ttest_ssockf_cgent_cases()}, + {ttest_ssockf_csock, [], ttest_ssockf_csock_cases()}, + {ttest_ssockf_csockf, [], ttest_ssockf_csockf_cases()}, + {ttest_ssockf_csocko, [], ttest_ssockf_csocko_cases()}, + {ttest_ssockf_csockt, [], ttest_ssockf_csockt_cases()} %% {tickets, [], ticket_cases()} ]. @@ -573,7 +597,11 @@ ttest_cases() -> %% Server: transport = socket(tcp), active = false %% Client: transport = gen_tcp (active = false, once and true) - {group, ttest_ssockf_cgen} + {group, ttest_ssockf_cgen}, + + %% Server: transport = socket(tcp), active = false + %% Client: transport = socket(tcp) (active = false, once and true) + {group, ttest_ssockf_csock} ]. @@ -917,6 +945,57 @@ ttest_ssockf_cgent_cases() -> ttest_ssockf_cgent_large_tcp6 ]. +%% Server: transport = socket(tcp), active = false +%% Client: transport = socket(tcp) +ttest_ssockf_csock_cases() -> + [ + {group, ttest_ssockf_csockf}, + {group, ttest_ssockf_csocko}, + {group, ttest_ssockf_csockt} + ]. + +%% Server: transport = socket(tcp), active = false +%% Client: transport = socket(tcp), active = false +ttest_ssockf_csockf_cases() -> + [ + ttest_ssockf_csockf_small_tcp4, + ttest_ssockf_csockf_small_tcp6, + + ttest_ssockf_csockf_medium_tcp4, + ttest_ssockf_csockf_medium_tcp6, + + ttest_ssockf_csockf_large_tcp4, + ttest_ssockf_csockf_large_tcp6 + ]. + +%% Server: transport = socket(tcp), active = false +%% Client: transport = socket(tcp), active = once +ttest_ssockf_csocko_cases() -> + [ + ttest_ssockf_csocko_small_tcp4, + ttest_ssockf_csocko_small_tcp6, + + ttest_ssockf_csocko_medium_tcp4, + ttest_ssockf_csocko_medium_tcp6, + + ttest_ssockf_csocko_large_tcp4, + ttest_ssockf_csocko_large_tcp6 + ]. + +%% Server: transport = socket(tcp), active = false +%% Client: transport = socket(tcp), active = true +ttest_ssockf_csockt_cases() -> + [ + ttest_ssockf_csockt_small_tcp4, + ttest_ssockf_csockt_small_tcp6, + + ttest_ssockf_csockt_medium_tcp4, + ttest_ssockf_csockt_medium_tcp6, + + ttest_ssockf_csockt_large_tcp4, + ttest_ssockf_csockt_large_tcp6 + ]. + %% ticket_cases() -> %% []. @@ -13267,7 +13346,7 @@ ttest_ssockf_cgeno_small_tcp6(doc) -> ttest_ssockf_cgeno_small_tcp6(_Config) when is_list(_Config) -> ttest_tcp(ttest_ssockf_cgeno_small_tcp6, inet6, - gen, false, + sock, false, gen, once, 1, 200). @@ -13311,7 +13390,7 @@ ttest_ssockf_cgeno_medium_tcp6(doc) -> ttest_ssockf_cgeno_medium_tcp6(_Config) when is_list(_Config) -> ttest_tcp(ttest_ssockf_cgeno_medium_tcp6, inet6, - gen, false, + sock, false, gen, once, 2, 20). @@ -13355,7 +13434,7 @@ ttest_ssockf_cgeno_large_tcp6(doc) -> ttest_ssockf_cgeno_large_tcp6(_Config) when is_list(_Config) -> ttest_tcp(ttest_ssockf_cgeno_large_tcp6, inet6, - gen, false, + sock, false, gen, once, 3, 2). @@ -13399,7 +13478,7 @@ ttest_ssockf_cgent_small_tcp6(doc) -> ttest_ssockf_cgent_small_tcp6(_Config) when is_list(_Config) -> ttest_tcp(ttest_ssockf_cgeno_small_tcp6, inet6, - gen, false, + sock, false, gen, true, 1, 200). @@ -13493,6 +13572,402 @@ ttest_ssockf_cgent_large_tcp6(_Config) when is_list(_Config) -> +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = false +%% Client: Transport = socket(tcp), Active = false +%% Message Size: small (=1) +%% Domain: inet +%% + +ttest_ssockf_csockf_small_tcp4(suite) -> + []; +ttest_ssockf_csockf_small_tcp4(doc) -> + []; +ttest_ssockf_csockf_small_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssockf_csockf_small_tcp4, + inet, + sock, false, + sock, false, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = false +%% Client: Transport = socket(tcp), Active = false +%% Message Size: small (=1) +%% Domain: inet6 +%% + +ttest_ssockf_csockf_small_tcp6(suite) -> + []; +ttest_ssockf_csockf_small_tcp6(doc) -> + []; +ttest_ssockf_csockf_small_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssockf_csockf_small_tcp6, + inet6, + sock, false, + sock, false, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = false +%% Client: Transport = socket(tcp), Active = false +%% Message Size: medium (=2) +%% Domain: inet +%% + +ttest_ssockf_csockf_medium_tcp4(suite) -> + []; +ttest_ssockf_csockf_medium_tcp4(doc) -> + []; +ttest_ssockf_csockf_medium_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssockf_csockf_medium_tcp4, + inet, + sock, false, + sock, false, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = false +%% Client: Transport = socket(tcp), Active = false +%% Message Size: medium (=2) +%% Domain: inet6 +%% + +ttest_ssockf_csockf_medium_tcp6(suite) -> + []; +ttest_ssockf_csockf_medium_tcp6(doc) -> + []; +ttest_ssockf_csockf_medium_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssockf_csockf_medium_tcp6, + inet6, + sock, false, + sock, false, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = false +%% Client: Transport = socket(tcp), Active = false +%% Message Size: large (=3) +%% Domain: inet +%% + +ttest_ssockf_csockf_large_tcp4(suite) -> + []; +ttest_ssockf_csockf_large_tcp4(doc) -> + []; +ttest_ssockf_csockf_large_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssockf_csockf_large_tcp4, + inet, + sock, false, + sock, false, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = false +%% Client: Transport = socket(tcp), Active = false +%% Message Size: large (=3) +%% Domain: inet6 +%% + +ttest_ssockf_csockf_large_tcp6(suite) -> + []; +ttest_ssockf_csockf_large_tcp6(doc) -> + []; +ttest_ssockf_csockf_large_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssockf_csockf_large_tcp6, + inet6, + sock, false, + sock, false, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = false +%% Client: Transport = socket(tcp), Active = once +%% Message Size: small (=1) +%% Domain: inet +%% + +ttest_ssockf_csocko_small_tcp4(suite) -> + []; +ttest_ssockf_csocko_small_tcp4(doc) -> + []; +ttest_ssockf_csocko_small_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssockf_csocko_small_tcp4, + inet, + sock, false, + sock, once, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = false +%% Client: Transport = socket(tcp), Active = once +%% Message Size: small (=1) +%% Domain: inet6 +%% + +ttest_ssockf_csocko_small_tcp6(suite) -> + []; +ttest_ssockf_csocko_small_tcp6(doc) -> + []; +ttest_ssockf_csocko_small_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssockf_csocko_small_tcp6, + inet6, + sock, false, + sock, once, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = false +%% Client: Transport = socket(tcp), Active = once +%% Message Size: medium (=2) +%% Domain: inet +%% + +ttest_ssockf_csocko_medium_tcp4(suite) -> + []; +ttest_ssockf_csocko_medium_tcp4(doc) -> + []; +ttest_ssockf_csocko_medium_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssockf_csocko_medium_tcp4, + inet, + sock, false, + sock, once, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = false +%% Client: Transport = socket(tcp), Active = once +%% Message Size: medium (=2) +%% Domain: inet6 +%% + +ttest_ssockf_csocko_medium_tcp6(suite) -> + []; +ttest_ssockf_csocko_medium_tcp6(doc) -> + []; +ttest_ssockf_csocko_medium_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssockf_csocko_medium_tcp6, + inet6, + sock, false, + sock, once, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = false +%% Client: Transport = socket(tcp), Active = once +%% Message Size: large (=3) +%% Domain: inet +%% + +ttest_ssockf_csocko_large_tcp4(suite) -> + []; +ttest_ssockf_csocko_large_tcp4(doc) -> + []; +ttest_ssockf_csocko_large_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssockf_csocko_large_tcp4, + inet, + sock, false, + sock, once, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = false +%% Client: Transport = socket(tcp), Active = once +%% Message Size: large (=3) +%% Domain: inet6 +%% + +ttest_ssockf_csocko_large_tcp6(suite) -> + []; +ttest_ssockf_csocko_large_tcp6(doc) -> + []; +ttest_ssockf_csocko_large_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssockf_csocko_large_tcp6, + inet6, + sock, false, + sock, once, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = false +%% Client: Transport = socket(tcp), Active = true +%% Message Size: small (=1) +%% Domain: inet +%% + +ttest_ssockf_csockt_small_tcp4(suite) -> + []; +ttest_ssockf_csockt_small_tcp4(doc) -> + []; +ttest_ssockf_csockt_small_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssockf_csockt_small_tcp4, + inet, + sock, false, + sock, true, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = false +%% Client: Transport = socket(tcp), Active = true +%% Message Size: small (=1) +%% Domain: inet6 +%% + +ttest_ssockf_csockt_small_tcp6(suite) -> + []; +ttest_ssockf_csockt_small_tcp6(doc) -> + []; +ttest_ssockf_csockt_small_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssockf_csocko_small_tcp6, + inet6, + sock, false, + sock, true, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = false +%% Client: Transport = socket(tcp), Active = true +%% Message Size: medium (=2) +%% Domain: inet +%% + +ttest_ssockf_csockt_medium_tcp4(suite) -> + []; +ttest_ssockf_csockt_medium_tcp4(doc) -> + []; +ttest_ssockf_csockt_medium_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssockf_csockt_medium_tcp4, + inet, + sock, false, + sock, true, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = false +%% Client: Transport = socket(tcp), Active = true +%% Message Size: medium (=2) +%% Domain: inet6 +%% + +ttest_ssockf_csockt_medium_tcp6(suite) -> + []; +ttest_ssockf_csockt_medium_tcp6(doc) -> + []; +ttest_ssockf_csockt_medium_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssockf_csockt_medium_tcp6, + inet6, + sock, false, + sock, true, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = false +%% Client: Transport = socket(tcp), Active = true +%% Message Size: large (=3) +%% Domain: inet +%% + +ttest_ssockf_csockt_large_tcp4(suite) -> + []; +ttest_ssockf_csockt_large_tcp4(doc) -> + []; +ttest_ssockf_csockt_large_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssockf_csockt_large_tcp4, + inet, + sock, false, + sock, true, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = false +%% Client: Transport = socket(tcp), Active = true +%% Message Size: large (=3) +%% Domain: inet6 +%% + +ttest_ssockf_csockt_large_tcp6(suite) -> + []; +ttest_ssockf_csockt_large_tcp6(doc) -> + []; +ttest_ssockf_csockt_large_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssockf_csockt_large_tcp6, + inet6, + sock, false, + sock, true, + 3, 2). + + + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% ttest_tcp(TC, -- cgit v1.2.3 From fcde2424e25c428323b497bbc256a8f22d0263b9 Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Wed, 19 Dec 2018 14:51:02 +0100 Subject: [socket-nif|test] Add more ttest (socket and gen_tcp) test cases Added a number of ttest test cases with transport socket and gen_tcp. Server with transport = socket(tcp), active = once and client using transport = gen_tcp (and active = false, once and true). OTP-14831 --- erts/emulator/test/socket_SUITE.erl | 485 +++++++++++++++++++++++++++++++++++- 1 file changed, 481 insertions(+), 4 deletions(-) diff --git a/erts/emulator/test/socket_SUITE.erl b/erts/emulator/test/socket_SUITE.erl index 3597f9c481..e91f3c7819 100644 --- a/erts/emulator/test/socket_SUITE.erl +++ b/erts/emulator/test/socket_SUITE.erl @@ -306,10 +306,30 @@ ttest_ssockf_csockt_medium_tcp4/1, ttest_ssockf_csockt_medium_tcp6/1, ttest_ssockf_csockt_large_tcp4/1, - ttest_ssockf_csockt_large_tcp6/1 + ttest_ssockf_csockt_large_tcp6/1, %% Server: transport = socket(tcp), active = once %% Client: transport = gen_tcp + ttest_ssocko_cgenf_small_tcp4/1, + ttest_ssocko_cgenf_small_tcp6/1, + ttest_ssocko_cgenf_medium_tcp4/1, + ttest_ssocko_cgenf_medium_tcp6/1, + ttest_ssocko_cgenf_large_tcp4/1, + ttest_ssocko_cgenf_large_tcp6/1, + + ttest_ssocko_cgeno_small_tcp4/1, + ttest_ssocko_cgeno_small_tcp6/1, + ttest_ssocko_cgeno_medium_tcp4/1, + ttest_ssocko_cgeno_medium_tcp6/1, + ttest_ssocko_cgeno_large_tcp4/1, + ttest_ssocko_cgeno_large_tcp6/1, + + ttest_ssocko_cgent_small_tcp4/1, + ttest_ssocko_cgent_small_tcp6/1, + ttest_ssocko_cgent_medium_tcp4/1, + ttest_ssocko_cgent_medium_tcp6/1, + ttest_ssocko_cgent_large_tcp4/1, + ttest_ssocko_cgent_large_tcp6/1 %% Server: transport = socket(tcp), active = once %% Client: transport = socket(tcp) @@ -376,7 +396,9 @@ all() -> {group, ttest_sgeno_csock}, {group, ttest_sgent_cgen}, {group, ttest_sgent_csock}, - {group, ttest_ssockf_cgen} + {group, ttest_ssockf_cgen}, + {group, ttest_ssockf_csock}, + {group, ttest_ssocko_cgen} %% {group, tickets} ]. @@ -424,7 +446,11 @@ groups() -> {ttest_ssockf_csock, [], ttest_ssockf_csock_cases()}, {ttest_ssockf_csockf, [], ttest_ssockf_csockf_cases()}, {ttest_ssockf_csocko, [], ttest_ssockf_csocko_cases()}, - {ttest_ssockf_csockt, [], ttest_ssockf_csockt_cases()} + {ttest_ssockf_csockt, [], ttest_ssockf_csockt_cases()}, + {ttest_ssocko_cgen, [], ttest_ssocko_cgen_cases()}, + {ttest_ssocko_cgenf, [], ttest_ssocko_cgenf_cases()}, + {ttest_ssocko_cgeno, [], ttest_ssocko_cgeno_cases()}, + {ttest_ssocko_cgent, [], ttest_ssocko_cgent_cases()} %% {tickets, [], ticket_cases()} ]. @@ -601,7 +627,11 @@ ttest_cases() -> %% Server: transport = socket(tcp), active = false %% Client: transport = socket(tcp) (active = false, once and true) - {group, ttest_ssockf_csock} + {group, ttest_ssockf_csock}, + + %% Server: transport = socket(tcp), active = once + %% Client: transport = gen_tcp (active = false, once and true) + {group, ttest_ssocko_cgen} ]. @@ -996,6 +1026,57 @@ ttest_ssockf_csockt_cases() -> ttest_ssockf_csockt_large_tcp6 ]. +%% Server: transport = socket(tcp), active = once +%% Client: transport = gen_tcp +ttest_ssocko_cgen_cases() -> + [ + {group, ttest_ssocko_cgenf}, + {group, ttest_ssocko_cgeno}, + {group, ttest_ssocko_cgent} + ]. + +%% Server: transport = socket(tcp), active = once +%% Client: transport = gen_tcp, active = false +ttest_ssocko_cgenf_cases() -> + [ + ttest_ssocko_cgenf_small_tcp4, + ttest_ssocko_cgenf_small_tcp6, + + ttest_ssocko_cgenf_medium_tcp4, + ttest_ssocko_cgenf_medium_tcp6, + + ttest_ssocko_cgenf_large_tcp4, + ttest_ssocko_cgenf_large_tcp6 + ]. + +%% Server: transport = socket(tcp), active = once +%% Client: transport = gen_tcp, active = once +ttest_ssocko_cgeno_cases() -> + [ + ttest_ssocko_cgeno_small_tcp4, + ttest_ssocko_cgeno_small_tcp6, + + ttest_ssocko_cgeno_medium_tcp4, + ttest_ssocko_cgeno_medium_tcp6, + + ttest_ssocko_cgeno_large_tcp4, + ttest_ssocko_cgeno_large_tcp6 + ]. + +%% Server: transport = socket(tcp), active = once +%% Client: transport = gen_tcp, active = true +ttest_ssocko_cgent_cases() -> + [ + ttest_ssocko_cgent_small_tcp4, + ttest_ssocko_cgent_small_tcp6, + + ttest_ssocko_cgent_medium_tcp4, + ttest_ssocko_cgent_medium_tcp6, + + ttest_ssocko_cgent_large_tcp4, + ttest_ssocko_cgent_large_tcp6 + ]. + %% ticket_cases() -> %% []. @@ -13968,6 +14049,402 @@ ttest_ssockf_csockt_large_tcp6(_Config) when is_list(_Config) -> +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = once +%% Client: Transport = gen_tcp, Active = false +%% Message Size: small (=1) +%% Domain: inet +%% + +ttest_ssocko_cgenf_small_tcp4(suite) -> + []; +ttest_ssocko_cgenf_small_tcp4(doc) -> + []; +ttest_ssocko_cgenf_small_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssocko_cgenf_small_tcp4, + inet, + sock, once, + gen, false, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = once +%% Client: Transport = gen_tcp, Active = false +%% Message Size: small (=1) +%% Domain: inet6 +%% + +ttest_ssocko_cgenf_small_tcp6(suite) -> + []; +ttest_ssocko_cgenf_small_tcp6(doc) -> + []; +ttest_ssocko_cgenf_small_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssocko_cgenf_small_tcp6, + inet6, + sock, once, + gen, false, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = once +%% Client: Transport = gen_tcp, Active = false +%% Message Size: medium (=2) +%% Domain: inet +%% + +ttest_ssocko_cgenf_medium_tcp4(suite) -> + []; +ttest_ssocko_cgenf_medium_tcp4(doc) -> + []; +ttest_ssocko_cgenf_medium_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssocko_cgenf_medium_tcp4, + inet, + sock, once, + gen, false, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = once +%% Client: Transport = gen_tcp, Active = false +%% Message Size: medium (=2) +%% Domain: inet6 +%% + +ttest_ssocko_cgenf_medium_tcp6(suite) -> + []; +ttest_ssocko_cgenf_medium_tcp6(doc) -> + []; +ttest_ssocko_cgenf_medium_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssocko_cgenf_medium_tcp6, + inet6, + sock, once, + gen, false, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = once +%% Client: Transport = gen_tcp, Active = false +%% Message Size: large (=3) +%% Domain: inet +%% + +ttest_ssocko_cgenf_large_tcp4(suite) -> + []; +ttest_ssocko_cgenf_large_tcp4(doc) -> + []; +ttest_ssocko_cgenf_large_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssocko_cgenf_large_tcp4, + inet, + sock, once, + gen, false, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = once +%% Client: Transport = gen_tcp, Active = false +%% Message Size: large (=3) +%% Domain: inet6 +%% + +ttest_ssocko_cgenf_large_tcp6(suite) -> + []; +ttest_ssocko_cgenf_large_tcp6(doc) -> + []; +ttest_ssocko_cgenf_large_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssocko_cgenf_large_tcp6, + inet6, + sock, once, + gen, false, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = once +%% Client: Transport = gen_tcp, Active = once +%% Message Size: small (=1) +%% Domain: inet +%% + +ttest_ssocko_cgeno_small_tcp4(suite) -> + []; +ttest_ssocko_cgeno_small_tcp4(doc) -> + []; +ttest_ssocko_cgeno_small_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssocko_cgeno_small_tcp4, + inet, + sock, once, + gen, once, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = false +%% Client: Transport = gen_tcp, Active = once +%% Message Size: small (=1) +%% Domain: inet6 +%% + +ttest_ssocko_cgeno_small_tcp6(suite) -> + []; +ttest_ssocko_cgeno_small_tcp6(doc) -> + []; +ttest_ssocko_cgeno_small_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssocko_cgeno_small_tcp6, + inet6, + sock, once, + gen, once, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = once +%% Client: Transport = gen_tcp, Active = once +%% Message Size: medium (=2) +%% Domain: inet +%% + +ttest_ssocko_cgeno_medium_tcp4(suite) -> + []; +ttest_ssocko_cgeno_medium_tcp4(doc) -> + []; +ttest_ssocko_cgeno_medium_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssocko_cgeno_medium_tcp4, + inet, + sock, once, + gen, once, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = false +%% Client: Transport = gen_tcp, Active = once +%% Message Size: medium (=2) +%% Domain: inet6 +%% + +ttest_ssocko_cgeno_medium_tcp6(suite) -> + []; +ttest_ssocko_cgeno_medium_tcp6(doc) -> + []; +ttest_ssocko_cgeno_medium_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssocko_cgeno_medium_tcp6, + inet6, + sock, once, + gen, once, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = once +%% Client: Transport = gen_tcp, Active = once +%% Message Size: large (=3) +%% Domain: inet +%% + +ttest_ssocko_cgeno_large_tcp4(suite) -> + []; +ttest_ssocko_cgeno_large_tcp4(doc) -> + []; +ttest_ssocko_cgeno_large_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssocko_cgeno_large_tcp4, + inet, + sock, once, + gen, once, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = false +%% Client: Transport = gen_tcp, Active = once +%% Message Size: large (=3) +%% Domain: inet6 +%% + +ttest_ssocko_cgeno_large_tcp6(suite) -> + []; +ttest_ssocko_cgeno_large_tcp6(doc) -> + []; +ttest_ssocko_cgeno_large_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssocko_cgeno_large_tcp6, + inet6, + sock, once, + gen, once, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = once +%% Client: Transport = gen_tcp, Active = true +%% Message Size: small (=1) +%% Domain: inet +%% + +ttest_ssocko_cgent_small_tcp4(suite) -> + []; +ttest_ssocko_cgent_small_tcp4(doc) -> + []; +ttest_ssocko_cgent_small_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssocko_cgent_small_tcp4, + inet, + sock, once, + gen, true, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = false +%% Client: Transport = gen_tcp, Active = true +%% Message Size: small (=1) +%% Domain: inet6 +%% + +ttest_ssocko_cgent_small_tcp6(suite) -> + []; +ttest_ssocko_cgent_small_tcp6(doc) -> + []; +ttest_ssocko_cgent_small_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssocko_cgeno_small_tcp6, + inet6, + sock, once, + gen, true, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = once +%% Client: Transport = gen_tcp, Active = true +%% Message Size: medium (=2) +%% Domain: inet +%% + +ttest_ssocko_cgent_medium_tcp4(suite) -> + []; +ttest_ssocko_cgent_medium_tcp4(doc) -> + []; +ttest_ssocko_cgent_medium_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssocko_cgent_medium_tcp4, + inet, + sock, once, + gen, true, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = false +%% Client: Transport = gen_tcp, Active = true +%% Message Size: medium (=2) +%% Domain: inet6 +%% + +ttest_ssocko_cgent_medium_tcp6(suite) -> + []; +ttest_ssocko_cgent_medium_tcp6(doc) -> + []; +ttest_ssocko_cgent_medium_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssocko_cgent_medium_tcp6, + inet6, + sock, once, + gen, true, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = once +%% Client: Transport = gen_tcp, Active = true +%% Message Size: large (=3) +%% Domain: inet +%% + +ttest_ssocko_cgent_large_tcp4(suite) -> + []; +ttest_ssocko_cgent_large_tcp4(doc) -> + []; +ttest_ssocko_cgent_large_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssocko_cgent_large_tcp4, + inet, + sock, once, + gen, true, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = false +%% Client: Transport = gen_tcp, Active = true +%% Message Size: large (=3) +%% Domain: inet6 +%% + +ttest_ssocko_cgent_large_tcp6(suite) -> + []; +ttest_ssocko_cgent_large_tcp6(doc) -> + []; +ttest_ssocko_cgent_large_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssocko_cgent_large_tcp6, + inet6, + sock, once, + gen, true, + 3, 2). + + + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% ttest_tcp(TC, -- cgit v1.2.3 From 8d69d71e2c7efdd078d943ad21c2339c6d588979 Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Wed, 19 Dec 2018 16:14:47 +0100 Subject: [socket-nif|test] Add more ttest (socket) test cases Added a number of ttest test cases with transport socket. Server with transport = socket(tcp), active = once and client using transport = socket(tcp) (and active = false, once and true). OTP-14831 --- erts/emulator/test/socket_SUITE.erl | 484 +++++++++++++++++++++++++++++++++++- 1 file changed, 480 insertions(+), 4 deletions(-) diff --git a/erts/emulator/test/socket_SUITE.erl b/erts/emulator/test/socket_SUITE.erl index e91f3c7819..0688fb5c1f 100644 --- a/erts/emulator/test/socket_SUITE.erl +++ b/erts/emulator/test/socket_SUITE.erl @@ -329,10 +329,30 @@ ttest_ssocko_cgent_medium_tcp4/1, ttest_ssocko_cgent_medium_tcp6/1, ttest_ssocko_cgent_large_tcp4/1, - ttest_ssocko_cgent_large_tcp6/1 + ttest_ssocko_cgent_large_tcp6/1, %% Server: transport = socket(tcp), active = once %% Client: transport = socket(tcp) + ttest_ssocko_csockf_small_tcp4/1, + ttest_ssocko_csockf_small_tcp6/1, + ttest_ssocko_csockf_medium_tcp4/1, + ttest_ssocko_csockf_medium_tcp6/1, + ttest_ssocko_csockf_large_tcp4/1, + ttest_ssocko_csockf_large_tcp6/1, + + ttest_ssocko_csocko_small_tcp4/1, + ttest_ssocko_csocko_small_tcp6/1, + ttest_ssocko_csocko_medium_tcp4/1, + ttest_ssocko_csocko_medium_tcp6/1, + ttest_ssocko_csocko_large_tcp4/1, + ttest_ssocko_csocko_large_tcp6/1, + + ttest_ssocko_csockt_small_tcp4/1, + ttest_ssocko_csockt_small_tcp6/1, + ttest_ssocko_csockt_medium_tcp4/1, + ttest_ssocko_csockt_medium_tcp6/1, + ttest_ssocko_csockt_large_tcp4/1, + ttest_ssocko_csockt_large_tcp6/1 %% Server: transport = socket(tcp), active = true %% Client: transport = gen_tcp @@ -398,7 +418,8 @@ all() -> {group, ttest_sgent_csock}, {group, ttest_ssockf_cgen}, {group, ttest_ssockf_csock}, - {group, ttest_ssocko_cgen} + {group, ttest_ssocko_cgen}, + {group, ttest_ssocko_csock} %% {group, tickets} ]. @@ -450,7 +471,11 @@ groups() -> {ttest_ssocko_cgen, [], ttest_ssocko_cgen_cases()}, {ttest_ssocko_cgenf, [], ttest_ssocko_cgenf_cases()}, {ttest_ssocko_cgeno, [], ttest_ssocko_cgeno_cases()}, - {ttest_ssocko_cgent, [], ttest_ssocko_cgent_cases()} + {ttest_ssocko_cgent, [], ttest_ssocko_cgent_cases()}, + {ttest_ssocko_csock, [], ttest_ssocko_csock_cases()}, + {ttest_ssocko_csockf, [], ttest_ssocko_csockf_cases()}, + {ttest_ssocko_csocko, [], ttest_ssocko_csocko_cases()}, + {ttest_ssocko_csockt, [], ttest_ssocko_csockt_cases()} %% {tickets, [], ticket_cases()} ]. @@ -631,7 +656,11 @@ ttest_cases() -> %% Server: transport = socket(tcp), active = once %% Client: transport = gen_tcp (active = false, once and true) - {group, ttest_ssocko_cgen} + {group, ttest_ssocko_cgen}, + + %% Server: transport = socket(tcp), active = once + %% Client: transport = socket(tcp) (active = false, once and true) + {group, ttest_ssocko_csock} ]. @@ -1077,6 +1106,57 @@ ttest_ssocko_cgent_cases() -> ttest_ssocko_cgent_large_tcp6 ]. +%% Server: transport = socket(tcp), active = once +%% Client: transport = socket(tcp) +ttest_ssocko_csock_cases() -> + [ + {group, ttest_ssocko_csockf}, + {group, ttest_ssocko_csocko}, + {group, ttest_ssocko_csockt} + ]. + +%% Server: transport = socket(tcp), active = once +%% Client: transport = socket(tcp), active = false +ttest_ssocko_csockf_cases() -> + [ + ttest_ssocko_csockf_small_tcp4, + ttest_ssocko_csockf_small_tcp6, + + ttest_ssocko_csockf_medium_tcp4, + ttest_ssocko_csockf_medium_tcp6, + + ttest_ssocko_csockf_large_tcp4, + ttest_ssocko_csockf_large_tcp6 + ]. + +%% Server: transport = socket(tcp), active = once +%% Client: transport = socket(tcp), active = once +ttest_ssocko_csocko_cases() -> + [ + ttest_ssocko_csocko_small_tcp4, + ttest_ssocko_csocko_small_tcp6, + + ttest_ssocko_csocko_medium_tcp4, + ttest_ssocko_csocko_medium_tcp6, + + ttest_ssocko_csocko_large_tcp4, + ttest_ssocko_csocko_large_tcp6 + ]. + +%% Server: transport = socket(tcp), active = once +%% Client: transport = socket(tcp), active = true +ttest_ssocko_csockt_cases() -> + [ + ttest_ssocko_csockt_small_tcp4, + ttest_ssocko_csockt_small_tcp6, + + ttest_ssocko_csockt_medium_tcp4, + ttest_ssocko_csockt_medium_tcp6, + + ttest_ssocko_csockt_large_tcp4, + ttest_ssocko_csockt_large_tcp6 + ]. + %% ticket_cases() -> %% []. @@ -14445,6 +14525,402 @@ ttest_ssocko_cgent_large_tcp6(_Config) when is_list(_Config) -> +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = once +%% Client: Transport = socket(tcp), Active = false +%% Message Size: small (=1) +%% Domain: inet +%% + +ttest_ssocko_csockf_small_tcp4(suite) -> + []; +ttest_ssocko_csockf_small_tcp4(doc) -> + []; +ttest_ssocko_csockf_small_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssocko_csockf_small_tcp4, + inet, + sock, once, + sock, false, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = once +%% Client: Transport = socket(tcp), Active = false +%% Message Size: small (=1) +%% Domain: inet6 +%% + +ttest_ssocko_csockf_small_tcp6(suite) -> + []; +ttest_ssocko_csockf_small_tcp6(doc) -> + []; +ttest_ssocko_csockf_small_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssocko_csockf_small_tcp6, + inet6, + sock, once, + sock, false, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = once +%% Client: Transport = socket(tcp), Active = false +%% Message Size: medium (=2) +%% Domain: inet +%% + +ttest_ssocko_csockf_medium_tcp4(suite) -> + []; +ttest_ssocko_csockf_medium_tcp4(doc) -> + []; +ttest_ssocko_csockf_medium_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssocko_csockf_medium_tcp4, + inet, + sock, once, + sock, false, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = once +%% Client: Transport = socket(tcp), Active = false +%% Message Size: medium (=2) +%% Domain: inet6 +%% + +ttest_ssocko_csockf_medium_tcp6(suite) -> + []; +ttest_ssocko_csockf_medium_tcp6(doc) -> + []; +ttest_ssocko_csockf_medium_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssocko_csockf_medium_tcp6, + inet6, + sock, once, + sock, false, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = once +%% Client: Transport = socket(tcp), Active = false +%% Message Size: large (=3) +%% Domain: inet +%% + +ttest_ssocko_csockf_large_tcp4(suite) -> + []; +ttest_ssocko_csockf_large_tcp4(doc) -> + []; +ttest_ssocko_csockf_large_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssocko_csockf_large_tcp4, + inet, + sock, once, + sock, false, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = once +%% Client: Transport = socket(tcp), Active = false +%% Message Size: large (=3) +%% Domain: inet6 +%% + +ttest_ssocko_csockf_large_tcp6(suite) -> + []; +ttest_ssocko_csockf_large_tcp6(doc) -> + []; +ttest_ssocko_csockf_large_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssocko_csockf_large_tcp6, + inet6, + sock, once, + sock, false, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = once +%% Client: Transport = socket(tcp), Active = once +%% Message Size: small (=1) +%% Domain: inet +%% + +ttest_ssocko_csocko_small_tcp4(suite) -> + []; +ttest_ssocko_csocko_small_tcp4(doc) -> + []; +ttest_ssocko_csocko_small_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssocko_csocko_small_tcp4, + inet, + sock, once, + sock, once, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = once +%% Client: Transport = socket(tcp), Active = once +%% Message Size: small (=1) +%% Domain: inet6 +%% + +ttest_ssocko_csocko_small_tcp6(suite) -> + []; +ttest_ssocko_csocko_small_tcp6(doc) -> + []; +ttest_ssocko_csocko_small_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssocko_csocko_small_tcp6, + inet6, + sock, once, + sock, once, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = once +%% Client: Transport = socket(tcp), Active = once +%% Message Size: medium (=2) +%% Domain: inet +%% + +ttest_ssocko_csocko_medium_tcp4(suite) -> + []; +ttest_ssocko_csocko_medium_tcp4(doc) -> + []; +ttest_ssocko_csocko_medium_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssocko_csocko_medium_tcp4, + inet, + sock, once, + sock, once, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = once +%% Client: Transport = socket(tcp), Active = once +%% Message Size: medium (=2) +%% Domain: inet6 +%% + +ttest_ssocko_csocko_medium_tcp6(suite) -> + []; +ttest_ssocko_csocko_medium_tcp6(doc) -> + []; +ttest_ssocko_csocko_medium_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssocko_csocko_medium_tcp6, + inet6, + sock, once, + sock, once, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = once +%% Client: Transport = socket(tcp), Active = once +%% Message Size: large (=3) +%% Domain: inet +%% + +ttest_ssocko_csocko_large_tcp4(suite) -> + []; +ttest_ssocko_csocko_large_tcp4(doc) -> + []; +ttest_ssocko_csocko_large_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssocko_csocko_large_tcp4, + inet, + sock, once, + sock, once, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = once +%% Client: Transport = socket(tcp), Active = once +%% Message Size: large (=3) +%% Domain: inet6 +%% + +ttest_ssocko_csocko_large_tcp6(suite) -> + []; +ttest_ssocko_csocko_large_tcp6(doc) -> + []; +ttest_ssocko_csocko_large_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssocko_csocko_large_tcp6, + inet6, + sock, once, + sock, once, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = once +%% Client: Transport = socket(tcp), Active = true +%% Message Size: small (=1) +%% Domain: inet +%% + +ttest_ssocko_csockt_small_tcp4(suite) -> + []; +ttest_ssocko_csockt_small_tcp4(doc) -> + []; +ttest_ssocko_csockt_small_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssocko_csockt_small_tcp4, + inet, + sock, once, + sock, true, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = once +%% Client: Transport = socket(tcp), Active = true +%% Message Size: small (=1) +%% Domain: inet6 +%% + +ttest_ssocko_csockt_small_tcp6(suite) -> + []; +ttest_ssocko_csockt_small_tcp6(doc) -> + []; +ttest_ssocko_csockt_small_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssocko_csocko_small_tcp6, + inet6, + sock, once, + sock, true, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = once +%% Client: Transport = socket(tcp), Active = true +%% Message Size: medium (=2) +%% Domain: inet +%% + +ttest_ssocko_csockt_medium_tcp4(suite) -> + []; +ttest_ssocko_csockt_medium_tcp4(doc) -> + []; +ttest_ssocko_csockt_medium_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssocko_csockt_medium_tcp4, + inet, + sock, once, + sock, true, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = once +%% Client: Transport = socket(tcp), Active = true +%% Message Size: medium (=2) +%% Domain: inet6 +%% + +ttest_ssocko_csockt_medium_tcp6(suite) -> + []; +ttest_ssocko_csockt_medium_tcp6(doc) -> + []; +ttest_ssocko_csockt_medium_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssocko_csockt_medium_tcp6, + inet6, + sock, once, + sock, true, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = once +%% Client: Transport = socket(tcp), Active = true +%% Message Size: large (=3) +%% Domain: inet +%% + +ttest_ssocko_csockt_large_tcp4(suite) -> + []; +ttest_ssocko_csockt_large_tcp4(doc) -> + []; +ttest_ssocko_csockt_large_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssocko_csockt_large_tcp4, + inet, + sock, once, + sock, true, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = once +%% Client: Transport = socket(tcp), Active = true +%% Message Size: large (=3) +%% Domain: inet6 +%% + +ttest_ssocko_csockt_large_tcp6(suite) -> + []; +ttest_ssocko_csockt_large_tcp6(doc) -> + []; +ttest_ssocko_csockt_large_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssocko_csockt_large_tcp6, + inet6, + sock, once, + sock, true, + 3, 2). + + + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% ttest_tcp(TC, -- cgit v1.2.3 From 1a617f1ec3dd819dea67bbaaeffef371370958a3 Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Wed, 19 Dec 2018 16:42:59 +0100 Subject: [socket-nif|test] Add more ttest (socket and gen_tcp) test cases Added a number of ttest test cases with transport socket and gen_tcp. Server with transport = socket(tcp), active = true and client using transport = gen_tcp (and active = false, once and true). OTP-14831 --- erts/emulator/test/socket_SUITE.erl | 482 +++++++++++++++++++++++++++++++++++- 1 file changed, 477 insertions(+), 5 deletions(-) diff --git a/erts/emulator/test/socket_SUITE.erl b/erts/emulator/test/socket_SUITE.erl index 0688fb5c1f..126f318ea6 100644 --- a/erts/emulator/test/socket_SUITE.erl +++ b/erts/emulator/test/socket_SUITE.erl @@ -352,10 +352,30 @@ ttest_ssocko_csockt_medium_tcp4/1, ttest_ssocko_csockt_medium_tcp6/1, ttest_ssocko_csockt_large_tcp4/1, - ttest_ssocko_csockt_large_tcp6/1 + ttest_ssocko_csockt_large_tcp6/1, %% Server: transport = socket(tcp), active = true %% Client: transport = gen_tcp + ttest_ssockt_cgenf_small_tcp4/1, + ttest_ssockt_cgenf_small_tcp6/1, + ttest_ssockt_cgenf_medium_tcp4/1, + ttest_ssockt_cgenf_medium_tcp6/1, + ttest_ssockt_cgenf_large_tcp4/1, + ttest_ssockt_cgenf_large_tcp6/1, + + ttest_ssockt_cgeno_small_tcp4/1, + ttest_ssockt_cgeno_small_tcp6/1, + ttest_ssockt_cgeno_medium_tcp4/1, + ttest_ssockt_cgeno_medium_tcp6/1, + ttest_ssockt_cgeno_large_tcp4/1, + ttest_ssockt_cgeno_large_tcp6/1, + + ttest_ssockt_cgent_small_tcp4/1, + ttest_ssockt_cgent_small_tcp6/1, + ttest_ssockt_cgent_medium_tcp4/1, + ttest_ssockt_cgent_medium_tcp6/1, + ttest_ssockt_cgent_large_tcp4/1, + ttest_ssockt_cgent_large_tcp6/1 %% Server: transport = socket(tcp), active = true %% Client: transport = socket(tcp) @@ -419,7 +439,8 @@ all() -> {group, ttest_ssockf_cgen}, {group, ttest_ssockf_csock}, {group, ttest_ssocko_cgen}, - {group, ttest_ssocko_csock} + {group, ttest_ssocko_csock}, + {group, ttest_ssockt_cgen} %% {group, tickets} ]. @@ -475,7 +496,11 @@ groups() -> {ttest_ssocko_csock, [], ttest_ssocko_csock_cases()}, {ttest_ssocko_csockf, [], ttest_ssocko_csockf_cases()}, {ttest_ssocko_csocko, [], ttest_ssocko_csocko_cases()}, - {ttest_ssocko_csockt, [], ttest_ssocko_csockt_cases()} + {ttest_ssocko_csockt, [], ttest_ssocko_csockt_cases()}, + {ttest_ssockt_cgen, [], ttest_ssockt_cgen_cases()}, + {ttest_ssockt_cgenf, [], ttest_ssockt_cgenf_cases()}, + {ttest_ssockt_cgeno, [], ttest_ssockt_cgeno_cases()}, + {ttest_ssockt_cgent, [], ttest_ssockt_cgent_cases()} %% {tickets, [], ticket_cases()} ]. @@ -1157,6 +1182,57 @@ ttest_ssocko_csockt_cases() -> ttest_ssocko_csockt_large_tcp6 ]. +%% Server: transport = socket(tcp), active = true +%% Client: transport = gen_tcp +ttest_ssockt_cgen_cases() -> + [ + {group, ttest_ssockt_cgenf}, + {group, ttest_ssockt_cgeno}, + {group, ttest_ssockt_cgent} + ]. + +%% Server: transport = socket(tcp), active = true +%% Client: transport = gen_tcp, active = false +ttest_ssockt_cgenf_cases() -> + [ + ttest_ssockt_cgenf_small_tcp4, + ttest_ssockt_cgenf_small_tcp6, + + ttest_ssockt_cgenf_medium_tcp4, + ttest_ssockt_cgenf_medium_tcp6, + + ttest_ssockt_cgenf_large_tcp4, + ttest_ssockt_cgenf_large_tcp6 + ]. + +%% Server: transport = socket(tcp), active = true +%% Client: transport = gen_tcp, active = once +ttest_ssockt_cgeno_cases() -> + [ + ttest_ssockt_cgeno_small_tcp4, + ttest_ssockt_cgeno_small_tcp6, + + ttest_ssockt_cgeno_medium_tcp4, + ttest_ssockt_cgeno_medium_tcp6, + + ttest_ssockt_cgeno_large_tcp4, + ttest_ssockt_cgeno_large_tcp6 + ]. + +%% Server: transport = socket(tcp), active = true +%% Client: transport = gen_tcp, active = true +ttest_ssockt_cgent_cases() -> + [ + ttest_ssockt_cgent_small_tcp4, + ttest_ssockt_cgent_small_tcp6, + + ttest_ssockt_cgent_medium_tcp4, + ttest_ssockt_cgent_medium_tcp6, + + ttest_ssockt_cgent_large_tcp4, + ttest_ssockt_cgent_large_tcp6 + ]. + %% ticket_cases() -> %% []. @@ -14286,7 +14362,7 @@ ttest_ssocko_cgeno_small_tcp4(_Config) when is_list(_Config) -> %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% This test case uses the time test (ttest) utility to implement a %% ping-pong like test case. -%% Server: Transport = socket(tcp), Active = false +%% Server: Transport = socket(tcp), Active = once %% Client: Transport = gen_tcp, Active = once %% Message Size: small (=1) %% Domain: inet6 @@ -14429,7 +14505,7 @@ ttest_ssocko_cgent_small_tcp6(suite) -> ttest_ssocko_cgent_small_tcp6(doc) -> []; ttest_ssocko_cgent_small_tcp6(_Config) when is_list(_Config) -> - ttest_tcp(ttest_ssocko_cgeno_small_tcp6, + ttest_tcp(ttest_ssocko_cgent_small_tcp6, inet6, sock, once, gen, true, @@ -14921,6 +14997,402 @@ ttest_ssocko_csockt_large_tcp6(_Config) when is_list(_Config) -> +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = true +%% Client: Transport = gen_tcp, Active = false +%% Message Size: small (=1) +%% Domain: inet +%% + +ttest_ssockt_cgenf_small_tcp4(suite) -> + []; +ttest_ssockt_cgenf_small_tcp4(doc) -> + []; +ttest_ssockt_cgenf_small_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssockt_cgenf_small_tcp4, + inet, + sock, true, + gen, false, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = true +%% Client: Transport = gen_tcp, Active = false +%% Message Size: small (=1) +%% Domain: inet6 +%% + +ttest_ssockt_cgenf_small_tcp6(suite) -> + []; +ttest_ssockt_cgenf_small_tcp6(doc) -> + []; +ttest_ssockt_cgenf_small_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssockt_cgenf_small_tcp6, + inet6, + sock, true, + gen, false, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = true +%% Client: Transport = gen_tcp, Active = false +%% Message Size: medium (=2) +%% Domain: inet +%% + +ttest_ssockt_cgenf_medium_tcp4(suite) -> + []; +ttest_ssockt_cgenf_medium_tcp4(doc) -> + []; +ttest_ssockt_cgenf_medium_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssockt_cgenf_medium_tcp4, + inet, + sock, true, + gen, false, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = true +%% Client: Transport = gen_tcp, Active = false +%% Message Size: medium (=2) +%% Domain: inet6 +%% + +ttest_ssockt_cgenf_medium_tcp6(suite) -> + []; +ttest_ssockt_cgenf_medium_tcp6(doc) -> + []; +ttest_ssockt_cgenf_medium_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssockt_cgenf_medium_tcp6, + inet6, + sock, true, + gen, false, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = true +%% Client: Transport = gen_tcp, Active = false +%% Message Size: large (=3) +%% Domain: inet +%% + +ttest_ssockt_cgenf_large_tcp4(suite) -> + []; +ttest_ssockt_cgenf_large_tcp4(doc) -> + []; +ttest_ssockt_cgenf_large_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssockt_cgenf_large_tcp4, + inet, + sock, true, + gen, false, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = true +%% Client: Transport = gen_tcp, Active = false +%% Message Size: large (=3) +%% Domain: inet6 +%% + +ttest_ssockt_cgenf_large_tcp6(suite) -> + []; +ttest_ssockt_cgenf_large_tcp6(doc) -> + []; +ttest_ssockt_cgenf_large_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssockt_cgenf_large_tcp6, + inet6, + sock, true, + gen, false, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = true +%% Client: Transport = gen_tcp, Active = once +%% Message Size: small (=1) +%% Domain: inet +%% + +ttest_ssockt_cgeno_small_tcp4(suite) -> + []; +ttest_ssockt_cgeno_small_tcp4(doc) -> + []; +ttest_ssockt_cgeno_small_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssockt_cgeno_small_tcp4, + inet, + sock, true, + gen, once, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = true +%% Client: Transport = gen_tcp, Active = once +%% Message Size: small (=1) +%% Domain: inet6 +%% + +ttest_ssockt_cgeno_small_tcp6(suite) -> + []; +ttest_ssockt_cgeno_small_tcp6(doc) -> + []; +ttest_ssockt_cgeno_small_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssockt_cgeno_small_tcp6, + inet6, + sock, true, + gen, once, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = true +%% Client: Transport = gen_tcp, Active = once +%% Message Size: medium (=2) +%% Domain: inet +%% + +ttest_ssockt_cgeno_medium_tcp4(suite) -> + []; +ttest_ssockt_cgeno_medium_tcp4(doc) -> + []; +ttest_ssockt_cgeno_medium_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssockt_cgeno_medium_tcp4, + inet, + sock, true, + gen, once, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = true +%% Client: Transport = gen_tcp, Active = once +%% Message Size: medium (=2) +%% Domain: inet6 +%% + +ttest_ssockt_cgeno_medium_tcp6(suite) -> + []; +ttest_ssockt_cgeno_medium_tcp6(doc) -> + []; +ttest_ssockt_cgeno_medium_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssockt_cgeno_medium_tcp6, + inet6, + sock, true, + gen, once, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = true +%% Client: Transport = gen_tcp, Active = once +%% Message Size: large (=3) +%% Domain: inet +%% + +ttest_ssockt_cgeno_large_tcp4(suite) -> + []; +ttest_ssockt_cgeno_large_tcp4(doc) -> + []; +ttest_ssockt_cgeno_large_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssockt_cgeno_large_tcp4, + inet, + sock, true, + gen, once, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = true +%% Client: Transport = gen_tcp, Active = once +%% Message Size: large (=3) +%% Domain: inet6 +%% + +ttest_ssockt_cgeno_large_tcp6(suite) -> + []; +ttest_ssockt_cgeno_large_tcp6(doc) -> + []; +ttest_ssockt_cgeno_large_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssockt_cgeno_large_tcp6, + inet6, + sock, true, + gen, once, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = true +%% Client: Transport = gen_tcp, Active = true +%% Message Size: small (=1) +%% Domain: inet +%% + +ttest_ssockt_cgent_small_tcp4(suite) -> + []; +ttest_ssockt_cgent_small_tcp4(doc) -> + []; +ttest_ssockt_cgent_small_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssockt_cgent_small_tcp4, + inet, + sock, true, + gen, true, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = true +%% Client: Transport = gen_tcp, Active = true +%% Message Size: small (=1) +%% Domain: inet6 +%% + +ttest_ssockt_cgent_small_tcp6(suite) -> + []; +ttest_ssockt_cgent_small_tcp6(doc) -> + []; +ttest_ssockt_cgent_small_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssockt_cgent_small_tcp6, + inet6, + sock, true, + gen, true, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = true +%% Client: Transport = gen_tcp, Active = true +%% Message Size: medium (=2) +%% Domain: inet +%% + +ttest_ssockt_cgent_medium_tcp4(suite) -> + []; +ttest_ssockt_cgent_medium_tcp4(doc) -> + []; +ttest_ssockt_cgent_medium_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssockt_cgent_medium_tcp4, + inet, + sock, true, + gen, true, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = true +%% Client: Transport = gen_tcp, Active = true +%% Message Size: medium (=2) +%% Domain: inet6 +%% + +ttest_ssockt_cgent_medium_tcp6(suite) -> + []; +ttest_ssockt_cgent_medium_tcp6(doc) -> + []; +ttest_ssockt_cgent_medium_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssockt_cgent_medium_tcp6, + inet6, + sock, true, + gen, true, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = true +%% Client: Transport = gen_tcp, Active = true +%% Message Size: large (=3) +%% Domain: inet +%% + +ttest_ssockt_cgent_large_tcp4(suite) -> + []; +ttest_ssockt_cgent_large_tcp4(doc) -> + []; +ttest_ssockt_cgent_large_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssockt_cgent_large_tcp4, + inet, + sock, true, + gen, true, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = true +%% Client: Transport = gen_tcp, Active = true +%% Message Size: large (=3) +%% Domain: inet6 +%% + +ttest_ssockt_cgent_large_tcp6(suite) -> + []; +ttest_ssockt_cgent_large_tcp6(doc) -> + []; +ttest_ssockt_cgent_large_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssockt_cgent_large_tcp6, + inet6, + sock, true, + gen, true, + 3, 2). + + + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% ttest_tcp(TC, -- cgit v1.2.3 From 88589149ae74505c411a9b7a1678962780055056 Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Wed, 19 Dec 2018 17:11:22 +0100 Subject: [socket-nif|test] Add more ttest (socket) test cases Added a number of ttest test cases with transport socket. Server with transport = socket(tcp), active = true and client using transport = socket(tcp) (and active = false, once and true). OTP-14831 --- erts/emulator/test/socket_SUITE.erl | 485 +++++++++++++++++++++++++++++++++++- 1 file changed, 482 insertions(+), 3 deletions(-) diff --git a/erts/emulator/test/socket_SUITE.erl b/erts/emulator/test/socket_SUITE.erl index 126f318ea6..9a5b781ccc 100644 --- a/erts/emulator/test/socket_SUITE.erl +++ b/erts/emulator/test/socket_SUITE.erl @@ -375,10 +375,30 @@ ttest_ssockt_cgent_medium_tcp4/1, ttest_ssockt_cgent_medium_tcp6/1, ttest_ssockt_cgent_large_tcp4/1, - ttest_ssockt_cgent_large_tcp6/1 + ttest_ssockt_cgent_large_tcp6/1, %% Server: transport = socket(tcp), active = true %% Client: transport = socket(tcp) + ttest_ssockt_csockf_small_tcp4/1, + ttest_ssockt_csockf_small_tcp6/1, + ttest_ssockt_csockf_medium_tcp4/1, + ttest_ssockt_csockf_medium_tcp6/1, + ttest_ssockt_csockf_large_tcp4/1, + ttest_ssockt_csockf_large_tcp6/1, + + ttest_ssockt_csocko_small_tcp4/1, + ttest_ssockt_csocko_small_tcp6/1, + ttest_ssockt_csocko_medium_tcp4/1, + ttest_ssockt_csocko_medium_tcp6/1, + ttest_ssockt_csocko_large_tcp4/1, + ttest_ssockt_csocko_large_tcp6/1, + + ttest_ssockt_csockt_small_tcp4/1, + ttest_ssockt_csockt_small_tcp6/1, + ttest_ssockt_csockt_medium_tcp4/1, + ttest_ssockt_csockt_medium_tcp6/1, + ttest_ssockt_csockt_large_tcp4/1, + ttest_ssockt_csockt_large_tcp6/1 %% Tickets ]). @@ -500,7 +520,11 @@ groups() -> {ttest_ssockt_cgen, [], ttest_ssockt_cgen_cases()}, {ttest_ssockt_cgenf, [], ttest_ssockt_cgenf_cases()}, {ttest_ssockt_cgeno, [], ttest_ssockt_cgeno_cases()}, - {ttest_ssockt_cgent, [], ttest_ssockt_cgent_cases()} + {ttest_ssockt_cgent, [], ttest_ssockt_cgent_cases()}, + {ttest_ssockt_csock, [], ttest_ssockt_csock_cases()}, + {ttest_ssockt_csockf, [], ttest_ssockt_csockf_cases()}, + {ttest_ssockt_csocko, [], ttest_ssockt_csocko_cases()}, + {ttest_ssockt_csockt, [], ttest_ssockt_csockt_cases()} %% {tickets, [], ticket_cases()} ]. @@ -685,7 +709,15 @@ ttest_cases() -> %% Server: transport = socket(tcp), active = once %% Client: transport = socket(tcp) (active = false, once and true) - {group, ttest_ssocko_csock} + {group, ttest_ssocko_csock}, + + %% Server: transport = socket(tcp), active = true + %% Client: transport = gen_tcp (active = false, once and true) + {group, ttest_ssockt_cgen}, + + %% Server: transport = socket(tcp), active = true + %% Client: transport = socket(tcp) (active = false, once and true) + {group, ttest_ssockt_csock} ]. @@ -1233,6 +1265,57 @@ ttest_ssockt_cgent_cases() -> ttest_ssockt_cgent_large_tcp6 ]. +%% Server: transport = socket(tcp), active = true +%% Client: transport = socket(tcp) +ttest_ssockt_csock_cases() -> + [ + {group, ttest_ssockt_csockf}, + {group, ttest_ssockt_csocko}, + {group, ttest_ssockt_csockt} + ]. + +%% Server: transport = socket(tcp), active = true +%% Client: transport = socket(tcp), active = false +ttest_ssockt_csockf_cases() -> + [ + ttest_ssockt_csockf_small_tcp4, + ttest_ssockt_csockf_small_tcp6, + + ttest_ssockt_csockf_medium_tcp4, + ttest_ssockt_csockf_medium_tcp6, + + ttest_ssockt_csockf_large_tcp4, + ttest_ssockt_csockf_large_tcp6 + ]. + +%% Server: transport = socket(tcp), active = true +%% Client: transport = socket(tcp), active = once +ttest_ssockt_csocko_cases() -> + [ + ttest_ssockt_csocko_small_tcp4, + ttest_ssockt_csocko_small_tcp6, + + ttest_ssockt_csocko_medium_tcp4, + ttest_ssockt_csocko_medium_tcp6, + + ttest_ssockt_csocko_large_tcp4, + ttest_ssockt_csocko_large_tcp6 + ]. + +%% Server: transport = socket(tcp), active = true +%% Client: transport = socket(tcp), active = true +ttest_ssockt_csockt_cases() -> + [ + ttest_ssockt_csockt_small_tcp4, + ttest_ssockt_csockt_small_tcp6, + + ttest_ssockt_csockt_medium_tcp4, + ttest_ssockt_csockt_medium_tcp6, + + ttest_ssockt_csockt_large_tcp4, + ttest_ssockt_csockt_large_tcp6 + ]. + %% ticket_cases() -> %% []. @@ -15393,6 +15476,402 @@ ttest_ssockt_cgent_large_tcp6(_Config) when is_list(_Config) -> +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = true +%% Client: Transport = socket(tcp), Active = false +%% Message Size: small (=1) +%% Domain: inet +%% + +ttest_ssockt_csockf_small_tcp4(suite) -> + []; +ttest_ssockt_csockf_small_tcp4(doc) -> + []; +ttest_ssockt_csockf_small_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssockt_csockf_small_tcp4, + inet, + sock, true, + sock, false, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = true +%% Client: Transport = socket(tcp), Active = false +%% Message Size: small (=1) +%% Domain: inet6 +%% + +ttest_ssockt_csockf_small_tcp6(suite) -> + []; +ttest_ssockt_csockf_small_tcp6(doc) -> + []; +ttest_ssockt_csockf_small_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssockt_csockf_small_tcp6, + inet6, + sock, true, + sock, false, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = true +%% Client: Transport = socket(tcp), Active = false +%% Message Size: medium (=2) +%% Domain: inet +%% + +ttest_ssockt_csockf_medium_tcp4(suite) -> + []; +ttest_ssockt_csockf_medium_tcp4(doc) -> + []; +ttest_ssockt_csockf_medium_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssockt_csockf_medium_tcp4, + inet, + sock, true, + sock, false, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = true +%% Client: Transport = socket(tcp), Active = false +%% Message Size: medium (=2) +%% Domain: inet6 +%% + +ttest_ssockt_csockf_medium_tcp6(suite) -> + []; +ttest_ssockt_csockf_medium_tcp6(doc) -> + []; +ttest_ssockt_csockf_medium_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssockt_csockf_medium_tcp6, + inet6, + sock, true, + sock, false, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = true +%% Client: Transport = socket(tcp), Active = false +%% Message Size: large (=3) +%% Domain: inet +%% + +ttest_ssockt_csockf_large_tcp4(suite) -> + []; +ttest_ssockt_csockf_large_tcp4(doc) -> + []; +ttest_ssockt_csockf_large_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssockt_csockf_large_tcp4, + inet, + sock, true, + sock, false, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = true +%% Client: Transport = socket(tcp), Active = false +%% Message Size: large (=3) +%% Domain: inet6 +%% + +ttest_ssockt_csockf_large_tcp6(suite) -> + []; +ttest_ssockt_csockf_large_tcp6(doc) -> + []; +ttest_ssockt_csockf_large_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssockt_csockf_large_tcp6, + inet6, + sock, true, + sock, false, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = true +%% Client: Transport = socket(tcp), Active = once +%% Message Size: small (=1) +%% Domain: inet +%% + +ttest_ssockt_csocko_small_tcp4(suite) -> + []; +ttest_ssockt_csocko_small_tcp4(doc) -> + []; +ttest_ssockt_csocko_small_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssockt_csocko_small_tcp4, + inet, + sock, true, + sock, once, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = true +%% Client: Transport = socket(tcp), Active = once +%% Message Size: small (=1) +%% Domain: inet6 +%% + +ttest_ssockt_csocko_small_tcp6(suite) -> + []; +ttest_ssockt_csocko_small_tcp6(doc) -> + []; +ttest_ssockt_csocko_small_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssockt_csocko_small_tcp6, + inet6, + sock, true, + sock, once, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = true +%% Client: Transport = socket(tcp), Active = once +%% Message Size: medium (=2) +%% Domain: inet +%% + +ttest_ssockt_csocko_medium_tcp4(suite) -> + []; +ttest_ssockt_csocko_medium_tcp4(doc) -> + []; +ttest_ssockt_csocko_medium_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssockt_csocko_medium_tcp4, + inet, + sock, true, + sock, once, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = true +%% Client: Transport = socket(tcp), Active = once +%% Message Size: medium (=2) +%% Domain: inet6 +%% + +ttest_ssockt_csocko_medium_tcp6(suite) -> + []; +ttest_ssockt_csocko_medium_tcp6(doc) -> + []; +ttest_ssockt_csocko_medium_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssockt_csocko_medium_tcp6, + inet6, + sock, true, + sock, once, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = true +%% Client: Transport = socket(tcp), Active = once +%% Message Size: large (=3) +%% Domain: inet +%% + +ttest_ssockt_csocko_large_tcp4(suite) -> + []; +ttest_ssockt_csocko_large_tcp4(doc) -> + []; +ttest_ssockt_csocko_large_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssockt_csocko_large_tcp4, + inet, + sock, true, + sock, once, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = true +%% Client: Transport = socket(tcp), Active = once +%% Message Size: large (=3) +%% Domain: inet6 +%% + +ttest_ssockt_csocko_large_tcp6(suite) -> + []; +ttest_ssockt_csocko_large_tcp6(doc) -> + []; +ttest_ssockt_csocko_large_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssockt_csocko_large_tcp6, + inet6, + sock, true, + sock, once, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = true +%% Client: Transport = socket(tcp), Active = true +%% Message Size: small (=1) +%% Domain: inet +%% + +ttest_ssockt_csockt_small_tcp4(suite) -> + []; +ttest_ssockt_csockt_small_tcp4(doc) -> + []; +ttest_ssockt_csockt_small_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssockt_csockt_small_tcp4, + inet, + sock, true, + sock, true, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = true +%% Client: Transport = socket(tcp), Active = true +%% Message Size: small (=1) +%% Domain: inet6 +%% + +ttest_ssockt_csockt_small_tcp6(suite) -> + []; +ttest_ssockt_csockt_small_tcp6(doc) -> + []; +ttest_ssockt_csockt_small_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssockt_csocko_small_tcp6, + inet6, + sock, true, + sock, true, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = true +%% Client: Transport = socket(tcp), Active = true +%% Message Size: medium (=2) +%% Domain: inet +%% + +ttest_ssockt_csockt_medium_tcp4(suite) -> + []; +ttest_ssockt_csockt_medium_tcp4(doc) -> + []; +ttest_ssockt_csockt_medium_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssockt_csockt_medium_tcp4, + inet, + sock, true, + sock, true, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = true +%% Client: Transport = socket(tcp), Active = true +%% Message Size: medium (=2) +%% Domain: inet6 +%% + +ttest_ssockt_csockt_medium_tcp6(suite) -> + []; +ttest_ssockt_csockt_medium_tcp6(doc) -> + []; +ttest_ssockt_csockt_medium_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssockt_csockt_medium_tcp6, + inet6, + sock, true, + sock, true, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = true +%% Client: Transport = socket(tcp), Active = true +%% Message Size: large (=3) +%% Domain: inet +%% + +ttest_ssockt_csockt_large_tcp4(suite) -> + []; +ttest_ssockt_csockt_large_tcp4(doc) -> + []; +ttest_ssockt_csockt_large_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssockt_csockt_large_tcp4, + inet, + sock, true, + sock, true, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = true +%% Client: Transport = socket(tcp), Active = true +%% Message Size: large (=3) +%% Domain: inet6 +%% + +ttest_ssockt_csockt_large_tcp6(suite) -> + []; +ttest_ssockt_csockt_large_tcp6(doc) -> + []; +ttest_ssockt_csockt_large_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssockt_csockt_large_tcp6, + inet6, + sock, true, + sock, true, + 3, 2). + + + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% ttest_tcp(TC, -- cgit v1.2.3 From 6cf1c94341dc274e93015f3b001b171140c5cba8 Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Thu, 20 Dec 2018 15:01:54 +0100 Subject: [socket-nif|test] ttest groupings --- erts/emulator/test/socket_SUITE.erl | 103 +++++++++++++++++++----------------- 1 file changed, 55 insertions(+), 48 deletions(-) diff --git a/erts/emulator/test/socket_SUITE.erl b/erts/emulator/test/socket_SUITE.erl index 9a5b781ccc..9ad15f06dc 100644 --- a/erts/emulator/test/socket_SUITE.erl +++ b/erts/emulator/test/socket_SUITE.erl @@ -449,18 +449,7 @@ all() -> {group, api}, {group, socket_closure}, {group, traffic}, - {group, ttest}, - {group, ttest_sgenf_cgen}, - {group, ttest_sgenf_csock}, - {group, ttest_sgeno_cgen}, - {group, ttest_sgeno_csock}, - {group, ttest_sgent_cgen}, - {group, ttest_sgent_csock}, - {group, ttest_ssockf_cgen}, - {group, ttest_ssockf_csock}, - {group, ttest_ssocko_cgen}, - {group, ttest_ssocko_csock}, - {group, ttest_ssockt_cgen} + {group, ttest} %% {group, tickets} ]. @@ -477,6 +466,7 @@ groups() -> {sc_remote_shutdown, [], sc_rs_cases()}, {traffic, [], traffic_cases()}, {ttest, [], ttest_cases()}, + {ttest_sgenf, [], ttest_sgenf_cases()}, {ttest_sgenf_cgen, [], ttest_sgenf_cgen_cases()}, {ttest_sgenf_cgenf, [], ttest_sgenf_cgenf_cases()}, {ttest_sgenf_cgeno, [], ttest_sgenf_cgeno_cases()}, @@ -485,6 +475,7 @@ groups() -> {ttest_sgenf_csockf, [], ttest_sgenf_csockf_cases()}, {ttest_sgenf_csocko, [], ttest_sgenf_csocko_cases()}, {ttest_sgenf_csockt, [], ttest_sgenf_csockt_cases()}, + {ttest_sgeno, [], ttest_sgeno_cases()}, {ttest_sgeno_cgen, [], ttest_sgeno_cgen_cases()}, {ttest_sgeno_cgenf, [], ttest_sgeno_cgenf_cases()}, {ttest_sgeno_cgeno, [], ttest_sgeno_cgeno_cases()}, @@ -493,6 +484,7 @@ groups() -> {ttest_sgeno_csockf, [], ttest_sgeno_csockf_cases()}, {ttest_sgeno_csocko, [], ttest_sgeno_csocko_cases()}, {ttest_sgeno_csockt, [], ttest_sgeno_csockt_cases()}, + {ttest_sgent, [], ttest_sgent_cases()}, {ttest_sgent_cgen, [], ttest_sgent_cgen_cases()}, {ttest_sgent_cgenf, [], ttest_sgent_cgenf_cases()}, {ttest_sgent_cgeno, [], ttest_sgent_cgeno_cases()}, @@ -501,6 +493,7 @@ groups() -> {ttest_sgent_csockf, [], ttest_sgent_csockf_cases()}, {ttest_sgent_csocko, [], ttest_sgent_csocko_cases()}, {ttest_sgent_csockt, [], ttest_sgent_csockt_cases()}, + {ttest_ssockf, [], ttest_ssockf_cases()}, {ttest_ssockf_cgen, [], ttest_ssockf_cgen_cases()}, {ttest_ssockf_cgenf, [], ttest_ssockf_cgenf_cases()}, {ttest_ssockf_cgeno, [], ttest_ssockf_cgeno_cases()}, @@ -509,6 +502,7 @@ groups() -> {ttest_ssockf_csockf, [], ttest_ssockf_csockf_cases()}, {ttest_ssockf_csocko, [], ttest_ssockf_csocko_cases()}, {ttest_ssockf_csockt, [], ttest_ssockf_csockt_cases()}, + {ttest_ssocko, [], ttest_ssocko_cases()}, {ttest_ssocko_cgen, [], ttest_ssocko_cgen_cases()}, {ttest_ssocko_cgenf, [], ttest_ssocko_cgenf_cases()}, {ttest_ssocko_cgeno, [], ttest_ssocko_cgeno_cases()}, @@ -517,6 +511,7 @@ groups() -> {ttest_ssocko_csockf, [], ttest_ssocko_csockf_cases()}, {ttest_ssocko_csocko, [], ttest_ssocko_csocko_cases()}, {ttest_ssocko_csockt, [], ttest_ssocko_csockt_cases()}, + {ttest_ssockt, [], ttest_ssockt_cases()}, {ttest_ssockt_cgen, [], ttest_ssockt_cgen_cases()}, {ttest_ssockt_cgenf, [], ttest_ssockt_cgenf_cases()}, {ttest_ssockt_cgeno, [], ttest_ssockt_cgeno_cases()}, @@ -672,56 +667,33 @@ traffic_cases() -> ttest_cases() -> [ %% Server: transport = gen_tcp, active = false - %% Client: transport = gen_tcp (active = false, once and true) - {group, ttest_sgenf_cgen}, - - %% Server: transport = gen_tcp, active = false - %% Client: transport = socket(tcp) (active = false, once and true) - {group, ttest_sgenf_csock}, - - %% Server: transport = gen_tcp, active = once - %% Client: transport = gen_tcp (active = false, once and true) - {group, ttest_sgeno_cgen}, + {group, ttest_sgenf}, %% Server: transport = gen_tcp, active = once - %% Client: transport = socket(tcp) (active = false, once and true) - {group, ttest_sgeno_csock}, - - %% Server: transport = gen_tcp, active = true - %% Client: transport = gen_tcp (active = false, once and true) - {group, ttest_sgent_cgen}, + {group, ttest_sgeno}, %% Server: transport = gen_tcp, active = true - %% Client: transport = socket(tcp) (active = false, once and true) - {group, ttest_sgent_csock}, + {group, ttest_sgent}, %% Server: transport = socket(tcp), active = false - %% Client: transport = gen_tcp (active = false, once and true) - {group, ttest_ssockf_cgen}, - - %% Server: transport = socket(tcp), active = false - %% Client: transport = socket(tcp) (active = false, once and true) - {group, ttest_ssockf_csock}, + {group, ttest_ssockf}, %% Server: transport = socket(tcp), active = once - %% Client: transport = gen_tcp (active = false, once and true) - {group, ttest_ssocko_cgen}, - - %% Server: transport = socket(tcp), active = once - %% Client: transport = socket(tcp) (active = false, once and true) - {group, ttest_ssocko_csock}, - - %% Server: transport = socket(tcp), active = true - %% Client: transport = gen_tcp (active = false, once and true) - {group, ttest_ssockt_cgen}, + {group, ttest_ssocko}, %% Server: transport = socket(tcp), active = true - %% Client: transport = socket(tcp) (active = false, once and true) - {group, ttest_ssockt_csock} + {group, ttest_ssockt} ]. +%% Server: transport = gen_tcp, active = false +ttest_sgenf_cases() -> + [ + {group, ttest_sgenf_cgen}, + {group, ttest_sgenf_csock} + ]. + %% Server: transport = gen_tcp, active = false %% Client: transport = gen_tcp ttest_sgenf_cgen_cases() -> @@ -818,6 +790,13 @@ ttest_sgenf_csockt_cases() -> ttest_sgenf_csockt_large_tcp6 ]. +%% Server: transport = gen_tcp, active = once +ttest_sgeno_cases() -> + [ + {group, ttest_sgeno_cgen}, + {group, ttest_sgeno_csock} + ]. + %% Server: transport = gen_tcp, active = once %% Client: transport = gen_tcp ttest_sgeno_cgen_cases() -> @@ -914,6 +893,13 @@ ttest_sgeno_csockt_cases() -> ttest_sgeno_csockt_large_tcp6 ]. +%% Server: transport = gen_tcp, active = true +ttest_sgent_cases() -> + [ + {group, ttest_sgent_cgen}, + {group, ttest_sgent_csock} + ]. + %% Server: transport = gen_tcp, active = true %% Client: transport = gen_tcp ttest_sgent_cgen_cases() -> @@ -1010,6 +996,13 @@ ttest_sgent_csockt_cases() -> ttest_sgent_csockt_large_tcp6 ]. +%% Server: transport = socket(tcp), active = false +ttest_ssockf_cases() -> + [ + {group, ttest_ssockf_cgen}, + {group, ttest_ssockf_csock} + ]. + %% Server: transport = socket(tcp), active = false %% Client: transport = gen_tcp ttest_ssockf_cgen_cases() -> @@ -1112,6 +1105,13 @@ ttest_ssockf_csockt_cases() -> ttest_ssockf_csockt_large_tcp6 ]. +%% Server: transport = socket(tcp), active = once +ttest_ssocko_cases() -> + [ + {group, ttest_ssocko_cgen}, + {group, ttest_ssocko_csock} + ]. + %% Server: transport = socket(tcp), active = once %% Client: transport = gen_tcp ttest_ssocko_cgen_cases() -> @@ -1214,6 +1214,13 @@ ttest_ssocko_csockt_cases() -> ttest_ssocko_csockt_large_tcp6 ]. +%% Server: transport = socket(tcp), active = true +ttest_ssockt_cases() -> + [ + {group, ttest_ssockt_cgen}, + {group, ttest_ssockt_csock} + ]. + %% Server: transport = socket(tcp), active = true %% Client: transport = gen_tcp ttest_ssockt_cgen_cases() -> -- cgit v1.2.3 From cc553264b4a6e06377b34c06858f75f3a17319de Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Wed, 19 Dec 2018 13:04:28 +0100 Subject: [socket-nif] Fixed OpenBSD config The macro (define) IPTOS_MINCOST does not exist on OpenBSD 6.3. OTP-14831 --- erts/emulator/nifs/common/socket_nif.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/erts/emulator/nifs/common/socket_nif.c b/erts/emulator/nifs/common/socket_nif.c index 80903c487f..6dcd4ae623 100644 --- a/erts/emulator/nifs/common/socket_nif.c +++ b/erts/emulator/nifs/common/socket_nif.c @@ -15198,9 +15198,11 @@ char* encode_cmsghdr_data_ip(ErlNifEnv* env, case IPTOS_RELIABILITY: *eCMsgHdrData = esock_atom_reliability; break; +#if defined(IPTOS_MINCOST) case IPTOS_MINCOST: *eCMsgHdrData = esock_atom_mincost; break; +#endif default: *eCMsgHdrData = MKUI(env, tos); break; @@ -15467,9 +15469,11 @@ BOOLEAN_T decode_ip_tos(ErlNifEnv* env, ERL_NIF_TERM eVal, int* val) } else if (COMPARE(eVal, esock_atom_reliability) == 0) { *val = IPTOS_RELIABILITY; result = TRUE; +#if defined(IPTOS_MINCOST) } else if (COMPARE(eVal, esock_atom_mincost) == 0) { *val = IPTOS_MINCOST; result = TRUE; +#endif } else { *val = -1; result = FALSE; @@ -15781,9 +15785,11 @@ ERL_NIF_TERM encode_ip_tos(ErlNifEnv* env, int val) result = esock_make_ok2(env, esock_atom_reliability); break; +#if defined(IPTOS_MINCOST) case IPTOS_MINCOST: result = esock_make_ok2(env, esock_atom_mincost); break; +#endif default: result = esock_make_ok2(env, MKI(env, val)); -- cgit v1.2.3 From 6023982cc4fc751fa5c1d858f1770a7e27094061 Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Wed, 19 Dec 2018 16:34:43 +0100 Subject: [socket-nif] Fixed erl_interface config/make issues on OpenBSD --- lib/erl_interface/src/Makefile.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/erl_interface/src/Makefile.in b/lib/erl_interface/src/Makefile.in index 614e7325a9..30e7c04289 100644 --- a/lib/erl_interface/src/Makefile.in +++ b/lib/erl_interface/src/Makefile.in @@ -31,7 +31,7 @@ .PHONY : debug opt release clean distclean depend -TARGET = @TARGET@ +include $(ERL_TOP)/make/target.mk # ---------------------------------------------------- # Application version and release dir specification -- cgit v1.2.3 From 10e9fef8a1d28095c18e735ccc1102c972fa14c9 Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Wed, 19 Dec 2018 16:35:42 +0100 Subject: [socket-nif] Updated gitignore for OpenBSD --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 4bd743f83a..daeff7ad22 100644 --- a/.gitignore +++ b/.gitignore @@ -38,6 +38,7 @@ sparc-sun-solaris[0-9]*.[0-9]* i386-pc-solaris[0-9]*.[0-9]* i386-unknown-freebsd[0-9]*.[0-9]* x86_64-unknown-freebsd[0-9]*.[0-9]* +x86_64-unknown-openbsd[0-9]*.[0-9]* tile-tilera-linux-gnu powerpc-unknown-linux-gnu aarch64-unknown-linux-gnu -- cgit v1.2.3 From ff3ce49e25ea150c0637bcb584cffdd88e560e5b Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Wed, 19 Dec 2018 18:06:03 +0100 Subject: [socket-nif|test] Updated test case api_b_open_and_close The test case tried to validate the (socket option) protocol, which is not actually available on OpenBSD. So, add a check if the option is supported. OTP-14831 --- erts/emulator/test/socket_SUITE.erl | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/erts/emulator/test/socket_SUITE.erl b/erts/emulator/test/socket_SUITE.erl index 9ad15f06dc..f77eb5502c 100644 --- a/erts/emulator/test/socket_SUITE.erl +++ b/erts/emulator/test/socket_SUITE.erl @@ -1444,11 +1444,20 @@ api_b_open_and_close(InitState) -> end}, #{desc => "get protocol", cmd => fun(#{socket := Sock} = State) -> - Res = socket:getopt(Sock, socket, protocol), - {ok, {State, Res}} + case socket:supports(options, socket, protocol) of + true -> + Res = socket:getopt(Sock, socket, protocol), + {ok, {State, Res}}; + false -> + {ok, {State, not_supported}} + end end}, #{desc => "validate protocol", - cmd => fun({#{protocol := Protocol} = State, {ok, Protocol}}) -> + cmd => fun({State, not_supported}) -> + ?SEV_IPRINT("socket option 'protocol' " + "not supported"), + {ok, State}; + ({#{protocol := Protocol} = State, {ok, Protocol}}) -> {ok, State}; ({#{protocol := ExpProtocol}, {ok, Protocol}}) -> {error, {unexpected_type, ExpProtocol, Protocol}}; -- cgit v1.2.3 From 5cb57ba153b9febfca17f90166791e104262615e Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Fri, 21 Dec 2018 12:16:44 +0100 Subject: [socket-nif|test] Buffer set and adjusted traffic ping-pong iterations Handle (rcv and snd) buffer set failure. The number of iterations (message exchanges) that the traffic ping-cases has been adjusted down (by a factor of 10). Also, improve skip'ing. OTP-14831 --- erts/emulator/test/socket_SUITE.erl | 54 ++++++++++++++++++++++------ erts/emulator/test/socket_test_evaluator.erl | 14 ++++++-- 2 files changed, 56 insertions(+), 12 deletions(-) diff --git a/erts/emulator/test/socket_SUITE.erl b/erts/emulator/test/socket_SUITE.erl index f77eb5502c..3adda52e1e 100644 --- a/erts/emulator/test/socket_SUITE.erl +++ b/erts/emulator/test/socket_SUITE.erl @@ -18,7 +18,14 @@ %% %CopyrightEnd% %% +%% Run the entire test suite: %% ts:run(emulator, socket_SUITE, [batch]). +%% +%% Run a specific group: +%% ts:run(emulator, socket_SUITE, {group, foo}, [batch]). +%% +%% Run a specific test case: +%% ts:run(emulator, socket_SUITE, foo, [batch]). -module(socket_SUITE). @@ -433,9 +440,9 @@ -define(TPP_MEDIUM, lists:flatten(lists:duplicate(1024, ?TPP_SMALL))). -define(TPP_LARGE, lists:flatten(lists:duplicate(1024, ?TPP_MEDIUM))). --define(TPP_SMALL_NUM, 100000). --define(TPP_MEDIUM_NUM, 100000). --define(TPP_LARGE_NUM, 1000). +-define(TPP_SMALL_NUM, 10000). +-define(TPP_MEDIUM_NUM, 1000). +-define(TPP_LARGE_NUM, 100). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -2736,6 +2743,7 @@ api_to_connect_tcp(InitState) -> end}, #{desc => "create node", cmd => fun(#{host := Host} = State) -> + ?SEV_IPRINT("try create node on ~p", [Host]), case start_node(Host, client) of {ok, Node} -> ?SEV_IPRINT("client node ~p started", @@ -9475,14 +9483,34 @@ traffic_ping_pong_sendmsg_and_recvmsg_tcp(InitState) -> traffic_ping_pong_send_and_receive_tcp(#{msg := Msg} = InitState) -> Fun = fun(Sock) -> {ok, RcvSz} = socket:getopt(Sock, socket, rcvbuf), + ?SEV_IPRINT("RcvBuf is ~p (needs atleast ~p)", + [RcvSz, 16+size(Msg)]), if (RcvSz < size(Msg)) -> - ok = socket:setopt(Sock, socket, rcvbuf, 1024+size(Msg)); + case socket:setopt(Sock, + socket, rcvbuf, 1024+size(Msg)) of + ok -> + ok; + {error, enobufs} -> + skip({failed_change, rcvbuf}); + {error, Reason1} -> + ?FAIL({rcvbuf, Reason1}) + end; true -> ok end, {ok, SndSz} = socket:getopt(Sock, socket, sndbuf), + ?SEV_IPRINT("SndBuf is ~p (needs atleast ~p)", + [SndSz, 16+size(Msg)]), if (SndSz < size(Msg)) -> - ok = socket:setopt(Sock, socket, sndbuf, 1024+size(Msg)); + case socket:setopt(Sock, + socket, sndbuf, 1024+size(Msg)) of + ok -> + ok; + {error, enobufs} -> + skip({failed_change, sndbuf}); + {error, Reason2} -> + ?FAIL({sndbuf, Reason2}) + end; true -> ok end, @@ -9924,7 +9952,7 @@ traffic_ping_pong_send_and_receive_tcp2(InitState) -> Result2 = erlang:delete_element(1, Result), {ok, State#{server_result => Result2}}; {ok, BadResult} -> - ?SEV_EPRINT("bad sever result: " + ?SEV_EPRINT("bad server result: " "~n ~p", [BadResult]), {error, {invalid_server_result, BadResult}}; {error, _} = ERROR -> @@ -10166,10 +10194,10 @@ tpp_tcp_client_await_continue(Parent, Slogan) -> ?SEV_IPRINT("await continue (~p)", [Slogan]), case ?SEV_AWAIT_CONTINUE(Parent, parent, Slogan) of ok -> - %% ?SEV_IPRINT("continue (~p): ok", [Slogan]), + ?SEV_IPRINT("continue (~p): ok", [Slogan]), ok; {ok, Data} -> - %% ?SEV_IPRINT("continue (~p): ok with data", [Slogan]), + ?SEV_IPRINT("continue (~p): ok with data", [Slogan]), Data; {error, Reason} -> ?SEV_EPRINT("continue (~p): error" @@ -16526,7 +16554,11 @@ sock_close(Sock) -> local_host() -> try net_adm:localhost() of Host when is_list(Host) -> - list_to_atom(Host) + %% Convert to shortname if long + case string:tokens(Host, [$.]) of + [H|_] -> + list_to_atom(H) + end catch C:E:S -> erlang:raise(C, E, S) @@ -16546,7 +16578,9 @@ which_local_addr(Domain) -> which_addr(_Domain, []) -> ?FAIL(no_address); -which_addr(Domain, [{Name, IFO}|_IFL]) when (Name =/= "lo") -> +which_addr(Domain, [{"lo" ++ _, _}|IFL]) -> + which_addr(Domain, IFL); +which_addr(Domain, [{_Name, IFO}|_IFL]) -> which_addr2(Domain, IFO); which_addr(Domain, [_|IFL]) -> which_addr(Domain, IFL). diff --git a/erts/emulator/test/socket_test_evaluator.erl b/erts/emulator/test/socket_test_evaluator.erl index deea7e5d36..fe6a6ff70a 100644 --- a/erts/emulator/test/socket_test_evaluator.erl +++ b/erts/emulator/test/socket_test_evaluator.erl @@ -130,6 +130,10 @@ loop(ID, [#{desc := Desc, "~n Reason: ~p", [ID, Reason]), exit({command_failed, ID, Reason, State}) catch + throw:{skip, R} = E:_ -> + eprint("command ~w skip: " + "~n Skip Reason: ~p", [ID, R]), + exit(E); C:E:S -> eprint("command ~w crashed: " "~n Class: ~p" @@ -150,6 +154,8 @@ await_finish(Evs) -> await_finish([], []) -> ok; await_finish([], Fails) -> + ?SEV_EPRINT("Fails: " + "~n ~p", [Fails]), Fails; await_finish(Evs, Fails) -> receive @@ -443,8 +449,12 @@ await(ExpPid, Name, Announcement, Slogan, OtherPids) ok; {Announcement, Pid, Slogan, Extra} when (Pid =:= ExpPid) -> {ok, Extra}; + {'DOWN', _, process, Pid, {skip, SkipReason}} when (Pid =:= ExpPid) -> + iprint("Unexpected SKIP from ~w (~p): " + "~n ~p", [Name, Pid, SkipReason]), + ?LIB:skip({Name, SkipReason}); {'DOWN', _, process, Pid, Reason} when (Pid =:= ExpPid) -> - eprint("Unexpected DOWN regarding ~w ~p: " + eprint("Unexpected DOWN from ~w (~p): " "~n ~p", [Name, Pid, Reason]), {error, {unexpected_exit, Name}}; {'DOWN', _, process, OtherPid, Reason} -> @@ -476,7 +486,7 @@ pi(Pid, Item) -> check_down(Pid, DownReason, Pids) -> case lists:keymember(Pid, 1, Pids) of {value, {_, Name}} -> - eprint("Unexpected DOWN regarding ~w ~p: " + eprint("Unexpected DOWN from ~w (~p): " "~n ~p", [Name, Pid, DownReason]), {error, {unexpected_exit, Name}}; false -> -- cgit v1.2.3 From e8dace61a2ec8c997e3051547f1d401053626088 Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Fri, 21 Dec 2018 16:49:18 +0100 Subject: [socket-nif|test] Adjusten runtime of test cases of group ttest It took "forever" to run the ttest test cases, so the runtime of these have been adjusted down to 10 seconds (from 60 seconds). OTP-14831 --- erts/emulator/test/socket_SUITE.erl | 45 +++++-------------------------------- 1 file changed, 5 insertions(+), 40 deletions(-) diff --git a/erts/emulator/test/socket_SUITE.erl b/erts/emulator/test/socket_SUITE.erl index 3adda52e1e..6d44a245e4 100644 --- a/erts/emulator/test/socket_SUITE.erl +++ b/erts/emulator/test/socket_SUITE.erl @@ -444,6 +444,8 @@ -define(TPP_MEDIUM_NUM, 1000). -define(TPP_LARGE_NUM, 100). +-define(TTEST_RUNTIME, ?SECS(10)). + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -15923,10 +15925,12 @@ ttest_tcp(TC, ServerMod, ServerActive, ClientMod, ClientActive, MsgID, MaxOutstanding) -> - Runtime = ?SECS(60), + Runtime = ?TTEST_RUNTIME, tc_try(TC, fun() -> if (Domain =/= inet) -> not_yet_implemented(); true -> ok end, + %% This may be overkill, depending on the runtime, + %% but better safe then sorry... ?TT(Runtime + ?SECS(60)), InitState = #{domain => Domain, msg_id => MsgID, @@ -15942,45 +15946,6 @@ ttest_tcp(TC, %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% This test is run with the following combinations: -%% (this does not take the message size into consideration) -%% server(gen, false) - client(gen, false) -%% server(gen, false) - client(gen, once) -%% server(gen, false) - client(gen, true) -%% server(gen, false) - client(sock, false) -%% server(gen, false) - client(sock, once) -%% server(gen, false) - client(sock, true) -%% server(gen, once) - client(gen, false) -%% server(gen, once) - client(gen, once) -%% server(gen, once) - client(gen, true) -%% server(gen, once) - client(sock, false) -%% server(gen, once) - client(sock, once) -%% server(gen, once) - client(sock, true) -%% server(gen, true) - client(gen, false) -%% server(gen, true) - client(gen, once) -%% server(gen, true) - client(gen, true) -%% server(gen, true) - client(sock, false) -%% server(gen, true) - client(sock, once) -%% server(gen, true) - client(sock, true) -%% server(sock, false) - client(gen, false) -%% server(sock, false) - client(gen, once) -%% server(sock, false) - client(gen, true) -%% server(sock, false) - client(sock, false) -%% server(sock, false) - client(sock, once) -%% server(sock, false) - client(sock, true) -%% server(sock, once) - client(gen, false) -%% server(sock, once) - client(gen, once) -%% server(sock, once) - client(gen, true) -%% server(sock, once) - client(sock, false) -%% server(sock, once) - client(sock, once) -%% server(sock, once) - client(sock, true) -%% server(sock, true) - client(gen, false) -%% server(sock, true) - client(gen, once) -%% server(sock, true) - client(gen, true) -%% server(sock, true) - client(sock, false) -%% server(sock, true) - client(sock, once) -%% server(sock, true) - client(sock, true) - ttest_tcp(InitState) -> ServerSeq = [ -- cgit v1.2.3 From 2fb8288e182116180c977aa2c623d066171f6c16 Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Fri, 21 Dec 2018 17:07:04 +0100 Subject: [socket-nif|test] Add simple (escript) wrapper for ttest Add a simple wrapper script for the socket ttest program(s). Intended to be used when running a performance test in a standard shell (bash). OTP-14831 --- erts/emulator/test/esock-ttest | 350 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 350 insertions(+) create mode 100755 erts/emulator/test/esock-ttest diff --git a/erts/emulator/test/esock-ttest b/erts/emulator/test/esock-ttest new file mode 100755 index 0000000000..418b19eec1 --- /dev/null +++ b/erts/emulator/test/esock-ttest @@ -0,0 +1,350 @@ +#!/usr/bin/env escript + +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2018-2018. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% + +%% ========================================================================== +%% +%% This is a simple wrapper escript on top of the socket ttest program(s). +%% The idea is to make it simple to run in a normal shell (bash). +%% +%% ========================================================================== + +-define(SECS(I), timer:seconds(I)). + +-define(CLIENT_MSG_1_MAX_OUTSTANDING, 100). +-define(CLIENT_MSG_2_MAX_OUTSTANDING, 10). +-define(CLIENT_MSG_3_MAX_OUTSTANDING, 1). + +main(Args) -> + State = process_args(Args), + exec(State), + ok. + +usage(ErrorString) when is_list(ErrorString) -> + eprint(ErrorString), + usage(), + erlang:halt(0). + +usage() -> + io:format("usage: ~s [options]" + "~n" + "~n This erlang script is used to start the (e)socket ttest " + "~n units (server or client)." + "~n" + "~n options: " + "~n --help Display this info and exit. " + "~n --server [server-options] Start a server. " + "~n There are no mandatory server options." + "~n --client client-options Start a client" + "~n Some client options are mandatory and" + "~n others optional." + "~n --active boolean() | once." + "~n Valid for both client and server." + "~n Defaults to: false" + "~n --transport Which transport to use: gen|sock[:plain|msg]" + "~n gen: gen_tcp" + "~n sock: socket" + "~n plain: recv/send (default)" + "~n msg: recvmsg/sendmsg" + "~n Defaults to: sock:plain" + "~n --scon : Address and port of the server." + "~n The address part is in the standard form:" + "~n \"a.b.c.d\"." + "~n Only valid for client." + "~n Mandatory." + "~n --msg-id <1|2|3> Choose which message to use during the test." + "~n Basically: " + "~n 1: small" + "~n 2: medium" + "~n 3: large" + "~n Defaults to: 1" + "~n --max-outstanding How many messages to send before waiting for" + "~n a reply." + "~n Valid only for client." + "~n Defaults to: " + "~n MsgID 1: 100" + "~n MsgID 2: 10" + "~n MsgID 3: 1" + "~n --runtime