From 8e14247bc5faf5abf67901b6ae5028f2b1897c65 Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Fri, 4 May 2018 16:00:26 +0200 Subject: [socket-nif] Preliminary setopt *Very* partial setopt implementation. --- erts/emulator/nifs/common/socket_nif.c | 1029 ++++++++++++++++++++++++++++++-- erts/preloaded/src/socket.erl | 721 ++++++++++++++++++++-- 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 #include #include +#include #ifdef HAVE_UNISTD_H #include @@ -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(...) + * + * How do we know what type each option have? tag in value type? + * + */ + 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: + /* + * Should we care about the value? That is, if it is valid? + * And what is the valid range anyway for ttl? 0 - 255? + * + */ + 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 +++ + * + * + * 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. + * + * + * + * 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. + * + */ +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, <)) + 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) -> %% %% %% -%% 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!! %% %% --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). -- cgit v1.2.3