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/nifs/common/socket_nif.c | 1927 ++++++++++++++++++++++++++++++++ 1 file changed, 1927 insertions(+) create mode 100644 erts/emulator/nifs/common/socket_nif.c (limited to 'erts/emulator/nifs/common') 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) -- cgit v1.2.3