aboutsummaryrefslogtreecommitdiffstats
path: root/erts
diff options
context:
space:
mode:
Diffstat (limited to 'erts')
-rw-r--r--erts/emulator/Makefile.in2
-rw-r--r--erts/emulator/nifs/common/socket_nif.c1927
-rw-r--r--erts/preloaded/ebin/init.beambin51428 -> 51508 bytes
-rw-r--r--erts/preloaded/src/Makefile1
-rw-r--r--erts/preloaded/src/init.erl1
-rw-r--r--erts/preloaded/src/socket.erl878
6 files changed, 2809 insertions, 0 deletions
diff --git a/erts/emulator/Makefile.in b/erts/emulator/Makefile.in
index 054692819e..6ef36ed3e4 100644
--- a/erts/emulator/Makefile.in
+++ b/erts/emulator/Makefile.in
@@ -640,6 +640,7 @@ PRELOAD_BEAM = $(ERL_TOP)/erts/preloaded/ebin/otp_ring0.beam \
$(ERL_TOP)/erts/preloaded/ebin/prim_eval.beam \
$(ERL_TOP)/erts/preloaded/ebin/prim_inet.beam \
$(ERL_TOP)/erts/preloaded/ebin/prim_file.beam \
+ $(ERL_TOP)/erts/preloaded/ebin/socket.beam \
$(ERL_TOP)/erts/preloaded/ebin/zlib.beam \
$(ERL_TOP)/erts/preloaded/ebin/prim_zip.beam \
$(ERL_TOP)/erts/preloaded/ebin/erl_prim_loader.beam \
@@ -881,6 +882,7 @@ NIF_OBJS = \
$(OBJDIR)/erl_tracer_nif.o \
$(OBJDIR)/prim_buffer_nif.o \
$(OBJDIR)/prim_file_nif.o \
+ $(OBJDIR)/socket_nif.o \
$(OBJDIR)/zlib_nif.o
ifeq ($(TARGET),win32)
diff --git a/erts/emulator/nifs/common/socket_nif.c b/erts/emulator/nifs/common/socket_nif.c
new file mode 100644
index 0000000000..2c51ff2000
--- /dev/null
+++ b/erts/emulator/nifs/common/socket_nif.c
@@ -0,0 +1,1927 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 2018-2018. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * %CopyrightEnd%
+ *
+ * ----------------------------------------------------------------------
+ * Purpose : The NIF (C) part of the socket interface
+ * ----------------------------------------------------------------------
+ *
+ */
+
+/* #include <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)
diff --git a/erts/preloaded/ebin/init.beam b/erts/preloaded/ebin/init.beam
index 858a9dc63e..6658ff1a33 100644
--- a/erts/preloaded/ebin/init.beam
+++ b/erts/preloaded/ebin/init.beam
Binary files differ
diff --git a/erts/preloaded/src/Makefile b/erts/preloaded/src/Makefile
index 4333f6643a..aa9390b038 100644
--- a/erts/preloaded/src/Makefile
+++ b/erts/preloaded/src/Makefile
@@ -39,6 +39,7 @@ PRE_LOADED_ERL_MODULES = \
prim_buffer \
prim_file \
prim_inet \
+ socket \
zlib \
prim_zip \
otp_ring0 \
diff --git a/erts/preloaded/src/init.erl b/erts/preloaded/src/init.erl
index 253fcf7a1f..303301cbff 100644
--- a/erts/preloaded/src/init.erl
+++ b/erts/preloaded/src/init.erl
@@ -202,6 +202,7 @@ boot(BootArgs) ->
%% Load the static nifs
zlib:on_load(),
+ socket:on_load(),
erl_tracer:on_load(),
prim_buffer:on_load(),
prim_file:on_load(),
diff --git a/erts/preloaded/src/socket.erl b/erts/preloaded/src/socket.erl
new file mode 100644
index 0000000000..f8e3d0419a
--- /dev/null
+++ b/erts/preloaded/src/socket.erl
@@ -0,0 +1,878 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2018-2018. All Rights Reserved.
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+-module(socket).
+
+%% Administrative and "global" utility functions
+-export([
+ on_load/0, on_load/1, on_load/2,
+ info/0
+ ]).
+
+-export([
+ open/2, open/3, open/4,
+ bind/2, bind/3,
+ connect/3,
+ listen/1, listen/2,
+ accept/1, accept/2,
+ accept4/1, accept4/2, accept4/3,
+
+ send/2, send/3, sendto/5,
+ recv/1, recv/2, recvfrom/1, recvfrom/2,
+
+ close/1,
+
+ setopt/3,
+ getopt/2,
+ formated_timestamp/0
+ ]).
+
+-export_type([
+ domain/0,
+ type/0,
+ protocol/0,
+ socket/0,
+
+ ip_address/0,
+ ip4_address/0,
+ ip6_address/0,
+ port_number/0,
+
+ accept_flags/0,
+ accept_flag/0,
+
+ send_flags/0,
+ send_flag/0
+ ]).
+
+
+%% We support only a subset of all domains.
+-type domain() :: local | inet | inet6.
+
+%% We support only a subset of all types.
+-type type() :: stream | dgram | raw | seqpacket.
+
+%% We support only a subset of all protocols:
+-type protocol() :: ip | tcp | udp | sctp.
+
+-type ip_address() :: ip4_address() | ip6_address().
+
+-type ip4_address() :: {0..255, 0..255, 0..255, 0..255}.
+
+-type ip6_address() ::
+ {0..65535,
+ 0..65535,
+ 0..65535,
+ 0..65535,
+ 0..65535,
+ 0..65535,
+ 0..65535,
+ 0..65535}.
+
+-type port_number() :: 0..65535.
+
+-type socket_info() :: map().
+%% -record(socket, {info :: socket_info,
+%% ref :: reference()}).
+-opaque socket() :: {socket, socket_info(), reference()}.
+%% -opaque socket() :: #socket{}.
+
+-type accept_flags() :: [accept_flag()].
+-type accept_flag() :: nonblock | cloexec.
+
+-type send_flags() :: [send_flag()].
+-type send_flag() :: confirm |
+ dontroute |
+ dontwait |
+ eor |
+ more |
+ nosignal |
+ oob.
+
+-type recv_flags() :: [recv_flag()].
+-type recv_flag() :: cmsg_cloexec |
+ dontwait |
+ errqueue |
+ oob |
+ peek |
+ trunc |
+ waitall.
+
+-type setopt_key() :: foo.
+-type getopt_key() :: foo.
+
+-define(SOCKET_DOMAIN_LOCAL, 1).
+-define(SOCKET_DOMAIN_UNIX, ?SOCKET_DOMAIN_LOCAL).
+-define(SOCKET_DOMAIN_INET, 2).
+-define(SOCKET_DOMAIN_INET6, 3).
+
+-define(SOCKET_TYPE_STREAM, 1).
+-define(SOCKET_TYPE_DGRAM, 2).
+-define(SOCKET_TYPE_RAW, 3).
+%% -define(SOCKET_TYPE_RDM, 4).
+-define(SOCKET_TYPE_SEQPACKET, 5).
+
+-define(SOCKET_PROTOCOL_IP, 1).
+-define(SOCKET_PROTOCOL_TCP, 2).
+-define(SOCKET_PROTOCOL_UDP, 3).
+-define(SOCKET_PROTOCOL_SCTP, 4).
+
+-define(SOCKET_LISTEN_BACKLOG_DEFAULT, 5).
+
+%% Bit numbers (from right).
+-define(SOCKET_ACCEPT_FLAG_NONBLOCK, 0).
+-define(SOCKET_ACCEPT_FLAG_CLOEXEC, 1).
+
+-define(SOCKET_ACCEPT_FLAGS_DEFAULT, []).
+
+-define(SOCKET_SEND_FLAG_CONFIRM, 0).
+-define(SOCKET_SEND_FLAG_DONTROUTE, 1).
+-define(SOCKET_SEND_FLAG_DONTWAIT, 2).
+-define(SOCKET_SEND_FLAG_EOR, 3).
+-define(SOCKET_SEND_FLAG_MORE, 4).
+-define(SOCKET_SEND_FLAG_NOSIGNAL, 5).
+-define(SOCKET_SEND_FLAG_OOB, 6).
+
+-define(SOCKET_SEND_FLAGS_DEFAULT, []).
+
+-define(SOCKET_RECV_FLAG_CMSG_CLOEXEC, 0).
+-define(SOCKET_RECV_FLAG_DONTWAIT, 1).
+-define(SOCKET_RECV_FLAG_ERRQUEUE, 2).
+-define(SOCKET_RECV_FLAG_OOB, 3).
+-define(SOCKET_RECV_FLAG_PEEK, 4).
+-define(SOCKET_RECV_FLAG_TRUNC, 5).
+-define(SOCKET_RECV_FLAG_WAITALL, 6).
+
+-define(SOCKET_RECV_FLAGS_DEFAULT, []).
+
+-define(SOCKET_SETOPT_KEY_DEBUG, 0).
+
+-define(SOCKET_GETOPT_KEY_DEBUG, ?SOCKET_SETOPT_KEY_DEBUG).
+
+
+
+%% ===========================================================================
+%%
+%% Administrative and utility API
+%%
+%% ===========================================================================
+
+-spec on_load() -> ok.
+
+%% Should we require that the Extra arg is a map?
+on_load() ->
+ on_load(#{}).
+
+-spec on_load(Extra) -> ok when
+ Extra :: maps:map().
+
+on_load(Extra) when is_map(Extra) ->
+ on_load(atom_to_list(?MODULE), Extra).
+
+-spec on_load(Path, Extra) -> ok when
+ Path :: string(),
+ Extra :: maps:map().
+
+on_load(Path, Extra) when is_list(Path) andalso is_map(Extra) ->
+ on_load(nif_is_loaded(), Path, Extra).
+
+on_load(true, _Path, _Extra) ->
+ ok;
+on_load(false, Path, Extra) ->
+ ok = erlang:load_nif(Path, maps:put(timestamp, formated_timestamp(), Extra)).
+
+
+
+-spec info() -> list().
+
+info() ->
+ nif_info().
+
+
+
+%% ===========================================================================
+%%
+%% The proper socket API
+%%
+%% ===========================================================================
+
+%% open - create an endpoint for communication
+
+open(Domain, Type) ->
+ open(Domain, Type, null).
+
+-spec open(Domain, Type, Protocol) -> {ok, Socket} | {error, Reason} when
+ Domain :: domain(),
+ Type :: type(),
+ Protocol :: null | protocol(),
+ Socket :: socket(),
+ Reason :: term().
+
+open(Domain, Type, Protocol) ->
+ open(Domain, Type, Protocol, #{}).
+
+-spec open(Domain, Type, Protocol, Extra) -> {ok, Socket} | {error, Reason} when
+ Domain :: domain(),
+ Type :: type(),
+ Protocol :: null | protocol(),
+ Extra :: map(),
+ Socket :: socket(),
+ Reason :: term().
+
+open(Domain, Type, Protocol0, Extra) when is_map(Extra) ->
+ try
+ begin
+ Protocol = default_protocol(Protocol0, Type),
+ EDomain = enc_domain(Domain),
+ EType = enc_type(Domain, Type),
+ EProtocol = enc_protocol(Type, Protocol),
+ case nif_open(EDomain, EType, EProtocol, Extra) of
+ {ok, SockRef} ->
+ SocketInfo = #{domain => Domain,
+ type => Type,
+ protocol => Protocol},
+ Socket = {socket, SocketInfo, SockRef},
+ {ok, Socket};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end
+ catch
+ throw:T ->
+ T;
+ error:Reason ->
+ {error, Reason}
+ end.
+
+%% Note that this is just a convenience function for when the protocol was
+%% not specified. If its actually specied, then that will be selected.
+%% Also, this only works for the some of the type's (stream, dgram and
+%% seqpacket).
+default_protocol(null, stream) -> tcp;
+default_protocol(null, dgram) -> udp;
+default_protocol(null, seqpacket) -> sctp;
+default_protocol(null, Type) -> throw({error, {no_default_protocol, Type}});
+default_protocol(Protocol, _) -> Protocol.
+
+
+%% bind - bind a name to a socket
+
+-spec bind(Socket, FileOrAddr) -> ok | {error, Reason} when
+ Socket :: socket(),
+ FileOrAddr :: binary() | string() | ip_address() | any | loopback,
+ Reason :: term().
+
+bind(Socket, File) when is_binary(File) ->
+ if
+ byte_size(File) =:= 0 ->
+ {error, einval};
+ byte_size(File) =< 255 ->
+ nif_bind(Socket, File);
+ true ->
+ {error, einval}
+ end;
+bind(Socket, File) when is_list(File) andalso (File =/= []) ->
+ Bin = unicode:characters_to_binary(File, file:native_name_encoding()),
+ if
+ byte_size(Bin) =< 255 ->
+ nif_bind(Socket, Bin);
+ true ->
+ {error, einval}
+ end;
+bind(Socket, Addr) when is_tuple(Addr) orelse
+ (Addr =:= any) orelse
+ (Addr =:= loopback) ->
+ bind(Socket, Addr, 0).
+
+
+-spec bind(Socket, Address, Port) -> ok | {ok, NewPort} | {error, Reason} when
+ Socket :: socket(),
+ Address :: ip_address() | any | loopback,
+ Port :: port_number(),
+ NewPort :: port_number(),
+ Reason :: term().
+
+%% Shall we keep info about domain so that we can verify address?
+bind({socket, _, SockRef}, Addr, Port)
+ when (is_tuple(Addr) andalso
+ ((size(Addr) =:= 4) orelse (size(Addr) =:= 8))) orelse
+ ((Addr =:= any) orelse (Addr =:= loopback)) andalso
+ (is_integer(Port) andalso (Port >= 0)) ->
+ nif_bind(SockRef, {Addr, Port}).
+
+
+%% connect - initiate a connection on a socket
+
+-spec connect(Socket, Addr, Port) -> ok | {error, Reason} when
+ Socket :: socket(),
+ Addr :: ip_address(),
+ Port :: port_number(),
+ Reason :: term().
+
+connect(Socket, Addr, Port) ->
+ connect(Socket, Addr, Port, infinity).
+
+-spec connect(Socket, Addr, Port, Timeout) -> ok | {error, Reason} when
+ Socket :: socket(),
+ Addr :: ip_address(),
+ Port :: port_number(),
+ Timeout :: timeout(),
+ Reason :: term().
+
+connect(_Socket, _Addr, _Port, Timeout)
+ when (is_integer(Timeout) andalso (Timeout =< 0)) ->
+ {error, timeout};
+connect({socket, _, SockRef}, Addr, Port, Timeout)
+ when (is_tuple(Addr) andalso
+ ((size(Addr) =:= 4) orelse (size(Addr) =:= 8))) andalso
+ (is_integer(Port) andalso (Port >= 0)) andalso
+ ((Timeout =:= infinity) orelse is_integer(Timeout)) ->
+ TS = timestamp(Timeout),
+ case nif_connect(SockRef, Addr, Port) of
+ ok ->
+ %% Connected!
+ ok;
+ {ok, Ref} ->
+ %% Connecting...
+ NewTimeout = next_timeout(TS, Timeout),
+ receive
+ {select, SockRef, Ref, ready_output} ->
+ nif_finalize_connection(SockRef)
+ after NewTimeout ->
+ nif_cancel(SockRef, Ref),
+ {error, timeout}
+ end;
+ {error, _} = ERROR ->
+ ERROR
+ end.
+
+
+%% listen - listen for connections on a socket
+
+-spec listen(Socket, Backlog) -> ok | {error, Reason} when
+ Socket :: socket(),
+ Backlog :: pos_integer(),
+ Reason :: term().
+
+listen(Socket) ->
+ listen(Socket, ?SOCKET_LISTEN_BACKLOG_DEFAULT).
+
+listen({socket, _, SockRef}, Backlog)
+ when (is_integer(Backlog) andalso (Backlog >= 0)) orelse is_boolean(Backlog) ->
+ EBacklog = enc_backlog(Backlog),
+ nif_listen(SockRef, EBacklog).
+
+
+
+%% accept, accept4 - accept a connection on a socket
+
+-spec accept(LSocket, Timeout) -> {ok, Socket} | {error, Reason} when
+ LSocket :: socket(),
+ Timeout :: timeout(),
+ Socket :: socket(),
+ Reason :: term().
+
+accept(Socket) ->
+ accept(Socket, infinity).
+
+%% Do we really need this optimization?
+accept(_, Timeout) when is_integer(Timeout) andalso (Timeout < 0) ->
+ {error, timeout};
+accept({socket, _, LSockRef}, Timeout)
+ when is_integer(Timeout) orelse (Timeout =:= infinity) ->
+ Ref = make_ref(),
+ do_accept(LSockRef, Ref, Timeout).
+
+do_accept(_, _Ref, Timeout) when is_integer(Timeout) andalso (Timeout < 0) ->
+ {error, timeout};
+do_accept(LSockRef, Ref, Timeout) ->
+ TS = timestamp(Timeout),
+ case nif_accept(LSockRef, Ref) of
+ {ok, SockRef} ->
+ Socket = {socket, foo, SockRef},
+ {ok, Socket};
+ {error, eagain} ->
+ receive
+ {select, LSockRef, Ref, ready_input} ->
+ do_accept(LSockRef, make_ref(), next_timeout(TS, Timeout))
+ after Timeout ->
+ %% Shall we cancel the select? How?
+ nif_cancel(LSockRef, Ref),
+ flush_select_msgs(LSockRef, Ref),
+ {error, timeout}
+ end
+ end.
+
+flush_select_msgs(LSRef, Ref) ->
+ receive
+ {select, LSRef, Ref, _} ->
+ flush_select_msgs(LSRef, Ref)
+ after 0 ->
+ ok
+ end.
+
+
+-spec accept4(LSocket, Flags, Timeout) -> {ok, Socket} | {error, Reason} when
+ LSocket :: socket(),
+ Flags :: accept_flags(),
+ Timeout :: timeout(),
+ Socket :: socket(),
+ Reason :: term().
+
+accept4(LSocket) ->
+ accept4(LSocket, ?SOCKET_ACCEPT_FLAGS_DEFAULT).
+
+accept4(LSocket, Flags) when is_list(Flags) ->
+ accept4(LSocket, Flags, infinity);
+accept4(LSocket, Timeout) ->
+ accept4(LSocket, ?SOCKET_ACCEPT_FLAGS_DEFAULT, Timeout).
+
+%% Do we really need this optimization?
+accept4(_LSocket, _Flags, Timeout) when is_integer(Timeout) andalso (Timeout < 0) ->
+ {error, timeout};
+accept4({socket, _, LSockRef}, Flags, Timeout)
+ when is_list(Flags) andalso
+ (is_integer(Timeout) orelse (Timeout =:= infinity)) ->
+ EFlags = enc_accept_flags(Flags),
+ Ref = make_ref(),
+ do_accept4(LSockRef, EFlags, Ref, Timeout).
+
+do_accept4(_, _EFlags, _Ref, Timeout)
+ when is_integer(Timeout) andalso (Timeout < 0) ->
+ {error, timeout};
+do_accept4(LSockRef, EFlags, Ref, Timeout) ->
+ TS = timestamp(Timeout),
+ case nif_accept4(LSockRef, EFlags, Ref) of
+ {ok, SockRef} ->
+ Socket = {socket, foo, SockRef},
+ {ok, Socket};
+ {error, eagain} ->
+ receive
+ {select, LSockRef, Ref, ready_input} ->
+ do_accept4(LSockRef, EFlags, make_ref(),
+ next_timeout(TS, Timeout))
+ after Timeout ->
+ %% Shall we cancel the select? How?
+ nif_cancel(LSockRef, Ref),
+ flush_select_msgs(LSockRef, Ref),
+ {error, timeout}
+ end
+ end.
+
+
+
+%% send, sendto, sendmsg - send a message on a socket
+
+-spec send(Socket, Data, Flags) -> ok | {error, Reason} when
+ Socket :: socket(),
+ Data :: binary(),
+ Flags :: send_flags(),
+ Reason :: term().
+
+send(Socket, Data) ->
+ send(Socket, Data, ?SOCKET_SEND_FLAGS_DEFAULT).
+
+send({socket, _, SockRef}, Data, Flags)
+ when is_binary(Data) andalso is_list(Flags) ->
+ EFlags = enc_send_flags(Flags),
+ nif_send(SockRef, Data, EFlags).
+
+
+-spec sendto(Socket, Data, Flags, DestAddr, Port) -> ok | {error, Reason} when
+ Socket :: socket(),
+ Data :: binary(),
+ Flags :: send_flags(),
+ DestAddr :: ip_address(),
+ Port :: port_number(),
+ Reason :: term().
+
+sendto({socket, _, SockRef}, Data, Flags, DestAddr, DestPort)
+ when is_binary(Data) andalso
+ is_list(Flags) andalso
+ (is_tuple(DestAddr) andalso
+ ((size(DestAddr) =:= 4) orelse
+ (size(DestAddr) =:= 8))) andalso
+ (is_integer(DestPort) andalso (DestPort >= 0)) ->
+ EFlags = enc_send_flags(Flags),
+ nif_sendto(SockRef, Data, EFlags, DestAddr, DestPort).
+
+
+%% -spec sendmsg(Socket, MsgHdr, Flags) -> ok | {error, Reason} when
+%% Socket :: socket(),
+%% MsgHdr :: msg_header(),
+%% Flags :: send_flags(),
+%% Reason :: term().
+
+
+
+%% recv, recvfrom, recvmsg - receive a message from a socket
+
+-spec recv(Socket, Flags) -> {ok, Data} | {error, Reason} when
+ Socket :: socket(),
+ Flags :: recv_flags(),
+ Data :: binary(),
+ Reason :: term().
+
+recv(Socket) ->
+ recv(Socket, ?SOCKET_RECV_FLAGS_DEFAULT).
+
+%% WE "may" need a timeout option here...
+recv({socket, _, SockRef}, Flags) when is_list(Flags) ->
+ EFlags = enc_recv_flags(Flags),
+ nif_recv(SockRef, EFlags).
+
+
+-spec recvfrom(Socket, Flags) -> {ok, Data, SrcAddr, SrcPort} | {error, Reason} when
+ Socket :: socket(),
+ Flags :: recv_flags(),
+ Data :: binary(),
+ SrcAddr :: ip_address(),
+ SrcPort :: port_number(),
+ Reason :: term().
+
+recvfrom(Socket) ->
+ recvfrom(Socket, ?SOCKET_RECV_FLAGS_DEFAULT).
+
+recvfrom({socket, _, SockRef}, Flags) when is_list(Flags) ->
+ EFlags = enc_recv_flags(Flags),
+ nif_recvfrom(SockRef, EFlags).
+
+%% -spec recvmsg(Socket, [out] MsgHdr, Flags) -> {ok, Data} | {error, Reason} when
+%% Socket :: socket(),
+%% MsgHdr :: msg_header(),
+%% Flags :: recv_flags(),
+%% Data :: binary(),
+%% Reason :: term().
+
+
+
+%% close - close a file descriptor
+
+-spec close(Socket) -> ok | {error, Reason} when
+ Socket :: socket(),
+ Reason :: term().
+
+close({socket, _, SockRef}) ->
+ nif_close(SockRef).
+
+
+
+%% setopt - manipulate individual properties of a socket
+%%
+%% What properties are valid depend on what kind of socket it is
+%% (domain, type and protocol)
+%% If its an "invalid" option (or value), we should not crash but return some
+%% useful error...
+%%
+
+-spec setopt(Socket, Key, Value) -> ok | {error, Reason} when
+ Socket :: socket(),
+ Key :: setopt_key(),
+ Value :: term(),
+ Reason :: term().
+
+setopt({socket, Info, SockRef}, Key, Value) ->
+ try
+ begin
+ Domain = maps:get(domain, Info),
+ Type = maps:get(type, Info),
+ Protocol = maps:get(protocol, Info),
+ EKey = enc_setopt_key(Key, Domain, Type, Protocol),
+ EVal = enc_setopt_value(Key, Value, Domain, Type, Protocol),
+ nif_setopt(SockRef, EKey, EVal)
+ end
+ catch
+ throw:T ->
+ T;
+ error:Reason ->
+ {error, Reason} % Process more?
+ end.
+
+
+%% getopt - retrieve individual properties of a socket
+%%
+%% What properties are valid depend on what kind of socket it is
+%% (domain, type and protocol).
+%% If its an "invalid" option, we should not crash but return some
+%% useful error...
+%%
+
+-spec getopt(Socket, Key) -> {ok, Value} | {error, Reason} when
+ Socket :: socket(),
+ Key :: getopt_key(),
+ Value :: term(),
+ Reason :: term().
+
+getopt({socket, Info, SockRef}, Key) ->
+ try
+ begin
+ Domain = maps:get(domain, Info),
+ Type = maps:get(type, Info),
+ Protocol = maps:get(protocol, Info),
+ EKey = enc_getopt_key(Key, Domain, Type, Protocol),
+ %% We may need to decode the value (for the same reason
+ %% we needed to encode the value for setopt).
+ case nif_getopt(SockRef, EKey) of
+ {ok, EVal} ->
+ Val = dec_getopt_value(Key, EVal, Domain, Type, Protocol),
+ {ok, Val};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end
+ catch
+ throw:T ->
+ T;
+ error:Reason ->
+ {error, Reason} % Process more?
+ end.
+
+
+
+%% ===========================================================================
+%%
+%% Encode / decode
+%%
+%% ===========================================================================
+
+-spec enc_domain(Domain) -> non_neg_integer() when
+ Domain :: domain().
+
+enc_domain(local) -> ?SOCKET_DOMAIN_LOCAL;
+enc_domain(inet) -> ?SOCKET_DOMAIN_INET;
+enc_domain(inet6) -> ?SOCKET_DOMAIN_INET6;
+enc_domain(Domain) -> throw({error, {invalid_domain, Domain}}).
+
+-spec enc_type(Domain, Type) -> non_neg_integer() when
+ Domain :: domain(),
+ Type :: type().
+
+%% What combos are valid?
+enc_type(_, stream) -> ?SOCKET_TYPE_STREAM;
+enc_type(_, dgram) -> ?SOCKET_TYPE_DGRAM;
+enc_type(_, raw) -> ?SOCKET_TYPE_RAW;
+enc_type(_, seqpacket) -> ?SOCKET_TYPE_SEQPACKET;
+enc_type(_, Type) -> throw({error, {invalid_type, Type}}).
+
+-spec enc_protocol(Type, Protocol) -> non_neg_integer() when
+ Type :: type(),
+ Protocol :: protocol().
+
+enc_protocol(dgram, ip) -> ?SOCKET_PROTOCOL_IP;
+enc_protocol(stream, tcp) -> ?SOCKET_PROTOCOL_TCP;
+enc_protocol(dgram, udp) -> ?SOCKET_PROTOCOL_UDP;
+enc_protocol(seqpacket, sctp) -> ?SOCKET_PROTOCOL_SCTP;
+enc_protocol(Type, Proto) -> throw({error, {invalid_protocol, {Type, Proto}}}).
+
+
+-spec enc_backlog(Backlog) -> non_neg_integer() when
+ Backlog :: non_neg_integer() | boolean().
+
+enc_backlog(true) ->
+ ?SOCKET_LISTEN_BACKLOG_DEFAULT;
+enc_backlog(false) ->
+ 0;
+enc_backlog(Backlog) when is_integer(Backlog) andalso (Backlog >= 0) ->
+ Backlog.
+
+-spec enc_accept_flags(Flags) -> non_neg_integer() when
+ Flags :: accept_flags().
+
+enc_accept_flags(Flags) ->
+ EFlags = [{nonblock, ?SOCKET_ACCEPT_FLAG_NONBLOCK},
+ {cloexec, ?SOCKET_ACCEPT_FLAG_CLOEXEC}],
+ enc_flags(Flags, EFlags).
+
+
+-spec enc_send_flags(Flags) -> non_neg_integer() when
+ Flags :: send_flags().
+
+enc_send_flags(Flags) ->
+ EFlags = [{confirm, ?SOCKET_SEND_FLAG_CONFIRM},
+ {dontroute, ?SOCKET_SEND_FLAG_DONTROUTE},
+ {dontwait, ?SOCKET_SEND_FLAG_DONTWAIT},
+ {eor, ?SOCKET_SEND_FLAG_EOR},
+ {more, ?SOCKET_SEND_FLAG_MORE},
+ {nosignal, ?SOCKET_SEND_FLAG_NOSIGNAL},
+ {oob, ?SOCKET_SEND_FLAG_OOB}],
+ enc_flags(Flags, EFlags).
+
+-spec enc_recv_flags(Flags) -> non_neg_integer() when
+ Flags :: recv_flags().
+
+enc_recv_flags(Flags) ->
+ EFlags = [{cmsg_cloexec, ?SOCKET_RECV_FLAG_CMSG_CLOEXEC},
+ {dontwait, ?SOCKET_RECV_FLAG_DONTWAIT},
+ {errqueue, ?SOCKET_RECV_FLAG_ERRQUEUE},
+ {oob, ?SOCKET_RECV_FLAG_OOB},
+ {peek, ?SOCKET_RECV_FLAG_PEEK},
+ {trunc, ?SOCKET_RECV_FLAG_TRUNC},
+ {waitall, ?SOCKET_RECV_FLAG_WAITALL}],
+ enc_flags(Flags, EFlags).
+
+
+enc_flags([], _) ->
+ 0;
+enc_flags(Flags, EFlags) ->
+ F = fun(Flag, Acc) ->
+ case lists:keysearch(Flag, 1, EFlags) of
+ {value, {Flag, EFlag}} ->
+ Acc bor (1 bsl EFlag);
+ false ->
+ throw({error, {unknown_flag, Flag}})
+ end
+ end,
+ lists:foldl(F, 0, Flags).
+
+%% We should ...really... do something with the domain, type and protocol args...
+enc_setopt_key(debug, _, _, _) ->
+ ?SOCKET_SETOPT_KEY_DEBUG.
+
+%% We should ...really... do something with the domain, type and protocol args...
+enc_setopt_value(debug, V, _, _, _) when is_boolean(V) ->
+ V.
+
+
+%% We should ...really... do something with the domain, type and protocol args...
+enc_getopt_key(debug, _, _, _) ->
+ ?SOCKET_GETOPT_KEY_DEBUG.
+
+%% We should ...really... do something with the domain, type and protocol args...
+dec_getopt_value(debug, B, _, _, _) when is_boolean(B) ->
+ B.
+
+
+
+%% ===========================================================================
+%%
+%% Misc utility functions
+%%
+%% ===========================================================================
+
+formated_timestamp() ->
+ format_timestamp(os:timestamp()).
+
+format_timestamp(Now) ->
+ N2T = fun(N) -> calendar:now_to_local_time(N) end,
+ format_timestamp(Now, N2T, true).
+
+format_timestamp({_N1, _N2, N3} = N, N2T, true) ->
+ FormatExtra = ".~.2.0w",
+ ArgsExtra = [N3 div 10000],
+ format_timestamp(N, N2T, FormatExtra, ArgsExtra);
+format_timestamp({_N1, _N2, _N3} = N, N2T, false) ->
+ FormatExtra = "",
+ ArgsExtra = [],
+ format_timestamp(N, N2T, FormatExtra, ArgsExtra).
+
+format_timestamp(N, N2T, FormatExtra, ArgsExtra) ->
+ {Date, Time} = N2T(N),
+ {YYYY,MM,DD} = Date,
+ {Hour,Min,Sec} = Time,
+ FormatDate =
+ io_lib:format("~.4w-~.2.0w-~.2.0w ~.2.0w:~.2.0w:~.2.0w" ++ FormatExtra,
+ [YYYY, MM, DD, Hour, Min, Sec] ++ ArgsExtra),
+ lists:flatten(FormatDate).
+
+
+%% A timestamp in ms
+
+timestamp(infinity) ->
+ undefined;
+timestamp(_) ->
+ timestamp().
+
+timestamp() ->
+ {A,B,C} = os:timestamp(),
+ A*1000000000+B*1000+(C div 1000).
+
+next_timeout(infinity = Timeout, _) ->
+ Timeout;
+next_timeout(Timeout, TS) ->
+ NewTimeout = Timeout - tdiff(TS, timestamp()),
+ if
+ (NewTimeout > 0) ->
+ NewTimeout;
+ true ->
+ 0
+ end.
+
+tdiff(T1, T2) ->
+ T2 - T1.
+
+
+
+
+
+%% ===========================================================================
+%%
+%% Below follows the actual NIF-functions.
+%%
+%% ===========================================================================
+
+nif_is_loaded() -> false.
+
+nif_info() ->
+ erlang:error(badarg).
+
+nif_open(_Domain, _Type, _Protocol, _Extra) ->
+ erlang:error(badarg).
+
+nif_bind(_SRef, _LocalAddr) ->
+ erlang:error(badarg).
+
+nif_connect(_SRef, _Addr, _Port) ->
+ erlang:error(badarg).
+
+nif_finalize_connection(_SRef) ->
+ erlang:error(badarg).
+
+nif_listen(_SRef, _Backlog) ->
+ erlang:error(badarg).
+
+nif_accept(_SRef, _Ref) ->
+ erlang:error(badarg).
+
+nif_accept4(_SRef, _Flags, _Ref) ->
+ erlang:error(badarg).
+
+nif_send(_SRef, _Data, _Flags) ->
+ erlang:error(badarg).
+
+nif_sendto(_SRef, _Data, _Flags, _Dest, _Port) ->
+ erlang:error(badarg).
+
+nif_recv(_SRef, _Flags) ->
+ erlang:error(badarg).
+
+nif_recvfrom(_SRef, _Flags) ->
+ erlang:error(badarg).
+
+nif_cancel(_SRef, _Ref) ->
+ erlang:error(badarg).
+
+nif_close(_SRef) ->
+ erlang:error(badarg).
+
+nif_setopt(_Ref, _Key, _Val) ->
+ erlang:error(badarg).
+
+nif_getopt(_Ref, _Key) ->
+ erlang:error(badarg).