/*
* %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>
#include <netinet/ip.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 <netinet/udp.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)
/* Two byte integer decoding */
#define get_int16(s) ((((unsigned char*) (s))[0] << 8) | \
(((unsigned char*) (s))[1]))
#define SASSERT(e) \
((void) ((e) ? 1 : (xabort(#e, __func__, __FILE__, __LINE__), 0)))
/* Debug stuff... */
#define SOCKET_NIF_DEBUG_DEFAULT TRUE
#define SOCKET_DEBUG_DEFAULT TRUE
/* Counters and stuff (Don't know where to sent this stuff anyway) */
#define SOCKET_NIF_IOW_DEFAULT FALSE
/* Used in debug printouts */
#ifdef __WIN32__
#define LLU "%I64u"
#else
#define LLU "%llu"
#endif
typedef unsigned long long llu_t;
/* Socket stuff */
#define INVALID_SOCKET -1
#define INVALID_EVENT -1
#define SOCKET_ERROR -1
#define SOCKET int
#define HANDLE long int
/* ==============================================================================
* 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 *** */
#if defined(TCP_CA_NAME_MAX)
#define SOCKET_OPT_TCP_CONGESTION_NAME_MAX TCP_CA_NAME_MAX
#else
/* This is really excessive, but just in case... */
#define SOCKET_OPT_TCP_CONGESTION_NAME_MAX 256
#endif
/* *** Socket state defs *** */
#define SOCKET_FLAG_OPEN 0x0001
#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_CLOSE 0x0080
#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_CLOSING (SOCKET_FLAG_CLOSE)
#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)
#define SOCKET_SEND_FLAG_CONFIRM 0
#define SOCKET_SEND_FLAG_DONTROUTE 1
#define SOCKET_SEND_FLAG_EOR 2
#define SOCKET_SEND_FLAG_MORE 3
#define SOCKET_SEND_FLAG_NOSIGNAL 4
#define SOCKET_SEND_FLAG_OOB 5
#define SOCKET_SEND_FLAG_LOW SOCKET_SEND_FLAG_CONFIRM
#define SOCKET_SEND_FLAG_HIGH SOCKET_SEND_FLAG_OOB
#define SOCKET_RECV_FLAG_CMSG_CLOEXEC 0
#define SOCKET_RECV_FLAG_ERRQUEUE 1
#define SOCKET_RECV_FLAG_OOB 2
#define SOCKET_RECV_FLAG_PEEK 3
#define SOCKET_RECV_FLAG_TRUNC 4
#define SOCKET_RECV_FLAG_LOW SOCKET_RECV_FLAG_CMSG_CLOEXEC
#define SOCKET_RECV_FLAG_HIGH SOCKET_RECV_FLAG_TRUNC
#define SOCKET_RECV_BUFFER_SIZE_DEFAULT 2048
typedef union {
struct {
unsigned int open:1;
// 0 = not conn, 1 = connecting, 2 = connected
unsigned int connect:2;
// unsigned int connecting:1;
// unsigned int connected:1;
// 0 = not listen, 1 = listening, 2 = accepting
unsigned int listen:2;
// unsigned int listening:1;
// unsigned int accepting:1;
/* Room for more... */
} flags;
unsigned int field; // Make it easy to reset all flags...
} SocketState;
/*
#define IS_OPEN(d) ((d)->state.flags.open)
#define IS_CONNECTED(d) ((d)->state.flags.connect == SOCKET_STATE_CONNECTED)
#define IS_CONNECTING(d) ((d)->state.flags.connect == SOCKET_STATE_CONNECTING)
*/
/*----------------------------------------------------------------------------
* Interface constants.
*
* 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
/* shutdown how */
#define SOCKET_SHUTDOWN_HOW_RD 0
#define SOCKET_SHUTDOWN_HOW_WR 1
#define SOCKET_SHUTDOWN_HOW_RDWR 2
#define SOCKET_OPT_LEVEL_OTP 0
#define SOCKET_OPT_LEVEL_SOCKET 1
#define SOCKET_OPT_LEVEL_IP 2
#define SOCKET_OPT_LEVEL_IPV6 3
#define SOCKET_OPT_LEVEL_TCP 4
#define SOCKET_OPT_LEVEL_UDP 5
#define SOCKET_OPT_LEVEL_SCTP 6
#define SOCKET_OPT_OTP_DEBUG 0
#define SOCKET_OPT_OTP_IOW 1
#define SOCKET_OPT_SOCK_KEEPALIVE 9
#define SOCKET_OPT_SOCK_LINGER 10
#define SOCKET_OPT_SOCK_REUSEADDR 21
#define SOCKET_OPT_IP_RECVTOS 0
#define SOCKET_OPT_IP_ROUTER_ALERT 1
#define SOCKET_OPT_IP_TOS 2
#define SOCKET_OPT_IP_TTL 3
#define SOCKET_OPT_IPV6_HOPLIMIT 12
#define SOCKET_OPT_TCP_CONGESTION 0
#define SOCKET_OPT_TCP_MAXSEG 1
#define SOCKET_OPT_TCP_NODELAY 2
#define SOCKET_OPT_UDP_CORK 0
#define SOCKET_OPT_SCTP_AUTOCLOSE 7
#define SOCKET_OPT_SCTP_NODELAY 22
/* =================================================================== *
* *
* Various enif macros *
* *
* =================================================================== */
#define MALLOC(SZ) enif_alloc((SZ))
#define FREE(P) enif_free((P))
#define MKA(E,S) enif_make_atom((E), (S))
#define MKBIN(E,B) enif_make_binary((E), (B))
#define MKI(E,I) enif_make_int((E), (I))
#define MKLA(E,A,L) enif_make_list_from_array((E), (A), (L))
#define MKREF(E) enif_make_ref((E))
#define MKS(E,S) enif_make_string((E), (S), ERL_NIF_LATIN1)
#define MKSL(E,S,L) enif_make_string_len((E), (S), (L), ERL_NIF_LATIN1)
#define MKSBIN(E,B,ST,SZ) enif_make_sub_binary((E), (B), (ST), (SZ))
#define MKT2(E,E1,E2) enif_make_tuple2((E), (E1), (E2))
#define MKT3(E,E1,E2,E3) enif_make_tuple3((E), (E1), (E2), (E3))
#define MKT4(E,E1,E2,E3,E4) enif_make_tuple4((E), (E1), (E2), (E3), (E4))
#define MKT8(E,E1,E2,E3,E4,E5,E6,E7,E8) \
enif_make_tuple8((E), (E1), (E2), (E3), (E4), (E5), (E6), (E7), (E8))
#define MCREATE(N) enif_mutex_create((N))
#define MDESTROY(M) enif_mutex_destroy((M))
#define MLOCK(M) enif_mutex_lock((M))
#define MUNLOCK(M) enif_mutex_unlock((M))
#define MONP(E,D,P,M) enif_monitor_process((E), (D), (P), (M))
#define DEMONP(E,D,M) enif_demonitor_process((E), (D), (M))
#define SELECT(E,FD,M,O,P,R) \
if (enif_select((E), (FD), (M), (O), (P), (R)) < 0) \
return enif_make_badarg((E));
#define 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_STR(E, L, B, SZ) \
enif_get_string((E), (L), (B), (SZ), ERL_NIF_LATIN1)
#define GET_UINT(E, TE, IP) enif_get_uint((E), (TE), (IP))
#define GET_TUPLE(E, TE, TSZ, TA) enif_get_tuple((E), (TE), (TSZ), (TA))
#define ALLOC_BIN(SZ, BP) enif_alloc_binary((SZ), (BP))
/* =================================================================== *
* *
* Basic socket operations *
* *
* =================================================================== */
#ifdef __WIN32__
/* *** Windows macros *** */
#define sock_accept(s, addr, len) \
make_noninheritable_handle(accept((s), (addr), (len)))
#define sock_bind(s, addr, len) bind((s), (addr), (len))
#define sock_close(s) closesocket((s))
#define sock_close_event(e) WSACloseEvent(e)
#define sock_connect(s, addr, len) connect((s), (addr), (len))
#define sock_create_event(s) WSACreateEvent()
#define sock_errno() WSAGetLastError()
#define sock_getopt(s,l,o,v,ln) getsockopt((s),(l),(o),(v),(ln))
#define sock_htons(x) htons((x))
#define sock_htonl(x) htonl((x))
#define sock_listen(s, b) listen((s), (b))
#define sock_name(s, addr, len) getsockname((s), (addr), (len))
#define sock_ntohs(x) ntohs((x))
#define sock_open(domain, type, proto) \
make_noninheritable_handle(socket((domain), (type), (proto)))
#define sock_recv(s,buf,len,flag) recv((s),(buf),(len),(flag))
#define sock_recvfrom(s,buf,blen,flag,addr,alen) \
recvfrom((s),(buf),(blen),(flag),(addr),(alen))
#define sock_send(s,buf,len,flag) send((s),(buf),(len),(flag))
#define sock_sendto(s,buf,blen,flag,addr,alen) \
sendto((s),(buf),(blen),(flag),(addr),(alen))
#define sock_setopt(s,l,o,v,ln) setsockopt((s),(l),(o),(v),(ln))
#define sock_shutdown(s, how) shutdown((s), (how))
#define SET_BLOCKING(s) ioctlsocket(s, FIONBIO, &zero_value)
#define SET_NONBLOCKING(s) ioctlsocket(s, FIONBIO, &one_value)
static unsigned long zero_value = 0;
static unsigned long one_value = 1;
#else /* !__WIN32__ */
#ifdef HAS_ACCEPT4
// We have to figure out what the flags are...
#define sock_accept(s, addr, len) accept4((s), (addr), (len), (SOCK_CLOEXEC))
#else
#define sock_accept(s, addr, len) accept((s), (addr), (len))
#endif
#define sock_bind(s, addr, len) bind((s), (addr), (len))
#define sock_close(s) close((s))
#define sock_close_event(e) /* do nothing */
#define sock_connect(s, addr, len) connect((s), (addr), (len))
#define sock_create_event(s) (s) /* return file descriptor */
#define sock_errno() errno
#define sock_getopt(s,t,n,v,l) getsockopt((s),(t),(n),(v),(l))
#define sock_htons(x) htons((x))
#define sock_htonl(x) htonl((x))
#define sock_listen(s, b) listen((s), (b))
#define sock_name(s, addr, len) getsockname((s), (addr), (len))
#define sock_ntohs(x) ntohs((x))
#define sock_open(domain, type, proto) socket((domain), (type), (proto))
#define sock_recv(s,buf,len,flag) recv((s),(buf),(len),(flag))
#define sock_recvfrom(s,buf,blen,flag,addr,alen) \
recvfrom((s),(buf),(blen),(flag),(addr),(alen))
#define sock_send(s,buf,len,flag) send((s), (buf), (len), (flag))
#define sock_sendto(s,buf,blen,flag,addr,alen) \
sendto((s),(buf),(blen),(flag),(addr),(alen))
#define sock_setopt(s,l,o,v,ln) setsockopt((s),(l),(o),(v),(ln))
#define sock_shutdown(s, how) shutdown((s), (how))
#endif /* !__WIN32__ */
#ifdef HAVE_SOCKLEN_T
# define SOCKLEN_T socklen_t
#else
# define SOCKLEN_T size_t
#endif
/* The general purpose sockaddr */
typedef 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
} SocketAddress;
#define which_address_port(sap) \
((((sap)->sai.sin_family == AF_INET) || \
((sap)->sai.sin_family == AF_INET6)) ? \
((sap)->sai.sin_port) : -1)
typedef struct {
ErlNifPid pid; // PID of the requesting process
ErlNifMonitor mon; // Monitor to the requesting process
ERL_NIF_TERM ref; // The (unique) reference (ID) of the request
} SocketRequestor;
typedef struct socket_request_queue_element {
struct socket_request_queue_element* nextP;
SocketRequestor data;
} SocketRequestQueueElement;
typedef struct {
SocketRequestQueueElement* first;
SocketRequestQueueElement* last;
} SocketRequestQueue;
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 stuff +++ */
ErlNifMutex* writeMtx;
SocketRequestor currentWriter;
SocketRequestor* currentWriterP; // NULL or points to currentWriter
SocketRequestQueue writersQ;
BOOLEAN_T isWritable;
uint32_t writePkgCnt;
uint32_t writeByteCnt;
uint32_t writeTries;
uint32_t writeWaits;
uint32_t writeFails;
/* +++ Read stuff +++ */
ErlNifMutex* readMtx;
SocketRequestor currentReader;
SocketRequestor* currentReaderP; // NULL or points to currentReader
SocketRequestQueue readersQ;
BOOLEAN_T isReadable;
ErlNifBinary rbuffer; // DO WE NEED THIS
uint32_t readCapacity; // DO WE NEED THIS
uint32_t readPkgCnt;
uint32_t readByteCnt;
uint32_t readTries;
uint32_t readWaits;
/* +++ Accept stuff +++ */
ErlNifMutex* accMtx;
SocketRequestor currentAcceptor;
SocketRequestor* currentAcceptorP; // NULL or points to currentReader
SocketRequestQueue acceptorsQ;
/* +++ Config & Misc stuff +++ */
size_t rBufSz; // Read buffer size (when data length = 0 is specified)
BOOLEAN_T iow; // Inform On Wrap
BOOLEAN_T dbg;
/* +++ Close stuff +++ */
ErlNifMutex* closeMtx;
ErlNifPid closerPid;
ErlNifMonitor closerMon;
ERL_NIF_TERM closeRef;
BOOLEAN_T closeLocal;
} SocketDescriptor;
#define SOCKET_OPT_VALUE_UNDEF 0
#define SOCKET_OPT_VALUE_BOOL 1
#define SOCKET_OPT_VALUE_INT 2
#define SOCKET_OPT_VALUE_LINGER 3
#define SOCKET_OPT_VALUE_BIN 4
#define SOCKET_OPT_VALUE_STR 5
typedef struct {
unsigned int tag;
union {
BOOLEAN_T boolVal;
int intVal;
struct linger lingerVal;
ErlNifBinary binVal;
struct {
unsigned int len;
char* str;
} strVal;
} u;
/*
void* optValP; // Points to the actual data (above)
socklen_t optValLen; // The size of the option value
*/
} SocketOptValue;
/* Global stuff (do we really need to "collect"
* these things?)
*/
typedef struct {
/* These are for debugging, testing and the like */
ERL_NIF_TERM version;
ERL_NIF_TERM buildDate;
BOOLEAN_T dbg;
ErlNifMutex* cntMtx;
BOOLEAN_T iow;
uint32_t numSockets;
uint32_t numTypeDGrams;
uint32_t numTypeStreams;
uint32_t numTypeSeqPkg;
uint32_t numDomainLocal;
uint32_t numDomainInet;
uint32_t numDomainInet6;
uint32_t numProtoIP;
uint32_t numProtoTCP;
uint32_t numProtoUDP;
uint32_t numProtoSCTP;
} SocketData;
/* ----------------------------------------------------------------------
* F o r w a r d s
* ----------------------------------------------------------------------
*/
/* THIS IS JUST TEMPORARY */
extern char* erl_errno_id(int error);
static ERL_NIF_TERM nif_is_loaded(ErlNifEnv* env,
int argc,
const ERL_NIF_TERM argv[]);
static ERL_NIF_TERM nif_info(ErlNifEnv* env,
int argc,
const ERL_NIF_TERM argv[]);
/*
This is a *global* debug function (enable or disable for all
operations and all sockets.
static ERL_NIF_TERM nif_debug(ErlNifEnv* env,
int argc,
const ERL_NIF_TERM argv[]);
*/
static ERL_NIF_TERM nif_open(ErlNifEnv* env,
int argc,
const ERL_NIF_TERM argv[]);
static ERL_NIF_TERM nif_bind(ErlNifEnv* env,
int argc,
const ERL_NIF_TERM argv[]);
static ERL_NIF_TERM nif_connect(ErlNifEnv* env,
int argc,
const ERL_NIF_TERM argv[]);
static ERL_NIF_TERM nif_listen(ErlNifEnv* env,
int argc,
const ERL_NIF_TERM argv[]);
static ERL_NIF_TERM nif_accept(ErlNifEnv* env,
int argc,
const ERL_NIF_TERM argv[]);
static ERL_NIF_TERM nif_send(ErlNifEnv* env,
int argc,
const ERL_NIF_TERM argv[]);
static ERL_NIF_TERM nif_sendto(ErlNifEnv* env,
int argc,
const ERL_NIF_TERM argv[]);
static ERL_NIF_TERM nif_recv(ErlNifEnv* env,
int argc,
const ERL_NIF_TERM argv[]);
static ERL_NIF_TERM nif_recvfrom(ErlNifEnv* env,
int argc,
const ERL_NIF_TERM argv[]);
static ERL_NIF_TERM nif_close(ErlNifEnv* env,
int argc,
const ERL_NIF_TERM argv[]);
static ERL_NIF_TERM nif_shutdown(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_link_if2idx(ErlNifEnv* env,
int argc,
const ERL_NIF_TERM argv[]);
static ERL_NIF_TERM nif_link_idx2if(ErlNifEnv* env,
int argc,
const ERL_NIF_TERM argv[]);
static ERL_NIF_TERM nif_link_ifs(ErlNifEnv* env,
int argc,
const ERL_NIF_TERM argv[]);
static ERL_NIF_TERM nif_finalize_connection(ErlNifEnv* env,
int argc,
const ERL_NIF_TERM argv[]);
static ERL_NIF_TERM nif_finalize_close(ErlNifEnv* env,
int argc,
const ERL_NIF_TERM argv[]);
static ERL_NIF_TERM nif_cancel(ErlNifEnv* env,
int argc,
const ERL_NIF_TERM argv[]);
static ERL_NIF_TERM nopen(ErlNifEnv* env,
int domain,
int type,
int protocol,
char* netns);
static ERL_NIF_TERM nbind(ErlNifEnv* env,
SocketDescriptor* descP,
ERL_NIF_TERM addr);
static ERL_NIF_TERM nconnect(ErlNifEnv* env,
SocketDescriptor* descP,
const ERL_NIF_TERM* addr,
int port);
static ERL_NIF_TERM nlisten(ErlNifEnv* env,
SocketDescriptor* descP,
int backlog);
static ERL_NIF_TERM naccept_listening(ErlNifEnv* env,
SocketDescriptor* descP,
ERL_NIF_TERM ref);
static ERL_NIF_TERM naccept_accepting(ErlNifEnv* env,
SocketDescriptor* descP,
ERL_NIF_TERM ref);
static ERL_NIF_TERM naccept(ErlNifEnv* env,
SocketDescriptor* descP,
ERL_NIF_TERM ref);
static ERL_NIF_TERM nsend(ErlNifEnv* env,
SocketDescriptor* descP,
ERL_NIF_TERM sendRef,
ErlNifBinary* dataP,
int flags);
static ERL_NIF_TERM nsendto(ErlNifEnv* env,
SocketDescriptor* descP,
ERL_NIF_TERM sendRef,
ErlNifBinary* dataP,
int flags,
SocketAddress* toAddrP,
unsigned int toAddrLen);
static ERL_NIF_TERM nrecv(ErlNifEnv* env,
SocketDescriptor* descP,
ERL_NIF_TERM recvRef,
int len,
int flags);
static ERL_NIF_TERM nrecvfrom(ErlNifEnv* env,
SocketDescriptor* descP,
ERL_NIF_TERM recvRef,
uint16_t bufSz,
int flags);
static ERL_NIF_TERM nclose(ErlNifEnv* env,
SocketDescriptor* descP);
static ERL_NIF_TERM nshutdown(ErlNifEnv* env,
SocketDescriptor* descP,
int how);
static ERL_NIF_TERM nsetopt(ErlNifEnv* env,
SocketDescriptor* descP,
BOOLEAN_T isEncoded,
BOOLEAN_T isOTP,
int level,
int opt,
SocketOptValue* valP);
static ERL_NIF_TERM nsetopt_otp(ErlNifEnv* env,
SocketDescriptor* descP,
int opt,
SocketOptValue* valP);
static ERL_NIF_TERM nsetopt_gen(ErlNifEnv* env,
SocketDescriptor* descP,
int level,
int opt,
SocketOptValue* valP);
static ERL_NIF_TERM nlink_if2idx(ErlNifEnv* env,
char* ifn);
static ERL_NIF_TERM nlink_idx2if(ErlNifEnv* env,
unsigned int id);
static ERL_NIF_TERM nlink_ifs(ErlNifEnv* env);
static unsigned int nlink_ifs_length(struct if_nameindex* p);
static ERL_NIF_TERM send_check_result(ErlNifEnv* env,
SocketDescriptor* descP,
ssize_t written,
ssize_t dataSize,
ERL_NIF_TERM sendRef);
static ERL_NIF_TERM recv_check_result(ErlNifEnv* env,
SocketDescriptor* descP,
int read,
int toRead,
ErlNifBinary* bufP,
ERL_NIF_TERM recvRef);
static ERL_NIF_TERM recvfrom_check_result(ErlNifEnv* env,
SocketDescriptor* descP,
int read,
ErlNifBinary* bufP,
SocketAddress* fromAddrP,
unsigned int fromAddrLen,
ERL_NIF_TERM recvRef);
static ERL_NIF_TERM nfinalize_connection(ErlNifEnv* env,
SocketDescriptor* descP);
static ERL_NIF_TERM nfinalize_close(ErlNifEnv* env,
SocketDescriptor* descP);
static char* decode_laddress(ErlNifEnv* env,
int domain,
ERL_NIF_TERM localAddr,
SocketAddress* localP,
unsigned int* addrLenP);
static char* decode_laddress_binary(ErlNifEnv* env,
int domain,
ERL_NIF_TERM localAddr,
SocketAddress* localP,
unsigned int* addrLenP);
static char* decode_laddress_tuple(ErlNifEnv* env,
int domain,
ERL_NIF_TERM laddr,
SocketAddress* localP,
unsigned int* addrLenP);
static char* decode_address_tuple(ErlNifEnv* env,
int domain,
const ERL_NIF_TERM* addrt,
int port,
SocketAddress* localP,
unsigned int* addrLenP);
static char* decode_address_atom(ErlNifEnv* env,
int domain,
char* addr,
int addrLen,
int port,
SocketAddress* localP,
unsigned int* addrLenP);
static char* decode_send_addr(ErlNifEnv* env,
int domain,
ERL_NIF_TERM addr,
int port,
SocketAddress** toAddrP,
unsigned int* addrLenP);
static char* decode_send_addr_tuple(ErlNifEnv* env,
int domain,
ERL_NIF_TERM addr,
int port,
SocketAddress* toAddrP,
unsigned int* addrLenP);
static void encode_address(ErlNifEnv* env,
SocketAddress* fromAddrP,
unsigned int fromAddrLen,
ERL_NIF_TERM* fromDomainT,
ERL_NIF_TERM* fromSourceT);
static BOOLEAN_T decode_sock_linger(ErlNifEnv* env,
ERL_NIF_TERM eVal,
struct linger* valP);
static BOOLEAN_T decode_ip_tos(ErlNifEnv* env,
ERL_NIF_TERM eVal,
int* val);
static BOOLEAN_T decode_bool(ErlNifEnv* env,
ERL_NIF_TERM eVal,
BOOLEAN_T* val);
static void inform_waiting_procs(ErlNifEnv* env,
SocketDescriptor* descP,
SocketRequestQueue* q,
BOOLEAN_T free,
ERL_NIF_TERM reason);
static int socket_setopt(int sock,
int level,
int opt,
const void* optVal,
const socklen_t optLen);
static BOOLEAN_T verify_is_connected(SocketDescriptor* descP, int* err);
static SocketDescriptor* alloc_descriptor(SOCKET sock, HANDLE event);
static int compare_pids(ErlNifEnv* env,
const ErlNifPid* pid1,
const ErlNifPid* pid2);
static BOOLEAN_T edomain2domain(int edomain, int* domain);
static BOOLEAN_T etype2type(int etype, int* type);
static BOOLEAN_T eproto2proto(int eproto, int* proto);
static BOOLEAN_T ehow2how(unsigned int ehow, int* how);
static BOOLEAN_T esendflags2sendflags(unsigned int esendflags, int* sendflags);
static BOOLEAN_T erecvflags2recvflags(unsigned int erecvflags, int* recvflags);
static BOOLEAN_T elevel2level(BOOLEAN_T isEncoded,
int eLevel,
BOOLEAN_T* isOTP,
int* level);
static BOOLEAN_T eoptval2optval(ErlNifEnv* env,
BOOLEAN_T isEncoded,
BOOLEAN_T isOTP,
int level,
int eOpt,
ERL_NIF_TERM eVal,
int* opt,
SocketOptValue* val);
static BOOLEAN_T eoptval2optval_otp(ErlNifEnv* env,
int eOpt,
ERL_NIF_TERM eVal,
int* opt,
SocketOptValue* valP);
static BOOLEAN_T eoptval2optval_plain(ErlNifEnv* env,
int eOpt,
ERL_NIF_TERM eVal,
int* opt,
SocketOptValue* valP);
static BOOLEAN_T eoptval2optval_socket(ErlNifEnv* env,
int eOpt,
ERL_NIF_TERM eVal,
int* opt,
SocketOptValue* valP);
static BOOLEAN_T eoptval2optval_ip(ErlNifEnv* env,
int eOpt,
ERL_NIF_TERM eVal,
int* opt,
SocketOptValue* valP);
#if defined(SOL_IPV6)
static BOOLEAN_T eoptval2optval_ipv6(ErlNifEnv* env,
int eOpt,
ERL_NIF_TERM eVal,
int* opt,
SocketOptValue* valP);
#endif
static BOOLEAN_T eoptval2optval_tcp(ErlNifEnv* env,
int eOpt,
ERL_NIF_TERM eVal,
int* opt,
SocketOptValue* valP);
static BOOLEAN_T eoptval2optval_udp(ErlNifEnv* env,
int eOpt,
ERL_NIF_TERM eVal,
int* opt,
SocketOptValue* valP);
#ifdef HAVE_SCTP
static BOOLEAN_T eoptval2optval_sctp(ErlNifEnv* env,
int eOpt,
ERL_NIF_TERM eVal,
int* opt,
SocketOptValue* valP);
#endif
#ifdef HAVE_SETNS
static BOOLEAN_T emap2netns(ErlNifEnv* env, ERL_NIF_TERM map, char** netns);
static BOOLEAN_T change_network_namespace(char* netns, int* cns, int* err);
static BOOLEAN_T restore_network_namespace(int ns, SOCKET sock, int* err);
#endif
static BOOLEAN_T cnt_inc(uint32_t* cnt, uint32_t inc);
#if defined(HAVE_SYS_UN_H) || defined(SO_BINDTODEVICE)
static size_t my_strnlen(const char *s, size_t maxlen);
#endif
static void socket_dtor(ErlNifEnv* env, void* obj);
static void socket_stop(ErlNifEnv* env,
void* obj,
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_ok2(ErlNifEnv* env, ERL_NIF_TERM val);
static ERL_NIF_TERM make_ok3(ErlNifEnv* env, ERL_NIF_TERM val1, ERL_NIF_TERM val2);
static ERL_NIF_TERM make_error(ErlNifEnv* env, ERL_NIF_TERM reason);
static ERL_NIF_TERM make_error1(ErlNifEnv* env, char* reason);
static ERL_NIF_TERM make_error2(ErlNifEnv* env, int err);
static char* send_msg_error_closed(ErlNifEnv* env,
ErlNifPid* pid);
static char* send_msg_error(ErlNifEnv* env,
ERL_NIF_TERM reason,
ErlNifPid* pid);
static char* send_msg_nif_abort(ErlNifEnv* env,
ERL_NIF_TERM ref,
ERL_NIF_TERM reason,
ErlNifPid* pid);
static char* send_msg(ErlNifEnv* env,
ERL_NIF_TERM msg,
ErlNifPid* pid);
static void xabort(const char* expr,
const char* func,
const char* file,
int line);
static BOOLEAN_T extract_item_on_load(ErlNifEnv* env,
ERL_NIF_TERM map,
ERL_NIF_TERM key,
ERL_NIF_TERM* val);
static BOOLEAN_T extract_debug_on_load(ErlNifEnv* env,
ERL_NIF_TERM map,
BOOLEAN_T def);
static BOOLEAN_T extract_iow_on_load(ErlNifEnv* env,
ERL_NIF_TERM map,
BOOLEAN_T def);
static int on_load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info);
#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_close[] = "close";
static char str_closed[] = "closed";
static char str_closing[] = "closing";
static char str_error[] = "error";
static char str_false[] = "false";
static char str_nif_abort[] = "nif_abort";
static char str_ok[] = "ok";
static char str_select[] = "select";
static char str_timeout[] = "timeout";
static char str_true[] = "true";
static char str_undefined[] = "undefined";
/* (special) error string constants */
static char str_eagain[] = "eagain";
static char str_eafnosupport[] = "eafnosupport";
static char str_einval[] = "einval";
static char str_eisconn[] = "eisconn";
static char str_enotclosing[] = "enotclosing";
static char str_enotconn[] = "enotconn";
static char str_exalloc[] = "exalloc";
static char str_exbadstate[] = "exbadstate";
static char str_exbusy[] = "exbusy";
static char str_exmon[] = "exmonitor"; // failed monitor
static char str_exself[] = "exself"; // failed self
static char str_exsend[] = "exsend"; // failed send
/* *** Atoms *** */
static ERL_NIF_TERM atom_close;
static ERL_NIF_TERM atom_closed;
static ERL_NIF_TERM atom_closing;
static ERL_NIF_TERM atom_error;
static ERL_NIF_TERM atom_false;
static ERL_NIF_TERM atom_nif_abort;
static ERL_NIF_TERM atom_ok;
static ERL_NIF_TERM atom_select;
static ERL_NIF_TERM atom_timeout;
static ERL_NIF_TERM atom_true;
static ERL_NIF_TERM atom_undefined;
static ERL_NIF_TERM atom_eagain;
static ERL_NIF_TERM atom_eafnosupport;
static ERL_NIF_TERM atom_einval;
static ERL_NIF_TERM atom_eisconn;
static ERL_NIF_TERM atom_enotclosing;
static ERL_NIF_TERM atom_enotconn;
static ERL_NIF_TERM atom_exalloc;
static ERL_NIF_TERM atom_exbadstate;
static ERL_NIF_TERM atom_exbusy;
static ERL_NIF_TERM atom_exmon;
static ERL_NIF_TERM atom_exself;
static ERL_NIF_TERM atom_exsend;
/* *** Sockets *** */
static ErlNifResourceType* sockets;
static ErlNifResourceTypeInit socketInit = {
socket_dtor,
socket_stop,
(ErlNifResourceDown*) socket_down
};
// Initiated when the nif is loaded
static SocketData socketData;
/* ----------------------------------------------------------------------
* N I F F u n c t i o n s
* ----------------------------------------------------------------------
*
* Utility and admin functions:
* ----------------------------
* nif_is_loaded/0
* nif_info/0
* (nif_debug/1)
*
* The "proper" socket functions:
* ------------------------------
* nif_open(Domain, Type, Protocol, Extra)
* nif_bind(Sock, LocalAddr)
* nif_connect(Sock, Addr, Port)
* nif_listen(Sock, Backlog)
* nif_accept(LSock, Ref)
* nif_send(Sock, SendRef, Data, Flags)
* nif_sendto(Sock, SendRef, Data, Flags, DstAddr, DstPort)
* nif_recv(Sock, RecvRef, Length, Flags)
* nif_recvfrom(Sock, Flags)
* nif_close(Sock)
* nif_shutdown(Sock, How)
*
* And some functions to manipulate and retrieve socket options:
* -------------------------------------------------------------
* nif_setopt/3
* nif_getopt/2
*
* And some utility functions:
* -------------------------------------------------------------
* nif_link_if2idx/1
* nif_link_idx2if/1
* nif_link_ifs/0
*
* And some socket admin functions:
* -------------------------------------------------------------
* nif_cancel(Sock, Ref)
*/
/* ----------------------------------------------------------------------
* nif_is_loaded
*
* Description:
* This functions only purpose is to return the atom 'true'.
* This will happen *if* the (socket) nif library is loaded.
* If its not, the erlang (nif_is_loaded) will instead return
* 'false'.
*/
static
ERL_NIF_TERM nif_is_loaded(ErlNifEnv* env,
int argc,
const ERL_NIF_TERM argv[])
{
if (argc != 0)
return enif_make_badarg(env);
return atom_true;
}
/* ----------------------------------------------------------------------
* nif_info
*
* Description:
* This is currently just a placeholder...
*/
static
ERL_NIF_TERM nif_info(ErlNifEnv* env,
int argc,
const ERL_NIF_TERM argv[])
{
ERL_NIF_TERM info = enif_make_new_map(env);
return info;
}
/* ----------------------------------------------------------------------
* nif_open
*
* Description:
* Create an endpoint for communication.
*
* Arguments:
* Domain
* Type
* Protocol
* Extra - A map with obscure options.
* Currently the only allowed option is netns (network namespace).
* This is *only* allowed on linux!
*/
static
ERL_NIF_TERM nif_open(ErlNifEnv* env,
int argc,
const ERL_NIF_TERM argv[])
{
int edomain, etype, eproto;
int domain, type, proto;
char* netns;
ERL_NIF_TERM emap;
/* Extract arguments and perform preliminary validation */
if ((argc != 4) ||
!enif_get_int(env, argv[0], &edomain) ||
!enif_get_int(env, argv[1], &etype) ||
!enif_get_int(env, argv[2], &eproto) ||
!enif_is_map(env, argv[3])) {
return enif_make_badarg(env);
}
emap = argv[3];
if (!edomain2domain(edomain, &domain))
return enif_make_badarg(env);
if (!etype2type(etype, &type))
return enif_make_badarg(env);
if (!eproto2proto(eproto, &proto))
return enif_make_badarg(env);
#ifdef HAVE_SETNS
/* We *currently* only support one extra option: netns */
if (!emap2netns(env, emap, &netns))
return enif_make_badarg(env);
#else
netns = NULL;
#endif
return nopen(env, domain, type, proto, netns);
}
/* nopen - create an endpoint for communication
*
* Assumes the input has been validated.
*/
static
ERL_NIF_TERM nopen(ErlNifEnv* env,
int domain, int type, int protocol,
char* netns)
{
SocketDescriptor* descP;
ERL_NIF_TERM res;
int save_errno = 0;
SOCKET sock;
HANDLE event;
#ifdef HAVE_SETNS
int current_ns;
#endif
#ifdef HAVE_SETNS
if (!change_network_namespace(netns, ¤t_ns, &save_errno))
return make_error2(env, save_errno);
#endif
if ((sock = sock_open(domain, type, protocol)) == INVALID_SOCKET)
return make_error2(env, sock_errno());
#ifdef HAVE_SETNS
if (!restore_network_namespace(current_ns, sock, &save_errno))
return make_error2(env, save_errno);
if (netns != NULL)
FREE(netns);
#endif
if ((event = sock_create_event(sock)) == INVALID_EVENT) {
save_errno = sock_errno();
while ((sock_close(sock) == INVALID_SOCKET) && (sock_errno() == EINTR));
return make_error2(env, save_errno);
}
SET_NONBLOCKING(sock);
/* Create and initiate the socket "descriptor" */
if ((descP = alloc_descriptor(sock, event)) == NULL) {
sock_close(sock);
// Not sure if this is really the proper error, but...
return enif_make_badarg(env);
}
descP->state = SOCKET_STATE_OPEN;
descP->domain = domain;
descP->type = type;
descP->protocol = protocol;
res = enif_make_resource(env, descP);
enif_release_resource(descP); // We should really store a reference ...
/* Keep track of the creator
* This should not be a problem but just in case
* the *open* function is used with the wrong kind
* of environment...
*/
if (enif_self(env, &descP->ctrlPid) == NULL)
return make_error(env, atom_exself);
if (MONP(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!
*/
SELECT(env,
event,
(ERL_NIF_SELECT_READ),
descP, NULL, atom_undefined);
#endif
return make_ok2(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...
*/
/* WHY NOT !IS_OPEN(...) */
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;
unsigned int addrLen;
char* err;
int port;
if ((err = decode_laddress(env, descP->domain, addr, &local, &addrLen)) != NULL)
return make_error1(env, err);
if (IS_SOCKET_ERROR(sock_bind(descP->sock,
(struct sockaddr*) &local, addrLen))) {
return make_error2(env, sock_errno());
}
port = which_address_port(&local);
if (port == 0) {
SOCKLEN_T len = sizeof(local);
sys_memzero((char *) &local, len);
sock_name(descP->sock, &local.sa, &len);
port = which_address_port(&local);
} else if (port == -1) {
port = 0;
}
return make_ok2(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,
unsigned int* addrLenP)
{
if (IS_BIN(env, localAddr)) {
return decode_laddress_binary(env, domain, localAddr, localP, addrLenP);
} else if (IS_TUPLE(env, localAddr)) {
return decode_laddress_tuple(env, domain, localAddr, localP, addrLenP);
} 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,
unsigned int* addrLenP)
{
unsigned int addrLen;
#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->sal.sun_path))
return str_einval;
sys_memzero((char*)localP, sizeof(struct sockaddr_un));
localP->sal.sun_family = domain;
sys_memcpy(localP->sal.sun_path, bin.data, bin.size);
addrLen = offsetof(struct sockaddr_un, sun_path) + bin.size;
#ifndef NO_SA_LEN
localP->u.sal.sun_len = addrLen;
#endif
*addrLenP = addrLen;
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,
unsigned int* addrLenP)
{
const ERL_NIF_TERM* laddrt;
int laddrtSz;
int port;
/* First, get the tuple and verify its size (2) */
if (!GET_TUPLE(env, laddr, &laddrtSz, &laddrt))
return str_einval;
if (laddrtSz != 2)
return str_einval;
/* So far so good. The first element is either a tuple or an atom */
if (IS_TUPLE(env, laddrt[0]) &&
IS_NUM(env, laddrt[1])) {
/* We handle two different tuples:
* - size 4 (INET)
* - size 8 (INET6)
*/
const ERL_NIF_TERM* addrt;
int addrtSz;
if (!GET_TUPLE(env, laddrt[0], &addrtSz, &addrt))
return str_einval; // PLACEHOLDER
if (!GET_INT(env, laddrt[1], &port))
return str_einval; // PLACEHOLDER
switch (domain) {
case AF_INET:
if (addrtSz != 4)
return str_einval;
break;
#if defined(HAVE_IN6) && defined(AF_INET6)
case AF_INET6:
if (addrtSz != 8)
return str_einval;
break;
#endif
default:
return str_eafnosupport;
break;
}
return decode_address_tuple(env,
domain, addrt, port,
localP, addrLenP);
} else if (IS_ATOM(env, laddrt[0]) &&
IS_NUM(env, laddrt[1])) {
/* There are only two atoms we handle:
* - any
* - loopback
*/
unsigned int len;
char a[16]; // Just in case...
if (!(GET_ATOM_LEN(env, laddrt[0], &len) &&
(len > 0) &&
(len <= (sizeof("loopback")))))
return str_einval;
if (!GET_ATOM(env, laddrt[0], a, sizeof(a)))
return str_einval;
return decode_address_atom(env,
domain, a, len, port,
localP, addrLenP);
} else {
return str_einval;
}
}
/* ----------------------------------------------------------------------
* nif_connect
*
* 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)
{
unsigned int addrLen;
int code;
char* xerr;
/* Verify that we are where in the proper state */
if (!IS_OPEN(descP))
return make_error(env, atom_exbadstate);
if (IS_CONNECTED(descP))
return make_error(env, atom_eisconn);
if (IS_CONNECTING(descP))
return make_error(env, atom_einval);
if ((xerr = decode_address_tuple(env,
descP->domain, addr, port,
&descP->remote, &addrLen)) != NULL)
return make_error1(env, xerr);
code = sock_connect(descP->sock,
(struct sockaddr*) &descP->remote, addrLen);
if (IS_SOCKET_ERROR(code) &&
((sock_errno() == ERRNO_BLOCK) || /* Winsock2 */
(sock_errno() == EINPROGRESS))) { /* Unix & OSE!! */
ERL_NIF_TERM ref = MKREF(env);
descP->state = SOCKET_STATE_CONNECTING;
SELECT(env,
descP->sock,
(ERL_NIF_SELECT_WRITE),
descP, NULL, ref);
return make_ok2(env, ref);
} else if (code == 0) { /* ok we are connected */
descP->state = SOCKET_STATE_CONNECTED;
/* Do we need to do somthing for "active" mode?
* Is there even such a thing *here*?
*/
return atom_ok;
} else {
return make_error2(env, sock_errno());
}
}
/* ----------------------------------------------------------------------
* nif_finalize_connection
*
* Description:
* Make socket ready for input and output.
*
* Arguments:
* Socket (ref) - Points to the socket descriptor.
*/
static
ERL_NIF_TERM nif_finalize_connection(ErlNifEnv* env,
int argc,
const ERL_NIF_TERM argv[])
{
SocketDescriptor* descP;
/* Extract arguments and perform preliminary validation */
if ((argc != 1) ||
!enif_get_resource(env, argv[0], sockets, (void**) &descP)) {
return enif_make_badarg(env);
}
return nfinalize_connection(env, descP);
}
/* *** nfinalize_connection ***
* Perform the final check to verify a connection.
*/
static
ERL_NIF_TERM nfinalize_connection(ErlNifEnv* env,
SocketDescriptor* descP)
{
int error;
if (descP->state != SOCKET_STATE_CONNECTING)
return make_error(env, atom_enotconn);
if (!verify_is_connected(descP, &error)) {
descP->state = SOCKET_STATE_OPEN; /* restore state */
return make_error2(env, error);
}
descP->state = SOCKET_STATE_CONNECTED;
return atom_ok;
}
/* *** verify_is_connected ***
* Check if a connection has been established.
*/
static
BOOLEAN_T verify_is_connected(SocketDescriptor* descP, int* err)
{
/*
* *** This is strange ***
*
* This *should* work on Windows NT too, but doesn't.
* An bug in Winsock 2.0 for Windows NT?
*
* See "Unix Netwok Programming", W.R.Stevens, p 412 for a
* discussion about Unix portability and non blocking connect.
*/
#ifndef SO_ERROR
int sz, code;
sz = sizeof(descP->remote);
sys_memzero((char *) &descP->remote, sz);
code = sock_peer(desc->sock,
(struct sockaddr*) &descP->remote, &sz);
if (IS_SOCKET_ERROR(code)) {
*err = sock_errno();
return FALSE;
}
#else
int error = 0; /* Has to be initiated, we check it */
unsigned int sz = sizeof(error); /* even if we get -1 */
int code = sock_getopt(descP->sock,
SOL_SOCKET, SO_ERROR,
(void *)&error, &sz);
if ((code < 0) || error) {
*err = error;
return FALSE;
}
#endif /* SO_ERROR */
*err = 0;
return TRUE;
}
/* ----------------------------------------------------------------------
* nif_listen
*
* Description:
* Listen for connections on a socket.
*
* Arguments:
* Socket (ref) - Points to the socket descriptor.
* Backlog - The maximum length to which the queue of pending
* connections for socket may grow.
*/
static
ERL_NIF_TERM nif_listen(ErlNifEnv* env,
int argc,
const ERL_NIF_TERM argv[])
{
SocketDescriptor* descP;
int backlog;
/* Extract arguments and perform preliminary validation */
if ((argc != 2) ||
!enif_get_resource(env, argv[0], sockets, (void**) &descP) ||
!GET_INT(env, argv[1], &backlog)) {
return enif_make_badarg(env);
}
return nlisten(env, descP, backlog);
}
static
ERL_NIF_TERM nlisten(ErlNifEnv* env,
SocketDescriptor* descP,
int backlog)
{
if (descP->state == SOCKET_STATE_CLOSED)
return make_error(env, atom_exbadstate);
if (!IS_OPEN(descP))
return make_error(env, atom_exbadstate);
if (IS_SOCKET_ERROR(sock_listen(descP->sock, backlog)))
return make_error2(env, sock_errno());
descP->state = SOCKET_STATE_LISTENING;
return atom_ok;
}
/* ----------------------------------------------------------------------
* nif_accept
*
* Description:
* Accept a connection on a socket.
*
* Arguments:
* Socket (ref) - Points to the socket descriptor.
* Request ref - Unique "id" of this request
* (used for the select, if none is in queue).
*/
static
ERL_NIF_TERM nif_accept(ErlNifEnv* env,
int argc,
const ERL_NIF_TERM argv[])
{
SocketDescriptor* descP;
ERL_NIF_TERM ref;
/* Extract arguments and perform preliminary validation */
if ((argc != 2) ||
!enif_get_resource(env, argv[0], sockets, (void**) &descP)) {
return enif_make_badarg(env);
}
ref = argv[1];
return naccept(env, descP, ref);
}
static
ERL_NIF_TERM naccept(ErlNifEnv* env,
SocketDescriptor* descP,
ERL_NIF_TERM ref)
{
ERL_NIF_TERM res;
switch (descP->state) {
case SOCKET_STATE_LISTENING:
MLOCK(descP->accMtx);
res = naccept_listening(env, descP, ref);
MUNLOCK(descP->accMtx);
break;
case SOCKET_STATE_ACCEPTING:
MLOCK(descP->accMtx);
res = naccept_accepting(env, descP, ref);
MUNLOCK(descP->accMtx);
break;
default:
res = make_error(env, atom_einval);
break;
}
return res;
}
/* *** naccept_listening ***
* We have no active acceptor and no acceptors in queue.
*/
static
ERL_NIF_TERM naccept_listening(ErlNifEnv* env,
SocketDescriptor* descP,
ERL_NIF_TERM ref)
{
SocketAddress remote;
unsigned int n;
SOCKET accSock;
HANDLE accEvent;
int save_errno;
ErlNifPid caller;
if (enif_self(env, &caller) == NULL)
return make_error(env, atom_exself);
n = sizeof(remote);
sys_memzero((char *) &remote, n);
accSock = sock_accept(descP->sock, (struct sockaddr*) &remote, &n);
if (accSock == INVALID_SOCKET) {
save_errno = sock_errno();
if (save_errno == ERRNO_BLOCK) {
/* *** Try again later *** */
descP->currentAcceptor.pid = caller;
if (MONP(env, descP,
&descP->currentAcceptor.pid,
&descP->currentAcceptor.mon) > 0)
return make_error(env, atom_exmon);
descP->currentAcceptor.ref = ref;
SELECT(env,
descP->sock,
(ERL_NIF_SELECT_READ),
descP, NULL, ref);
/* Shall we really change state?
* The ready event is sent directly to the calling
* process, which simply calls this function again.
* Basically, state accepting means that we have
* an "outstanding" accept.
* Shall we store the pid of the calling process?
* And if someone else calls accept, return with ebusy?
* Can any process call accept or just the controlling
* process?
* We also need a monitor it case the calling process is
* called before we are done!
*
* Change state (to accepting) and store pid of the acceptor
* (current process). Only accept calls from the acceptor
* process (ebusy) and once we have a successful accept,
* change state back to listening. If cancel is called instead
* (only accepted from the acceptor process), we reset
* state to listening and also resets the pid to "null"
* (is there such a value?).
* Need a mutex to secure that we don't test and change the
* pid at the same time.
*/
descP->state = SOCKET_STATE_ACCEPTING;
return make_error(env, atom_eagain);
} else {
return make_error2(env, save_errno);
}
} else {
SocketDescriptor* accDescP;
ERL_NIF_TERM accRef;
/*
* We got one
*/
if ((accEvent = sock_create_event(accSock)) == INVALID_EVENT) {
save_errno = sock_errno();
while ((sock_close(accSock) == INVALID_SOCKET) &&
(sock_errno() == EINTR));
return make_error2(env, save_errno);
}
if ((accDescP = alloc_descriptor(accSock, accEvent)) == NULL) {
sock_close(accSock);
return enif_make_badarg(env);
}
accDescP->domain = descP->domain;
accDescP->type = descP->type;
accDescP->protocol = descP->protocol;
accRef = enif_make_resource(env, accDescP);
enif_release_resource(accDescP); // We should really store a reference ...
accDescP->ctrlPid = caller;
if (MONP(env, accDescP,
&accDescP->ctrlPid,
&accDescP->ctrlMon) > 0) {
sock_close(accSock);
return make_error(env, atom_exmon);
}
accDescP->remote = remote;
SET_NONBLOCKING(accDescP->sock);
#ifdef __WIN32__
/* See 'What is the point of this?' above */
SELECT(env,
descP->sock,
(ERL_NIF_SELECT_READ),
descP, NULL, atom_undefined);
#endif
accDescP->state = SOCKET_STATE_CONNECTED;
return make_ok2(env, accRef);
}
}
/* *** naccept_accepting ***
* We have an active acceptor and possibly acceptors waiting in queue.
* At the moment the queue is *not* implemented.
*/
static
ERL_NIF_TERM naccept_accepting(ErlNifEnv* env,
SocketDescriptor* descP,
ERL_NIF_TERM ref)
{
SocketAddress remote;
unsigned int n;
SOCKET accSock;
HANDLE accEvent;
ErlNifPid caller;
int save_errno;
if (enif_self(env, &caller) == NULL)
return make_error(env, atom_exself);
if (compare_pids(env, &descP->currentAcceptor.pid, &caller) != 0) {
/* This will have to do until we implement the queue.
* When we have the queue, we should simply push this request,
* and instead return with eagain (the caller will then wait
* for the select message).
*/
return make_error(env, atom_exbusy);
}
n = sizeof(descP->remote);
sys_memzero((char *) &remote, n);
accSock = sock_accept(descP->sock, (struct sockaddr*) &remote, &n);
if (accSock == INVALID_SOCKET) {
save_errno = sock_errno();
if (save_errno == ERRNO_BLOCK) {
/*
* Just try again, no real error, just a ghost trigger from poll,
*/
SELECT(env,
descP->sock,
(ERL_NIF_SELECT_READ),
descP, NULL, ref);
return make_error(env, atom_eagain);
} else {
return make_error2(env, save_errno);
}
} else {
SocketDescriptor* accDescP;
ERL_NIF_TERM accRef;
/*
* We got one
*/
if ((accEvent = sock_create_event(accSock)) == INVALID_EVENT) {
save_errno = sock_errno();
while ((sock_close(accSock) == INVALID_SOCKET) &&
(sock_errno() == EINTR));
return make_error2(env, save_errno);
}
if ((accDescP = alloc_descriptor(accSock, accEvent)) == NULL) {
sock_close(accSock);
return enif_make_badarg(env);
}
accDescP->domain = descP->domain;
accDescP->type = descP->type;
accDescP->protocol = descP->protocol;
accRef = enif_make_resource(env, accDescP);
enif_release_resource(accDescP); // We should really store a reference ...
accDescP->ctrlPid = caller;
if (MONP(env, accDescP,
&accDescP->ctrlPid,
&accDescP->ctrlMon) > 0) {
sock_close(accSock);
return make_error(env, atom_exmon);
}
accDescP->remote = remote;
SET_NONBLOCKING(accDescP->sock);
#ifdef __WIN32__
/* See 'What is the point of this?' above */
SELECT(env,
descP->sock,
(ERL_NIF_SELECT_READ),
descP, NULL, atom_undefined);
#endif
accDescP->state = SOCKET_STATE_CONNECTED;
/* Here we should have the test if we have something in the queue.
* And if so, pop it and copy the (waiting) acceptor, and then
* make a new select with that info).
*/
descP->state = SOCKET_STATE_LISTENING;
return make_ok2(env, accRef);
}
}
/* ----------------------------------------------------------------------
* nif_send
*
* Description:
* Send a message on a socket
*
* Arguments:
* Socket (ref) - Points to the socket descriptor.
* SendRef - A unique id for this (send) request.
* Data - The data to send in the form of a IOVec.
* Flags - Send flags.
*/
static
ERL_NIF_TERM nif_send(ErlNifEnv* env,
int argc,
const ERL_NIF_TERM argv[])
{
SocketDescriptor* descP;
ERL_NIF_TERM sendRef;
ErlNifBinary data;
unsigned int eflags;
int flags;
ERL_NIF_TERM res;
/* Extract arguments and perform preliminary validation */
if ((argc != 4) ||
!enif_get_resource(env, argv[0], sockets, (void**) &descP) ||
!GET_BIN(env, argv[2], &data) ||
!GET_UINT(env, argv[3], &eflags)) {
return enif_make_badarg(env);
}
sendRef = argv[1];
if (!IS_CONNECTED(descP))
return make_error(env, atom_enotconn);
if (!esendflags2sendflags(eflags, &flags))
return enif_make_badarg(env);
MLOCK(descP->writeMtx);
/* We need to handle the case when another process tries
* to write at the same time.
* If the current write could not write its entire package
* this time (resulting in an select). The write of the
* other process must be made to wait until current
* is done!
* Basically, we need a write queue!
*
* A 'writing' field (boolean), which is set if we did
* not manage to write the entire message and reset every
* time we do.
*/
res = nsend(env, descP, sendRef, &data, flags);
MUNLOCK(descP->writeMtx);
return res;
}
/* What do we do when another process tries to write
* when the current writer has a select already waiting?
* Queue it? And what about simultaneous read and write?
* Queue up all operations towards the socket?
*
* We (may) need a currentOp field and an ops queue field.
*/
static
ERL_NIF_TERM nsend(ErlNifEnv* env,
SocketDescriptor* descP,
ERL_NIF_TERM sendRef,
ErlNifBinary* dataP,
int flags)
{
ssize_t written;
if (!descP->isWritable)
return enif_make_badarg(env);
/* We ignore the wrap for the moment.
* Maybe we should issue a wrap-message to controlling process...
*/
cnt_inc(&descP->writeTries, 1);
written = sock_send(descP->sock, dataP->data, dataP->size, flags);
return send_check_result(env, descP, written, dataP->size, sendRef);
}
/* ----------------------------------------------------------------------
* nif_sendto
*
* Description:
* Send a message on a socket
*
* Arguments:
* Socket (ref) - Points to the socket descriptor.
* SendRef - A unique id for this (send) request.
* Data - The data to send in the form of a IOVec.
* Flags - Send flags.
* DestAddr - Destination address.
* DestPort - Destination Port.
*/
static
ERL_NIF_TERM nif_sendto(ErlNifEnv* env,
int argc,
const ERL_NIF_TERM argv[])
{
SocketDescriptor* descP;
ERL_NIF_TERM sendRef;
ErlNifBinary data;
unsigned int eflags;
int flags;
ERL_NIF_TERM addr;
int port;
SocketAddress remoteAddr;
SocketAddress* remoteAddrP = &remoteAddr;
unsigned int remoteAddrLen;
char* xerr;
// ERL_NIF_TERM res;
/* Extract arguments and perform preliminary validation */
if ((argc != 6) ||
!enif_get_resource(env, argv[0], sockets, (void**) &descP) ||
!GET_BIN(env, argv[2], &data) ||
!GET_UINT(env, argv[3], &eflags) ||
!GET_INT(env, argv[5], &port)) {
return enif_make_badarg(env);
}
sendRef = argv[1];
addr = argv[4];
/* THIS TEST IS NOT CORRECT!!! */
if (!IS_OPEN(descP))
return make_error(env, atom_einval);
if (!esendflags2sendflags(eflags, &flags))
return enif_make_badarg(env);
if ((xerr = decode_send_addr(env, descP->domain,
addr, port,
&remoteAddrP,
&remoteAddrLen)) != NULL)
return make_error1(env, xerr);
return nsendto(env, descP, sendRef, &data, flags, remoteAddrP, remoteAddrLen);
}
static
ERL_NIF_TERM nsendto(ErlNifEnv* env,
SocketDescriptor* descP,
ERL_NIF_TERM sendRef,
ErlNifBinary* dataP,
int flags,
SocketAddress* toAddrP,
unsigned int toAddrLen)
{
ssize_t written;
if (!descP->isWritable)
return enif_make_badarg(env);
/* We ignore the wrap for the moment.
* Maybe we should issue a wrap-message to controlling process...
*/
cnt_inc(&descP->writeTries, 1);
if (toAddrP != NULL) {
written = sock_sendto(descP->sock,
dataP->data, dataP->size, flags,
&toAddrP->sa, toAddrLen);
} else {
written = sock_sendto(descP->sock,
dataP->data, dataP->size, flags,
NULL, 0);
}
return send_check_result(env, descP, written, dataP->size, sendRef);
}
/* ----------------------------------------------------------------------
* nif_writev / nif_sendv
*
* Description:
* Send a message (vector) on a socket
*
* Arguments:
* Socket (ref) - Points to the socket descriptor.
* SendRef - A unique id for this (send) request.
* Data - A vector of binaries
* Flags - Send flags.
*/
#ifdef FOBAR
static
ERL_NIF_TERM nwritev(ErlNifEnv* env,
SocketDescriptor* descP,
ERL_NIF_TERM sendRef,
ERL_NIF_TERM data)
{
ERL_NIF_TERM tail;
ErlNifIOVec vec;
ErlNifIOVec* iovec = &vec;
SysIOVec* sysiovec;
int save_errno;
int iovcnt, n;
if (!enif_inspect_iovec(env, MAX_VSZ, data, &tail, &iovec))
return enif_make_badarg(env);
if (enif_ioq_size(descP->outQ) > 0) {
/* If the I/O queue contains data we enqueue the iovec
* and then peek the data to write out of the queue.
*/
if (!enif_ioq_enqv(q, iovec, 0))
return -3;
sysiovec = enif_ioq_peek(descP->outQ, &iovcnt);
} else {
/* If the I/O queue is empty we skip the trip through it. */
iovcnt = iovec->iovcnt;
sysiovec = iovec->iov;
}
/* Attempt to write the data */
n = writev(fd, sysiovec, iovcnt);
saved_errno = errno;
if (enif_ioq_size(descP->outQ) == 0) {
/* If the I/O queue was initially empty we enqueue any
remaining data into the queue for writing later. */
if (n >= 0 && !enif_ioq_enqv(descP->outQ, iovec, n))
return -3;
} else {
/* Dequeue any data that was written from the queue. */
if (n > 0 && !enif_ioq_deq(descP->outQ, n, NULL))
return -4;
}
/* return n, which is either number of bytes written or -1 if
some error happened */
errno = saved_errno;
return n;
}
#endif
/* ----------------------------------------------------------------------
* nif_recv
*
* Description:
* Receive a message on a socket.
* Normally used only on a connected socket!
* If we are trying to read > 0 bytes, then that is what we do.
* But if we have specified 0 bytes, then we want to read
* whatever is in the buffers (everything it got).
*
* Arguments:
* Socket (ref) - Points to the socket descriptor.
* RecvRef - A unique id for this (send) request.
* Length - The number of bytes to receive.
* Flags - Receive flags.
*/
static
ERL_NIF_TERM nif_recv(ErlNifEnv* env,
int argc,
const ERL_NIF_TERM argv[])
{
SocketDescriptor* descP;
ERL_NIF_TERM recvRef;
int len;
unsigned int eflags;
int flags;
ERL_NIF_TERM res;
if ((argc != 4) ||
!enif_get_resource(env, argv[0], sockets, (void**) &descP) ||
!GET_INT(env, argv[2], &len) ||
!GET_UINT(env, argv[3], &eflags)) {
return enif_make_badarg(env);
}
recvRef = argv[1];
if (!IS_CONNECTED(descP))
return make_error(env, atom_enotconn);
if (!erecvflags2recvflags(eflags, &flags))
return enif_make_badarg(env);
MLOCK(descP->readMtx);
/* We need to handle the case when another process tries
* to receive at the same time.
* If the current recv could not read its entire package
* this time (resulting in an select). The read of the
* other process must be made to wait until current
* is done!
* Basically, we need a read queue!
*
* A 'reading' field (boolean), which is set if we did
* not manage to read the entire message and reset every
* time we do.
*/
res = nrecv(env, descP, recvRef, len, flags);
MUNLOCK(descP->readMtx);
return res;
}
/* The (read) buffer handling *must* be optimized!
* But for now we make it easy for ourselves by
* allocating a binary (of the specified or default
* size) and then throwing it away...
*/
static
ERL_NIF_TERM nrecv(ErlNifEnv* env,
SocketDescriptor* descP,
ERL_NIF_TERM recvRef,
int len,
int flags)
{
ssize_t read;
ErlNifBinary buf;
if (!descP->isReadable)
return enif_make_badarg(env);
/* Allocate a buffer:
* Either as much as we want to read or (if zero (0)) use the "default"
* size (what has been configured).
*/
if (!ALLOC_BIN((len ? len : descP->rBufSz), &buf))
return make_error(env, atom_exalloc);
/* We ignore the wrap for the moment.
* Maybe we should issue a wrap-message to controlling process...
*/
cnt_inc(&descP->readTries, 1);
read = sock_recv(descP->sock, buf.data, buf.size, flags);
return recv_check_result(env, descP,
read, len,
&buf,
recvRef);
}
/* ----------------------------------------------------------------------
* nif_recvfrom
*
* Description:
* Receive a message on a socket.
* Normally used only on a (un-) connected socket!
* If a buffer size = 0 is specified, then the we will use the default
* buffer size for this socket (whatever has been configured).
*
* Arguments:
* Socket (ref) - Points to the socket descriptor.
* RecvRef - A unique id for this (send) request.
* BufSz - Size of the buffer into which we put the received message.
* Flags - Receive flags.
*/
static
ERL_NIF_TERM nif_recvfrom(ErlNifEnv* env,
int argc,
const ERL_NIF_TERM argv[])
{
SocketDescriptor* descP;
ERL_NIF_TERM recvRef;
unsigned int bufSz;
unsigned int eflags;
int flags;
ERL_NIF_TERM res;
if ((argc != 4) ||
!enif_get_resource(env, argv[0], sockets, (void**) &descP) ||
!GET_UINT(env, argv[2], &bufSz) ||
!GET_UINT(env, argv[3], &eflags)) {
return enif_make_badarg(env);
}
recvRef = argv[1];
/* if (IS_OPEN(descP)) */
/* return make_error(env, atom_enotconn); */
if (!erecvflags2recvflags(eflags, &flags))
return enif_make_badarg(env);
MLOCK(descP->readMtx);
/* <KOLLA>
* We need to handle the case when another process tries
* to receive at the same time.
* If the current recv could not read its entire package
* this time (resulting in an select). The read of the
* other process must be made to wait until current
* is done!
* Basically, we need a read queue!
*
* A 'reading' field (boolean), which is set if we did
* not manage to read the entire message and reset every
* time we do.
* </KOLLA>
*/
res = nrecvfrom(env, descP, recvRef, bufSz, flags);
MUNLOCK(descP->readMtx);
return res;
}
/* The (read) buffer handling *must* be optimized!
* But for now we make it easy for ourselves by
* allocating a binary (of the specified or default
* size) and then throwing it away...
*/
static
ERL_NIF_TERM nrecvfrom(ErlNifEnv* env,
SocketDescriptor* descP,
ERL_NIF_TERM recvRef,
uint16_t bufSz,
int flags)
{
SocketAddress fromAddr;
unsigned int addrLen;
ssize_t read;
ErlNifBinary buf;
if (!descP->isReadable)
return enif_make_badarg(env);
/* Allocate a buffer:
* Either as much as we want to read or (if zero (0)) use the "default"
* size (what has been configured).
*/
if (!ALLOC_BIN((bufSz ? bufSz : descP->rBufSz), &buf))
return make_error(env, atom_exalloc);
/* We ignore the wrap for the moment.
* Maybe we should issue a wrap-message to controlling process...
*/
cnt_inc(&descP->readTries, 1);
addrLen = sizeof(fromAddr);
sys_memzero((char*) &fromAddr, addrLen);
read = sock_recvfrom(descP->sock, buf.data, buf.size, flags,
&fromAddr.sa, &addrLen);
return recvfrom_check_result(env, descP,
read,
&buf,
&fromAddr, addrLen,
recvRef);
}
/* ----------------------------------------------------------------------
* nif_close
*
* Description:
* Close a (socket) file descriptor.
*
* Arguments:
* Socket (ref) - Points to the socket descriptor.
*/
static
ERL_NIF_TERM nif_close(ErlNifEnv* env,
int argc,
const ERL_NIF_TERM argv[])
{
SocketDescriptor* descP;
if ((argc != 1) ||
!enif_get_resource(env, argv[0], sockets, (void**) &descP)) {
return enif_make_badarg(env);
}
return nclose(env, descP);
}
static
ERL_NIF_TERM nclose(ErlNifEnv* env,
SocketDescriptor* descP)
{
ERL_NIF_TERM reply, reason;
BOOLEAN_T doClose;
int selectRes;
MLOCK(descP->closeMtx);
if (descP->state == SOCKET_STATE_CLOSED) {
reason = atom_closed;
doClose = FALSE;
} else if (descP->state == SOCKET_STATE_CLOSING) {
reason = atom_closing;
doClose = FALSE;
} else {
/* Store the PID of the caller,
* since we need to inform it when we
* (that is, the stop callback function)
* completes.
*/
if (enif_self(env, &descP->closerPid) == NULL) {
MUNLOCK(descP->closeMtx);
return make_error(env, atom_exself);
}
/* Monitor the caller, since we should complete this operation even if
* the caller dies (for whatever reason).
*/
if (MONP(env, descP,
&descP->closerPid,
&descP->closerMon) > 0) {
MUNLOCK(descP->closeMtx);
return make_error(env, atom_exmon);
}
descP->closeLocal = TRUE;
descP->state = SOCKET_STATE_CLOSING;
doClose = TRUE;
}
MUNLOCK(descP->closeMtx);
if (doClose) {
descP->closeRef = MKREF(env);
selectRes = enif_select(env, descP->sock, (ERL_NIF_SELECT_STOP),
descP, NULL, descP->closeRef);
if (selectRes & ERL_NIF_SELECT_STOP_CALLED) {
/* Prep done - inform the caller it can finalize (close) directly */
reply = atom_ok;
} else if (selectRes & ERL_NIF_SELECT_STOP_SCHEDULED) {
/* The stop callback function has been *scheduled* which means that we
* have to wait for it to complete. */
reply = make_ok2(env, descP->closeRef);
} else {
/* <KOLLA>
*
* WE SHOULD REALLY HAVE A WAY TO CLOBBER THE SOCKET,
* SO WE DON'T LET STUFF LEAK.
* NOW, BECAUSE WE FAILED TO SELECT, WE CANNOT FINISH
* THE CLOSE, WHAT TO DO? ABORT?
*
* </KOLLA>
*/
reason = MKT2(env, atom_select, MKI(env, selectRes));
reply = make_error(env, reason);
}
} else {
reply = make_error(env, reason);
}
return reply;
}
/* ----------------------------------------------------------------------
* nif_finalize_close
*
* Description:
* Perform the actual socket close!
* Note that this function is executed in a dirfty scheduler.
*
* Arguments:
* Socket (ref) - Points to the socket descriptor.
*/
static
ERL_NIF_TERM nif_finalize_close(ErlNifEnv* env,
int argc,
const ERL_NIF_TERM argv[])
{
SocketDescriptor* descP;
/* Extract arguments and perform preliminary validation */
if ((argc != 1) ||
!enif_get_resource(env, argv[0], sockets, (void**) &descP)) {
return enif_make_badarg(env);
}
return nfinalize_close(env, descP);
}
/* *** nfinalize_close ***
* Perform the final step in the socket close.
*/
static
ERL_NIF_TERM nfinalize_close(ErlNifEnv* env,
SocketDescriptor* descP)
{
ERL_NIF_TERM reply;
if (descP->state == SOCKET_STATE_CLOSED)
return atom_ok;
if (descP->state != SOCKET_STATE_CLOSING)
return make_error(env, atom_enotclosing);
/* This nif is executed in a dirty scheduler just so that
* it can "hang" (whith minumum effect on the VM) while the
* kernel writes our buffers. IF we have set the linger option
* for this ({true, integer() > 0}). For this to work we must
* be blocking...
*/
SET_BLOCKING(descP->sock);
if (sock_close(descP->sock) != 0) {
int save_errno = sock_errno();
if (save_errno != ERRNO_BLOCK) {
/* Not all data in the buffers where sent,
* make sure the caller gets this.
*/
reply = make_error(env, atom_timeout);
} else {
reply = make_error2(env, save_errno);
}
} else {
reply = atom_ok;
}
sock_close_event(descP->event);
descP->sock = INVALID_SOCKET;
descP->event = INVALID_EVENT;
descP->state = SOCKET_STATE_CLOSED;
return reply;
}
/* ----------------------------------------------------------------------
* nif_shutdown
*
* Description:
* Disable sends and/or receives on a socket.
*
* Arguments:
* Socket (ref) - Points to the socket descriptor.
* How - What will be shutdown.
*/
static
ERL_NIF_TERM nif_shutdown(ErlNifEnv* env,
int argc,
const ERL_NIF_TERM argv[])
{
SocketDescriptor* descP;
unsigned int ehow;
int how;
if ((argc != 2) ||
!enif_get_resource(env, argv[0], sockets, (void**) &descP) ||
!GET_UINT(env, argv[1], &ehow)) {
return enif_make_badarg(env);
}
if (!ehow2how(ehow, &how))
return enif_make_badarg(env);
return nshutdown(env, descP, how);
}
static
ERL_NIF_TERM nshutdown(ErlNifEnv* env,
SocketDescriptor* descP,
int how)
{
ERL_NIF_TERM reply;
if (sock_shutdown(descP->sock, how) == 0) {
switch (how) {
case SHUT_RD:
descP->isReadable = FALSE;
break;
case SHUT_WR:
descP->isWritable = FALSE;
break;
case SHUT_RDWR:
descP->isReadable = FALSE;
descP->isWritable = FALSE;
break;
}
reply = atom_ok;
} else {
reply = make_error2(env, sock_errno());
}
return reply;
}
/* ----------------------------------------------------------------------
* nif_setopt
*
* Description:
* Set socket option.
* Its possible to use a "raw" mode (not encoded). That is, we do not
* interpret level, opt and value. They are passed "as is" to the
* setsockopt function call (the value arguments is assumed to be a
* binary, already encoded).
*
* Arguments:
* Socket (ref) - Points to the socket descriptor.
* Encoded - Are the "arguments" encoded or not.
* Level - Level of the socket option.
* Opt - The socket option.
* Value - Value of the socket option (type depend on the option).
*/
static
ERL_NIF_TERM nif_setopt(ErlNifEnv* env,
int argc,
const ERL_NIF_TERM argv[])
{
SocketDescriptor* descP;
unsigned int eIsEncoded;
BOOLEAN_T isEncoded, isOTP;
int eLevel, level = -1;
int eOpt, opt = -1;
ERL_NIF_TERM eVal;
SocketOptValue val;
if ((argc != 5) ||
!enif_get_resource(env, argv[0], sockets, (void**) &descP) ||
!GET_UINT(env, argv[1], &eIsEncoded) ||
!GET_INT(env, argv[2], &eLevel) ||
!GET_INT(env, argv[3], &eOpt)) {
return enif_make_badarg(env);
}
eVal = argv[4];
isEncoded = ((eIsEncoded == 0) ? FALSE : TRUE);
if (!elevel2level(isEncoded, eLevel, &isOTP, &level))
return make_error(env, atom_einval);
if (!eoptval2optval(env, isEncoded, isOTP, level, eOpt, eVal, &opt, &val))
return make_error(env, atom_einval);
return nsetopt(env, descP, isEncoded, isOTP, level, opt, &val);
}
static
ERL_NIF_TERM nsetopt(ErlNifEnv* env,
SocketDescriptor* descP,
BOOLEAN_T isEncoded,
BOOLEAN_T isOTP,
int level,
int opt,
SocketOptValue* valP)
{
ERL_NIF_TERM result;
int res;
if (!isEncoded) {
res = socket_setopt(descP->sock, level, opt,
valP->u.binVal.data, valP->u.binVal.size);
if (res != 0)
result = make_error2(env, res);
else
result = atom_ok;
} else {
if (isOTP) {
/* These are not actual socket options,
* but options for our implementation.
*/
result = nsetopt_otp(env, descP, opt, valP);
} else {
/* Basically, call setsockopt(...)
* <KOLLA>
* How do we know what type each option have? tag in value type?
* </KOLLA>
*/
result = nsetopt_gen(env, descP, level, opt, valP);
}
}
return result;
}
static
ERL_NIF_TERM nsetopt_otp(ErlNifEnv* env,
SocketDescriptor* descP,
int opt,
SocketOptValue* valP)
{
ERL_NIF_TERM result;
/* Make an idiot check just to be on the safe side... */
if (valP->tag == SOCKET_OPT_VALUE_UNDEF)
return make_error(env, atom_einval);
switch (opt) {
case SOCKET_OPT_OTP_DEBUG:
descP->dbg = valP->u.boolVal;
result = atom_ok;
break;
case SOCKET_OPT_OTP_IOW:
descP->iow = valP->u.boolVal;
result = atom_ok;
break;
default:
result = make_error(env, atom_einval);
break;
}
return result;
}
static
ERL_NIF_TERM nsetopt_gen(ErlNifEnv* env,
SocketDescriptor* descP,
int level,
int opt,
SocketOptValue* valP)
{
socklen_t optLen;
int res;
ERL_NIF_TERM result;
switch (valP->tag) {
case SOCKET_OPT_VALUE_INT:
{
optLen = sizeof(valP->u.intVal);
res = socket_setopt(descP->sock, level, opt,
(void*) &valP->u.intVal, optLen);
if (res != 0)
result = make_error2(env, res);
else
result = atom_ok;
}
break;
case SOCKET_OPT_VALUE_BIN:
{
optLen = valP->u.binVal.size;
res = socket_setopt(descP->sock, level, opt,
&valP->u.binVal.data, optLen);
if (res != 0)
result = make_error2(env, res);
else
result = atom_ok;
}
break;
case SOCKET_OPT_VALUE_LINGER:
{
optLen = sizeof(valP->u.lingerVal);
res = socket_setopt(descP->sock, level, opt,
&valP->u.lingerVal, optLen);
if (res != 0)
result = make_error2(env, res);
else
result = atom_ok;
}
break;
case SOCKET_OPT_VALUE_STR:
{
optLen = valP->u.strVal.len;
res = socket_setopt(descP->sock, level, opt,
valP->u.strVal.str, optLen);
if (res != 0)
result = make_error2(env, res);
else
result = atom_ok;
}
break;
default:
result = make_error(env, atom_einval);
}
return result;
}
static
BOOLEAN_T elevel2level(BOOLEAN_T isEncoded,
int eLevel,
BOOLEAN_T* isOTP,
int* level)
{
BOOLEAN_T result;
if (isEncoded) {
switch (eLevel) {
case SOCKET_OPT_LEVEL_OTP:
*isOTP = TRUE;
*level = -1;
result = TRUE;
break;
case SOCKET_OPT_LEVEL_SOCKET:
*isOTP = FALSE;
*level = SOL_SOCKET;
result = TRUE;
break;
case SOCKET_OPT_LEVEL_IP:
*isOTP = FALSE;
#if defined(SOL_IP)
*level = SOL_IP;
#else
*level = IPROTO_IP;
#endif
result = TRUE;
break;
#if defined(SOL_IPV6)
case SOCKET_OPT_LEVEL_IPV6:
*isOTP = FALSE;
*level = SOL_IPV6;
result = TRUE;
break;
#endif
case SOCKET_OPT_LEVEL_TCP:
*isOTP = FALSE;
*level = IPPROTO_TCP;
result = TRUE;
break;
case SOCKET_OPT_LEVEL_UDP:
*isOTP = FALSE;
*level = IPPROTO_UDP;
result = TRUE;
break;
#ifdef HAVE_SCTP
case SOCKET_OPT_LEVEL_SCTP:
*isOTP = FALSE;
*level = IPPROTO_SCTP;
result = TRUE;
break;
#endif
default:
*isOTP = FALSE;
*level = -1;
result = FALSE;
break;
}
} else {
*isOTP = FALSE;
*level = eLevel;
result = TRUE;
}
return result;
}
static
BOOLEAN_T eoptval2optval(ErlNifEnv* env,
BOOLEAN_T isEncoded,
BOOLEAN_T isOTP,
int level,
int eOpt,
ERL_NIF_TERM eVal,
int* opt,
SocketOptValue* valP)
{
if (isOTP) {
return eoptval2optval_otp(env, eOpt, eVal, opt, valP);
} else if (!isEncoded) {
return eoptval2optval_plain(env, eOpt, eVal, opt, valP);
} else {
switch (level) {
case SOL_SOCKET:
return eoptval2optval_socket(env, eOpt, eVal, opt, valP);
break;
#if defined(SOL_IP)
case SOL_IP:
#else
case IPPROTO_IP:
#endif
return eoptval2optval_ip(env, eOpt, eVal, opt, valP);
break;
#if defined(SOL_IPV6)
case SOL_IPV6:
return eoptval2optval_ipv6(env, eOpt, eVal, opt, valP);
break;
#endif
case IPPROTO_TCP:
return eoptval2optval_tcp(env, eOpt, eVal, opt, valP);
break;
case IPPROTO_UDP:
return eoptval2optval_udp(env, eOpt, eVal, opt, valP);
break;
#ifdef HAVE_SCTP
case IPPROTO_SCTP:
return eoptval2optval_sctp(env, eOpt, eVal, opt, valP);
break;
#endif
default:
*opt = -1;
return FALSE;
}
}
}
static
BOOLEAN_T eoptval2optval_otp(ErlNifEnv* env,
int eOpt,
ERL_NIF_TERM eVal,
int* opt,
SocketOptValue* valP)
{
BOOLEAN_T result = FALSE;
switch (eOpt) {
case SOCKET_OPT_OTP_IOW:
case SOCKET_OPT_OTP_DEBUG:
{
if (decode_bool(env, eVal, &valP->u.boolVal)) {
valP->tag = SOCKET_OPT_VALUE_BOOL;
*opt = eOpt;
result = TRUE;
} else {
valP->tag = SOCKET_OPT_VALUE_UNDEF;
*opt = -1;
result = FALSE;
}
}
break;
default:
*opt = -1;
valP->tag = SOCKET_OPT_VALUE_UNDEF;
}
return result;
}
static
BOOLEAN_T eoptval2optval_plain(ErlNifEnv* env,
int eOpt,
ERL_NIF_TERM eVal,
int* opt,
SocketOptValue* valP)
{
if (!GET_BIN(env, eVal, &valP->u.binVal))
return FALSE;
valP->tag = SOCKET_OPT_VALUE_BIN;
*opt = eOpt;
return TRUE;
}
static
BOOLEAN_T eoptval2optval_socket(ErlNifEnv* env,
int eOpt,
ERL_NIF_TERM eVal,
int* opt,
SocketOptValue* valP)
{
switch (eOpt) {
#if defined(SO_KEEPALIVE)
case SOCKET_OPT_SOCK_KEEPALIVE:
{
BOOLEAN_T val;
if (decode_bool(env, eVal, &val)) {
*opt = SO_KEEPALIVE;
valP->tag = SOCKET_OPT_VALUE_INT;
valP->u.intVal = (val) ? 1 : 0;
return TRUE;
} else {
*opt = -1;
valP->tag = SOCKET_OPT_VALUE_UNDEF;
return FALSE;
}
}
break;
#endif
#if defined(SO_LINGER)
case SOCKET_OPT_SOCK_LINGER:
{
if (decode_sock_linger(env, eVal, &valP->u.lingerVal)) {
*opt = SO_LINGER;
valP->tag = SOCKET_OPT_VALUE_LINGER;
return TRUE;
} else {
*opt = -1;
valP->tag = SOCKET_OPT_VALUE_UNDEF;
return FALSE;
}
}
break;
#endif
#if defined(SO_REUSEADDR)
case SOCKET_OPT_SOCK_REUSEADDR:
{
BOOLEAN_T val;
if (decode_bool(env, eVal, &val)) {
*opt = SO_REUSEADDR;
valP->tag = SOCKET_OPT_VALUE_INT;
valP->u.intVal = (val) ? 1 : 0;
return TRUE;
} else {
*opt = -1;
valP->tag = SOCKET_OPT_VALUE_UNDEF;
return FALSE;
}
}
break;
#endif
default:
*opt = -1;
valP->tag = SOCKET_OPT_VALUE_UNDEF;
return FALSE;
}
}
static
BOOLEAN_T eoptval2optval_ip(ErlNifEnv* env,
int eOpt,
ERL_NIF_TERM eVal,
int* opt,
SocketOptValue* valP)
{
switch (eOpt) {
#if defined(IP_RECVTOS)
case SOCKET_OPT_IP_RECVTOS:
{
BOOLEAN_T val;
if (decode_bool(env, eVal, &val)) {
*opt = IP_RECVTOS;
valP->tag = SOCKET_OPT_VALUE_INT;
valP->u.intVal = (val) ? 1 : 0;
return TRUE;
} else {
*opt = -1;
valP->tag = SOCKET_OPT_VALUE_UNDEF;
return TRUE;
}
}
break;
#endif
#if defined(IP_ROUTER_ALERT)
case SOCKET_OPT_IP_ROUTER_ALERT:
if (GET_INT(env, eVal, &valP->u.intVal)) {
valP->tag = SOCKET_OPT_VALUE_INT;
*opt = IP_ROUTER_ALERT;
return TRUE;
} else {
*opt = -1;
valP->tag = SOCKET_OPT_VALUE_UNDEF;
return FALSE;
}
break;
#endif
#if defined(IP_TOS)
case SOCKET_OPT_IP_TOS:
{
if (decode_ip_tos(env, eVal, &valP->u.intVal)) {
valP->tag = SOCKET_OPT_VALUE_INT;
*opt = IP_TOS;
return TRUE;
} else {
*opt = -1;
return FALSE;
}
}
break;
#endif
#if defined(IP_TTL)
case SOCKET_OPT_IP_TTL:
/* <KOLLA>
* Should we care about the value? That is, if it is valid?
* And what is the valid range anyway for ttl? 0 - 255?
* </KOLLA>
*/
if (!GET_INT(env, eVal, &valP->u.intVal))
return FALSE; // PLACEHOLDER - We should really be more informative
valP->tag = SOCKET_OPT_VALUE_INT;
*opt = IP_TTL;
return TRUE;
break;
#endif
default:
*opt = -1;
valP->tag = SOCKET_OPT_VALUE_UNDEF;
return FALSE;
}
}
#if defined(SOL_IPV6)
static
BOOLEAN_T eoptval2optval_ipv6(ErlNifEnv* env,
int eOpt,
ERL_NIF_TERM eVal,
int* opt,
SocketOptValue* valP)
{
BOOLEAN_T result = FALSE;
switch (eOpt) {
#if defined(IPV6_HOPLIMIT)
case SOCKET_OPT_IPV6_HOPLIMIT:
{
BOOLEAN_T val;
if (decode_bool(env, eVal, &val)) {
valP->tag = SOCKET_OPT_VALUE_INT;
valP->u.intVal = (val) ? 1 : 0;
*opt = IPV6_HOPLIMIT;
result = TRUE;
} else {
*opt = -1;
result = FALSE;
}
}
break;
#endif
default:
*opt = -1;
valP->tag = SOCKET_OPT_VALUE_UNDEF;
result = FALSE;
break;
}
return result;
}
#endif
static
BOOLEAN_T eoptval2optval_tcp(ErlNifEnv* env,
int eOpt,
ERL_NIF_TERM eVal,
int* opt,
SocketOptValue* valP)
{
switch (eOpt) {
#if defined(TCP_CONGESTION)
case SOCKET_OPT_TCP_CONGESTION:
{
valP->u.strVal.len = SOCKET_OPT_TCP_CONGESTION_NAME_MAX+1;
valP->u.strVal.str = MALLOC(valP->u.strVal.len);
if (GET_STR(env, eVal, valP->u.strVal.str, valP->u.strVal.len) > 0) {
valP->tag = SOCKET_OPT_VALUE_STR;
*opt = TCP_CONGESTION;
return TRUE;
} else {
FREE(valP->u.strVal.str);
*opt = -1;
valP->tag = SOCKET_OPT_VALUE_UNDEF;
return FALSE;
}
}
break;
#endif
#if defined(TCP_MAXSEG)
case SOCKET_OPT_TCP_MAXSEG:
if (!GET_INT(env, eVal, &valP->u.intVal)) {
valP->tag = SOCKET_OPT_VALUE_INT;
*opt = TCP_MAXSEG;
return TRUE;
} else {
*opt = -1;
valP->tag = SOCKET_OPT_VALUE_UNDEF;
return FALSE;
}
break;
#endif
#if defined(TCP_NODELAY)
case SOCKET_OPT_TCP_NODELAY:
if (decode_bool(env, eVal, &valP->u.boolVal)) {
valP->tag = SOCKET_OPT_VALUE_BOOL;
*opt = TCP_NODELAY;
return TRUE;
} else {
valP->tag = SOCKET_OPT_VALUE_UNDEF;
*opt = -1;
return FALSE;
}
break;
#endif
default:
*opt = -1;
valP->tag = SOCKET_OPT_VALUE_UNDEF;
return FALSE;
}
}
/* +++ decode UDP socket options +++
* Currently there are only one option, cork, and that may only
* work on linux...
*/
static
BOOLEAN_T eoptval2optval_udp(ErlNifEnv* env,
int eOpt,
ERL_NIF_TERM eVal,
int* opt,
SocketOptValue* valP)
{
switch (eOpt) {
#if defined(UDP_CORK)
case SOCKET_OPT_UDP_CORK:
if (decode_bool(env, eVal, &valP->u.boolVal)) {
valP->tag = SOCKET_OPT_VALUE_BOOL;
*opt = UDP_CORK;
return TRUE;
} else {
valP->tag = SOCKET_OPT_VALUE_UNDEF;
*opt = -1;
return FALSE;
}
break;
#endif
default:
*opt = -1;
valP->tag = SOCKET_OPT_VALUE_UNDEF;
return FALSE;
}
}
#ifdef HAVE_SCTP
static
BOOLEAN_T eoptval2optval_sctp(ErlNifEnv* env,
int eOpt,
ERL_NIF_TERM eVal,
int* opt,
SocketOptValue* valP)
{
switch (eOpt) {
#if defined(SCTP_AUTOCLOSE)
case SOCKET_OPT_SCTP_AUTOCLOSE:
if (GET_INT(env, eVal, &valP->u.intVal)) {
valP->tag = SOCKET_OPT_VALUE_INT;
*opt = SCTP_AUTOCLOSE;
return TRUE;
} else {
valP->tag = SOCKET_OPT_VALUE_UNDEF;
*opt = -1;
return FALSE; // PLACEHOLDER - We should really be more informative
}
break;
#endif
#if defined(SCTP_NODELAY)
case SOCKET_OPT_SCTP_NODELAY:
if (decode_bool(env, eVal, &valP->u.boolVal)) {
valP->tag = SOCKET_OPT_VALUE_BOOL;
*opt = SCTP_NODELAY;
return TRUE;
} else {
valP->tag = SOCKET_OPT_VALUE_UNDEF;
*opt = -1;
return FALSE;
}
break;
#endif
default:
*opt = -1;
valP->tag = SOCKET_OPT_VALUE_UNDEF;
return FALSE;
}
}
#endif
/* +++ socket_setopt +++
*
* <Per H @ Tail-f>
* The original code here had problems that possibly
* only occur if you abuse it for non-INET sockets, but anyway:
* a) If the getsockopt for SO_PRIORITY or IP_TOS failed, the actual
* requested setsockopt was never even attempted.
* b) If {get,set}sockopt for one of IP_TOS and SO_PRIORITY failed,
* but ditto for the other worked and that was actually the requested
* option, failure was still reported to erlang.
* </Per H @ Tail-f>
*
* <PaN>
* The relations between SO_PRIORITY, TOS and other options
* is not what you (or at least I) would expect...:
* If TOS is set after priority, priority is zeroed.
* If any other option is set after tos, tos might be zeroed.
* Therefore, save tos and priority. If something else is set,
* restore both after setting, if tos is set, restore only
* prio and if prio is set restore none... All to keep the
* user feeling socket options are independent.
* </PaN>
*/
static
int socket_setopt(int sock, int level, int opt,
const void* optVal, const socklen_t optLen)
{
int res;
#if defined(IP_TOS) && defined(SOL_IP) && defined(SO_PRIORITY)
int tmpIValPRIO;
int tmpIValTOS;
int resPRIO;
int resTOS;
SOCKLEN_T tmpArgSzPRIO = sizeof(tmpIValPRIO);
SOCKLEN_T tmpArgSzTOS = sizeof(tmpIValTOS);
resPRIO = sock_getopt(sock, SOL_SOCKET, SO_PRIORITY,
&tmpIValPRIO, &tmpArgSzPRIO);
resTOS = sock_getopt(sock, SOL_IP, IP_TOS,
&tmpIValTOS, &tmpArgSzTOS);
res = sock_setopt(sock, level, opt, optVal, optLen);
if (res == 0) {
/* Ok, now we *maybe* need to "maybe" restore PRIO and TOS...
* maybe, possibly, ...
*/
if (opt != SO_PRIORITY) {
if ((opt != IP_TOS) && (resTOS == 0)) {
resTOS = sock_setopt(sock, SOL_IP, IP_TOS,
(void *) &tmpIValTOS,
tmpArgSzTOS);
res = resTOS;
}
if ((res == 0) && (resPRIO == 0)) {
resPRIO = sock_setopt(sock, SOL_SOCKET, SO_PRIORITY,
&tmpIValPRIO,
tmpArgSzPRIO);
/* Some kernels set a SO_PRIORITY by default
* that you are not permitted to reset,
* silently ignore this error condition.
*/
if ((resPRIO != 0) && (sock_errno() == EPERM)) {
res = 0;
} else {
res = resPRIO;
}
}
}
}
#else
res = sock_setopt(sock, level, opt, optVal, optLen);
#endif
return res;
}
/* ----------------------------------------------------------------------
* nif_link_if2idx
*
* Description:
* Perform a Interface Name to Interface Index translation.
*
* Arguments:
* Ifn - Interface name to be translated.
*/
static
ERL_NIF_TERM nif_link_if2idx(ErlNifEnv* env,
int argc,
const ERL_NIF_TERM argv[])
{
ERL_NIF_TERM eifn;
char ifn[IF_NAMESIZE+1];
if (argc != 1) {
return enif_make_badarg(env);
}
eifn = argv[0];
if (0 >= GET_STR(env, eifn, ifn, sizeof(ifn)))
return make_error2(env, atom_einval);
return nlink_if2idx(env, ifn);
}
static
ERL_NIF_TERM nlink_if2idx(ErlNifEnv* env,
char* ifn)
{
unsigned int idx = if_nametoindex(ifn);
if (idx == 0)
return make_error2(env, sock_errno());
else
return make_ok2(env, idx);
}
/* ----------------------------------------------------------------------
* nif_link_idx2if
*
* Description:
* Perform a Interface Index to Interface Name translation.
*
* Arguments:
* Idx - Interface index to be translated.
*/
static
ERL_NIF_TERM nif_link_idx2if(ErlNifEnv* env,
int argc,
const ERL_NIF_TERM argv[])
{
unsigned int idx;
if ((argc != 1) ||
!GET_UINT(env, argv[0], &idx)) {
return enif_make_badarg(env);
}
return nlink_idx2if(env, idx);
}
static
ERL_NIF_TERM nlink_idx2if(ErlNifEnv* env,
unsigned int idx)
{
ERL_NIF_TERM result;
char* ifn = MALLOC(IF_NAMESIZE+1);
if (ifn == NULL)
return enif_make_badarg(env); // PLACEHOLDER
if (NULL == if_indextoname(idx, ifn)) {
result = make_ok2(env, MKS(env, ifn));
} else {
result = make_error2(env, sock_errno());
}
FREE(ifn); // <KOLL> OR IS THIS AUTOMATIC? </KOLLA>
return result;
}
/* ----------------------------------------------------------------------
* nif_link_idx2if
*
* Description:
* Get network interface names and indexes.
*
*/
static
ERL_NIF_TERM nif_link_ifs(ErlNifEnv* env,
int argc,
const ERL_NIF_TERM argv[])
{
if (argc != 0) {
return enif_make_badarg(env);
}
return nlink_ifs(env);
}
static
ERL_NIF_TERM nlink_ifs(ErlNifEnv* env)
{
ERL_NIF_TERM result;
struct if_nameindex* ifs = if_nameindex();
if (ifs == NULL) {
result = make_error2(env, sock_errno());
} else {
/*
* We got some interfaces:
* 1) Calculate how many - the only way is to iterate through the list
* until its end (which is indicated by an entry with index = zero
* and if_name = NULL).
* 2) Allocate an ERL_NIF_TERM array of the calculated length.
* 3) Iterate through the array of interfaces and for each create
* a two tuple: {Idx, If}
*
* Or shall we instead build a list in reverse order and then when
* its done, reverse that? Check
*/
unsigned int len = nlink_ifs_length(ifs);
if (len > 0) {
ERL_NIF_TERM* array = MALLOC(len * sizeof(ERL_NIF_TERM));
unsigned int i;
for (i = 0; i < len; i++) {
array[i] = MKT2(env,
MKI(env, ifs[i].if_index),
MKS(env, ifs[i].if_name));
}
result = make_ok2(env, MKLA(env, array, len));
FREE(array);
} else {
result = make_ok2(env, enif_make_list(env, 0));
}
}
if (ifs != NULL)
if_freenameindex(ifs);
return result;
}
static
unsigned int nlink_ifs_length(struct if_nameindex* p)
{
unsigned int len = 0;
while ((p[len].if_index == 0) && (p[len].if_name == NULL)) {
len++;
}
return len;
}
/* ----------------------------------------------------------------------
* U t i l i t y F u n c t i o n s
* ----------------------------------------------------------------------
*/
static
ERL_NIF_TERM send_check_result(ErlNifEnv* env,
SocketDescriptor* descP,
ssize_t written,
ssize_t dataSize,
ERL_NIF_TERM sendRef)
{
if (written == dataSize) {
cnt_inc(&descP->writePkgCnt, 1);
cnt_inc(&descP->writeByteCnt, written);
return atom_ok;
} else if (written < 0) {
/* Ouch, check what kind of failure */
int save_errno = sock_errno();
if ((save_errno != EAGAIN) &&
(save_errno != EINTR)) {
cnt_inc(&descP->writeFails, 1);
return make_error2(env, save_errno);
} else {
/* Ok, try again later */
/* <KOLLA>
* SHOULD RESULT IN {error, eagain}!!!!
* </KOLLA>
*/
written = 0;
}
}
/* We failed to write the *entire* packet (anything less then size
* of the packet, which is 0 <= written < sizeof packet),
* so schedule the rest for later.
*/
cnt_inc(&descP->writeWaits, 1);
SELECT(env, descP->sock, (ERL_NIF_SELECT_WRITE),
descP, NULL, sendRef);
return make_ok2(env, enif_make_int(env, written));
}
static
ERL_NIF_TERM recv_check_result(ErlNifEnv* env,
SocketDescriptor* descP,
int read,
int toRead,
ErlNifBinary* bufP,
ERL_NIF_TERM recvRef)
{
ERL_NIF_TERM data;
/* There is a special case: If the provided 'to read' value is
* zero (0). That means that we reads as much as we can, using
* the default read buffer size.
*/
if (bufP->size == read) {
/* +++ We filled the buffer +++ */
if (toRead == 0) {
/* +++ Give us everything you have got => needs to continue +++ */
/* How do we do this?
* Either:
* 1) Send up each chunk of data for each of the read
* and let the erlang code assemble it: {ok, false, Bin}
* (when complete it should return {ok, true, Bin}).
* We need to read atleast one more time to be sure if its
* done...
* 2) Or put it in a buffer here, and then let the erlang code
* know that it should call again (special return value)
* (continuous binary realloc "here").
*
* => We choose alt 1 for now.
*/
data = MKBIN(env, bufP);
return make_ok3(env, atom_false, data);
} else {
/* +++ We got exactly as much as we requested +++ */
/* <KOLLA>
* WE NEED TO INFORM ANY WAITING READERS
* </KOLLA>
*/
data = MKBIN(env, bufP);
return make_ok3(env, atom_true, data);
}
} else if (read < 0) {
/* +++ Error handling +++ */
int save_errno = sock_errno();
if (save_errno == ECONNRESET) {
/* +++ Oups - closed +++ */
/* <KOLLA>
*
* IF THE CURRENT PROCESS IS *NOT* THE CONTROLLING
* PROCESS, WE NEED TO INFORM IT!!!
*
* ALL WAITING PROCESSES MUST ALSO GET THE ERROR!!
* HANDLED BY THE STOP (CALLBACK) FUNCTION?
*
* SINCE THIS IS A REMOTE CLOSE, WE DON'T NEED TO WAIT
* FOR OUTPUT TO BE WRITTEN (NO ONE WILL READ), JUST
* ABORT THE SOCKET REGARDLESS OF LINGER???
*
* </KOLLA>
*/
descP->closeLocal = FALSE;
descP->state = SOCKET_STATE_CLOSING;
SELECT(env,
descP->sock,
(ERL_NIF_SELECT_STOP),
descP, NULL, recvRef);
return make_error(env, atom_closed);
} else if ((save_errno == ERRNO_BLOCK) ||
(save_errno == EAGAIN)) {
return make_error(env, atom_eagain);
} else {
return make_error2(env, save_errno);
}
} else {
/* +++ We did not fill the buffer +++ */
if (toRead == 0) {
/* +++ We got a chunk of data but +++
* +++ since we did not fill the +++
* +++ buffer, we must split it +++
* +++ into a sub-binary. +++
*/
data = MKBIN(env, bufP);
data = MKSBIN(env, data, 0, read);
return make_ok3(env, atom_true, data);
} else {
/* +++ We got only a part of what was expected +++
* +++ => receive more later. +++ */
return make_ok3(env, atom_false, MKBIN(env, bufP));
}
}
}
/* The recvfrom function delivers one (1) message. If our buffer
* is to small, the message will be truncated. So, regardless
* if we filled the buffer or not, we have got what we are going
* to get regarding this message.
*/
static
ERL_NIF_TERM recvfrom_check_result(ErlNifEnv* env,
SocketDescriptor* descP,
int read,
ErlNifBinary* bufP,
SocketAddress* fromAddrP,
unsigned int fromAddrLen,
ERL_NIF_TERM recvRef)
{
ERL_NIF_TERM data;
/* There is a special case: If the provided 'to read' value is
* zero (0). That means that we reads as much as we can, using
* the default read buffer size.
*/
if (read < 0) {
/* +++ Error handling +++ */
int save_errno = sock_errno();
if (save_errno == ECONNRESET) {
/* +++ Oups - closed +++ */
/* <KOLLA>
* IF THE CURRENT PROCESS IS *NOT* THE CONTROLLING
* PROCESS, WE NEED TO INFORM IT!!!
*
* ALL WAITING PROCESSES MUST ALSO GET THE ERROR!!
*
* </KOLLA>
*/
descP->closeLocal = FALSE;
descP->state = SOCKET_STATE_CLOSING;
SELECT(env,
descP->sock,
(ERL_NIF_SELECT_STOP),
descP, NULL, recvRef);
return make_error(env, atom_closed);
} else if ((save_errno == ERRNO_BLOCK) ||
(save_errno == EAGAIN)) {
return make_error(env, atom_eagain);
} else {
return make_error2(env, save_errno);
}
} else {
/* +++ We sucessfully got a message - time to encode the address +++ */
ERL_NIF_TERM fromDomainT, fromSourceT;
encode_address(env,
fromAddrP, fromAddrLen,
&fromDomainT, &fromSourceT);
if (read == bufP->size) {
data = MKBIN(env, bufP);
} else {
/* +++ We got a chunk of data but +++
* +++ since we did not fill the +++
* +++ buffer, we must split it +++
* +++ into a sub-binary. +++
*/
data = MKBIN(env, bufP);
data = MKSBIN(env, data, 0, read);
}
return make_ok2(env, MKT3(env, fromDomainT, fromSourceT, data));
}
}
/* *** decode_send_addr ***
*
* The rather odd thing about the 'toAddrP' (the **) is
* because we need to be able to return a NULL pointer,
* in the case of the dest address is the atom 'null'.
* Its possible to call the sendto function with the
* args NULL (address) and 0 (port number).
*
* This function whouls really have a char* return value
* type!!
*/
static
char* decode_send_addr(ErlNifEnv* env,
int domain,
ERL_NIF_TERM addr,
int port,
SocketAddress** toAddrP,
unsigned int* toAddrLenP)
{
if (IS_ATOM(env, addr)) {
unsigned int len;
char a[16]; // Just in case...
/* The only acceptable value is the atom 'null' */
if (!(GET_ATOM_LEN(env, addr, &len) &&
(len > 0) &&
(len <= (sizeof("null")))))
return str_einval;
if (!GET_ATOM(env, addr, a, sizeof(a)))
return str_einval;
*toAddrP = NULL;
if (strncmp(a, "null", len) == 0)
return NULL;
else
return str_einval;
} else if (IS_TUPLE(env, addr)) {
/* We now know that the we have a proper address. */
return decode_send_addr_tuple(env, domain, addr, port,
*toAddrP, toAddrLenP);
} else {
return str_einval;
}
}
static
char* decode_send_addr_tuple(ErlNifEnv* env,
int domain,
ERL_NIF_TERM addr,
int port,
SocketAddress* toAddrP,
unsigned int* toAddrLenP)
{
/* We handle two different tuples:
* - size 4 (INET)
* - size 8 (INET6)
*/
const ERL_NIF_TERM* addrt;
int addrtSz;
if (!GET_TUPLE(env, addr, &addrtSz, &addrt))
return str_einval; // PLACEHOLDER
switch (domain) {
case AF_INET:
if (addrtSz != 4)
return str_einval;
break;
#if defined(HAVE_IN6) && defined(AF_INET6)
case AF_INET6:
if (addrtSz != 8)
return str_einval;
break;
#endif
default:
return str_eafnosupport;
break;
}
return decode_address_tuple(env, domain,
addrt, port,
toAddrP, toAddrLenP);
}
/* Decode the 4- or 8-element address tuple
* and initiate the socket address structure.
*/
static
char* decode_address_tuple(ErlNifEnv* env,
int domain,
const ERL_NIF_TERM* addrt,
int port,
SocketAddress* addrP,
unsigned int* addrLenP)
{
/* We now *know* that the size of the tuple is correct,
* so we don't need to check anything here, just unpack.
*/
switch (domain) {
case AF_INET:
{
int a, v;
char laddr[4];
sys_memzero((char*)addrP, sizeof(struct sockaddr_in));
#ifndef NO_SA_LEN
addrP->sai.sin_len = sizeof(struct sockaddr_in);
#endif
addrP->sai.sin_family = domain;
addrP->sai.sin_port = sock_htons(port);
for (a = 0; a < 4; a++) {
if (!GET_INT(env, addrt[a], &v))
return str_einval;
laddr[a] = v;
}
sys_memcpy(&addrP->sai.sin_addr, &laddr, sizeof(laddr));
*addrLenP = sizeof(struct sockaddr_in);
return NULL;
}
break;
#if defined(HAVE_IN6) && defined(AF_INET6)
case AF_INET6:
{
int a, v;
char laddr[16];
sys_memzero((char*)addrP, sizeof(struct sockaddr_in6));
#ifndef NO_SA_LEN
addrP->sai6.sin6_len = sizeof(struct sockaddr_in6);
#endif
addrP->sai6.sin6_family = domain;
addrP->sai6.sin6_port = sock_htons(port);
addrP->sai6.sin6_flowinfo = 0;
/* The address tuple is of size 8
* and each element is a two byte integer
*/
for (a = 0; a < 8; a++) {
if (!GET_INT(env, addrt[a], &v))
return str_einval;
laddr[a*2 ] = ((v >> 8) & 0xFF);
laddr[a*2+1] = (v & 0xFF);
}
sys_memcpy(&addrP->sai6.sin6_addr, &laddr, sizeof(laddr));
*addrLenP = sizeof(struct sockaddr_in6);
return NULL;
}
break;
#endif
} /* switch (domain) */
return str_eafnosupport;
}
/* Encode the 4- or 8-element address tuple from the socket address structure.
*
* This function is called when we have received a message. So, if we for some
* reason fail to decode the address or parts of it, it makes more sense to
* return with "undefined" for the values rather then fail completely (and not
* deliver the received message).
*
* Returns two things (assuming the encode works):
*
* Domain: inet | inet6 | local
* Source: {Address, Port} | string()
*
*/
static
void encode_address(ErlNifEnv* env,
SocketAddress* addrP,
unsigned int addrLen,
ERL_NIF_TERM* domainT,
ERL_NIF_TERM* sourceT)
{
short port;
switch (addrP->sa.sa_family) {
/* +++ inet (IPv4) +++ */
case AF_INET:
if (addrLen >= sizeof(struct sockaddr_in)) {
ERL_NIF_TERM addrT, portT;
unsigned int i;
ERL_NIF_TERM at4[4];
char* a4 = (char*) &addrP->sai.sin_addr;
port = sock_ntohs(addrP->sai.sin_port);
for (i = 0; i < 4; i++) {
at4[i] = MKI(env, a4[i]);
}
*domainT = MKA(env, "inet"); // Shall we encode these? See decode
addrT = MKT4(env, at4[0], at4[1], at4[2], at4[3]);
portT = MKI(env, port);
*sourceT = MKT2(env, addrT, portT);
} else {
*domainT = atom_undefined;
*sourceT = atom_undefined;
}
break;
/* +++ inet6 (IPv6) +++ */
#if defined(HAVE_IN6) && defined(AF_INET6)
case AF_INET6:
if (addrLen >= sizeof(struct sockaddr_in6)) {
ERL_NIF_TERM addrT, portT;
unsigned int i;
ERL_NIF_TERM at6[8];
char* a16 = (char*) &addrP->sai6.sin6_addr;
port = sock_ntohs(addrP->sai6.sin6_port);
/* The address tuple is of size 8
* and each element is a two byte integer
*/
for (i = 0; i < 8; i++) {
// at6[i] = MKI(env, get_int16(a16[i*2]));
at6[i] = MKI(env, get_int16(a16 + i*2));
}
*domainT = MKA(env, "inet6"); // Shall we encode these? See decode
addrT = MKT8(env,
at6[0], at6[1], at6[2], at6[3],
at6[4], at6[5], at6[6], at6[7]);
portT = MKI(env, port);
*sourceT = MKT2(env, addrT, portT);
} else {
*domainT = atom_undefined;
*sourceT = atom_undefined;
}
break;
#endif
/* +++ local (Unix Domain Sockets) +++ */
#ifdef HAVE_SYS_UN_H
case AF_UNIX:
{
size_t n, m;
*domainT = MKA(env, "local");
if (addrLen < offsetof(struct sockaddr_un, sun_path)) {
*sourceT = atom_undefined;
} else {
n = addrLen - offsetof(struct sockaddr_un, sun_path);
if (255 < n) {
*sourceT = atom_undefined;
} else {
m = my_strnlen(addrP->sal.sun_path, n);
#ifdef __linux__
/* Assume that the address is a zero terminated string,
* except when the first byte is \0 i.e the string length is 0,
* then use the reported length instead.
* This fix handles Linux's nonportable
* abstract socket address extension.
*/
if (m == 0) {
m = n;
}
#endif
*sourceT = MKSL(env, addrP->sal.sun_path, m);
}
}
}
break;
#endif
default:
*domainT = atom_undefined;
*sourceT = atom_undefined;
break;
} /* switch (addrP->sa.sa_family) */
}
/* Decode the address when its an atom.
* Currently we only accept two atoms: 'any' and 'loopback'
*/
static
char* decode_address_atom(ErlNifEnv* env,
int domain,
char* addr,
int addrLen,
int port,
SocketAddress* addrP,
unsigned int* addrLenP)
{
BOOLEAN_T any;
if (strncmp(addr, "any", addrLen) == 0) {
any = TRUE;
} if (strncmp(addr, "loopback", addrLen) == 0) {
any = FALSE;
} else {
return str_einval;
}
/* If we get this far, we *know* its either 'any' or 'loopback' */
switch (domain) {
case AF_INET:
{
struct in_addr addr;
if (any) {
addr.s_addr = sock_htonl(INADDR_ANY);
} else {
addr.s_addr = sock_htonl(INADDR_LOOPBACK);
}
sys_memzero((char*) addrP, sizeof(struct sockaddr_in));
#ifndef NO_SA_LEN
addrP->sai.sin_len = sizeof(struct sockaddr_in6);
#endif
addrP->sai.sin_family = domain;
addrP->sai.sin_port = sock_htons(port);
addrP->sai.sin_addr.s_addr = addr.s_addr;
*addrLenP = sizeof(struct sockaddr_in);
}
break;
#if defined(HAVE_IN6) && defined(AF_INET6)
case AF_INET6:
{
const struct in6_addr* paddr;
if (any) {
paddr = &in6addr_any;
} else {
paddr = &in6addr_loopback;
}
sys_memzero((char*)addrP, sizeof(struct sockaddr_in6));
#ifndef NO_SA_LEN
addrP->sai6.sin6_len = sizeof(struct sockaddr_in6);
#endif
addrP->sai6.sin6_family = domain;
addrP->sai6.sin6_port = sock_htons(port);
addrP->sai6.sin6_flowinfo = 0;
addrP->sai6.sin6_addr = *paddr;
*addrLenP = sizeof(struct sockaddr_in6);
}
break;
#endif
default:
return str_einval;
break;
}
return NULL;
}
static
BOOLEAN_T decode_bool(ErlNifEnv* env, ERL_NIF_TERM eVal, BOOLEAN_T* val)
{
unsigned int len;
char b[16]; // Just in case...
/* Verify that the value is actually an atom */
if (!IS_ATOM(env, eVal))
return FALSE;
/* Verify that the value is of acceptable length */
if (!(GET_ATOM_LEN(env, eVal, &len) &&
(len > 0) &&
(len <= sizeof("false"))))
return FALSE;
/* And finally try to extract the value */
if (!GET_ATOM(env, eVal, b, sizeof(b)))
return FALSE;
if (strncmp(b, "true", len) == 0)
*val = TRUE;
else
*val = FALSE;
return TRUE;
}
/* +++ decode the linger value +++
* The (socket) linger option is provided as a two tuple:
*
* {OnOff :: boolean(), Time :: integer()}
*
*/
static
BOOLEAN_T decode_sock_linger(ErlNifEnv* env, ERL_NIF_TERM eVal, struct linger* valP)
{
const ERL_NIF_TERM* lt; // The array of the elements of the tuple
int sz; // The size of the tuple - should be 2
BOOLEAN_T onOff;
int secs;
if (!GET_TUPLE(env, eVal, &sz, <))
return FALSE;
if (sz != 2)
return FALSE;
/* So fas so good - now check the two elements of the tuple. */
if (!decode_bool(env, lt[0], &onOff))
return FALSE;
if (!GET_INT(env, lt[1], &secs))
return FALSE;
valP->l_onoff = (onOff) ? 1 : 0;
valP->l_linger = secs;
return TRUE;
}
/* +++ decocde the ip socket option tos +++
* The (ip) option can be provide in two ways:
*
* atom() | integer()
*
* When its an atom it can have the values:
*
* lowdelay | throughput | reliability | mincost
*
*/
static
BOOLEAN_T decode_ip_tos(ErlNifEnv* env, ERL_NIF_TERM eVal, int* val)
{
BOOLEAN_T result = FALSE;
if (IS_ATOM(env, eVal)) {
unsigned int len;
char b[sizeof("reliability")+1]; // Just in case...
if (!(GET_ATOM_LEN(env, eVal, &len) &&
(len > 0) &&
(len <= (sizeof("reliability"))))) {
*val = -1;
return FALSE;
}
if (!GET_ATOM(env, eVal, b, sizeof(b))) {
*val = -1;
return FALSE;
}
if (strncmp(b, "lowdelay", len) == 0) {
*val = IPTOS_LOWDELAY;
result = TRUE;
} else if (strncmp(b, "throughput", len) == 0) {
*val = IPTOS_THROUGHPUT;
result = TRUE;
} else if (strncmp(b, "reliability", len) == 0) {
*val = IPTOS_RELIABILITY;
result = TRUE;
} else if (strncmp(b, "mincost", len) == 0) {
*val = IPTOS_MINCOST;
result = TRUE;
} else {
*val = -1;
result = FALSE;
}
} else if (IS_NUM(env, eVal)) {
if (GET_INT(env, eVal, val)) {
result = TRUE;
} else {
*val = -1;
result = FALSE;
}
} else {
*val = -1;
result = FALSE;
}
return result;
}
/* *** alloc_descriptor ***
* Allocate and perform basic initialization of a socket descriptor.
*
*/
static
SocketDescriptor* alloc_descriptor(SOCKET sock, HANDLE event)
{
SocketDescriptor* descP;
if ((descP = enif_alloc_resource(sockets, sizeof(SocketDescriptor))) != NULL) {
char buf[64]; /* Buffer used for building the mutex name */
sprintf(buf, "socket[w,%d]", sock);
descP->writeMtx = MCREATE(buf);
descP->currentWriterP = NULL; // currentWriter not used
descP->writersQ.first = NULL;
descP->writersQ.last = NULL;
descP->isWritable = TRUE;
descP->writePkgCnt = 0;
descP->writeByteCnt = 0;
descP->writeTries = 0;
descP->writeWaits = 0;
descP->writeFails = 0;
sprintf(buf, "socket[r,%d]", sock);
descP->readMtx = MCREATE(buf);
descP->currentReaderP = NULL; // currentReader not used
descP->readersQ.first = NULL;
descP->readersQ.last = NULL;
descP->isReadable = TRUE;
descP->readPkgCnt = 0;
descP->readByteCnt = 0;
descP->readTries = 0;
descP->readWaits = 0;
sprintf(buf, "socket[acc,%d]", sock);
descP->accMtx = MCREATE(buf);
descP->currentAcceptorP = NULL; // currentAcceptor not used
descP->acceptorsQ.first = NULL;
descP->acceptorsQ.last = NULL;
sprintf(buf, "socket[close,%d]", sock);
descP->closeMtx = MCREATE(buf);
descP->rBufSz = SOCKET_RECV_BUFFER_SIZE_DEFAULT;
descP->iow = FALSE;
descP->dbg = SOCKET_DEBUG_DEFAULT;
descP->sock = sock;
descP->event = event;
}
return descP;
}
/* compare_pids - Test if two pids are equal
*
*/
static
int compare_pids(ErlNifEnv* env,
const ErlNifPid* pid1,
const ErlNifPid* pid2)
{
ERL_NIF_TERM p1 = enif_make_pid(env, pid1);
ERL_NIF_TERM p2 = enif_make_pid(env, pid2);
return enif_is_identical(p1, p2);
}
/* 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
/* esendflags2sendflags - convert internal (erlang) send flags to (proper)
* send flags.
*/
static
BOOLEAN_T esendflags2sendflags(unsigned int esendflags, int* sendflags)
{
unsigned int ef;
int tmp = 0;
for (ef = SOCKET_SEND_FLAG_LOW; ef <= SOCKET_SEND_FLAG_HIGH; ef++) {
switch (ef) {
case SOCKET_SEND_FLAG_CONFIRM:
tmp |= MSG_CONFIRM;
break;
case SOCKET_SEND_FLAG_DONTROUTE:
tmp |= MSG_DONTROUTE;
break;
case SOCKET_SEND_FLAG_EOR:
tmp |= MSG_EOR;
break;
case SOCKET_SEND_FLAG_MORE:
tmp |= MSG_MORE;
break;
case SOCKET_SEND_FLAG_NOSIGNAL:
tmp |= MSG_NOSIGNAL;
break;
case SOCKET_SEND_FLAG_OOB:
tmp |= MSG_OOB;
break;
default:
return FALSE;
}
}
*sendflags = tmp;
return TRUE;
}
/* erecvflags2recvflags - convert internal (erlang) send flags to (proper)
* send flags.
*/
static
BOOLEAN_T erecvflags2recvflags(unsigned int erecvflags, int* recvflags)
{
unsigned int ef;
int tmp = 0;
for (ef = SOCKET_RECV_FLAG_LOW; ef <= SOCKET_RECV_FLAG_HIGH; ef++) {
switch (ef) {
case SOCKET_RECV_FLAG_CMSG_CLOEXEC:
tmp |= MSG_CMSG_CLOEXEC;
break;
case SOCKET_RECV_FLAG_ERRQUEUE:
tmp |= MSG_ERRQUEUE;
break;
case SOCKET_RECV_FLAG_OOB:
tmp |= MSG_OOB;
break;
case SOCKET_RECV_FLAG_PEEK:
tmp |= MSG_PEEK;
break;
case SOCKET_RECV_FLAG_TRUNC:
tmp |= MSG_TRUNC;
break;
default:
return FALSE;
}
}
*recvflags = tmp;
return TRUE;
}
/* eproto2proto - convert internal (erlang) protocol to (proper) protocol
*
* Note that only a subset is supported.
*/
static
BOOLEAN_T ehow2how(unsigned int ehow, int* how)
{
switch (ehow) {
case SOCKET_SHUTDOWN_HOW_RD:
*how = SHUT_RD;
break;
case SOCKET_SHUTDOWN_HOW_WR:
*how = SHUT_WR;
break;
case SOCKET_SHUTDOWN_HOW_RDWR:
*how = SHUT_RDWR;
break;
default:
return FALSE;
}
return TRUE;
}
#if defined(HAVE_SYS_UN_H) || defined(SO_BINDTODEVICE)
/* strnlen doesn't exist everywhere */
static
size_t my_strnlen(const char *s, size_t maxlen)
{
size_t i = 0;
while (i < maxlen && s[i] != '\0')
i++;
return i;
}
#endif
/* Create an ok two (2) tuple in the form: {ok, Any}.
* The second element (Any) is already in the form of an
* ERL_NIF_TERM so all we have to do is create the tuple.
*/
static
ERL_NIF_TERM make_ok2(ErlNifEnv* env, ERL_NIF_TERM any)
{
return MKT2(env, atom_ok, any);
}
/* Create an ok three (3) tuple in the form: {ok, Val1, Val2}.
* The second (Val1) and third (Val2) elements are already in
* the form of an ERL_NIF_TERM so all we have to do is create
* the tuple.
*/
static
ERL_NIF_TERM make_ok3(ErlNifEnv* env, ERL_NIF_TERM val1, ERL_NIF_TERM val2)
{
return MKT3(env, atom_ok, val1, val2);
}
/* Create an error two (2) tuple in the form: {error, Reason}.
* The second element (Reason) is already in the form of an
* ERL_NIF_TERM so all we have to do is create the tuple.
*/
static
ERL_NIF_TERM make_error(ErlNifEnv* env, ERL_NIF_TERM reason)
{
return MKT2(env, atom_error, reason);
}
/* Create an error two (2) tuple in the form: {error, Reason}.
* The second element, Reason, is a string to be converted into
* an atom.
*/
static
ERL_NIF_TERM make_error1(ErlNifEnv* env, char* reason)
{
return make_error(env, MKA(env, reason));
}
/* Create an error two (2) tuple in the form: {error, Reason}.
* The second element, Reason, is the errno value in its
* basic form (integer) which will (eventually) be converted
* into an atom.
*/
static
ERL_NIF_TERM make_error2(ErlNifEnv* env, int err)
{
return make_error1(env, erl_errno_id(err));
}
/* Send an error closed message to the specified process:
*
* This message is for processes that are waiting in the
* erlang API functions for a select message.
*/
static
char* send_msg_error_closed(ErlNifEnv* env,
ErlNifPid* pid)
{
return send_msg_error(env, atom_closed, pid);
}
/* Send an error message to the specified process:
* A message in the form:
*
* {error, Reason}
*
* This message is for processes that are waiting in the
* erlang API functions for a select message.
*/
static
char* send_msg_error(ErlNifEnv* env,
ERL_NIF_TERM reason,
ErlNifPid* pid)
{
ERL_NIF_TERM msg = enif_make_tuple2(env, atom_error, reason);
return send_msg(env, msg, pid);
}
/* Send an (nif-) abort message to the specified process:
* A message in the form:
*
* {nif_abort, Ref, Reason}
*
* This message is for processes that are waiting in the
* erlang API functions for a select message.
*/
static
char* send_msg_nif_abort(ErlNifEnv* env,
ERL_NIF_TERM ref,
ERL_NIF_TERM reason,
ErlNifPid* pid)
{
ERL_NIF_TERM msg = MKT3(env, atom_nif_abort, ref, reason);
return send_msg(env, msg, pid);
}
/* Send a message to the specified process.
*/
static
char* send_msg(ErlNifEnv* env,
ERL_NIF_TERM msg,
ErlNifPid* pid)
{
if (!enif_send(env, pid, NULL, msg))
return str_exsend;
else
return NULL;
}
static
void xabort(const char* expr,
const char* func,
const char* file,
int line)
{
fflush(stdout);
fprintf(stderr, "%s:%d:%s() Assertion failed: %s\n",
file, line, func, expr);
fflush(stderr);
abort();
}
/* ----------------------------------------------------------------------
* C o u n t e r F u n c t i o n s
* ----------------------------------------------------------------------
*/
static
BOOLEAN_T cnt_inc(uint32_t* cnt, uint32_t inc)
{
BOOLEAN_T wrap;
uint32_t max = 0xFFFFFFFF;
uint32_t current = *cnt;
if ((max - inc) >= current) {
*cnt += inc;
wrap = FALSE;
} else {
*cnt = inc - (max - current) - 1;
wrap = TRUE;
}
return (wrap);
}
/* ----------------------------------------------------------------------
* C a l l b a c k F u n c t i o n s
* ----------------------------------------------------------------------
*/
/* =========================================================================
* socket_dtor - Callback function for resource destructor
*
*/
static
void socket_dtor(ErlNifEnv* env, void* obj)
{
SocketDescriptor* descP = (SocketDescriptor*) obj;
MDESTROY(descP->writeMtx);
MDESTROY(descP->readMtx);
MDESTROY(descP->accMtx);
MDESTROY(descP->closeMtx);
}
/* =========================================================================
* socket_stop - Callback function for resource stop
*
* When the socket is stopped, we need to inform:
*
* * the controlling process
* * the current writer and any waiting writers
* * the current reader and any waiting readers
* * the current acceptor and any waiting acceptor
*
* Also, make sure no process gets the message twice
* (in case it is, for instance, both controlling process
* and a writer).
*
* <KOLLA>
*
* We do not handle linger-issues yet! So anything in the out
* buffers will be left for the OS to solve...
* Do we need a special "close"-thread? Dirty scheduler?
*
* What happens if we are "stopped" for another reason then 'close'?
* For instance, down?
*
* </KOLLA>
*/
static
void socket_stop(ErlNifEnv* env, void* obj, int fd, int is_direct_call)
{
SocketDescriptor* descP = (SocketDescriptor*) obj;
MLOCK(descP->writeMtx);
MLOCK(descP->readMtx);
MLOCK(descP->accMtx);
MLOCK(descP->closeMtx);
descP->state = SOCKET_STATE_CLOSING; // Just in case...???
descP->isReadable = FALSE;
descP->isWritable = FALSE;
/* We should check that we actually have a monitor.
* This *should* be done with a "NULL" monitor value,
* which there currently is none...
*/
DEMONP(env, descP, &descP->ctrlMon);
if (descP->currentWriterP != NULL) {
/* We have a (current) writer and *may* therefor also have
* writers waiting.
*/
SASSERT( (NULL == send_msg_nif_abort(env,
descP->currentWriter.ref,
atom_closed,
&descP->currentWriter.pid)) );
/* And also deal with the waiting writers (in the same way) */
inform_waiting_procs(env, descP, &descP->writersQ, TRUE, atom_closed);
}
if (descP->currentReaderP != NULL) {
/* We have a (current) reader and *may* therefor also have
* readers waiting.
*/
SASSERT( (NULL == send_msg_nif_abort(env,
descP->currentReader.ref,
atom_closed,
&descP->currentReader.pid)) );
/* And also deal with the waiting readers (in the same way) */
inform_waiting_procs(env, descP, &descP->readersQ, TRUE, atom_closed);
}
if (descP->currentAcceptorP != NULL) {
/* We have a (current) acceptor and *may* therefor also have
* acceptors waiting.
*/
SASSERT( (NULL == send_msg_nif_abort(env,
descP->currentAcceptor.ref,
atom_closed,
&descP->currentAcceptor.pid)) );
/* And also deal with the waiting acceptors (in the same way) */
inform_waiting_procs(env, descP, &descP->acceptorsQ, TRUE, atom_closed);
}
if (descP->sock != INVALID_SOCKET) {
/*
* <KOLLA>
*
* WE NEED TO CHECK IF THIS OPERATION IS TRIGGERED
* LOCALLY (VIA A CALL TO CLOSE) OR REMOTELLY
* (VIA I.E. ECONSRESET).
*
* </KOLLA>
*/
if (descP->closeLocal) {
/* +++ send close message to the waiting process +++
*
* {close, CloseRef}
*
* <KOLLA>
*
* WHAT HAPPENS IF THE RECEIVER HAS DIED IN THE MEANTIME????
*
* </KOLLA>
*/
send_msg(env, MKT2(env, atom_close, descP->closeRef), &descP->closerPid);
DEMONP(env, descP, &descP->closerMon);
} else {
/*
* <KOLLA>
*
* ABORT?
*
* </KOLLA>
*/
}
}
MUNLOCK(descP->closeMtx);
MUNLOCK(descP->accMtx);
MUNLOCK(descP->readMtx);
MUNLOCK(descP->writeMtx);
}
/* This function traverse the queue and sends the specified
* nif_abort message with the specified reason to each member,
* and if the 'free' argument is TRUE, the queue will be emptied.
*/
static
void inform_waiting_procs(ErlNifEnv* env,
SocketDescriptor* descP,
SocketRequestQueue* q,
BOOLEAN_T free,
ERL_NIF_TERM reason)
{
SocketRequestQueueElement* currentP = q->first;
SocketRequestQueueElement* nextP;
while (currentP != NULL) {
/* <KOLLA>
*
* Should we inform anyone if we fail to demonitor?
* NOT SURE WHAT THAT WOULD REPRESENT AND IT IS NOT
* IMPORTANT IN *THIS* CASE, BUT ITS A FUNDAMENTAL OP...
*
* </KOLLA>
*/
SASSERT( (NULL == send_msg_nif_abort(env,
currentP->data.ref,
reason,
¤tP->data.pid)) );
DEMONP(env, descP, ¤tP->data.mon);
nextP = currentP->nextP;
if (free) FREE(currentP);
currentP = nextP;
}
if (free) {
q->first = NULL;
q->last = NULL;
}
}
/* =========================================================================
* 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, 0},
{"nif_info", 0, nif_info, 0},
// {"nif_debug", 1, nif_debug_, 0},
// The proper "socket" interface
{"nif_open", 4, nif_open, 0},
{"nif_bind", 3, nif_bind, 0},
{"nif_connect", 3, nif_connect, 0},
{"nif_listen", 2, nif_listen, 0},
{"nif_accept", 2, nif_accept, 0},
{"nif_send", 4, nif_send, 0},
{"nif_sendto", 6, nif_sendto, 0},
{"nif_recv", 4, nif_recv, 0},
{"nif_recvfrom", 2, nif_recvfrom, 0},
{"nif_close", 1, nif_close, 0},
{"nif_shutdown", 2, nif_shutdown, 0},
{"nif_setopt", 3, nif_setopt, 0},
{"nif_getopt", 2, nif_getopt, 0},
/* Misc utility functions */
{"nif_link_if2idx", 1, nif_link_if2idx, 0},
{"nif_link_idx2if", 1, nif_link_idx2if, 0},
{"nif_link_ifs", 0, nif_link_ifs, 0},
/* "Extra" functions to "complete" the socket interface.
* For instance, the function nif_finalize_connection
* is called after the connect *select* has "completed".
*/
{"nif_finalize_connection", 1, nif_finalize_connection, 0},
{"nif_cancel", 2, nif_cancel, 0},
{"nif_finalize_close", 1, nif_finalize_close, ERL_NIF_DIRTY_JOB_IO_BOUND}
};
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;
}
static
BOOLEAN_T extract_iow_on_load(ErlNifEnv* env, ERL_NIF_TERM map, BOOLEAN_T def)
{
ERL_NIF_TERM iowKey = enif_make_atom(env, "iow");
ERL_NIF_TERM iowVal;
unsigned int len;
char b[16]; // Just in case...
/* Extra the value of the debug property */
if (!extract_item_on_load(env, map, iowKey, &iowVal))
return def;
/* Verify that the value is actually an atom */
if (!enif_is_atom(env, iowVal))
return def;
/* Verify that the value is of acceptable length */
if (!(GET_ATOM_LEN(env, iowVal, &len) &&
(len > 0) &&
(len <= sizeof("false"))))
return def;
/* And finally try to extract the value */
if (!GET_ATOM(env, iowVal, b, sizeof(b)))
return def;
if (strncmp(b, "true", len) == 0)
return TRUE;
else
return FALSE;
}
/* =======================================================================
* load_info - A map of misc info (e.g global debug)
*/
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);
/* +++ Global Counters +++ */
socketData.cntMtx = MCREATE("socket[gcnt]");
socketData.iow = extract_iow_on_load(env,
load_info,
SOCKET_NIF_IOW_DEFAULT);
socketData.numSockets = 0;
socketData.numTypeDGrams = 0;
socketData.numTypeStreams = 0;
socketData.numTypeSeqPkg = 0;
socketData.numDomainLocal = 0;
socketData.numDomainInet = 0;
socketData.numDomainInet6 = 0;
socketData.numProtoIP = 0;
socketData.numProtoTCP = 0;
socketData.numProtoUDP = 0;
socketData.numProtoSCTP = 0;
/* +++ Misc atoms +++ */
// atom_active = MKA(env, str_active);
// atom_active_n = MKA(env, str_active_n);
// atom_active_once = MKA(env, str_active_once);
// atom_binary = MKA(env, str_binary);
// atom_buildDate = MKA(env, str_buildDate);
atom_close = MKA(env, str_close);
atom_closed = MKA(env, str_closed);
atom_closing = MKA(env, str_closing);
atom_error = MKA(env, str_error);
atom_false = MKA(env, str_false);
// atom_list = MKA(env, str_list);
// atom_mode = MKA(env, str_mode);
atom_nif_abort = MKA(env, str_nif_abort);
atom_ok = MKA(env, str_ok);
// atom_once = MKA(env, str_once);
// atom_passive = MKA(env, str_passive);
// atom_receiver = MKA(env, str_receiver);
atom_select = MKA(env, str_select);
// atom_tcp_closed = MKA(env, str_tcp_closed);
atom_timeout = MKA(env, str_timeout);
atom_true = MKA(env, str_true);
atom_undefined = MKA(env, str_undefined);
// atom_version = MKA(env, str_version);
/* Error codes */
atom_eagain = MKA(env, str_eagain);
atom_eafnosupport = MKA(env, str_eafnosupport);
atom_einval = MKA(env, str_einval);
atom_eisconn = MKA(env, str_eisconn);
atom_enotclosing = MKA(env, str_enotclosing);
atom_enotconn = MKA(env, str_enotconn);
atom_exalloc = MKA(env, str_exalloc);
atom_exbadstate = MKA(env, str_exbadstate);
atom_exbusy = MKA(env, str_exbusy);
// atom_exnotopen = MKA(env, str_exnotopen);
atom_exmon = MKA(env, str_exmon);
atom_exself = MKA(env, str_exself);
atom_exsend = MKA(env, str_exsend);
// For storing "global" things...
// socketData.env = enif_alloc_env(); // We should really check
// socketData.version = MKA(env, ERTS_VERSION);
// socketData.buildDate = MKA(env, ERTS_BUILD_DATE);
sockets = enif_open_resource_type_x(env,
"sockets",
&socketInit,
ERL_NIF_RT_CREATE,
NULL);
return !sockets;
}
ERL_NIF_INIT(socket, socket_funcs, on_load, NULL, NULL, NULL)