From 90a150771faa3cf01e82919b0c17854de9987783 Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Wed, 1 Aug 2018 19:42:32 +0200 Subject: [socket-nif] Processing of more cmsg headers Added processing or more cmsg headers (for more options). Now (also) supports: socket:timestamp. Also various fixes and cleanups. For some reason calling getopt(Sock, 0, {13, int}) (or similar) fails with badarg even though the nif-function (nif_getopt) actually returns a valid value (for instance: {ok, 0}). OTP-14831 --- erts/doc/src/socket.xml | 26 ++- erts/doc/src/socket_usage.xml | 2 +- erts/emulator/nifs/common/socket_int.h | 5 + erts/emulator/nifs/common/socket_nif.c | 345 +++++++++++++++++++++----------- erts/emulator/nifs/common/socket_util.c | 73 +++++++ erts/emulator/nifs/common/socket_util.h | 6 + erts/preloaded/ebin/socket.beam | Bin 61376 -> 61704 bytes erts/preloaded/src/socket.erl | 52 +++-- lib/kernel/test/socket_server.erl | 123 ++++++++---- 9 files changed, 458 insertions(+), 174 deletions(-) diff --git a/erts/doc/src/socket.xml b/erts/doc/src/socket.xml index 53d1516f1e..2fb922408b 100644 --- a/erts/doc/src/socket.xml +++ b/erts/doc/src/socket.xml @@ -120,7 +120,7 @@ - + @@ -137,6 +137,9 @@ + + + @@ -155,6 +158,27 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/erts/doc/src/socket_usage.xml b/erts/doc/src/socket_usage.xml index 933341bd35..b7459e97fa 100644 --- a/erts/doc/src/socket_usage.xml +++ b/erts/doc/src/socket_usage.xml @@ -444,7 +444,7 @@ ip_tos() yes yes - may require admin capability + some high-priority levels may require superuser capability transparent diff --git a/erts/emulator/nifs/common/socket_int.h b/erts/emulator/nifs/common/socket_int.h index 80a9eb0734..3595c483d7 100644 --- a/erts/emulator/nifs/common/socket_int.h +++ b/erts/emulator/nifs/common/socket_int.h @@ -142,11 +142,14 @@ extern ERL_NIF_TERM esock_atom_reliability; extern ERL_NIF_TERM esock_atom_rights; extern ERL_NIF_TERM esock_atom_scope_id; extern ERL_NIF_TERM esock_atom_sctp; +extern ERL_NIF_TERM esock_atom_sec; extern ERL_NIF_TERM esock_atom_seqpacket; +extern ERL_NIF_TERM esock_atom_socket; extern ERL_NIF_TERM esock_atom_spec_dst; extern ERL_NIF_TERM esock_atom_stream; extern ERL_NIF_TERM esock_atom_tcp; extern ERL_NIF_TERM esock_atom_throughput; +extern ERL_NIF_TERM esock_atom_timestamp; extern ERL_NIF_TERM esock_atom_tos; extern ERL_NIF_TERM esock_atom_true; extern ERL_NIF_TERM esock_atom_trunc; @@ -154,6 +157,8 @@ extern ERL_NIF_TERM esock_atom_ttl; extern ERL_NIF_TERM esock_atom_type; extern ERL_NIF_TERM esock_atom_udp; extern ERL_NIF_TERM esock_atom_undefined; +extern ERL_NIF_TERM esock_atom_unknown; +extern ERL_NIF_TERM esock_atom_usec; /* ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ diff --git a/erts/emulator/nifs/common/socket_nif.c b/erts/emulator/nifs/common/socket_nif.c index 4b4082062b..4144341d71 100644 --- a/erts/emulator/nifs/common/socket_nif.c +++ b/erts/emulator/nifs/common/socket_nif.c @@ -421,6 +421,11 @@ static void (*esock_sctp_freepaddrs)(struct sockaddr *addrs) = NULL; #define SOCKET_RECV_BUFFER_SIZE_DEFAULT 2048 #define SOCKET_RECV_CTRL_BUFFER_SIZE_DEFAULT 1024 +#define VT2S(__VT__) (((__VT__) == SOCKET_OPT_VALUE_TYPE_UNSPEC) ? "unspec" : \ + (((__VT__) == SOCKET_OPT_VALUE_TYPE_INT) ? "int" : \ + ((__VT__) == SOCKET_OPT_VALUE_TYPE_BOOL) ? "bool" : \ + "undef")) + #define SOCKET_OPT_VALUE_TYPE_UNSPEC 0 #define SOCKET_OPT_VALUE_TYPE_INT 1 #define SOCKET_OPT_VALUE_TYPE_BOOL 2 @@ -1468,7 +1473,7 @@ static ERL_NIF_TERM ngetopt(ErlNifEnv* env, BOOLEAN_T isEncoded, BOOLEAN_T isOTP, int level, - int eOpt); + ERL_NIF_TERM eOpt); static ERL_NIF_TERM ngetopt_otp(ErlNifEnv* env, SocketDescriptor* descP, int eOpt); @@ -1479,7 +1484,7 @@ static ERL_NIF_TERM ngetopt_otp_iow(ErlNifEnv* env, static ERL_NIF_TERM ngetopt_native(ErlNifEnv* env, SocketDescriptor* descP, int level, - int eOpt); + ERL_NIF_TERM eOpt); static ERL_NIF_TERM ngetopt_native_unspec(ErlNifEnv* env, SocketDescriptor* descP, int level, @@ -1897,6 +1902,9 @@ extern char* encode_cmsghdrs(ErlNifEnv* env, ErlNifBinary* cmsgBinP, struct msghdr* msgHdrP, ERL_NIF_TERM* eCMsgHdr); +static char* encode_cmsghdr_level(ErlNifEnv* env, + int level, + ERL_NIF_TERM* eLevel); static char* encode_cmsghdr_type(ErlNifEnv* env, int level, int type, @@ -1909,6 +1917,13 @@ static char* encode_cmsghdr_data(ErlNifEnv* env, size_t dataPos, size_t dataLen, ERL_NIF_TERM* eCMsgHdrData); +static char* encode_cmsghdr_data_socket(ErlNifEnv* env, + ERL_NIF_TERM ctrlBuf, + int type, + unsigned char* dataP, + size_t dataPos, + size_t dataLen, + ERL_NIF_TERM* eCMsgHdrData); static char* encode_cmsghdr_data_ip(ErlNifEnv* env, ERL_NIF_TERM ctrlBuf, int type, @@ -2104,6 +2119,7 @@ static char str_association[] = "association"; static char str_assoc_id[] = "assoc_id"; static char str_authentication[] = "authentication"; // static char str_any[] = "any"; +static char str_bool[] = "bool"; static char str_close[] = "close"; static char str_closed[] = "closed"; static char str_closing[] = "closing"; @@ -2118,6 +2134,7 @@ static char str_in4_sockaddr[] = "in4_sockaddr"; static char str_in6_sockaddr[] = "in6_sockaddr"; static char str_include[] = "include"; static char str_initial[] = "initial"; +static char str_int[] = "int"; static char str_interface[] = "interface"; static char str_iow[] = "iow"; static char str_local_rwnd[] = "local_rwnd"; @@ -2149,7 +2166,6 @@ static char str_partial_delivery[] = "partial_delivery"; static char str_peer_error[] = "peer_error"; static char str_peer_rwnd[] = "peer_rwnd"; static char str_probe[] = "probe"; -static char str_sec[] = "sec"; static char str_select[] = "select"; static char str_sender_dry[] = "sender_dry"; static char str_send_failure[] = "send_failure"; @@ -2158,7 +2174,6 @@ static char str_slist[] = "slist"; static char str_sourceaddr[] = "sourceaddr"; static char str_timeout[] = "timeout"; static char str_true[] = "true"; -static char str_usec[] = "usec"; static char str_want[] = "want"; /* (special) error string constants */ @@ -2213,11 +2228,14 @@ ERL_NIF_TERM esock_atom_rights; ERL_NIF_TERM esock_atom_reliability; ERL_NIF_TERM esock_atom_scope_id; ERL_NIF_TERM esock_atom_sctp; +ERL_NIF_TERM esock_atom_sec; ERL_NIF_TERM esock_atom_seqpacket; +ERL_NIF_TERM esock_atom_socket; ERL_NIF_TERM esock_atom_spec_dst; ERL_NIF_TERM esock_atom_stream; ERL_NIF_TERM esock_atom_tcp; ERL_NIF_TERM esock_atom_throughput; +ERL_NIF_TERM esock_atom_timestamp; ERL_NIF_TERM esock_atom_tos; ERL_NIF_TERM esock_atom_true; ERL_NIF_TERM esock_atom_trunc; @@ -2226,6 +2244,7 @@ ERL_NIF_TERM esock_atom_type; ERL_NIF_TERM esock_atom_udp; ERL_NIF_TERM esock_atom_undefined; ERL_NIF_TERM esock_atom_unknown; +ERL_NIF_TERM esock_atom_usec; /* *** "Global" error (=reason) atoms *** */ ERL_NIF_TERM esock_atom_eagain; @@ -2238,6 +2257,7 @@ static ERL_NIF_TERM atom_address; static ERL_NIF_TERM atom_association; static ERL_NIF_TERM atom_assoc_id; static ERL_NIF_TERM atom_authentication; +static ERL_NIF_TERM atom_bool; static ERL_NIF_TERM atom_close; static ERL_NIF_TERM atom_closed; static ERL_NIF_TERM atom_closing; @@ -2252,6 +2272,7 @@ static ERL_NIF_TERM atom_in4_sockaddr; static ERL_NIF_TERM atom_in6_sockaddr; static ERL_NIF_TERM atom_include; static ERL_NIF_TERM atom_initial; +static ERL_NIF_TERM atom_int; static ERL_NIF_TERM atom_interface; static ERL_NIF_TERM atom_iow; static ERL_NIF_TERM atom_local_rwnd; @@ -2282,7 +2303,6 @@ static ERL_NIF_TERM atom_partial_delivery; static ERL_NIF_TERM atom_peer_error; static ERL_NIF_TERM atom_peer_rwnd; static ERL_NIF_TERM atom_probe; -static ERL_NIF_TERM atom_sec; static ERL_NIF_TERM atom_select; static ERL_NIF_TERM atom_sender_dry; static ERL_NIF_TERM atom_send_failure; @@ -2291,7 +2311,6 @@ static ERL_NIF_TERM atom_slist; static ERL_NIF_TERM atom_sourceaddr; static ERL_NIF_TERM atom_timeout; static ERL_NIF_TERM atom_true; -static ERL_NIF_TERM atom_usec; static ERL_NIF_TERM atom_want; static ERL_NIF_TERM atom_eisconn; @@ -7424,42 +7443,18 @@ ERL_NIF_TERM nsetopt_timeval_opt(ErlNifEnv* env, ERL_NIF_TERM eVal) { ERL_NIF_TERM result; - ERL_NIF_TERM eSec, eUSec; struct timeval timeVal; int res; - size_t sz; + char* xres; SSDBG( descP, ("SOCKET", "nsetopt_timeval_opt -> entry with" "\r\n eVal: %T" "\r\n", eVal) ); - // It must be a map - if (!IS_MAP(env, eVal)) - return esock_make_error(env, esock_atom_einval); - - // It must have atleast ten attributes - if (!enif_get_map_size(env, eVal, &sz) || (sz < 2)) - return esock_make_error(env, esock_atom_einval); - - SSDBG( descP, - ("SOCKET", "nsetopt_lvl_sctp_rtoinfo -> extract attributes\r\n") ); - - if (!GET_MAP_VAL(env, eVal, atom_sec, &eSec)) - return esock_make_error(env, esock_atom_einval); - - if (!GET_MAP_VAL(env, eVal, atom_usec, &eUSec)) - return esock_make_error(env, esock_atom_einval); + if ((xres = esock_decode_timeval(env, eVal, &timeVal)) != NULL) + return esock_make_error_str(env, xres); - SSDBG( descP, - ("SOCKET", "nsetopt_timeval_opt -> decode attributes\r\n") ); - - if (!GET_LONG(env, eSec, &timeVal.tv_sec)) - return esock_make_error(env, esock_atom_einval); - - if (!GET_LONG(env, eUSec, &timeVal.tv_usec)) - return esock_make_error(env, esock_atom_einval); - SSDBG( descP, ("SOCKET", "nsetopt_timeval_opt -> set timeval option\r\n") ); @@ -7668,26 +7663,26 @@ ERL_NIF_TERM nif_getopt(ErlNifEnv* env, { SocketDescriptor* descP; int eLevel, level = -1; - int eOpt; - ERL_NIF_TERM eIsEncoded; + ERL_NIF_TERM eIsEncoded, eOpt; BOOLEAN_T isEncoded, isOTP; SGDBG( ("SOCKET", "nif_getopt -> entry with argc: %d\r\n", argc) ); if ((argc != 4) || !enif_get_resource(env, argv[0], sockets, (void**) &descP) || - !GET_INT(env, argv[2], &eLevel) || - !GET_INT(env, argv[3], &eOpt)) { + !GET_INT(env, argv[2], &eLevel)) { + SGDBG( ("SOCKET", "nif_getopt -> failed processing args\r\n") ); return enif_make_badarg(env); } eIsEncoded = argv[1]; + eOpt = argv[3]; // Is "normally" an int, but if raw mode: {Int, ValueSz} SSDBG( descP, ("SOCKET", "nif_getopt -> args when sock = %d:" "\r\n Socket: %T" "\r\n eIsEncoded: %T" "\r\n eLevel: %d" - "\r\n eOpt: %d" + "\r\n eOpt: %T" "\r\n", descP->sock, argv[0], eIsEncoded, eLevel, eOpt) ); isEncoded = esock_decode_bool(eIsEncoded); @@ -7706,27 +7701,34 @@ ERL_NIF_TERM ngetopt(ErlNifEnv* env, BOOLEAN_T isEncoded, BOOLEAN_T isOTP, int level, - int eOpt) + ERL_NIF_TERM eOpt) { ERL_NIF_TERM result; + int opt; SSDBG( descP, ("SOCKET", "ngetopt -> entry with" - "\r\n isEncoded: %d" - "\r\n isOTP: %d" + "\r\n isEncoded: %s" + "\r\n isOTP: %s" "\r\n level: %d" - "\r\n eOpt: %d" - "\r\n", isEncoded, isOTP, level, eOpt) ); + "\r\n eOpt: %T" + "\r\n", B2S(isEncoded), B2S(isOTP), level, eOpt) ); if (isOTP) { /* These are not actual socket options, * but options for our implementation. */ - result = ngetopt_otp(env, descP, eOpt); + if (GET_INT(env, eOpt, &opt)) + result = ngetopt_otp(env, descP, opt); + else + result = esock_make_error(env, esock_atom_einval); } else if (!isEncoded) { result = ngetopt_native(env, descP, level, eOpt); } else { - result = ngetopt_level(env, descP, level, eOpt); + if (GET_INT(env, eOpt, &opt)) + result = ngetopt_level(env, descP, level, opt); + else + result = esock_make_error(env, esock_atom_einval); } SSDBG( descP, @@ -7809,7 +7811,7 @@ static ERL_NIF_TERM ngetopt_native(ErlNifEnv* env, SocketDescriptor* descP, int level, - int eOpt) + ERL_NIF_TERM eOpt) { ERL_NIF_TERM result = enif_make_badarg(env); int opt; @@ -7819,16 +7821,23 @@ ERL_NIF_TERM ngetopt_native(ErlNifEnv* env, SSDBG( descP, ("SOCKET", "ngetopt_native -> entry with" "\r\n level: %d" - "\r\n eOpt: %d" + "\r\n eOpt: %T" "\r\n", level, eOpt) ); /* - * We should really make it possible to specify common specific types, + * We should really make it possible to specify more common specific types, * such as integer or boolean (instead of the size)... * */ if (decode_native_get_opt(env, eOpt, &opt, &valueType, (int*) &valueSz)) { + + SSDBG( descP, + ("SOCKET", "ngetopt_native -> decoded opt" + "\r\n valueType: %d (%s)" + "\r\n ValueSize: %d" + "\r\n", valueType, VT2S(valueType), valueSz) ); + switch (valueType) { case SOCKET_OPT_VALUE_TYPE_UNSPEC: result = ngetopt_native_unspec(env, descP, level, opt, valueSz); @@ -7863,7 +7872,7 @@ ERL_NIF_TERM ngetopt_native_unspec(ErlNifEnv* env, int opt, SOCKOPTLEN_T valueSz) { - ERL_NIF_TERM result = enif_make_badarg(env); + ERL_NIF_TERM result = esock_make_error(env, esock_atom_einval); int res; SSDBG( descP, @@ -7880,19 +7889,33 @@ ERL_NIF_TERM ngetopt_native_unspec(ErlNifEnv* env, else result = esock_atom_ok; } else { + SOCKOPTLEN_T vsz = valueSz; ErlNifBinary val; - if (ALLOC_BIN(valueSz, &val)) { - res = sock_getopt(descP->sock, level, opt, val.data, &valueSz); + SSDBG( descP, ("SOCKET", "ngetopt_native_unspec -> try alloc buffer\r\n") ); + + if (ALLOC_BIN(vsz, &val)) { + int saveErrno; + res = sock_getopt(descP->sock, level, opt, val.data, &vsz); if (res != 0) { - result = esock_make_error_errno(env, sock_errno()); + saveErrno = sock_errno(); + + result = esock_make_error_errno(env, saveErrno); } else { - if (valueSz < val.size) { - if (REALLOC_BIN(&val, valueSz)) { - result = esock_make_ok2(env, MKBIN(env, &val)); - } else { - result = enif_make_badarg(env); - } + + /* Did we use all of the buffer? */ + if (vsz == val.size) { + + result = esock_make_ok2(env, MKBIN(env, &val)); + + } else { + + ERL_NIF_TERM tmp; + + tmp = MKBIN(env, &val); + tmp = MKSBIN(env, tmp, 0, vsz); + + result = esock_make_ok2(env, tmp); } } } else { @@ -9959,18 +9982,12 @@ ERL_NIF_TERM ngetopt_timeval_opt(ErlNifEnv* env, result = esock_make_error_errno(env, sock_errno()); } else { ERL_NIF_TERM eTimeVal; - ERL_NIF_TERM keys[] = {atom_sec, atom_usec}; - ERL_NIF_TERM vals[] = {MKL(env, val.tv_sec), MKL(env, val.tv_usec)}; - - unsigned int numKeys = sizeof(keys) / sizeof(ERL_NIF_TERM); - unsigned int numVals = sizeof(vals) / sizeof(ERL_NIF_TERM); - - ESOCK_ASSERT( (numKeys == numVals) ); - - if (!MKMA(env, keys, vals, numKeys, &eTimeVal)) - return esock_make_error(env, esock_atom_einval); - - result = esock_make_ok2(env, eTimeVal); + char* xres; + + if ((xres = esock_encode_timeval(env, &val, &eTimeVal)) != NULL) + result = esock_make_error_str(env, xres); + else + result = esock_make_ok2(env, eTimeVal); } SSDBG( descP, @@ -10562,6 +10579,28 @@ ERL_NIF_TERM recvmsg_check_result(ErlNifEnv* env, "\r\n", read, saveErrno, recvRef) ); + /* + * + * We need to handle read = 0 for other type(s) (DGRAM) when + * its actually valid to read 0 bytes. + * + * + */ + + if ((read == 0) && (descP->type == SOCK_STREAM)) { + + /* + * When a stream socket peer has performed an orderly shutdown, the return + * value will be 0 (the traditional "end-of-file" return). + * + * *We* do never actually try to read 0 bytes from a stream socket! + */ + + return esock_make_error(env, atom_closed); + + } + + /* There is a special case: If the provided 'to read' value is * zero (0). That means that we reads as much as we can, using * the default read buffer size. @@ -10575,7 +10614,7 @@ ERL_NIF_TERM recvmsg_check_result(ErlNifEnv* env, /* +++ Oups - closed +++ */ - SSDBG( descP, ("SOCKET", "recvfrom_check_result -> closed\r\n") ); + SSDBG( descP, ("SOCKET", "recvmsg_check_result -> closed\r\n") ); /* * IF THE CURRENT PROCESS IS *NOT* THE CONTROLLING @@ -10599,7 +10638,7 @@ ERL_NIF_TERM recvmsg_check_result(ErlNifEnv* env, } else if ((saveErrno == ERRNO_BLOCK) || (saveErrno == EAGAIN)) { - SSDBG( descP, ("SOCKET", "recvfrom_check_result -> eagain\r\n") ); + SSDBG( descP, ("SOCKET", "recvmsg_check_result -> eagain\r\n") ); SELECT(env, descP->sock, (ERL_NIF_SELECT_READ), descP, NULL, recvRef); @@ -10609,7 +10648,7 @@ ERL_NIF_TERM recvmsg_check_result(ErlNifEnv* env, SSDBG( descP, ("SOCKET", - "recvfrom_check_result -> errno: %d\r\n", saveErrno) ); + "recvmsg_check_result -> errno: %d\r\n", saveErrno) ); return esock_make_error_errno(env, saveErrno); } @@ -10637,7 +10676,7 @@ ERL_NIF_TERM recvmsg_check_result(ErlNifEnv* env, SSDBG( descP, ("SOCKET", - "recvfrom_check_result -> " + "recvmsg_check_result -> " "(msghdr) encode failed: %s\r\n", xres) ); return esock_make_error_str(env, xres); @@ -10645,7 +10684,7 @@ ERL_NIF_TERM recvmsg_check_result(ErlNifEnv* env, SSDBG( descP, ("SOCKET", - "recvfrom_check_result -> " + "recvmsg_check_result -> " "(msghdr) encode ok: %T\r\n", eMsgHdr) ); return esock_make_ok2(env, eMsgHdr); @@ -10684,12 +10723,19 @@ char* encode_msghdr(ErlNifEnv* env, ("SOCKET", "encode_msghdr -> entry with" "\r\n read: %d" "\r\n", read) ); - - if ((xres = esock_encode_sockaddr(env, - (SocketAddress*) msgHdrP->msg_name, - msgHdrP->msg_namelen, - &addr)) != NULL) - return xres; + + /* The address is not used if we are connected, + * so check (length = 0) before we try to encodel + */ + if (msgHdrP->msg_namelen != 0) { + if ((xres = esock_encode_sockaddr(env, + (SocketAddress*) msgHdrP->msg_name, + msgHdrP->msg_namelen, + &addr)) != NULL) + return xres; + } else { + addr = esock_atom_undefined; + } SSDBG( descP, ("SOCKET", "encode_msghdr -> try encode iov\r\n") ); if ((xres = esock_encode_iov(env, @@ -10814,7 +10860,7 @@ char* encode_cmsghdrs(ErlNifEnv* env, * so if its a protocol we don't know, we return its integer * value and leave it to the user. */ - if (esock_encode_protocol(env, currentP->cmsg_level, &level) != NULL) + if (encode_cmsghdr_level(env, currentP->cmsg_level, &level) != NULL) level = MKI(env, currentP->cmsg_level); if (encode_cmsghdr_type(env, @@ -10875,6 +10921,35 @@ char* encode_cmsghdrs(ErlNifEnv* env, +/* +++ encode_cmsghdr_level +++ + * + * Encode the type part of the cmsghdr(). + * + */ + +static +char* encode_cmsghdr_level(ErlNifEnv* env, + int level, + ERL_NIF_TERM* eLevel) +{ + char* xres; + + switch (level) { + case SOL_SOCKET: + *eLevel = esock_atom_socket; + xres = NULL; + break; + + default: + xres = esock_encode_protocol(env, level, eLevel); + break; + } + + return xres; +} + + + /* +++ encode_cmsghdr_type +++ * * Encode the type part of the cmsghdr(). @@ -10892,6 +10967,12 @@ char* encode_cmsghdr_type(ErlNifEnv* env, switch (level) { case SOL_SOCKET: switch (type) { +#if defined(SO_TIMESTAMP) + case SO_TIMESTAMP: + *eType = esock_atom_timestamp; + break; +#endif + #if defined(SCM_RIGHTS) case SCM_RIGHTS: *eType = esock_atom_rights; @@ -11011,13 +11092,13 @@ char* encode_cmsghdr_data(ErlNifEnv* env, char* xres; switch (level) { - /* - #if defined(SOL_SOCKET) - case SOL_SOCKET: - xres = encode_cmsghdr_data_socket(env, type, dataP, eCMsgHdrData); - break; - #endif - */ +#if defined(SOL_SOCKET) + case SOL_SOCKET: + xres = encode_cmsghdr_data_socket(env, ctrlBuf, type, + dataP, dataPos, dataLen, + eCMsgHdrData); + break; +#endif #if defined(SOL_IP) case SOL_IP: @@ -11068,6 +11149,45 @@ char* encode_cmsghdr_data(ErlNifEnv* env, +/* +++ encode_cmsghdr_data_socket +++ + * + * Encode the data part when "protocol" = socket of the cmsghdr(). + * + */ + +static +char* encode_cmsghdr_data_socket(ErlNifEnv* env, + ERL_NIF_TERM ctrlBuf, + int type, + unsigned char* dataP, + size_t dataPos, + size_t dataLen, + ERL_NIF_TERM* eCMsgHdrData) +{ + // char* xres; + + switch (type) { +#if defined(SO_TIMESTAMP) + case SO_TIMESTAMP: + { + struct timeval* timeP = (struct timeval*) dataP; + + if (esock_encode_timeval(env, timeP, eCMsgHdrData) != NULL) + *eCMsgHdrData = MKSBIN(env, ctrlBuf, dataPos, dataLen); + } + break; +#endif + + default: + *eCMsgHdrData = MKSBIN(env, ctrlBuf, dataPos, dataLen); + break; + } + + return NULL; +} + + + /* +++ encode_cmsghdr_data_ip +++ * * Encode the data part when protocol = IP of the cmsghdr(). @@ -11089,8 +11209,8 @@ char* encode_cmsghdr_data_ip(ErlNifEnv* env, #if defined(IP_TOS) case IP_TOS: { - unsigned char tos = IPTOS_TOS(*dataP); - switch (tos) { + unsigned char tos = *dataP; + switch (IPTOS_TOS(tos)) { case IPTOS_LOWDELAY: *eCMsgHdrData = esock_atom_lowdelay; break; @@ -11551,29 +11671,21 @@ BOOLEAN_T decode_native_get_opt(ErlNifEnv* env, ERL_NIF_TERM eVal, return FALSE; if (IS_ATOM(env, nativeOptT[1])) { - unsigned int len; - char t[16]; // Just in case - - if (!(GET_ATOM_LEN(env, nativeOptT[1], &len) && - (len > 0) && - (len <= (sizeof("bool"))))) - return FALSE; - if (!GET_ATOM(env, nativeOptT[1], t, sizeof(t))) - return FALSE; - - if (strncmp(t, "bool", len) == 0) { - *valueType = SOCKET_OPT_VALUE_TYPE_BOOL; - *valueSz = sizeof(int); // Just to be sure - } else if (strncmp(t, "int", len) == 0) { + if (COMPARE(nativeOptT[1], atom_int) == 0) { + SGDBG( ("SOCKET", "decode_native_get_opt -> int\r\n") ); *valueType = SOCKET_OPT_VALUE_TYPE_INT; *valueSz = sizeof(int); // Just to be sure + } else if (COMPARE(nativeOptT[1], atom_bool) == 0) { + SGDBG( ("SOCKET", "decode_native_get_opt -> bool\r\n") ); + *valueType = SOCKET_OPT_VALUE_TYPE_BOOL; + *valueSz = sizeof(int); // Just to be sure } else { return FALSE; } - } else if (IS_NUM(env, nativeOptT[1])) { if (GET_INT(env, nativeOptT[1], valueSz)) { + SGDBG( ("SOCKET", "decode_native_get_opt -> unspec\r\n") ); *valueType = SOCKET_OPT_VALUE_TYPE_UNSPEC; } else { return FALSE; @@ -11582,21 +11694,12 @@ BOOLEAN_T decode_native_get_opt(ErlNifEnv* env, ERL_NIF_TERM eVal, return FALSE; } + SGDBG( ("SOCKET", "decode_native_get_opt -> done\r\n") ); + return TRUE; } -/* -static -void encode_bool(BOOLEAN_T val, ERL_NIF_TERM* eVal) -{ - if (val) - *eVal = esock_atom_true; - else - *eVal = esock_atom_false; -} -*/ - /* +++ encode the ip socket option tos +++ * The (ip) option can be provide as: @@ -12872,6 +12975,7 @@ int on_load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info) atom_association = MKA(env, str_association); atom_assoc_id = MKA(env, str_assoc_id); atom_authentication = MKA(env, str_authentication); + atom_bool = MKA(env, str_bool); atom_close = MKA(env, str_close); atom_closed = MKA(env, str_closed); atom_closing = MKA(env, str_closing); @@ -12886,6 +12990,7 @@ int on_load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info) atom_in6_sockaddr = MKA(env, str_in6_sockaddr); atom_include = MKA(env, str_include); atom_initial = MKA(env, str_initial); + atom_int = MKA(env, str_int); atom_interface = MKA(env, str_interface); atom_iow = MKA(env, str_iow); atom_local_rwnd = MKA(env, str_local_rwnd); @@ -12916,7 +13021,6 @@ int on_load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info) atom_peer_rwnd = MKA(env, str_peer_rwnd); atom_peer_error = MKA(env, str_peer_error); atom_probe = MKA(env, str_probe); - atom_sec = MKA(env, str_sec); atom_select = MKA(env, str_select); atom_sender_dry = MKA(env, str_sender_dry); atom_send_failure = MKA(env, str_send_failure); @@ -12925,7 +13029,6 @@ int on_load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info) atom_sourceaddr = MKA(env, str_sourceaddr); atom_timeout = MKA(env, str_timeout); atom_true = MKA(env, str_true); - atom_usec = MKA(env, str_usec); atom_want = MKA(env, str_want); /* Global atom(s) */ @@ -12968,11 +13071,14 @@ int on_load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info) esock_atom_rights = MKA(env, "rights"); esock_atom_scope_id = MKA(env, "scope_id"); esock_atom_sctp = MKA(env, "sctp"); + esock_atom_sec = MKA(env, "sec"); esock_atom_seqpacket = MKA(env, "seqpacket"); + esock_atom_socket = MKA(env, "socket"); esock_atom_spec_dst = MKA(env, "spec_dst"); esock_atom_stream = MKA(env, "stream"); esock_atom_tcp = MKA(env, "tcp"); esock_atom_throughput = MKA(env, "throughput"); + esock_atom_timestamp = MKA(env, "timestamp"); esock_atom_tos = MKA(env, "tos"); esock_atom_true = MKA(env, "true"); esock_atom_trunc = MKA(env, "trunc"); @@ -12981,6 +13087,7 @@ int on_load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info) esock_atom_udp = MKA(env, "udp"); esock_atom_undefined = MKA(env, "undefined"); esock_atom_unknown = MKA(env, "unknown"); + esock_atom_usec = MKA(env, "usec"); /* Global error codes */ esock_atom_eafnosupport = MKA(env, ESOCK_STR_EAFNOSUPPORT); diff --git a/erts/emulator/nifs/common/socket_util.c b/erts/emulator/nifs/common/socket_util.c index 232e8200df..59cd1a3408 100644 --- a/erts/emulator/nifs/common/socket_util.c +++ b/erts/emulator/nifs/common/socket_util.c @@ -227,6 +227,11 @@ char* esock_encode_sockaddr(ErlNifEnv* env, { char* xres; + UDBG( ("SUTIL", "esock_encode_sockaddr -> entry with" + "\r\n family: %d" + "\r\n addrLen: %d" + "\r\n", sockAddrP->sa.sa_family, addrLen) ); + switch (sockAddrP->sa.sa_family) { case AF_INET: xres = esock_encode_sockaddr_in4(env, &sockAddrP->in4, addrLen, eSockAddr); @@ -860,6 +865,74 @@ char* esock_encode_ip6_address(ErlNifEnv* env, +/* +++ esock_encode_timeval +++ + * + * Encode a timeval struct into its erlang form, a map with two fields: + * + * sec + * usec + * + */ +extern +char* esock_encode_timeval(ErlNifEnv* env, + struct timeval* timeP, + ERL_NIF_TERM* eTime) +{ + ERL_NIF_TERM keys[] = {esock_atom_sec, esock_atom_usec}; + ERL_NIF_TERM vals[] = {MKL(env, timeP->tv_sec), MKL(env, timeP->tv_usec)}; + + unsigned int numKeys = sizeof(keys) / sizeof(ERL_NIF_TERM); + unsigned int numVals = sizeof(vals) / sizeof(ERL_NIF_TERM); + + ESOCK_ASSERT( (numKeys == numVals) ); + + if (!MKMA(env, keys, vals, numKeys, eTime)) + return ESOCK_STR_EINVAL; + + return NULL; +} + + + +/* +++ esock_decode_timeval +++ + * + * Decode a timeval in its erlang form (a map) into its native form, + * a timeval struct. + * + */ +extern +char* esock_decode_timeval(ErlNifEnv* env, + ERL_NIF_TERM eTime, + struct timeval* timeP) +{ + ERL_NIF_TERM eSec, eUSec; + size_t sz; + + // It must be a map + if (!IS_MAP(env, eTime)) + return ESOCK_STR_EINVAL; + + // It must have atleast two attributes + if (!enif_get_map_size(env, eTime, &sz) || (sz < 2)) + return ESOCK_STR_EINVAL; + + if (!GET_MAP_VAL(env, eTime, esock_atom_sec, &eSec)) + return ESOCK_STR_EINVAL; + + if (!GET_MAP_VAL(env, eTime, esock_atom_usec, &eUSec)) + return ESOCK_STR_EINVAL; + + if (!GET_LONG(env, eSec, &timeP->tv_sec)) + return ESOCK_STR_EINVAL; + + if (!GET_LONG(env, eUSec, &timeP->tv_usec)) + return ESOCK_STR_EINVAL; + + return NULL; +} + + + /* +++ esock_decode_domain +++ * * Decode the Erlang form of the 'domain' type, that is: diff --git a/erts/emulator/nifs/common/socket_util.h b/erts/emulator/nifs/common/socket_util.h index 5d720936f3..22eed77d6e 100644 --- a/erts/emulator/nifs/common/socket_util.h +++ b/erts/emulator/nifs/common/socket_util.h @@ -110,6 +110,12 @@ char* esock_encode_ip6_address(ErlNifEnv* env, ERL_NIF_TERM* eAddr); #endif +extern char* esock_encode_timeval(ErlNifEnv* env, + struct timeval* timeP, + ERL_NIF_TERM* eTime); +extern char* esock_decode_timeval(ErlNifEnv* env, + ERL_NIF_TERM eTime, + struct timeval* timeP); extern char* esock_decode_domain(ErlNifEnv* env, ERL_NIF_TERM eDomain, diff --git a/erts/preloaded/ebin/socket.beam b/erts/preloaded/ebin/socket.beam index f6ca653fed..2219b1b271 100644 Binary files a/erts/preloaded/ebin/socket.beam and b/erts/preloaded/ebin/socket.beam differ diff --git a/erts/preloaded/src/socket.erl b/erts/preloaded/src/socket.erl index b9d1705d45..5902c161db 100644 --- a/erts/preloaded/src/socket.erl +++ b/erts/preloaded/src/socket.erl @@ -90,12 +90,13 @@ raw_socket_option/0, timeval/0, - ip_tos_flag/0, + ip_tos/0, ip_mreq/0, ip_mreq_source/0, ip_pmtudisc/0, ip_msfilter_mode/0, ip_msfilter/0, + ip_pktinfo/0, ipv6_mreq/0, ipv6_pmtudisc/0, sctp_event_subscribe/0, @@ -107,6 +108,9 @@ msghdr_flag/0, msghdr_flags/0, msghdr/0, + cmsghdr_level/0, + cmsghdr_type/0, + cmsghdr_data/0, cmsghdr/0, uint8/0, @@ -168,11 +172,11 @@ usec := integer()}. %% 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 ip_tos() :: lowdeley | + throughput | + reliability | + mincost | + integer(). %% This type is used when requesting to become member of a multicast %% group with a call to setopt. Example: @@ -516,14 +520,32 @@ %% Only valid with recvmsg flags => msghdr_flags() }. -%% At some point we should be able to encode/decode the most common types -%% of control message headers. For now, we leave/take the data part raw -%% (as a binary) and leave it to the user to figure out (how to encode/decode -%% that bit). +%% We are able to (completely) decode *some* control message headers. +%% Even if we are able to decode both level and type, we may not be +%% able to decode the data, in which case it will be a binary. +-type ip_pktinfo() :: #{ + ifindex => non_neg_integer(), % Interface Index + spec_dst => ip4_address(), % Local Address + addr => ip4_address() % Header Destination address + }. +-type cmsghdr_level() :: socket | protocol() | integer(). +-type cmsghdr_type() :: timestamp | + rights | + credentials | + tos | + ttl | + origdstaddr | + integer(). +-type cmsghdr_data() :: timeval() | % if level = socket and type = timstamp + ip_pktinfo() | % if level = ip and type = pktinfo + ip_tos() | % if level = ip and type = tos + integer() | % if level = ip and type = ttl + sockaddr_in4() | % if level = ip and type = origdstaddr + binary(). -type cmsghdr() :: #{ - level => protocol() | integer(), - type => integer(), - data => binary() + level => cmsghdr_level(), + type => cmsghdr_type(), + data => cmsghdr_data() }. -define(SOCKET_DOMAIN_LOCAL, 1). @@ -1808,6 +1830,10 @@ do_recvmsg(SockRef, BufSz, CtrlSz, EFlags, Timeout) -> {error, timeout} end; + {error, closed} = ERROR -> + do_close(SockRef), + ERROR; + {error, _Reason} = ERROR -> ERROR diff --git a/lib/kernel/test/socket_server.erl b/lib/kernel/test/socket_server.erl index a804078917..f252be1683 100644 --- a/lib/kernel/test/socket_server.erl +++ b/lib/kernel/test/socket_server.erl @@ -21,17 +21,19 @@ -module(socket_server). -export([ - start/0, start/4, - start_tcp/0, start_tcp/1, start_tcp/2, - start_tcp4/0, start_tcp4/1, start_tcp6/0, start_tcp6/1, - start_udp/0, start_udp/1, start_udp/2, - start_udp4/0, start_udp4/1, start_udp6/0, start_udp6/1, + start/0, start/5, + start_tcp/0, start_tcp/1, start_tcp/3, + start_tcp4/0, start_tcp4/1, start_tcp4/2, + start_tcp6/0, start_tcp6/1, start_tcp6/2, + start_udp/0, start_udp/1, start_udp/3, + start_udp4/0, start_udp4/1, start_udp4/2, + start_udp6/0, start_udp6/1, start_udp6/2, start_sctp/0, start_sctp/1 ]). -define(LIB, socket_lib). --record(manager, {socket, peek, acceptors, handler_id, handlers}). +-record(manager, {socket, msg, peek, acceptors, handler_id, handlers}). -record(acceptor, {id, socket, manager}). -record(handler, {socket, peek, msg, type, manager}). @@ -50,16 +52,22 @@ start_tcp4() -> start_tcp4(false). start_tcp4(Peek) -> - start_tcp(inet, Peek). + start_tcp4(false, Peek). + +start_tcp4(UseMsg, Peek) -> + start_tcp(inet, UseMsg, Peek). start_tcp6() -> start_tcp6(false). start_tcp6(Peek) -> - start_tcp(inet6, Peek). + start_tcp6(false, Peek). + +start_tcp6(UseMsg, Peek) -> + start_tcp(inet6, UseMsg, Peek). -start_tcp(Domain, Peek) when is_boolean(Peek) -> - start(Domain, stream, tcp, Peek). +start_tcp(Domain, UseMsg, Peek) when is_boolean(UseMsg) andalso is_boolean(Peek) -> + start(Domain, stream, tcp, UseMsg, Peek). start_udp() -> start_udp4(). @@ -71,28 +79,34 @@ start_udp4() -> start_udp4(false). start_udp4(Peek) -> - start_udp(inet, Peek). + start_udp4(false, Peek). + +start_udp4(UseMsg, Peek) -> + start_udp(inet, UseMsg, Peek). start_udp6() -> - start_udp6(false). + start_udp6(false, false). start_udp6(Peek) -> - start_udp(inet6, Peek). + start_udp6(false, Peek). + +start_udp6(UseMsg, Peek) -> + start_udp(inet6, UseMsg, Peek). -start_udp(Domain, Peek) when is_boolean(Peek) -> - start(Domain, dgram, udp, Peek). +start_udp(Domain, UseMsg, Peek) when is_boolean(UseMsg) andalso is_boolean(Peek) -> + start(Domain, dgram, udp, UseMsg, Peek). start_sctp() -> start_sctp(inet). start_sctp(Domain) when ((Domain =:= inet) orelse (Domain =:= inet6)) -> - start(Domain, seqpacket, sctp, false). + start(Domain, seqpacket, sctp, true, false). -start(Domain, Type, Proto, Peek) -> +start(Domain, Type, Proto, UseMsg, Peek) -> put(sname, "starter"), i("try start manager"), - {Pid, MRef} = manager_start(Domain, Type, Proto, Peek), + {Pid, MRef} = manager_start(Domain, Type, Proto, UseMsg, Peek), i("manager (~p) started", [Pid]), loop(Pid, MRef). @@ -107,8 +121,8 @@ loop(Pid, MRef) -> %% ========================================================================= -manager_start(Domain, Type, Proto, Peek) -> - spawn_monitor(fun() -> manager_init(Domain, Type, Proto, Peek) end). +manager_start(Domain, Type, Proto, UseMsg, Peek) -> + spawn_monitor(fun() -> manager_init(Domain, Type, Proto, UseMsg, Peek) end). manager_start_handler(Pid, Sock) -> manager_request(Pid, {start_handler, Sock}). @@ -123,19 +137,20 @@ manager_reply(Pid, Ref, Reply) -> ?LIB:reply(manager, Pid, Ref, Reply). -manager_init(Domain, Type, Proto, Peek) -> +manager_init(Domain, Type, Proto, UseMsg, Peek) -> put(sname, "manager"), - do_manager_init(Domain, Type, Proto, Peek). + do_manager_init(Domain, Type, Proto, UseMsg, Peek). -do_manager_init(Domain, stream = Type, Proto, Peek) -> +do_manager_init(Domain, stream = Type, Proto, UseMsg, Peek) -> i("try start acceptor(s)"), {Sock, Acceptors} = manager_stream_init(Domain, Type, Proto), manager_loop(#manager{socket = Sock, + msg = UseMsg, peek = Peek, acceptors = Acceptors, handler_id = 1, handlers = []}); -do_manager_init(Domain, dgram = Type, Proto, Peek) -> +do_manager_init(Domain, dgram = Type, Proto, UseMsg, Peek) -> i("try open socket"), case socket:open(Domain, Type, Proto) of {ok, Sock} -> @@ -179,11 +194,12 @@ do_manager_init(Domain, dgram = Type, Proto, Peek) -> {ok, Name} -> f("~p", [Name]); {error, R} -> f("error: ~p", [R]) end]), - case handler_start(1, true, Sock, Peek) of + case handler_start(1, Sock, UseMsg, Peek) of {ok, {Pid, MRef}} -> i("handler (~p) started", [Pid]), handler_continue(Pid), manager_loop(#manager{peek = Peek, + msg = UseMsg, handler_id = 2, % Just in case handlers = [{1, Pid, MRef}]}); {error, SReason} -> @@ -196,7 +212,7 @@ do_manager_init(Domain, dgram = Type, Proto, Peek) -> "~n ~p", [OReason]), exit({failed_open_socket, OReason}) end; -do_manager_init(Domain, seqpacket = Type, sctp = Proto, _Peek) -> +do_manager_init(Domain, seqpacket = Type, sctp = Proto, _UseMsg, _Peek) -> %% This is as far as I have got with SCTP at the moment... case socket:open(Domain, Type, Proto) of {ok, Sock} -> @@ -248,9 +264,9 @@ do_manager_init(Domain, seqpacket = Type, sctp = Proto, _Peek) -> {error, Reason} -> exit({failed_open, Reason}) end; -do_manager_init(Domain, raw = Type, Proto, Peek) when is_integer(Proto) -> - do_manager_init(Domain, Type, {raw, Proto}, Peek); -do_manager_init(Domain, raw = Type, Proto, _Peek) -> +do_manager_init(Domain, raw = Type, Proto, UseMsg, Peek) when is_integer(Proto) -> + do_manager_init(Domain, Type, {raw, Proto}, UseMsg, Peek); +do_manager_init(Domain, raw = Type, Proto, _UseMsg, _Peek) -> case socket:open(Domain, Type, Proto) of {ok, Sock} -> i("(sctp) socket opened: " @@ -401,11 +417,12 @@ manager_handle_down(#manager{acceptors = Acceptors, manager_handle_request(#manager{peek = Peek, + msg = UseMsg, handler_id = HID, handlers = Handlers} = M, Pid, Ref, {start_handler, Sock}) -> i("try start handler (~w)", [HID]), - case handler_start(HID, false, Sock, Peek) of + case handler_start(HID, Sock, UseMsg, Peek) of {ok, {HPid, HMRef}} -> i("handler ~w started", [HID]), manager_reply(Pid, Ref, {ok, HPid}), @@ -564,10 +581,10 @@ acceptor_handle_accept_success(#acceptor{manager = Manager}, Sock) -> %% ========================================================================= -handler_start(ID, Msg, Sock, Peek) -> +handler_start(ID, Sock, UseMsg, Peek) -> Self = self(), H = {Pid, _} = spawn_monitor(fun() -> - handler_init(Self, ID, Msg, Peek, Sock) + handler_init(Self, ID, UseMsg, Peek, Sock) end), receive {handler, Pid, ok} -> @@ -720,7 +737,16 @@ handler_init(Manager, ID, Msg, Peek, Sock) -> RtHdr, AuthHdr, HopLimit, HopOpts, DstOpts, FlowInfo, UHops]), - SIP = + %% ok = socket:setopt(Sock, otp, debug, true), + %% case socket:getopt(Sock, 0, {13, int}) of + %% {ok, Val} -> + %% i("PktOpts ok: ~p", [Val]); + %% {error, Reason} -> + %% e("PktOpts err: ~p", [Reason]) + %% end, + %% ok = socket:setopt(Sock, otp, debug, false), + SSO = fun(O, V) -> soso(Sock, O, V) end, + SIP4 = fun(O, V) -> if (Type =:= dgram) -> @@ -729,8 +755,10 @@ handler_init(Manager, ID, Msg, Peek, Sock) -> ok end end, + SSO(timestamp, true), + SIP4(pktinfo, true), ok = soip(Sock, recvtos, true), - SIP(recvttl, true), + SIP4(recvttl, true), ok = soip(Sock, recvorigdstaddr, true), handler_loop(#handler{msg = Msg, @@ -743,8 +771,8 @@ handler_init(Manager, ID, Msg, Peek, Sock) -> so(Sock, Lvl, Opt, Val) -> ok = socket:setopt(Sock, Lvl, Opt, Val). -%% soso(Sock, Opt, Val) -> -%% so(Sock, socket, Opt, Val). +soso(Sock, Opt, Val) -> + so(Sock, socket, Opt, Val). soip(Sock, Opt, Val) -> so(Sock, ip, Opt, Val). @@ -791,16 +819,29 @@ handler_loop(H) -> recv(#handler{peek = true, socket = Sock, type = stream}) -> peek_recv(Sock); -recv(#handler{peek = false, socket = Sock, type = stream}) -> - do_recv(Sock); +recv(#handler{socket = Sock, msg = true, type = stream}) -> + case socket:recvmsg(Sock) of + {ok, #{addr := undefined = Source, + iov := [Data], + ctrl := CMsgHdrs, + flags := Flags}} -> + i("received message: " + "~n CMsgHdrs: ~p" + "~n Flags: ~p", [CMsgHdrs, Flags]), + {ok, {Source, Data}}; + {ok, X} -> + e("received *unexpected* message: " + "~n ~p", [X]), + {error, {unexpected, X}}; + {error, _} = ERROR -> + ERROR + end; recv(#handler{socket = Sock, msg = true, type = dgram}) -> - %% ok = socket:setopt(Sock, otp, debug, true), case socket:recvmsg(Sock) of {ok, #{addr := Source, iov := [Data], ctrl := CMsgHdrs, flags := Flags}} -> - %% ok = socket:setopt(Sock, otp, debug, false), i("received message: " "~n CMsgHdrs: ~p" "~n Flags: ~p", [CMsgHdrs, Flags]), @@ -810,6 +851,8 @@ recv(#handler{socket = Sock, msg = true, type = dgram}) -> {error, _} = ERROR -> ERROR end; +recv(#handler{peek = false, socket = Sock, type = stream}) -> + do_recv(Sock); recv(#handler{peek = Peek, socket = Sock, type = dgram}) when (Peek =:= true) -> %% ok = socket:setopt(Sock, otp, debug, true), -- cgit v1.2.3