aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMicael Karlberg <[email protected]>2018-05-04 16:00:26 +0200
committerMicael Karlberg <[email protected]>2018-09-18 13:01:37 +0200
commit8e14247bc5faf5abf67901b6ae5028f2b1897c65 (patch)
treed2b1f37fcd880862d99a92f699a197387a3b727d
parentb69436fc5970ff8fc749a37351e9d9c5a54f445e (diff)
downloadotp-8e14247bc5faf5abf67901b6ae5028f2b1897c65.tar.gz
otp-8e14247bc5faf5abf67901b6ae5028f2b1897c65.tar.bz2
otp-8e14247bc5faf5abf67901b6ae5028f2b1897c65.zip
[socket-nif] Preliminary setopt
*Very* partial setopt implementation.
-rw-r--r--erts/emulator/nifs/common/socket_nif.c1029
-rw-r--r--erts/preloaded/src/socket.erl721
2 files changed, 1660 insertions, 90 deletions
diff --git a/erts/emulator/nifs/common/socket_nif.c b/erts/emulator/nifs/common/socket_nif.c
index fdae18a513..532ac5c211 100644
--- a/erts/emulator/nifs/common/socket_nif.c
+++ b/erts/emulator/nifs/common/socket_nif.c
@@ -59,6 +59,7 @@
#include <ctype.h>
#include <sys/types.h>
#include <errno.h>
+#include <netinet/ip.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
@@ -338,6 +339,26 @@ typedef union {
#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 0
+#define SOCKET_OPT_SOCK_LINGER 1
+#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 0
+#define SOCKET_OPT_TCP_MAXSEG 0
+
+
/* =================================================================== *
* *
* Various enif macros *
@@ -407,7 +428,7 @@ typedef union {
#define sock_connect(s, addr, len) connect((s), (addr), (len))
#define sock_create_event(s) WSACreateEvent()
#define sock_errno() WSAGetLastError()
-#define sock_getopt(s,t,n,v,l) getsockopt((s),(t),(n),(v),(l))
+#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))
@@ -421,6 +442,7 @@ typedef union {
#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))
@@ -458,6 +480,7 @@ static unsigned long one_value = 1;
#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__ */
@@ -570,6 +593,27 @@ typedef struct {
} 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
+
+typedef struct {
+ unsigned int tag;
+ union {
+ BOOLEAN_T boolVal;
+ int intVal;
+ struct linger lingerVal;
+ ErlNifBinary binVal;
+ } 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?)
*/
@@ -650,12 +694,12 @@ static ERL_NIF_TERM nif_close(ErlNifEnv* env,
static ERL_NIF_TERM nif_shutdown(ErlNifEnv* env,
int argc,
const ERL_NIF_TERM argv[]);
-static ERL_NIF_TERM nif_setsockopt(ErlNifEnv* env,
- int argc,
- const ERL_NIF_TERM argv[]);
-static ERL_NIF_TERM nif_getsockopt(ErlNifEnv* env,
- int argc,
- const ERL_NIF_TERM argv[]);
+static ERL_NIF_TERM nif_setopt(ErlNifEnv* env,
+ int argc,
+ const ERL_NIF_TERM argv[]);
+static ERL_NIF_TERM nif_getopt(ErlNifEnv* env,
+ int argc,
+ const ERL_NIF_TERM argv[]);
static ERL_NIF_TERM nif_finalize_connection(ErlNifEnv* env,
int argc,
const ERL_NIF_TERM argv[]);
@@ -718,6 +762,22 @@ static ERL_NIF_TERM nclose(ErlNifEnv* env,
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 send_check_result(ErlNifEnv* env,
SocketDescriptor* descP,
@@ -789,6 +849,15 @@ static void encode_address(ErlNifEnv* env,
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,
@@ -796,6 +865,12 @@ static void inform_waiting_procs(ErlNifEnv* env,
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);
@@ -812,6 +887,62 @@ 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);
@@ -989,8 +1120,8 @@ static SocketData socketData;
*
* And some functions to manipulate and retrieve socket options:
* -------------------------------------------------------------
- * nif_setsockopt/3
- * nif_getsockopt/2
+ * nif_setopt/3
+ * nif_getopt/2
*
* And some socket admin functions:
* -------------------------------------------------------------
@@ -2829,6 +2960,697 @@ ERL_NIF_TERM nshutdown(ErlNifEnv* env,
}
+
+
+/* ----------------------------------------------------------------------
+ * 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;
+
+ 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;
+ result = TRUE;
+ } else {
+ result = FALSE;
+ }
+ *opt = eOpt;
+ }
+ 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
+
+ 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_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 {
+ return FALSE;
+ }
+ break;
+#endif
+
+ default:
+ *opt = -1;
+ valP->tag = SOCKET_OPT_VALUE_UNDEF;
+ return FALSE;
+ }
+}
+
+
+
+/* +++ decode UDP socket options +++
+ * Currently there are no such options, so this function
+ * is just a placeholder!
+ */
+static
+BOOLEAN_T eoptval2optval_udp(ErlNifEnv* env,
+ int eOpt,
+ ERL_NIF_TERM eVal,
+ int* opt,
+ SocketOptValue* valP)
+{
+ switch (eOpt) {
+ 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))
+ return FALSE; // PLACEHOLDER - We should really be more informative
+ valP->tag = SOCKET_OPT_VALUE_INT;
+ *opt = SCTP_AUTOCLOSE;
+ return TRUE;
+ 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;
+}
+
+
/* ----------------------------------------------------------------------
* U t i l i t y F u n c t i o n s
* ----------------------------------------------------------------------
@@ -3468,6 +4290,139 @@ char* decode_address_atom(ErlNifEnv* env,
}
+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, &lt))
+ 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.
@@ -4232,8 +5187,8 @@ ErlNifFunc socket_funcs[] =
{"nif_recvfrom", 2, nif_recvfrom, 0},
{"nif_close", 1, nif_close, 0},
{"nif_shutdown", 2, nif_shutdown, 0},
- {"nif_setsockopt", 3, nif_setsockopt, 0},
- {"nif_getsockopt", 2, nif_getsockopt, 0},
+ {"nif_setopt", 3, nif_setopt, 0},
+ {"nif_getopt", 2, nif_getopt, 0},
/* "Extra" functions to "complete" the socket interface.
* For instance, the function nif_finalize_connection
@@ -4260,38 +5215,38 @@ BOOLEAN_T extract_item_on_load(ErlNifEnv* env,
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...
+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;
+ /* 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 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;
+ /* 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;
+ /* 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;
+ if (strncmp(d, "true", len) == 0)
+ return TRUE;
+ else
+ return FALSE;
- }
+}
static
diff --git a/erts/preloaded/src/socket.erl b/erts/preloaded/src/socket.erl
index 8dce86c518..1090380769 100644
--- a/erts/preloaded/src/socket.erl
+++ b/erts/preloaded/src/socket.erl
@@ -20,6 +20,8 @@
-module(socket).
+-compile({no_auto_import,[error/1]}).
+
%% Administrative and "global" utility functions
-export([
on_load/0, on_load/1, on_load/2,
@@ -68,7 +70,18 @@
send_flags/0,
send_flag/0,
- shutdown_how/0
+ shutdown_how/0,
+
+ sockopt_level/0,
+ otp_socket_option/0,
+ socket_option/0,
+ ip_socket_option/0,
+ ipv6_socket_option/0,
+ tcp_socket_option/0,
+ udp_socket_option/0,
+ sctp_socket_option/0,
+
+ ip_tos_flag/0
]).
@@ -108,12 +121,189 @@
%% otp - The option is internal to our (OTP) imeplementation.
%% socket - The socket layer (SOL_SOCKET).
-%% ip - The IP layer (SOL_IP).
+%% ip - The IP layer (SOL_IP or is it IPPROTO_IP?).
%% ipv6 - The IPv6 layer (SOL_IPV6).
%% tcp - The TCP (Transport Control Protocol) layer (IPPROTO_TCP).
%% udp - The UDP (User Datagram Protocol) layer (IPPROTO_UDP).
+%% sctp - The SCTP (Stream Control Transmission Protocol) layer (IPPROTO_SCTP).
%% Int - Raw level, sent down and used "as is".
--type option_level() :: otp | socket | ip | ipv6 | tcp | udp | non_neg_integer().
+-type sockopt_level() :: otp |
+ socket |
+ ip | ipv6 | tcp | udp | sctp |
+ non_neg_integer().
+
+%% There are some options that are 'read-only'.
+%% Should those be included here or in a special list?
+%% Should we just document it and leave it to the user?
+%% Or catch it in the encode functions?
+%% A setopt for a readonly option leads to einval?
+
+-type otp_socket_option() :: debug |
+ iow |
+ rcvbuf |
+ sndbuf.
+%% Shall we have special treatment of linger??
+%% read-only options:
+%% domain | protocol | type.
+%% FreeBSD (only?): acceptfilter
+-type socket_option() :: acceptconn |
+ acceptfilter |
+ bindtodevice |
+ broadcast |
+ busy_poll |
+ debug |
+ dontroute |
+ error |
+ keepalive |
+ linger |
+ mark |
+ oobinline |
+ passcred |
+ peek_off |
+ peek_cred |
+ priority |
+ rcvbuf |
+ rcvbufforce |
+ rcvlowat | sndlowat |
+ rcvtimeo | sndtimeo |
+ reuseaddr |
+ reuseport |
+ rxq_ovfl |
+ setfib |
+ sndbuf |
+ sndbufforce |
+ timestamp |
+ type.
+%% Read-only options:
+%% mtu
+%%
+%% Options only valid for RAW sockets:
+%% nodefrag
+-type ip_socket_option() :: add_membership |
+ add_source_membership |
+ block_source |
+ dont_frag |
+ drop_membership |
+ drop_source_membership |
+ freebind |
+ hdrincl |
+ minttl |
+ msfilter |
+ mtu |
+ mtu_discover |
+ multicast_all |
+ multicast_if |
+ multicast_loop |
+ multicast_ttl |
+ nodefrag |
+ options |
+ pktinfo |
+ recverr |
+ recvif |
+ recvdstaddr |
+ recvopts |
+ recvorigdstaddr |
+ recvtos |
+ recvttl |
+ retopts |
+ router_alert |
+ sndsrcaddr |
+ tos |
+ transparent |
+ ttl |
+ unblock_source.
+-type ipv6_socket_option() ::
+ addform |
+ add_membership | drop_membership |
+ authhdr |
+ auth_level |
+ checksum |
+ dstopts |
+ esp_trans_level |
+ esp_network_level |
+ faith |
+ flowinfo |
+ hoplimit |
+ hopopts |
+ ipcomp_level |
+ join_group |
+ leave_group |
+ mtu |
+ mtu_discover |
+ multicast_hops |
+ multicast_if |
+ multicast_loop |
+ portrange |
+ pktinfo |
+ pktoptions |
+ recverr |
+ recvpktinfo |
+ recvtclass |
+ router_alert |
+ rthdr |
+ tclass |
+ unicast_hops |
+ use_min_mtu |
+ v6only.
+
+-type tcp_socket_option() :: congestion |
+ maxseg |
+ nodelay |
+ user_timeout.
+
+-type udp_socket_option() :: checksum |
+ maxdgram |
+ recvspace.
+-type sctp_socket_option() ::
+ adaption_layer |
+ associnfo |
+ auth_active_key |
+ auth_asconf |
+ auth_chunk |
+ auth_key |
+ auth_delete_key |
+ autoclose |
+ context |
+ default_send_params |
+ delayed_ack_time |
+ disable_fragments |
+ hmac_ident |
+ events |
+ explicit_eor |
+ fragment_interleave |
+ get_peer_addr_info |
+ initmsg |
+ i_want_mapped_v4_addr |
+ local_auth_chunks |
+ maxseg |
+ maxburst |
+ nodelay |
+ partial_delivery_point |
+ peer_addr_params |
+ peer_auth_chunks |
+ primary_addr |
+ reset_streams |
+ rtoinfo |
+ set_peer_primary_addr |
+ status |
+ use_ext_recvinfo.
+
+%% -type plain_socket_options() :: integer().
+%% -type sockopts() :: otp_socket_options() |
+%% socket_options() |
+%% ip_socket_options() |
+%% ipv6_socket_options() |
+%% tcp_socket_options() |
+%% udp_socket_options() |
+%% sctp_socket_options() |
+%% plain_socket_options().
+
+%% If the integer value is used its up to the caller to ensure its valid!
+-type ip_tos_flag() :: lowdeley |
+ throughput |
+ reliability |
+ mincost |
+ integer().
-type socket_info() :: map().
-record(socket, {info :: socket_info(),
@@ -148,9 +338,6 @@
peek |
trunc.
--type setopt_key() :: foo.
--type getopt_key() :: foo.
-
-type shutdown_how() :: read | write | read_write.
-record(msg_hdr,
@@ -213,24 +400,35 @@
-define(SOCKET_RECV_FLAGS_DEFAULT, []).
-define(SOCKET_RECV_TIMEOUT_DEFAULT, infinity).
--define(SOCKET_SETOPT_LEVEL_ENCODED, 0).
--define(SOCKET_SETOPT_LEVEL_RAW, 1).
--define(SOCKET_SETOPT_LEVEL_OTP, 0).
--define(SOCKET_SETOPT_LEVEL_SOCKET, 1).
--define(SOCKET_SETOPT_LEVEL_IP, 2).
--define(SOCKET_SETOPT_LEVEL_IPV6, 3).
--define(SOCKET_SETOPT_LEVEL_TCP, 4).
--define(SOCKET_SETOPT_LEVEL_UDP, 5).
+-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_SETOPT_KEY_DEBUG, 0).
+-define(SOCKET_OPT_OTP_DEBUG, 0).
+-define(SOCKET_OPT_OTP_IOW, 1).
--define(SOCKET_GETOPT_KEY_DEBUG, ?SOCKET_SETOPT_KEY_DEBUG).
+-define(SOCKET_OPT_SOCK_KEEPALIVE, 0).
+-define(SOCKET_OPT_SOCK_LINGER, 1).
+
+-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, 0).
+
+-define(SOCKET_OPT_TCP_MAXSEG, 0).
-define(SOCKET_SHUTDOWN_HOW_READ, 0).
-define(SOCKET_SHUTDOWN_HOW_WRITE, 1).
-define(SOCKET_SHUTDOWN_HOW_READ_WRITE, 2).
+
%% ===========================================================================
%%
%% Administrative and utility API
@@ -1059,28 +1257,64 @@ shutdown(#socket{ref = SockRef}, How) ->
%%
%% <KOLLA>
%%
-%% WE NEED TOP MAKE SURE THAT THE USER DOES NOT MAKE US BLOCKING
+%% WE NEED TO MAKE SURE THAT THE USER DOES NOT MAKE US BLOCKING
%% AS MUCH OF THE CODE EXPECTS TO BE NON-BLOCKING!!
%%
%% </KOLLA>
--spec setopt(Socket, Level, Key, Value) -> ok | {error, Reason} when
+-spec setopt(Socket, Level, Opt, Value) -> ok | {error, Reason} when
+ Socket :: socket(),
+ Level :: otp,
+ Opt :: otp_socket_option(),
+ Value :: term(),
+ Reason :: term()
+ ; (Socket, Level, Opt, Value) -> ok | {error, Reason} when
+ Socket :: socket(),
+ Level :: socket,
+ Opt :: socket_option(),
+ Value :: term(),
+ Reason :: term()
+ ; (Socket, Level, Opt, Value) -> ok | {error, Reason} when
+ Socket :: socket(),
+ Level :: ip,
+ Opt :: ip_socket_option(),
+ Value :: term(),
+ Reason :: term()
+ ; (Socket, Level, Opt, Value) -> ok | {error, Reason} when
+ Socket :: socket(),
+ Level :: ipv6,
+ Opt :: ipv6_socket_option(),
+ Value :: term(),
+ Reason :: term()
+ ; (Socket, Level, Opt, Value) -> ok | {error, Reason} when
+ Socket :: socket(),
+ Level :: tcp,
+ Opt :: tcp_socket_option(),
+ Value :: term(),
+ Reason :: term()
+ ; (Socket, Level, Opt, Value) -> ok | {error, Reason} when
+ Socket :: socket(),
+ Level :: udp,
+ Opt :: udp_socket_option(),
+ Value :: term(),
+ Reason :: term()
+ ; (Socket, Level, Opt, Value) -> ok | {error, Reason} when
Socket :: socket(),
- Level :: option_level(),
- Key :: setopt_key(),
+ Level :: sctp,
+ Opt :: sctp_socket_option(),
Value :: term(),
Reason :: term().
setopt(#socket{info = Info, ref = SockRef}, Level, Key, Value) ->
try
begin
- Domain = maps:get(domain, Info),
- Type = maps:get(type, Info),
- Protocol = maps:get(protocol, Info),
- ELevel = enc_setopt_level(Level),
- EKey = enc_setopt_key(Level, Key, Domain, Type, Protocol),
- EVal = enc_setopt_value(Level, Key, Value, Domain, Type, Protocol),
- nif_setopt(SockRef, ELevel, EKey, EVal)
+ Domain = maps:get(domain, Info),
+ Type = maps:get(type, Info),
+ Protocol = maps:get(protocol, Info),
+ {EIsEncoded, ELevel} = enc_setopt_level(Level),
+ EKey = enc_setopt_key(Level, Key, Domain, Type, Protocol),
+ EVal = enc_setopt_value(Level, Key, Value, Domain, Type, Protocol),
+ nif_setopt(SockRef, EIsEncoded, ELevel, EKey, EVal)
end
catch
throw:T ->
@@ -1090,6 +1324,9 @@ setopt(#socket{info = Info, ref = SockRef}, Level, Key, Value) ->
end.
+
+%% ===========================================================================
+%%
%% getopt - retrieve individual properties of a socket
%%
%% What properties are valid depend on what kind of socket it is
@@ -1100,8 +1337,44 @@ setopt(#socket{info = Info, ref = SockRef}, Level, Key, Value) ->
-spec getopt(Socket, Level, Key) -> {ok, Value} | {error, Reason} when
Socket :: socket(),
- Level :: option_level(),
- Key :: getopt_key(),
+ Level :: otp,
+ Key :: otp_socket_option(),
+ Value :: term(),
+ Reason :: term()
+ ; (Socket, Level, Key) -> {ok, Value} | {error, Reason} when
+ Socket :: socket(),
+ Level :: socket,
+ Key :: socket_option(),
+ Value :: term(),
+ Reason :: term()
+ ; (Socket, Level, Key) -> {ok, Value} | {error, Reason} when
+ Socket :: socket(),
+ Level :: ip,
+ Key :: ip_socket_option(),
+ Value :: term(),
+ Reason :: term()
+ ; (Socket, Level, Key) -> {ok, Value} | {error, Reason} when
+ Socket :: socket(),
+ Level :: ipv6,
+ Key :: ipv6_socket_option(),
+ Value :: term(),
+ Reason :: term()
+ ; (Socket, Level, Key) -> {ok, Value} | {error, Reason} when
+ Socket :: socket(),
+ Level :: tcp,
+ Key :: tcp_socket_option(),
+ Value :: term(),
+ Reason :: term()
+ ; (Socket, Level, Key) -> {ok, Value} | {error, Reason} when
+ Socket :: socket(),
+ Level :: udp,
+ Key :: udp_socket_option(),
+ Value :: term(),
+ Reason :: term()
+ ; (Socket, Level, Key) -> {ok, Value} | {error, Reason} when
+ Socket :: socket(),
+ Level :: sctp,
+ Key :: sctp_socket_option(),
Value :: term(),
Reason :: term().
@@ -1111,13 +1384,14 @@ getopt(#socket{info = Info, ref = SockRef}, Level, Key) ->
Domain = maps:get(domain, Info),
Type = maps:get(type, Info),
Protocol = maps:get(protocol, Info),
- ELevel = enc_getopt_level(Level),
+ {EIsEncoded, ELevel} = enc_getopt_level(Level),
EKey = enc_getopt_key(Level, Key, Domain, Type, Protocol),
%% We may need to decode the value (for the same reason
%% we needed to encode the value for setopt).
- case nif_getopt(SockRef, ELevel, EKey) of
+ case nif_getopt(SockRef, EIsEncoded, ELevel, EKey) of
{ok, EVal} ->
- Val = dec_getopt_value(Level, Key, EVal, Domain, Type, Protocol),
+ Val = dec_getopt_value(Level, Key, EVal,
+ Domain, Type, Protocol),
{ok, Val};
{error, _} = ERROR ->
ERROR
@@ -1205,51 +1479,123 @@ enc_flags(Flags, EFlags) ->
end,
lists:foldl(F, 0, Flags).
+
+%% +++ Encode setopt level +++
+
+-spec enc_setopt_level(Level) -> {IsEncoded, EncodedLevel} when
+ Level :: sockopt_level(),
+ IsEncoded :: boolean(),
+ EncodedLevel :: integer().
+
enc_setopt_level(otp) ->
- {?SOCKET_SETOPT_LEVEL_ENCODED, ?SOCKET_SETOPT_LEVEL_OTP};
+ {true, ?SOCKET_OPT_LEVEL_OTP};
enc_setopt_level(socket) ->
- {?SOCKET_SETOPT_LEVEL_ENCODED, ?SOCKET_SETOPT_LEVEL_SOCKET};
+ {true, ?SOCKET_OPT_LEVEL_SOCKET};
enc_setopt_level(ip) ->
- {?SOCKET_SETOPT_LEVEL_ENCODED, ?SOCKET_SETOPT_LEVEL_IP};
+ {true, ?SOCKET_OPT_LEVEL_IP};
enc_setopt_level(ipv6) ->
- {?SOCKET_SETOPT_LEVEL_ENCODED, ?SOCKET_SETOPT_LEVEL_IPV6};
+ {true, ?SOCKET_OPT_LEVEL_IPV6};
enc_setopt_level(tcp) ->
- {?SOCKET_SETOPT_LEVEL_ENCODED, ?SOCKET_SETOPT_LEVEL_TCP};
+ {true, ?SOCKET_OPT_LEVEL_TCP};
enc_setopt_level(udp) ->
- {?SOCKET_SETOPT_LEVEL_ENCODED, ?SOCKET_SETOPT_LEVEL_UDP};
-%% Any option that is of an raw level must be provided as a binary
+ {true, ?SOCKET_OPT_LEVEL_UDP};
+%% Any option that is of an plain level must be provided as a binary
%% already fully encoded!
enc_setopt_level(L) when is_integer(L) ->
- {?SOCKET_SETOPT_LEVEL_RAW, L}.
+ {false, L}.
-%% We should ...really... do something with the domain, type and protocol args...
-%% Also, any option which has an integer level (raw) must also be provided
-%% in a raw mode, that is, as an integer.
-enc_setopt_key(L, K, _, _, _) when is_integer(L) andalso is_integer(K) ->
- K;
-enc_setopt_key(otp, debug, _, _, _) ->
- ?SOCKET_SETOPT_KEY_DEBUG.
+%% +++ Encode setopt key +++
%% We should ...really... do something with the domain, type and protocol args...
+%% Also, any option (key) which has an integer level (plain) must also be provided
+%% in a plain mode, that is, as an integer.
+%% Also, not all options are available on all platforms. That is something we
+%% don't check here, but in the nif-code.
+
+enc_setopt_key(Level, Opt, Domain, Type, Protocol) ->
+ enc_sockopt_key(Level, Opt, set, Domain, Type, Protocol).
+
+
+%% +++ Encode setopt value +++
+%%
+%% For the most part this function does *not* do an actually encode,
+%% it simply validates the value type. But in some cases it actually
+%% encodes the value into an more manageable type.
+
enc_setopt_value(otp, debug, V, _, _, _) when is_boolean(V) ->
V;
+enc_setopt_value(otp, iow, V, _, _, _) when is_boolean(V) ->
+ V;
+enc_setopt_value(otp = L, Opt, V, _D, _T, _P) ->
+ not_supported({L, Opt, V});
+
+enc_setopt_value(socket, keepalive, V, _D, _T, _P) when is_boolean(V) ->
+ V;
enc_setopt_value(socket, linger, abort, D, T, P) ->
enc_setopt_value(socket, linger, {true, 0}, D, T, P);
enc_setopt_value(socket, linger, {OnOff, Secs} = V, _D, _T, _P)
when is_boolean(OnOff) andalso is_integer(Secs) andalso (Secs >= 0) ->
V;
-enc_setopt_value(L, _, V, _, _, _) when is_integer(L) andalso is_binary(V) ->
+enc_setopt_value(socket = L, Opt, V, _D, _T, _P) ->
+ not_supported({L, Opt, V});
+
+enc_setopt_value(ip, recvtos, V, _D, _T, _P)
+ when is_boolean(V) ->
+ V;
+enc_setopt_value(ip, router_alert, V, _D, _T, _P)
+ when is_integer(V) ->
+ V;
+enc_setopt_value(ip, tos, V, _D, _T, _P)
+ when (V =:= lowdelay) orelse
+ (V =:= throughput) orelse
+ (V =:= reliability) orelse
+ (V =:= mincost) orelse
+ is_integer(V) ->
+ V;
+enc_setopt_value(ip, ttl, V, _D, _T, _P)
+ when is_integer(V) ->
+ V;
+enc_setopt_value(ip = L, Opt, V, _D, _T, _P) ->
+ not_supported({L, Opt, V});
+
+enc_setopt_value(ipv6, hoplimit, V, _D, T, _P)
+ when is_boolean(V) andalso ((T =:= dgram) orelse (T =:= raw)) ->
+ V;
+enc_setopt_value(ipv6 = L, Opt, V, _D, _T, _P) ->
+ not_supported({L, Opt, V});
+
+enc_setopt_value(tcp, maxseg, V, _D, T, P)
+ when is_integer(V) andalso
+ (T =:= stream) andalso
+ (P =:= tcp) ->
+ V;
+enc_setopt_value(tcp = L, Opt, V, _D, _T, _P) ->
+ not_supported({L, Opt, V});
+
+enc_setopt_value(udp = L, Opt, _V, _D, _T, _P) ->
+ not_supported({L, Opt});
+
+enc_setopt_value(L, Opt, V, _, _, _)
+ when is_integer(L) andalso is_integer(Opt) andalso is_binary(V) ->
V.
+
+%% +++ Encode getopt value +++
+
enc_getopt_level(Level) ->
enc_setopt_level(Level).
-%% We should ...really... do something with the domain, type and protocol args...
-enc_getopt_key(otp, debug, _, _, _) ->
- ?SOCKET_GETOPT_KEY_DEBUG.
+
+%% +++ Encode getopt key +++
+
+enc_getopt_key(Level, Opt, Domain, Type, Protocol) ->
+ enc_sockopt_key(Level, Opt, get, Domain, Type, Protocol).
+
+
+%% +++ Decode getopt value +++
%% We should ...really... do something with the domain, type and protocol args...
dec_getopt_value(otp, debug, B, _, _, _) when is_boolean(B) ->
@@ -1257,6 +1603,260 @@ dec_getopt_value(otp, debug, B, _, _, _) when is_boolean(B) ->
+%% +++ Encode socket option key +++
+
+%% Most options are usable both for set and get, but some are
+%% are only available for e.g. get.
+-spec enc_sockopt_key(Level, Opt,
+ Direction,
+ Domain, Type, Protocol) -> non_neg_integer() when
+ Level :: otp,
+ Opt :: otp_socket_option(),
+ Direction :: set | get,
+ Domain :: domain(),
+ Type :: type(),
+ Protocol :: protocol()
+ ; (Level, Direction, Opt,
+ Domain, Type, Protocol) -> non_neg_integer() when
+ Level :: socket,
+ Opt :: socket_option(),
+ Direction :: set | get,
+ Domain :: domain(),
+ Type :: type(),
+ Protocol :: protocol()
+ ; (Level, Direction, Opt,
+ Domain, Type, Protocol) -> non_neg_integer() when
+ Level :: ip,
+ Opt :: ip_socket_option(),
+ Direction :: set | get,
+ Domain :: domain(),
+ Type :: type(),
+ Protocol :: protocol()
+ ; (Level, Direction, Opt,
+ Domain, Type, Protocol) -> non_neg_integer() when
+ Level :: ipv6,
+ Opt :: ipv6_socket_option(),
+ Direction :: set | get,
+ Domain :: domain(),
+ Type :: type(),
+ Protocol :: protocol()
+ ; (Level, Direction, Opt,
+ Domain, Type, Protocol) -> non_neg_integer() when
+ Level :: tcp,
+ Opt :: tcp_socket_option(),
+ Direction :: set | get,
+ Domain :: domain(),
+ Type :: type(),
+ Protocol :: protocol()
+ ; (Level, Direction, Opt,
+ Domain, Type, Protocol) -> non_neg_integer() when
+ Level :: udp,
+ Opt :: udp_socket_option(),
+ Direction :: set | get,
+ Domain :: domain(),
+ Type :: type(),
+ Protocol :: protocol()
+ ; (Level, Direction, Opt,
+ Domain, Type, Protocol) -> non_neg_integer() when
+ Level :: sctp,
+ Opt :: sctp_socket_option(),
+ Direction :: set | get,
+ Domain :: domain(),
+ Type :: type(),
+ Protocol :: protocol()
+ ; (Level, Direction, Opt,
+ Domain, Type, Protocol) -> non_neg_integer() when
+ Level :: integer(),
+ Opt :: integer(),
+ Direction :: set | get,
+ Domain :: domain(),
+ Type :: type(),
+ Protocol :: protocol().
+
+
+%% +++ OTP socket options +++
+enc_sockopt_key(otp, debug, _, _, _, _) ->
+ ?SOCKET_OPT_OTP_DEBUG;
+enc_sockopt_key(otp, iow, _, _, _, _) ->
+ ?SOCKET_OPT_OTP_IOW;
+
+%% +++ SOCKET socket options +++
+enc_sockopt_key(socket, acceptconn = Opt, get = _Dir, _D, _T, _P) ->
+ not_supported(Opt);
+enc_sockopt_key(socket, acceptfilter = Opt, _Dir, _D, _T, _P) ->
+ not_supported(Opt);
+%% Before linux 3.8, this socket option could be set.
+%% Size of buffer for name: IFNAMSZ
+%% So, we let the implementation decide.
+enc_sockopt_key(socket, bindtodevide = Opt, _Dir, _D, _T, _P) ->
+ not_supported(Opt);
+enc_sockopt_key(socket, broadcast = Opt, _Dir, _D, dgram = _T, _P) ->
+ not_supported(Opt);
+enc_sockopt_key(socket, busy_poll = Opt, _Dir, _D, _T, _P) ->
+ not_supported(Opt);
+enc_sockopt_key(socket, debug = Opt, _Dir, _D, _T, _P) ->
+ not_supported(Opt);
+enc_sockopt_key(socket, dontroute = Opt, _Dir, _D, _T, _P) ->
+ not_supported(Opt);
+enc_sockopt_key(socket, error = Opt, get = _Dir, _D, _T, _P) ->
+ not_supported(Opt);
+%% This is only for connection-oriented sockets, but who are those?
+%% Type = stream or Protocol = tcp?
+%% For now, we just let is pass and it will fail later if not ok...
+enc_sockopt_key(socket, keepalive = _Opt, _Dir, _D, _T, _P) ->
+ ?SOCKET_OPT_SOCK_KEEPALIVE;
+enc_sockopt_key(socket, linger = _Opt, _Dir, _D, _T, _P) ->
+ ?SOCKET_OPT_SOCK_LINGER;
+enc_sockopt_key(socket, mark = Opt, _Dir, _D, _T, _P) ->
+ not_supported(Opt);
+enc_sockopt_key(socket, oobinline = Opt, _Dir, _D, _T, _P) ->
+ not_supported(Opt);
+enc_sockopt_key(socket, passcred = Opt, _Dir, _D, _T, _P) ->
+ not_supported(Opt);
+enc_sockopt_key(socket, peek_off = Opt, _Dir, local = _D, _T, _P) ->
+ not_supported(Opt);
+enc_sockopt_key(socket, peek_cred = Opt, get = _Dir, _D, _T, _P) ->
+ not_supported(Opt);
+enc_sockopt_key(socket, priority = Opt, _Dir, _D, _T, _P) ->
+ not_supported(Opt);
+enc_sockopt_key(socket, rcvbuf = Opt, _Dir, _D, _T, _P) ->
+ not_supported(Opt);
+enc_sockopt_key(socket, rcvbufforce = Opt, _Dir, _D, _T, _P) ->
+ not_supported(Opt);
+%% May not work on linux.
+enc_sockopt_key(socket, rcvlowat = Opt, _Dir, _D, _T, _P) ->
+ not_supported(Opt);
+enc_sockopt_key(socket, rcvtimeo = Opt, _Dir, _D, _T, _P) ->
+ not_supported(Opt);
+enc_sockopt_key(socket, reuseaddr = Opt, _Dir, _D, _T, _P) ->
+ not_supported(Opt);
+enc_sockopt_key(socket, reuseport = Opt, _Dir, D, _T, _P)
+ when ((D =:= inet) orelse (D =:= inet6)) ->
+ not_supported(Opt);
+enc_sockopt_key(socket, rxq_ovfl = Opt, _Dir, _D, _T, _P) ->
+ not_supported(Opt);
+enc_sockopt_key(socket, setfib = Opt, set = _Dir, _D, _T, _P) ->
+ not_supported(Opt);
+enc_sockopt_key(socket, sndbuf = Opt, _Dir, _D, _T, _P) ->
+ not_supported(Opt);
+enc_sockopt_key(socket, sndbufforce = Opt, _Dir, _D, _T, _P) ->
+ not_supported(Opt);
+%% Not changeable on linux.
+enc_sockopt_key(socket, sndlowat = Opt, _Dir, _D, _T, _P) ->
+ not_supported(Opt);
+enc_sockopt_key(socket, sndtimeo = Opt, _Dir, _D, _T, _P) ->
+ not_supported(Opt);
+enc_sockopt_key(socket, timestamp = Opt, _Dir, _D, _T, _P) ->
+ not_supported(Opt);
+enc_sockopt_key(socket, UnknownOpt, _Dir, _D, _T, _P) ->
+ unknown(UnknownOpt);
+
+%% +++ IP socket options +++
+enc_sockopt_key(ip, add_membership = Opt, set = _Dir, _D, _T, _P) ->
+ not_supported(Opt);
+enc_sockopt_key(ip, add_source_membership = Opt, set = _Dir, _D, _T, _P) ->
+ not_supported(Opt);
+enc_sockopt_key(ip, block_source = Opt, set = _Dir, _D, _T, _P) ->
+ not_supported(Opt);
+%% FreeBSD only?
+%% Only respected on udp and raw ip (unless the hdrincl option has been set).
+enc_sockopt_key(ip, dontfrag = Opt, _Dir, _D, _T, _P) ->
+ not_supported(Opt);
+enc_sockopt_key(ip, drop_membership = Opt, set = _Dir, _D, _T, _P) ->
+ not_supported(Opt);
+enc_sockopt_key(ip, drop_source_membership = Opt, set = _Dir, _D, _T, _P) ->
+ not_supported(Opt);
+%% Linux only?
+enc_sockopt_key(ip, free_bind = Opt, _Dir, _D, _T, _P) ->
+ not_supported(Opt);
+enc_sockopt_key(ip, hdrincl = Opt, _Dir, _D, raw = _T, _P) ->
+ not_supported(Opt);
+%% FreeBSD only?
+enc_sockopt_key(ip, minttl = Opt, _Dir, _D, raw = _T, _P) ->
+ not_supported(Opt);
+enc_sockopt_key(ip, msfilter = Opt, _Dir, _D, _T, _P) ->
+ not_supported(Opt);
+enc_sockopt_key(ip, mtu = Opt, get = _Dir, _D, _T, _P) ->
+ not_supported(Opt);
+enc_sockopt_key(ip, mtu_discover = Opt, _Dir, _D, _T, _P) ->
+ not_supported(Opt);
+enc_sockopt_key(ip, multicast_all = Opt, _Dir, _D, _T, _P) ->
+ not_supported(Opt);
+enc_sockopt_key(ip, multicast_if = Opt, _Dir, _D, _T, _P) ->
+ not_supported(Opt);
+enc_sockopt_key(ip, multicast_loop = Opt, _Dir, _D, _T, _P) ->
+ not_supported(Opt);
+enc_sockopt_key(ip, multicast_ttl = Opt, _Dir, _D, _T, _P) ->
+ not_supported(Opt);
+enc_sockopt_key(ip, nodefrag = Opt, _Dir, _D, raw = _T, _P) ->
+ not_supported(Opt);
+enc_sockopt_key(ip, options = Opt, _Dir, _D, _T, _P) ->
+ not_supported(Opt);
+enc_sockopt_key(ip, pktinfo = Opt, _Dir, _D, _T, _P) ->
+ not_supported(Opt);
+%% This require special code for accessing the errors.
+%% via calling the recvmsg with the MSG_ERRQUEUE flag set,
+enc_sockopt_key(ip, recverr = Opt, _Dir, _D, _T, _P) ->
+ not_supported(Opt);
+enc_sockopt_key(ip, recvif = Opt, _Dir, _D, dgram = _T, _P) ->
+ not_supported(Opt);
+enc_sockopt_key(ip, recvdstaddr = Opt, _Dir, _D, _T, _P) ->
+ not_supported(Opt);
+enc_sockopt_key(ip, recvopts = Opt, _Dir, _D, T, _P) when (T =/= stream) ->
+ not_supported(Opt);
+enc_sockopt_key(ip, recvorigdstaddr = Opt, _Dir, _D, _T, _P) ->
+ not_supported(Opt);
+enc_sockopt_key(ip, recvtos = _Opt, _Dir, _D, _T, _P) ->
+ ?SOCKET_OPT_IP_RECVTOS;
+enc_sockopt_key(ip, recvttl = Opt, _Dir, _D, T, _P) when (T =/= stream) ->
+ not_supported(Opt);
+enc_sockopt_key(ip, retopts = Opt, _Dir, _D, _T, _P) ->
+ not_supported(Opt);
+enc_sockopt_key(ip, router_alert = Opt, _Dir, _D, raw = _T, _P) ->
+ not_supported(Opt);
+%% On FreeBSD it specifies that this option is only valid
+%% for stream, dgram and "some" raw sockets...
+%% No such condition on linux (in the man page)...
+enc_sockopt_key(ip, tos = _Opt, _Dir, _D, _T, _P) ->
+ ?SOCKET_OPT_IP_TOS;
+enc_sockopt_key(ip, transparent = Opt, _Dir, _D, _T, _P) ->
+ not_supported(Opt);
+enc_sockopt_key(ip, ttl = _Opt, _Dir, _D, _T, _P) ->
+ ?SOCKET_OPT_IP_TTL;
+enc_sockopt_key(ip, unblock_source = Opt, set = _Dir, _D, _T, _P) ->
+ not_supported(Opt);
+enc_sockopt_key(ip, UnknownOpt, _Dir, _D, _T, _P) ->
+ unknown(UnknownOpt);
+
+%% IPv6 socket options
+enc_sockopt_key(ipv6, hoplimit = _Opt, _Dir, _D, T, _P)
+ when (T =:= dgram) orelse (T =:= raw) ->
+ ?SOCKET_OPT_IPV6_HOPLIMIT;
+enc_sockopt_key(ipv6, UnknownOpt, _Dir, _D, _T, _P) ->
+ unknown(UnknownOpt);
+
+%% TCP socket options
+enc_sockopt_key(tcp, UnknownOpt, _Dir, _D, _T, _P) ->
+ unknown(UnknownOpt);
+
+%% UDP socket options
+enc_sockopt_key(udp, UnknownOpt, _Dir, _D, _T, _P) ->
+ unknown(UnknownOpt);
+
+%% SCTP socket options
+enc_sockopt_key(sctp, UnknownOpt, _Dir, _D, _T, _P) ->
+ unknown(UnknownOpt);
+
+%% +++ Plain socket options +++
+enc_sockopt_key(Level, Opt, _Dir, _D, _T, _P)
+ when is_integer(Level) andalso is_integer(Opt) ->
+ Opt;
+
+enc_sockopt_key(Level, Opt, _Dir, _Domain, _Type, _Protocol) ->
+ unknown({Level, Opt}).
+
+
+
enc_shutdown_how(read) ->
?SOCKET_SHUTDOWN_HOW_READ;
enc_shutdown_how(write) ->
@@ -1336,6 +1936,21 @@ tdiff(T1, T2) ->
+%% ===========================================================================
+%%
+%% Error functions
+%%
+%% ===========================================================================
+
+not_supported(What) ->
+ error({not_supported, What}).
+
+unknown(What) ->
+ error({unknown, What}).
+
+error(Reason) ->
+ throw({error, Reason}).
+
%% ===========================================================================
%%
@@ -1390,8 +2005,8 @@ nif_shutdown(_SRef, _How) ->
nif_finalize_close(_SRef) ->
erlang:error(badarg).
-nif_setopt(_Ref, _Lev, _Key, _Val) ->
+nif_setopt(_Ref, _IsEnc, _Lev, _Key, _Val) ->
erlang:error(badarg).
-nif_getopt(_Ref, _Lev, _Key) ->
+nif_getopt(_Ref, _IsEnc, _Lev, _Key) ->
erlang:error(badarg).