/* * %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 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 *** */ #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_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 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_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)) #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))) #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)) #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)) #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)) #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 { 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; 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; /* 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 */ 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_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_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[]); 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 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); #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_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 /* *** 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_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; /* *** 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_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 ((sock_close(sock) == INVALID_SOCKET) && (sock_errno() == EINTR)); return make_error2(env, save_errno); } SET_NONBLOCKING(sock); /* Create and initiate the socket "descriptor" */ 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->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 ... /* 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(&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 * * 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) { int code; char* xerr; /* 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 = SOCKET_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(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 != SOCKET_STATE_CONNECTING) return make_error(env, atom_eisnconn); 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 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); if ((code < 0) || error) { *err = error; return FALSE; } #endif /* SO_ERROR */ *err = 0; return TRUE; } /* ---------------------------------------------------------------------- * 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; } /* ---------------------------------------------------------------------- * 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 * ---------------------------------------------------------------------- */ /* 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_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_finalize_connection * is called after the connect *select* has "completed". */ {"nif_finalize_connection", 1, nif_finalize_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_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); // 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); 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)