aboutsummaryrefslogtreecommitdiffstats
path: root/erts/emulator/nifs/common/socket_nif.c
diff options
context:
space:
mode:
authorMicael Karlberg <[email protected]>2018-03-22 17:57:15 +0100
committerMicael Karlberg <[email protected]>2018-09-18 13:01:37 +0200
commit3ca71520bfb664f0ea809ffdf41505936e4d5e90 (patch)
tree1e14f5b647ac4e41d376e4ff47bb7570b4ea00bc /erts/emulator/nifs/common/socket_nif.c
parent716d9c2256ddbba2417fd39053a891b870c43472 (diff)
downloadotp-3ca71520bfb664f0ea809ffdf41505936e4d5e90.tar.gz
otp-3ca71520bfb664f0ea809ffdf41505936e4d5e90.tar.bz2
otp-3ca71520bfb664f0ea809ffdf41505936e4d5e90.zip
[socket-nif] preliminary version of the new socket interface (nififying)
Diffstat (limited to 'erts/emulator/nifs/common/socket_nif.c')
-rw-r--r--erts/emulator/nifs/common/socket_nif.c1927
1 files changed, 1927 insertions, 0 deletions
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 <stdio.h> */
+/* #include <stdlib.h> */
+/* #include <stdarg.h> */
+/* #include <string.h> */
+/* #include <unistd.h> */
+/* #include <errno.h> */
+/* #include <netdb.h> */
+/* #include <sys/types.h> */
+/* #include <sys/wait.h> */
+/* #include <sys/socket.h> */
+/* #include <netinet/in.h> */
+/* #include <arpa/inet.h> */
+/* #include <sys/time.h> */
+/* #include <fcntl.h> */
+
+
+#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 <stdio.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <ctype.h>
+#include <sys/types.h>
+#include <errno.h>
+
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#ifdef HAVE_SYS_UIO_H
+#include <sys/uio.h>
+#endif
+
+#ifdef HAVE_NET_IF_DL_H
+#include <net/if_dl.h>
+#endif
+
+#ifdef HAVE_IFADDRS_H
+#include <ifaddrs.h>
+#endif
+
+#ifdef HAVE_NETPACKET_PACKET_H
+#include <netpacket/packet.h>
+#endif
+
+#ifdef HAVE_SYS_UN_H
+#include <sys/un.h>
+#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 <winsock2.h>
+#endif
+#include <windows.h>
+#include <Ws2tcpip.h> /* 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 <sdkddkver.h>
+# ifdef NTDDI_VERSION
+# undef NTDDI_VERSION
+# endif
+# define NTDDI_VERSION NTDDI_WINXP
+#endif
+#include <iphlpapi.h>
+
+#undef WANT_NONBLOCKING
+#include "sys.h"
+
+#else /* !__WIN32__ */
+
+#include <sys/time.h>
+#ifdef NETDB_H_NEEDS_IN_H
+#include <netinet/in.h>
+#endif
+#include <netdb.h>
+
+#include <sys/socket.h>
+#include <netinet/in.h>
+
+#ifdef DEF_INADDR_LOOPBACK_IN_RPC_TYPES_H
+#include <rpc/types.h>
+#endif
+
+#include <netinet/tcp.h>
+#include <arpa/inet.h>
+
+#include <sys/param.h>
+#ifdef HAVE_ARPA_NAMESER_H
+#include <arpa/nameser.h>
+#endif
+
+#ifdef HAVE_SYS_SOCKIO_H
+#include <sys/sockio.h>
+#endif
+
+#ifdef HAVE_SYS_IOCTL_H
+#include <sys/ioctl.h>
+#endif
+
+#include <net/if.h>
+
+#ifdef HAVE_SCHED_H
+#include <sched.h>
+#endif
+
+#ifdef HAVE_SETNS_H
+#include <setns.h>
+#endif
+
+#define HAVE_UDP
+
+#ifndef WANT_NONBLOCKING
+#define WANT_NONBLOCKING
+#endif
+#include "sys.h"
+
+#endif
+
+#include <erl_nif.h>
+
+
+/* All platforms fail on malloc errors. */
+#define FATAL_MALLOC
+
+
+
+/* *** Boolean *type* stuff... *** */
+typedef unsigned int BOOLEAN_T;
+#define TRUE 1
+#define FALSE 0
+#define BOOL2STR(__B__) ((__B__) ? "true" : "false")
+#define BOOL2ATOM(__B__) ((__B__) ? atom_true : atom_false)
+
+
+/* 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, &current_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)