diff --git a/erts/config.h.in b/erts/config.h.in index 3831b8be2723..eb7b978a1f47 100644 --- a/erts/config.h.in +++ b/erts/config.h.in @@ -1064,8 +1064,29 @@ /* Define to 1 if the system has the type 'struct ip_mreqn'. */ #undef HAVE_STRUCT_IP_MREQN -/* Define to 1 if 'assoc_id' is a member of 'struct sctp_accoc_value'. */ -#undef HAVE_STRUCT_SCTP_ACCOC_VALUE_ASSOC_ID +/* Define to 1 if 'sas_assoc_id' is a member of 'struct sctp_assoc_stats'. */ +#undef HAVE_STRUCT_SCTP_ASSOC_STATS_SAS_ASSOC_ID + +/* Define to 1 if 'sas_ipackets' is a member of 'struct sctp_assoc_stats'. */ +#undef HAVE_STRUCT_SCTP_ASSOC_STATS_SAS_IPACKETS + +/* Define to 1 if 'sas_obs_rto_ipaddr' is a member of 'struct + sctp_assoc_stats'. */ +#undef HAVE_STRUCT_SCTP_ASSOC_STATS_SAS_OBS_RTO_IPADDR + +/* Define to 1 if 'sas_opackets' is a member of 'struct sctp_assoc_stats'. */ +#undef HAVE_STRUCT_SCTP_ASSOC_STATS_SAS_OPACKETS + +/* Define to 1 if 'sas_outofseqtsns' is a member of 'struct sctp_assoc_stats'. + */ +#undef HAVE_STRUCT_SCTP_ASSOC_STATS_SAS_OUTOFSEQTSNS + +/* Define to 1 if 'assoc_id' is a member of 'struct sctp_assoc_value'. */ +#undef HAVE_STRUCT_SCTP_ASSOC_VALUE_ASSOC_ID + +/* Define to 1 if 'sctp_assoc_reset_event' is a member of 'struct + sctp_event_subscribe'. */ +#undef HAVE_STRUCT_SCTP_EVENT_SUBSCRIBE_SCTP_ASSOC_RESET_EVENT /* Define to 1 if 'sctp_authentication_event' is a member of 'struct sctp_event_subscribe'. */ @@ -1075,18 +1096,43 @@ sctp_event_subscribe'. */ #undef HAVE_STRUCT_SCTP_EVENT_SUBSCRIBE_SCTP_SENDER_DRY_EVENT +/* Define to 1 if 'sctp_stream_change_event' is a member of 'struct + sctp_event_subscribe'. */ +#undef HAVE_STRUCT_SCTP_EVENT_SUBSCRIBE_SCTP_STREAM_CHANGE_EVENT + +/* Define to 1 if 'sctp_stream_reset_event' is a member of 'struct + sctp_event_subscribe'. */ +#undef HAVE_STRUCT_SCTP_EVENT_SUBSCRIBE_SCTP_STREAM_RESET_EVENT + +/* Define to 1 if 'spp_dscp' is a member of 'struct sctp_paddrparams'. */ +#undef HAVE_STRUCT_SCTP_PADDRPARAMS_SPP_DSCP + /* Define to 1 if 'spp_flags' is a member of 'struct sctp_paddrparams'. */ #undef HAVE_STRUCT_SCTP_PADDRPARAMS_SPP_FLAGS /* Define to 1 if 'spp_pathmtu' is a member of 'struct sctp_paddrparams'. */ #undef HAVE_STRUCT_SCTP_PADDRPARAMS_SPP_PATHMTU +/* Define to 1 if 'spp_pv6_flowlabel' is a member of 'struct + sctp_paddrparams'. */ +#undef HAVE_STRUCT_SCTP_PADDRPARAMS_SPP_PV6_FLOWLABEL + /* Define to 1 if 'spp_sackdelay' is a member of 'struct sctp_paddrparams'. */ #undef HAVE_STRUCT_SCTP_PADDRPARAMS_SPP_SACKDELAY +/* Define to 1 if 'pdapi_seq' is a member of 'struct sctp_pdapi_event'. */ +#undef HAVE_STRUCT_SCTP_PDAPI_EVENT_PDAPI_SEQ + +/* Define to 1 if 'pdapi_stream' is a member of 'struct sctp_pdapi_event'. */ +#undef HAVE_STRUCT_SCTP_PDAPI_EVENT_PDAPI_STREAM + /* Define to 1 if 'sre_data' is a member of 'struct sctp_remote_error'. */ #undef HAVE_STRUCT_SCTP_REMOTE_ERROR_SRE_DATA +/* Define to 1 if 'ssfe_data' is a member of 'struct sctp_send_failed_event'. + */ +#undef HAVE_STRUCT_SCTP_SEND_FAILED_EVENT_SSFE_DATA + /* Define to 1 if 'ssf_data' is a member of 'struct sctp_send_failed'. */ #undef HAVE_STRUCT_SCTP_SEND_FAILED_SSF_DATA diff --git a/erts/configure b/erts/configure index 74b24de63aea..f51c9da1e956 100755 --- a/erts/configure +++ b/erts/configure @@ -18678,16 +18678,16 @@ then : fi - ac_fn_c_check_member "$LINENO" "struct sctp_accoc_value" "assoc_id" "ac_cv_member_struct_sctp_accoc_value_assoc_id" "#if HAVE_SYS_SOCKET_H + ac_fn_c_check_member "$LINENO" "struct sctp_assoc_value" "assoc_id" "ac_cv_member_struct_sctp_assoc_value_assoc_id" "#if HAVE_SYS_SOCKET_H #include #endif #include " -if test "x$ac_cv_member_struct_sctp_accoc_value_assoc_id" = xyes +if test "x$ac_cv_member_struct_sctp_assoc_value_assoc_id" = xyes then : -printf "%s\n" "#define HAVE_STRUCT_SCTP_ACCOC_VALUE_ASSOC_ID 1" >>confdefs.h +printf "%s\n" "#define HAVE_STRUCT_SCTP_ASSOC_VALUE_ASSOC_ID 1" >>confdefs.h fi @@ -19099,7 +19099,72 @@ esac fi printf "%s\n" "#define HAVE_DECL_SCTPS_SHUTDOWN_ACK_SENT $ac_have_decl" >>confdefs.h - ac_fn_c_check_member "$LINENO" "struct sctp_paddrparams" "spp_pathmtu" "ac_cv_member_struct_sctp_paddrparams_spp_pathmtu" "#if HAVE_SYS_SOCKET_H + ac_fn_c_check_member "$LINENO" "struct sctp_assoc_stats" "sas_assoc_id" "ac_cv_member_struct_sctp_assoc_stats_sas_assoc_id" "#if HAVE_SYS_SOCKET_H + #include + #endif + #include + +" +if test "x$ac_cv_member_struct_sctp_assoc_stats_sas_assoc_id" = xyes +then : + +printf "%s\n" "#define HAVE_STRUCT_SCTP_ASSOC_STATS_SAS_ASSOC_ID 1" >>confdefs.h + + +fi +ac_fn_c_check_member "$LINENO" "struct sctp_assoc_stats" "sas_obs_rto_ipaddr" "ac_cv_member_struct_sctp_assoc_stats_sas_obs_rto_ipaddr" "#if HAVE_SYS_SOCKET_H + #include + #endif + #include + +" +if test "x$ac_cv_member_struct_sctp_assoc_stats_sas_obs_rto_ipaddr" = xyes +then : + +printf "%s\n" "#define HAVE_STRUCT_SCTP_ASSOC_STATS_SAS_OBS_RTO_IPADDR 1" >>confdefs.h + + +fi +ac_fn_c_check_member "$LINENO" "struct sctp_assoc_stats" "sas_opackets" "ac_cv_member_struct_sctp_assoc_stats_sas_opackets" "#if HAVE_SYS_SOCKET_H + #include + #endif + #include + +" +if test "x$ac_cv_member_struct_sctp_assoc_stats_sas_opackets" = xyes +then : + +printf "%s\n" "#define HAVE_STRUCT_SCTP_ASSOC_STATS_SAS_OPACKETS 1" >>confdefs.h + + +fi +ac_fn_c_check_member "$LINENO" "struct sctp_assoc_stats" "sas_ipackets" "ac_cv_member_struct_sctp_assoc_stats_sas_ipackets" "#if HAVE_SYS_SOCKET_H + #include + #endif + #include + +" +if test "x$ac_cv_member_struct_sctp_assoc_stats_sas_ipackets" = xyes +then : + +printf "%s\n" "#define HAVE_STRUCT_SCTP_ASSOC_STATS_SAS_IPACKETS 1" >>confdefs.h + + +fi +ac_fn_c_check_member "$LINENO" "struct sctp_assoc_stats" "sas_outofseqtsns" "ac_cv_member_struct_sctp_assoc_stats_sas_outofseqtsns" "#if HAVE_SYS_SOCKET_H + #include + #endif + #include + +" +if test "x$ac_cv_member_struct_sctp_assoc_stats_sas_outofseqtsns" = xyes +then : + +printf "%s\n" "#define HAVE_STRUCT_SCTP_ASSOC_STATS_SAS_OUTOFSEQTSNS 1" >>confdefs.h + + +fi +ac_fn_c_check_member "$LINENO" "struct sctp_paddrparams" "spp_pathmtu" "ac_cv_member_struct_sctp_paddrparams_spp_pathmtu" "#if HAVE_SYS_SOCKET_H #include #endif #include @@ -19137,6 +19202,58 @@ then : printf "%s\n" "#define HAVE_STRUCT_SCTP_PADDRPARAMS_SPP_FLAGS 1" >>confdefs.h +fi +ac_fn_c_check_member "$LINENO" "struct sctp_paddrparams" "spp_pv6_flowlabel" "ac_cv_member_struct_sctp_paddrparams_spp_pv6_flowlabel" "#if HAVE_SYS_SOCKET_H + #include + #endif + #include + +" +if test "x$ac_cv_member_struct_sctp_paddrparams_spp_pv6_flowlabel" = xyes +then : + +printf "%s\n" "#define HAVE_STRUCT_SCTP_PADDRPARAMS_SPP_PV6_FLOWLABEL 1" >>confdefs.h + + +fi +ac_fn_c_check_member "$LINENO" "struct sctp_paddrparams" "spp_dscp" "ac_cv_member_struct_sctp_paddrparams_spp_dscp" "#if HAVE_SYS_SOCKET_H + #include + #endif + #include + +" +if test "x$ac_cv_member_struct_sctp_paddrparams_spp_dscp" = xyes +then : + +printf "%s\n" "#define HAVE_STRUCT_SCTP_PADDRPARAMS_SPP_DSCP 1" >>confdefs.h + + +fi +ac_fn_c_check_member "$LINENO" "struct sctp_pdapi_event" "pdapi_stream" "ac_cv_member_struct_sctp_pdapi_event_pdapi_stream" "#if HAVE_SYS_SOCKET_H + #include + #endif + #include + +" +if test "x$ac_cv_member_struct_sctp_pdapi_event_pdapi_stream" = xyes +then : + +printf "%s\n" "#define HAVE_STRUCT_SCTP_PDAPI_EVENT_PDAPI_STREAM 1" >>confdefs.h + + +fi +ac_fn_c_check_member "$LINENO" "struct sctp_pdapi_event" "pdapi_seq" "ac_cv_member_struct_sctp_pdapi_event_pdapi_seq" "#if HAVE_SYS_SOCKET_H + #include + #endif + #include + +" +if test "x$ac_cv_member_struct_sctp_pdapi_event_pdapi_seq" = xyes +then : + +printf "%s\n" "#define HAVE_STRUCT_SCTP_PDAPI_EVENT_PDAPI_SEQ 1" >>confdefs.h + + fi ac_fn_c_check_member "$LINENO" "struct sctp_remote_error" "sre_data" "ac_cv_member_struct_sctp_remote_error_sre_data" "#if HAVE_SYS_SOCKET_H #include @@ -19163,6 +19280,19 @@ then : printf "%s\n" "#define HAVE_STRUCT_SCTP_SEND_FAILED_SSF_DATA 1" >>confdefs.h +fi +ac_fn_c_check_member "$LINENO" "struct sctp_send_failed_event" "ssfe_data" "ac_cv_member_struct_sctp_send_failed_event_ssfe_data" "#if HAVE_SYS_SOCKET_H + #include + #endif + #include + +" +if test "x$ac_cv_member_struct_sctp_send_failed_event_ssfe_data" = xyes +then : + +printf "%s\n" "#define HAVE_STRUCT_SCTP_SEND_FAILED_EVENT_SSFE_DATA 1" >>confdefs.h + + fi ac_fn_c_check_member "$LINENO" "struct sctp_event_subscribe" "sctp_authentication_event" "ac_cv_member_struct_sctp_event_subscribe_sctp_authentication_event" "#if HAVE_SYS_SOCKET_H #include @@ -19189,6 +19319,45 @@ then : printf "%s\n" "#define HAVE_STRUCT_SCTP_EVENT_SUBSCRIBE_SCTP_SENDER_DRY_EVENT 1" >>confdefs.h +fi +ac_fn_c_check_member "$LINENO" "struct sctp_event_subscribe" "sctp_stream_reset_event" "ac_cv_member_struct_sctp_event_subscribe_sctp_stream_reset_event" "#if HAVE_SYS_SOCKET_H + #include + #endif + #include + +" +if test "x$ac_cv_member_struct_sctp_event_subscribe_sctp_stream_reset_event" = xyes +then : + +printf "%s\n" "#define HAVE_STRUCT_SCTP_EVENT_SUBSCRIBE_SCTP_STREAM_RESET_EVENT 1" >>confdefs.h + + +fi +ac_fn_c_check_member "$LINENO" "struct sctp_event_subscribe" "sctp_assoc_reset_event" "ac_cv_member_struct_sctp_event_subscribe_sctp_assoc_reset_event" "#if HAVE_SYS_SOCKET_H + #include + #endif + #include + +" +if test "x$ac_cv_member_struct_sctp_event_subscribe_sctp_assoc_reset_event" = xyes +then : + +printf "%s\n" "#define HAVE_STRUCT_SCTP_EVENT_SUBSCRIBE_SCTP_ASSOC_RESET_EVENT 1" >>confdefs.h + + +fi +ac_fn_c_check_member "$LINENO" "struct sctp_event_subscribe" "sctp_stream_change_event" "ac_cv_member_struct_sctp_event_subscribe_sctp_stream_change_event" "#if HAVE_SYS_SOCKET_H + #include + #endif + #include + +" +if test "x$ac_cv_member_struct_sctp_event_subscribe_sctp_stream_change_event" = xyes +then : + +printf "%s\n" "#define HAVE_STRUCT_SCTP_EVENT_SUBSCRIBE_SCTP_STREAM_CHANGE_EVENT 1" >>confdefs.h + + fi diff --git a/erts/configure.ac b/erts/configure.ac index 9ed8c5e39862..5dea338cc212 100644 --- a/erts/configure.ac +++ b/erts/configure.ac @@ -1806,13 +1806,15 @@ case "x$enable_sctp" in fi;; esac +dnl Check how the members of struct sctp_send_failed_event +dnl are named: on linux prefixed by ssf_ but on freesd by ssfe_ AS_IF([test x"$ac_cv_header_netinet_sctp_h" = x"yes"], [ AS_IF([test "x$enable_sctp" = "xlib"], AC_CHECK_LIB(sctp, sctp_bindx)) AC_CHECK_FUNCS([sctp_bindx sctp_peeloff sctp_getladdrs sctp_freeladdrs sctp_getpaddrs sctp_freepaddrs \ sctp_connectx]) - AC_CHECK_MEMBERS([struct sctp_accoc_value.assoc_id], [], [], + AC_CHECK_MEMBERS([struct sctp_assoc_value.assoc_id], [], [], [#if HAVE_SYS_SOCKET_H #include #endif @@ -1837,13 +1839,26 @@ AS_IF([test x"$ac_cv_header_netinet_sctp_h" = x"yes"], #endif #include ]) - AC_CHECK_MEMBERS([struct sctp_paddrparams.spp_pathmtu, + AC_CHECK_MEMBERS([struct sctp_assoc_stats.sas_assoc_id, + struct sctp_assoc_stats.sas_obs_rto_ipaddr, + struct sctp_assoc_stats.sas_opackets, + struct sctp_assoc_stats.sas_ipackets, + struct sctp_assoc_stats.sas_outofseqtsns, + struct sctp_paddrparams.spp_pathmtu, struct sctp_paddrparams.spp_sackdelay, struct sctp_paddrparams.spp_flags, + struct sctp_paddrparams.spp_pv6_flowlabel, + struct sctp_paddrparams.spp_dscp, + struct sctp_pdapi_event.pdapi_stream, + struct sctp_pdapi_event.pdapi_seq, struct sctp_remote_error.sre_data, struct sctp_send_failed.ssf_data, + struct sctp_send_failed_event.ssfe_data, struct sctp_event_subscribe.sctp_authentication_event, - struct sctp_event_subscribe.sctp_sender_dry_event], [], [], + struct sctp_event_subscribe.sctp_sender_dry_event, + struct sctp_event_subscribe.sctp_stream_reset_event, + struct sctp_event_subscribe.sctp_assoc_reset_event, + struct sctp_event_subscribe.sctp_stream_change_event], [], [], [#if HAVE_SYS_SOCKET_H #include #endif diff --git a/erts/emulator/Makefile.in b/erts/emulator/Makefile.in index 5d6c12c8a544..8d86c2e95a01 100644 --- a/erts/emulator/Makefile.in +++ b/erts/emulator/Makefile.in @@ -306,6 +306,10 @@ LIB_SUFFIX=.a EXE_SUFFIX= endif +ifeq ($(findstring solaris,$(TARGET)),solaris) +ESOCK_CFLAGS = -DESOCK_NON_EMPTY_IOV +endif + ifeq (@EMU_LOCK_CHECKING@,yes) NO_INLINE_FUNCTIONS=true endif @@ -944,13 +948,13 @@ $(OBJDIR)/%.o: drivers/$(ERLANG_OSTYPE)/%.c $(V_CC) $(CFLAGS) $(INCLUDES) -Idrivers/common -Idrivers/$(ERLANG_OSTYPE) -I../etc/$(ERLANG_OSTYPE) -c $< -o $@ $(OBJDIR)/%.o: nifs/common/%.c - $(V_CC) $(CFLAGS) -DLIBSCTP=$(LIBSCTP) $(INCLUDES) -Inifs/common -Inifs/$(ERLANG_OSTYPE) -c $< -o $@ + $(V_CC) $(CFLAGS) $(ESOCK_CFLAGS) -DLIBSCTP=$(LIBSCTP) $(INCLUDES) -Inifs/common -Inifs/$(ERLANG_OSTYPE) -c $< -o $@ $(OBJDIR)/%.o: nifs/common/%.cpp - $(V_CXX) $(CFLAGS) -DLIBSCTP=$(LIBSCTP) $(INCLUDES) -Inifs/common -Inifs/$(ERLANG_OSTYPE) -c $< -o $@ + $(V_CXX) $(CFLAGS) $(ESOCK_CFLAGS) -DLIBSCTP=$(LIBSCTP) $(INCLUDES) -Inifs/common -Inifs/$(ERLANG_OSTYPE) -c $< -o $@ $(OBJDIR)/%.o: nifs/$(ERLANG_OSTYPE)/%.c - $(V_CC) $(CFLAGS) $(INCLUDES) -Inifs/common -Inifs/$(ERLANG_OSTYPE) -I../etc/$(ERLANG_OSTYPE) -c $< -o $@ + $(V_CC) $(CFLAGS) $(ESOCK_CFLAGS) -DLIBSCTP=$(LIBSCTP) $(INCLUDES) -Inifs/common -Inifs/$(ERLANG_OSTYPE) -I../etc/$(ERLANG_OSTYPE) -c $< -o $@ # # Build the beamasm sources. @@ -1488,10 +1492,12 @@ COMPDB_CXXFLAGS=-MM $(filter-out -fno-tree-copyrename,$(CXXFLAGS)) $(DEP_INCLUDE compdb: clang -MJ $(TTF_DIR)/beam.json $(COMPDB_CFLAGS) $(BEAM_SRC) > /dev/null clang++ -MJ $(TTF_DIR)/beam.cpp.json $(COMPDB_CXXFLAGS) $(BEAM_CPP_SRC) > /dev/null - clang -MJ $(TTF_DIR)/drv.json $(COMPDB_CFLAGS) -DLIBSCTP=$(LIBSCTP) $(DRV_COMMON_SRC) > /dev/null + clang -MJ $(TTF_DIR)/drv.json \ + $(COMPDB_CFLAGS) -DLIBSCTP=$(LIBSCTP) $(DRV_COMMON_SRC) > /dev/null clang -MJ $(TTF_DIR)/drv_$(ERLANG_OSTYPE).json \ $(COMPDB_CFLAGS) -I../etc/$(ERLANG_OSTYPE) $(DRV_OSTYPE_SRC) > /dev/null - clang -MJ $(TTF_DIR)/nif.json $(COMPDB_CFLAGS) $(NIF_COMMON_SRC) > /dev/null + clang -MJ $(TTF_DIR)/nif.json \ + $(COMPDB_CFLAGS) -DLIBSCTP=$(LIBSCTP) $(NIF_COMMON_SRC) > /dev/null clang -MJ $(TTF_DIR)/nif_$(ERLANG_OSTYPE).json \ $(COMPDB_CFLAGS) -I../etc/$(ERLANG_OSTYPE) $(NIF_OSTYPE_SRC) > /dev/null clang -MJ $(TTF_DIR)/sys.json $(COMPDB_CFLAGS) $(SYS_SRC) > /dev/null diff --git a/erts/emulator/nifs/common/prim_socket_int.h b/erts/emulator/nifs/common/prim_socket_int.h index 919cdaff5e8d..fb625b7d76d8 100644 --- a/erts/emulator/nifs/common/prim_socket_int.h +++ b/erts/emulator/nifs/common/prim_socket_int.h @@ -105,6 +105,11 @@ * ********************************************************************* * */ +#define ESOCK_IDENTITY(c) c +#define ESOCK_STRINGIFY_1(b) ESOCK_IDENTITY(#b) +#define ESOCK_STRINGIFY(a) ESOCK_STRINGIFY_1(a) + + #define ESOCK_GET_RESOURCE(ENV, REF, RES) \ enif_get_resource((ENV), (REF), esocks, (RES)) @@ -318,6 +323,16 @@ extern const int esock_msg_flags_length; extern const ESockFlag esock_ioctl_flags[]; extern const int esock_ioctl_flags_length; +#if defined(HAVE_SCTP) +typedef sctp_assoc_t ESockAssocId; +#else +/* This is just a placeholder. + * If we don *not* have sctp support, + * then this will never actually be used. + */ +typedef int ESockAssocId; +#endif + /* ********************************************************************* * * The socket nif global info * @@ -613,6 +628,17 @@ extern ERL_NIF_TERM esock_make_monitor_term(ErlNifEnv* env, extern BOOLEAN_T esock_monitor_eq(const ESockMonitor* monP, const ErlNifMonitor* mon); +/* SCTP Functions */ +#if defined(HAVE_SCTP) + +#if defined(SCTP_SNDRCV) +extern BOOLEAN_T esock_cmsg_encode_sctp_sndrcv(ErlNifEnv *env, + unsigned char *data, + size_t dataLen, + ERL_NIF_TERM *eResult); +#endif + +#endif /* *** Counter functions *** */ extern BOOLEAN_T esock_cnt_inc(ESockCounter* cnt, ESockCounter inc); diff --git a/erts/emulator/nifs/common/prim_socket_nif.c b/erts/emulator/nifs/common/prim_socket_nif.c index ac76306b80e6..6c16c5f709d6 100644 --- a/erts/emulator/nifs/common/prim_socket_nif.c +++ b/erts/emulator/nifs/common/prim_socket_nif.c @@ -315,8 +315,8 @@ ERL_NIF_INIT(prim_socket, esock_funcs, on_load, NULL, NULL, NULL) # undef HAVE_DECL_SCTP_SHUTDOWN_ACK_SENT # define HAVE_DECL_SCTP_SHUTDOWN_ACK_SENT 1 #endif -/* New spelling in lksctp 2.6.22 or maybe even earlier: - * adaption -> adaptation +/* New spelling inn lksctp 2.6.22 or maybe even earlier: + * adaption -> ad_taptation */ #if !defined(SCTP_ADAPTATION_LAYER) && defined (SCTP_ADAPTION_LAYER) # define SCTP_ADAPTATION_LAYER SCTP_ADAPTION_LAYER @@ -523,6 +523,8 @@ const ESockFlag esock_msg_flags[] = { #endif &esock_atom_errqueue}, + /* If this is an SCTP socket, this is actually used for + * 'MSG_NOTIFICATION' */ { #ifdef MSG_MORE MSG_MORE, @@ -561,7 +563,15 @@ const ESockFlag esock_msg_flags[] = { #else 0, #endif - &esock_atom_trunc} + &esock_atom_trunc}, + + { +#if defined(HAVE_SCTP) && defined(MSG_NOTIFICATION) + MSG_NOTIFICATION, +#else + 0, +#endif + &esock_atom_notification} }; const int esock_msg_flags_length = NUM(esock_msg_flags); @@ -971,7 +981,7 @@ static unsigned long one_value = 1; #define sock_errno() errno #define sock_getopt(s,t,n,v,l) getsockopt((s),(t),(n),(v),(l)) // #define sock_htons(x) htons((x)) -// #define sock_htonl(x) htonl((x)) +#define sock_htonl(x) htonl((x)) #define sock_listen(s, b) listen((s), (b)) #define sock_name(s, addr, len) getsockname((s), (addr), (len)) // #define sock_ntohs(x) ntohs((x)) @@ -1043,6 +1053,7 @@ ESockSendfileCounters initESockSendfileCounters = * nif_connect * nif_listen * nif_accept + * nif_peeloff * nif_send * nif_sendto * nif_sendmsg @@ -1056,7 +1067,9 @@ ESockSendfileCounters initESockSendfileCounters = * nif_setopt * nif_getopt * nif_sockname + * nif_socknames * nif_peername + * nif_peernames * nif_ioctl * nif_finalize_close * nif_cancel @@ -1071,6 +1084,7 @@ ESockSendfileCounters initESockSendfileCounters = ESOCK_NIF_FUNC_DEF(connect); \ ESOCK_NIF_FUNC_DEF(listen); \ ESOCK_NIF_FUNC_DEF(accept); \ + ESOCK_NIF_FUNC_DEF(peeloff); \ ESOCK_NIF_FUNC_DEF(send); \ ESOCK_NIF_FUNC_DEF(sendto); \ ESOCK_NIF_FUNC_DEF(sendmsg); \ @@ -1082,7 +1096,9 @@ ESockSendfileCounters initESockSendfileCounters = ESOCK_NIF_FUNC_DEF(setopt); \ ESOCK_NIF_FUNC_DEF(getopt); \ ESOCK_NIF_FUNC_DEF(sockname); \ + ESOCK_NIF_FUNC_DEF(socknames); \ ESOCK_NIF_FUNC_DEF(peername); \ + ESOCK_NIF_FUNC_DEF(peernames); \ ESOCK_NIF_FUNC_DEF(ioctl); \ ESOCK_NIF_FUNC_DEF(finalize_close); \ ESOCK_NIF_FUNC_DEF(cancel); @@ -1119,11 +1135,15 @@ typedef struct { ESockIOOpenWithFd open_with_fd; ESockIOOpenPlain open_plain; ESockIOBind bind; + ESockIOBindx bindx; ESockIOConnect connect; + ESockIOConnectx connectx; ESockIOListen listen; ESockIOAccept accept; + ESockIOPeelOff peeloff; + ESockIOSend send; ESockIOSendTo sendto; ESockIOSendMsg sendmsg; @@ -1141,7 +1161,9 @@ typedef struct { ESockIOShutdown shutdown; ESockIOSockName sockname; + ESockIOSockNames socknames; ESockIOPeerName peername; + ESockIOPeerNames peernames; /* The various cancel operations */ ESockIOCancelConnect cancel_connect; @@ -1189,7 +1211,13 @@ struct ESockOpt ERL_NIF_TERM (*getopt)(ErlNifEnv* env, ESockDescriptor* descP, int level, - int opt); + int opt, + /* **Optional** value argument to + * the get function. *Most* options + * do not use this, and in those cases + * this should be the atom 'undefined'. + */ + ERL_NIF_TERM eInVal); ERL_NIF_TERM *nameP; // Pointer to option name atom }; @@ -1305,11 +1333,13 @@ static ERL_NIF_TERM esock_setopt_level_opt(ErlNifEnv* env, static ERL_NIF_TERM esock_getopt_bool_opt(ErlNifEnv* env, ESockDescriptor* descP, int level, - int opt); + int opt, + ERL_NIF_TERM eval); static ERL_NIF_TERM esock_getopt_int_opt(ErlNifEnv* env, ESockDescriptor* descP, int level, - int opt); + int opt, + ERL_NIF_TERM eval); static ERL_NIF_TERM esock_getopt_size_opt(ErlNifEnv* env, ESockDescriptor* descP, int level, @@ -1441,7 +1471,8 @@ static ERL_NIF_TERM esock_setopt_so_bindtodevice(ErlNifEnv* env, static ERL_NIF_TERM esock_getopt_bsp_state(ErlNifEnv* env, ESockDescriptor* descP, int level, - int opt); + int opt, + ERL_NIF_TERM eVal); static ERL_NIF_TERM esock_encode_bsp_state_socket_address(ErlNifEnv* env, SOCKET_ADDRESS* addr); static ERL_NIF_TERM esock_encode_bsp_state_type(ErlNifEnv* env, int type); @@ -1459,7 +1490,8 @@ static ERL_NIF_TERM esock_getopt_linger(ErlNifEnv* env, ESockDescriptor* descP, int level, - int opt); + int opt, + ERL_NIF_TERM eVal); #endif #if defined(IP_MSFILTER) && defined(IP_MSFILTER_SIZE) @@ -1561,6 +1593,18 @@ static ERL_NIF_TERM esock_setopt_tcp_congestion(ErlNifEnv* env, #if defined(HAVE_SCTP) +#if defined(SCTP_ADAPTATION_LAYER) +static ERL_NIF_TERM esock_setopt_sctp_adaptation_layer(ErlNifEnv* env, + ESockDescriptor* descP, + int level, + int opt, + ERL_NIF_TERM eVal); +static ERL_NIF_TERM esock_getopt_sctp_adaptation_layer(ErlNifEnv* env, + ESockDescriptor* descP, + int level, + int opt, + ERL_NIF_TERM eval); +#endif #if defined(SCTP_ASSOCINFO) static ERL_NIF_TERM esock_setopt_sctp_associnfo(ErlNifEnv* env, ESockDescriptor* descP, @@ -1568,16 +1612,39 @@ static ERL_NIF_TERM esock_setopt_sctp_associnfo(ErlNifEnv* env, int opt, ERL_NIF_TERM eVal); #endif +#if defined(SCTP_DEFAULT_SEND_PARAM) +static +ERL_NIF_TERM esock_setopt_sctp_default_send_param(ErlNifEnv* env, + ESockDescriptor* descP, + int level, + int opt, + ERL_NIF_TERM eVal); +static +ERL_NIF_TERM esock_getopt_sctp_default_send_param(ErlNifEnv* env, + ESockDescriptor* descP, + int level, + int opt, + ERL_NIF_TERM eval); +static BOOLEAN_T decode_sctp_send_recv_info_flags(ErlNifEnv* env, + ESockDescriptor* descP, + ERL_NIF_TERM eFlags, + Uint16* flags); +static BOOLEAN_T encode_sctp_send_recv_info_flags(ErlNifEnv* env, + ESockDescriptor* descP, + unsigned int flags); +#endif #if defined(SCTP_EVENTS) static ERL_NIF_TERM esock_setopt_sctp_events(ErlNifEnv* env, ESockDescriptor* descP, int level, int opt, ERL_NIF_TERM eVal); -static int esock_setopt_sctp_event(ErlNifEnv *env, - ERL_NIF_TERM eMap, - ERL_NIF_TERM eKey, - BOOLEAN_T *failure); +static int esock_setopt_sctp_event(ErlNifEnv* env, + ESockDescriptor* descP, + ERL_NIF_TERM eMap, + ERL_NIF_TERM eKey, + ERL_NIF_TERM eDafaultKey, + BOOLEAN_T* failure); #endif #if defined(SCTP_INITMSG) static ERL_NIF_TERM esock_setopt_sctp_initmsg(ErlNifEnv* env, @@ -1594,25 +1661,107 @@ static ERL_NIF_TERM esock_setopt_sctp_rtoinfo(ErlNifEnv* env, ERL_NIF_TERM eVal); #endif +#if defined(SCTP_PRIMARY_ADDR) +static +ERL_NIF_TERM esock_setopt_sctp_primary_addr(ErlNifEnv* env, + ESockDescriptor* descP, + int level, + int opt, + ERL_NIF_TERM eVal); +#endif +#if defined(SCTP_SET_PEER_PRIMARY_ADDR) +static +ERL_NIF_TERM esock_setopt_sctp_set_peer_primary_addr(ErlNifEnv* env, + ESockDescriptor* descP, + int level, + int opt, + ERL_NIF_TERM eVal); +#endif + +#if defined(SCTP_PEER_ADDR_PARAMS) +static ERL_NIF_TERM esock_setopt_sctp_peer_addr_params(ErlNifEnv* env, + ESockDescriptor* descP, + int level, + int opt, + ERL_NIF_TERM eVal); +static ERL_NIF_TERM esock_getopt_sctp_peer_addr_params(ErlNifEnv* env, + ESockDescriptor* descP, + int level, + int opt, + ERL_NIF_TERM eval); +#if defined(HAVE_STRUCT_SCTP_PADDRPARAMS_SPP_FLAGS) +static BOOLEAN_T decode_pap_flags(ErlNifEnv* env, + ESockDescriptor* descP, + ERL_NIF_TERM eFlags, + unsigned int* flags); +static ERL_NIF_TERM encode_pap_flags(ErlNifEnv* env, + ESockDescriptor* descP, + unsigned int flags); +#endif +#endif + +#if defined(SCTP_GET_PEER_ADDR_INFO) +static +ERL_NIF_TERM esock_getopt_sctp_get_peer_addr_info(ErlNifEnv* env, + ESockDescriptor* descP, + int level, + int opt, + ERL_NIF_TERM eval); +#endif + +#if defined(SCTP_STATUS) +static ERL_NIF_TERM esock_getopt_sctp_status(ErlNifEnv* env, + ESockDescriptor* descP, + int level, + int opt, + ERL_NIF_TERM eval); +static ERL_NIF_TERM encode_sctp_sstat_state(ErlNifEnv* env, + ESockDescriptor* descP, + int state); +#endif +#if defined(SCTP_STATUS) || defined(SCTP_GET_PEER_ADDR_INFO) +static ERL_NIF_TERM encode_sctp_paddrinfo(ErlNifEnv* env, + ESockDescriptor* descP, + struct sctp_paddrinfo* infoP); +static ERL_NIF_TERM encode_sctp_spinfo_state(ErlNifEnv* env, + ESockDescriptor* descP, + int state); + +#endif +#if defined(SCTP_GET_ASSOC_STATS) +static ERL_NIF_TERM esock_getopt_sctp_get_assoc_stats(ErlNifEnv* env, + ESockDescriptor* descP, + int level, + int opt, + ERL_NIF_TERM eval); +#endif +#if defined(SCTP_STATUS) || defined(SCTP_PEER_ADDR_PARAMS) +static ERL_NIF_TERM encode_sockaddr(ErlNifEnv* env, + struct sockaddr_storage* addrP); +#endif // #if defined(SCTP_STATUS) || defined(SCTP_PEER_ADDR_PARAMS) + #endif // defined(HAVE_SCTP) static ERL_NIF_TERM esock_getopt(ErlNifEnv* env, ESockDescriptor* descP, int level, - int opt); + int opt, + ERL_NIF_TERM eval); #if defined(SO_BINDTODEVICE) static ERL_NIF_TERM esock_getopt_so_bindtodevice(ErlNifEnv* env, ESockDescriptor* descP, int level, - int opt); + int opt, + ERL_NIF_TERM eval); #endif #if defined(SO_DOMAIN) static ERL_NIF_TERM esock_getopt_sock_domain(ErlNifEnv* env, ESockDescriptor* descP, int level, - int opt); + int opt, + ERL_NIF_TERM eval); #endif #if defined(SO_TYPE) @@ -1620,7 +1769,8 @@ static ERL_NIF_TERM esock_getopt_sock_type(ErlNifEnv* env, ESockDescriptor* descP, int level, - int opt); + int opt, + ERL_NIF_TERM eval); #endif #if defined(SO_PROTOCOL) @@ -1628,26 +1778,30 @@ static ERL_NIF_TERM esock_getopt_sock_protocol(ErlNifEnv* env, ESockDescriptor* descP, int level, - int opt); + int opt, + ERL_NIF_TERM eval); #endif #if defined(IP_MTU_DISCOVER) static ERL_NIF_TERM esock_getopt_ip_mtu_discover(ErlNifEnv* env, ESockDescriptor* descP, int level, - int opt); + int opt, + ERL_NIF_TERM eval); #endif #if defined(IP_MULTICAST_IF) static ERL_NIF_TERM esock_getopt_multicast_if(ErlNifEnv* env, ESockDescriptor* descP, int level, - int opt); + int opt, + ERL_NIF_TERM eval); #endif #if defined(IP_TOS) static ERL_NIF_TERM esock_getopt_tos(ErlNifEnv* env, ESockDescriptor* descP, int level, - int opt); + int opt, + ERL_NIF_TERM eval); #endif @@ -1657,7 +1811,8 @@ static ERL_NIF_TERM esock_getopt_tos(ErlNifEnv* env, static ERL_NIF_TERM esock_getopt_ipv6_mtu_discover(ErlNifEnv* env, ESockDescriptor* descP, int level, - int opt); + int opt, + ERL_NIF_TERM eval); #endif #endif // defined(HAVE_IPV6) @@ -1666,14 +1821,16 @@ static ERL_NIF_TERM esock_getopt_ipv6_mtu_discover(ErlNifEnv* env, static ERL_NIF_TERM esock_getopt_pktoptions(ErlNifEnv* env, ESockDescriptor* descP, int level, - int opt); + int opt, + ERL_NIF_TERM eval); #endif #if defined(TCP_CONGESTION) static ERL_NIF_TERM esock_getopt_tcp_congestion(ErlNifEnv* env, ESockDescriptor* descP, int level, - int opt); + int opt, + ERL_NIF_TERM eval); #endif @@ -1683,19 +1840,22 @@ static ERL_NIF_TERM esock_getopt_tcp_congestion(ErlNifEnv* env, static ERL_NIF_TERM esock_getopt_sctp_associnfo(ErlNifEnv* env, ESockDescriptor* descP, int level, - int opt); + int opt, + ERL_NIF_TERM eval); #endif #if defined(SCTP_INITMSG) static ERL_NIF_TERM esock_getopt_sctp_initmsg(ErlNifEnv* env, ESockDescriptor* descP, int level, - int opt); + int opt, + ERL_NIF_TERM eval); #endif #if defined(SCTP_RTOINFO) static ERL_NIF_TERM esock_getopt_sctp_rtoinfo(ErlNifEnv* env, ESockDescriptor* descP, int level, - int opt); + int opt, + ERL_NIF_TERM eval); #endif #endif // defined(HAVE_SCTP) @@ -1904,6 +2064,10 @@ static ERL_NIF_TERM encode_sctp_assoc_t(ErlNifEnv* env, static BOOLEAN_T ehow2how(ERL_NIF_TERM ehow, int* how); +static BOOLEAN_T esock_decode_action(ErlNifEnv* env, + ERL_NIF_TERM eAction, + int* action); + /* #if defined(HAS_AF_LOCAL) || defined(SO_BINDTODEVICE) @@ -1964,21 +2128,39 @@ static const struct in6_addr in6addr_loopback = GLOBAL_ATOM_DECL(acc_fails); \ GLOBAL_ATOM_DECL(acc_tries); \ GLOBAL_ATOM_DECL(acc_waits); \ - GLOBAL_ATOM_DECL(adaption_layer); \ + GLOBAL_ATOM_DECL(action); \ + GLOBAL_ATOM_DECL(adaptation_event); \ + GLOBAL_ATOM_DECL(adaptation_indication); \ + GLOBAL_ATOM_DECL(adaptation_layer); \ + GLOBAL_ATOM_DECL(add); \ GLOBAL_ATOM_DECL(addr); \ GLOBAL_ATOM_DECL(addrform); \ + GLOBAL_ATOM_DECL(addr_added); \ + GLOBAL_ATOM_DECL(addr_available); \ + GLOBAL_ATOM_DECL(addr_confirmed); \ + GLOBAL_ATOM_DECL(addr_made_prim); \ + GLOBAL_ATOM_DECL(addr_over); \ + GLOBAL_ATOM_DECL(addr_potentially_failed); \ + GLOBAL_ATOM_DECL(addr_removed); \ + GLOBAL_ATOM_DECL(addr_unreachable); \ GLOBAL_ATOM_DECL(add_membership); \ GLOBAL_ATOM_DECL(add_socket); \ GLOBAL_ATOM_DECL(add_source_membership); \ GLOBAL_ATOM_DECL(alen); \ GLOBAL_ATOM_DECL(allmulti); \ GLOBAL_ATOM_DECL(already); \ + GLOBAL_ATOM_DECL(altkeynumber); \ GLOBAL_ATOM_DECL(any); \ GLOBAL_ATOM_DECL(appletlk); \ GLOBAL_ATOM_DECL(arcnet); \ GLOBAL_ATOM_DECL(associnfo); \ + GLOBAL_ATOM_DECL(assoc_change); \ + GLOBAL_ATOM_DECL(assoc_id); \ + GLOBAL_ATOM_DECL(assoc_reset); \ GLOBAL_ATOM_DECL(atm); \ GLOBAL_ATOM_DECL(authhdr); \ + GLOBAL_ATOM_DECL(authinfo); \ + GLOBAL_ATOM_DECL(authkey); \ GLOBAL_ATOM_DECL(auth_active_key); \ GLOBAL_ATOM_DECL(auth_asconf); \ GLOBAL_ATOM_DECL(auth_chunk); \ @@ -1992,6 +2174,7 @@ static const struct in6_addr in6addr_loopback = GLOBAL_ATOM_DECL(base_addr); \ GLOBAL_ATOM_DECL(bindtodevice); \ GLOBAL_ATOM_DECL(block_source); \ + GLOBAL_ATOM_DECL(bound); \ GLOBAL_ATOM_DECL(bridge); \ GLOBAL_ATOM_DECL(broadcast); \ GLOBAL_ATOM_DECL(bsp_state); \ @@ -2004,6 +2187,7 @@ static const struct in6_addr in6addr_loopback = GLOBAL_ATOM_DECL(cancel); \ GLOBAL_ATOM_DECL(cancelled); \ GLOBAL_ATOM_DECL(cantconfig); \ + GLOBAL_ATOM_DECL(cant_str_assoc); \ GLOBAL_ATOM_DECL(cellular); \ GLOBAL_ATOM_DECL(chaos); \ GLOBAL_ATOM_DECL(checksum); \ @@ -2013,6 +2197,8 @@ static const struct in6_addr in6addr_loopback = GLOBAL_ATOM_DECL(closing); \ GLOBAL_ATOM_DECL(cmsg_cloexec); \ GLOBAL_ATOM_DECL(command); \ + GLOBAL_ATOM_DECL(comm_lost); \ + GLOBAL_ATOM_DECL(comm_up); \ GLOBAL_ATOM_DECL(completion); \ GLOBAL_ATOM_DECL(completion_status); \ GLOBAL_ATOM_DECL(confirm); \ @@ -2022,18 +2208,24 @@ static const struct in6_addr in6addr_loopback = GLOBAL_ATOM_DECL(connecting); \ GLOBAL_ATOM_DECL(connection_time); \ GLOBAL_ATOM_DECL(context); \ + GLOBAL_ATOM_DECL(cookie_echoed); \ + GLOBAL_ATOM_DECL(cookie_wait); \ GLOBAL_ATOM_DECL(cork); \ GLOBAL_ATOM_DECL(counters); \ GLOBAL_ATOM_DECL(credentials); \ GLOBAL_ATOM_DECL(ctrl); \ GLOBAL_ATOM_DECL(ctrunc); \ + GLOBAL_ATOM_DECL(cum_tsn); \ GLOBAL_ATOM_DECL(cwnd); \ GLOBAL_ATOM_DECL(data); \ + GLOBAL_ATOM_DECL(data_sent); \ GLOBAL_ATOM_DECL(data_size); \ + GLOBAL_ATOM_DECL(data_unsent); \ GLOBAL_ATOM_DECL(debug); \ GLOBAL_ATOM_DECL(default); \ - GLOBAL_ATOM_DECL(default_send_params); \ + GLOBAL_ATOM_DECL(default_send_param); \ GLOBAL_ATOM_DECL(delayed_ack_time); \ + GLOBAL_ATOM_DECL(denied); \ GLOBAL_ATOM_DECL(dgram); \ GLOBAL_ATOM_DECL(dhcprunning); \ GLOBAL_ATOM_DECL(disabled); \ @@ -2046,6 +2238,8 @@ static const struct in6_addr in6addr_loopback = GLOBAL_ATOM_DECL(dormant); \ GLOBAL_ATOM_DECL(drop_membership); \ GLOBAL_ATOM_DECL(drop_source_membership); \ + GLOBAL_ATOM_DECL(dstaddrv4); \ + GLOBAL_ATOM_DECL(dstaddrv6); \ GLOBAL_ATOM_DECL(dstopts); \ GLOBAL_ATOM_DECL(dup); \ GLOBAL_ATOM_DECL(dup_acks_in); \ @@ -2055,8 +2249,10 @@ static const struct in6_addr in6addr_loopback = GLOBAL_ATOM_DECL(eether); \ GLOBAL_ATOM_DECL(efile); \ GLOBAL_ATOM_DECL(egp); \ + GLOBAL_ATOM_DECL(empty); \ GLOBAL_ATOM_DECL(enabled); \ GLOBAL_ATOM_DECL(enotsup); \ + GLOBAL_ATOM_DECL(eof); \ GLOBAL_ATOM_DECL(eor); \ GLOBAL_ATOM_DECL(error); \ GLOBAL_ATOM_DECL(errqueue); \ @@ -2068,6 +2264,7 @@ static const struct in6_addr in6addr_loopback = GLOBAL_ATOM_DECL(events); \ GLOBAL_ATOM_DECL(exclusiveaddruse); \ GLOBAL_ATOM_DECL(explicit_eor); \ + GLOBAL_ATOM_DECL(failed); \ GLOBAL_ATOM_DECL(faith); \ GLOBAL_ATOM_DECL(false); \ GLOBAL_ATOM_DECL(family); \ @@ -2080,6 +2277,7 @@ static const struct in6_addr in6addr_loopback = GLOBAL_ATOM_DECL(fragment_interleave); \ GLOBAL_ATOM_DECL(freebind); \ GLOBAL_ATOM_DECL(frelay); \ + GLOBAL_ATOM_DECL(get_assoc_stats); \ GLOBAL_ATOM_DECL(get_overlapped_result); \ GLOBAL_ATOM_DECL(get_peer_addr_info); \ GLOBAL_ATOM_DECL(gif); \ @@ -2098,11 +2296,15 @@ static const struct in6_addr in6addr_loopback = GLOBAL_ATOM_DECL(ifindex); \ GLOBAL_ATOM_DECL(igmp); \ GLOBAL_ATOM_DECL(implink); \ + GLOBAL_ATOM_DECL(inbound_streams); \ + GLOBAL_ATOM_DECL(incoming_ssn); \ GLOBAL_ATOM_DECL(index); \ + GLOBAL_ATOM_DECL(indication); \ GLOBAL_ATOM_DECL(inet); \ GLOBAL_ATOM_DECL(inet6); \ GLOBAL_ATOM_DECL(infiniband); \ GLOBAL_ATOM_DECL(info); \ + GLOBAL_ATOM_DECL(init); \ GLOBAL_ATOM_DECL(initmsg); \ GLOBAL_ATOM_DECL(invalid); \ GLOBAL_ATOM_DECL(integer_range); \ @@ -2121,9 +2323,12 @@ static const struct in6_addr in6addr_loopback = GLOBAL_ATOM_DECL(keepidle); \ GLOBAL_ATOM_DECL(keepintvl); \ GLOBAL_ATOM_DECL(kernel); \ + GLOBAL_ATOM_DECL(keynumber); \ + GLOBAL_ATOM_DECL(key_number); \ GLOBAL_ATOM_DECL(knowsepoch); \ GLOBAL_ATOM_DECL(last_ack); \ GLOBAL_ATOM_DECL(leave_group); \ + GLOBAL_ATOM_DECL(length); \ GLOBAL_ATOM_DECL(level); \ GLOBAL_ATOM_DECL(linger); \ GLOBAL_ATOM_DECL(link); \ @@ -2134,6 +2339,7 @@ static const struct in6_addr in6addr_loopback = GLOBAL_ATOM_DECL(local); \ GLOBAL_ATOM_DECL(localtlk); \ GLOBAL_ATOM_DECL(local_auth_chunks); \ + GLOBAL_ATOM_DECL(local_tsn); \ GLOBAL_ATOM_DECL(loop); \ GLOBAL_ATOM_DECL(loopback); \ GLOBAL_ATOM_DECL(lowdelay); \ @@ -2144,6 +2350,9 @@ static const struct in6_addr in6addr_loopback = GLOBAL_ATOM_DECL(maxburst); \ GLOBAL_ATOM_DECL(maxdg); \ GLOBAL_ATOM_DECL(maxseg); \ + GLOBAL_ATOM_DECL(max_attempts); \ + GLOBAL_ATOM_DECL(max_init_timeo); \ + GLOBAL_ATOM_DECL(max_instreams); \ GLOBAL_ATOM_DECL(max_msg_size); \ GLOBAL_ATOM_DECL(md5sig); \ GLOBAL_ATOM_DECL(mem_end); \ @@ -2165,6 +2374,7 @@ static const struct in6_addr in6addr_loopback = GLOBAL_ATOM_DECL(multicast_loop); \ GLOBAL_ATOM_DECL(multicast_ttl); \ GLOBAL_ATOM_DECL(name); \ + GLOBAL_ATOM_DECL(native); \ GLOBAL_ATOM_DECL(netns); \ GLOBAL_ATOM_DECL(netrom); \ GLOBAL_ATOM_DECL(nlen); \ @@ -2176,17 +2386,20 @@ static const struct in6_addr in6addr_loopback = GLOBAL_ATOM_DECL(noopt); \ GLOBAL_ATOM_DECL(nopush); \ GLOBAL_ATOM_DECL(nosignal); \ + GLOBAL_ATOM_DECL(notification); \ GLOBAL_ATOM_DECL(notrailers); \ GLOBAL_ATOM_DECL(not_bound); \ GLOBAL_ATOM_DECL(not_found); \ - GLOBAL_ATOM_DECL(num_general_errors); \ GLOBAL_ATOM_DECL(not_owner); \ + GLOBAL_ATOM_DECL(num_general_errors); \ + GLOBAL_ATOM_DECL(num_ostreams); \ GLOBAL_ATOM_DECL(num_threads); \ GLOBAL_ATOM_DECL(num_unexpected_accepts); \ GLOBAL_ATOM_DECL(num_unexpected_connects); \ GLOBAL_ATOM_DECL(num_unexpected_reads); \ GLOBAL_ATOM_DECL(num_unexpected_writes); \ GLOBAL_ATOM_DECL(num_unknown_cmds); \ + GLOBAL_ATOM_DECL(nxtinfo); \ GLOBAL_ATOM_DECL(oactive); \ GLOBAL_ATOM_DECL(off); \ GLOBAL_ATOM_DECL(ok); \ @@ -2197,13 +2410,16 @@ static const struct in6_addr in6addr_loopback = GLOBAL_ATOM_DECL(origdstaddr); \ GLOBAL_ATOM_DECL(other); \ GLOBAL_ATOM_DECL(otherhost); \ + GLOBAL_ATOM_DECL(outbound_streams); \ GLOBAL_ATOM_DECL(outgoing); \ + GLOBAL_ATOM_DECL(outgoing_ssn); \ GLOBAL_ATOM_DECL(packet); \ - GLOBAL_ATOM_DECL(partial_delivery_point); \ + GLOBAL_ATOM_DECL(partial_delivery); \ GLOBAL_ATOM_DECL(passcred); \ GLOBAL_ATOM_DECL(path); \ GLOBAL_ATOM_DECL(peek); \ GLOBAL_ATOM_DECL(peek_off); \ + GLOBAL_ATOM_DECL(peer_addr_change); \ GLOBAL_ATOM_DECL(peer_addr_params); \ GLOBAL_ATOM_DECL(peer_auth_chunks); \ GLOBAL_ATOM_DECL(peercred); \ @@ -2211,13 +2427,16 @@ static const struct in6_addr in6addr_loopback = GLOBAL_ATOM_DECL(pktoptions); \ GLOBAL_ATOM_DECL(pkttype); \ GLOBAL_ATOM_DECL(pointopoint); \ + GLOBAL_ATOM_DECL(policy); \ GLOBAL_ATOM_DECL(port); \ GLOBAL_ATOM_DECL(portrange); \ GLOBAL_ATOM_DECL(portsel); \ - GLOBAL_ATOM_DECL(ppromisc); \ + GLOBAL_ATOM_DECL(ppid); \ GLOBAL_ATOM_DECL(ppp); \ + GLOBAL_ATOM_DECL(ppromisc); \ GLOBAL_ATOM_DECL(primary_addr); \ GLOBAL_ATOM_DECL(prim_file); \ + GLOBAL_ATOM_DECL(prinfo); \ GLOBAL_ATOM_DECL(priority); \ GLOBAL_ATOM_DECL(private); \ GLOBAL_ATOM_DECL(promisc); \ @@ -2228,6 +2447,7 @@ static const struct in6_addr in6addr_loopback = GLOBAL_ATOM_DECL(rawip); \ GLOBAL_ATOM_DECL(rcvbuf); \ GLOBAL_ATOM_DECL(rcvbufforce); \ + GLOBAL_ATOM_DECL(rcvinfo); \ GLOBAL_ATOM_DECL(rcvlowat); \ GLOBAL_ATOM_DECL(rcvtimeo); \ GLOBAL_ATOM_DECL(rcv_buf); \ @@ -2252,8 +2472,13 @@ static const struct in6_addr in6addr_loopback = GLOBAL_ATOM_DECL(recvtos); \ GLOBAL_ATOM_DECL(recvttl); \ GLOBAL_ATOM_DECL(reliability); \ + GLOBAL_ATOM_DECL(remote_causes); \ + GLOBAL_ATOM_DECL(remote_error); \ + GLOBAL_ATOM_DECL(remote_tsn); \ + GLOBAL_ATOM_DECL(remove); \ GLOBAL_ATOM_DECL(renaming); \ GLOBAL_ATOM_DECL(reset_streams); \ + GLOBAL_ATOM_DECL(restart); \ GLOBAL_ATOM_DECL(retopts); \ GLOBAL_ATOM_DECL(reuseaddr); \ GLOBAL_ATOM_DECL(reuseport); \ @@ -2267,11 +2492,13 @@ static const struct in6_addr in6addr_loopback = GLOBAL_ATOM_DECL(rxq_ovfl); \ GLOBAL_ATOM_DECL(scope_id); \ GLOBAL_ATOM_DECL(sctp); \ + GLOBAL_ATOM_DECL(sctp_notification); \ GLOBAL_ATOM_DECL(sec); \ GLOBAL_ATOM_DECL(select); \ GLOBAL_ATOM_DECL(select_failed); \ GLOBAL_ATOM_DECL(select_sent); \ GLOBAL_ATOM_DECL(send); \ + GLOBAL_ATOM_DECL(sender_dry); \ GLOBAL_ATOM_DECL(sendfile); \ GLOBAL_ATOM_DECL(sendfile_byte); \ GLOBAL_ATOM_DECL(sendfile_deferred_close); \ @@ -2285,26 +2512,44 @@ static const struct in6_addr in6addr_loopback = GLOBAL_ATOM_DECL(sendsrcaddr); \ GLOBAL_ATOM_DECL(sendto); \ GLOBAL_ATOM_DECL(sendv); \ + GLOBAL_ATOM_DECL(send_failed); \ + GLOBAL_ATOM_DECL(send_failed_event); \ + GLOBAL_ATOM_DECL(seq); \ GLOBAL_ATOM_DECL(seqpacket); \ GLOBAL_ATOM_DECL(setfib); \ GLOBAL_ATOM_DECL(set_peer_primary_addr); \ + GLOBAL_ATOM_DECL(shutdown_ack_sent); \ + GLOBAL_ATOM_DECL(shutdown_comp); \ + GLOBAL_ATOM_DECL(shutdown_event); \ + GLOBAL_ATOM_DECL(shutdown_pending); \ + GLOBAL_ATOM_DECL(shutdown_received); \ + GLOBAL_ATOM_DECL(shutdown_sent); \ + GLOBAL_ATOM_DECL(sid); \ GLOBAL_ATOM_DECL(simplex); \ GLOBAL_ATOM_DECL(slave); \ GLOBAL_ATOM_DECL(slen); \ GLOBAL_ATOM_DECL(sndbuf); \ GLOBAL_ATOM_DECL(sndbufforce); \ + GLOBAL_ATOM_DECL(sndinfo); \ GLOBAL_ATOM_DECL(sndlowat); \ + GLOBAL_ATOM_DECL(sndrcv); \ GLOBAL_ATOM_DECL(sndtimeo); \ GLOBAL_ATOM_DECL(snd_wnd); \ GLOBAL_ATOM_DECL(sockaddr); \ GLOBAL_ATOM_DECL(socket); \ + GLOBAL_ATOM_DECL(socket_debug); \ + GLOBAL_ATOM_DECL(socket_tag); \ GLOBAL_ATOM_DECL(socktype); \ GLOBAL_ATOM_DECL(spec_dst); \ + GLOBAL_ATOM_DECL(ssn); \ GLOBAL_ATOM_DECL(staticarp); \ GLOBAL_ATOM_DECL(state); \ GLOBAL_ATOM_DECL(status); \ GLOBAL_ATOM_DECL(stf); \ GLOBAL_ATOM_DECL(stream); \ + GLOBAL_ATOM_DECL(streams); \ + GLOBAL_ATOM_DECL(stream_change); \ + GLOBAL_ATOM_DECL(stream_reset); \ GLOBAL_ATOM_DECL(syncnt); \ GLOBAL_ATOM_DECL(syn_rcvd); \ GLOBAL_ATOM_DECL(syn_retrans); \ @@ -2318,9 +2563,11 @@ static const struct in6_addr in6addr_loopback = GLOBAL_ATOM_DECL(timeout); \ GLOBAL_ATOM_DECL(timeout_episodes); \ GLOBAL_ATOM_DECL(timestamp_enabled); \ + GLOBAL_ATOM_DECL(time_to_live); \ GLOBAL_ATOM_DECL(time_wait); \ GLOBAL_ATOM_DECL(true); \ GLOBAL_ATOM_DECL(trunc); \ + GLOBAL_ATOM_DECL(tsn); \ GLOBAL_ATOM_DECL(ttl); \ GLOBAL_ATOM_DECL(tunnel); \ GLOBAL_ATOM_DECL(tunnel6); \ @@ -2331,6 +2578,7 @@ static const struct in6_addr in6addr_loopback = GLOBAL_ATOM_DECL(undefined); \ GLOBAL_ATOM_DECL(unicast_hops); \ GLOBAL_ATOM_DECL(unknown); \ + GLOBAL_ATOM_DECL(unordered); \ GLOBAL_ATOM_DECL(unspec); \ GLOBAL_ATOM_DECL(up); \ GLOBAL_ATOM_DECL(update_accept_context); \ @@ -2369,14 +2617,17 @@ GLOBAL_ATOMS; GLOBAL_ERROR_REASON_ATOMS; #undef GLOBAL_ATOM_DECL ERL_NIF_TERM esock_atom_socket_tag; // This has a "special" name ('$socket') +ERL_NIF_TERM esock_atom_esock_name; // This has a "special" name ('$esock_name') /* *** Local atoms *** */ #define LOCAL_ATOMS \ LOCAL_ATOM_DECL(accepting); \ + LOCAL_ATOM_DECL(active); \ LOCAL_ATOM_DECL(adaptation_layer); \ LOCAL_ATOM_DECL(add); \ - LOCAL_ATOM_DECL(addr_unreach); \ LOCAL_ATOM_DECL(address); \ + LOCAL_ATOM_DECL(addr_over); \ + LOCAL_ATOM_DECL(addr_unreach); \ LOCAL_ATOM_DECL(adm_prohibited); \ LOCAL_ATOM_DECL(association); \ LOCAL_ATOM_DECL(assoc_id); \ @@ -2391,17 +2642,28 @@ ERL_NIF_TERM esock_atom_socket_tag; // This has a "special" name ('$socket') LOCAL_ATOM_DECL(cookie_life); \ LOCAL_ATOM_DECL(counter_wrap); \ LOCAL_ATOM_DECL(ctype); \ + LOCAL_ATOM_DECL(cwnd); \ LOCAL_ATOM_DECL(data_io); \ LOCAL_ATOM_DECL(debug_filename); \ LOCAL_ATOM_DECL(del); \ LOCAL_ATOM_DECL(dest_unreach); \ + LOCAL_ATOM_DECL(disable_heartbeats); \ + LOCAL_ATOM_DECL(disable_pmtu_discovery); \ + LOCAL_ATOM_DECL(disable_sack); \ LOCAL_ATOM_DECL(do); \ LOCAL_ATOM_DECL(dont); \ + LOCAL_ATOM_DECL(dscp); \ LOCAL_ATOM_DECL(dtor); \ LOCAL_ATOM_DECL(eei); \ + LOCAL_ATOM_DECL(enable_heartbeats); \ + LOCAL_ATOM_DECL(enable_pmtu_discovery); \ + LOCAL_ATOM_DECL(enable_sack); \ + LOCAL_ATOM_DECL(eof); \ LOCAL_ATOM_DECL(exclude); \ LOCAL_ATOM_DECL(false); \ + LOCAL_ATOM_DECL(fragmentation_point); \ LOCAL_ATOM_DECL(frag_needed); \ + LOCAL_ATOM_DECL(gap_ack_recv); \ LOCAL_ATOM_DECL(genaddr); \ LOCAL_ATOM_DECL(gifaddr); \ LOCAL_ATOM_DECL(gifbrdaddr); \ @@ -2415,21 +2677,32 @@ ERL_NIF_TERM esock_atom_socket_tag; // This has a "special" name ('$socket') LOCAL_ATOM_DECL(gifname); \ LOCAL_ATOM_DECL(gifnetmask); \ LOCAL_ATOM_DECL(giftxqlen); \ + LOCAL_ATOM_DECL(heartbeat_interval); \ LOCAL_ATOM_DECL(host_unknown); \ LOCAL_ATOM_DECL(host_unreach); \ LOCAL_ATOM_DECL(how); \ - LOCAL_ATOM_DECL(in4_sockaddr); \ - LOCAL_ATOM_DECL(in6_sockaddr); \ + LOCAL_ATOM_DECL(inactive); \ LOCAL_ATOM_DECL(include); \ + LOCAL_ATOM_DECL(indication); \ LOCAL_ATOM_DECL(initial); \ LOCAL_ATOM_DECL(interface); \ LOCAL_ATOM_DECL(integer); \ + LOCAL_ATOM_DECL(in_ctrl_chunks); \ + LOCAL_ATOM_DECL(in_dup_chunks); \ + LOCAL_ATOM_DECL(in_ordered_chunks); \ + LOCAL_ATOM_DECL(in_packets); \ + LOCAL_ATOM_DECL(in_sacks); \ + LOCAL_ATOM_DECL(in_streams); \ + LOCAL_ATOM_DECL(in_unordered_chunks); \ + LOCAL_ATOM_DECL(in4_sockaddr); \ + LOCAL_ATOM_DECL(in6_sockaddr); \ LOCAL_ATOM_DECL(ioctl_flags); \ LOCAL_ATOM_DECL(ioctl_requests); \ LOCAL_ATOM_DECL(iov_max); \ LOCAL_ATOM_DECL(iow); \ LOCAL_ATOM_DECL(io_backend); \ LOCAL_ATOM_DECL(io_num_threads); \ + LOCAL_ATOM_DECL(ipv6_flowlabel); \ LOCAL_ATOM_DECL(listening); \ LOCAL_ATOM_DECL(local_addr); \ LOCAL_ATOM_DECL(local_rwnd); \ @@ -2438,6 +2711,8 @@ ERL_NIF_TERM esock_atom_socket_tag; // This has a "special" name ('$socket') LOCAL_ATOM_DECL(max_attempts); \ LOCAL_ATOM_DECL(max_init_timeo); \ LOCAL_ATOM_DECL(max_instreams); \ + LOCAL_ATOM_DECL(max_rto); \ + LOCAL_ATOM_DECL(max_rto_addr); \ LOCAL_ATOM_DECL(asocmaxrxt); \ LOCAL_ATOM_DECL(min); \ LOCAL_ATOM_DECL(missing); \ @@ -2479,14 +2754,26 @@ ERL_NIF_TERM esock_atom_socket_tag; // This has a "special" name ('$socket') LOCAL_ATOM_DECL(origin); \ LOCAL_ATOM_DECL(otp); \ LOCAL_ATOM_DECL(otp_socket_option);\ + LOCAL_ATOM_DECL(out_ctrl_chunks); \ + LOCAL_ATOM_DECL(out_dup_chunks); \ + LOCAL_ATOM_DECL(out_ordered_chunks); \ + LOCAL_ATOM_DECL(out_of_seq_tsns); \ + LOCAL_ATOM_DECL(out_packets); \ + LOCAL_ATOM_DECL(out_sacks); \ + LOCAL_ATOM_DECL(out_streams); \ + LOCAL_ATOM_DECL(out_unordered_chunks); \ LOCAL_ATOM_DECL(owner); \ - LOCAL_ATOM_DECL(partial_delivery); \ + LOCAL_ATOM_DECL(path_max_rxt); \ + LOCAL_ATOM_DECL(path_mtu); \ LOCAL_ATOM_DECL(peer_error); \ LOCAL_ATOM_DECL(peer_rwnd); \ + LOCAL_ATOM_DECL(pending_data); \ LOCAL_ATOM_DECL(pkt_toobig); \ LOCAL_ATOM_DECL(policy_fail); \ LOCAL_ATOM_DECL(port); \ LOCAL_ATOM_DECL(port_unreach); \ + LOCAL_ATOM_DECL(potentially_failed); \ + LOCAL_ATOM_DECL(primary); \ LOCAL_ATOM_DECL(probe); \ LOCAL_ATOM_DECL(protocols); \ LOCAL_ATOM_DECL(rcvall); \ @@ -2497,14 +2784,19 @@ ERL_NIF_TERM esock_atom_socket_tag; // This has a "special" name ('$socket') LOCAL_ATOM_DECL(read_pkg_max); \ LOCAL_ATOM_DECL(read_waits); \ LOCAL_ATOM_DECL(read_write); \ + LOCAL_ATOM_DECL(rto); \ + LOCAL_ATOM_DECL(rtx_chunks); \ + LOCAL_ATOM_DECL(rwnd); \ LOCAL_ATOM_DECL(registry); \ LOCAL_ATOM_DECL(reject_route); \ LOCAL_ATOM_DECL(remote); \ LOCAL_ATOM_DECL(remote_addr); \ LOCAL_ATOM_DECL(rstates); \ + LOCAL_ATOM_DECL(sack_delay); \ LOCAL_ATOM_DECL(selected); \ - LOCAL_ATOM_DECL(sender_dry); \ LOCAL_ATOM_DECL(send_failure); \ + LOCAL_ATOM_DECL(send_heartbeat_immediately); \ + LOCAL_ATOM_DECL(set_heartbeat_delay_to_zero); \ LOCAL_ATOM_DECL(shutdown); \ LOCAL_ATOM_DECL(sifaddr); \ LOCAL_ATOM_DECL(sifbrdaddr); \ @@ -2515,15 +2807,18 @@ ERL_NIF_TERM esock_atom_socket_tag; // This has a "special" name ('$socket') LOCAL_ATOM_DECL(siftxqlen); \ LOCAL_ATOM_DECL(slist); \ LOCAL_ATOM_DECL(sndctrlbuf); \ - LOCAL_ATOM_DECL(socket_debug); \ LOCAL_ATOM_DECL(socket_level); \ LOCAL_ATOM_DECL(socket_option); \ LOCAL_ATOM_DECL(sourceaddr); \ + LOCAL_ATOM_DECL(srtt); \ LOCAL_ATOM_DECL(tcp_info); \ LOCAL_ATOM_DECL(time_exceeded); \ LOCAL_ATOM_DECL(true); \ LOCAL_ATOM_DECL(txstatus); \ LOCAL_ATOM_DECL(txtime); \ + LOCAL_ATOM_DECL(unacked_data); \ + LOCAL_ATOM_DECL(unconfirmed); \ + LOCAL_ATOM_DECL(unordered); \ LOCAL_ATOM_DECL(want); \ LOCAL_ATOM_DECL(write); \ LOCAL_ATOM_DECL(write_pkg_max); \ @@ -2595,10 +2890,19 @@ static ESockIoBackend io_backend = {0}; ((io_backend.bind != NULL) ? \ io_backend.bind((ENV), (D), (SAP), (AL)) : \ enif_raise_exception((ENV), MKA((ENV), "notsup"))) -#define ESOCK_IO_CONNECT(ENV, D, SR, CR, AP, AL) \ - ((io_backend.connect != NULL) ? \ - io_backend.connect((ENV), (D), \ - (SR), (CR), (AP), (AL)) : \ +#define ESOCK_IO_BINDX(ENV, D, SAS, AC, ACT) \ + ((io_backend.bindx != NULL) ? \ + io_backend.bindx((ENV), (D), (SAS), (AC), (ACT)) : \ + enif_raise_exception((ENV), MKA((ENV), "notsup"))) +#define ESOCK_IO_CONNECT(ENV, D, SR, CR, AP, AL) \ + ((io_backend.connect != NULL) ? \ + io_backend.connect((ENV), (D), \ + (SR), (CR), (AP), (AL)) : \ + enif_raise_exception((ENV), MKA((ENV), "notsup"))) +#define ESOCK_IO_CONNECTX(ENV, D, SR, CR, ADDRS, ACNT) \ + ((io_backend.connectx != NULL) ? \ + io_backend.connectx((ENV), (D), \ + (SR), (CR), (ADDRS), (ACNT)) : \ enif_raise_exception((ENV), MKA((ENV), "notsup"))) #define ESOCK_IO_LISTEN(ENV, D, BL) \ ((io_backend.listen != NULL) ? \ @@ -2608,6 +2912,10 @@ static ESockIoBackend io_backend = {0}; ((io_backend.accept != NULL) ? \ io_backend.accept((ENV), (D), (SR), (AR)) : \ enif_raise_exception((ENV), MKA((ENV), "notsup"))) +#define ESOCK_IO_PEELOFF(ENV, D, SR, AID) \ + ((io_backend.peeloff != NULL) ? \ + io_backend.peeloff((ENV), (D), (SR), (AID)) : \ + enif_raise_exception((ENV), MKA((ENV), "notsup"))) #define ESOCK_IO_SEND(ENV, D, SR, RF, L, F) \ ((io_backend.send != NULL) ? \ io_backend.send((ENV), (D), \ @@ -2686,13 +2994,21 @@ static ESockIoBackend io_backend = {0}; ((io_backend.shutdown != NULL) ? \ io_backend.shutdown((ENV), (D), (H)) : \ enif_raise_exception((ENV), MKA((ENV), "notsup"))) -#define ESOCK_IO_SOCKNAME(ENV, D) \ - ((io_backend.sockname != NULL) ? \ - io_backend.sockname((ENV), (D)) : \ +#define ESOCK_IO_SOCKNAME(ENV, D) \ + ((io_backend.sockname != NULL) ? \ + io_backend.sockname((ENV), (D)) : \ + enif_raise_exception((ENV), MKA((ENV), "notsup"))) +#define ESOCK_IO_SOCKNAMES(ENV, D, AID) \ + ((io_backend.socknames != NULL) ? \ + io_backend.socknames((ENV), (D), (AID)) : \ enif_raise_exception((ENV), MKA((ENV), "notsup"))) -#define ESOCK_IO_PEERNAME(ENV, D) \ - ((io_backend.peername != NULL) ? \ - io_backend.peername((ENV), (D)) : \ +#define ESOCK_IO_PEERNAME(ENV, D) \ + ((io_backend.peername != NULL) ? \ + io_backend.peername((ENV), (D)) : \ + enif_raise_exception((ENV), MKA((ENV), "notsup"))) +#define ESOCK_IO_PEERNAMES(ENV, D, AID) \ + ((io_backend.peernames != NULL) ? \ + io_backend.peernames((ENV), (D), (AID)) : \ enif_raise_exception((ENV), MKA((ENV), "notsup"))) #define ESOCK_IO_CANCEL_CONNECT(ENV, D, OR) \ ((io_backend.cancel_connect != NULL) ? \ @@ -2722,9 +3038,9 @@ static ESockIoBackend io_backend = {0}; ((io_backend.setopt_otp != NULL) ? \ io_backend.setopt_otp((ENV), (D), (L), (O)) : \ enif_raise_exception((ENV), MKA((ENV), "notsup"))) -#define ESOCK_IO_GETOPT(ENV, D, L, O) \ +#define ESOCK_IO_GETOPT(ENV, D, L, O, V) \ ((io_backend.getopt != NULL) ? \ - io_backend.getopt((ENV), (D), (L), (O)) : \ + io_backend.getopt((ENV), (D), (L), (O), (V)) : \ enif_raise_exception((ENV), MKA((ENV), "notsup"))) #define ESOCK_IO_GETOPT_NATIVE(ENV, D, L, O, VS) \ ((io_backend.getopt_native != NULL) ? \ @@ -3568,11 +3884,23 @@ static struct ESockOpt optLevelIPV6[] = /* SCTP_* options ------------------------------------------------------ */ #ifdef HAVE_SCTP + static struct ESockOpt optLevelSCTP[] = { - {0, NULL, NULL, &esock_atom_adaption_layer}, + /* SCTP_ADAPTATION_LAYER */ + { +#ifdef SCTP_ADAPTATION_LAYER + SCTP_ADAPTATION_LAYER, + esock_setopt_sctp_adaptation_layer, + esock_getopt_sctp_adaptation_layer, +#else + 0, + NULL, NULL, +#endif + &esock_atom_adaptation_layer}, + /* SCTP_ASSOCINFO */ { #ifdef SCTP_ASSOCINFO SCTP_ASSOCINFO, @@ -3597,7 +3925,6 @@ static struct ESockOpt optLevelSCTP[] = &esock_atom_autoclose}, {0, NULL, NULL, &esock_atom_context}, - {0, NULL, NULL, &esock_atom_default_send_params}, {0, NULL, NULL, &esock_atom_delayed_ack_time}, { @@ -3609,6 +3936,16 @@ static struct ESockOpt optLevelSCTP[] = #endif &esock_atom_disable_fragments}, + { +#ifdef SCTP_DEFAULT_SEND_PARAM + SCTP_DEFAULT_SEND_PARAM, + esock_setopt_sctp_default_send_param, + esock_getopt_sctp_default_send_param, +#else + 0, NULL, NULL, +#endif + &esock_atom_default_send_param}, + {0, NULL, NULL, &esock_atom_hmac_ident}, { @@ -3622,7 +3959,15 @@ static struct ESockOpt optLevelSCTP[] = {0, NULL, NULL, &esock_atom_explicit_eor}, {0, NULL, NULL, &esock_atom_fragment_interleave}, - {0, NULL, NULL, &esock_atom_get_peer_addr_info}, + { +#ifdef SCTP_GET_PEER_ADDR_INFO + SCTP_GET_PEER_ADDR_INFO, + NULL, esock_getopt_sctp_get_peer_addr_info, +#else + 0, NULL, NULL, +#endif + &esock_atom_get_peer_addr_info + }, { #ifdef SCTP_INITMSG @@ -3633,7 +3978,16 @@ static struct ESockOpt optLevelSCTP[] = #endif &esock_atom_initmsg}, - {0, NULL, NULL, &esock_atom_i_want_mapped_v4_addr}, + /* SCTP_I_WANT_MAPPED_V4_ADDR */ + { +#ifdef SCTP_I_WANT_MAPPED_V4_ADDR + SCTP_I_WANT_MAPPED_V4_ADDR, + esock_setopt_bool_opt, NULL, +#else + 0, NULL, NULL, +#endif + &esock_atom_i_want_mapped_v4_addr}, + {0, NULL, NULL, &esock_atom_local_auth_chunks}, { @@ -3656,10 +4010,19 @@ static struct ESockOpt optLevelSCTP[] = #endif &esock_atom_nodelay}, - {0, NULL, NULL, &esock_atom_partial_delivery_point}, + {0, NULL, NULL, &esock_atom_partial_delivery}, {0, NULL, NULL, &esock_atom_peer_addr_params}, {0, NULL, NULL, &esock_atom_peer_auth_chunks}, - {0, NULL, NULL, &esock_atom_primary_addr}, + + { +#ifdef SCTP_PRIMARY_ADDR + SCTP_PRIMARY_ADDR, + esock_setopt_sctp_primary_addr, NULL, +#else + 0, NULL, NULL, +#endif + &esock_atom_primary_addr}, + {0, NULL, NULL, &esock_atom_reset_streams}, { @@ -3671,13 +4034,52 @@ static struct ESockOpt optLevelSCTP[] = #endif &esock_atom_rtoinfo}, - {0, NULL, NULL, &esock_atom_set_peer_primary_addr}, - {0, NULL, NULL, &esock_atom_status}, + { +#ifdef SCTP_SET_PEER_PRIMARY_ADDR + SCTP_SET_PEER_PRIMARY_ADDR, + esock_setopt_sctp_set_peer_primary_addr, NULL, +#else + 0, NULL, NULL, +#endif + &esock_atom_set_peer_primary_addr}, + + { +#ifdef SCTP_PEER_ADDR_PARAMS + SCTP_PEER_ADDR_PARAMS, + esock_setopt_sctp_peer_addr_params, + esock_getopt_sctp_peer_addr_params, +#else + 0, NULL, NULL, +#endif + &esock_atom_peer_addr_params}, + + /* SCTP_STATUS */ + { +#ifdef SCTP_STATUS + SCTP_STATUS, + NULL, esock_getopt_sctp_status, +#else + 0, NULL, NULL, +#endif + &esock_atom_status}, + + /* SCTP_GET_ASSOC_STATS */ + { +#ifdef SCTP_GET_ASSOC_STATS + SCTP_GET_ASSOC_STATS, + NULL, esock_getopt_sctp_get_assoc_stats, +#else + 0, NULL, NULL, +#endif + &esock_atom_get_assoc_stats}, + {0, NULL, NULL, &esock_atom_use_ext_recvinfo} }; + #endif // #ifdef HAVE_SCTP + /* TCP_* options ------------------------------------------------------- */ static struct ESockOpt optLevelTCP[] = @@ -3947,9 +4349,12 @@ extern ErlNifEnv* esock_alloc_env(const char* slogan) * nif_open(FD, Extra) * nif_open(Domain, Type, Protocol, Extra) * nif_bind(Sock, LocalAddr) + * nif_bind(Sock, [Addr], Action) * nif_connect(Sock, SockAddr) + * nif_connect(Sock, [SockAddr], AssocId) * nif_listen(Sock, Backlog) * nif_accept(LSock, Ref) + * nif_peeloff(Sock, AssocId) * nif_send(Sock, Data, Flags, SendRef) * nif_sendto(Sock, Data, Dest, Flags, SendRef) * nif_sendmsg(Sock, Msg, Flags, SendRef, IOV) @@ -3963,12 +4368,14 @@ extern ErlNifEnv* esock_alloc_env(const char* slogan) * nif_close(Sock) * nif_shutdown(Sock, How) * nif_sockname(Sock) + * nif_socknames(Sock, AssocId) * nif_peername(Sock) + * nif_peernames(Sock, AssocId) * * And some functions to manipulate and retrieve socket options: * ------------------------------------------------------------- * nif_setopt/5 - * nif_getopt/3,4 + * nif_getopt/3,5 * * And some utility functions: * ------------------------------------------------------------- @@ -4086,7 +4493,7 @@ ERL_NIF_TERM esock_global_info(ErlNifEnv* env) { ERL_NIF_TERM keys[] = {esock_atom_debug, - atom_socket_debug, + esock_atom_socket_debug, atom_eei, esock_atom_use_registry, atom_iow, @@ -4101,18 +4508,6 @@ ERL_NIF_TERM esock_global_info(ErlNifEnv* env) gcnt, iovMax, ESOCK_IO_INFO(env) - /* This mess is just a temporary hack - * and shall be replaced by a callback - * function (eventually). - * That function should return a 'term' (a map). - */ - /* -#ifdef __WIN32__ - MKA(env, "win_esaio") -#else - MKA(env, "unix_essio") -#endif - */ }, info; unsigned int @@ -4500,7 +4895,7 @@ ERL_NIF_TERM nif_command(ErlNifEnv* env, "\r\n cdata: %T" "\r\n", command, cdata) ); - result = ESOCK_IO_CMD(env, command, cdata); + result = esock_command(env, command, cdata); SGDBG( ("SOCKET", "nif_command -> done with result: " "\r\n %T" @@ -4511,28 +4906,68 @@ ERL_NIF_TERM nif_command(ErlNifEnv* env, } +/* + * Some commands we handle here, but *also* pass on to the I/O backend + * (such as debug). + * That is currently all commands, but we may add commands in the future + * that are intended *only* for the I/O backend. + * So and command that we at this level do not know should just be + * passed on to the I/O backend. + */ static ERL_NIF_TERM esock_command(ErlNifEnv* env, ERL_NIF_TERM command, ERL_NIF_TERM cdata) { - int cmp; + ERL_NIF_TERM res = esock_atom_invalid; + int cmp; SGDBG( ("SOCKET", "esock_command -> entry with %T\r\n", command) ); - cmp = COMPARE(command, atom_socket_debug); + cmp = COMPARE(command, esock_atom_socket_debug); if (cmp == 0) { - return esock_command_socket_debug(env, cdata); + res = esock_command_socket_debug(env, cdata); } else if (cmp < 0) { if (COMPARE(command, esock_atom_debug) == 0) - return esock_command_debug(env, cdata); + res = esock_command_debug(env, cdata); } else { // 0 < cmp if (COMPARE(command, esock_atom_use_registry) == 0) - return esock_command_use_socket_registry(env, cdata); + res = esock_command_use_socket_registry(env, cdata); } - SGDBG( ("SOCKET", "esock_command -> invalid command: %T\r\n", - command) ); + if (IS_OK(res)) { + + /* We do not care about the I/O backend result here, + * since we *know* this to be a valid update. + * A "failure" from the I/O backend only means + * that it does not know about this command. + */ + + ESOCK_IO_CMD(env, command, cdata); + + return res; + + } else if (IS_INVALID(res)) { + + /* This only means that it was a command that *this level* + * do not know, but it *may* make sense for the I/O backend, + * so just pass it on. + */ + res = ESOCK_IO_CMD(env, command, cdata); + + if (IS_INVALID(res)) + return esock_raise_invalid(env, + MKT2(env, esock_atom_command, command)); + else + return res; + + } else { + + if (IS_INVALID(res)) + return esock_raise_invalid(env, + MKT2(env, esock_atom_command, command)); + else + return res; - return esock_raise_invalid(env, MKT2(env, esock_atom_command, command)); + } } @@ -5266,7 +5701,16 @@ BOOLEAN_T esock_open_which_protocol(SOCKET sock, int* proto) * * Arguments: * [0] Socket (ref) - Points to the socket descriptor. - * [1] LocalAddr - Local address is a sockaddr map ( socket:sockaddr() ). + * [1] LocalAddr - Local address(s) is either + * - a sockaddr map ( socket:sockaddr() ). + * This the standard bind + * - a list of sockaddr maps: + * - IPv4 socket: + * [socket:sockaddr_in()] | + * - IPv6 socket: + * [socket:sockaddr_in() | socket:sockaddr_in6()]. + * This is valid only for sctp/seqpacket + * [2] Action - Add or Remove (only if arg 1 is a list */ static ERL_NIF_TERM nif_bind(ErlNifEnv* env, @@ -5274,45 +5718,145 @@ ERL_NIF_TERM nif_bind(ErlNifEnv* env, const ERL_NIF_TERM argv[]) { ESockDescriptor* descP; - ERL_NIF_TERM eSockAddr, ret; - ESockAddress sockAddr; - SOCKLEN_T addrLen; - - ESOCK_ASSERT( argc == 2 ); + ERL_NIF_TERM ret; SGDBG( ("SOCKET", "nif_bind -> entry with argc: %d\r\n", argc) ); + ESOCK_ASSERT( (argc == 2) || (argc == 3) ); + /* Extract arguments and perform preliminary validation */ if (! ESOCK_GET_RESOURCE(env, argv[0], (void**) &descP)) { return enif_make_badarg(env); } - eSockAddr = argv[1]; - if (! esock_decode_sockaddr(env, eSockAddr, &sockAddr, &addrLen)) - return esock_make_invalid(env, esock_atom_sockaddr); - MLOCK(descP->readMtx); + switch (argc) { + case 2: // Classic bind + { + ESockAddress sockAddr; + SOCKLEN_T addrLen; + ERL_NIF_TERM eSockAddr = argv[1]; - SSDBG( descP, - ("SOCKET", "nif_bind(%T) {%d,0x%X} ->" - "\r\n SockAddr: %T" - "\r\n", - argv[0], descP->sock, descP->readState, - eSockAddr) ); + if (! esock_decode_sockaddr(env, eSockAddr, &sockAddr, &addrLen)) + return esock_make_invalid(env, esock_atom_sockaddr); - ret = ESOCK_IO_BIND(env, descP, &sockAddr, addrLen); + MLOCK(descP->readMtx); - SSDBG( descP, ("SOCKET", "nif_bind(%T) -> done with" - "\r\n ret: %T" - "\r\n", argv[0], ret) ); + SSDBG( descP, + ("SOCKET", "nif_bind(%T) {%d,0x%X} ->" + "\r\n SockAddr: %T" + "\r\n", + argv[0], descP->sock, descP->readState, + eSockAddr) ); - MUNLOCK(descP->readMtx); + ret = ESOCK_IO_BIND(env, descP, &sockAddr, addrLen); + + SSDBG( descP, ("SOCKET", "nif_bind(%T) -> done with" + "\r\n ret: %T" + "\r\n", argv[0], ret) ); + + MUNLOCK(descP->readMtx); + } + break; + + case 3: // SCTP bind (bindx) +#if defined(HAVE_SCTP) + { + ESockAddress* sockAddrs = NULL; // Allocated by decode func + int addrsCnt = 0; + ERL_NIF_TERM eSockAddrs = argv[1]; // basically [socket:sockaddr()] + int action = 0; + ERL_NIF_TERM eAction = argv[2]; // 'add' | 'remove' + + if (!IS_LIST(env, eSockAddrs)) + return enif_make_badarg(env); + + if ((!esock_decode_sockaddrs(env, descP->dbg, descP->domain, + eSockAddrs, &sockAddrs, &addrsCnt)) || + (sockAddrs == NULL)) { + /* Either not a list or an empty list + * either way; Bad User! + */ + return esock_make_invalid(env, esock_atom_sockaddr); + } + + if (!esock_decode_action(env, eAction, &action)) + return esock_make_invalid(env, esock_atom_action); + + MLOCK(descP->readMtx); + + SSDBG( descP, + ("SOCKET", "nif_bind(%T) {%d,0x%X} ->" + "\r\n SockAddrs: %T" + "\r\n Action: %T" + "\r\n", + argv[0], descP->sock, descP->readState, + eSockAddrs, eAction) ); + + ret = ESOCK_IO_BINDX(env, descP, sockAddrs, addrsCnt, action); + + /* Allocated by esock_decode_sockaddrs, so its up to us to free */ + FREE(sockAddrs); + + SSDBG( descP, ("SOCKET", "nif_bind(%T) -> done with" + "\r\n ret: %T" + "\r\n", argv[0], ret) ); + + MUNLOCK(descP->readMtx); + + } +#else + return enif_raise_exception(env, MKA(env, "notsup")); +#endif + break; + + default: + /* + * This is just to make sure we catch cut-and-paste errors. + * The assert above should ensure we never get here. + */ + return enif_make_badarg(env); + break; + } return ret; } +/* This function is only used for SCTP and if we do not "have" sctp, + * it should not be called. But just to be on the safe side, return FALSE. + */ +static +BOOLEAN_T esock_decode_action(ErlNifEnv* env, + ERL_NIF_TERM eAction, + int* action) +{ +#if defined(HAVE_SCTP) + BOOLEAN_T res; + int a; + + if (IS_IDENTICAL(eAction, esock_atom_add)) { + res = TRUE; + a = SCTP_BINDX_ADD_ADDR; + } else if (IS_IDENTICAL(eAction, esock_atom_remove)) { + res = TRUE; + a = SCTP_BINDX_REM_ADDR; + } else { + res = FALSE; + a = 0; + } + + *action = a; + + return res; +#else + return FALSE; +#endif +} + + + /* ---------------------------------------------------------------------- * nif_connect * @@ -5320,12 +5864,11 @@ ERL_NIF_TERM nif_bind(ErlNifEnv* env, * Initiate a connection on a socket * * Arguments: - * Socket (ref) - Points to the socket descriptor. + * [0] Socket (ref) - Points to the socket descriptor. * Optional arguments: - * ConnectRef - Ref for the connection - * SockAddr - Socket Address of "remote" host. - * This is sockaddr(), which is either - * sockaddr_in4 or sockaddr_in6. + * [1] ConnectRef - Ref for the connection + * [2] SockAddr | [SockAddr] - Socket Address of "remote" host. + * This is sockaddr(). */ static ERL_NIF_TERM nif_connect(ErlNifEnv* env, @@ -5334,7 +5877,7 @@ ERL_NIF_TERM nif_connect(ErlNifEnv* env, { ESockDescriptor* descP; ERL_NIF_TERM res, sockRef, connRef; - ESockAddress addr, *addrP; + ESockAddress addr; SOCKLEN_T addrLen; ESOCK_ASSERT( argc >= 1 ); @@ -5347,51 +5890,133 @@ ERL_NIF_TERM nif_connect(ErlNifEnv* env, if (! ESOCK_GET_RESOURCE(env, sockRef, (void**) &descP)) return enif_make_badarg(env); + SSDBG( descP, + ("SOCKET", "nif_connect(%T, %d, 0x%X) -> argc: %d " + "\r\n", + sockRef, descP->sock, descP->writeState, argc) ); + if (argc == 3) { - ERL_NIF_TERM eSockAddr = argv[2]; + ERL_NIF_TERM esa = argv[2]; // sockaddr() | "[sockaddr()]" connRef = argv[1]; - if (! enif_is_ref(env, connRef)) + if (! IS_REF(env, connRef)) return enif_make_badarg(env); - if (! esock_decode_sockaddr(env, eSockAddr, &addr, &addrLen)) - return esock_make_invalid(env, esock_atom_sockaddr); - addrP = &addr; + if (IS_LIST(env, esa)) { +#if defined(HAVE_SCTP) + ESockAddress* sockAddrs; + int addrsCnt; - MLOCK(descP->writeMtx); + MLOCK(descP->writeMtx); + + SSDBG( descP, + ("SOCKET", "nif_connect(%T, %d, 0x%X) -> decode sockAddrs " + "\r\n", + sockRef, descP->sock, descP->writeState) ); + + /* The SockAddrs must all be either IPv4 or IPv6 with the + * following limitation: + * - If socket is IPv4: All addresses must be IPv4. + * - If socket is IPv6: The addresses can be *either* + * IPv4 *or* IPv6. + */ + + if ((!esock_decode_sockaddrs(env, descP->dbg, descP->domain, + esa, &sockAddrs, &addrsCnt)) || + (sockAddrs == NULL)) { + + /* Either not a list or an empty list */ + + SSDBG( descP, + ("SOCKET", "nif_connect(%T, %d, 0x%X) -> " + "\r\n failed decode socket addresses: " + "\r\n %T" + "\r\n", + sockRef, descP->sock, descP->writeState, esa) ); + + res = esock_make_invalid(env, esock_atom_sockaddr); + } else { + + SSDBG( descP, + ("SOCKET", "nif_connect(%T, %d, 0x%X) -> connectx with" + "\r\n connRef:" + "\r\n %T" + "\r\n sockAddrs:" + "\r\n %T" + "\r\n addrsCnt: " + "\r\n %d" + "\r\n", + sockRef, descP->sock, descP->writeState, + connRef, esa, addrsCnt) ); + + res = ESOCK_IO_CONNECTX(env, descP, sockRef, connRef, + sockAddrs, addrsCnt); + + FREE( sockAddrs ); + } +#else + return enif_raise_exception(env, MKA(env, "notsup")); +#endif + } else { + + MLOCK(descP->writeMtx); + + SSDBG( descP, + ("SOCKET", "nif_connect(%T, %d, 0x%X) -> decode sockAddr " + "\r\n", + sockRef, descP->sock, descP->writeState) ); + + if (! esock_decode_sockaddr(env, esa, &addr, &addrLen)) { + + SSDBG( descP, + ("SOCKET", + "nif_connect(%T, %d, 0x%X) -> failed decode sockAddr:" + "\r\n %T)" + "\r\n", + sockRef, descP->sock, descP->writeState, esa) ); + + res = esock_make_invalid(env, esock_atom_sockaddr); + } + + SSDBG( descP, + ("SOCKET", "nif_connect(%T, %d, 0x%X) -> connect with" + "\r\n ConnRef: %T" + "\r\n SockAddr: %T" + "\r\n", + sockRef, descP->sock, descP->writeState, + connRef, esa) ); + + res = ESOCK_IO_CONNECT(env, descP, sockRef, connRef, + &addr, addrLen); + + } - SSDBG( descP, - ("SOCKET", "nif_connect(%T), {%d0x%X} ->" - "\r\n ConnRef: %T" - "\r\n SockAddr: %T" - "\r\n", - sockRef, descP->sock, descP->writeState, - connRef, eSockAddr) ); } else { ESOCK_ASSERT( argc == 1 ); - connRef = esock_atom_undefined; - addrP = NULL; - addrLen = 0; - MLOCK(descP->writeMtx); SSDBG( descP, - ("SOCKET", "nif_connect(%T), {%d,0x%X} ->" + ("SOCKET", "nif_connect(%T), {%d0x%X} -> connect" "\r\n", - sockRef, descP->sock, descP->writeState - ) ); + sockRef, descP->sock, descP->writeState) ); + + res = ESOCK_IO_CONNECT(env, descP, sockRef, + esock_atom_undefined, NULL, 0); + } - res = ESOCK_IO_CONNECT(env, descP, sockRef, connRef, addrP, addrLen); + SSDBG( descP, + ("SOCKET", "nif_connect(%T, %d, 0x%X) -> unlock\r\n", + sockRef, descP->sock, descP->writeState) ); + + MUNLOCK(descP->writeMtx); - SSDBG( descP, ("SOCKET", "nif_connect(%T) -> done with" + SSDBG( descP, ("SOCKET", "nif_connect(%T) -> done when" "\r\n res: %T" "\r\n", sockRef, res) ); - MUNLOCK(descP->writeMtx); - return res; } @@ -5534,7 +6159,7 @@ ERL_NIF_TERM nif_accept(ErlNifEnv* env, #ifndef __WIN32__ SSDBG( descP, - ("SOCKET", "nif_accept%T), {%d,0x%X} ->" + ("SOCKET", "nif_accept(%T), {%d,0x%X} ->" "\r\n ReqRef: %T" "\r\n Current Acceptor addr: %p" "\r\n Current Acceptor pid: %T" @@ -5575,10 +6200,81 @@ ERL_NIF_TERM nif_accept(ErlNifEnv* env, /* ---------------------------------------------------------------------- - * nif_send + * nif_peeloff * * Description: - * Send a message on a socket + * Branch off an assoc into a separate socket. + * + * Arguments: + * Socket (ref) - Points to the socket descriptor. + * Assoc Id - Id of the association to branch off. + */ + +#define ESOCK_ENSURE_SCTP(D) \ + if ((D)->protocol != IPPROTO_SCTP) return enif_make_badarg(env) + +static +ERL_NIF_TERM nif_peeloff(ErlNifEnv* env, + int argc, + const ERL_NIF_TERM argv[]) +{ +#if defined(HAVE_SCTP) + ESockDescriptor* descP; + ERL_NIF_TERM sockRef, eAssocId, res; + sctp_assoc_t assocId; + + SGDBG( ("SOCKET", "nif_peeloff -> entry with argc: %d\r\n", argc) ); + + ESOCK_ASSERT( argc == 2 ); + + /* Extract arguments and perform preliminary validation */ + + sockRef = argv[0]; + if (! ESOCK_GET_RESOURCE(env, sockRef, (void**) &descP)) { + return enif_make_badarg(env); + } + + ESOCK_ENSURE_SCTP(descP); + + eAssocId = argv[1]; + + SSDBG( descP, + ("SOCKET", "nif_peeloff(%T, %d, 0x%X, 0x%X) ->" + "\r\n AssocId: %T" + "\r\n", + sockRef, descP->sock, + descP->readState, descP->writeState, eAssocId) ); + + if (! decode_sctp_assoc_t(env, eAssocId, &assocId)) { + if (IS_INTEGER(env, eAssocId)) + return esock_make_error_integer_range(env, eAssocId); + else + return enif_make_badarg(env); + } + + MLOCK(descP->readMtx); + + res = ESOCK_IO_PEELOFF(env, descP, sockRef, assocId); + + SSDBG( descP, ("SOCKET", "nif_peeloff(%T) -> done with" + "\r\n res: %T" + "\r\n", sockRef, res) ); + + MUNLOCK(descP->readMtx); + + return res; +#else + return enif_raise_exception(env, MKA(env, "notsup")); +#endif +} + + + +/* ---------------------------------------------------------------------- + * nif_send + * + * Description: + * Send a message on a socket * * Arguments: * Socket (ref) - Points to the socket descriptor. @@ -6334,18 +7030,21 @@ ERL_NIF_TERM nif_recvmsg(ErlNifEnv* env, const ERL_NIF_TERM argv[]) { ESockDescriptor* descP; - ERL_NIF_TERM sockRef, recvRef; - ErlNifUInt64 eBufSz, eCtrlSz; - ssize_t bufSz, ctrlSz; + ERL_NIF_TERM sockRef, eBufSz, eCtrlSz, eflags, recvRef; + ErlNifUInt64 bufSz0, ctrlSz0; + ssize_t bufSz, ctrlSz; int flags; ERL_NIF_TERM res; - BOOLEAN_T a1ok, a2ok; ESOCK_ASSERT( argc == 5 ); SGDBG( ("SOCKET", "nif_recvmsg -> entry with argc: %d\r\n", argc) ); - sockRef = argv[0]; // We need this in case we send abort (to the caller) + /* Extract the (raw) arguments */ + sockRef = argv[0]; + eBufSz = argv[1]; + eCtrlSz = argv[2]; + eflags = argv[3]; recvRef = argv[4]; if (! ESOCK_GET_RESOURCE(env, sockRef, (void**) &descP)) { @@ -6359,29 +7058,99 @@ ERL_NIF_TERM nif_recvmsg(ErlNifEnv* env, return enif_make_badarg(env); } - if ((! (a1ok = GET_UINT64(env, argv[1], &eBufSz))) || - (! (a2ok = GET_UINT64(env, argv[2], &eCtrlSz))) || - (! GET_INT(env, argv[3], &flags))) { - if ((! IS_INTEGER(env, argv[1])) || - (! IS_INTEGER(env, argv[2])) || - (! IS_INTEGER(env, argv[3]))) + + /* *** Extract and check bufSz *** */ + SSDBG( descP, + ("SOCKET", "nif_recvmsg(%T,%d,0x%X) ->" + "\r\n extract and verify buffer size" + "\r\n", + sockRef, descP->sock, descP->readState) ); + if (! GET_UINT64(env, eBufSz, &bufSz0)) { + if (! IS_INTEGER(env, eBufSz)) { + SSDBG( descP, + ("SOCKET", "nif_recvmsg(%T,%d,0x%X) ->" + "\r\n buffer size not an integer: %T" + "\r\n", + sockRef, descP->sock, descP->readState, eBufSz) ); return enif_make_badarg(env); + } else { + SSDBG( descP, + ("SOCKET", "nif_recvmsg(%T,%d,0x%X) ->" + "\r\n buffer size integer range: %T" + "\r\n", + sockRef, descP->sock, descP->readState, eBufSz) ); + return esock_make_error_integer_range(env, eBufSz); + } + } + bufSz = (ssize_t) bufSz0; + if (bufSz0 != (ErlNifUInt64) bufSz) { + SSDBG( descP, + ("SOCKET", "nif_recvmsg(%T,%d,0x%X) ->" + "\r\n buffer size integer overflow: %T" + "\r\n", + sockRef, descP->sock, descP->readState, eBufSz) ); + return esock_make_error_integer_range(env, bufSz0); + } - if (! a1ok) - return esock_make_error_integer_range(env, argv[1]); - if (! a2ok) - return esock_make_error_integer_range(env, argv[2]); - return - esock_make_error_integer_range(env, argv[3]); + + /* *** Extract and check ctrlSz *** */ + SSDBG( descP, + ("SOCKET", "nif_recvmsg(%T,%d,0x%X) ->" + "\r\n extract and verify ctrl size" + "\r\n", + sockRef, descP->sock, descP->readState) ); + if (! GET_UINT64(env, eCtrlSz, &ctrlSz0)) { + if (! IS_INTEGER(env, eCtrlSz)) { + SSDBG( descP, + ("SOCKET", "nif_recvmsg(%T,%d,0x%X) ->" + "\r\n ctrl size not an integer: %T" + "\r\n", + sockRef, descP->sock, descP->readState, eCtrlSz) ); + return enif_make_badarg(env); + } else { + SSDBG( descP, + ("SOCKET", "nif_recvmsg(%T,%d,0x%X) ->" + "\r\n ctrl size integer range: %T" + "\r\n", + sockRef, descP->sock, descP->readState, eCtrlSz) ); + return esock_make_error_integer_range(env, eCtrlSz); + } } + ctrlSz = (ssize_t) ctrlSz0; + if (ctrlSz0 != (ErlNifUInt64) ctrlSz) { + SSDBG( descP, + ("SOCKET", "nif_recvmsg(%T,%d,0x%X) ->" + "\r\n ctrl size integer overflow: %T" + "\r\n", + sockRef, descP->sock, descP->readState, eCtrlSz) ); + return esock_make_error_integer_range(env, ctrlSz0); + } + - bufSz = (ssize_t) eBufSz; - if (eBufSz != (ErlNifUInt64) bufSz) - return esock_make_error_integer_range(env, eBufSz); + /* *** Extract and check flags *** */ + SSDBG( descP, + ("SOCKET", "nif_recvmsg(%T,%d,0x%X) ->" + "\r\n extract and verify flags" + "\r\n", + sockRef, descP->sock, descP->readState) ); + if (! GET_INT(env, eflags, &flags)) { + if (! IS_INTEGER(env, eflags)) { + SSDBG( descP, + ("SOCKET", "nif_recvmsg(%T,%d,0x%X) ->" + "\r\n flags not an integer: %T" + "\r\n", + sockRef, descP->sock, descP->readState, eflags) ); + return enif_make_badarg(env); + } else { + SSDBG( descP, + ("SOCKET", "nif_recvmsg(%T,%d,0x%X) ->" + "\r\n flags integer range: %T" + "\r\n", + sockRef, descP->sock, descP->readState, eflags) ); + return esock_make_error_integer_range(env, eflags); + } + } - ctrlSz = (ssize_t) eCtrlSz; - if (eCtrlSz != (ErlNifUInt64) ctrlSz) - return esock_make_error_integer_range(env, eCtrlSz); MLOCK(descP->readMtx); @@ -7910,6 +8679,80 @@ ERL_NIF_TERM esock_setopt_tcp_congestion(ErlNifEnv* env, #if defined(HAVE_SCTP) +/* esock_setopt_sctp_adaptation_layer - Level SCTP option + */ + +#if defined(SCTP_ADAPTATION_LAYER) +static +ERL_NIF_TERM esock_setopt_sctp_adaptation_layer(ErlNifEnv* env, + ESockDescriptor* descP, + int level, + int opt, + ERL_NIF_TERM eVal) +{ + ERL_NIF_TERM eaind; + struct sctp_setadaptation ad; + + SSDBG( descP, + ("SOCKET", "esock_setopt_sctp_adaptation_layer -> entry with" + "\r\n eVal: %T" + "\r\n", eVal) ); + + // It must be a map + if (! IS_MAP(env, eVal)) { + + SSDBG( descP, + ("SOCKET", + "esock_setopt_sctp_adaptation_layer -> " + "value - not a map: " + "\r\n eVal: %T" + "\r\n", eVal) ); + + return esock_make_invalid(env, esock_atom_value); + } + + SSDBG( descP, + ("SOCKET", + "esock_setopt_sctp_adaptation_layer -> extract attributes\r\n") ); + + if ((! GET_MAP_VAL(env, eVal, atom_indication, &eaind))) { + + SSDBG( descP, + ("SOCKET", + "esock_setopt_sctp_adaptation_layer -> " + "failed get indication\r\n") ); + + return esock_make_invalid(env, esock_atom_value); + } + + SSDBG( descP, + ("SOCKET", + "esock_setopt_sctp_adaptation_layer -> decode attributes\r\n") ); + + if (! GET_UINT(env, eaind, &ad.ssb_adaptation_ind)) { + + SSDBG( descP, + ("SOCKET", + "esock_setopt_sctp_adaptation_layer -> " + "failed convert to (unsigned) int: " + "\r\n eaind: %T" + "\r\n", eaind) ); + + return esock_make_invalid(env, esock_atom_value); + } + + SSDBG( descP, + ("SOCKET", + "esock_setopt_sctp_adaptation_layer -> set option\r\n") ); + + return esock_setopt_level_opt(env, descP, level, opt, + &ad, sizeof(ad)); + +} +#endif + + + /* esock_setopt_sctp_associnfo - Level SCTP ASSOCINFO option */ @@ -7996,3049 +8839,5924 @@ ERL_NIF_TERM esock_setopt_sctp_associnfo(ErlNifEnv* env, -/* esock_setopt_sctp_events - Level SCTP EVENTS option +#if defined(SCTP_DEFAULT_SEND_PARAM) + +/* esock_setopt_sctp_send_recv_info - Level SCTP SCTP_DEFAULT_SEND_PARAM option */ -#if defined(SCTP_EVENTS) static -ERL_NIF_TERM esock_setopt_sctp_events(ErlNifEnv* env, - ESockDescriptor* descP, - int level, - int opt, - ERL_NIF_TERM eVal) +ERL_NIF_TERM esock_setopt_sctp_default_send_param(ErlNifEnv* env, + ESockDescriptor* descP, + int level, + int opt, + ERL_NIF_TERM eVal) { - struct sctp_event_subscribe events; - BOOLEAN_T error; + ERL_NIF_TERM eStream, eFlags, ePPID, eContext, et2l, eAssocID; + struct sctp_sndrcvinfo info; + unsigned int stream; SSDBG( descP, - ("SOCKET", "esock_setopt_sctp_events {%d} -> entry with" - "\r\n eVal: %T" - "\r\n", descP->sock, eVal) ); + ("SOCKET", "esock_setopt_sctp_default_send_param(%d) -> entry with" + "\r\n level: %d" + "\r\n opt: %d" + "\r\n eVal: %T" + "\r\n", descP->sock, level, opt, eVal) ); // It must be a map - if (! IS_MAP(env, eVal)) - goto invalid; + if (! IS_MAP(env, eVal)) { + + SSDBG( descP, + ("SOCKET", + "esock_setopt_sctp_default_send_param -> " + "value - not a map: " + "\r\n eVal: %T" + "\r\n", eVal) ); + + return esock_make_invalid(env, esock_atom_value); + } SSDBG( descP, ("SOCKET", - "esock_setopt_sctp_events {%d} -> decode attributes\r\n", - descP->sock) ); + "esock_setopt_sctp_default_send_param -> " + "extract attributes\r\n") ); - error = FALSE; - - events.sctp_data_io_event = - esock_setopt_sctp_event(env, eVal, atom_data_io, &error); - events.sctp_association_event = - esock_setopt_sctp_event(env, eVal, atom_association, &error); - events.sctp_address_event = - esock_setopt_sctp_event(env, eVal, atom_address, &error); - events.sctp_send_failure_event = - esock_setopt_sctp_event(env, eVal, atom_send_failure, &error); - events.sctp_peer_error_event = - esock_setopt_sctp_event(env, eVal, atom_peer_error, &error); - events.sctp_shutdown_event = - esock_setopt_sctp_event(env, eVal, atom_shutdown, &error); - events.sctp_partial_delivery_event = - esock_setopt_sctp_event(env, eVal, atom_partial_delivery, &error); - events.sctp_adaptation_layer_event = - esock_setopt_sctp_event(env, eVal, atom_adaptation_layer, &error); + if ( (! GET_MAP_VAL(env, eVal, esock_atom_stream, &eStream)) || + (! GET_MAP_VAL(env, eVal, esock_atom_flags, &eFlags)) || + (! GET_MAP_VAL(env, eVal, esock_atom_ppid, &ePPID)) || + (! GET_MAP_VAL(env, eVal, esock_atom_context, &eContext)) || + (! GET_MAP_VAL(env, eVal, esock_atom_time_to_live, &et2l)) ) { -#if defined(HAVE_STRUCT_SCTP_EVENT_SUBSCRIBE_SCTP_AUTHENTICATION_EVENT) - events.sctp_authentication_event = - esock_setopt_sctp_event(env, eVal, atom_authentication, &error); -#endif + SSDBG( descP, + ("SOCKET", "esock_setopt_sctp_default_send_param -> " + "failed extracting attributes\r\n") ); -#if defined(HAVE_STRUCT_SCTP_EVENT_SUBSCRIBE_SCTP_SENDER_DRY_EVENT) - events.sctp_sender_dry_event = - esock_setopt_sctp_event(env, eVal, atom_sender_dry, &error); -#endif + return esock_make_invalid(env, esock_atom_value); - if (error) { - goto invalid; - } else { - ERL_NIF_TERM result; + } - result = esock_setopt_level_opt(env, descP, level, opt, - &events, sizeof(events)); - SSDBG( descP, - ("SOCKET", - "esock_setopt_sctp_events {%d} -> set events -> %T\r\n", - descP->sock, result) ); + /* The assocId is only needed if caller is + * using the one-to-many style assoc. + */ + if (! GET_MAP_VAL(env, eVal, atom_assoc_id, &eAssocID)) + eAssocID = esock_atom_undefined; - return result; - } - invalid: SSDBG( descP, ("SOCKET", - "esock_setopt_sctp_events {%d} -> invalid\r\n", - descP->sock) ); + "esock_setopt_sctp_default_send_param -> decode attributes\r\n") ); - return esock_make_invalid(env, esock_atom_value); -} + sys_memzero((char*) &info, sizeof(info)); -/* Return the value to make use of automatic type casting. - * Set *error if something goes wrong. - */ -static int esock_setopt_sctp_event(ErlNifEnv *env, - ERL_NIF_TERM eMap, - ERL_NIF_TERM eKey, - BOOLEAN_T *error) -{ - ERL_NIF_TERM eVal; - BOOLEAN_T val; + if (! GET_UINT(env, eStream, &stream)) { - if (GET_MAP_VAL(env, eMap, eKey, &eVal)) - if (esock_decode_bool(eVal, &val)) - return (int) val; + SSDBG( descP, + ("SOCKET", "esock_setopt_sctp_default_send_param -> " + "invalid stream\r\n") ); - *error = TRUE; - return 0; -} -#endif + return esock_make_invalid(env, esock_atom_value); + } else { + info.sinfo_stream = (Uint16) stream; + } + if (! decode_sctp_send_recv_info_flags(env, descP, eFlags, + &info.sinfo_flags) ) { + SSDBG( descP, + ("SOCKET", "esock_setopt_sctp_default_send_param -> " + "invalid flags\r\n") ); -/* esock_setopt_sctp_initmsg - Level SCTP INITMSG option - */ + return esock_make_invalid(env, esock_atom_value); -#if defined(SCTP_INITMSG) -static -ERL_NIF_TERM esock_setopt_sctp_initmsg(ErlNifEnv* env, - ESockDescriptor* descP, - int level, - int opt, - ERL_NIF_TERM eVal) -{ - ERL_NIF_TERM eNumOut, eMaxIn, eMaxAttempts, eMaxInitTO; - struct sctp_initmsg initMsg; - unsigned int tmp; + } + + if (! GET_UINT(env, ePPID, &info.sinfo_ppid)) { - SSDBG( descP, - ("SOCKET", "esock_setopt_sctp_initmsg -> entry with" - "\r\n eVal: %T" - "\r\n", eVal) ); + SSDBG( descP, + ("SOCKET", "esock_setopt_sctp_default_send_param -> " + "invalid ppid\r\n") ); - // It must be a map - if (! IS_MAP(env, eVal)) - goto invalid; + return esock_make_invalid(env, esock_atom_value); + } - SSDBG( descP, - ("SOCKET", - "esock_setopt_sctp_initmsg -> extract attributes\r\n") ); + if (! GET_UINT(env, eContext, &info.sinfo_context)) { - if ((! GET_MAP_VAL(env, eVal, atom_num_outstreams, &eNumOut)) || - (! GET_MAP_VAL(env, eVal, atom_max_instreams, &eMaxIn)) || - (! GET_MAP_VAL(env, eVal, atom_max_attempts, &eMaxAttempts)) || - (! GET_MAP_VAL(env, eVal, atom_max_init_timeo, &eMaxInitTO))) - goto invalid; + SSDBG( descP, + ("SOCKET", "esock_setopt_sctp_default_send_param -> " + "invalid context\r\n") ); - SSDBG( descP, - ("SOCKET", - "esock_setopt_sctp_initmsg -> decode attributes\r\n") ); + return esock_make_invalid(env, esock_atom_value); + } - if (! GET_UINT(env, eNumOut, &tmp)) - goto invalid; - initMsg.sinit_num_ostreams = (Uint16) tmp; + if (! GET_UINT(env, et2l, &info.sinfo_timetolive)) { - if (! GET_UINT(env, eMaxIn, &tmp)) - goto invalid; - initMsg.sinit_max_instreams = (Uint16) tmp; + SSDBG( descP, + ("SOCKET", "esock_setopt_sctp_default_send_param -> " + "invalid time_to_live\r\n") ); - if (! GET_UINT(env, eMaxAttempts, &tmp)) - goto invalid; - initMsg.sinit_max_attempts = (Uint16) tmp; + return esock_make_invalid(env, esock_atom_value); + } - if (! GET_UINT(env, eMaxInitTO, &tmp)) - goto invalid; - initMsg.sinit_max_init_timeo = (Uint16) tmp; + /* The assocId is only needed if caller is + * using the one-to-many style assoc. + */ + if ( (!IS_UNDEFINED(eAssocID)) && + (!decode_sctp_assoc_t(env, eAssocID, &info.sinfo_assoc_id)) ) { + + SSDBG( descP, + ("SOCKET", "esock_setopt_sctp_default_send_param -> " + "invalid assoc-id\r\n") ); + + return esock_make_invalid(env, esock_atom_value); + } + SSDBG( descP, ("SOCKET", - "esock_setopt_sctp_initmsg -> set initmsg option\r\n") ); + "esock_setopt_sctp_default_send_param -> set option\r\n") ); return esock_setopt_level_opt(env, descP, level, opt, - &initMsg, sizeof(initMsg)); + &info, sizeof(info)); - invalid: - return esock_make_invalid(env, esock_atom_value); } -#endif - -/* esock_setopt_sctp_rtoinfo - Level SCTP RTOINFO option +/* esock_getopt_sctp_send_recv_info - Level SCTP SCTP_DEFAULT_SEND_PARAM option */ -#if defined(SCTP_RTOINFO) static -ERL_NIF_TERM esock_setopt_sctp_rtoinfo(ErlNifEnv* env, - ESockDescriptor* descP, - int level, - int opt, - ERL_NIF_TERM eVal) +ERL_NIF_TERM esock_getopt_sctp_default_send_param(ErlNifEnv* env, + ESockDescriptor* descP, + int level, + int opt, + ERL_NIF_TERM eval) { - ERL_NIF_TERM eAssocId, eInitial, eMax, eMin; - struct sctp_rtoinfo rtoInfo; - - SSDBG( descP, - ("SOCKET", "esock_setopt_sctp_rtoinfo -> entry with" - "\r\n eVal: %T" - "\r\n", eVal) ); + struct sctp_sndrcvinfo val; + SOCKOPTLEN_T valSz = sizeof(val); + int res; + ERL_NIF_TERM result; - // It must be a map - if (! IS_MAP(env, eVal)) - goto invalid; + VOID(eval); SSDBG( descP, - ("SOCKET", - "esock_setopt_sctp_rtoinfo -> extract attributes\r\n") ); + ("SOCKET", "%s(%d) -> entry with" + "\r\n level: %d" + "\r\n opt: %d" + "\r\n", __FUNCTION__, descP->sock, level, opt) ); - if ((! GET_MAP_VAL(env, eVal, atom_assoc_id, &eAssocId)) || - (! GET_MAP_VAL(env, eVal, atom_initial, &eInitial)) || - (! GET_MAP_VAL(env, eVal, atom_max, &eMax)) || - (! GET_MAP_VAL(env, eVal, atom_min, &eMin))) - goto invalid; + sys_memzero((char*) &val, valSz); + res = sock_getopt(descP->sock, level, opt, &val, &valSz); - SSDBG( descP, - ("SOCKET", - "esock_setopt_sctp_rtoinfo -> decode attributes\r\n") ); + if (res != 0) { + int save_errno = sock_errno(); - if (! decode_sctp_assoc_t(env, eAssocId, &rtoInfo.srto_assoc_id)) - goto invalid; + SSDBG( descP, + ("SOCKET", + "%s -> " + "failed get option: " + "\r\n errno: %T (%d)" + "\r\n", __FUNCTION__, erl_errno_id(save_errno), save_errno) ); - if ((! GET_UINT(env, eInitial, &rtoInfo.srto_initial)) || - (! GET_UINT(env, eMax, &rtoInfo.srto_max)) || - (! GET_UINT(env, eMin, &rtoInfo.srto_min))) - goto invalid; + result = esock_make_error_errno(env, save_errno); - SSDBG( descP, - ("SOCKET", - "esock_setopt_sctp_rtoinfo -> set associnfo option\r\n") ); + } else { + ERL_NIF_TERM eStream, eSSN, eFlags, ePPID, eContext; + ERL_NIF_TERM eT2L, eTSN, eCumTSN, eAssocID; + + eStream = MKUI(env, val.sinfo_stream); + eSSN = MKUI(env, val.sinfo_ssn); + eFlags = encode_sctp_send_recv_info_flags(env, + descP, val.sinfo_flags); + ePPID = MKUI(env, val.sinfo_ppid); + eContext = MKUI(env, val.sinfo_context); + eT2L = MKUI(env, val.sinfo_timetolive); + eTSN = MKUI(env, val.sinfo_tsn); + eCumTSN = MKUI(env, val.sinfo_cumtsn); + eAssocID = encode_sctp_assoc_t(env, val.sinfo_assoc_id); - return esock_setopt_level_opt(env, descP, level, opt, - &rtoInfo, sizeof(rtoInfo)); + { + ERL_NIF_TERM eInfo; + ERL_NIF_TERM keys[] = {esock_atom_esock_name, + esock_atom_stream, + esock_atom_ssn, + esock_atom_flags, + esock_atom_ppid, + esock_atom_context, + esock_atom_time_to_live, + esock_atom_tsn, + esock_atom_cum_tsn, + atom_assoc_id}; + ERL_NIF_TERM vals[] = {MKA(env, "sctp_send_recv_info"), + eStream, + eSSN, + eFlags, + ePPID, + eContext, + eT2L, + eTSN, + eCumTSN, + eAssocID}; + size_t numKeys = NUM(keys); + + ESOCK_ASSERT( numKeys == NUM(vals) ); + ESOCK_ASSERT( MKMA(env, keys, vals, numKeys, &eInfo) ); - invalid: - return esock_make_invalid(env, esock_atom_value); -} -#endif + SSDBG( descP, + ("SOCKET", + "esock_getopt_sctp_default_send_param -> " + "got option: " + "\r\n %T" + "\r\n", eInfo) ); -#endif // defined(HAVE_SCTP) + result = esock_make_ok2(env, eInfo); + } + } + SSDBG( descP, + ("SOCKET", + "esock_getopt_sctp_default_send_param -> done\r\n") ); + return result; +} -/* esock_setopt_bool_opt - set an option that has an (integer) bool value +/* decode_sctp_send_recv_info_flags + * + * Decode the flags of the send recv info structure + * + * The 'flags' can either be a "raw" integer, in which case we + * take it as is. Or it can be a list of flag(): + * + * flags() :: integer() | [flag()] + * flag() :: unordered | addr_over | abort | eof + * */ - static -ERL_NIF_TERM esock_setopt_bool_opt(ErlNifEnv* env, - ESockDescriptor* descP, - int level, - int opt, - ERL_NIF_TERM eVal) +BOOLEAN_T decode_sctp_send_recv_info_flags(ErlNifEnv* env, + ESockDescriptor* descP, + ERL_NIF_TERM eFlags, + Uint16* flags) { - BOOLEAN_T val; - int ival; + BOOLEAN_T result; + unsigned int tmp, len; + + /* Check first if its a "raw" int => take as is */ + if (GET_UINT(env, eFlags, &tmp)) { - if (! esock_decode_bool(eVal, &val)) - return esock_make_invalid(env, esock_atom_value); + result = TRUE; - ival = (val) ? 1 : 0; - return esock_setopt_level_opt(env, descP, level, opt, - &ival, sizeof(ival)); -} + } else { + /* Not a "raw" integer => it then *must* be a list. + * We do not check this, we simply try to get the list length... + */ + if (! GET_LIST_LEN(env, eFlags, &len)) { -/* esock_setopt_int_opt - set an option that has an integer value - */ + SSDBG( descP, + ("SOCKET", + "decode_sctp_send_recv_info_flags -> " + "failed get (flags) list length: " + "\r\n %T" + "\r\n", eFlags) ); -static -ERL_NIF_TERM esock_setopt_int_opt(ErlNifEnv* env, - ESockDescriptor* descP, - int level, - int opt, - ERL_NIF_TERM eVal) -{ - ERL_NIF_TERM result; - int val; + tmp = 0; + result = FALSE; + } else { - if (GET_INT(env, eVal, &val)) { - result = - esock_setopt_level_opt(env, descP, level, opt, - &val, sizeof(val)); - } else { - result = esock_make_invalid(env, esock_atom_value); + /* If the list length is zero, default, we are done */ + if (len == 0) { + + tmp = 0; + result = TRUE; + + } else { + ERL_NIF_TERM ef, elem, tail; + unsigned int idx; + + for (idx = 0, ef = eFlags, tmp = 0, result = TRUE; idx < len; idx++) { + + if (! GET_LIST_ELEM(env, ef, &elem, &tail)) { + + SSDBG( descP, + ("SOCKET", + "decode_sctp_send_recv_info_flags -> " + "failed get flag %d\r\n", + idx) ); + + tmp = 0; // Just in case + result = FALSE; + + break; + } + + if (IS_IDENTICAL(elem, atom_unordered)) { + tmp |= SCTP_UNORDERED; + } else if (IS_IDENTICAL(elem, atom_addr_over)) { + tmp |= SCTP_ADDR_OVER; + } else if (IS_IDENTICAL(elem, esock_atom_abort)) { + tmp |= SCTP_ABORT; + } else if (IS_IDENTICAL(elem, atom_eof)) { + tmp |= SCTP_EOF; + } else { + + SSDBG( descP, + ("SOCKET", + "decode_sctp_send_recv_info_flags -> " + "invalid flag %d: %T\r\n", idx, elem) ); + + tmp = 0; // Just in case + result = FALSE; + + break; + } + + ef = tail; + } + } + } } - return result; -} + *flags = tmp; + return result; +} -/* esock_setopt_str_opt - set an option that has an string value +/* decode_sctp_send_recv_info_flags + * + * Decode the flags of the send recv info structure + * + * The 'flags' can either be a "raw" integer, in which case we + * take it as is. Or it can be a list of flag(): + * + * flags() :: integer() | [flag()] + * flag() :: unordered | addr_over | abort | eof + * */ - -#if defined(USE_SETOPT_STR_OPT) static -ERL_NIF_TERM esock_setopt_str_opt(ErlNifEnv* env, - ESockDescriptor* descP, - int level, - int opt, - int max, - ERL_NIF_TERM eVal) +BOOLEAN_T encode_sctp_send_recv_info_flags(ErlNifEnv* env, + ESockDescriptor* descP, + unsigned int flags) { - ERL_NIF_TERM result; - int optLen; - char* val = MALLOC(max); - ErlNifBinary bin; + ERL_NIF_TERM eflags; - ESOCK_ASSERT( val != NULL ); - - if ((optLen = GET_STR(env, eVal, val, max)) > 0) { - - optLen--; - - result = - esock_setopt_level_opt(env, descP, level, opt, - val, optLen); - - } else if (enif_inspect_binary(env, eVal, &bin)) { - - optLen = esock_strnlen((char*) bin.data, max - 1); - sys_memcpy(val, bin.data, optLen); - val[optLen] = '\0'; + if (flags == 0) { + eflags = MKEL(env); + } else { + SocketTArray efTA = TARRAY_CREATE(4); + + if ((flags & SCTP_UNORDERED) != 0) + TARRAY_ADD(efTA, atom_unordered); - result = - esock_setopt_level_opt(env, descP, level, opt, - val, optLen); + if ((flags & SCTP_ADDR_OVER) != 0) + TARRAY_ADD(efTA, atom_addr_over); - } else { + if ((flags & SCTP_ABORT) != 0) + TARRAY_ADD(efTA, esock_atom_abort); - result = esock_make_invalid(env, esock_atom_value); + if ((flags & SCTP_EOF) != 0) + TARRAY_ADD(efTA, atom_eof); + TARRAY_TOLIST(efTA, env, &eflags); } - FREE(val); + return eflags; - return result; } + #endif -/* esock_setopt_timeval_opt - set an option that has an (timeval) bool value +/* esock_setopt_sctp_events - Level SCTP EVENTS option */ -#if (defined(SO_RCVTIMEO) || defined(SO_SNDTIMEO)) \ - && defined(ESOCK_USE_RCVSNDTIMEO) +#if defined(SCTP_EVENTS) static -ERL_NIF_TERM esock_setopt_timeval_opt(ErlNifEnv* env, +ERL_NIF_TERM esock_setopt_sctp_events(ErlNifEnv* env, ESockDescriptor* descP, int level, int opt, ERL_NIF_TERM eVal) { - ERL_NIF_TERM result; - struct timeval timeVal; + struct sctp_event_subscribe events = {0}; + BOOLEAN_T error; SSDBG( descP, - ("SOCKET", "esock_setopt_timeval_opt -> entry with" + ("SOCKET", "esock_setopt_sctp_events {%d} -> entry with" "\r\n eVal: %T" - "\r\n", eVal) ); + "\r\n", descP->sock, eVal) ); - if (! esock_decode_timeval(env, eVal, &timeVal)) - return esock_make_invalid(env, esock_atom_value); + // It must be a map + if (! IS_MAP(env, eVal)) + goto invalid; SSDBG( descP, - ("SOCKET", "esock_setopt_timeval_opt -> set timeval option\r\n") ); + ("SOCKET", + "esock_setopt_sctp_events {%d} -> decode attributes\r\n", + descP->sock) ); - result = - esock_setopt_level_opt(env, descP, level, opt, - &timeVal, sizeof(timeVal)); + error = FALSE; - SSDBG( descP, - ("SOCKET", "esock_setopt_timeval_opt -> done with" - "\r\n result: %T" - "\r\n", result) ); + /* 'error' is only set (to TRUE) if an error occurs. + * On success it is not tuched. + */ - return result; + events.sctp_data_io_event = + esock_setopt_sctp_event(env, descP, + eVal, atom_data_io, + esock_atom_true, &error); + events.sctp_association_event = + esock_setopt_sctp_event(env, descP, + eVal, atom_association, + esock_atom_true, &error); + events.sctp_address_event = + esock_setopt_sctp_event(env, descP, + eVal, atom_address, + esock_atom_true, &error); + events.sctp_send_failure_event = + esock_setopt_sctp_event(env, descP, + eVal, atom_send_failure, + esock_atom_true, &error); + events.sctp_peer_error_event = + esock_setopt_sctp_event(env, descP, + eVal, atom_peer_error, + esock_atom_true, &error); + events.sctp_shutdown_event = + esock_setopt_sctp_event(env, descP, + eVal, atom_shutdown, + esock_atom_true, &error); + events.sctp_partial_delivery_event = + esock_setopt_sctp_event(env, descP, + eVal, esock_atom_partial_delivery, + esock_atom_true, &error); + events.sctp_adaptation_layer_event = + esock_setopt_sctp_event(env, descP, + eVal, atom_adaptation_layer, + esock_atom_false, &error); -} +#if defined(HAVE_STRUCT_SCTP_EVENT_SUBSCRIBE_SCTP_AUTHENTICATION_EVENT) + events.sctp_authentication_event = + esock_setopt_sctp_event(env, descP, + eVal, atom_authentication, + esock_atom_false, &error); #endif +#if defined(HAVE_STRUCT_SCTP_EVENT_SUBSCRIBE_SCTP_SENDER_DRY_EVENT) + events.sctp_sender_dry_event = + esock_setopt_sctp_event(env, descP, + eVal, esock_atom_sender_dry, + esock_atom_false, &error); +#endif -static ERL_NIF_TERM esock_setopt_level_opt(ErlNifEnv* env, - ESockDescriptor* descP, - int level, - int opt, - void* optVal, - socklen_t optLen) -{ - if (socket_setopt(descP->sock, level, opt, optVal, optLen)) - return esock_make_error_errno(env, sock_errno()); - else - return esock_atom_ok; -} +#if defined(HAVE_STRUCT_SCTP_EVENT_SUBSCRIBE_SCTP_STREAM_RESET_EVENT) + events.sctp_stream_reset_event = + esock_setopt_sctp_event(env, descP, + eVal, esock_atom_stream_reset, + esock_atom_false, &error); +#endif +#if defined(HAVE_STRUCT_SCTP_EVENT_SUBSCRIBE_SCTP_ASSOC_RESET_EVENT) + events.sctp_assoc_reset_event = + esock_setopt_sctp_event(env, descP, + eVal, esock_atom_assoc_reset, + esock_atom_false, &error); +#endif +#if defined(HAVE_STRUCT_SCTP_EVENT_SUBSCRIBE_SCTP_STREAM_CHANGE_EVENT) + events.sctp_assoc_reset_event = + esock_setopt_sctp_event(env, descP, + eVal, esock_atom_stream_change, + esock_atom_false, &error); +#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 (error) { + goto invalid; + } else { + ERL_NIF_TERM result; -#if defined(IP_TOS) && defined(SOL_IP) && defined(SO_PRIORITY) - int tmpIValPRIO = 0; - int tmpIValTOS = 0; - int resPRIO; - int resTOS; - SOCKOPTLEN_T tmpArgSzPRIO = sizeof(tmpIValPRIO); - SOCKOPTLEN_T tmpArgSzTOS = sizeof(tmpIValTOS); + result = esock_setopt_level_opt(env, descP, level, opt, + &events, sizeof(events)); + SSDBG( descP, + ("SOCKET", + "esock_setopt_sctp_events {%d} -> set events -> %T\r\n", + descP->sock, result) ); - resPRIO = sock_getopt(sock, SOL_SOCKET, SO_PRIORITY, - &tmpIValPRIO, &tmpArgSzPRIO); - resTOS = sock_getopt(sock, SOL_IP, IP_TOS, - &tmpIValTOS, &tmpArgSzTOS); + return result; + } - res = sock_setopt(sock, level, opt, optVal, optLen); - if (res == 0) { + invalid: + SSDBG( descP, + ("SOCKET", + "esock_setopt_sctp_events {%d} -> invalid\r\n", + descP->sock) ); - /* Ok, now we *maybe* need to "maybe" restore PRIO and TOS... - * maybe, possibly, ... - */ + return esock_make_invalid(env, esock_atom_value); +} - 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); +/* Return the value to make use of automatic type casting. + * Set *error if something goes wrong. + */ +static +int esock_setopt_sctp_event(ErlNifEnv* env, + ESockDescriptor* descP, + ERL_NIF_TERM eMap, + ERL_NIF_TERM eKey, + ERL_NIF_TERM eDefaultVal, + BOOLEAN_T* failure) +{ + ERL_NIF_TERM eVal; + BOOLEAN_T val, defaultVal; - /* Some kernels set a SO_PRIORITY by default - * that you are not permitted to reset, - * silently ignore this error condition. - */ + SSDBG( descP, + ("SOCKET", + "esock_setopt_sctp_event -> try get attribute %T\r\n", + eKey)); - if ((resPRIO != 0) && (sock_errno() == EPERM)) { - res = 0; - } else { - res = resPRIO; - } - } - } - } + if (GET_MAP_VAL(env, eMap, eKey, &eVal)) { + + SSDBG( descP, + ("SOCKET", + "esock_setopt_sctp_event -> attribute %T: %T - try decode\r\n", + eKey, eVal)); -#else + if (esock_decode_bool(eVal, &val)) { + return (int) val; + } else { + SSDBG( descP, + ("SOCKET", + "esock_setopt_sctp_event -> failed decode attribute %T\r\n", + eKey)); + } - res = sock_setopt(sock, level, opt, optVal, optLen); + } else { -#endif + SSDBG( descP, + ("SOCKET", + "esock_setopt_sctp_event -> " + "attribute %T not specified - use default: %T\r\n", + eKey, eDefaultVal)); - return res; + if (esock_decode_bool(eDefaultVal, &defaultVal)) { + return (int) defaultVal; + } + + } + + *failure = TRUE; + return 0; } +#endif -/* ---------------------------------------------------------------------- - * nif_getopt - * - * Description: - * Get socket option. - * Its possible to use a ValueSpec to select - * how the value should be decoded. - * - * Arguments: - * Socket (ref) - Points to the socket descriptor. - * Level (int) - Protocol level, encoded or native - * Opt (int) - Option, encoded or native - * ValueSpec (term) - How to decode the value [optional] +/* esock_setopt_sctp_initmsg - Level SCTP INITMSG option */ +#if defined(SCTP_INITMSG) static -ERL_NIF_TERM nif_getopt(ErlNifEnv* env, - int argc, - const ERL_NIF_TERM argv[]) +ERL_NIF_TERM esock_setopt_sctp_initmsg(ErlNifEnv* env, + ESockDescriptor* descP, + int level, + int opt, + ERL_NIF_TERM eVal) { - ESockDescriptor* descP; - ERL_NIF_TERM esock, elevel, eopt, evspec; - int level, opt; + ERL_NIF_TERM eNumOut, eMaxIn, eMaxAttempts, eMaxInitTO; + struct sctp_initmsg initMsg; + unsigned int tmp; - ESOCK_ASSERT( (argc == 3) || (argc == 4) ); + SSDBG( descP, + ("SOCKET", "esock_setopt_sctp_initmsg -> entry with" + "\r\n eVal: %T" + "\r\n", eVal) ); - esock = argv[0]; - elevel = argv[1]; - eopt = argv[2]; - evspec = ((argc == 4) ? argv[3] : esock_atom_undefined); + // It must be a map + if (! IS_MAP(env, eVal)) + goto invalid; - SGDBG( ("SOCKET", - "nif_getopt -> entry with argc: %d" - "\r\n esock: %T" - "\r\n elevel: %T" - "\r\n eopt: %T" - "\r\n evspec: %T" - "\r\n", argc, esock, elevel, eopt, evspec) ); + SSDBG( descP, + ("SOCKET", + "esock_setopt_sctp_initmsg -> extract attributes\r\n") ); - if (! ESOCK_GET_RESOURCE(env, esock, (void**) &descP)) { - SGDBG( ("SOCKET", - "nif_getopt -> failed initial args check - sock\r\n") ); - return enif_make_badarg(env); - } + if ((! GET_MAP_VAL(env, eVal, atom_num_outstreams, &eNumOut)) || + (! GET_MAP_VAL(env, eVal, atom_max_instreams, &eMaxIn)) || + (! GET_MAP_VAL(env, eVal, atom_max_attempts, &eMaxAttempts)) || + (! GET_MAP_VAL(env, eVal, atom_max_init_timeo, &eMaxInitTO))) + goto invalid; - if (! GET_INT(env, eopt, &opt)) { - SSDBG( descP, - ("SOCKET", - "nif_getopt -> failed initial args check - opt\r\n") ); - if (! IS_INTEGER(env, eopt)) - return enif_make_badarg(env); - else - return esock_make_error_integer_range(env, eopt); - } + SSDBG( descP, + ("SOCKET", + "esock_setopt_sctp_initmsg -> decode attributes\r\n") ); - if ((COMPARE(elevel, atom_otp) == 0) && - (argc == 3)) { - return ESOCK_IO_GETOPT_OTP(env, descP, opt) ; - } + if (! GET_UINT(env, eNumOut, &tmp)) + goto invalid; + initMsg.sinit_num_ostreams = (Uint16) tmp; - if (esock_decode_level(env, elevel, &level)) { - if (argc == 4) { - return ESOCK_IO_GETOPT_NATIVE(env, descP, level, opt, evspec); - } else { - return ESOCK_IO_GETOPT(env, descP, level, opt); - } - } + if (! GET_UINT(env, eMaxIn, &tmp)) + goto invalid; + initMsg.sinit_max_instreams = (Uint16) tmp; - SGDBG( ("SOCKET", "nif_getopt -> failed args check\r\n") ); - if (IS_INTEGER(env, elevel)) - return esock_make_error_integer_range(env, elevel); - else - return enif_make_badarg(env); + if (! GET_UINT(env, eMaxAttempts, &tmp)) + goto invalid; + initMsg.sinit_max_attempts = (Uint16) tmp; + + if (! GET_UINT(env, eMaxInitTO, &tmp)) + goto invalid; + initMsg.sinit_max_init_timeo = (Uint16) tmp; + + SSDBG( descP, + ("SOCKET", + "esock_setopt_sctp_initmsg -> set initmsg option\r\n") ); + + return esock_setopt_level_opt(env, descP, level, opt, + &initMsg, sizeof(initMsg)); + invalid: + return esock_make_invalid(env, esock_atom_value); } +#endif -/* esock_getopt_otp - Handle OTP (level) options +/* esock_setopt_sctp_rtoinfo - Level SCTP RTOINFO option */ +#if defined(SCTP_RTOINFO) static -ERL_NIF_TERM esock_getopt_otp(ErlNifEnv* env, - ESockDescriptor* descP, - int eOpt) +ERL_NIF_TERM esock_setopt_sctp_rtoinfo(ErlNifEnv* env, + ESockDescriptor* descP, + int level, + int opt, + ERL_NIF_TERM eVal) { - ERL_NIF_TERM result; + ERL_NIF_TERM eAssocId, eInitial, eMax, eMin; + struct sctp_rtoinfo rtoInfo; SSDBG( descP, - ("SOCKET", "esock_getopt_otp -> entry with" - "\r\n eOpt: %d" - "\r\n", eOpt) ); + ("SOCKET", "esock_setopt_sctp_rtoinfo -> entry with" + "\r\n eVal: %T" + "\r\n", eVal) ); - switch (eOpt) { - case ESOCK_OPT_OTP_DEBUG: - MLOCK(descP->readMtx); - result = esock_getopt_otp_debug(env, descP); - MUNLOCK(descP->readMtx); - break; + // It must be a map + if (! IS_MAP(env, eVal)) + goto invalid; - case ESOCK_OPT_OTP_IOW: - MLOCK(descP->readMtx); - result = esock_getopt_otp_iow(env, descP); - MUNLOCK(descP->readMtx); - break; + SSDBG( descP, + ("SOCKET", + "esock_setopt_sctp_rtoinfo -> extract attributes\r\n") ); - case ESOCK_OPT_OTP_CTRL_PROC: - MLOCK(descP->readMtx); - result = esock_getopt_otp_ctrl_proc(env, descP); - MUNLOCK(descP->readMtx); - break; + if ((! GET_MAP_VAL(env, eVal, atom_assoc_id, &eAssocId)) || + (! GET_MAP_VAL(env, eVal, atom_initial, &eInitial)) || + (! GET_MAP_VAL(env, eVal, atom_max, &eMax)) || + (! GET_MAP_VAL(env, eVal, atom_min, &eMin))) + goto invalid; - case ESOCK_OPT_OTP_SELECT_READ: - MLOCK(descP->readMtx); - result = esock_getopt_otp_select_read(env, descP); - MUNLOCK(descP->readMtx); - break; + SSDBG( descP, + ("SOCKET", + "esock_setopt_sctp_rtoinfo -> decode attributes\r\n") ); - case ESOCK_OPT_OTP_RCVBUF: - MLOCK(descP->readMtx); - result = esock_getopt_otp_rcvbuf(env, descP); - MUNLOCK(descP->readMtx); - break; + if (! decode_sctp_assoc_t(env, eAssocId, &rtoInfo.srto_assoc_id)) + goto invalid; - case ESOCK_OPT_OTP_RCVCTRLBUF: - MLOCK(descP->readMtx); - result = esock_getopt_otp_rcvctrlbuf(env, descP); - MUNLOCK(descP->readMtx); - break; + if ((! GET_UINT(env, eInitial, &rtoInfo.srto_initial)) || + (! GET_UINT(env, eMax, &rtoInfo.srto_max)) || + (! GET_UINT(env, eMin, &rtoInfo.srto_min))) + goto invalid; - case ESOCK_OPT_OTP_SNDCTRLBUF: - MLOCK(descP->writeMtx); - result = esock_getopt_otp_sndctrlbuf(env, descP); - MUNLOCK(descP->writeMtx); - break; + SSDBG( descP, + ("SOCKET", + "esock_setopt_sctp_rtoinfo -> set associnfo option\r\n") ); - case ESOCK_OPT_OTP_FD: - MLOCK(descP->readMtx); - result = esock_getopt_otp_fd(env, descP); - MUNLOCK(descP->readMtx); - break; + return esock_setopt_level_opt(env, descP, level, opt, + &rtoInfo, sizeof(rtoInfo)); - case ESOCK_OPT_OTP_META: - MLOCK(descP->writeMtx); - result = esock_getopt_otp_meta(env, descP); - MUNLOCK(descP->writeMtx); - break; + invalid: + return esock_make_invalid(env, esock_atom_value); +} +#endif - case ESOCK_OPT_OTP_USE_REGISTRY: - MLOCK(descP->readMtx); - result = esock_getopt_otp_use_registry(env, descP); - MUNLOCK(descP->readMtx); - break; - /* *** INTERNAL *** */ - case ESOCK_OPT_OTP_DOMAIN: - MLOCK(descP->readMtx); - result = esock_getopt_otp_domain(env, descP); - MUNLOCK(descP->readMtx); - break; +/* *** esock_setopt_sctp_primary_addr *** + * Level SCTP SCTP_PRIMARY_ADDR option + */ -#if 0 - case ESOCK_OPT_OTP_TYPE: - MLOCK(descP->readMtx); - result = esock_getopt_otp_type(env, descP); - MUNLOCK(descP->readMtx); - break; +#if defined(SCTP_PRIMARY_ADDR) +static +ERL_NIF_TERM esock_setopt_sctp_primary_addr(ErlNifEnv* env, + ESockDescriptor* descP, + int level, + int opt, + ERL_NIF_TERM eVal) +{ + ERL_NIF_TERM eAssocId, eAddr; + struct sctp_setpeerprim primAddr; + SOCKLEN_T primAddrLen; - case ESOCK_OPT_OTP_PROTOCOL: - MLOCK(descP->readMtx); - result = esock_getopt_otp_protocol(env, descP); - MUNLOCK(descP->readMtx); - break; - case ESOCK_OPT_OTP_DTP: - MLOCK(descP->readMtx); - result = esock_getopt_otp_dtp(env, descP); - MUNLOCK(descP->readMtx); - break; -#endif + SSDBG( descP, + ("SOCKET", "esock_setopt_sctp_primary_addr -> entry with" + "\r\n eVal: %T" + "\r\n", eVal) ); - default: - MLOCK(descP->readMtx); - SSDBG( descP, - ("SOCKET", "esock_getopt_otp {%d} -> invalid with" - "\r\n eOpt: %d" - "\r\n", descP->sock, eOpt) ); - MUNLOCK(descP->readMtx); + // It must be a map + if (! IS_MAP(env, eVal)) + return esock_make_invalid(env, esock_atom_value); - /* This is an internal error - prim_inet gave us junk */ - result = - esock_raise_invalid(env, - MKT2(env, - atom_otp_socket_option, - MKI(env, eOpt))); - break; - } + SSDBG( descP, + ("SOCKET", + "esock_setopt_sctp_primary_addr -> " + "extract attributes\r\n") ); - return result; -} + if ((! GET_MAP_VAL(env, eVal, atom_assoc_id, &eAssocId)) || + (! GET_MAP_VAL(env, eVal, esock_atom_addr, &eAddr))) + return esock_make_invalid(env, esock_atom_value); + SSDBG( descP, + ("SOCKET", + "esock_setopt_sctp_primary_addr -> " + "decode attributes\r\n") ); -/* esock_getopt_otp_debug - Handle the OTP (level) debug option - */ + if (! decode_sctp_assoc_t(env, eAssocId, &primAddr.sspp_assoc_id)) + return esock_make_invalid(env, esock_atom_value); -static -ERL_NIF_TERM esock_getopt_otp_debug(ErlNifEnv* env, - ESockDescriptor* descP) -{ - ERL_NIF_TERM eVal; + PUSH_SUPPRESS_ADDRESS_OF_PACKED_MEMBER(); + if ((! esock_decode_sockaddr(env, eAddr, + (ESockAddress*) (&primAddr.sspp_addr), + &primAddrLen))) + return esock_make_invalid(env, esock_atom_value); + POP_SUPPRESS_ADDRESS_OF_PACKED_MEMBER(); - if (! IS_OPEN(descP->readState)) { - SSDBG( descP, - ("SOCKET", "esock_getopt_otp_debug {%d} -> done closed\r\n", - descP->sock) ); - return esock_make_error_closed(env); - } + SSDBG( descP, + ("SOCKET", + "esock_setopt_sctp_primary_addr -> set option\r\n") ); - eVal = esock_encode_bool(descP->dbg); + return esock_setopt_level_opt(env, descP, level, opt, + &primAddr, sizeof(primAddr)); - return esock_make_ok2(env, eVal); } +#endif -/* esock_getopt_otp_iow - Handle the OTP (level) iow option +/* *** esock_setopt_sctp_set_peer_primary_addr *** + * Level SCTP SCTP_SET_PEER_PRIMARY_ADDR option */ +#if defined(SCTP_SET_PEER_PRIMARY_ADDR) static -ERL_NIF_TERM esock_getopt_otp_iow(ErlNifEnv* env, - ESockDescriptor* descP) +ERL_NIF_TERM esock_setopt_sctp_set_peer_primary_addr(ErlNifEnv* env, + ESockDescriptor* descP, + int level, + int opt, + ERL_NIF_TERM eVal) { - ERL_NIF_TERM eVal; + ERL_NIF_TERM eAssocId, eAddr; + struct sctp_setpeerprim primAddr; + SOCKLEN_T primAddrLen; - if (! IS_OPEN(descP->readState)) { - SSDBG( descP, - ("SOCKET", "esock_getopt_otp_iow {%d} -> done closed\r\n", - descP->sock) ); - return esock_make_error_closed(env); - } - - eVal = esock_encode_bool(descP->iow); SSDBG( descP, - ("SOCKET", "esock_getopt_otp_iow {%d} ->" + ("SOCKET", "esock_setopt_sctp_set_peer_primary_addr -> entry with" "\r\n eVal: %T" - "\r\n", descP->sock, eVal) ); - - return esock_make_ok2(env, eVal); -} + "\r\n", eVal) ); + // It must be a map + if (! IS_MAP(env, eVal)) + return esock_make_invalid(env, esock_atom_value); + SSDBG( descP, + ("SOCKET", + "esock_setopt_sctp_set_peer_primary_addr -> " + "extract attributes\r\n") ); -/* esock_getopt_otp_select_read - Handle the OTP (level) select_read option - */ + if ((! GET_MAP_VAL(env, eVal, atom_assoc_id, &eAssocId)) || + (! GET_MAP_VAL(env, eVal, esock_atom_addr, &eAddr))) + return esock_make_invalid(env, esock_atom_value); -static -ERL_NIF_TERM esock_getopt_otp_select_read(ErlNifEnv* env, - ESockDescriptor* descP) -{ - ERL_NIF_TERM eVal; + SSDBG( descP, + ("SOCKET", + "esock_setopt_sctp_set_peer_primary_addr -> " + "decode attributes\r\n") ); - if (! IS_OPEN(descP->readState)) { - SSDBG( descP, - ("SOCKET", "esock_getopt_otp_select_read {%d} -> done closed\r\n", - descP->sock) ); - return esock_make_error_closed(env); - } + if (! decode_sctp_assoc_t(env, eAssocId, &primAddr.sspp_assoc_id)) + return esock_make_invalid(env, esock_atom_value); - eVal = esock_encode_bool(descP->selectRead); + PUSH_SUPPRESS_ADDRESS_OF_PACKED_MEMBER(); + if ((! esock_decode_sockaddr(env, eAddr, + (ESockAddress*) (&primAddr.sspp_addr), + &primAddrLen))) + return esock_make_invalid(env, esock_atom_value); + POP_SUPPRESS_ADDRESS_OF_PACKED_MEMBER(); SSDBG( descP, - ("SOCKET", "esock_getopt_otp_select_read {%d} ->" - "\r\n eVal: %T" - "\r\n", descP->sock, eVal) ); + ("SOCKET", + "esock_setopt_sctp_set_peer_primary_addr -> set option\r\n") ); + + return esock_setopt_level_opt(env, descP, level, opt, + &primAddr, sizeof(primAddr)); - return esock_make_ok2(env, eVal); } +#endif +#if defined(SCTP_PEER_ADDR_PARAMS) -/* esock_getopt_otp_ctrl_proc - Handle the OTP (level) controlling_process option +/* *** esock_setopt_sctp_peer_addr_params *** + * Level SCTP SCTP_PEER_ADDR_PARAMS option */ static -ERL_NIF_TERM esock_getopt_otp_ctrl_proc(ErlNifEnv* env, - ESockDescriptor* descP) +ERL_NIF_TERM esock_setopt_sctp_peer_addr_params(ErlNifEnv* env, + ESockDescriptor* descP, + int level, + int opt, + ERL_NIF_TERM eVal) { - ERL_NIF_TERM eVal; + struct sctp_paddrparams pap; + SOCKLEN_T papLen; + unsigned int pmr; + ERL_NIF_TERM eAssocID, eAddr, ehbi, ePathMaxRxt; +#if defined(HAVE_STRUCT_SCTP_PADDRPARAMS_SPP_PATHMTU) + ERL_NIF_TERM ePathMtu; +#endif +#if defined(HAVE_STRUCT_SCTP_PADDRPARAMS_SPP_SACKDELAY) + ERL_NIF_TERM eSackDelay; +#endif +#if defined(HAVE_STRUCT_SCTP_PADDRPARAMS_SPP_FLAGS) + ERL_NIF_TERM eFlags; +#endif +#if defined(HAVE_STRUCT_SCTP_PADDRPARAMS_SPP_IPV6_FLOWLABEL) + ERL_NIF_TERM eFlowLabel; +#endif +#if defined(HAVE_STRUCT_SCTP_PADDRPARAMS_SPP_DSCP) + unsigned int dscp; + ERL_NIF_TERM eDSCP; +#endif + + SSDBG( descP, + ("SOCKET", "esock_setopt_sctp_peer_addr_params -> entry with" + "\r\n eVal: %T" + "\r\n", eVal) ); + + // It must be a map + if (! IS_MAP(env, eVal)) { - if (! IS_OPEN(descP->readState)) { SSDBG( descP, - ("SOCKET", - "esock_getopt_otp_ctrl_proc {%d} -> done closed\r\n", - descP->sock) ); - return esock_make_error_closed(env); - } + ("SOCKET", "esock_setopt_sctp_peer_addr_params -> " + "value *not* a map\r\n") ); - eVal = MKPID(env, &descP->ctrlPid); + return esock_make_invalid(env, esock_atom_value); + } SSDBG( descP, - ("SOCKET", "esock_getopt_otp_ctrlProc {%d} ->" - "\r\n eVal: %T" - "\r\n", descP->sock, eVal) ); + ("SOCKET", + "esock_setopt_sctp_peer_addr_params -> " + "extract attributes\r\n") ); - return esock_make_ok2(env, eVal); -} + if ( (! GET_MAP_VAL(env, eVal, atom_assoc_id, &eAssocID)) || + (! GET_MAP_VAL(env, eVal, esock_atom_addr, &eAddr)) || + (! GET_MAP_VAL(env, eVal, atom_heartbeat_interval, &ehbi)) || + (! GET_MAP_VAL(env, eVal, atom_path_max_rxt, &ePathMaxRxt)) ) { + SSDBG( descP, + ("SOCKET", "esock_setopt_sctp_peer_addr_params -> " + "failed extracting attributes\r\n") ); -/* esock_getopt_otp_rcvbuf - Handle the OTP (level) rcvbuf option - */ + return esock_make_invalid(env, esock_atom_value); + } -static -ERL_NIF_TERM esock_getopt_otp_rcvbuf(ErlNifEnv* env, - ESockDescriptor* descP) -{ - ERL_NIF_TERM eVal; +#if defined(HAVE_STRUCT_SCTP_PADDRPARAMS_SPP_FLAGS) + if (! GET_MAP_VAL(env, eVal, esock_atom_flags, &eFlags) ) { - if (! IS_OPEN(descP->readState)) { SSDBG( descP, - ("SOCKET", "esock_getopt_otp_rcvbuf {%d} -> done closed\r\n", - descP->sock) ); - return esock_make_error_closed(env); + ("SOCKET", "esock_setopt_sctp_peer_addr_params -> " + "failed extracting attribute flags\r\n") ); + + return esock_make_invalid(env, esock_atom_value); } +#endif -#ifdef __WIN32__ - eVal = MKUL(env, (unsigned long) descP->rBufSz); -#else - if (descP->rNum == 0) { - eVal = MKUL(env, (unsigned long) descP->rBufSz); - } else { - eVal = MKT2(env, - MKI(env, descP->rNum), - MKUL(env, (unsigned long) descP->rBufSz)); +#if defined(HAVE_STRUCT_SCTP_PADDRPARAMS_SPP_PATHMTU) + if (! GET_MAP_VAL(env, eVal, atom_path_mtu, &ePathMtu) ) { + + SSDBG( descP, + ("SOCKET", "esock_setopt_sctp_peer_addr_params -> " + "failed extracting attribute pathmtu\r\n") ); + + return esock_make_invalid(env, esock_atom_value); } #endif - SSDBG( descP, - ("SOCKET", "esock_getopt_otp_rcvbuf {%d} ->" - "\r\n eVal: %T" - "\r\n", descP->sock, eVal) ); +#if defined(HAVE_STRUCT_SCTP_PADDRPARAMS_SPP_SACKDELAY) + if (! GET_MAP_VAL(env, eVal, atom_sack_delay, &eSackDelay) ) { - return esock_make_ok2(env, eVal); -} + SSDBG( descP, + ("SOCKET", "esock_setopt_sctp_peer_addr_params -> " + "failed extracting attribute sackdelay\r\n") ); + + return esock_make_invalid(env, esock_atom_value); + } +#endif +#if defined(HAVE_STRUCT_SCTP_PADDRPARAMS_SPP_IPV6_FLOWLABEL) + if (! GET_MAP_VAL(env, eVal, atom_sack_delay, &eFlowLabel) ) { + SSDBG( descP, + ("SOCKET", "esock_setopt_sctp_peer_addr_params -> " + "failed extracting attribute ipv6_flowlabel\r\n") ); -/* esock_getopt_otp_rcvctrlbuf - Handle the OTP (level) rcvctrlbuf option - */ + return esock_make_invalid(env, esock_atom_value); + } +#endif -static -ERL_NIF_TERM esock_getopt_otp_rcvctrlbuf(ErlNifEnv* env, - ESockDescriptor* descP) -{ - ERL_NIF_TERM eVal; +#if defined(HAVE_STRUCT_SCTP_PADDRPARAMS_SPP_DSCP) + if (! GET_MAP_VAL(env, eVal, atom_sack_delay, &eDSCP) ) { - if (! IS_OPEN(descP->readState)) { SSDBG( descP, - ("SOCKET", - "esock_getopt_otp_rcvctrlbuf {%d} -> done closed\r\n", - descP->sock) ); - return esock_make_error_closed(env); - } + ("SOCKET", "esock_setopt_sctp_peer_addr_params -> " + "failed extracting attribute dscp\r\n") ); - eVal = MKUL(env, (unsigned long) descP->rCtrlSz); + return esock_make_invalid(env, esock_atom_value); + } +#endif SSDBG( descP, - ("SOCKET", "esock_getopt_otp_rcvctrlbuf {%d} ->" - "\r\n eVal: %T" - "\r\n", descP->sock, eVal) ); - - return esock_make_ok2(env, eVal); -} + ("SOCKET", + "esock_setopt_sctp_peer_addr_params -> " + "decode attributes\r\n") ); + if (! decode_sctp_assoc_t(env, eAssocID, &pap.spp_assoc_id)) { + SSDBG( descP, + ("SOCKET", "esock_setopt_sctp_peer_addr_params -> " + "invalid assocId\r\n") ); -/* esock_getopt_otp_sndctrlbuf - Handle the OTP (level) sndctrlbuf option - */ + return esock_make_invalid(env, esock_atom_value); + } -static -ERL_NIF_TERM esock_getopt_otp_sndctrlbuf(ErlNifEnv* env, - ESockDescriptor* descP) -{ - ERL_NIF_TERM eVal; + PUSH_SUPPRESS_ADDRESS_OF_PACKED_MEMBER(); + if ((! esock_decode_sockaddr(env, eAddr, + (ESockAddress*) (&pap.spp_address), + &papLen))) { - if (! IS_OPEN(descP->writeState)) { SSDBG( descP, - ("SOCKET", - "esock_getopt_otp_sndctrlbuf {%d} -> done closed\r\n", - descP->sock) ); - return esock_make_error_closed(env); + ("SOCKET", "esock_setopt_sctp_peer_addr_params -> " + "invalid address\r\n") ); + + return esock_make_invalid(env, esock_atom_value); } - eVal = MKUL(env, (unsigned long) descP->wCtrlSz); + if (! GET_UINT(env, ehbi, &pap.spp_hbinterval)) { - SSDBG( descP, - ("SOCKET", "esock_getopt_otp_sndctrlbuf {%d} ->" - "\r\n eVal: %T" - "\r\n", descP->sock, eVal) ); + SSDBG( descP, + ("SOCKET", "esock_setopt_sctp_peer_addr_params -> " + "invalid hb-interval\r\n") ); - return esock_make_ok2(env, eVal); -} + return esock_make_invalid(env, esock_atom_value); + } + if (! GET_UINT(env, ePathMaxRxt, &pmr)) { + SSDBG( descP, + ("SOCKET", "esock_setopt_sctp_peer_addr_params -> " + "invalid path-max-rxt\r\n") ); -/* esock_getopt_otp_fd - Handle the OTP (level) fd option - */ + return esock_make_invalid(env, esock_atom_value); + } else { + pap.spp_pathmaxrxt = (Uint16) pmr; + } -static -ERL_NIF_TERM esock_getopt_otp_fd(ErlNifEnv* env, - ESockDescriptor* descP) -{ - ERL_NIF_TERM eVal; +#if defined(HAVE_STRUCT_SCTP_PADDRPARAMS_SPP_PATHMTU) + if (! GET_UINT(env, ePathMtu, &pap.spp_pathmtu)) { - if (! IS_OPEN(descP->readState)) { SSDBG( descP, - ("SOCKET", "esock_getopt_otp_debug {%d} -> done closed\r\n", - descP->sock) ); - return esock_make_error_closed(env); + ("SOCKET", "esock_setopt_sctp_peer_addr_params -> " + "invalid path-mtu\r\n") ); + + return esock_make_invalid(env, esock_atom_value); } +#endif - eVal = MKI(env, descP->sock); +#if defined(HAVE_STRUCT_SCTP_PADDRPARAMS_SPP_SACKDELAY) + if (! GET_UINT(env, eSackDelay, &pap.spp_sackdelay)) { - SSDBG( descP, - ("SOCKET", "esock_getopt_otp_fd {%d} ->" - "\r\n eVal: %T" - "\r\n", descP->sock, eVal) ); + SSDBG( descP, + ("SOCKET", "esock_setopt_sctp_peer_addr_params -> " + "invalid sack-delay\r\n") ); - return esock_make_ok2(env, eVal); -} + return esock_make_invalid(env, esock_atom_value); + } +#endif +#if defined(HAVE_STRUCT_SCTP_PADDRPARAMS_SPP_FLAGS) + if (! decode_pap_flags(env, descP, eFlags, &pap.spp_flags)) { + SSDBG( descP, + ("SOCKET", "esock_setopt_sctp_peer_addr_params -> " + "invalid flags\r\n") ); -/* esock_getopt_otp_meta - Handle the OTP (level) meta option - */ + return esock_make_invalid(env, esock_atom_value); + } +#endif -static -ERL_NIF_TERM esock_getopt_otp_meta(ErlNifEnv* env, - ESockDescriptor* descP) -{ - ERL_NIF_TERM eVal; +#if defined(HAVE_STRUCT_SCTP_PADDRPARAMS_SPP_IPV6_FLOWLABEL) + if (! GET_UINT(env, eFlowLabel, &pap.spp_ipv6_flowlabel)) { - if (! IS_OPEN(descP->writeState)) { SSDBG( descP, - ("SOCKET", "esock_getopt_otp_meta {%d} -> done closed\r\n", - descP->sock) ); - return esock_make_error_closed(env); + ("SOCKET", "esock_setopt_sctp_peer_addr_params -> " + "invalid (ipv6) flowlabel\r\n") ); + + return esock_make_invalid(env, esock_atom_value); } +#endif - eVal = CP_TERM(env, descP->meta.ref); +#if defined(HAVE_STRUCT_SCTP_PADDRPARAMS_SPP_DSCP) + if (! GET_UINT(env, eDSCP, &dscp)) { - SSDBG( descP, - ("SOCKET", "esock_getopt_otp_meta {%d} ->" - "\r\n eVal: %T" - "\r\n", descP->sock, eVal) ); + SSDBG( descP, + ("SOCKET", "esock_setopt_sctp_peer_addr_params -> " + "invalid DSCP\r\n") ); - return esock_make_ok2(env, eVal); -} + return esock_make_invalid(env, esock_atom_value); + } else { + pap.spp_dscp = (unsigned char) dscp; + } +#endif + POP_SUPPRESS_ADDRESS_OF_PACKED_MEMBER(); + SSDBG( descP, + ("SOCKET", + "esock_setopt_sctp_set_peer_primary_addr -> set option\r\n") ); + return esock_setopt_level_opt(env, descP, level, opt, + &pap, sizeof(pap)); -/* esock_getopt_otp_use_registry - Handle the OTP (level) use_registry option - */ +} +/* decode_pap_flags + * + * Decode Peer Address Parameter flags + * + * The 'flags' can either be a "raw" integer, in which case we + * take it as is. Or it can be a list of atom (flags): + * + * integer() | [atom()] + * + */ +#if defined(HAVE_STRUCT_SCTP_PADDRPARAMS_SPP_FLAGS) static -ERL_NIF_TERM esock_getopt_otp_use_registry(ErlNifEnv* env, - ESockDescriptor* descP) +BOOLEAN_T decode_pap_flags(ErlNifEnv* env, + ESockDescriptor* descP, + ERL_NIF_TERM eFlags, + unsigned int* flags) { - ERL_NIF_TERM eVal = esock_encode_bool(descP->useReg); + unsigned int tmp, len; - return esock_make_ok2(env, eVal); -} + /* Check first if its a "raw" int => take as is */ + if (! GET_UINT(env, eFlags, &tmp)) { + /* Not an int => It then *must* be a list of atoms! */ + if (! IS_LIST(env, eFlags) ) { + SSDBG( descP, + ("SOCKET", "decode_pap_flags -> invalid flags: " + "\r\n %T", eFlags) ); -/* - * esock_getopt_otp_domain - Handle the OTP (level) domain option - */ + *flags = 0; // Just in case -static -ERL_NIF_TERM esock_getopt_otp_domain(ErlNifEnv* env, - ESockDescriptor* descP) -{ - ERL_NIF_TERM domain, result; + return FALSE; + } - if (! IS_OPEN(descP->readState)) { - SSDBG( descP, - ("SOCKET", "esock_getopt_otp_domain {%d} -> done closed\r\n", - descP->sock) ); - return esock_make_error_closed(env); - } + if (! GET_LIST_LEN(env, eFlags, &len)) { - esock_encode_domain(env, descP->domain, &domain); - result = esock_make_ok2(env, domain); + SSDBG( descP, + ("SOCKET", + "decode_pap_flags -> invalid flags (list) length\r\n") ); - SSDBG( descP, - ("SOCKET", "esock_getopt_otp_domain {%d} ->" - "\r\n result: %T" - "\r\n", descP->sock, result) ); + *flags = 0; // Just in case - return result; -} + return FALSE; + } + if (len == 0) { + tmp = 0; + } else { + ERL_NIF_TERM ef, elem, tail; + unsigned int idx; + BOOLEAN_T hb = FALSE, pmtu = FALSE; +#if defined(SPP_SACKDELAY_DISABLE) || defined(SPP_SACKDELAY_ENABLE) + BOOLEAN_T sack = FALSE; +#endif + + /* We have three pair of flags that both cannot exist + * at the same time: + * * enable_hearbeats | disable_hearbeats + * * enable_pmtu_discovery | disable_pmtu_discovery + * * enable_sack | disable_sack + */ + for (idx = 0, ef = eFlags, tmp = 0; idx < len; idx++) { + if (! GET_LIST_ELEM(env, ef, &elem, &tail)) { + + SSDBG( descP, + ("SOCKET", + "decode_pap_flags -> failed get flag %d\r\n", + idx) ); -#if 0 + *flags = 0; // Just in case -/* - * esock_getopt_otp_type - Handle the OTP (level) type options. - */ + return FALSE; + } -static -ERL_NIF_TERM esock_getopt_otp_type(ErlNifEnv* env, - ESockDescriptor* descP) -{ - ERL_NIF_TERM type, result; + if (IS_IDENTICAL(elem, atom_enable_heartbeats)) { - if (! IS_OPEN(descP->readState)) { - SSDBG( descP, - ("SOCKET", "esock_getopt_otp_type {%d} -> done closed\r\n", - descP->sock) ); - return esock_make_error_closed(env); - } + /* SPP_HB_ENABLE */ - esock_encode_type(env, descP->type, &type); - result = esock_make_ok2(env, type); + if (hb) { + + SSDBG( descP, + ("SOCKET", + "decode_pap_flags -> " + "invalid enable_heartbeats flag - " + "disable_heartbeats already found\r\n") ); - SSDBG( descP, - ("SOCKET", "esock_getopt_otp_type {%d} ->" - "\r\n result: %T" - "\r\n", descP->sock, result) ); + *flags = 0; // Just in case - return result; -} + return FALSE; + } else { + tmp |= SPP_HB_ENABLE; + hb = TRUE; + } + } else if (IS_IDENTICAL(elem, atom_disable_heartbeats)) { + /* SPP_HB_DISABLE */ -/* - * esock_getopt_otp_protocol - Handle the OTP (level) protocol options. - */ + if (hb) { + + SSDBG( descP, + ("SOCKET", + "decode_pap_flags -> " + "invalid disable_heartbeats flag - " + "enable_heartbeats already found\r\n") ); -static -ERL_NIF_TERM esock_getopt_otp_protocol(ErlNifEnv* env, - ESockDescriptor* descP) -{ - ERL_NIF_TERM protocol, result; + *flags = 0; // Just in case - if (! IS_OPEN(descP->readState)) { - SSDBG( descP, - ("SOCKET", "esock_getopt_otp_protocol {%d} -> done closed\r\n", - descP->sock) ); - return esock_make_error_closed(env); - } + return FALSE; + } else { + tmp |= SPP_HB_DISABLE; + hb = TRUE; + } + } else if (IS_IDENTICAL(elem, atom_send_heartbeat_immediately)) { - protocol = MKI(env, descP->protocol); - result = esock_make_ok2(env, protocol); + /* SPP_HB_DEMAND */ - SSDBG( descP, - ("SOCKET", "esock_getopt_otp_protocol {%d} ->" - "\r\n result: %T" - "\r\n", descP->sock, result) ); + tmp |= SPP_HB_DEMAND; - return result; -} + } else if (IS_IDENTICAL(elem, atom_enable_pmtu_discovery)) { + /* SPP_PMTUD_ENABLE */ + if (pmtu) { + + SSDBG( descP, + ("SOCKET", + "decode_pap_flags -> " + "invalid enable_pmtu_discovery flag - " + "disable_pmtu_discovery already found\r\n") ); -/* - * esock_getopt_otp_dtp - Handle the OTP (level) type options. - */ + *flags = 0; // Just in case -static -ERL_NIF_TERM esock_getopt_otp_dtp(ErlNifEnv* env, - ESockDescriptor* descP) -{ - ERL_NIF_TERM domain, type, protocol, dtp, result; + return FALSE; + } else { + tmp |= SPP_PMTUD_ENABLE; + pmtu = TRUE; + } - if (! IS_OPEN(descP->readState)) { - SSDBG( descP, - ("SOCKET", "esock_getopt_otp_dtp {%d} -> done closed\r\n", - descP->sock) ); - return esock_make_error_closed(env); - } + } else if (IS_IDENTICAL(elem, atom_disable_pmtu_discovery)) { - esock_encode_domain(env, descP->domain, &domain); - esock_encode_type(env, descP->type, &type); - protocol = MKI(env, descP->protocol); - dtp = MKT3(env, domain, type, protocol); - result = esock_make_ok2(env, dtp); + /* SPP_PMTUD_DISABLE */ - SSDBG( descP, - ("SOCKET", "esock_getopt_otp_dtp {%d} ->" - "\r\n result: %T" - "\r\n", descP->sock, result) ); + if (pmtu) { + + SSDBG( descP, + ("SOCKET", + "decode_pap_flags -> " + "invalid disable_pmtu_discovery flag - " + "enable_pmtu_discovery already found\r\n") ); - return result; -} + *flags = 0; // Just in case + return FALSE; -#endif // #if 0 + } else { + tmp |= SPP_PMTUD_DISABLE; + pmtu = TRUE; + } + } else if (IS_IDENTICAL(elem, atom_enable_sack)) { -/* How to decode the value is specified with valueSpec - */ +#if defined(SPP_SACKDELAY_ENABLE) -static -ERL_NIF_TERM esock_getopt_native(ErlNifEnv* env, - ESockDescriptor* descP, - int level, - int opt, - ERL_NIF_TERM valueSpec) -{ - ERL_NIF_TERM result; - SOCKOPTLEN_T valueSz; - int sz; - ErlNifBinary bin; + /* SPP_SACKDELAY_ENABLE */ - MLOCK(descP->readMtx); + if (sack) { + + SSDBG( descP, + ("SOCKET", + "decode_pap_flags -> " + "invalid enable_sack flag - " + "disable_sack already found\r\n") ); - SSDBG( descP, - ("SOCKET", "esock_getopt_native {%d} -> entry" - "\r\n level: %d" - "\r\n opt: %d" - "\r\n valueSpec: %T" - "\r\n", descP->sock, - level, opt, valueSpec) ); + *flags = 0; // Just in case - if (! IS_OPEN(descP->readState)) { - SSDBG( descP, - ("SOCKET", "esock_getopt_native {%d} -> done closed\r\n", - descP->sock) ); - MUNLOCK(descP->readMtx); - return esock_make_error_closed(env); - } + return FALSE; + } else { + tmp |= SPP_SACKDELAY_ENABLE; + sack = TRUE; + } - /* - * We could make it possible to specify more types, - * such as string, NUL terminated or not, etc... - * - */ +#else - if (GET_INT(env, valueSpec, &sz)) { - valueSz = (SOCKOPTLEN_T) sz; - if ((int) valueSz == sz) { - SSDBG( descP, - ("SOCKET", "esock_getopt_native {%d} -> binary size" - "\r\n valueSz: %d" - "\r\n", descP->sock, sz) ); - result = - esock_getopt_size_opt(env, descP, level, opt, valueSz); - } else { - result = esock_make_invalid(env, esock_atom_value); - } - } else if (COMPARE(valueSpec, atom_integer) == 0) { - SSDBG( descP, - ("SOCKET", "esock_getopt_native {%d} -> integer" - "\r\n", descP->sock) ); - result = esock_getopt_int_opt(env, descP, level, opt); - } else if (COMPARE(valueSpec, atom_boolean) == 0) { - SSDBG( descP, - ("SOCKET", "esock_getopt_native {%d} -> boolean" - "\r\n", descP->sock) ); - result = esock_getopt_bool_opt(env, descP, level, opt); - } else if (enif_inspect_binary(env, valueSpec, &bin)) { - SSDBG( descP, - ("SOCKET", "esock_getopt_native {%d} -> binary" - "\r\n size: %lu" - "\r\n", descP->sock, (unsigned long) bin.size) ); - result = esock_getopt_bin_opt(env, descP, level, opt, &bin); - } else { - result = esock_make_invalid(env, esock_atom_value); - } + SSDBG( descP, + ("SOCKET", + "decode_pap_flags -> " + "invalid enable_sack flag - UNDEFINED\r\n") ); - SSDBG( descP, - ("SOCKET", "esock_getopt_native {%d} -> done when" - "\r\n result: %T" - "\r\n", descP->sock, result) ); +#endif - MUNLOCK(descP->readMtx); - return result; -} + } else if (IS_IDENTICAL(elem, atom_disable_sack)) { +#if defined(SPP_SACKDELAY_DISABLE) + /* SPP_SACKDELAY_DISABLE */ -/* esock_getopt - An option that we know how to decode - */ -static -ERL_NIF_TERM esock_getopt(ErlNifEnv* env, - ESockDescriptor* descP, - int level, - int opt) -{ - ERL_NIF_TERM result; - const struct ESockOpt *optP; + if (sack) { + + SSDBG( descP, + ("SOCKET", + "decode_pap_flags -> " + "invalid disable_sack flag - " + "enable_sack already found\r\n") ); - MLOCK(descP->readMtx); + *flags = 0; // Just in case - SSDBG( descP, - ("SOCKET", "esock_getopt {%d} -> entry with" - "\r\n level: %d" - "\r\n opt: %d" - "\r\n", descP->sock, level, opt) ); + return FALSE; + } else { + tmp |= SPP_SACKDELAY_DISABLE; + sack = TRUE; + } - if (! IS_OPEN(descP->readState)) { - SSDBG( descP, - ("SOCKET", "esock_getopt {%d} -> done when closed\r\n", - descP->sock) ); - MUNLOCK(descP->readMtx); - return esock_make_error_closed(env); - } +#else + SSDBG( descP, + ("SOCKET", + "decode_pap_flags -> " + "invalid disable_sack flag - UNDEFINED\r\n") ); +#endif - optP = lookupOpt(level, opt); + } else if (IS_IDENTICAL(elem, atom_set_heartbeat_delay_to_zero)) { - if (optP == NULL) { + /* SPP_HB_TIME_IS_ZERO */ - result = esock_make_invalid(env, atom_socket_option); + tmp |= SPP_HB_TIME_IS_ZERO; - SSDBG( descP, - ("SOCKET", "esock_getopt {%d} -> unknown option\r\n", - descP->sock) ); + } else if (IS_IDENTICAL(elem, atom_ipv6_flowlabel)) { +#if defined(SPP_IPV6_FLOWLABEL) - } else if (optP->getopt == NULL) { + /* SPP_IPV6_FLOWLABEL */ - result = esock_make_invalid(env, atom_socket_option); + tmp |= SPP_IPV6_FLOWLABEL; - SSDBG( descP, - ("SOCKET", "esock_getopt {%d} -> opt not gettable\r\n", - descP->sock) ); +#else + SSDBG( descP, + ("SOCKET", + "decode_pap_flags -> " + "invalid ipv6_flowlabel flag - UNDEFINED\r\n") ); +#endif + } else if (IS_IDENTICAL(elem, atom_dscp)) { - } else { +#if defined(SPP_DSCP) + /* SPP_DSCP */ - result = (optP->getopt)(env, descP, level, opt); + tmp |= SPP_DSCP; - SSDBG( descP, - ("SOCKET", "esock_getopt {%d} -> done when" - "\r\n result: %T" - "\r\n", descP->sock, result) ); - } +#else + SSDBG( descP, + ("SOCKET", + "decode_pap_flags -> " + "invalid dscp flag - UNDEFINED\r\n") ); +#endif + } else { + + SSDBG( descP, + ("SOCKET", + "decode_pap_flags -> " + "unknown flag: %T" + "\r\n", elem) ); - MUNLOCK(descP->readMtx); - return result; -} + *flags = 0; // Just in case + return FALSE; + } -#if defined(SO_BINDTODEVICE) -static -ERL_NIF_TERM esock_getopt_so_bindtodevice(ErlNifEnv* env, - ESockDescriptor* descP, - int level, - int opt) -{ - return esock_getopt_str_opt(env, descP, level, opt, IFNAMSIZ+1, FALSE); + ef = tail; + + } + + } + } + + *flags = tmp; + + return TRUE; } #endif + - -#if defined(SO_BSP_STATE) -/* We need to allocate *all* of the memory used by the CSADDR_INFO - * structure. *Including* the 'sockaddr' structures pointed to by - * LocalAddr and RemoteAddr (lpSockaddr in SOCKET_ADDRESS). - * The '2*' is just to "dead sure" that we have enough... +/* *** esock_getopt_sctp_peer_addr_params *** + * Level SCTP SCTP_PEER_ADDR_PARAMS option */ + static -ERL_NIF_TERM esock_getopt_bsp_state(ErlNifEnv* env, - ESockDescriptor* descP, - int level, - int opt) +ERL_NIF_TERM esock_getopt_sctp_peer_addr_params(ErlNifEnv* env, + ESockDescriptor* descP, + int level, + int opt, + ERL_NIF_TERM eval) { - ERL_NIF_TERM result; - SOCKOPTLEN_T valSz = 2*(sizeof(CSADDR_INFO) + 2*sizeof(SOCKADDR)); - CSADDR_INFO* valP = MALLOC(valSz); - int res; - - SSDBG( descP, - ("SOCKET", "esock_getopt_bsp_state(%d) -> entry\r\n", descP->sock) ); + ERL_NIF_TERM result; + struct sctp_paddrparams val; + SOCKOPTLEN_T valSz = sizeof(val); + int res; - sys_memzero((void *) valP, valSz); + VOID(eval); -#ifdef __WIN32__ - res = sock_getopt(descP->sock, level, opt, (char*) valP, &valSz); -#else - res = sock_getopt(descP->sock, level, opt, valP, &valSz); -#endif + SSDBG( descP, + ("SOCKET", "esock_getopt_sctp_peer_addr_params -> entry\r\n") ); + + sys_memzero((char*) &val, valSz); + res = sock_getopt(descP->sock, level, opt, &val, &valSz); if (res != 0) { - int save_errno = sock_errno(); - ERL_NIF_TERM reason = ENO2T(env, save_errno); + int save_errno = sock_errno(); SSDBG( descP, - ("SOCKET", "esock_getopt_bsp_state(%d) -> error: " - "\r\n %T" - "\r\n", descP->sock, reason) ); + ("SOCKET", + "esock_getopt_sctp_peer_addr_params -> " + "failed get option: " + "\r\n errno: %T (%d)" + "\r\n", erl_errno_id(save_errno), save_errno) ); - result = esock_make_error(env, reason); + result = esock_make_error_errno(env, save_errno); - } else if (valSz > 0) { - ERL_NIF_TERM - la = esock_encode_bsp_state_socket_address(env, &valP->LocalAddr), - ra = esock_encode_bsp_state_socket_address(env, &valP->RemoteAddr), - type = esock_encode_bsp_state_type(env, valP->iSocketType), - proto = esock_encode_bsp_state_protocol(env, valP->iProtocol), - keys[] = {atom_local_addr, atom_remote_addr, esock_atom_type, esock_atom_protocol}, - vals[] = {la, ra, type, proto}, - bspState; - size_t numKeys = NUM(keys); + } else { + ERL_NIF_TERM eAssocID, eAddr, ehbi, ePathMaxRxt; +#if defined(HAVE_STRUCT_SCTP_PADDRPARAMS_SPP_PATHMTU) + ERL_NIF_TERM ePathMtu; +#endif +#if defined(HAVE_STRUCT_SCTP_PADDRPARAMS_SPP_SACKDELAY) + ERL_NIF_TERM eSackDelay; +#endif +#if defined(HAVE_STRUCT_SCTP_PADDRPARAMS_SPP_FLAGS) + ERL_NIF_TERM eFlags; +#endif +#if defined(HAVE_STRUCT_SCTP_PADDRPARAMS_SPP_IPV6_FLOWLABEL) + ERL_NIF_TERM eFlowLabel; +#endif +#if defined(HAVE_STRUCT_SCTP_PADDRPARAMS_SPP_DSCP) + ERL_NIF_TERM eDSCP; +#endif SSDBG( descP, - ("SOCKET", "esock_getopt_bsp_state(%d) -> values encoded:" - "\r\n la: %T" - "\r\n ra: %T" - "\r\n type: %T" - "\r\n proto: %T" - "\r\n", descP->sock, - la, ra, type, proto) ); + ("SOCKET", "esock_getopt_sctp_peer_addr_params -> " + "encode fields\r\n") ); - ESOCK_ASSERT( numKeys == NUM(vals) ); - ESOCK_ASSERT( MKMA(env, keys, vals, numKeys, &bspState) ); + eAssocID = encode_sctp_assoc_t(env, val.spp_assoc_id); + PUSH_SUPPRESS_ADDRESS_OF_PACKED_MEMBER(); + eAddr = encode_sockaddr(env, &val.spp_address); + POP_SUPPRESS_ADDRESS_OF_PACKED_MEMBER(); + ehbi = MKUI(env, val.spp_hbinterval); + ePathMaxRxt = MKUI(env, val.spp_pathmaxrxt); +#if defined(HAVE_STRUCT_SCTP_PADDRPARAMS_SPP_PATHMTU) + ePathMtu = MKUI(env, val.spp_pathmtu); +#endif +#if defined(HAVE_STRUCT_SCTP_PADDRPARAMS_SPP_SACKDELAY) + eSackDelay = MKUI(env, val.spp_sackdelay); +#endif +#if defined(HAVE_STRUCT_SCTP_PADDRPARAMS_SPP_FLAGS) + eFlags = encode_pap_flags(env, descP, val.spp_flags); +#endif +#if defined(HAVE_STRUCT_SCTP_PADDRPARAMS_SPP_IPV6_FLOWLABEL) + eFlowLabel = MKUI(env, val.spp_ipv6_flowlabel); +#endif +#if defined(HAVE_STRUCT_SCTP_PADDRPARAMS_SPP_DSCP) + eDSCP = MKUI(env, val.spp_dscp); +#endif - SSDBG( descP, - ("SOCKET", "esock_getopt_bsp_state(%d) -> " - "\r\n BSP State: %T" - "\r\n", descP->sock, bspState) ); - - result = esock_make_ok2(env, bspState); - } else { - result = esock_make_ok2(env, esock_atom_undefined); - } + { + ERL_NIF_TERM ePAP; + ERL_NIF_TERM keys[] = {esock_atom_esock_name, + esock_atom_assoc_id, + esock_atom_addr, + atom_heartbeat_interval, + atom_path_max_rxt +#if defined(HAVE_STRUCT_SCTP_PADDRPARAMS_SPP_PATHMTU) + ,atom_path_mtu +#endif +#if defined(HAVE_STRUCT_SCTP_PADDRPARAMS_SPP_SACKDELAY) + ,atom_sack_delay +#endif +#if defined(HAVE_STRUCT_SCTP_PADDRPARAMS_SPP_FLAGS) + ,esock_atom_flags +#endif +#if defined(HAVE_STRUCT_SCTP_PADDRPARAMS_SPP_IPV6_FLOWLABEL) + ,atom_ipv6_flowlabel +#endif +#if defined(HAVE_STRUCT_SCTP_PADDRPARAMS_SPP_DSCP) + ,atom_dscp +#endif + }; + ERL_NIF_TERM vals[] = {MKA(env, "sctp_peer_address_parameters"), + eAssocID, + eAddr, + ehbi, + ePathMaxRxt +#if defined(HAVE_STRUCT_SCTP_PADDRPARAMS_SPP_PATHMTU) + ,ePathMtu +#endif +#if defined(HAVE_STRUCT_SCTP_PADDRPARAMS_SPP_SACKDELAY) + ,eSackDelay +#endif +#if defined(HAVE_STRUCT_SCTP_PADDRPARAMS_SPP_FLAGS) + ,eFlags +#endif +#if defined(HAVE_STRUCT_SCTP_PADDRPARAMS_SPP_IPV6_FLOWLABEL) + ,eFlowLabel, +#endif +#if defined(HAVE_STRUCT_SCTP_PADDRPARAMS_SPP_DSCP) + ,eDSCP +#endif + }; + size_t numKeys = NUM(keys); - FREE( valP ); + ESOCK_ASSERT( numKeys == NUM(vals) ); + ESOCK_ASSERT( MKMA(env, keys, vals, numKeys, &ePAP) ); - SSDBG( descP, - ("SOCKET", "esock_getopt_bsp_state(%d) -> done when" - "\r\n result: %T" - "\r\n", descP->sock, result) ); + SSDBG( descP, + ("SOCKET", + "esock_getopt_sctp_peer_addr_params -> " + "\r\n %T" + "\r\n", ePAP) ); + + result = esock_make_ok2(env, ePAP); + } + } return result; + } +/* encode_pap_flags + * + * Encode Peer Address Parameter flags + * + */ +#if defined(HAVE_STRUCT_SCTP_PADDRPARAMS_SPP_FLAGS) static -ERL_NIF_TERM esock_encode_bsp_state_socket_address(ErlNifEnv* env, - SOCKET_ADDRESS* addr) +ERL_NIF_TERM encode_pap_flags(ErlNifEnv* env, + ESockDescriptor* descP, + unsigned int flags) { - ERL_NIF_TERM eaddr; + ERL_NIF_TERM eflags; - if (addr == NULL) - return esock_atom_undefined; + if (flags == 0) { + eflags = MKEL(env); + } else { + SocketTArray efTA = TARRAY_CREATE(10); - if ((addr->lpSockaddr == NULL) || - (addr->iSockaddrLength == 0)) - return esock_atom_undefined; + if ((flags & SPP_HB_ENABLE) != 0) + TARRAY_ADD(efTA, atom_enable_heartbeats); - esock_encode_sockaddr(env, - (ESockAddress*) addr->lpSockaddr, - addr->iSockaddrLength, - &eaddr); + if ((flags & SPP_HB_DISABLE) != 0) + TARRAY_ADD(efTA, atom_disable_heartbeats); - return eaddr; -} + if ((flags & SPP_HB_DEMAND) != 0) + TARRAY_ADD(efTA, atom_send_heartbeat_immediately); + if ((flags & SPP_PMTUD_ENABLE) != 0) + TARRAY_ADD(efTA, atom_enable_pmtu_discovery); -static -ERL_NIF_TERM esock_encode_bsp_state_type(ErlNifEnv* env, int type) -{ - ERL_NIF_TERM etype; + if ((flags & SPP_PMTUD_DISABLE) != 0) + TARRAY_ADD(efTA, atom_disable_pmtu_discovery); - switch (type) { - case SOCK_STREAM: - etype = esock_atom_stream; - break; +#if defined(SPP_SACKDELAY_ENABLE) + if ((flags & SPP_SACKDELAY_ENABLE) != 0) + TARRAY_ADD(efTA, atom_enable_sack); +#endif - case SOCK_DGRAM: - etype = esock_atom_dgram; - break; +#if defined(SPP_SACKDELAY_DISABLE) + if ((flags & SPP_SACKDELAY_DISABLE) != 0) + TARRAY_ADD(efTA, atom_disable_sack); +#endif - case SOCK_RDM: - etype = esock_atom_rdm; - break; + if ((flags & SPP_HB_TIME_IS_ZERO) != 0) + TARRAY_ADD(efTA, atom_set_heartbeat_delay_to_zero); - case SOCK_SEQPACKET: - etype = esock_atom_seqpacket; - break; +#if defined(SPP_IPV6_FLOWLABEL) + if ((flags & SPP_IPV6_FLOWLABEL) != 0) + TARRAY_ADD(efTA, atom_ipv6_flowlabel); +#endif - default: - etype = MKI(env, type); - break; +#if defined(SPP_DSCP) + if ((flags & SPP_DSCP) != 0) + TARRAY_ADD(efTA, atom_dscp); +#endif + + TARRAY_TOLIST(efTA, env, &eflags); } - return etype; + return eflags; } +#endif + +#endif + + +#if defined(SCTP_GET_PEER_ADDR_INFO) + +/* *** esock_getopt_sctp_get_peer_addr_info *** + * Level SCTP SCTP_GET_PEER_ADDR_INFO option + * + * To get the value of this option we actually need to initiate + * the structure with the peer address first! + */ static -ERL_NIF_TERM esock_encode_bsp_state_protocol(ErlNifEnv* env, int proto) +ERL_NIF_TERM esock_getopt_sctp_get_peer_addr_info(ErlNifEnv* env, + ESockDescriptor* descP, + int level, + int opt, + ERL_NIF_TERM eval) { - ERL_NIF_TERM eproto; - - switch (proto) { - case IPPROTO_TCP: - eproto = esock_atom_tcp; - break; + struct sctp_paddrinfo val; + SOCKOPTLEN_T valSz = sizeof(val); + ERL_NIF_TERM eaddr, eaid; + SOCKLEN_T addrLen = 0; + int res = 0; + ERL_NIF_TERM result; - case IPPROTO_UDP: - eproto = esock_atom_udp; - break; + SSDBG( descP, + ("SOCKET", + "%s -> entry with" + "\r\n eval: %T" + "\r\n", __FUNCTION__, eval) ); - /* - * In Wista and later the IPPROTO_PGM constant is defined in the - * Ws2def.h header file to the same value as the IPPROTO_RM constant - * defined in the Wsrm.h header file. - * => So we use IPPROTO_PGM also but translate to rm... - * - */ -#if defined(IPPROTO_RM) || defined(IPPROTO_PGM) -#if defined(IPPROTO_RM) - case IPPROTO_RM: -#else if defined(IPPROTO_PGM) - case IPPROTO_PGM: -#endif - eproto = esock_atom_rm; - break; -#endif + sys_memzero((char*) &val, valSz); - default: - eproto = MKI(env, proto); - break; + if (! IS_MAP(env, eval)) { + SSDBG( descP, ("SOCKET", "%s -> eval not a map\r\n", __FUNCTION__) ); + return enif_make_badarg(env); } - return eproto; -} + if (! GET_MAP_VAL(env, eval, esock_atom_addr, &eaddr)) { + SSDBG( descP, + ("SOCKET", "%s -> addr not found in eval\r\n", __FUNCTION__) ); + return enif_make_badarg(env); + } + PUSH_SUPPRESS_ADDRESS_OF_PACKED_MEMBER(); + if (! esock_decode_sockaddr(env, eaddr, + (ESockAddress*) (&val.spinfo_address), + &addrLen)) { + SSDBG( descP, + ("SOCKET", + "%s -> failed decode addr: %T\r\n", __FUNCTION__, eaddr) ); + return enif_make_badarg(env); + } + POP_SUPPRESS_ADDRESS_OF_PACKED_MEMBER(); + + if (! GET_MAP_VAL(env, eval, esock_atom_assoc_id, &eaid)) { + SSDBG( descP, + ("SOCKET", + "%s -> assoc_id not found in eval\r\n", __FUNCTION__) ); + return enif_make_badarg(env); + } + if (! decode_sctp_assoc_t(env, eaid, &val.spinfo_assoc_id)) { + SSDBG( descP, + ("SOCKET", + "%s -> failed decode assoc_id: %T\r\n", __FUNCTION__, eaid) ); + return enif_make_badarg(env); + } -#endif + SSDBG( descP, ("SOCKET", "%s -> try get option\r\n", __FUNCTION__) ); + res = sock_getopt(descP->sock, level, opt, &val, &valSz); + if (res != 0) { + int save_errno = sock_errno(); -#if defined(SO_DOMAIN) -static -ERL_NIF_TERM esock_getopt_sock_domain(ErlNifEnv* env, - ESockDescriptor* descP, - int level, - int opt) -{ - int val; - ERL_NIF_TERM result; + SSDBG( descP, + ("SOCKET", + "%s -> " + "failed get option: " + "\r\n errno: %T (%d)" + "\r\n", __FUNCTION__, erl_errno_id(save_errno), save_errno) ); - if (! esock_getopt_int(descP->sock, level, opt, &val)) { - result = esock_make_error_errno(env, sock_errno()); + result = esock_make_error_errno(env, save_errno); + } else { - ERL_NIF_TERM domain; - esock_encode_domain(env, val, &domain); - result = esock_make_ok2(env, domain); + ERL_NIF_TERM pdi; + + SSDBG( descP, + ("SOCKET", "%s -> got option - now encode\r\n", __FUNCTION__) ); + + pdi = encode_sctp_paddrinfo(env, descP, &val); + result = esock_make_ok2(env, pdi); } + + + SSDBG( descP, + ("SOCKET", + "%s -> done when" + "\r\n result: %T\r\n", __FUNCTION__, result) ); return result; } + #endif -#if defined(SO_LINGER) +/* *** esock_getopt_sctp_status *** + * + * Retreive current status info about an assoc. + * + * Level SCTP SCTP_STATUS option + */ + +#if defined(SCTP_STATUS) static -ERL_NIF_TERM esock_getopt_linger(ErlNifEnv* env, - ESockDescriptor* descP, - int level, - int opt) +ERL_NIF_TERM esock_getopt_sctp_status(ErlNifEnv* env, + ESockDescriptor* descP, + int level, + int opt, + ERL_NIF_TERM eval) { - ERL_NIF_TERM result; - struct linger val; - SOCKOPTLEN_T valSz = sizeof(val); - int res; + ERL_NIF_TERM result; + struct sctp_status val; + SOCKOPTLEN_T valSz = sizeof(val); + int res; + + /* On the client side we do not need to provide an assoc-id + * (presumably because it should only have one). But on the + * server side, we need to initiate the status structire with + * the assoc-id. + */ - sys_memzero((void *) &val, sizeof(val)); + SSDBG( descP, ("SOCKET", "esock_getopt_sctp_status -> entry with" + "\r\n level: %d" + "\r\n opt: %d" + "\r\n", level, opt) ); + + sys_memzero((char*) &val, valSz); + + if (! IS_IDENTICAL(eval, esock_atom_undefined)) { + ERL_NIF_TERM eaid; + + if (! IS_MAP(env, eval)) { + SSDBG( descP, + ("SOCKET", "%s -> eval not a map\r\n", + __FUNCTION__) ); + return enif_make_badarg(env); + } + + if (! GET_MAP_VAL(env, eval, esock_atom_assoc_id, &eaid)) { + SSDBG( descP, + ("SOCKET", + "%s -> assoc_id not found in eval\r\n", + __FUNCTION__) ); + return enif_make_badarg(env); + } + if (! decode_sctp_assoc_t(env, eaid, &val.sstat_assoc_id)) { + SSDBG( descP, + ("SOCKET", + "%s -> failed decode assoc_id: %T\r\n", + __FUNCTION__, eaid) ); + return enif_make_badarg(env); + } + + } + + SSDBG( descP, ("SOCKET", "%s -> try get option\r\n", __FUNCTION__) ); -#ifdef __WIN32__ - res = sock_getopt(descP->sock, level, opt, (char*) &val, &valSz); -#else res = sock_getopt(descP->sock, level, opt, &val, &valSz); -#endif if (res != 0) { - result = esock_make_error_errno(env, sock_errno()); - } else { - ERL_NIF_TERM - lOnOff = ((val.l_onoff != 0) ? atom_true : atom_false), - lSecs = MKI(env, val.l_linger), - keys[] = {atom_onoff, esock_atom_linger}, - vals[] = {lOnOff, lSecs}, - linger; - size_t numKeys = NUM(keys); + int save_errno = sock_errno(); SSDBG( descP, - ("SOCKET", "esock_getopt_linger(%d) -> " - "\r\n val.l_onoff: %d" - "\r\n lOnOff: %T" - "\r\n val.l_linger: %d" - "\r\n lSecs: %T" - "\r\n", descP->sock, - val.l_onoff, lOnOff, - val.l_linger, lSecs) ); - + ("SOCKET", + "esock_getopt_sctp_status -> " + "failed get option: " + "\r\n errno: %s (%d)" + "\r\n", erl_errno_id(save_errno), save_errno) ); + + result = esock_make_error_errno(env, save_errno); + + } else { + ERL_NIF_TERM eStatus; + ERL_NIF_TERM keys[] = {esock_atom_esock_name, + esock_atom_assoc_id, + esock_atom_state, + atom_rwnd, + atom_unacked_data, + atom_pending_data, + atom_in_streams, + atom_out_streams, + atom_fragmentation_point, + atom_primary}; + ERL_NIF_TERM vals[] = {MKA(env, "sctp_status"), + encode_sctp_assoc_t(env, val.sstat_assoc_id), + encode_sctp_sstat_state(env, descP, val.sstat_state), + MKUI(env, val.sstat_rwnd), + MKUI(env, val.sstat_unackdata), + MKUI(env, val.sstat_penddata), + MKUI(env, val.sstat_instrms), + MKUI(env, val.sstat_outstrms), + MKUI(env, val.sstat_fragmentation_point), + encode_sctp_paddrinfo(env, descP, &val.sstat_primary)}; + size_t numKeys = NUM(keys); + ESOCK_ASSERT( numKeys == NUM(vals) ); - ESOCK_ASSERT( MKMA(env, keys, vals, numKeys, &linger) ); + ESOCK_ASSERT( MKMA(env, keys, vals, numKeys, &eStatus) ); SSDBG( descP, - ("SOCKET", "esock_getopt_linger(%d) -> " - "\r\n linger: %T" - "\r\n", descP->sock, linger) ); - - result = esock_make_ok2(env, linger); + ("SOCKET", + "esock_getopt_sctp_status -> " + "got option: " + "\r\n %T" + "\r\n", eStatus) ); + + result = esock_make_ok2(env, eStatus); } return result; + } -#endif - - -#if defined(SO_TYPE) static -ERL_NIF_TERM esock_getopt_sock_type(ErlNifEnv* env, - ESockDescriptor* descP, - int level, - int opt) +ERL_NIF_TERM encode_sctp_sstat_state(ErlNifEnv* env, + ESockDescriptor* descP, + int state) { - ERL_NIF_TERM result; - int val; + ERL_NIF_TERM estate; - if (! esock_getopt_int(descP->sock, level, opt, &val)) { - result = esock_make_error_errno(env, sock_errno()); - } else { - ERL_NIF_TERM type; - esock_encode_type(env, val, &type); - result = esock_make_ok2(env, type); - } + switch (state) { +#if defined(HAVE_DECL_SCTP_EMPTY) +#if HAVE_DECL_SCTP_EMPTY != 0 + case SCTP_EMPTY: + estate = esock_atom_empty; + break; +#endif +#endif - return result; -} +#if defined(HAVE_DECL_SCTP_BOUND) +#if HAVE_DECL_SCTP_BOUND != 0 + case SCTP_BOUND: + estate = esock_atom_bound; + break; +#endif #endif +#if defined(HAVE_DECL_SCTP_LISTEN) +#if HAVE_DECL_SCTP_LISTEN != 0 + case SCTP_LISTEN: + estate = esock_atom_listen; + break; +#endif +#endif -#if defined(SO_PROTOCOL) -static -ERL_NIF_TERM esock_getopt_sock_protocol(ErlNifEnv* env, - ESockDescriptor* descP, - int level, - int opt) -{ - ERL_NIF_TERM result; - int val; - if (! esock_getopt_int(descP->sock, level, opt, &val)) { - result = esock_make_error_errno(env, sock_errno()); - } else { - ERL_NIF_TERM protocol; - protocol = -#ifdef AF_LOCAL - /* For AF_LOCAL, the protocols table value for 0 is wrong */ - (val == 0) && (descP->domain == AF_LOCAL) ? - esock_atom_default : - /* It is correct for AF_INET and hopefully for AF_INET6, - * but for future additions it is an open question - */ + +#if defined(HAVE_DECL_SCTP_CLOSED) +#if HAVE_DECL_SCTP_CLOSED != 0 + case SCTP_CLOSED: + estate = esock_atom_closed; + break; +#endif #endif - MKI(env, val); - result = esock_make_ok2(env, protocol); - } +#if defined(HAVE_DECL_SCTP_COOKIE_WAIT) +#if HAVE_DECL_SCTP_COOKIE_WAIT != 0 + case SCTP_COOKIE_WAIT: + estate = esock_atom_cookie_wait; + break; +#endif +#endif - return result; -} +#if defined(HAVE_DECL_SCTP_COOKIE_ECHOED) +#if HAVE_DECL_SCTP_COOKIE_ECHOED != 0 + case SCTP_COOKIE_ECHOED: + estate = esock_atom_cookie_echoed; + break; +#endif #endif +#if defined(HAVE_DECL_SCTP_ESTABLISHED) +#if HAVE_DECL_SCTP_ESTABLISHED != 0 + case SCTP_ESTABLISHED: + estate = esock_atom_established; + break; +#endif +#endif -/* esock_getopt_ip_mtu_discover - Level IP MTU_DISCOVER option - */ -#if defined(IP_MTU_DISCOVER) -static -ERL_NIF_TERM esock_getopt_ip_mtu_discover(ErlNifEnv* env, - ESockDescriptor* descP, - int level, - int opt) -{ - ERL_NIF_TERM result; - int mtuDisc; +#if defined(HAVE_DECL_SCTP_SHUTDOWN_PENDING) +#if HAVE_DECL_SCTP_SHUTDOWN_PENDING != 0 + case SCTP_SHUTDOWN_PENDING: + estate = esock_atom_shutdown_pending; + break; +#endif +#endif - if (! esock_getopt_int(descP->sock, level, opt, &mtuDisc)) { - result = esock_make_error_errno(env, sock_errno()); - } else { - ERL_NIF_TERM eMtuDisc; - encode_ip_pmtudisc(env, mtuDisc, &eMtuDisc); - result = esock_make_ok2(env, eMtuDisc); - } +#if defined(HAVE_DECL_SCTP_SHUTDOWN_SENT) +#if HAVE_DECL_SCTP_SHUTDOWN_SENT != 0 + case SCTP_SHUTDOWN_SENT: + estate = esock_atom_shutdown_sent; + break; +#endif +#endif - return result; +#if defined(HAVE_DECL_SCTP_SHUTDOWN_RECEIVED) +#if HAVE_DECL_SCTP_SHUTDOWN_RECEIVED != 0 + case SCTP_SHUTDOWN_RECEIVED: + estate = esock_atom_shutdown_received; + break; +#endif +#endif + +#if defined(HAVE_DECL_SCTP_SHUTDOWN_ACK_SENT) +#if HAVE_DECL_SCTP_SHUTDOWN_ACK_SENT != 0 + case SCTP_SHUTDOWN_ACK_SENT: + estate = esock_atom_shutdown_ack_sent; + break; +#endif +#endif + + default: + estate = MKI(env, state); + break; + } + return estate; } + #endif -/* esock_getopt_multicast_if - Level IP MULTICAST_IF option +/* *** esock_getopt_sctp_get_assoc_stats *** + * + * Retreive current statistics about an assoc. + * + * Level SCTP SCTP_GET_ASSOC_STATS option */ -#if defined(IP_MULTICAST_IF) +#if defined(SCTP_GET_ASSOC_STATS) static -ERL_NIF_TERM esock_getopt_multicast_if(ErlNifEnv* env, - ESockDescriptor* descP, - int level, - int opt) +ERL_NIF_TERM esock_getopt_sctp_get_assoc_stats(ErlNifEnv* env, + ESockDescriptor* descP, + int level, + int opt, + ERL_NIF_TERM eval) { - ERL_NIF_TERM result; - ERL_NIF_TERM eAddr; - struct in_addr ifAddr; - SOCKOPTLEN_T ifAddrSz = sizeof(ifAddr); - int res; + ERL_NIF_TERM result; + struct sctp_assoc_stats val; + SOCKOPTLEN_T valSz = sizeof(val); + int res; - sys_memzero((void *) &ifAddr, ifAddrSz); + VOID(eval); -#ifdef __WIN32__ - res = sock_getopt(descP->sock, level, opt, (char*) &ifAddr, &ifAddrSz); -#else - res = sock_getopt(descP->sock, level, opt, &ifAddr, &ifAddrSz); -#endif + SSDBG( descP, + ("SOCKET", "esock_getopt_sctp_get_assoc_stats -> entry\r\n") ); + + sys_memzero((char*) &val, valSz); + res = sock_getopt(descP->sock, level, opt, &val, &valSz); if (res != 0) { - result = esock_make_error_errno(env, sock_errno()); + int save_errno = sock_errno(); + + SSDBG( descP, + ("SOCKET", + "esock_getopt_sctp_get_assoc_stats -> " + "failed get option: " + "\r\n errno: %T (%d)" + "\r\n", erl_errno_id(save_errno), save_errno) ); + + result = esock_make_error_errno(env, save_errno); + } else { - esock_encode_in_addr(env, &ifAddr, &eAddr); - result = esock_make_ok2(env, eAddr); + ERL_NIF_TERM eStats; + ERL_NIF_TERM keys[] = {esock_atom_esock_name, +#if defined(HAVE_STRUCT_SCTP_ASSOC_STATS_SAS_ASSOC_ID) + esock_atom_assoc_id, +#endif +#if defined(HAVE_STRUCT_SCTP_ASSOC_STATS_SAS_OBS_RTO_IPADDR) + atom_max_rto_addr, +#endif + atom_max_rto, + atom_in_sacks, + atom_out_sacks, +#if defined(HAVE_STRUCT_SCTP_ASSOC_STATS_SAS_IPACKETS) + atom_in_packets, +#endif +#if defined(HAVE_STRUCT_SCTP_ASSOC_STATS_SAS_OPACKETS) + atom_out_packets, +#endif + atom_rtx_chunks, + atom_out_of_seq_tsns, + atom_in_dup_chunks, + atom_gap_ack_recv, + atom_in_unordered_chunks, + atom_out_unordered_chunks, + atom_in_ordered_chunks, + atom_out_ordered_chunks, + atom_in_ctrl_chunks, + atom_out_ctrl_chunks}; + ERL_NIF_TERM vals[] = {MKA(env, "sctp_assoc_stats"), +#if defined(HAVE_STRUCT_SCTP_ASSOC_STATS_SAS_ASSOC_ID) + encode_sctp_assoc_t(env, val.sas_assoc_id), +#endif +#if defined(HAVE_STRUCT_SCTP_ASSOC_STATS_SAS_OBS_RTO_IPADDR) + encode_sockaddr(env, &val.sas_obs_rto_ipaddr), +#endif + MKUI64(env, val.sas_maxrto), + MKUI64(env, val.sas_isacks), + MKUI64(env, val.sas_osacks), +#if defined(HAVE_STRUCT_SCTP_ASSOC_STATS_SAS_IPACKETS) + MKUI64(env, val.sas_ipackets), +#endif +#if defined(HAVE_STRUCT_SCTP_ASSOC_STATS_SAS_OPACKETS) + MKUI64(env, val.sas_opackets), +#endif + MKUI64(env, val.sas_rtxchunks), +#if defined(HAVE_STRUCT_SCTP_ASSOC_STATS_SAS_OUTOFSEQTSNS) + MKUI64(env, val.sas_outofseqtsns), +#else + MKUI64(env, val.sas_outseqtsns), +#endif + MKUI64(env, val.sas_idupchunks), + MKUI64(env, val.sas_gapcnt), + MKUI64(env, val.sas_iuodchunks), + MKUI64(env, val.sas_ouodchunks), + MKUI64(env, val.sas_iodchunks), + MKUI64(env, val.sas_oodchunks), + MKUI64(env, val.sas_ictrlchunks), + MKUI64(env, val.sas_octrlchunks)}; + size_t numKeys = NUM(keys); + + ESOCK_ASSERT( numKeys == NUM(vals) ); + ESOCK_ASSERT( MKMA(env, keys, vals, numKeys, &eStats) ); + + SSDBG( descP, + ("SOCKET", + "esock_getopt_sctp_assoc_stats -> " + "got option: " + "\r\n %T" + "\r\n", eStats) ); + + result = esock_make_ok2(env, eStats); } return result; - + } #endif +#endif // defined(HAVE_SCTP) + -/* esock_getopt_tos - Level IP TOS option + + +/* esock_setopt_bool_opt - set an option that has an (integer) bool value */ -#if defined(IP_TOS) static -ERL_NIF_TERM esock_getopt_tos(ErlNifEnv* env, - ESockDescriptor* descP, - int level, - int opt) +ERL_NIF_TERM esock_setopt_bool_opt(ErlNifEnv* env, + ESockDescriptor* descP, + int level, + int opt, + ERL_NIF_TERM eVal) { - ERL_NIF_TERM result; - int val = 0; + BOOLEAN_T val; + int ival; - if (! esock_getopt_int(descP->sock, level, opt, &val)) { - result = esock_make_error_errno(env, sock_errno()); - } else { - result = esock_make_ok2(env, encode_ip_tos(env, val)); - } + if (! esock_decode_bool(eVal, &val)) + return esock_make_invalid(env, esock_atom_value); - return result; + ival = (val) ? 1 : 0; + return esock_setopt_level_opt(env, descP, level, opt, + &ival, sizeof(ival)); } -#endif -#if defined(HAVE_IPV6) - -/* esock_getopt_ipv6_mtu_discover - Level IPv6 MTU_DISCOVER option +/* esock_setopt_int_opt - set an option that has an integer value */ -#if defined(IPV6_MTU_DISCOVER) static -ERL_NIF_TERM esock_getopt_ipv6_mtu_discover(ErlNifEnv* env, - ESockDescriptor* descP, - int level, - int opt) +ERL_NIF_TERM esock_setopt_int_opt(ErlNifEnv* env, + ESockDescriptor* descP, + int level, + int opt, + ERL_NIF_TERM eVal) { - ERL_NIF_TERM result; - int mtuDisc; + ERL_NIF_TERM result; + int val; - if (! esock_getopt_int(descP->sock, level, opt, &mtuDisc)) { - result = esock_make_error_errno(env, sock_errno()); + if (GET_INT(env, eVal, &val)) { + result = + esock_setopt_level_opt(env, descP, level, opt, + &val, sizeof(val)); } else { - ERL_NIF_TERM eMtuDisc; - encode_ipv6_pmtudisc(env, mtuDisc, &eMtuDisc); - result = esock_make_ok2(env, eMtuDisc); + result = esock_make_invalid(env, esock_atom_value); } - return result; - } -#endif -#endif // defined(HAVE_IPV6) -/* esock_getopt_tcp_congestion - Level TCP CONGESTION option +/* esock_setopt_str_opt - set an option that has an string value */ -#if defined(TCP_CONGESTION) +#if defined(USE_SETOPT_STR_OPT) static -ERL_NIF_TERM esock_getopt_tcp_congestion(ErlNifEnv* env, - ESockDescriptor* descP, - int level, - int opt) +ERL_NIF_TERM esock_setopt_str_opt(ErlNifEnv* env, + ESockDescriptor* descP, + int level, + int opt, + int max, + ERL_NIF_TERM eVal) { - int max = ESOCK_OPT_TCP_CONGESTION_NAME_MAX+1; + ERL_NIF_TERM result; + int optLen; + char* val = MALLOC(max); + ErlNifBinary bin; - return esock_getopt_str_opt(env, descP, level, opt, max, TRUE); -} -#endif + ESOCK_ASSERT( val != NULL ); + if ((optLen = GET_STR(env, eVal, val, max)) > 0) { + optLen--; -#if defined(HAVE_SCTP) + result = + esock_setopt_level_opt(env, descP, level, opt, + val, optLen); -/* esock_getopt_sctp_associnfo - Level SCTP ASSOCINFO option - * - * - * - * We should really specify which association this relates to, - * as it is now we get assoc-id = 0. If this socket is an - * association (and not an endpoint) then it will have an - * assoc id. But since the sctp support at present is "limited", - * we leave it for now. - * What do we do if this is an endpoint? Invalid op? Or just leave - * it for the OS? - * - * - */ -#ifndef __WIN32__ -#if defined(SCTP_ASSOCINFO) -static -ERL_NIF_TERM esock_getopt_sctp_associnfo(ErlNifEnv* env, - ESockDescriptor* descP, - int level, - int opt) -{ - ERL_NIF_TERM result; - struct sctp_assocparams val; - SOCKOPTLEN_T valSz = sizeof(val); - int res; + } else if (enif_inspect_binary(env, eVal, &bin)) { - sys_memzero((char*) &val, valSz); - res = sock_getopt(descP->sock, level, opt, &val, &valSz); + optLen = esock_strnlen((char*) bin.data, max - 1); + sys_memcpy(val, bin.data, optLen); + val[optLen] = '\0'; + + result = + esock_setopt_level_opt(env, descP, level, opt, + val, optLen); - if (res != 0) { - result = esock_make_error_errno(env, sock_errno()); } else { - ERL_NIF_TERM eAssocParams; - ERL_NIF_TERM keys[] = {atom_assoc_id, - atom_asocmaxrxt, - atom_number_peer_destinations, - atom_peer_rwnd, - atom_local_rwnd, - atom_cookie_life}; - ERL_NIF_TERM vals[] = {encode_sctp_assoc_t(env, val.sasoc_assoc_id), - MKUI(env, val.sasoc_asocmaxrxt), - MKUI(env, val.sasoc_number_peer_destinations), - MKUI(env, val.sasoc_peer_rwnd), - MKUI(env, val.sasoc_local_rwnd), - MKUI(env, val.sasoc_cookie_life)}; - size_t numKeys = NUM(keys); - ESOCK_ASSERT( numKeys == NUM(vals) ); - ESOCK_ASSERT( MKMA(env, keys, vals, numKeys, &eAssocParams) ); - result = esock_make_ok2(env, eAssocParams); + result = esock_make_invalid(env, esock_atom_value); + } + FREE(val); + return result; } #endif -#endif // #ifndef __WIN32__ - -/* esock_getopt_sctp_initmsg - Level SCTP INITMSG option - * +/* esock_setopt_timeval_opt - set an option that has an (timeval) bool value */ -#ifndef __WIN32__ -#if defined(SCTP_INITMSG) + +#if (defined(SO_RCVTIMEO) || defined(SO_SNDTIMEO)) \ + && defined(ESOCK_USE_RCVSNDTIMEO) static -ERL_NIF_TERM esock_getopt_sctp_initmsg(ErlNifEnv* env, - ESockDescriptor* descP, - int level, - int opt) +ERL_NIF_TERM esock_setopt_timeval_opt(ErlNifEnv* env, + ESockDescriptor* descP, + int level, + int opt, + ERL_NIF_TERM eVal) { - ERL_NIF_TERM result; - struct sctp_initmsg val; - SOCKOPTLEN_T valSz = sizeof(val); - int res; - - sys_memzero((char*) &val, valSz); - res = sock_getopt(descP->sock, level, opt, &val, &valSz); + ERL_NIF_TERM result; + struct timeval timeVal; - if (res != 0) { - result = esock_make_error_errno(env, sock_errno()); - } else { - ERL_NIF_TERM eInitMsg; - ERL_NIF_TERM keys[] = {atom_num_outstreams, atom_max_instreams, - atom_max_attempts, atom_max_init_timeo}; - ERL_NIF_TERM vals[] = {MKUI(env, val.sinit_num_ostreams), - MKUI(env, val.sinit_max_instreams), - MKUI(env, val.sinit_max_attempts), - MKUI(env, val.sinit_max_init_timeo)}; - unsigned int numKeys = NUM(keys); - unsigned int numVals = NUM(vals); + SSDBG( descP, + ("SOCKET", "esock_setopt_timeval_opt -> entry with" + "\r\n eVal: %T" + "\r\n", eVal) ); - ESOCK_ASSERT( numKeys == numVals ); - ESOCK_ASSERT( MKMA(env, keys, vals, numKeys, &eInitMsg) ); - result = esock_make_ok2(env, eInitMsg); - } + if (! esock_decode_timeval(env, eVal, &timeVal)) + return esock_make_invalid(env, esock_atom_value); + + SSDBG( descP, + ("SOCKET", "esock_setopt_timeval_opt -> set timeval option\r\n") ); + + result = + esock_setopt_level_opt(env, descP, level, opt, + &timeVal, sizeof(timeVal)); + + SSDBG( descP, + ("SOCKET", "esock_setopt_timeval_opt -> done with" + "\r\n result: %T" + "\r\n", result) ); return result; + } #endif -#endif // #ifndef __WIN32__ -/* esock_getopt_sctp_rtoinfo - Level SCTP ASSOCINFO option - * - * +static ERL_NIF_TERM esock_setopt_level_opt(ErlNifEnv* env, + ESockDescriptor* descP, + int level, + int opt, + void* optVal, + socklen_t optLen) +{ + if (socket_setopt(descP->sock, level, opt, optVal, optLen)) + return esock_make_error_errno(env, sock_errno()); + else + return esock_atom_ok; +} + + + +/* +++ socket_setopt +++ * - * We should really specify which association this relates to, - * as it is now we get assoc-id = 0. If this socket is an - * association (and not an endpoint) then it will have an - * assoc id (we can assume). But since the sctp support at - * present is "limited", we leave it for now. - * What do we do if this is an endpoint? Invalid op? + * + * 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. + * */ -#ifndef __WIN32__ -#if defined(SCTP_RTOINFO) static -ERL_NIF_TERM esock_getopt_sctp_rtoinfo(ErlNifEnv* env, - ESockDescriptor* descP, - int level, - int opt) +int socket_setopt(int sock, int level, int opt, + const void* optVal, const socklen_t optLen) { - ERL_NIF_TERM result; - struct sctp_rtoinfo val; - SOCKOPTLEN_T valSz = sizeof(val); - int res; + int res; - sys_memzero((char*) &val, valSz); - res = sock_getopt(descP->sock, level, opt, &val, &valSz); +#if defined(IP_TOS) && defined(SOL_IP) && defined(SO_PRIORITY) + int tmpIValPRIO = 0; + int tmpIValTOS = 0; + int resPRIO; + int resTOS; + SOCKOPTLEN_T tmpArgSzPRIO = sizeof(tmpIValPRIO); + SOCKOPTLEN_T tmpArgSzTOS = sizeof(tmpIValTOS); - if (res != 0) { - result = esock_make_error_errno(env, sock_errno()); - } else { - ERL_NIF_TERM eRTOInfo; - ERL_NIF_TERM keys[] = {atom_assoc_id, atom_initial, atom_max, atom_min}; - ERL_NIF_TERM vals[] = {encode_sctp_assoc_t(env, val.srto_assoc_id), - MKUI(env, val.srto_initial), - MKUI(env, val.srto_max), - MKUI(env, val.srto_min)}; - unsigned int numKeys = NUM(keys); - unsigned int numVals = NUM(vals); + resPRIO = sock_getopt(sock, SOL_SOCKET, SO_PRIORITY, + &tmpIValPRIO, &tmpArgSzPRIO); + resTOS = sock_getopt(sock, SOL_IP, IP_TOS, + &tmpIValTOS, &tmpArgSzTOS); - ESOCK_ASSERT( numKeys == numVals ); - ESOCK_ASSERT( MKMA(env, keys, vals, numKeys, &eRTOInfo) ); - result = esock_make_ok2(env, eRTOInfo); + 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; + } + } + } } - return result; -} -#endif -#endif // #ifndef __WIN32__ +#else + res = sock_setopt(sock, level, opt, optVal, optLen); +#endif -#endif // defined(HAVE_SCTP) + return res; +} -/* esock_getopt_bool_opt - get an (integer) bool option +/* ---------------------------------------------------------------------- + * nif_getopt + * + * Description: + * Get socket option. + * Its possible to use a ValueSpec to select + * how the value should be decoded. + * + * Arguments: + * Socket (ref) - Points to the socket descriptor. + * Level (int) - Protocol level, encoded or native + * Opt (int) - Option, encoded or native + * The next two are optional. But if present, both are. + * kind atom - 'native' | 'value' [optional] + * Value (term) - value_spec: How to decode the value + * opt_value: Some options have a value input + * [optional] */ + static -ERL_NIF_TERM esock_getopt_bool_opt(ErlNifEnv* env, - ESockDescriptor* descP, - int level, - int opt) +ERL_NIF_TERM nif_getopt(ErlNifEnv* env, + int argc, + const ERL_NIF_TERM argv[]) { - ERL_NIF_TERM result; - int val = 0; + ESockDescriptor* descP; + ERL_NIF_TERM esock, elevel, eopt, kind, eval; + int level, opt; - if (! esock_getopt_int(descP->sock, level, opt, &val)) { - result = esock_make_error_errno(env, sock_errno()); + ESOCK_ASSERT( (argc == 3) || (argc == 5) ); + + esock = argv[0]; + + if (! ESOCK_GET_RESOURCE(env, esock, (void**) &descP)) { + SGDBG( ("SOCKET", + "nif_getopt -> failed initial args check - sock\r\n") ); + return enif_make_badarg(env); + } + + elevel = argv[1]; + eopt = argv[2]; + if (argc == 5) { + kind = argv[3]; + eval = argv[4]; } else { - ERL_NIF_TERM bval = ((val) ? atom_true : atom_false); + kind = esock_atom_undefined; + eval = esock_atom_undefined; + } - result = esock_make_ok2(env, bval); + SSDBG( descP, + ("SOCKET", + "nif_getopt -> entry with argc: %d" + "\r\n esock: %T" + "\r\n elevel: %T" + "\r\n eopt: %T" + "\r\n kind: %T" + "\r\n eval: %T" + "\r\n", argc, esock, elevel, eopt, kind, eval) ); + + if (! GET_INT(env, eopt, &opt)) { + SSDBG( descP, + ("SOCKET", + "nif_getopt -> failed initial args check - opt\r\n") ); + if (! IS_INTEGER(env, eopt)) + return enif_make_badarg(env); + else + return esock_make_error_integer_range(env, eopt); } - return result; -} + if ((COMPARE(elevel, atom_otp) == 0) && + (argc == 3)) { + return ESOCK_IO_GETOPT_OTP(env, descP, opt) ; + } -/* esock_getopt_int_opt - get an integer option - */ -static -ERL_NIF_TERM esock_getopt_int_opt(ErlNifEnv* env, - ESockDescriptor* descP, - int level, - int opt) -{ - int val; + if (esock_decode_level(env, elevel, &level)) { + if (argc == 5) { + if (IS_IDENTICAL(kind, esock_atom_native)) + // In this case 'eval' is a ValueSpec + return ESOCK_IO_GETOPT_NATIVE(env, descP, level, opt, eval); + else + // In this case 'eval' is a Value + return ESOCK_IO_GETOPT(env, descP, level, opt, eval); + } else { + /* + * We could use 'eval' here to be absolutely sure that + * we get the undefined value (just in case we rewrite + * the code and then forget that the value *should* be + * 'undefined' here. + * Possible debugging? + */ + return ESOCK_IO_GETOPT(env, descP, + level, opt, esock_atom_undefined); + } + } - if (! esock_getopt_int(descP->sock, level, opt, &val)) - return esock_make_error_errno(env, sock_errno()); + SGDBG( ("SOCKET", "nif_getopt -> failed args check\r\n") ); + if (IS_INTEGER(env, elevel)) + return esock_make_error_integer_range(env, elevel); + else + return enif_make_badarg(env); - return esock_make_ok2(env, MKI(env, val)); } -/* esock_getopt_int - get an integer option +/* esock_getopt_otp - Handle OTP (level) options */ -extern -BOOLEAN_T esock_getopt_int(SOCKET sock, - int level, - int opt, - int* valP) + +static +ERL_NIF_TERM esock_getopt_otp(ErlNifEnv* env, + ESockDescriptor* descP, + int eOpt) { - int val = 0; - SOCKOPTLEN_T valSz = sizeof(val); + ERL_NIF_TERM result; -#ifdef __WIN32__ - if (sock_getopt(sock, level, opt, (char*) &val, &valSz) != 0) -#else - if (sock_getopt(sock, level, opt, &val, &valSz) != 0) -#endif - return FALSE; + SSDBG( descP, + ("SOCKET", "esock_getopt_otp -> entry with" + "\r\n eOpt: %d" + "\r\n", eOpt) ); - *valP = val; - return TRUE; -} + switch (eOpt) { + case ESOCK_OPT_OTP_DEBUG: + MLOCK(descP->readMtx); + result = esock_getopt_otp_debug(env, descP); + MUNLOCK(descP->readMtx); + break; + case ESOCK_OPT_OTP_IOW: + MLOCK(descP->readMtx); + result = esock_getopt_otp_iow(env, descP); + MUNLOCK(descP->readMtx); + break; + case ESOCK_OPT_OTP_CTRL_PROC: + MLOCK(descP->readMtx); + result = esock_getopt_otp_ctrl_proc(env, descP); + MUNLOCK(descP->readMtx); + break; -static -ERL_NIF_TERM esock_getopt_size_opt(ErlNifEnv* env, - ESockDescriptor* descP, - int level, - int opt, - SOCKOPTLEN_T valueSz) -{ - ERL_NIF_TERM result; - int res; + case ESOCK_OPT_OTP_SELECT_READ: + MLOCK(descP->readMtx); + result = esock_getopt_otp_select_read(env, descP); + MUNLOCK(descP->readMtx); + break; - if (valueSz == 0) { - res = sock_getopt(descP->sock, level, opt, NULL, NULL); - if (res != 0) - result = esock_make_error_errno(env, sock_errno()); - else - result = esock_atom_ok; - } else { - SOCKOPTLEN_T vsz = valueSz; - ErlNifBinary val; + case ESOCK_OPT_OTP_RCVBUF: + MLOCK(descP->readMtx); + result = esock_getopt_otp_rcvbuf(env, descP); + MUNLOCK(descP->readMtx); + break; - ESOCK_ASSERT( ALLOC_BIN(vsz, &val) ); - sys_memzero(val.data, val.size); - res = sock_getopt(descP->sock, level, opt, val.data, &vsz); - if (res != 0) { - result = esock_make_error_errno(env, sock_errno()); - FREE_BIN(&val); - } else { + case ESOCK_OPT_OTP_RCVCTRLBUF: + MLOCK(descP->readMtx); + result = esock_getopt_otp_rcvctrlbuf(env, descP); + MUNLOCK(descP->readMtx); + break; - /* Did we use all of the buffer? */ - if (vsz == val.size) { - result = esock_make_ok2(env, MKBIN(env, &val)); + case ESOCK_OPT_OTP_SNDCTRLBUF: + MLOCK(descP->writeMtx); + result = esock_getopt_otp_sndctrlbuf(env, descP); + MUNLOCK(descP->writeMtx); + break; - } else { + case ESOCK_OPT_OTP_FD: + MLOCK(descP->readMtx); + result = esock_getopt_otp_fd(env, descP); + MUNLOCK(descP->readMtx); + break; - ERL_NIF_TERM tmp; + case ESOCK_OPT_OTP_META: + MLOCK(descP->writeMtx); + result = esock_getopt_otp_meta(env, descP); + MUNLOCK(descP->writeMtx); + break; - tmp = MKBIN(env, &val); - tmp = MKSBIN(env, tmp, 0, vsz); + case ESOCK_OPT_OTP_USE_REGISTRY: + MLOCK(descP->readMtx); + result = esock_getopt_otp_use_registry(env, descP); + MUNLOCK(descP->readMtx); + break; - result = esock_make_ok2(env, tmp); - } - } + /* *** INTERNAL *** */ + case ESOCK_OPT_OTP_DOMAIN: + MLOCK(descP->readMtx); + result = esock_getopt_otp_domain(env, descP); + MUNLOCK(descP->readMtx); + break; + +#if 0 + case ESOCK_OPT_OTP_TYPE: + MLOCK(descP->readMtx); + result = esock_getopt_otp_type(env, descP); + MUNLOCK(descP->readMtx); + break; + + case ESOCK_OPT_OTP_PROTOCOL: + MLOCK(descP->readMtx); + result = esock_getopt_otp_protocol(env, descP); + MUNLOCK(descP->readMtx); + break; + + case ESOCK_OPT_OTP_DTP: + MLOCK(descP->readMtx); + result = esock_getopt_otp_dtp(env, descP); + MUNLOCK(descP->readMtx); + break; +#endif + + default: + MLOCK(descP->readMtx); + SSDBG( descP, + ("SOCKET", "esock_getopt_otp {%d} -> invalid with" + "\r\n eOpt: %d" + "\r\n", descP->sock, eOpt) ); + MUNLOCK(descP->readMtx); + + /* This is an internal error - prim_inet gave us junk */ + result = + esock_raise_invalid(env, + MKT2(env, + atom_otp_socket_option, + MKI(env, eOpt))); + break; } return result; } +/* esock_getopt_otp_debug - Handle the OTP (level) debug option + */ static -ERL_NIF_TERM esock_getopt_bin_opt(ErlNifEnv* env, - ESockDescriptor* descP, - int level, - int opt, - ErlNifBinary* binP) +ERL_NIF_TERM esock_getopt_otp_debug(ErlNifEnv* env, + ESockDescriptor* descP) { - ERL_NIF_TERM result; - int res; - SOCKOPTLEN_T vsz; - ErlNifBinary val; + ERL_NIF_TERM eVal; - vsz = (SOCKOPTLEN_T) binP->size; - if (SZT(vsz) != binP->size) { - result = esock_make_error_invalid(env, esock_atom_data_size); - } else { - ESOCK_ASSERT( ALLOC_BIN(vsz, &val) ); - sys_memcpy(val.data, binP->data, vsz); - res = sock_getopt(descP->sock, level, opt, val.data, &vsz); - if (res != 0) { - result = esock_make_error_errno(env, sock_errno()); - FREE_BIN(&val); - } else { + if (! IS_OPEN(descP->readState)) { + SSDBG( descP, + ("SOCKET", "esock_getopt_otp_debug {%d} -> done closed\r\n", + descP->sock) ); + return esock_make_error_closed(env); + } - /* Did we use all of the buffer? */ - if (vsz == val.size) { - result = esock_make_ok2(env, MKBIN(env, &val)); + eVal = esock_encode_bool(descP->dbg); - } else { + return esock_make_ok2(env, eVal); +} - ERL_NIF_TERM tmp; - tmp = MKBIN(env, &val); - tmp = MKSBIN(env, tmp, 0, vsz); +/* esock_getopt_otp_iow - Handle the OTP (level) iow option + */ - result = esock_make_ok2(env, tmp); - } - } +static +ERL_NIF_TERM esock_getopt_otp_iow(ErlNifEnv* env, + ESockDescriptor* descP) +{ + ERL_NIF_TERM eVal; + + if (! IS_OPEN(descP->readState)) { + SSDBG( descP, + ("SOCKET", "esock_getopt_otp_iow {%d} -> done closed\r\n", + descP->sock) ); + return esock_make_error_closed(env); } - return result; + eVal = esock_encode_bool(descP->iow); + + SSDBG( descP, + ("SOCKET", "esock_getopt_otp_iow {%d} ->" + "\r\n eVal: %T" + "\r\n", descP->sock, eVal) ); + + return esock_make_ok2(env, eVal); } -/* esock_getopt_timeval_opt - get an timeval option +/* esock_getopt_otp_select_read - Handle the OTP (level) select_read option */ -#ifndef __WIN32__ -#if (defined(SO_RCVTIMEO) || defined(SO_SNDTIMEO)) \ - && defined(ESOCK_USE_RCVSNDTIMEO) + static -ERL_NIF_TERM esock_getopt_timeval_opt(ErlNifEnv* env, - ESockDescriptor* descP, - int level, - int opt) +ERL_NIF_TERM esock_getopt_otp_select_read(ErlNifEnv* env, + ESockDescriptor* descP) { - ERL_NIF_TERM result; - struct timeval val; - SOCKOPTLEN_T valSz = sizeof(val); - int res; + ERL_NIF_TERM eVal; - sys_memzero((char*) &val, valSz); - res = sock_getopt(descP->sock, level, opt, &val, &valSz); + if (! IS_OPEN(descP->readState)) { + SSDBG( descP, + ("SOCKET", "esock_getopt_otp_select_read {%d} -> done closed\r\n", + descP->sock) ); + return esock_make_error_closed(env); + } - if (res != 0) { - result = esock_make_error_errno(env, sock_errno()); - } else { - ERL_NIF_TERM eTimeVal; + eVal = esock_encode_bool(descP->selectRead); - esock_encode_timeval(env, &val, &eTimeVal); - result = esock_make_ok2(env, eTimeVal); - } + SSDBG( descP, + ("SOCKET", "esock_getopt_otp_select_read {%d} ->" + "\r\n eVal: %T" + "\r\n", descP->sock, eVal) ); - return result; + return esock_make_ok2(env, eVal); } -#endif -#endif // #ifndef __WIN32__ -#ifndef __WIN32__ -#if defined(IP_PKTOPTIONS) || defined(IPV6_PKTOPTIONS) -/* Calculate CMSG_NXTHDR without having a struct msghdr*. - * CMSG_LEN only caters for alignment for start of data. - * To get how much to advance we need to use CMSG_SPACE - * on the payload length. To get the payload length we - * take the calculated cmsg->cmsg_len and subtract the - * header length. To get the header length we use - * the pointer difference from the cmsg start pointer - * to the CMSG_DATA(cmsg) pointer. - * - * Some platforms (seen on ppc Linux 2.6.29-3.ydl61.3) - * may return 0 as the cmsg_len if the cmsg is to be ignored. +/* esock_getopt_otp_ctrl_proc - Handle the OTP (level) controlling_process option */ -#define ESOCK_LEN_CMSG_DATA(__CMSG__) \ - ((__CMSG__)->cmsg_len < sizeof (struct cmsghdr) ? 0 : \ - (__CMSG__)->cmsg_len - ((char*)ESOCK_CMSG_DATA(__CMSG__) - (char*)(__CMSG__))) -#define ESOCK_NEXT_CMSG_HDR(__CMSG__) \ - ((struct cmsghdr*)(((char*)(__CMSG__)) + ESOCK_CMSG_SPACE(ESOCK_LEN_CMSG_DATA(__CMSG__)))) static -ERL_NIF_TERM esock_getopt_pktoptions(ErlNifEnv* env, - ESockDescriptor* descP, - int level, - int opt) +ERL_NIF_TERM esock_getopt_otp_ctrl_proc(ErlNifEnv* env, + ESockDescriptor* descP) { - ERL_NIF_TERM result, ePktOpts; - int res; - ErlNifBinary cmsgs; - SOCKOPTLEN_T sz = (SOCKOPTLEN_T) descP->rCtrlSz; - SocketTArray cmsghdrs = TARRAY_CREATE(16); - ERL_NIF_TERM ctrlBuf; + ERL_NIF_TERM eVal; - ESOCK_ASSERT( ALLOC_BIN(sz, &cmsgs) ); + if (! IS_OPEN(descP->readState)) { + SSDBG( descP, + ("SOCKET", + "esock_getopt_otp_ctrl_proc {%d} -> done closed\r\n", + descP->sock) ); + return esock_make_error_closed(env); + } - sys_memzero(cmsgs.data, cmsgs.size); - sz = cmsgs.size; // Make no assumption about the size - res = sock_getopt(descP->sock, level, opt, cmsgs.data, &sz); + eVal = MKPID(env, &descP->ctrlPid); - if (res != 0) { - result = esock_make_error_errno(env, sock_errno()); - } else { - struct cmsghdr* currentP; - struct cmsghdr* endOfBuf; + SSDBG( descP, + ("SOCKET", "esock_getopt_otp_ctrlProc {%d} ->" + "\r\n eVal: %T" + "\r\n", descP->sock, eVal) ); - ctrlBuf = MKBIN(env, &cmsgs); // The *entire* binary + return esock_make_ok2(env, eVal); +} - for (endOfBuf = (struct cmsghdr*)(cmsgs.data + cmsgs.size), - currentP = (struct cmsghdr*)(cmsgs.data); - (currentP != NULL) && (currentP < endOfBuf); - currentP = ESOCK_NEXT_CMSG_HDR(currentP)) { - unsigned char* dataP = UCHARP(ESOCK_CMSG_DATA(currentP)); - size_t dataPos = dataP - cmsgs.data; - size_t dataLen = (UCHARP(currentP) + currentP->cmsg_len) - dataP; - SSDBG( descP, - ("SOCKET", "esock_getopt_pktoptions {%d} -> cmsg header data: " - "\r\n currentP: 0x%lX" - "\r\n level: %d" - "\r\n data: %d" - "\r\n len: %d [0x%lX]" - "\r\n dataP: 0x%lX" - "\r\n dataPos: %d" - "\r\n dataLen: %d [0x%lX]" - "\r\n", descP->sock, - currentP, - currentP->cmsg_level, - currentP->cmsg_type, - currentP->cmsg_len, currentP->cmsg_len, - dataP, - dataPos, - dataLen, dataLen) ); +/* esock_getopt_otp_rcvbuf - Handle the OTP (level) rcvbuf option + */ - /* - * Check that this is within the allocated buffer... - * The 'next control message header' is a bit adhoc, - * so this check is a bit... - */ - if ((dataPos > cmsgs.size) || - (dataLen > cmsgs.size) || - ((dataPos + dataLen) > ((size_t)endOfBuf))) { - break; - } else { - ERL_NIF_TERM cmsgHdr; - ERL_NIF_TERM keys[] = {esock_atom_level, - esock_atom_type, - esock_atom_data, - esock_atom_value}; - ERL_NIF_TERM vals[NUM(keys)]; - size_t numKeys = NUM(keys); - BOOLEAN_T haveValue; - - vals[0] = esock_encode_level(env, currentP->cmsg_level); - vals[2] = MKSBIN(env, ctrlBuf, dataPos, dataLen); +static +ERL_NIF_TERM esock_getopt_otp_rcvbuf(ErlNifEnv* env, + ESockDescriptor* descP) +{ + ERL_NIF_TERM eVal; - haveValue = esock_encode_cmsg(env, - currentP->cmsg_level, - currentP->cmsg_type, - dataP, dataLen, &vals[1], &vals[3]); + if (! IS_OPEN(descP->readState)) { + SSDBG( descP, + ("SOCKET", "esock_getopt_otp_rcvbuf {%d} -> done closed\r\n", + descP->sock) ); + return esock_make_error_closed(env); + } - SSDBG( descP, - ("SOCKET", "esock_getopt_pktoptions {%d} -> " - "\r\n %T: %T" - "\r\n %T: %T" - "\r\n %T: %T" - "\r\n", descP->sock, - keys[0], vals[0], keys[1], vals[1], keys[2], vals[2]) ); +#ifdef __WIN32__ + eVal = MKUL(env, (unsigned long) descP->rBufSz); +#else + if (descP->rNum == 0) { + eVal = MKUL(env, (unsigned long) descP->rBufSz); + } else { + eVal = MKT2(env, + MKI(env, descP->rNum), + MKUL(env, (unsigned long) descP->rBufSz)); + } +#endif - if (haveValue) { - SSDBG( descP, - ("SOCKET", "esock_getopt_pktoptions {%d} -> " - "\r\n %T: %T" - "\r\n", descP->sock, keys[3], vals[3]) ); - } + SSDBG( descP, + ("SOCKET", "esock_getopt_otp_rcvbuf {%d} ->" + "\r\n eVal: %T" + "\r\n", descP->sock, eVal) ); - /* Guard against cut-and-paste errors */ - ESOCK_ASSERT( numKeys == NUM(vals) ); + return esock_make_ok2(env, eVal); +} - /* Make control message header map */ - ESOCK_ASSERT( MKMA(env, keys, vals, - numKeys - (haveValue ? 0 : 1), &cmsgHdr) ); - SSDBG( descP, - ("SOCKET", "esock_getopt_pktoptions {%d} -> header processed: " - "\r\n %T" - "\r\n", descP->sock, cmsgHdr) ); - /* And finally add it to the list... */ - TARRAY_ADD(cmsghdrs, cmsgHdr); - } +/* esock_getopt_otp_rcvctrlbuf - Handle the OTP (level) rcvctrlbuf option + */ + +static +ERL_NIF_TERM esock_getopt_otp_rcvctrlbuf(ErlNifEnv* env, + ESockDescriptor* descP) +{ + ERL_NIF_TERM eVal; + + if (! IS_OPEN(descP->readState)) { + SSDBG( descP, + ("SOCKET", + "esock_getopt_otp_rcvctrlbuf {%d} -> done closed\r\n", + descP->sock) ); + return esock_make_error_closed(env); } + eVal = MKUL(env, (unsigned long) descP->rCtrlSz); + SSDBG( descP, - ("SOCKET", "esock_getopt_pktoptions {%d} -> " - "cmsg headers processed when" - "\r\n TArray Size: %d" - "\r\n", descP->sock, TARRAY_SZ(cmsghdrs)) ); + ("SOCKET", "esock_getopt_otp_rcvctrlbuf {%d} ->" + "\r\n eVal: %T" + "\r\n", descP->sock, eVal) ); - /* The tarray is populated - convert it to a list */ - TARRAY_TOLIST(cmsghdrs, env, &ePktOpts); + return esock_make_ok2(env, eVal); +} + + + +/* esock_getopt_otp_sndctrlbuf - Handle the OTP (level) sndctrlbuf option + */ + +static +ERL_NIF_TERM esock_getopt_otp_sndctrlbuf(ErlNifEnv* env, + ESockDescriptor* descP) +{ + ERL_NIF_TERM eVal; + + if (! IS_OPEN(descP->writeState)) { + SSDBG( descP, + ("SOCKET", + "esock_getopt_otp_sndctrlbuf {%d} -> done closed\r\n", + descP->sock) ); + return esock_make_error_closed(env); + } + + eVal = MKUL(env, (unsigned long) descP->wCtrlSz); + + SSDBG( descP, + ("SOCKET", "esock_getopt_otp_sndctrlbuf {%d} ->" + "\r\n eVal: %T" + "\r\n", descP->sock, eVal) ); + + return esock_make_ok2(env, eVal); +} + + + +/* esock_getopt_otp_fd - Handle the OTP (level) fd option + */ + +static +ERL_NIF_TERM esock_getopt_otp_fd(ErlNifEnv* env, + ESockDescriptor* descP) +{ + ERL_NIF_TERM eVal; + + if (! IS_OPEN(descP->readState)) { + SSDBG( descP, + ("SOCKET", "esock_getopt_otp_debug {%d} -> done closed\r\n", + descP->sock) ); + return esock_make_error_closed(env); + } + + eVal = MKI(env, descP->sock); + + SSDBG( descP, + ("SOCKET", "esock_getopt_otp_fd {%d} ->" + "\r\n eVal: %T" + "\r\n", descP->sock, eVal) ); + + return esock_make_ok2(env, eVal); +} + + + +/* esock_getopt_otp_meta - Handle the OTP (level) meta option + */ + +static +ERL_NIF_TERM esock_getopt_otp_meta(ErlNifEnv* env, + ESockDescriptor* descP) +{ + ERL_NIF_TERM eVal; + + if (! IS_OPEN(descP->writeState)) { + SSDBG( descP, + ("SOCKET", "esock_getopt_otp_meta {%d} -> done closed\r\n", + descP->sock) ); + return esock_make_error_closed(env); + } + + eVal = CP_TERM(env, descP->meta.ref); + + SSDBG( descP, + ("SOCKET", "esock_getopt_otp_meta {%d} ->" + "\r\n eVal: %T" + "\r\n", descP->sock, eVal) ); + + return esock_make_ok2(env, eVal); +} + + + +/* esock_getopt_otp_use_registry - Handle the OTP (level) use_registry option + */ + +static +ERL_NIF_TERM esock_getopt_otp_use_registry(ErlNifEnv* env, + ESockDescriptor* descP) +{ + ERL_NIF_TERM eVal = esock_encode_bool(descP->useReg); + + return esock_make_ok2(env, eVal); +} + + + +/* + * esock_getopt_otp_domain - Handle the OTP (level) domain option + */ + +static +ERL_NIF_TERM esock_getopt_otp_domain(ErlNifEnv* env, + ESockDescriptor* descP) +{ + ERL_NIF_TERM domain, result; + + if (! IS_OPEN(descP->readState)) { + SSDBG( descP, + ("SOCKET", "esock_getopt_otp_domain {%d} -> done closed\r\n", + descP->sock) ); + return esock_make_error_closed(env); + } + + esock_encode_domain(env, descP->domain, &domain); + result = esock_make_ok2(env, domain); + + SSDBG( descP, + ("SOCKET", "esock_getopt_otp_domain {%d} ->" + "\r\n result: %T" + "\r\n", descP->sock, result) ); + + return result; +} + + + +#if 0 + +/* + * esock_getopt_otp_type - Handle the OTP (level) type options. + */ + +static +ERL_NIF_TERM esock_getopt_otp_type(ErlNifEnv* env, + ESockDescriptor* descP) +{ + ERL_NIF_TERM type, result; + + if (! IS_OPEN(descP->readState)) { + SSDBG( descP, + ("SOCKET", "esock_getopt_otp_type {%d} -> done closed\r\n", + descP->sock) ); + return esock_make_error_closed(env); + } + + esock_encode_type(env, descP->type, &type); + result = esock_make_ok2(env, type); + + SSDBG( descP, + ("SOCKET", "esock_getopt_otp_type {%d} ->" + "\r\n result: %T" + "\r\n", descP->sock, result) ); + + return result; +} + + + +/* + * esock_getopt_otp_protocol - Handle the OTP (level) protocol options. + */ + +static +ERL_NIF_TERM esock_getopt_otp_protocol(ErlNifEnv* env, + ESockDescriptor* descP) +{ + ERL_NIF_TERM protocol, result; + + if (! IS_OPEN(descP->readState)) { + SSDBG( descP, + ("SOCKET", "esock_getopt_otp_protocol {%d} -> done closed\r\n", + descP->sock) ); + return esock_make_error_closed(env); + } + + protocol = MKI(env, descP->protocol); + result = esock_make_ok2(env, protocol); + + SSDBG( descP, + ("SOCKET", "esock_getopt_otp_protocol {%d} ->" + "\r\n result: %T" + "\r\n", descP->sock, result) ); + + return result; +} + + + +/* + * esock_getopt_otp_dtp - Handle the OTP (level) type options. + */ + +static +ERL_NIF_TERM esock_getopt_otp_dtp(ErlNifEnv* env, + ESockDescriptor* descP) +{ + ERL_NIF_TERM domain, type, protocol, dtp, result; + + if (! IS_OPEN(descP->readState)) { + SSDBG( descP, + ("SOCKET", "esock_getopt_otp_dtp {%d} -> done closed\r\n", + descP->sock) ); + return esock_make_error_closed(env); + } + + esock_encode_domain(env, descP->domain, &domain); + esock_encode_type(env, descP->type, &type); + protocol = MKI(env, descP->protocol); + dtp = MKT3(env, domain, type, protocol); + result = esock_make_ok2(env, dtp); + + SSDBG( descP, + ("SOCKET", "esock_getopt_otp_dtp {%d} ->" + "\r\n result: %T" + "\r\n", descP->sock, result) ); + + return result; +} + + +#endif // #if 0 + + +/* How to decode the value is specified with valueSpec + */ + +static +ERL_NIF_TERM esock_getopt_native(ErlNifEnv* env, + ESockDescriptor* descP, + int level, + int opt, + ERL_NIF_TERM valueSpec) +{ + ERL_NIF_TERM result; + SOCKOPTLEN_T valueSz; + int sz; + ErlNifBinary bin; + + MLOCK(descP->readMtx); + + SSDBG( descP, + ("SOCKET", "esock_getopt_native {%d} -> entry" + "\r\n level: %d" + "\r\n opt: %d" + "\r\n valueSpec: %T" + "\r\n", descP->sock, + level, opt, valueSpec) ); + + if (! IS_OPEN(descP->readState)) { + SSDBG( descP, + ("SOCKET", "esock_getopt_native {%d} -> done closed\r\n", + descP->sock) ); + MUNLOCK(descP->readMtx); + return esock_make_error_closed(env); + } + + /* + * We could make it possible to specify more types, + * such as string, NUL terminated or not, etc... + * + */ + + if (GET_INT(env, valueSpec, &sz)) { + valueSz = (SOCKOPTLEN_T) sz; + if ((int) valueSz == sz) { + SSDBG( descP, + ("SOCKET", "esock_getopt_native {%d} -> binary size" + "\r\n valueSz: %d" + "\r\n", descP->sock, sz) ); + result = + esock_getopt_size_opt(env, descP, level, opt, valueSz); + } else { + result = esock_make_invalid(env, esock_atom_value); + } + } else if (COMPARE(valueSpec, atom_integer) == 0) { + SSDBG( descP, + ("SOCKET", "esock_getopt_native {%d} -> integer" + "\r\n", descP->sock) ); + result = esock_getopt_int_opt(env, descP, level, opt, + esock_atom_undefined); + } else if (COMPARE(valueSpec, atom_boolean) == 0) { + SSDBG( descP, + ("SOCKET", "esock_getopt_native {%d} -> boolean" + "\r\n", descP->sock) ); + result = esock_getopt_bool_opt(env, descP, level, opt, + esock_atom_undefined); + } else if (enif_inspect_binary(env, valueSpec, &bin)) { + SSDBG( descP, + ("SOCKET", "esock_getopt_native {%d} -> binary" + "\r\n size: %lu" + "\r\n", descP->sock, (unsigned long) bin.size) ); + result = esock_getopt_bin_opt(env, descP, level, opt, &bin); + } else { + result = esock_make_invalid(env, esock_atom_value); + } + + SSDBG( descP, + ("SOCKET", "esock_getopt_native {%d} -> done when" + "\r\n result: %T" + "\r\n", descP->sock, result) ); + + MUNLOCK(descP->readMtx); + return result; +} + + + +/* esock_getopt - An option that we know how to decode + */ +static +ERL_NIF_TERM esock_getopt(ErlNifEnv* env, + ESockDescriptor* descP, + int level, + int opt, + ERL_NIF_TERM eval) +{ + ERL_NIF_TERM result; + const struct ESockOpt *optP; + + MLOCK(descP->readMtx); + + SSDBG( descP, + ("SOCKET", "esock_getopt {%d} -> entry with" + "\r\n level: %d" + "\r\n opt: %d" + "\r\n", descP->sock, level, opt) ); + + if (! IS_OPEN(descP->readState)) { + SSDBG( descP, + ("SOCKET", "esock_getopt {%d} -> done when closed\r\n", + descP->sock) ); + MUNLOCK(descP->readMtx); + return esock_make_error_closed(env); + } + + optP = lookupOpt(level, opt); + + if (optP == NULL) { + + result = esock_make_invalid(env, atom_socket_option); + + SSDBG( descP, + ("SOCKET", "esock_getopt {%d} -> unknown option\r\n", + descP->sock) ); + + } else if (optP->getopt == NULL) { + + result = esock_make_invalid(env, atom_socket_option); + + SSDBG( descP, + ("SOCKET", "esock_getopt {%d} -> opt not gettable\r\n", + descP->sock) ); + + } else { + + result = (optP->getopt)(env, descP, level, opt, eval); + + SSDBG( descP, + ("SOCKET", "esock_getopt {%d} -> done when" + "\r\n result: %T" + "\r\n", descP->sock, result) ); + } + + MUNLOCK(descP->readMtx); + return result; +} + + +#if defined(SO_BINDTODEVICE) +static +ERL_NIF_TERM esock_getopt_so_bindtodevice(ErlNifEnv* env, + ESockDescriptor* descP, + int level, + int opt, + ERL_NIF_TERM eval) +{ + VOID(eval); + + return esock_getopt_str_opt(env, descP, level, opt, IFNAMSIZ+1, FALSE); +} +#endif + + +#if defined(SO_BSP_STATE) +/* We need to allocate *all* of the memory used by the CSADDR_INFO + * structure. *Including* the 'sockaddr' structures pointed to by + * LocalAddr and RemoteAddr (lpSockaddr in SOCKET_ADDRESS). + * The '2*' is just to "dead sure" that we have enough... + */ +static +ERL_NIF_TERM esock_getopt_bsp_state(ErlNifEnv* env, + ESockDescriptor* descP, + int level, + int opt, + ERL_NIF_TERM eVal) +{ + ERL_NIF_TERM result; + SOCKOPTLEN_T valSz = 2*(sizeof(CSADDR_INFO) + 2*sizeof(SOCKADDR)); + CSADDR_INFO* valP = MALLOC(valSz); + int res; + + (void) eVal; + + SSDBG( descP, + ("SOCKET", "esock_getopt_bsp_state(%d) -> entry\r\n", descP->sock) ); + + sys_memzero((void *) valP, valSz); + +#ifdef __WIN32__ + res = sock_getopt(descP->sock, level, opt, (char*) valP, &valSz); +#else + res = sock_getopt(descP->sock, level, opt, valP, &valSz); +#endif + + if (res != 0) { + int save_errno = sock_errno(); + ERL_NIF_TERM reason = ENO2T(env, save_errno); + + SSDBG( descP, + ("SOCKET", "esock_getopt_bsp_state(%d) -> error: " + "\r\n %T" + "\r\n", descP->sock, reason) ); + + result = esock_make_error(env, reason); + + } else if (valSz > 0) { + ERL_NIF_TERM + la = esock_encode_bsp_state_socket_address(env, &valP->LocalAddr), + ra = esock_encode_bsp_state_socket_address(env, &valP->RemoteAddr), + type = esock_encode_bsp_state_type(env, valP->iSocketType), + proto = esock_encode_bsp_state_protocol(env, valP->iProtocol), + keys[] = {atom_local_addr, atom_remote_addr, esock_atom_type, esock_atom_protocol}, + vals[] = {la, ra, type, proto}, + bspState; + size_t numKeys = NUM(keys); + + SSDBG( descP, + ("SOCKET", "esock_getopt_bsp_state(%d) -> values encoded:" + "\r\n la: %T" + "\r\n ra: %T" + "\r\n type: %T" + "\r\n proto: %T" + "\r\n", descP->sock, + la, ra, type, proto) ); + + ESOCK_ASSERT( numKeys == NUM(vals) ); + ESOCK_ASSERT( MKMA(env, keys, vals, numKeys, &bspState) ); + + SSDBG( descP, + ("SOCKET", "esock_getopt_bsp_state(%d) -> " + "\r\n BSP State: %T" + "\r\n", descP->sock, bspState) ); + + result = esock_make_ok2(env, bspState); + } else { + result = esock_make_ok2(env, esock_atom_undefined); + } + + FREE( valP ); + + SSDBG( descP, + ("SOCKET", "esock_getopt_bsp_state(%d) -> done when" + "\r\n result: %T" + "\r\n", descP->sock, result) ); + + return result; +} + + +static +ERL_NIF_TERM esock_encode_bsp_state_socket_address(ErlNifEnv* env, + SOCKET_ADDRESS* addr) +{ + ERL_NIF_TERM eaddr; + + if (addr == NULL) + return esock_atom_undefined; + + if ((addr->lpSockaddr == NULL) || + (addr->iSockaddrLength == 0)) + return esock_atom_undefined; + + esock_encode_sockaddr(env, + (ESockAddress*) addr->lpSockaddr, + addr->iSockaddrLength, + &eaddr); + + return eaddr; +} + + +static +ERL_NIF_TERM esock_encode_bsp_state_type(ErlNifEnv* env, int type) +{ + ERL_NIF_TERM etype; + + switch (type) { + case SOCK_STREAM: + etype = esock_atom_stream; + break; + + case SOCK_DGRAM: + etype = esock_atom_dgram; + break; + + case SOCK_RDM: + etype = esock_atom_rdm; + break; + + case SOCK_SEQPACKET: + etype = esock_atom_seqpacket; + break; + + default: + etype = MKI(env, type); + break; + } + + return etype; +} + + +static +ERL_NIF_TERM esock_encode_bsp_state_protocol(ErlNifEnv* env, int proto) +{ + ERL_NIF_TERM eproto; + + switch (proto) { + case IPPROTO_TCP: + eproto = esock_atom_tcp; + break; + + case IPPROTO_UDP: + eproto = esock_atom_udp; + break; + + /* + * In Wista and later the IPPROTO_PGM constant is defined in the + * Ws2def.h header file to the same value as the IPPROTO_RM constant + * defined in the Wsrm.h header file. + * => So we use IPPROTO_PGM also but translate to rm... + * + */ +#if defined(IPPROTO_RM) || defined(IPPROTO_PGM) +#if defined(IPPROTO_RM) + case IPPROTO_RM: +#else if defined(IPPROTO_PGM) + case IPPROTO_PGM: +#endif + eproto = esock_atom_rm; + break; +#endif + + default: + eproto = MKI(env, proto); + break; + } + + return eproto; +} + +#endif + + + +#if defined(SO_DOMAIN) +static +ERL_NIF_TERM esock_getopt_sock_domain(ErlNifEnv* env, + ESockDescriptor* descP, + int level, + int opt, + ERL_NIF_TERM eval) +{ + int val; + ERL_NIF_TERM result; + + VOID(eval); + + if (! esock_getopt_int(descP->sock, level, opt, &val)) { + result = esock_make_error_errno(env, sock_errno()); + } else { + ERL_NIF_TERM domain; + esock_encode_domain(env, val, &domain); + result = esock_make_ok2(env, domain); + } + + return result; +} +#endif + + +#if defined(SO_LINGER) +static +ERL_NIF_TERM esock_getopt_linger(ErlNifEnv* env, + ESockDescriptor* descP, + int level, + int opt, + ERL_NIF_TERM eval) +{ + ERL_NIF_TERM result; + struct linger val; + SOCKOPTLEN_T valSz = sizeof(val); + int res; + + (void) eval; + + sys_memzero((void *) &val, sizeof(val)); + +#ifdef __WIN32__ + res = sock_getopt(descP->sock, level, opt, (char*) &val, &valSz); +#else + res = sock_getopt(descP->sock, level, opt, &val, &valSz); +#endif + + if (res != 0) { + result = esock_make_error_errno(env, sock_errno()); + } else { + ERL_NIF_TERM + lOnOff = ((val.l_onoff != 0) ? atom_true : atom_false), + lSecs = MKI(env, val.l_linger), + keys[] = {atom_onoff, esock_atom_linger}, + vals[] = {lOnOff, lSecs}, + linger; + size_t numKeys = NUM(keys); + + SSDBG( descP, + ("SOCKET", "esock_getopt_linger(%d) -> " + "\r\n val.l_onoff: %d" + "\r\n lOnOff: %T" + "\r\n val.l_linger: %d" + "\r\n lSecs: %T" + "\r\n", descP->sock, + val.l_onoff, lOnOff, + val.l_linger, lSecs) ); + + ESOCK_ASSERT( numKeys == NUM(vals) ); + ESOCK_ASSERT( MKMA(env, keys, vals, numKeys, &linger) ); + + SSDBG( descP, + ("SOCKET", "esock_getopt_linger(%d) -> " + "\r\n linger: %T" + "\r\n", descP->sock, linger) ); + + result = esock_make_ok2(env, linger); + } + + return result; +} +#endif + + + +#if defined(SO_TYPE) +static +ERL_NIF_TERM esock_getopt_sock_type(ErlNifEnv* env, + ESockDescriptor* descP, + int level, + int opt, + ERL_NIF_TERM eval) +{ + ERL_NIF_TERM result; + int val; + + (void) eval; + + if (! esock_getopt_int(descP->sock, level, opt, &val)) { + result = esock_make_error_errno(env, sock_errno()); + } else { + ERL_NIF_TERM type; + esock_encode_type(env, val, &type); + result = esock_make_ok2(env, type); + } + + return result; +} +#endif + + +#if defined(SO_PROTOCOL) +static +ERL_NIF_TERM esock_getopt_sock_protocol(ErlNifEnv* env, + ESockDescriptor* descP, + int level, + int opt, + ERL_NIF_TERM eval) +{ + ERL_NIF_TERM result; + int val; + + VOID(eval); + + if (! esock_getopt_int(descP->sock, level, opt, &val)) { + result = esock_make_error_errno(env, sock_errno()); + } else { + ERL_NIF_TERM protocol; + + protocol = +#ifdef AF_LOCAL + /* For AF_LOCAL, the protocols table value for 0 is wrong */ + (val == 0) && (descP->domain == AF_LOCAL) ? + esock_atom_default : + /* It is correct for AF_INET and hopefully for AF_INET6, + * but for future additions it is an open question + */ +#endif + MKI(env, val); + + result = esock_make_ok2(env, protocol); + } + + return result; +} +#endif + + +/* esock_getopt_ip_mtu_discover - Level IP MTU_DISCOVER option + */ +#if defined(IP_MTU_DISCOVER) +static +ERL_NIF_TERM esock_getopt_ip_mtu_discover(ErlNifEnv* env, + ESockDescriptor* descP, + int level, + int opt, + ERL_NIF_TERM eval) +{ + ERL_NIF_TERM result; + int mtuDisc; + + (void) eval; + + if (! esock_getopt_int(descP->sock, level, opt, &mtuDisc)) { + result = esock_make_error_errno(env, sock_errno()); + } else { + ERL_NIF_TERM eMtuDisc; + encode_ip_pmtudisc(env, mtuDisc, &eMtuDisc); + result = esock_make_ok2(env, eMtuDisc); + } + + return result; + +} +#endif + + +/* esock_getopt_multicast_if - Level IP MULTICAST_IF option + */ + +#if defined(IP_MULTICAST_IF) +static +ERL_NIF_TERM esock_getopt_multicast_if(ErlNifEnv* env, + ESockDescriptor* descP, + int level, + int opt, + ERL_NIF_TERM eval) +{ + ERL_NIF_TERM result; + ERL_NIF_TERM eAddr; + struct in_addr ifAddr; + SOCKOPTLEN_T ifAddrSz = sizeof(ifAddr); + int res; + + (void) eval; + + sys_memzero((void *) &ifAddr, ifAddrSz); + +#ifdef __WIN32__ + res = sock_getopt(descP->sock, level, opt, (char*) &ifAddr, &ifAddrSz); +#else + res = sock_getopt(descP->sock, level, opt, &ifAddr, &ifAddrSz); +#endif + + if (res != 0) { + result = esock_make_error_errno(env, sock_errno()); + } else { + esock_encode_in_addr(env, &ifAddr, &eAddr); + result = esock_make_ok2(env, eAddr); + } + + return result; + +} +#endif + + + +/* esock_getopt_tos - Level IP TOS option + */ + +#if defined(IP_TOS) +static +ERL_NIF_TERM esock_getopt_tos(ErlNifEnv* env, + ESockDescriptor* descP, + int level, + int opt, + ERL_NIF_TERM eval) +{ + ERL_NIF_TERM result; + int val = 0; + + (void) eval; + + if (! esock_getopt_int(descP->sock, level, opt, &val)) { + result = esock_make_error_errno(env, sock_errno()); + } else { + result = esock_make_ok2(env, encode_ip_tos(env, val)); + } + + return result; +} +#endif + + + +#if defined(HAVE_IPV6) + +/* esock_getopt_ipv6_mtu_discover - Level IPv6 MTU_DISCOVER option + */ + +#if defined(IPV6_MTU_DISCOVER) +static +ERL_NIF_TERM esock_getopt_ipv6_mtu_discover(ErlNifEnv* env, + ESockDescriptor* descP, + int level, + int opt, + ERL_NIF_TERM eval) +{ + ERL_NIF_TERM result; + int mtuDisc; + + (void) eval; + + if (! esock_getopt_int(descP->sock, level, opt, &mtuDisc)) { + result = esock_make_error_errno(env, sock_errno()); + } else { + ERL_NIF_TERM eMtuDisc; + encode_ipv6_pmtudisc(env, mtuDisc, &eMtuDisc); + result = esock_make_ok2(env, eMtuDisc); + } + + return result; + +} +#endif + +#endif // defined(HAVE_IPV6) + + +/* esock_getopt_tcp_congestion - Level TCP CONGESTION option + */ + +#if defined(TCP_CONGESTION) +static +ERL_NIF_TERM esock_getopt_tcp_congestion(ErlNifEnv* env, + ESockDescriptor* descP, + int level, + int opt, + ERL_NIF_TERM eval) +{ + int max = ESOCK_OPT_TCP_CONGESTION_NAME_MAX+1; + + VOID(eval); + + return esock_getopt_str_opt(env, descP, level, opt, max, TRUE); +} +#endif + + + +#if defined(HAVE_SCTP) + +/* esock_getopt_sctp_adaptation_layer - Level SCTP option + * + */ +#if defined(SCTP_ADAPTATION_LAYER) +static +ERL_NIF_TERM esock_getopt_sctp_adaptation_layer(ErlNifEnv* env, + ESockDescriptor* descP, + int level, + int opt, + ERL_NIF_TERM eval) +{ + ERL_NIF_TERM result; + struct sctp_setadaptation ad; + SOCKOPTLEN_T adSz = sizeof(ad); + int res; + + VOID(eval); + + SSDBG( descP, + ("SOCKET", "esock_getopt_sctp_adaptation_layer -> entry\r\n") ); + + sys_memzero((char*) &ad, adSz); + res = sock_getopt(descP->sock, level, opt, &ad, &adSz); + + if (res != 0) { + int save_errno = sock_errno(); + + SSDBG( descP, + ("SOCKET", + "esock_getopt_sctp_adaptation_layer -> " + "failed get option: " + "\r\n errno: %T (%d)" + "\r\n", erl_errno_id(save_errno), save_errno) ); + + result = esock_make_error_errno(env, save_errno); + + } else { + ERL_NIF_TERM ead; + ERL_NIF_TERM keys[] = {esock_atom_esock_name, + atom_indication}; + ERL_NIF_TERM vals[] = {MKA(env, "sctp_adaptation_layer"), + MKUI(env, ad.ssb_adaptation_ind)}; + size_t numKeys = NUM(keys); + + ESOCK_ASSERT( numKeys == NUM(vals) ); + ESOCK_ASSERT( MKMA(env, keys, vals, numKeys, &ead) ); + + SSDBG( descP, + ("SOCKET", + "esock_getopt_sctp_adaptation_layer -> " + "got option: " + "\r\n %T" + "\r\n", ead) ); + + result = esock_make_ok2(env, ead); + } + + SSDBG( descP, + ("SOCKET", + "esock_getopt_sctp_adaptation_layer -> done\r\n") ); + + return result; +} +#endif + + + +/* esock_getopt_sctp_associnfo - Level SCTP ASSOCINFO option + * + * + * + * We should really specify which association this relates to, + * as it is now we get assoc-id = 0. If this socket is an + * association (and not an endpoint) then it will have an + * assoc id. But since the sctp support at present is "limited", + * we leave it for now. + * What do we do if this is an endpoint? Invalid op? Or just leave + * it for the OS? + * + * + */ +#ifndef __WIN32__ +#if defined(SCTP_ASSOCINFO) +static +ERL_NIF_TERM esock_getopt_sctp_associnfo(ErlNifEnv* env, + ESockDescriptor* descP, + int level, + int opt, + ERL_NIF_TERM eval) +{ + ERL_NIF_TERM result; + struct sctp_assocparams val; + SOCKOPTLEN_T valSz = sizeof(val); + int res; + + VOID(eval); + + sys_memzero((char*) &val, valSz); + res = sock_getopt(descP->sock, level, opt, &val, &valSz); + + if (res != 0) { + result = esock_make_error_errno(env, sock_errno()); + } else { + ERL_NIF_TERM eAssocParams; + ERL_NIF_TERM keys[] = {esock_atom_esock_name, + atom_assoc_id, + atom_asocmaxrxt, + atom_number_peer_destinations, + atom_peer_rwnd, + atom_local_rwnd, + atom_cookie_life}; + ERL_NIF_TERM vals[] = {MKA(env, "sctp_associnfo"), + encode_sctp_assoc_t(env, val.sasoc_assoc_id), + MKUI(env, val.sasoc_asocmaxrxt), + MKUI(env, val.sasoc_number_peer_destinations), + MKUI(env, val.sasoc_peer_rwnd), + MKUI(env, val.sasoc_local_rwnd), + MKUI(env, val.sasoc_cookie_life)}; + size_t numKeys = NUM(keys); + + ESOCK_ASSERT( numKeys == NUM(vals) ); + ESOCK_ASSERT( MKMA(env, keys, vals, numKeys, &eAssocParams) ); + + SSDBG( descP, + ("SOCKET", + "esock_getopt_sctp_associnfo -> " + "got option: " + "\r\n %T" + "\r\n", eAssocParams) ); + + result = esock_make_ok2(env, eAssocParams); + } + + return result; +} +#endif +#endif // #ifndef __WIN32__ + + + + +/* esock_getopt_sctp_initmsg - Level SCTP INITMSG option + * + */ +#ifndef __WIN32__ +#if defined(SCTP_INITMSG) +static +ERL_NIF_TERM esock_getopt_sctp_initmsg(ErlNifEnv* env, + ESockDescriptor* descP, + int level, + int opt, + ERL_NIF_TERM eval) +{ + ERL_NIF_TERM result; + struct sctp_initmsg val; + SOCKOPTLEN_T valSz = sizeof(val); + int res; + + VOID(eval); + + sys_memzero((char*) &val, valSz); + res = sock_getopt(descP->sock, level, opt, &val, &valSz); + + if (res != 0) { + result = esock_make_error_errno(env, sock_errno()); + } else { + ERL_NIF_TERM eInitMsg; + ERL_NIF_TERM keys[] = {atom_num_outstreams, atom_max_instreams, + atom_max_attempts, atom_max_init_timeo}; + ERL_NIF_TERM vals[] = {MKUI(env, val.sinit_num_ostreams), + MKUI(env, val.sinit_max_instreams), + MKUI(env, val.sinit_max_attempts), + MKUI(env, val.sinit_max_init_timeo)}; + unsigned int numKeys = NUM(keys); + unsigned int numVals = NUM(vals); + + ESOCK_ASSERT( numKeys == numVals ); + ESOCK_ASSERT( MKMA(env, keys, vals, numKeys, &eInitMsg) ); + result = esock_make_ok2(env, eInitMsg); + } + + return result; +} +#endif +#endif // #ifndef __WIN32__ + + +/* esock_getopt_sctp_rtoinfo - Level SCTP ASSOCINFO option + * + * + * + * We should really specify which association this relates to, + * as it is now we get assoc-id = 0. If this socket is an + * association (and not an endpoint) then it will have an + * assoc id (we can assume). But since the sctp support at + * present is "limited", we leave it for now. + * What do we do if this is an endpoint? Invalid op? + * + * + */ +#ifndef __WIN32__ +#if defined(SCTP_RTOINFO) +static +ERL_NIF_TERM esock_getopt_sctp_rtoinfo(ErlNifEnv* env, + ESockDescriptor* descP, + int level, + int opt, + ERL_NIF_TERM eval) +{ + ERL_NIF_TERM result; + struct sctp_rtoinfo val; + SOCKOPTLEN_T valSz = sizeof(val); + int res; + + VOID(eval); + + sys_memzero((char*) &val, valSz); + res = sock_getopt(descP->sock, level, opt, &val, &valSz); + + if (res != 0) { + result = esock_make_error_errno(env, sock_errno()); + } else { + ERL_NIF_TERM eRTOInfo; + ERL_NIF_TERM keys[] = {atom_assoc_id, atom_initial, atom_max, atom_min}; + ERL_NIF_TERM vals[] = {encode_sctp_assoc_t(env, val.srto_assoc_id), + MKUI(env, val.srto_initial), + MKUI(env, val.srto_max), + MKUI(env, val.srto_min)}; + unsigned int numKeys = NUM(keys); + unsigned int numVals = NUM(vals); + + ESOCK_ASSERT( numKeys == numVals ); + ESOCK_ASSERT( MKMA(env, keys, vals, numKeys, &eRTOInfo) ); + result = esock_make_ok2(env, eRTOInfo); + } + + return result; +} +#endif +#endif // #ifndef __WIN32__ + + + +#endif // defined(HAVE_SCTP) + + + +/* esock_getopt_bool_opt - get an (integer) bool option + */ +static +ERL_NIF_TERM esock_getopt_bool_opt(ErlNifEnv* env, + ESockDescriptor* descP, + int level, + int opt, + ERL_NIF_TERM eval) +{ + ERL_NIF_TERM result; + int val = 0; + + (void) eval; + + if (! esock_getopt_int(descP->sock, level, opt, &val)) { + result = esock_make_error_errno(env, sock_errno()); + } else { + ERL_NIF_TERM bval = ((val) ? atom_true : atom_false); + + result = esock_make_ok2(env, bval); + } + return result; +} + + +/* esock_getopt_int_opt - get an integer option + */ +static +ERL_NIF_TERM esock_getopt_int_opt(ErlNifEnv* env, + ESockDescriptor* descP, + int level, + int opt, + ERL_NIF_TERM eval) +{ + int val; + + (void) eval; + + if (! esock_getopt_int(descP->sock, level, opt, &val)) + return esock_make_error_errno(env, sock_errno()); + + return esock_make_ok2(env, MKI(env, val)); +} + + + +/* esock_getopt_int - get an integer option + */ +extern +BOOLEAN_T esock_getopt_int(SOCKET sock, + int level, + int opt, + int* valP) +{ + int val = 0; + SOCKOPTLEN_T valSz = sizeof(val); + +#ifdef __WIN32__ + if (sock_getopt(sock, level, opt, (char*) &val, &valSz) != 0) +#else + if (sock_getopt(sock, level, opt, &val, &valSz) != 0) +#endif + return FALSE; + + *valP = val; + return TRUE; +} + + + +static +ERL_NIF_TERM esock_getopt_size_opt(ErlNifEnv* env, + ESockDescriptor* descP, + int level, + int opt, + SOCKOPTLEN_T valueSz) +{ + ERL_NIF_TERM result; + int res; + + if (valueSz == 0) { + res = sock_getopt(descP->sock, level, opt, NULL, NULL); + if (res != 0) + result = esock_make_error_errno(env, sock_errno()); + else + result = esock_atom_ok; + } else { + SOCKOPTLEN_T vsz = valueSz; + ErlNifBinary val; + + ESOCK_ASSERT( ALLOC_BIN(vsz, &val) ); + sys_memzero(val.data, val.size); + res = sock_getopt(descP->sock, level, opt, val.data, &vsz); + if (res != 0) { + result = esock_make_error_errno(env, sock_errno()); + FREE_BIN(&val); + } else { + + /* 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); + } + } + } + + return result; +} + + + +static +ERL_NIF_TERM esock_getopt_bin_opt(ErlNifEnv* env, + ESockDescriptor* descP, + int level, + int opt, + ErlNifBinary* binP) +{ + ERL_NIF_TERM result; + int res; + SOCKOPTLEN_T vsz; + ErlNifBinary val; + + vsz = (SOCKOPTLEN_T) binP->size; + if (SZT(vsz) != binP->size) { + result = esock_make_error_invalid(env, esock_atom_data_size); + } else { + ESOCK_ASSERT( ALLOC_BIN(vsz, &val) ); + sys_memcpy(val.data, binP->data, vsz); + res = sock_getopt(descP->sock, level, opt, val.data, &vsz); + if (res != 0) { + result = esock_make_error_errno(env, sock_errno()); + FREE_BIN(&val); + } else { + + /* 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); + } + } + } + + return result; +} + + + +/* esock_getopt_timeval_opt - get an timeval option + */ +#ifndef __WIN32__ +#if (defined(SO_RCVTIMEO) || defined(SO_SNDTIMEO)) \ + && defined(ESOCK_USE_RCVSNDTIMEO) +static +ERL_NIF_TERM esock_getopt_timeval_opt(ErlNifEnv* env, + ESockDescriptor* descP, + int level, + int opt) +{ + ERL_NIF_TERM result; + struct timeval val; + SOCKOPTLEN_T valSz = sizeof(val); + int res; + + sys_memzero((char*) &val, valSz); + res = sock_getopt(descP->sock, level, opt, &val, &valSz); + + if (res != 0) { + result = esock_make_error_errno(env, sock_errno()); + } else { + ERL_NIF_TERM eTimeVal; + + esock_encode_timeval(env, &val, &eTimeVal); + result = esock_make_ok2(env, eTimeVal); + } + + return result; +} +#endif +#endif // #ifndef __WIN32__ + + +#ifndef __WIN32__ +#if defined(IP_PKTOPTIONS) || defined(IPV6_PKTOPTIONS) + +/* Calculate CMSG_NXTHDR without having a struct msghdr*. + * CMSG_LEN only caters for alignment for start of data. + * To get how much to advance we need to use CMSG_SPACE + * on the payload length. To get the payload length we + * take the calculated cmsg->cmsg_len and subtract the + * header length. To get the header length we use + * the pointer difference from the cmsg start pointer + * to the CMSG_DATA(cmsg) pointer. + * + * Some platforms (seen on ppc Linux 2.6.29-3.ydl61.3) + * may return 0 as the cmsg_len if the cmsg is to be ignored. + */ +#define ESOCK_LEN_CMSG_DATA(__CMSG__) \ + ((__CMSG__)->cmsg_len < sizeof (struct cmsghdr) ? 0 : \ + (__CMSG__)->cmsg_len - ((char*)ESOCK_CMSG_DATA(__CMSG__) - (char*)(__CMSG__))) +#define ESOCK_NEXT_CMSG_HDR(__CMSG__) \ + ((struct cmsghdr*)(((char*)(__CMSG__)) + ESOCK_CMSG_SPACE(ESOCK_LEN_CMSG_DATA(__CMSG__)))) + +static +ERL_NIF_TERM esock_getopt_pktoptions(ErlNifEnv* env, + ESockDescriptor* descP, + int level, + int opt, + ERL_NIF_TERM eval) +{ + ERL_NIF_TERM result, ePktOpts; + int res; + ErlNifBinary cmsgs; + SOCKOPTLEN_T sz = (SOCKOPTLEN_T) descP->rCtrlSz; + SocketTArray cmsghdrs = TARRAY_CREATE(16); + ERL_NIF_TERM ctrlBuf; + + VOID(eval); + + ESOCK_ASSERT( ALLOC_BIN(sz, &cmsgs) ); + + sys_memzero(cmsgs.data, cmsgs.size); + sz = cmsgs.size; // Make no assumption about the size + res = sock_getopt(descP->sock, level, opt, cmsgs.data, &sz); + + if (res != 0) { + result = esock_make_error_errno(env, sock_errno()); + } else { + struct cmsghdr* currentP; + struct cmsghdr* endOfBuf; + + ctrlBuf = MKBIN(env, &cmsgs); // The *entire* binary + + for (endOfBuf = (struct cmsghdr*)(cmsgs.data + cmsgs.size), + currentP = (struct cmsghdr*)(cmsgs.data); + (currentP != NULL) && (currentP < endOfBuf); + currentP = ESOCK_NEXT_CMSG_HDR(currentP)) { + unsigned char* dataP = UCHARP(ESOCK_CMSG_DATA(currentP)); + size_t dataPos = dataP - cmsgs.data; + size_t dataLen = (UCHARP(currentP) + currentP->cmsg_len) - dataP; + + SSDBG( descP, + ("SOCKET", "esock_getopt_pktoptions {%d} -> cmsg header data: " + "\r\n currentP: 0x%lX" + "\r\n level: %d" + "\r\n data: %d" + "\r\n len: %d [0x%lX]" + "\r\n dataP: 0x%lX" + "\r\n dataPos: %d" + "\r\n dataLen: %d [0x%lX]" + "\r\n", descP->sock, + currentP, + currentP->cmsg_level, + currentP->cmsg_type, + currentP->cmsg_len, currentP->cmsg_len, + dataP, + dataPos, + dataLen, dataLen) ); + + /* + * Check that this is within the allocated buffer... + * The 'next control message header' is a bit adhoc, + * so this check is a bit... + */ + if ((dataPos > cmsgs.size) || + (dataLen > cmsgs.size) || + ((dataPos + dataLen) > ((size_t)endOfBuf))) { + break; + } else { + ERL_NIF_TERM cmsgHdr; + ERL_NIF_TERM keys[] = {esock_atom_level, + esock_atom_type, + esock_atom_data, + esock_atom_value}; + ERL_NIF_TERM vals[NUM(keys)]; + size_t numKeys = NUM(keys); + BOOLEAN_T haveValue; + + vals[0] = esock_encode_level(env, currentP->cmsg_level); + vals[2] = MKSBIN(env, ctrlBuf, dataPos, dataLen); + + haveValue = esock_encode_cmsg(env, + currentP->cmsg_level, + currentP->cmsg_type, + dataP, dataLen, &vals[1], &vals[3]); + + SSDBG( descP, + ("SOCKET", "esock_getopt_pktoptions {%d} -> " + "\r\n %T: %T" + "\r\n %T: %T" + "\r\n %T: %T" + "\r\n", descP->sock, + keys[0], vals[0], keys[1], vals[1], keys[2], vals[2]) ); + + if (haveValue) { + SSDBG( descP, + ("SOCKET", "esock_getopt_pktoptions {%d} -> " + "\r\n %T: %T" + "\r\n", descP->sock, keys[3], vals[3]) ); + } + + /* Guard against cut-and-paste errors */ + ESOCK_ASSERT( numKeys == NUM(vals) ); + + /* Make control message header map */ + ESOCK_ASSERT( MKMA(env, keys, vals, + numKeys - (haveValue ? 0 : 1), &cmsgHdr) ); + + SSDBG( descP, + ("SOCKET", "esock_getopt_pktoptions {%d} -> header processed: " + "\r\n %T" + "\r\n", descP->sock, cmsgHdr) ); + + /* And finally add it to the list... */ + TARRAY_ADD(cmsghdrs, cmsgHdr); + } + } + + SSDBG( descP, + ("SOCKET", "esock_getopt_pktoptions {%d} -> " + "cmsg headers processed when" + "\r\n TArray Size: %d" + "\r\n", descP->sock, TARRAY_SZ(cmsghdrs)) ); + + /* The tarray is populated - convert it to a list */ + TARRAY_TOLIST(cmsghdrs, env, &ePktOpts); + + result = esock_make_ok2(env, ePktOpts); + } + + FREE_BIN(&cmsgs); + + return result; +} +#endif +#endif // #ifndef __WIN32__ + + + + +/* esock_getopt_str_opt - get a string option + * + * We provide the max size of the string. This is the + * size of the buffer we allocate for the value. + * The actual size of the (read) value will be communicated + * in the valSz variable. + */ +#ifndef __WIN32__ +#if defined(USE_GETOPT_STR_OPT) +static +ERL_NIF_TERM esock_getopt_str_opt(ErlNifEnv* env, + ESockDescriptor* descP, + int level, + int opt, + int max, + BOOLEAN_T stripNUL) +{ + ERL_NIF_TERM result; + char* val = MALLOC(max); + SOCKOPTLEN_T valSz = max; + int res; + + ESOCK_ASSERT( val != NULL ); + + sys_memzero(val, max); + res = sock_getopt(descP->sock, level, opt, val, &valSz); + + if (res != 0) { + result = esock_make_error_errno(env, sock_errno()); + } else { + if (stripNUL && + valSz > 0 && + val[valSz - 1] == '\0') valSz--; + + result = esock_make_ok2(env, MKSL(env, val, valSz)); + } + FREE(val); + + return result; +} +#endif // if defined(USE_GETOPT_STR_OPT) +#endif // #ifndef __WIN32__ + + + +/* ---------------------------------------------------------------------- + * nif_sockname - get socket name + * + * Description: + * Returns the current address to which the socket is bound. + * + * Arguments: + * Socket (ref) - Points to the socket descriptor. + */ + +static +ERL_NIF_TERM nif_sockname(ErlNifEnv* env, + int argc, + const ERL_NIF_TERM argv[]) +{ + ESockDescriptor* descP; + ERL_NIF_TERM res; + + ESOCK_ASSERT( argc == 1 ); + + SGDBG( ("SOCKET", "nif_sockname -> entry with argc: %d\r\n", argc) ); + + /* Extract arguments and perform preliminary validation */ + + if (! ESOCK_GET_RESOURCE(env, argv[0], (void**) &descP)) { + return enif_make_badarg(env); + } + + MLOCK(descP->readMtx); + + SSDBG( descP, + ("SOCKET", "nif_sockname(%T) {%d}" + "\r\n", argv[0], descP->sock) ); + + res = ESOCK_IO_SOCKNAME(env, descP); + + SSDBG( descP, + ("SOCKET", "nif_sockname(%T) {%d} -> done with res = %T\r\n", + argv[0], descP->sock, res) ); + + MUNLOCK(descP->readMtx); + + return res; +} + + +/* ======================================================================== + */ + +static +ERL_NIF_TERM esock_sockname(ErlNifEnv* env, + ESockDescriptor* descP) +{ + ESockAddress sa; + ESockAddress* saP = &sa; +#ifdef __WIN32__ + int sz = sizeof(ESockAddress); +#else + SOCKLEN_T sz = sizeof(ESockAddress); +#endif + + if (! IS_OPEN(descP->readState)) + return esock_make_error_closed(env); + + SSDBG( descP, + ("SOCKET", "esock_sockname {%d} -> open - try get sockname\r\n", + descP->sock) ); + + sys_memzero((char*) saP, sz); + if (sock_name(descP->sock, (struct sockaddr*) saP, &sz) < 0) { + return esock_make_error_errno(env, sock_errno()); + } else { + ERL_NIF_TERM esa; + + SSDBG( descP, + ("SOCKET", "esock_sockname {%d} -> " + "got sockname - try decode\r\n", + descP->sock) ); + + esock_encode_sockaddr(env, saP, (SOCKLEN_T) sz, &esa); + + SSDBG( descP, + ("SOCKET", "esock_sockname {%d} -> decoded: " + "\r\n %T\r\n", + descP->sock, esa) ); + + return esock_make_ok2(env, esa); + } +} + + + +/* ---------------------------------------------------------------------- + * nif_socknames - Get socket name(s) + * + * Description: + * Returns the current (local) addresses to which the AssocId of the + * socket is bound. + * + * Arguments: + * Socket (ref) - Points to the socket descriptor. + * AssocId (int) - Association Id. + */ + +static +ERL_NIF_TERM nif_socknames(ErlNifEnv* env, + int argc, + const ERL_NIF_TERM argv[]) +{ +#if defined(HAVE_SCTP) + ESockDescriptor* descP; + ERL_NIF_TERM sockRef, eAssocId, res; + sctp_assoc_t assocId; + + ESOCK_ASSERT( argc == 2 ); + + SGDBG( ("SOCKET", "nif_socknames -> entry with argc: %d\r\n", argc) ); + + /* Extract arguments and perform preliminary validation */ + + sockRef = argv[0]; + if (! ESOCK_GET_RESOURCE(env, sockRef, (void**) &descP)) { + return enif_make_badarg(env); + } + eAssocId = argv[1]; + + SSDBG( descP, + ("SOCKET", "nif_socknames(%T, %d, 0x%X, 0x%X) ->" + "\r\n AssocId: %T" + "\r\n", + sockRef, descP->sock, + descP->readState, descP->writeState, eAssocId) ); + + if (! decode_sctp_assoc_t(env, eAssocId, &assocId)) { + if (IS_INTEGER(env, eAssocId)) + return esock_make_error_integer_range(env, eAssocId); + else + return enif_make_badarg(env); + } + + MLOCK(descP->readMtx); + + res = ESOCK_IO_SOCKNAMES(env, descP, assocId); + + SSDBG( descP, + ("SOCKET", "nif_socknames(%T) {%d} -> done with res = %T\r\n", + sockRef, descP->sock, res) ); + + MUNLOCK(descP->readMtx); + + return res; +#else + return enif_raise_exception(env, MKA(env, "notsup")); +#endif +} + + + +/* ---------------------------------------------------------------------- + * nif_peername - get name of the connected peer socket + * + * Description: + * Returns the address of the peer connected to the socket. + * + * Arguments: + * Socket (ref) - Points to the socket descriptor. + */ + +static +ERL_NIF_TERM nif_peername(ErlNifEnv* env, + int argc, + const ERL_NIF_TERM argv[]) +{ + ESockDescriptor* descP; + ERL_NIF_TERM res; + + ESOCK_ASSERT( argc == 1 ); + + SGDBG( ("SOCKET", "nif_peername -> entry with argc: %d\r\n", argc) ); + + /* Extract arguments and perform preliminary validation */ + + if (! ESOCK_GET_RESOURCE(env, argv[0], (void**) &descP)) { + return enif_make_badarg(env); + } + + MLOCK(descP->readMtx); + + SSDBG( descP, + ("SOCKET", "nif_peername(%T) {%d}" + "\r\n", argv[0], descP->sock) ); + + res = ESOCK_IO_PEERNAME(env, descP); + + SSDBG( descP, + ("SOCKET", "nif_peername(%T) {%d} -> done with res = %T\r\n", + argv[0], descP->sock, res) ); + + MUNLOCK(descP->readMtx); + + return res; +} + + + +/* ======================================================================== + */ + +static +ERL_NIF_TERM esock_peername(ErlNifEnv* env, + ESockDescriptor* descP) +{ + ESockAddress sa; + ESockAddress* saP = &sa; +#ifdef __WIN32__ + int sz = sizeof(ESockAddress); +#else + SOCKLEN_T sz = sizeof(ESockAddress); +#endif + + if (! IS_OPEN(descP->readState)) + return esock_make_error_closed(env); + + SSDBG( descP, + ("SOCKET", "esock_peername {%d} -> open - try get peername (%d)\r\n", + descP->sock, sz) ); + + sys_memzero((char*) saP, sz); + if (sock_peer(descP->sock, (struct sockaddr*) saP, &sz) < 0) { + return esock_make_error_errno(env, sock_errno()); + } else { + ERL_NIF_TERM esa; + + SSDBG( descP, + ("SOCKET", "esock_peername {%d} -> " + "got peername (%d) - try decode\r\n", + descP->sock, sz) ); + + esock_encode_sockaddr(env, saP, (SOCKLEN_T) sz, &esa); + + SSDBG( descP, + ("SOCKET", "esock_peername {%d} -> decoded: " + "\r\n %T\r\n", + descP->sock, esa) ); + + return esock_make_ok2(env, esa); + } +} + + + +/* ---------------------------------------------------------------------- + * nif_peernames - socket addresses of the other end of a connection + * + * Description: + * Returns the current remote addresses to which the AssocId of the + * socket is bound. + * + * Arguments: + * Socket (ref) - Points to the socket descriptor. + * AssocId (int) - Association Id. + */ + +static +ERL_NIF_TERM nif_peernames(ErlNifEnv* env, + int argc, + const ERL_NIF_TERM argv[]) +{ +#if defined(HAVE_SCTP) + ESockDescriptor* descP; + ERL_NIF_TERM sockRef, eAssocId, res; + sctp_assoc_t assocId; + + ESOCK_ASSERT( argc == 2 ); + + SGDBG( ("SOCKET", "nif_peernames -> entry with argc: %d\r\n", argc) ); + + /* Extract arguments and perform preliminary validation */ + + sockRef = argv[0]; + if (! ESOCK_GET_RESOURCE(env, sockRef, (void**) &descP)) { + return enif_make_badarg(env); + } + eAssocId = argv[1]; + + SSDBG( descP, + ("SOCKET", "nif_peernames(%T, %d, 0x%X, 0x%X) ->" + "\r\n AssocId: %T" + "\r\n", + sockRef, descP->sock, + descP->readState, descP->writeState, eAssocId) ); + + if (! decode_sctp_assoc_t(env, eAssocId, &assocId)) { + if (IS_INTEGER(env, eAssocId)) + return esock_make_error_integer_range(env, eAssocId); + else + return enif_make_badarg(env); + } + + MLOCK(descP->readMtx); + + res = ESOCK_IO_PEERNAMES(env, descP, assocId); + + SSDBG( descP, + ("SOCKET", "nif_peernames(%T) {%d} -> done with res = %T\r\n", + sockRef, descP->sock, res) ); + + MUNLOCK(descP->readMtx); + + return res; +#else + return enif_raise_exception(env, MKA(env, "notsup")); +#endif +} + + + +/* ---------------------------------------------------------------------- + * nif_ioctl - control device - get + * + * Description: + * Returns whatever info the ioctl returns for the specific (get) request. + * WHEN SET IS IMPLEMENTED, WE NED ANOTHER ARGUMENT!! + * + * Arguments: + * Socket (ref) - Points to the socket descriptor. + * Request - The ioctl get/set request + * NameOrIdx - Name or Index of the interface - only for some requests + * Currently only one (get) request does not provide this + * (and the value) argument; gifconf + * Currently, only one (get) request use index; gifname + * All other requests (get and set) use Name as "key". + * Val - Value to *set* + * This argument is *only* provided for set requests. + */ + +static +ERL_NIF_TERM nif_ioctl(ErlNifEnv* env, + int argc, + const ERL_NIF_TERM argv[]) +{ + ESockDescriptor* descP; + ERL_NIF_TERM res; + unsigned long req; + + SGDBG( ("SOCKET", "nif_ioctl -> entry with argc: %d\r\n", argc) ); + + ESOCK_ASSERT( (argc == 2) || (argc == 3) || (argc == 4) ); + + if (! ESOCK_GET_RESOURCE(env, argv[0], (void**) &descP)) { + SGDBG( ("SOCKET", "nif_ioctl -> no resource\r\n") ); + return enif_make_badarg(env); + } + + if (! GET_ULONG(env, argv[1], &req)) { + SGDBG( ("SOCKET", "nif_ioctl -> 'request' not 'unsigned long'\r\n") ); + return enif_make_badarg(env); + } + + SSDBG( descP, + ("SOCKET", "nif_ioctl(%T) {%d} -> ioctl request %d" + "\r\n", argv[0], descP->sock, req) ); + + /* Is this really enough? Why not the write mutex also? */ + MLOCK(descP->readMtx); + + if (! IS_OPEN(descP->readState)) { + res = esock_make_error_closed(env); + } else { + + switch (argc) { + case 2: + /* Only one request with this number of arguments: gifconf + * Socket and request (=gifconf) + */ + + /* Two arguments: socket and request */ + res = ESOCK_IO_IOCTL_2(env, descP, req); + break; + + case 3: + /* (Currently) All *other* get requests has 3 arguments + * Socket, request and name/index + */ + { + ERL_NIF_TERM earg = argv[2]; + + /* Three arguments: socket, request and arg */ + res = ESOCK_IO_IOCTL_3(env, descP, req, earg); + } + break; + + case 4: + /* (Currently) Set requests has 4 arguments + * Socket, request, name and value + */ + { + ERL_NIF_TERM earg1 = argv[2]; // (currently) Name + ERL_NIF_TERM earg2 = argv[3]; // Value + + res = ESOCK_IO_IOCTL_4(env, descP, req, earg1, earg2); + } + break; + + default: + /* This is just to protect against programming errors, + * since we have an assert above! + */ + res = esock_make_error(env, esock_atom_einval); + break; + } + } + + MUNLOCK(descP->readMtx); + + SSDBG( descP, + ("SOCKET", "nif_ioctl(%T) {%d} -> done with res = %T\r\n", + argv[0], descP->sock, res) ); + + return res; +} + + + + +/* ---------------------------------------------------------------------- + * nif_cancel + * + * Description: + * Cancel a previous select! + * + * Arguments: + * Socket (ref) - Points to the socket descriptor. + * Operation (atom) - What kind of operation (accept, send, ...) + * is to be cancelled + * Ref (ref) - Unique id for the operation + */ +static +ERL_NIF_TERM nif_cancel(ErlNifEnv* env, + int argc, + const ERL_NIF_TERM argv[]) +{ + ESockDescriptor* descP; + ERL_NIF_TERM op, sockRef, opRef; + + ESOCK_ASSERT( argc == 3 ); + + SGDBG( ("SOCKET", "nif_cancel -> entry with argc: %d\r\n", argc) ); + + /* Extract arguments and perform preliminary validation */ + + sockRef = argv[0]; + if (! ESOCK_GET_RESOURCE(env, sockRef, (void**) &descP)) { + return enif_make_badarg(env); + } + op = argv[1]; + opRef = argv[2]; + if ((! IS_ATOM(env, op)) || + (! enif_is_ref(env, opRef))) { + return enif_make_badarg(env); + } + + return esock_cancel(env, descP, op, sockRef, opRef); + +} + + +static +ERL_NIF_TERM esock_cancel(ErlNifEnv* env, + ESockDescriptor* descP, + ERL_NIF_TERM op, + ERL_NIF_TERM sockRef, + ERL_NIF_TERM opRef) +{ + ERL_NIF_TERM result; + int cmp; + + /* + * + * Do we really need all these variants? Should it not be enough with: + * + * connect | accept | send | recv + * + * + */ + + /* Hand crafted binary search */ + if ((cmp = COMPARE(op, esock_atom_recvmsg)) == 0) { + MLOCK(descP->readMtx); + result = ESOCK_IO_CANCEL_RECV(env, descP, sockRef, opRef); + MUNLOCK(descP->readMtx); + return result; + } + if (cmp < 0) { + if ((cmp = COMPARE(op, esock_atom_recv)) == 0) { + MLOCK(descP->readMtx); + result = ESOCK_IO_CANCEL_RECV(env, descP, sockRef, opRef); + MUNLOCK(descP->readMtx); + return result; + } + if (cmp < 0) { + if (COMPARE(op, esock_atom_connect) == 0) { + MLOCK(descP->writeMtx); + result = ESOCK_IO_CANCEL_CONNECT(env, descP, opRef); + MUNLOCK(descP->writeMtx); + return result; + } + if (COMPARE(op, esock_atom_accept) == 0) { + MLOCK(descP->readMtx); + result = ESOCK_IO_CANCEL_ACCEPT(env, descP, sockRef, opRef); + MUNLOCK(descP->readMtx); + return result; + } + } else { + if (COMPARE(op, esock_atom_recvfrom) == 0) { + MLOCK(descP->readMtx); + result = ESOCK_IO_CANCEL_RECV(env, descP, sockRef, opRef); + MUNLOCK(descP->readMtx); + return result; + } + } + } else { + if ((cmp = COMPARE(op, esock_atom_sendmsg)) == 0) { + MLOCK(descP->writeMtx); + result = ESOCK_IO_CANCEL_SEND(env, descP, sockRef, opRef); + MUNLOCK(descP->writeMtx); + return result; + } + if (cmp < 0) { + if (COMPARE(op, esock_atom_send) == 0) { + MLOCK(descP->writeMtx); + result = ESOCK_IO_CANCEL_SEND(env, descP, sockRef, opRef); + MUNLOCK(descP->writeMtx); + return result; + } + if (COMPARE(op, esock_atom_sendfile) == 0) { + MLOCK(descP->writeMtx); + result = ESOCK_IO_CANCEL_SEND(env, descP, sockRef, opRef); + MUNLOCK(descP->writeMtx); + return result; + } + } else { + if (COMPARE(op, esock_atom_sendto) == 0) { + MLOCK(descP->writeMtx); + result = ESOCK_IO_CANCEL_SEND(env, descP, sockRef, opRef); + MUNLOCK(descP->writeMtx); + return result; + } + if (COMPARE(op, esock_atom_sendv) == 0) { + MLOCK(descP->writeMtx); + result = ESOCK_IO_CANCEL_SEND(env, descP, sockRef, opRef); + MUNLOCK(descP->writeMtx); + return result; + } + } + } + + { + const char *reason; + + MLOCK(descP->readMtx); + MLOCK(descP->writeMtx); + + if (! IS_OPEN(descP->readState)) { + result = esock_make_error_closed(env); + reason = "closed"; + } else { + result = enif_make_badarg(env); + reason = "badarg"; + } + + SSDBG( descP, + ("SOCKET", "esock_cancel(%T), {%d,0x%X} -> %s" + "\r\n", + sockRef, descP->sock, + descP->readState | descP->writeState, reason) ); + + MUNLOCK(descP->writeMtx); + MUNLOCK(descP->readMtx); + + return result; + } +} + + +#ifndef __WIN32__ +extern +ERL_NIF_TERM esock_cancel_read_select(ErlNifEnv* env, + ESockDescriptor* descP, + ERL_NIF_TERM opRef) +{ + return esock_cancel_mode_select(env, descP, opRef, + ERL_NIF_SELECT_READ, + ERL_NIF_SELECT_READ_CANCELLED); +} +#endif // #ifndef __WIN32__ + + +#ifndef __WIN32__ +extern +ERL_NIF_TERM esock_cancel_write_select(ErlNifEnv* env, + ESockDescriptor* descP, + ERL_NIF_TERM opRef) +{ + return esock_cancel_mode_select(env, descP, opRef, + ERL_NIF_SELECT_WRITE, + ERL_NIF_SELECT_WRITE_CANCELLED); +} +#endif // #ifndef __WIN32__ + + +#ifndef __WIN32__ +extern +ERL_NIF_TERM esock_cancel_mode_select(ErlNifEnv* env, + ESockDescriptor* descP, + ERL_NIF_TERM opRef, + int smode, + int rmode) +{ + /* Assumes cancelling only one mode */ - result = esock_make_ok2(env, ePktOpts); - } + int selectRes = esock_select_cancel(env, descP->sock, smode, descP); - FREE_BIN(&cmsgs); + if (selectRes >= 0) { + /* Success */ + if ((selectRes & rmode) != 0) { + /* Was cancelled */ + return esock_atom_ok; + } else { + /* Has already sent the message */ + return esock_atom_select_sent; + } + } else { + /* Stopped? */ + SSDBG( descP, + ("SOCKET", + "esock_cancel_mode_select {%d} -> failed: %d (0x%lX)" + "\r\n", descP->sock, selectRes, selectRes) ); - return result; + return esock_atom_not_found; + } } -#endif #endif // #ifndef __WIN32__ - -/* esock_getopt_str_opt - get a string option - * - * We provide the max size of the string. This is the - * size of the buffer we allocate for the value. - * The actual size of the (read) value will be communicated - * in the valSz variable. +/* ---------------------------------------------------------------------- + * U t i l i t y F u n c t i o n s + * ---------------------------------------------------------------------- */ -#ifndef __WIN32__ -#if defined(USE_GETOPT_STR_OPT) -static -ERL_NIF_TERM esock_getopt_str_opt(ErlNifEnv* env, - ESockDescriptor* descP, - int level, - int opt, - int max, - BOOLEAN_T stripNUL) + +extern +BOOLEAN_T esock_encode_cmsg(ErlNifEnv* env, + int level, + int type, + unsigned char* dataP, + size_t dataLen, + ERL_NIF_TERM* eType, + ERL_NIF_TERM* eData) { - ERL_NIF_TERM result; - char* val = MALLOC(max); - SOCKOPTLEN_T valSz = max; - int res; + const ESockCmsgSpec *cmsgTable; + size_t num; - ESOCK_ASSERT( val != NULL ); + if ((cmsgTable = esock_lookup_cmsg_table(level, &num)) != NULL) { + size_t n; - sys_memzero(val, max); - res = sock_getopt(descP->sock, level, opt, val, &valSz); + /* Linear search for type number in level table + */ + for (n = 0; n < num; n++) { + if (cmsgTable[n].type == type) { + /* Found the type number in the level table; + * return the symbolic type (atom) + * and try to encode the data + */ - if (res != 0) { - result = esock_make_error_errno(env, sock_errno()); - } else { - if (stripNUL && - valSz > 0 && - val[valSz - 1] == '\0') valSz--; + *eType = *cmsgTable[n].nameP; - result = esock_make_ok2(env, MKSL(env, val, valSz)); + if (cmsgTable[n].encode != NULL) + return cmsgTable[n].encode(env, dataP, dataLen, eData); + else + return FALSE; + } + } } - FREE(val); + /* No level table, or unknown type number in the table; + * just return the type number as an erlang integer + */ - return result; -} -#endif // if defined(USE_GETOPT_STR_OPT) -#endif // #ifndef __WIN32__ + *eType = MKI(env, type); + return FALSE; +} -/* ---------------------------------------------------------------------- - * nif_sockname - get socket name +/* +++ esock_encode_msg_flags +++ * - * Description: - * Returns the current address to which the socket is bound. + * Encode a list of msg_flag(). * - * Arguments: - * Socket (ref) - Points to the socket descriptor. */ -static -ERL_NIF_TERM nif_sockname(ErlNifEnv* env, - int argc, - const ERL_NIF_TERM argv[]) +extern +void esock_encode_msg_flags(ErlNifEnv* env, + ESockDescriptor* descP, + int msgFlags, + ERL_NIF_TERM* flags) { - ESockDescriptor* descP; - ERL_NIF_TERM res; + SSDBG( descP, + ("SOCKET", "encode_msg_flags {%d} -> entry with" + "\r\n msgFlags: %d (0x%lX)" + "\r\n", descP->sock, msgFlags, msgFlags) ); - ESOCK_ASSERT( argc == 1 ); + if (msgFlags == 0) { + *flags = MKEL(env); + } else { + size_t n; + SocketTArray ta = TARRAY_CREATE(10); // Just to be on the safe side - SGDBG( ("SOCKET", "nif_sockname -> entry with argc: %d\r\n", argc) ); + for (n = 0; n < esock_msg_flags_length; n++) { + int f = esock_msg_flags[n].flag; + if ((f != 0) && ((msgFlags & f) == f)) { + msgFlags &= ~f; + TARRAY_ADD(ta, *(esock_msg_flags[n].name)); + } + if (msgFlags == 0) goto done; + } + /* Append remaining flags as an integer */ + if (msgFlags != 0) + TARRAY_ADD(ta, MKI(env, msgFlags)); - /* Extract arguments and perform preliminary validation */ + done: + SSDBG( descP, + ("SOCKET", "encode_msg_flags {%d} -> flags processed when" + "\r\n TArray size: %d" + "\r\n", descP->sock, TARRAY_SZ(ta)) ); - if (! ESOCK_GET_RESOURCE(env, argv[0], (void**) &descP)) { - return enif_make_badarg(env); + TARRAY_TOLIST(ta, env, flags); } +} - MLOCK(descP->readMtx); - SSDBG( descP, - ("SOCKET", "nif_sockname(%T) {%d}" - "\r\n", argv[0], descP->sock) ); +#ifdef SCM_TIMESTAMP +static +BOOLEAN_T esock_cmsg_encode_timeval(ErlNifEnv *env, + unsigned char *data, + size_t dataLen, + ERL_NIF_TERM *eResult) { + struct timeval* timeP = (struct timeval *) data; - res = ESOCK_IO_SOCKNAME(env, descP); + if (dataLen < sizeof(*timeP)) + return FALSE; - SSDBG( descP, - ("SOCKET", "nif_sockname(%T) {%d} -> done with res = %T\r\n", - argv[0], descP->sock, res) ); + esock_encode_timeval(env, timeP, eResult); + return TRUE; +} - MUNLOCK(descP->readMtx); +static BOOLEAN_T esock_cmsg_decode_timeval(ErlNifEnv *env, + ERL_NIF_TERM eValue, + struct cmsghdr *cmsgP, + size_t rem, + size_t *usedP) +{ + struct timeval time, *timeP; - return res; -} + if (! esock_decode_timeval(env, eValue, &time)) + return FALSE; + if ((timeP = esock_init_cmsghdr(cmsgP, rem, sizeof(*timeP), usedP)) == NULL) + return FALSE; + *timeP = time; + return TRUE; +} +#endif -/* ======================================================================== - */ +#if defined(IP_TOS) || defined(IP_RECVTOS) static -ERL_NIF_TERM esock_sockname(ErlNifEnv* env, - ESockDescriptor* descP) +BOOLEAN_T esock_cmsg_encode_ip_tos(ErlNifEnv *env, + unsigned char *data, + size_t dataLen, + ERL_NIF_TERM *eResult) { - ESockAddress sa; - ESockAddress* saP = &sa; -#ifdef __WIN32__ - int sz = sizeof(ESockAddress); -#else - SOCKLEN_T sz = sizeof(ESockAddress); -#endif + unsigned char tos; - if (! IS_OPEN(descP->readState)) - return esock_make_error_closed(env); - - SSDBG( descP, - ("SOCKET", "esock_sockname {%d} -> open - try get sockname\r\n", - descP->sock) ); + if (dataLen < sizeof(tos)) + return FALSE; - sys_memzero((char*) saP, sz); - if (sock_name(descP->sock, (struct sockaddr*) saP, &sz) < 0) { - return esock_make_error_errno(env, sock_errno()); - } else { - ERL_NIF_TERM esa; + tos = *data; - SSDBG( descP, - ("SOCKET", "esock_sockname {%d} -> " - "got sockname - try decode\r\n", - descP->sock) ); + *eResult = encode_ip_tos(env, tos); + return TRUE; +} - esock_encode_sockaddr(env, saP, (SOCKLEN_T) sz, &esa); +static BOOLEAN_T esock_cmsg_decode_ip_tos(ErlNifEnv *env, + ERL_NIF_TERM eValue, + struct cmsghdr *cmsgP, + size_t rem, + size_t *usedP) +{ + int tos, *tosP; - SSDBG( descP, - ("SOCKET", "esock_sockname {%d} -> decoded: " - "\r\n %T\r\n", - descP->sock, esa) ); + if (! decode_ip_tos(env, eValue, &tos)) + return FALSE; - return esock_make_ok2(env, esa); - } + if ((tosP = esock_init_cmsghdr(cmsgP, rem, sizeof(*tosP), usedP)) == NULL) + return FALSE; + + *tosP = tos; + return TRUE; } +#endif // #ifdef IP_TOS +#if defined(IP_TTL) || \ + defined(IPV6_HOPLIMIT) || \ + defined(IPV6_TCLASS) || defined(IPV6_RECVTCLASS) +static +BOOLEAN_T esock_cmsg_encode_int(ErlNifEnv *env, + unsigned char *data, + size_t dataLen, + ERL_NIF_TERM *eResult) { + int value; + + if (dataLen < sizeof(value)) + return FALSE; -/* ---------------------------------------------------------------------- - * nif_peername - get name of the connected peer socket - * - * Description: - * Returns the address of the peer connected to the socket. - * - * Arguments: - * Socket (ref) - Points to the socket descriptor. - */ + value = *((int *) data); + *eResult = MKI(env, value); + return TRUE; +} -static -ERL_NIF_TERM nif_peername(ErlNifEnv* env, - int argc, - const ERL_NIF_TERM argv[]) +extern +BOOLEAN_T esock_cmsg_decode_int(ErlNifEnv* env, + ERL_NIF_TERM eValue, + struct cmsghdr* cmsgP, + size_t rem, + size_t* usedP) { - ESockDescriptor* descP; - ERL_NIF_TERM res; + int value, *valueP; - ESOCK_ASSERT( argc == 1 ); + if (! GET_INT(env, eValue, &value)) + return FALSE; - SGDBG( ("SOCKET", "nif_peername -> entry with argc: %d\r\n", argc) ); + valueP = esock_init_cmsghdr(cmsgP, rem, sizeof(*valueP), usedP); + if (valueP == NULL) + return FALSE; - /* Extract arguments and perform preliminary validation */ + *valueP = value; + return TRUE; +} +#endif - if (! ESOCK_GET_RESOURCE(env, argv[0], (void**) &descP)) { - return enif_make_badarg(env); - } - MLOCK(descP->readMtx); +extern +BOOLEAN_T esock_cmsg_decode_bool(ErlNifEnv* env, + ERL_NIF_TERM eValue, + struct cmsghdr* cmsgP, + size_t rem, + size_t* usedP) +{ + BOOLEAN_T v; + int* valueP; - SSDBG( descP, - ("SOCKET", "nif_peername(%T) {%d}" - "\r\n", argv[0], descP->sock) ); + if (! esock_decode_bool(eValue, &v)) + return FALSE; - res = ESOCK_IO_PEERNAME(env, descP); + if ((valueP = esock_init_cmsghdr(cmsgP, rem, + sizeof(*valueP), usedP)) == NULL) + return FALSE; - SSDBG( descP, - ("SOCKET", "nif_peername(%T) {%d} -> done with res = %T\r\n", - argv[0], descP->sock, res) ); + *valueP = v? 1 : 0; + return TRUE; +} - MUNLOCK(descP->readMtx); - return res; -} +#ifdef IP_RECVTTL +static +BOOLEAN_T esock_cmsg_encode_uchar(ErlNifEnv *env, + unsigned char *data, + size_t dataLen, + ERL_NIF_TERM *eResult) { + unsigned char value; + if (dataLen < sizeof(value)) + return FALSE; + value = *data; + *eResult = MKUI(env, value); + return TRUE; +} +#endif -/* ======================================================================== - */ +#ifdef IP_PKTINFO static -ERL_NIF_TERM esock_peername(ErlNifEnv* env, - ESockDescriptor* descP) +BOOLEAN_T esock_cmsg_encode_in_pktinfo(ErlNifEnv *env, + unsigned char *data, + size_t dataLen, + ERL_NIF_TERM *eResult) { - ESockAddress sa; - ESockAddress* saP = &sa; -#ifdef __WIN32__ - int sz = sizeof(ESockAddress); + struct in_pktinfo* pktInfoP = (struct in_pktinfo*) data; + ERL_NIF_TERM ifIndex; + ERL_NIF_TERM specDst, addr; + + if (dataLen < sizeof(*pktInfoP)) + return FALSE; + + ifIndex = MKUI(env, pktInfoP->ipi_ifindex); +#ifndef __WIN32__ + /* On Windows, the field ipi_spec_dst does not exist */ + esock_encode_in_addr(env, &pktInfoP->ipi_spec_dst, &specDst); +#endif + esock_encode_in_addr(env, &pktInfoP->ipi_addr, &addr); + + { + ERL_NIF_TERM keys[] = {esock_atom_ifindex, + esock_atom_spec_dst, + esock_atom_addr}; + ERL_NIF_TERM vals[] = {ifIndex, +#ifndef __WIN32__ + specDst, #else - SOCKLEN_T sz = sizeof(ESockAddress); + esock_atom_undefined, #endif + addr}; + unsigned int numKeys = NUM(keys); + unsigned int numVals = NUM(vals); - if (! IS_OPEN(descP->readState)) - return esock_make_error_closed(env); + ESOCK_ASSERT( numKeys == numVals ); + ESOCK_ASSERT( MKMA(env, keys, vals, numKeys, eResult) ); + } + return TRUE; +} +#endif - SSDBG( descP, - ("SOCKET", "esock_peername {%d} -> open - try get peername (%d)\r\n", - descP->sock, sz) ); - sys_memzero((char*) saP, sz); - if (sock_peer(descP->sock, (struct sockaddr*) saP, &sz) < 0) { - return esock_make_error_errno(env, sock_errno()); - } else { - ERL_NIF_TERM esa; +#if defined(HAVE_SCTP) - SSDBG( descP, - ("SOCKET", "esock_peername {%d} -> " - "got peername (%d) - try decode\r\n", - descP->sock, sz) ); +#if defined(SCTP_INIT) +/* Do we actually need this function since this info is for sendmsg: decode */ +static +BOOLEAN_T esock_cmsg_encode_sctp_init(ErlNifEnv *env, + unsigned char *data, + size_t dataLen, + ERL_NIF_TERM *eResult) +{ + struct sctp_initmsg* initMsgP = (struct sctp_initmsg*) data; + ERL_NIF_TERM numOStreams, maxInStreams, maxAttempts, maxInitTimeO; - esock_encode_sockaddr(env, saP, (SOCKLEN_T) sz, &esa); + if (dataLen < sizeof(struct sctp_initmsg)) { - SSDBG( descP, - ("SOCKET", "esock_peername {%d} -> decoded: " - "\r\n %T\r\n", - descP->sock, esa) ); + *eResult = esock_atom_undefined; - return esock_make_ok2(env, esa); - } -} + return FALSE; + } + + numOStreams = MKUI(env, initMsgP->sinit_num_ostreams); + maxInStreams = MKUI(env, initMsgP->sinit_max_instreams); + maxAttempts = MKUI(env, initMsgP->sinit_max_attempts); + maxInitTimeO = MKUI(env, initMsgP->sinit_max_init_timeo); + { + ERL_NIF_TERM keys[] = {esock_atom_esock_name, + esock_atom_num_ostreams, + esock_atom_max_instreams, + esock_atom_max_attempts, + esock_atom_max_init_timeo}; + ERL_NIF_TERM vals[] = {MKA(env, "sctp_initmsg"), + numOStreams, + maxInStreams, + maxAttempts, + maxInitTimeO}; + unsigned int numKeys = NUM(keys); + unsigned int numVals = NUM(vals); + ESOCK_ASSERT( numKeys == numVals ); + ESOCK_ASSERT( MKMA(env, keys, vals, numKeys, eResult) ); + + } -/* ---------------------------------------------------------------------- - * nif_ioctl - control device - get - * - * Description: - * Returns whatever info the ioctl returns for the specific (get) request. - * WHEN SET IS IMPLEMENTED, WE NED ANOTHER ARGUMENT!! - * - * Arguments: - * Socket (ref) - Points to the socket descriptor. - * Request - The ioctl get/set request - * NameOrIdx - Name or Index of the interface - only for some requests - * Currently only one (get) request does not provide this - * (and the value) argument; gifconf - * Currently, only one (get) request use index; gifname - * All other requests (get and set) use Name as "key". - * Val - Value to *set* - * This argument is *only* provided for set requests. - */ + return TRUE; +} static -ERL_NIF_TERM nif_ioctl(ErlNifEnv* env, - int argc, - const ERL_NIF_TERM argv[]) +BOOLEAN_T esock_cmsg_decode_sctp_init(ErlNifEnv* env, + ERL_NIF_TERM eValue, + struct cmsghdr* cmsgP, + size_t rem, + size_t* usedP) { - ESockDescriptor* descP; - ERL_NIF_TERM res; - unsigned long req; + struct sctp_initmsg initMsg, *initMsgP; + ERL_NIF_TERM eNumOStreams, eMaxInStreams; + ERL_NIF_TERM eMaxAttempts, eMaxInitTimeO; - SGDBG( ("SOCKET", "nif_ioctl -> entry with argc: %d\r\n", argc) ); + /* Number of Out Streams */ + if (! GET_MAP_VAL(env, eValue, esock_atom_num_ostreams, &eNumOStreams)) + return FALSE; + if (! GET_UINT(env, eNumOStreams, (unsigned int*) &initMsg.sinit_num_ostreams)) + return FALSE; - ESOCK_ASSERT( (argc == 2) || (argc == 3) || (argc == 4) ); + /* Max In Streams */ + if (! GET_MAP_VAL(env, eValue, esock_atom_max_instreams, &eMaxInStreams)) + return FALSE; + if (! GET_UINT(env, eMaxInStreams, (unsigned int*) &initMsg.sinit_max_instreams)) + return FALSE; - if (! ESOCK_GET_RESOURCE(env, argv[0], (void**) &descP)) { - SGDBG( ("SOCKET", "nif_ioctl -> no resource\r\n") ); - return enif_make_badarg(env); - } + /* Max Attempts */ + if (! GET_MAP_VAL(env, eValue, esock_atom_max_attempts, &eMaxAttempts)) + return FALSE; + if (! GET_UINT(env, eMaxAttempts, (unsigned int*) &initMsg.sinit_max_attempts)) + return FALSE; - if (! GET_ULONG(env, argv[1], &req)) { - SGDBG( ("SOCKET", "nif_ioctl -> 'request' not 'unsigned long'\r\n") ); - return enif_make_badarg(env); - } + /* Max init TimeOuts */ + if (! GET_MAP_VAL(env, eValue, esock_atom_max_init_timeo, &eMaxInitTimeO)) + return FALSE; + if (! GET_UINT(env, eMaxInitTimeO, (unsigned int*) &initMsg.sinit_max_init_timeo)) + return FALSE; - SSDBG( descP, - ("SOCKET", "nif_ioctl(%T) {%d} -> ioctl request %d" - "\r\n", argv[0], descP->sock, req) ); + if ((initMsgP = esock_init_cmsghdr(cmsgP, rem, sizeof(*initMsgP), usedP)) == NULL) + return FALSE; - /* Is this really enough? Why not the write mutex also? */ - MLOCK(descP->readMtx); + *initMsgP = initMsg; - if (! IS_OPEN(descP->readState)) { - res = esock_make_error_closed(env); - } else { + return TRUE; +} +#endif - switch (argc) { - case 2: - /* Only one request with this number of arguments: gifconf - * Socket and request (=gifconf) - */ +#if defined(SCTP_SNDRCV) +static +ERL_NIF_TERM esock_cmsg_encode_sctp_sndrcv_flags(ErlNifEnv* env, + unsigned int flags) +{ + SocketTArray ta = TARRAY_CREATE(4); + ERL_NIF_TERM eflags; - /* Two arguments: socket and request */ - res = ESOCK_IO_IOCTL_2(env, descP, req); - break; +#if defined(SCTP_UNORDERED) + if (flags & SCTP_UNORDERED) + TARRAY_ADD(ta, esock_atom_unordered); +#endif - case 3: - /* (Currently) All *other* get requests has 3 arguments - * Socket, request and name/index - */ - { - ERL_NIF_TERM earg = argv[2]; +#if defined(SCTP_ADDR_OVER) + if (flags & SCTP_ADDR_OVER) + TARRAY_ADD(ta, esock_atom_addr_over); +#endif - /* Three arguments: socket, request and arg */ - res = ESOCK_IO_IOCTL_3(env, descP, req, earg); - } - break; +#if defined(SCTP_ABORT) + if (flags & SCTP_ABORT) + TARRAY_ADD(ta, esock_atom_abort); +#endif - case 4: - /* (Currently) Set requests has 4 arguments - * Socket, request, name and value - */ - { - ERL_NIF_TERM earg1 = argv[2]; // (currently) Name - ERL_NIF_TERM earg2 = argv[3]; // Value +#if defined(SCTP_EOF) + if (flags & SCTP_EOF) + TARRAY_ADD(ta, esock_atom_eof); +#endif + + TARRAY_TOLIST(ta, env, &eflags); + + return eflags; +} - res = ESOCK_IO_IOCTL_4(env, descP, req, earg1, earg2); - } - break; +extern +BOOLEAN_T esock_cmsg_encode_sctp_sndrcv(ErlNifEnv *env, + unsigned char *data, + size_t dataLen, + ERL_NIF_TERM *eResult) +{ + struct sctp_sndrcvinfo* infoP = (struct sctp_sndrcvinfo*) data; + ERL_NIF_TERM stream, ssn, flags, ppid, context, + time_to_live, tsn, cum_tsn, assocId; - default: - /* This is just to protect against programming errors, - * since we have an assert above! - */ - res = esock_make_error(env, esock_atom_einval); - break; - } - } + if (dataLen < sizeof(struct sctp_sndrcvinfo)) { - MUNLOCK(descP->readMtx); + *eResult = esock_atom_undefined; - SSDBG( descP, - ("SOCKET", "nif_ioctl(%T) {%d} -> done with res = %T\r\n", - argv[0], descP->sock, res) ); + return FALSE; + } - return res; -} + stream = MKUI(env, infoP->sinfo_stream); + ssn = MKUI(env, infoP->sinfo_ssn); + flags = esock_cmsg_encode_sctp_sndrcv_flags(env, + infoP->sinfo_flags); + ppid = MKUI(env, infoP->sinfo_ppid); + context = MKUI(env, infoP->sinfo_context); + time_to_live = MKUI(env, infoP->sinfo_timetolive); + tsn = MKUI(env, infoP->sinfo_tsn); + cum_tsn = MKUI(env, infoP->sinfo_cumtsn); + assocId = encode_sctp_assoc_t(env, infoP->sinfo_assoc_id); + { + ERL_NIF_TERM keys[] = {esock_atom_esock_name, + esock_atom_stream, + esock_atom_ssn, + esock_atom_flags, + esock_atom_ppid, + esock_atom_context, + esock_atom_time_to_live, + esock_atom_tsn, + esock_atom_cum_tsn, + esock_atom_assoc_id}; + ERL_NIF_TERM vals[] = {MKA(env, "sctp_sndrcvinfo"), + stream, + ssn, + flags, + ppid, + context, + time_to_live, + tsn, + cum_tsn, + assocId}; + unsigned int numKeys = NUM(keys); + unsigned int numVals = NUM(vals); + ESOCK_ASSERT( numKeys == numVals ); + ESOCK_ASSERT( MKMA(env, keys, vals, numKeys, eResult) ); + } + + return TRUE; +} -/* ---------------------------------------------------------------------- - * nif_cancel - * - * Description: - * Cancel a previous select! - * - * Arguments: - * Socket (ref) - Points to the socket descriptor. - * Operation (atom) - What kind of operation (accept, send, ...) - * is to be cancelled - * Ref (ref) - Unique id for the operation - */ static -ERL_NIF_TERM nif_cancel(ErlNifEnv* env, - int argc, - const ERL_NIF_TERM argv[]) +BOOLEAN_T esock_cmsg_decode_sctp_sndrcv_flags(ErlNifEnv* env, + ERL_NIF_TERM eflags, + uint16_t* flags) { - ESockDescriptor* descP; - ERL_NIF_TERM op, sockRef, opRef; + ERL_NIF_TERM elem, tail, list; + unsigned int i, len; + int cflags = 0; - ESOCK_ASSERT( argc == 3 ); + if (!IS_LIST(env, eflags)) + return FALSE; - SGDBG( ("SOCKET", "nif_cancel -> entry with argc: %d\r\n", argc) ); + if (IS_EMPTY_LIST(env, eflags)) { + *flags = 0; + return TRUE; + } + + if (! GET_LIST_LEN(env, eflags, &len)) + return FALSE; - /* Extract arguments and perform preliminary validation */ + for (i = 0, list = eflags; i < len; i++) { - sockRef = argv[0]; - if (! ESOCK_GET_RESOURCE(env, sockRef, (void**) &descP)) { - return enif_make_badarg(env); - } - op = argv[1]; - opRef = argv[2]; - if ((! IS_ATOM(env, op)) || - (! enif_is_ref(env, opRef))) { - return enif_make_badarg(env); + /* Extract the (current) head of the (eflags) list */ + if (! GET_LIST_ELEM(env, list, &elem, &tail)) + return FALSE; + + if (IS_IDENTICAL(elem, esock_atom_unordered)) { + cflags |= SCTP_UNORDERED; + } else if (IS_IDENTICAL(elem, esock_atom_addr_over)) { + cflags |= SCTP_ADDR_OVER; + } else if (IS_IDENTICAL(elem, esock_atom_abort)) { + cflags |= SCTP_ABORT; + } else if (IS_IDENTICAL(elem, esock_atom_eof)) { + cflags |= SCTP_EOF; + } + + list = tail; } - return esock_cancel(env, descP, op, sockRef, opRef); + *flags = cflags; + return TRUE; } - static -ERL_NIF_TERM esock_cancel(ErlNifEnv* env, - ESockDescriptor* descP, - ERL_NIF_TERM op, - ERL_NIF_TERM sockRef, - ERL_NIF_TERM opRef) +BOOLEAN_T esock_cmsg_decode_sctp_sndrcv(ErlNifEnv* env, + ERL_NIF_TERM eValue, + struct cmsghdr* cmsgP, + size_t rem, + size_t* usedP) { - ERL_NIF_TERM result; - int cmp; + struct sctp_sndrcvinfo info = {0}; + struct sctp_sndrcvinfo* infoP; + ERL_NIF_TERM tmp; + unsigned int itmp; + + /* Stream :: uint16() - mandatory (= no default value) */ + // ESOCK_PRINTF("%s -> get stream\r\n", __FUNCTION__); + if (! GET_MAP_VAL(env, eValue, esock_atom_stream, &tmp)) + return FALSE; + if (! GET_UINT(env, tmp, (unsigned int*) &info.sinfo_stream)) + return FALSE; - /* - * - * Do we really need all these variants? Should it not be enough with: - * - * connect | accept | send | recv - * - * - */ + /* SSN :: uint16() - optional (default value = 0) */ + // ESOCK_PRINTF("%s -> get ssn\r\n", __FUNCTION__); + if (! GET_MAP_VAL(env, eValue, esock_atom_ssn, &tmp)) { + info.sinfo_ssn = 0; + } else { + if (! GET_UINT(env, tmp, (unsigned int*) &info.sinfo_ssn)) + return FALSE; + } - /* Hand crafted binary search */ - if ((cmp = COMPARE(op, esock_atom_recvmsg)) == 0) { - MLOCK(descP->readMtx); - result = ESOCK_IO_CANCEL_RECV(env, descP, sockRef, opRef); - MUNLOCK(descP->readMtx); - return result; + /* Flags :: [info_flags()] - optional (default value = []) */ + // ESOCK_PRINTF("%s -> get flags\r\n", __FUNCTION__); + if (! GET_MAP_VAL(env, eValue, esock_atom_flags, &tmp)) { + info.sinfo_flags = 0; + } else { + if (!esock_cmsg_decode_sctp_sndrcv_flags(env, tmp, &info.sinfo_flags)) + return FALSE; } - if (cmp < 0) { - if ((cmp = COMPARE(op, esock_atom_recv)) == 0) { - MLOCK(descP->readMtx); - result = ESOCK_IO_CANCEL_RECV(env, descP, sockRef, opRef); - MUNLOCK(descP->readMtx); - return result; - } - if (cmp < 0) { - if (COMPARE(op, esock_atom_connect) == 0) { - MLOCK(descP->writeMtx); - result = ESOCK_IO_CANCEL_CONNECT(env, descP, opRef); - MUNLOCK(descP->writeMtx); - return result; - } - if (COMPARE(op, esock_atom_accept) == 0) { - MLOCK(descP->readMtx); - result = ESOCK_IO_CANCEL_ACCEPT(env, descP, sockRef, opRef); - MUNLOCK(descP->readMtx); - return result; - } - } else { - if (COMPARE(op, esock_atom_recvfrom) == 0) { - MLOCK(descP->readMtx); - result = ESOCK_IO_CANCEL_RECV(env, descP, sockRef, opRef); - MUNLOCK(descP->readMtx); - return result; - } - } + + /* PPid :: uint16() - optional (default value = 0) */ + // ESOCK_PRINTF("%s -> get ppid\r\n", __FUNCTION__); + if (! GET_MAP_VAL(env, eValue, esock_atom_ppid, &tmp)) { + itmp = 0; } else { - if ((cmp = COMPARE(op, esock_atom_sendmsg)) == 0) { - MLOCK(descP->writeMtx); - result = ESOCK_IO_CANCEL_SEND(env, descP, sockRef, opRef); - MUNLOCK(descP->writeMtx); - return result; - } - if (cmp < 0) { - if (COMPARE(op, esock_atom_send) == 0) { - MLOCK(descP->writeMtx); - result = ESOCK_IO_CANCEL_SEND(env, descP, sockRef, opRef); - MUNLOCK(descP->writeMtx); - return result; - } - if (COMPARE(op, esock_atom_sendfile) == 0) { - MLOCK(descP->writeMtx); - result = ESOCK_IO_CANCEL_SEND(env, descP, sockRef, opRef); - MUNLOCK(descP->writeMtx); - return result; - } - } else { - if (COMPARE(op, esock_atom_sendto) == 0) { - MLOCK(descP->writeMtx); - result = ESOCK_IO_CANCEL_SEND(env, descP, sockRef, opRef); - MUNLOCK(descP->writeMtx); - return result; - } - if (COMPARE(op, esock_atom_sendv) == 0) { - MLOCK(descP->writeMtx); - result = ESOCK_IO_CANCEL_SEND(env, descP, sockRef, opRef); - MUNLOCK(descP->writeMtx); - return result; - } - } + if (! GET_UINT(env, tmp, &itmp)) + return FALSE; } + info.sinfo_ppid = sock_htonl(itmp); - { - const char *reason; + /* Context :: uint32() - optional (default value = 0) */ + // ESOCK_PRINTF("%s -> get context\r\n", __FUNCTION__); + if (! GET_MAP_VAL(env, eValue, esock_atom_context, &tmp)) { + info.sinfo_context = 0; + } else { + if (! GET_UINT(env, tmp, &info.sinfo_context)) + return FALSE; + } - MLOCK(descP->readMtx); - MLOCK(descP->writeMtx); + /* Time To Live :: uint32() - optional (default value = 0) */ + // ESOCK_PRINTF("%s -> get t2l\r\n", __FUNCTION__); + if (! GET_MAP_VAL(env, eValue, esock_atom_time_to_live, &tmp)) { + info.sinfo_timetolive = 0; + } else { + if (! GET_UINT(env, tmp, &info.sinfo_timetolive)) + return FALSE; + } - if (! IS_OPEN(descP->readState)) { - result = esock_make_error_closed(env); - reason = "closed"; - } else { - result = enif_make_badarg(env); - reason = "badarg"; - } + /* TSN :: uint32() - optional (default value = 0) */ + // ESOCK_PRINTF("%s -> get tsn\r\n", __FUNCTION__); + if (! GET_MAP_VAL(env, eValue, esock_atom_tsn, &tmp)) { + info.sinfo_tsn = 0; + } else { + if (! GET_UINT(env, tmp, &info.sinfo_tsn)) + return FALSE; + } - SSDBG( descP, - ("SOCKET", "esock_cancel(%T), {%d,0x%X} -> %s" - "\r\n", - sockRef, descP->sock, - descP->readState | descP->writeState, reason) ); + /* Cum TSN :: uint32() - optional (default value = 0) */ + // ESOCK_PRINTF("%s -> get cum tsn\r\n", __FUNCTION__); + if (! GET_MAP_VAL(env, eValue, esock_atom_cum_tsn, &tmp)) { + info.sinfo_cumtsn = 0; + } else { + if (! GET_UINT(env, tmp, &info.sinfo_cumtsn)) + return FALSE; + } - MUNLOCK(descP->writeMtx); - MUNLOCK(descP->readMtx); + /* Assoc Id :: int32() - mandatory (= no default value) */ + // ESOCK_PRINTF("%s -> get assoc-id\r\n", __FUNCTION__); + if (! GET_MAP_VAL(env, eValue, esock_atom_assoc_id, &tmp)) + return FALSE; + if (! decode_sctp_assoc_t(env, tmp, &info.sinfo_assoc_id)) + return FALSE; - return result; - } -} + // ESOCK_PRINTF("%s -> get build cmsghdr\r\n", __FUNCTION__); + if ((infoP = esock_init_cmsghdr(cmsgP, rem, sizeof(*infoP), usedP)) == NULL) + return FALSE; + *infoP = info; -#ifndef __WIN32__ -extern -ERL_NIF_TERM esock_cancel_read_select(ErlNifEnv* env, - ESockDescriptor* descP, - ERL_NIF_TERM opRef) -{ - return esock_cancel_mode_select(env, descP, opRef, - ERL_NIF_SELECT_READ, - ERL_NIF_SELECT_READ_CANCELLED); + // ESOCK_PRINTF("%s -> done\r\n", __FUNCTION__); + + return TRUE; } -#endif // #ifndef __WIN32__ +#endif -#ifndef __WIN32__ -extern -ERL_NIF_TERM esock_cancel_write_select(ErlNifEnv* env, - ESockDescriptor* descP, - ERL_NIF_TERM opRef) +#if defined(SCTP_SNDINFO) +/* Do we actually need this function since this info is for sendmsg: decode */ +static +BOOLEAN_T esock_cmsg_encode_sctp_sndinfo(ErlNifEnv *env, + unsigned char *data, + size_t dataLen, + ERL_NIF_TERM *eResult) { - return esock_cancel_mode_select(env, descP, opRef, - ERL_NIF_SELECT_WRITE, - ERL_NIF_SELECT_WRITE_CANCELLED); -} -#endif // #ifndef __WIN32__ + struct sctp_sndinfo* infoP = (struct sctp_sndinfo*) data; + ERL_NIF_TERM sid, flags, ppid, context, assocId; + if (dataLen < sizeof(struct sctp_sndinfo)) { -#ifndef __WIN32__ -extern -ERL_NIF_TERM esock_cancel_mode_select(ErlNifEnv* env, - ESockDescriptor* descP, - ERL_NIF_TERM opRef, - int smode, - int rmode) -{ - /* Assumes cancelling only one mode */ + *eResult = esock_atom_undefined; - int selectRes = esock_select_cancel(env, descP->sock, smode, descP); + return FALSE; + } - if (selectRes >= 0) { - /* Success */ - if ((selectRes & rmode) != 0) { - /* Was cancelled */ - return esock_atom_ok; - } else { - /* Has already sent the message */ - return esock_atom_select_sent; - } - } else { - /* Stopped? */ - SSDBG( descP, - ("SOCKET", - "esock_cancel_mode_select {%d} -> failed: %d (0x%lX)" - "\r\n", descP->sock, selectRes, selectRes) ); + sid = MKUI(env, infoP->snd_sid); + // What flags? How to decode? + flags = MKUI(env, infoP->snd_flags); + ppid = MKUI(env, infoP->snd_ppid); + context = MKUI(env, infoP->snd_context); + assocId = encode_sctp_assoc_t(env, infoP->snd_assoc_id); + + { + ERL_NIF_TERM keys[] = {esock_atom_esock_name, + esock_atom_sid, + esock_atom_flags, + esock_atom_ppid, + esock_atom_context, + esock_atom_assoc_id}; + ERL_NIF_TERM vals[] = {MKA(env, "sctp_sndinfo"), + sid, + flags, + ppid, + context, + assocId}; + unsigned int numKeys = NUM(keys); + unsigned int numVals = NUM(vals); - return esock_atom_not_found; + ESOCK_ASSERT( numKeys == numVals ); + ESOCK_ASSERT( MKMA(env, keys, vals, numKeys, eResult) ); + } -} -#endif // #ifndef __WIN32__ + return TRUE; +} +static +BOOLEAN_T esock_cmsg_decode_sctp_sndinfo(ErlNifEnv* env, + ERL_NIF_TERM eValue, + struct cmsghdr* cmsgP, + size_t rem, + size_t* usedP) +{ + struct sctp_sndinfo info, *infoP; + ERL_NIF_TERM eSID, eFlags, ePPid, eContext, eAssocId; -/* ---------------------------------------------------------------------- - * U t i l i t y F u n c t i o n s - * ---------------------------------------------------------------------- - */ + /* SID */ + if (! GET_MAP_VAL(env, eValue, esock_atom_sid, &eSID)) + return FALSE; + if (! GET_UINT(env, eSID, (unsigned int*) &info.snd_sid)) + return FALSE; -extern -BOOLEAN_T esock_encode_cmsg(ErlNifEnv* env, - int level, - int type, - unsigned char* dataP, - size_t dataLen, - ERL_NIF_TERM* eType, - ERL_NIF_TERM* eData) -{ - const ESockCmsgSpec *cmsgTable; - size_t num; + /* Flags - This should really be a list of (flag) atoms, but for now... */ + if (! GET_MAP_VAL(env, eValue, esock_atom_flags, &eFlags)) + return FALSE; + if (! GET_UINT(env, eFlags, (unsigned int*) &info.snd_flags)) + return FALSE; - if ((cmsgTable = esock_lookup_cmsg_table(level, &num)) != NULL) { - size_t n; + /* PPid */ + if (! GET_MAP_VAL(env, eValue, esock_atom_ppid, &ePPid)) + return FALSE; + if (! GET_UINT(env, ePPid, &info.snd_ppid)) + return FALSE; - /* Linear search for type number in level table - */ - for (n = 0; n < num; n++) { - if (cmsgTable[n].type == type) { - /* Found the type number in the level table; - * return the symbolic type (atom) - * and try to encode the data - */ + /* Context */ + if (! GET_MAP_VAL(env, eValue, esock_atom_context, &eContext)) + return FALSE; + if (! GET_UINT(env, eContext, &info.snd_context)) + return FALSE; - *eType = *cmsgTable[n].nameP; + /* Assoc Id */ + if (! GET_MAP_VAL(env, eValue, esock_atom_assoc_id, &eAssocId)) + return FALSE; + if (! decode_sctp_assoc_t(env, eAssocId, &info.snd_assoc_id)) + return FALSE; - if (cmsgTable[n].encode != NULL) - return cmsgTable[n].encode(env, dataP, dataLen, eData); - else - return FALSE; - } - } - } - /* No level table, or unknown type number in the table; - * just return the type number as an erlang integer - */ + if ((infoP = esock_init_cmsghdr(cmsgP, rem, sizeof(*infoP), usedP)) == NULL) + return FALSE; - *eType = MKI(env, type); + *infoP = info; - return FALSE; + return TRUE; } +#endif +#if defined(SCTP_RCVINFO) +static +BOOLEAN_T esock_cmsg_encode_sctp_rcvinfo(ErlNifEnv *env, + unsigned char *data, + size_t dataLen, + ERL_NIF_TERM *eResult) +{ + struct sctp_rcvinfo* infoP = (struct sctp_rcvinfo*) data; + ERL_NIF_TERM sid, ssn, flags, ppid, tsn, cum_tsn, context, assocId; -/* +++ esock_encode_msg_flags +++ - * - * Encode a list of msg_flag(). - * - */ + if (dataLen < sizeof(struct sctp_rcvinfo)) { -extern -void esock_encode_msg_flags(ErlNifEnv* env, - ESockDescriptor* descP, - int msgFlags, - ERL_NIF_TERM* flags) -{ - SSDBG( descP, - ("SOCKET", "encode_msg_flags {%d} -> entry with" - "\r\n msgFlags: %d (0x%lX)" - "\r\n", descP->sock, msgFlags, msgFlags) ); + *eResult = esock_atom_undefined; - if (msgFlags == 0) { - *flags = MKEL(env); - } else { - size_t n; - SocketTArray ta = TARRAY_CREATE(10); // Just to be on the safe side + return FALSE; + } - for (n = 0; n < esock_msg_flags_length; n++) { - int f = esock_msg_flags[n].flag; - if ((f != 0) && ((msgFlags & f) == f)) { - msgFlags &= ~f; - TARRAY_ADD(ta, *(esock_msg_flags[n].name)); - } - if (msgFlags == 0) goto done; - } - /* Append remaining flags as an integer */ - if (msgFlags != 0) - TARRAY_ADD(ta, MKI(env, msgFlags)); + sid = MKUI(env, infoP->rcv_sid); + ssn = MKUI(env, infoP->rcv_ssn); + // What flags? How to decode? + flags = MKUI(env, infoP->rcv_flags); + ppid = MKUI(env, infoP->rcv_ppid); + tsn = MKUI(env, infoP->rcv_tsn); + cum_tsn = MKUI(env, infoP->rcv_cumtsn); + context = MKUI(env, infoP->rcv_context); + assocId = encode_sctp_assoc_t(env, infoP->rcv_assoc_id); - done: - SSDBG( descP, - ("SOCKET", "encode_msg_flags {%d} -> flags processed when" - "\r\n TArray size: %d" - "\r\n", descP->sock, TARRAY_SZ(ta)) ); + { + ERL_NIF_TERM keys[] = {esock_atom_esock_name, + esock_atom_sid, + esock_atom_ssn, + esock_atom_flags, + esock_atom_ppid, + esock_atom_tsn, + esock_atom_cum_tsn, + esock_atom_context, + esock_atom_assoc_id}; + ERL_NIF_TERM vals[] = {MKA(env, "sctp_rcvinfo"), + sid, + ssn, + flags, + ppid, + tsn, + cum_tsn, + context, + assocId}; + unsigned int numKeys = NUM(keys); + unsigned int numVals = NUM(vals); - TARRAY_TOLIST(ta, env, flags); + ESOCK_ASSERT( numKeys == numVals ); + ESOCK_ASSERT( MKMA(env, keys, vals, numKeys, eResult) ); + } -} + return TRUE; +} -#ifdef SCM_TIMESTAMP static -BOOLEAN_T esock_cmsg_encode_timeval(ErlNifEnv *env, - unsigned char *data, - size_t dataLen, - ERL_NIF_TERM *eResult) { - struct timeval* timeP = (struct timeval *) data; +BOOLEAN_T esock_cmsg_decode_sctp_rcvinfo(ErlNifEnv* env, + ERL_NIF_TERM eValue, + struct cmsghdr* cmsgP, + size_t rem, + size_t* usedP) +{ + struct sctp_rcvinfo info, *infoP; + ERL_NIF_TERM eSID, eSSN, eFlags, ePPid, eTSN, eCumTSN; + ERL_NIF_TERM eContext, eAssocId; - if (dataLen < sizeof(*timeP)) + /* SID */ + if (! GET_MAP_VAL(env, eValue, esock_atom_sid, &eSID)) + return FALSE; + if (! GET_UINT(env, eSID, (unsigned int*) &info.rcv_sid)) return FALSE; - esock_encode_timeval(env, timeP, eResult); + /* SSN */ + if (! GET_MAP_VAL(env, eValue, esock_atom_ssn, &eSSN)) + return FALSE; + if (! GET_UINT(env, eSSN, (unsigned int*) &info.rcv_ssn)) + return FALSE; + + /* Flags - This should really be a list of (flag) atoms, but for now... */ + if (! GET_MAP_VAL(env, eValue, esock_atom_flags, &eFlags)) + return FALSE; + if (! GET_UINT(env, eFlags, (unsigned int*) &info.rcv_flags)) + return FALSE; + + /* PPid */ + if (! GET_MAP_VAL(env, eValue, esock_atom_ppid, &ePPid)) + return FALSE; + if (! GET_UINT(env, ePPid, &info.rcv_ppid)) + return FALSE; + + /* TSN */ + if (! GET_MAP_VAL(env, eValue, esock_atom_tsn, &eTSN)) + return FALSE; + if (! GET_UINT(env, eTSN, &info.rcv_tsn)) + return FALSE; + + /* Cum TSN */ + if (! GET_MAP_VAL(env, eValue, esock_atom_cum_tsn, &eCumTSN)) + return FALSE; + if (! GET_UINT(env, eCumTSN, &info.rcv_cumtsn)) + return FALSE; + + /* Context */ + if (! GET_MAP_VAL(env, eValue, esock_atom_context, &eContext)) + return FALSE; + if (! GET_UINT(env, eContext, &info.rcv_context)) + return FALSE; + + /* Assoc Id */ + if (! GET_MAP_VAL(env, eValue, esock_atom_assoc_id, &eAssocId)) + return FALSE; + if (! decode_sctp_assoc_t(env, eAssocId, &info.rcv_assoc_id)) + return FALSE; + + if ((infoP = esock_init_cmsghdr(cmsgP, rem, sizeof(*infoP), usedP)) == NULL) + return FALSE; + + *infoP = info; + return TRUE; } +#endif -static BOOLEAN_T esock_cmsg_decode_timeval(ErlNifEnv *env, - ERL_NIF_TERM eValue, - struct cmsghdr *cmsgP, - size_t rem, - size_t *usedP) +#if defined(SCTP_NXTINFO) +static +BOOLEAN_T esock_cmsg_encode_sctp_nxtinfo(ErlNifEnv *env, + unsigned char *data, + size_t dataLen, + ERL_NIF_TERM *eResult) { - struct timeval time, *timeP; + struct sctp_nxtinfo* infoP = (struct sctp_nxtinfo*) data; + ERL_NIF_TERM sid, flags, ppid, length, assocId; - if (! esock_decode_timeval(env, eValue, &time)) - return FALSE; + if (dataLen < sizeof(struct sctp_nxtinfo)) { + + *eResult = esock_atom_undefined; - if ((timeP = esock_init_cmsghdr(cmsgP, rem, sizeof(*timeP), usedP)) == NULL) return FALSE; + } + + sid = MKUI(env, infoP->nxt_sid); + // What flags? How to decode? + flags = MKUI(env, infoP->nxt_flags); + ppid = MKUI(env, infoP->nxt_ppid); + length = MKUI(env, infoP->nxt_length); + assocId = encode_sctp_assoc_t(env, infoP->nxt_assoc_id); + + { + ERL_NIF_TERM keys[] = {esock_atom_esock_name, + esock_atom_sid, + esock_atom_flags, + esock_atom_ppid, + esock_atom_length, + esock_atom_assoc_id}; + ERL_NIF_TERM vals[] = {MKA(env, "sctp_nxtinfo"), + sid, + flags, + ppid, + length, + assocId}; + unsigned int numKeys = NUM(keys); + unsigned int numVals = NUM(vals); + + ESOCK_ASSERT( numKeys == numVals ); + ESOCK_ASSERT( MKMA(env, keys, vals, numKeys, eResult) ); + + } - *timeP = time; return TRUE; } #endif - -#if defined(IP_TOS) || defined(IP_RECVTOS) +#if defined(SCTP_PRINFO) +/* Do we actually need this function since this info is for sendmsg: decode */ static -BOOLEAN_T esock_cmsg_encode_ip_tos(ErlNifEnv *env, - unsigned char *data, - size_t dataLen, - ERL_NIF_TERM *eResult) +BOOLEAN_T esock_cmsg_encode_sctp_prinfo(ErlNifEnv *env, + unsigned char *data, + size_t dataLen, + ERL_NIF_TERM *eResult) { - unsigned char tos; + struct sctp_prinfo* infoP = (struct sctp_prinfo*) data; + ERL_NIF_TERM policy, value; + + if (dataLen < sizeof(struct sctp_prinfo)) { + + *eResult = esock_atom_undefined; - if (dataLen < sizeof(tos)) return FALSE; + } - tos = *data; + policy = MKUI(env, infoP->pr_policy); + value = MKUI(env, infoP->pr_value); + + { + ERL_NIF_TERM keys[] = {esock_atom_esock_name, + esock_atom_policy, + esock_atom_value}; + ERL_NIF_TERM vals[] = {MKA(env, "sctp_prinfo"), + policy, + value}; + unsigned int numKeys = NUM(keys); + unsigned int numVals = NUM(vals); + + ESOCK_ASSERT( numKeys == numVals ); + ESOCK_ASSERT( MKMA(env, keys, vals, numKeys, eResult) ); + + } - *eResult = encode_ip_tos(env, tos); return TRUE; } -static BOOLEAN_T esock_cmsg_decode_ip_tos(ErlNifEnv *env, - ERL_NIF_TERM eValue, - struct cmsghdr *cmsgP, - size_t rem, - size_t *usedP) +static +BOOLEAN_T esock_cmsg_decode_sctp_prinfo(ErlNifEnv* env, + ERL_NIF_TERM eValue, + struct cmsghdr* cmsgP, + size_t rem, + size_t* usedP) { - int tos, *tosP; + struct sctp_prinfo info, *infoP; + ERL_NIF_TERM ePolicy, ePRValue; - if (! decode_ip_tos(env, eValue, &tos)) + /* Policy */ + if (! GET_MAP_VAL(env, eValue, esock_atom_policy, &ePolicy)) + return FALSE; + if (! GET_UINT(env, ePolicy, (unsigned int*) &info.pr_policy)) return FALSE; - if ((tosP = esock_init_cmsghdr(cmsgP, rem, sizeof(*tosP), usedP)) == NULL) + /* Value */ + if (! GET_MAP_VAL(env, eValue, esock_atom_value, &ePRValue)) + return FALSE; + if (! GET_UINT(env, ePRValue, &info.pr_value)) return FALSE; - *tosP = tos; + if ((infoP = esock_init_cmsghdr(cmsgP, rem, sizeof(*infoP), usedP)) == NULL) + return FALSE; + + *infoP = info; + return TRUE; } -#endif // #ifdef IP_TOS +#endif + +#if defined(SCTP_AUTHINFO) +/* Do we actually need this function since this info is for sendmsg: decode */ +static +BOOLEAN_T esock_cmsg_encode_sctp_authinfo(ErlNifEnv *env, + unsigned char *data, + size_t dataLen, + ERL_NIF_TERM *eResult) +{ + struct sctp_authinfo* infoP = (struct sctp_authinfo*) data; + ERL_NIF_TERM keyNum; + if (dataLen < sizeof(struct sctp_authinfo)) { + + *eResult = esock_atom_undefined; + + return FALSE; + } + + keyNum = MKUI(env, infoP->auth_keynumber); + + { + ERL_NIF_TERM keys[] = {esock_atom_esock_name, + esock_atom_key_number}; + ERL_NIF_TERM vals[] = {MKA(env, "sctp_authinfo"), + keyNum}; + unsigned int numKeys = NUM(keys); + unsigned int numVals = NUM(vals); + + ESOCK_ASSERT( numKeys == numVals ); + ESOCK_ASSERT( MKMA(env, keys, vals, numKeys, eResult) ); + + } + + return TRUE; +} -#if defined(IP_TTL) || \ - defined(IPV6_HOPLIMIT) || \ - defined(IPV6_TCLASS) || defined(IPV6_RECVTCLASS) static -BOOLEAN_T esock_cmsg_encode_int(ErlNifEnv *env, - unsigned char *data, - size_t dataLen, - ERL_NIF_TERM *eResult) { - int value; +BOOLEAN_T esock_cmsg_decode_sctp_authinfo(ErlNifEnv* env, + ERL_NIF_TERM eValue, + struct cmsghdr* cmsgP, + size_t rem, + size_t* usedP) +{ + struct sctp_authinfo info, *infoP; + ERL_NIF_TERM eKeyNumber; - if (dataLen < sizeof(value)) + /* KeyNumber */ + if (! GET_MAP_VAL(env, eValue, esock_atom_key_number, &eKeyNumber)) + return FALSE; + if (! GET_UINT(env, eKeyNumber, (unsigned int*) &info.auth_keynumber)) return FALSE; - value = *((int *) data); - *eResult = MKI(env, value); + if ((infoP = esock_init_cmsghdr(cmsgP, rem, sizeof(*infoP), usedP)) == NULL) + return FALSE; + + *infoP = info; + return TRUE; } +#endif -extern -BOOLEAN_T esock_cmsg_decode_int(ErlNifEnv* env, - ERL_NIF_TERM eValue, - struct cmsghdr* cmsgP, - size_t rem, - size_t* usedP) +#if defined(SCTP_DSTADDRV4) +static +BOOLEAN_T esock_cmsg_encode_sctp_dstaddr_v4(ErlNifEnv *env, + unsigned char *data, + size_t dataLen, + ERL_NIF_TERM *eResult) { - int value, *valueP; + SOCKLEN_T addrLen = (SOCKLEN_T) dataLen; - if (! GET_INT(env, eValue, &value)) + if (addrLen != dataLen) return FALSE; - valueP = esock_init_cmsghdr(cmsgP, rem, sizeof(*valueP), usedP); - if (valueP == NULL) - return FALSE; + esock_encode_sockaddr_in(env, + (struct sockaddr_in*) data, addrLen, + eResult); - *valueP = value; return TRUE; } -#endif - -extern -BOOLEAN_T esock_cmsg_decode_bool(ErlNifEnv* env, - ERL_NIF_TERM eValue, - struct cmsghdr* cmsgP, - size_t rem, - size_t* usedP) +static +BOOLEAN_T esock_cmsg_decode_sctp_dstaddr_v4(ErlNifEnv* env, + ERL_NIF_TERM eValue, + struct cmsghdr* cmsgP, + size_t rem, + size_t* usedP) { - BOOLEAN_T v; - int* valueP; + struct sockaddr_in sa, *saP; + SOCKLEN_T saLen; - if (! esock_decode_bool(eValue, &v)) + if (! esock_decode_sockaddr_in(env, eValue, &sa, &saLen)) return FALSE; - if ((valueP = esock_init_cmsghdr(cmsgP, rem, - sizeof(*valueP), usedP)) == NULL) + VOID(saLen); + + if ((saP = esock_init_cmsghdr(cmsgP, rem, sizeof(*saP), usedP)) == NULL) return FALSE; - *valueP = v? 1 : 0; + *saP = sa; + return TRUE; } +#endif - -#ifdef IP_RECVTTL +#if defined(SCTP_DSTADDRV6) static -BOOLEAN_T esock_cmsg_encode_uchar(ErlNifEnv *env, - unsigned char *data, - size_t dataLen, - ERL_NIF_TERM *eResult) { - unsigned char value; +BOOLEAN_T esock_cmsg_encode_sctp_dstaddr_v6(ErlNifEnv *env, + unsigned char *data, + size_t dataLen, + ERL_NIF_TERM *eResult) +{ + SOCKLEN_T addrLen = (SOCKLEN_T) dataLen; - if (dataLen < sizeof(value)) + if (addrLen != dataLen) return FALSE; - value = *data; - *eResult = MKUI(env, value); + esock_encode_sockaddr_in6(env, + (struct sockaddr_in6*) data, addrLen, + eResult); + return TRUE; } -#endif - -#ifdef IP_PKTINFO static -BOOLEAN_T esock_cmsg_encode_in_pktinfo(ErlNifEnv *env, - unsigned char *data, - size_t dataLen, - ERL_NIF_TERM *eResult) +BOOLEAN_T esock_cmsg_decode_sctp_dstaddr_v6(ErlNifEnv* env, + ERL_NIF_TERM eValue, + struct cmsghdr* cmsgP, + size_t rem, + size_t* usedP) { - struct in_pktinfo* pktInfoP = (struct in_pktinfo*) data; - ERL_NIF_TERM ifIndex; - ERL_NIF_TERM specDst, addr; + struct sockaddr_in6 sa, *saP; + SOCKLEN_T saLen; - if (dataLen < sizeof(*pktInfoP)) + if (! esock_decode_sockaddr_in6(env, eValue, &sa, &saLen)) return FALSE; - ifIndex = MKUI(env, pktInfoP->ipi_ifindex); -#ifndef __WIN32__ - /* On Windows, the field ipi_spec_dst does not exist */ - esock_encode_in_addr(env, &pktInfoP->ipi_spec_dst, &specDst); -#endif - esock_encode_in_addr(env, &pktInfoP->ipi_addr, &addr); + VOID(saLen); - { - ERL_NIF_TERM keys[] = {esock_atom_ifindex, - esock_atom_spec_dst, - esock_atom_addr}; - ERL_NIF_TERM vals[] = {ifIndex, -#ifndef __WIN32__ - specDst, -#else - esock_atom_undefined, -#endif - addr}; - unsigned int numKeys = NUM(keys); - unsigned int numVals = NUM(vals); + if ((saP = esock_init_cmsghdr(cmsgP, rem, sizeof(*saP), usedP)) == NULL) + return FALSE; + + *saP = sa; - ESOCK_ASSERT( numKeys == numVals ); - ESOCK_ASSERT( MKMA(env, keys, vals, numKeys, eResult) ); - } return TRUE; } #endif +#endif // if defined(HAVE_SCTP) + #ifndef __WIN32__ #ifdef IP_ORIGDSTADDR @@ -11046,7 +14764,8 @@ static BOOLEAN_T esock_cmsg_encode_sockaddr(ErlNifEnv *env, unsigned char *data, size_t dataLen, - ERL_NIF_TERM *eResult) { + ERL_NIF_TERM *eResult) +{ SOCKLEN_T addrLen = (SOCKLEN_T) dataLen; if (addrLen != dataLen) @@ -11470,6 +15189,214 @@ static ESockCmsgSpec cmsgLevelIPv6[] = }; #endif // #ifdef HAVE_IPV6 + +#if defined(HAVE_SCTP) + /* These are cmsg_types. +typedef enum sctp_cmsg_type { + SCTP_INIT, / * 5.2.1 SCTP Initiation Structure * / +#define SCTP_INIT SCTP_INIT + SCTP_SNDRCV, / * 5.2.2 SCTP Header Information Structure * / +#define SCTP_SNDRCV SCTP_SNDRCV + SCTP_SNDINFO, / * 5.3.4 SCTP Send Information Structure * / +#define SCTP_SNDINFO SCTP_SNDINFO + SCTP_RCVINFO, / * 5.3.5 SCTP Receive Information Structure * / +#define SCTP_RCVINFO SCTP_RCVINFO + SCTP_NXTINFO, / * 5.3.6 SCTP Next Receive Information Structure * / +#define SCTP_NXTINFO SCTP_NXTINFO + SCTP_PRINFO, / * 5.3.7 SCTP PR-SCTP Information Structure * / +#define SCTP_PRINFO SCTP_PRINFO + SCTP_AUTHINFO, / * 5.3.8 SCTP AUTH Information Structure * / +#define SCTP_AUTHINFO SCTP_AUTHINFO + SCTP_DSTADDRV4, / * 5.3.9 SCTP Destination IPv4 Address Structure * / +#define SCTP_DSTADDRV4 SCTP_DSTADDRV4 + SCTP_DSTADDRV6, / * 5.3.10 SCTP Destination IPv6 Address Structure * / +#define SCTP_DSTADDRV6 SCTP_DSTADDRV6 +} sctp_cmsg_t; +*/ + +static ESockCmsgSpec cmsgLevelSCTP[] = + { +#if defined(SCTP_INIT) + /* + * Intended for: sendmsg() + * + * cmsg_level: IPPROTO_SCTP + * cmsg_type: SCTP_INIT + * cmsg_data: struct sctp_initmsg + * + * #{'$esock_name' => sctp_initmsg, + * num_ostream :: uint16(), + * max_instream :: uint16(), + * max_attempts :: uint16(), + * max_init_timeo :: uint16() + * } + * + * We do not *really* need an encode function... + */ + {SCTP_INIT, + esock_cmsg_encode_sctp_init, esock_cmsg_decode_sctp_init, + &esock_atom_init}, +#endif + +#if defined(SCTP_SNDRCV) + /* + * Intended for: sendmsg() | recvmsg() + * + * cmsg_level: IPPROTO_SCTP + * cmsg_type: SCTP_SNDRCV + * cmsg_data: struct sctp_sndrcvinfo + * + * #{'$esock_name' => sctp_sndrcvinfo, + * stream :: uint16(), + * ssn :: uint16(), + * flags :: uint16() | [atom()], + * ppid :: uint32(), + * context :: uint32(), + * time_to_live :: uint32(), + * tsn :: uint32(), + * cum_tsn :: uint32(), + * assoc_id :: assoc_id() + * } + */ + {SCTP_SNDRCV, + esock_cmsg_encode_sctp_sndrcv, esock_cmsg_decode_sctp_sndrcv, + &esock_atom_sndrcv}, +#endif + +#if defined(SCTP_SNDINFO) + /* + * Intended for: sendmsg() + * + * cmsg_level: IPPROTO_SCTP + * cmsg_type: SCTP_SNDINFO + * cmsg_data: struct sctp_sndinfo + * + * #{'$esock_name' => sctp_sndinfo, + * sid :: uint16(), + * flags :: uint16(), + * ppid :: uint32(), + * context :: uint32(), + * assoc_id :: assoc_id() + * } + * + * We do not *really* need an encode function... + */ + {SCTP_SNDINFO, + esock_cmsg_encode_sctp_sndinfo, esock_cmsg_decode_sctp_sndinfo, + &esock_atom_sndinfo}, +#endif + +#if defined(SCTP_RCVINFO) + /* + * Intended for: recvmsg() + * + * cmsg_level: IPPROTO_SCTP + * cmsg_type: SCTP_RCVINFO + * cmsg_data: struct sctp_rcvinfo + * + * #{'$esock_name' => sctp_rcvinfo, + * sid :: uint16(), + * ssn :: uint16(), + * flags :: uint16(), + * ppid :: uint32(), + * tsn :: uint32(), + * cum_tsn :: uint32(), + * context :: uint32(), + * assoc_id :: assoc_id() + * } + * + * We do not *really* need an decode function... + */ + {SCTP_RCVINFO, + esock_cmsg_encode_sctp_rcvinfo, esock_cmsg_decode_sctp_rcvinfo, + &esock_atom_rcvinfo}, +#endif + +#if defined(SCTP_NXTINFO) + /* + * Intended for: recvmsg() + * + * cmsg_level: IPPROTO_SCTP + * cmsg_type: SCTP_NXTINFO + * cmsg_data: struct sctp_nxtinfo + * + * #{'$esock_name' => sctp_nxtinfo, + * sid :: uint16(), + * flags :: uint16(), + * ppid :: uint32(), + * length :: uint32(), + * assoc_id :: assoc_id() + * } + */ + {SCTP_NXTINFO, + esock_cmsg_encode_sctp_nxtinfo, NULL, + &esock_atom_nxtinfo}, +#endif + +#if defined(SCTP_PRINFO) + /* + * Intended for: sendmsg() + * + * cmsg_level: IPPROTO_SCTP + * cmsg_type: SCTP_PRINFO + * cmsg_data: struct sctp_prinfo + * + * #{'$esock_name' => sctp_prinfo, + * policy :: uint16(), + * value :: uint32() + * } + * + * We do not *really* need an encode function... + */ + {SCTP_PRINFO, + esock_cmsg_encode_sctp_prinfo, esock_cmsg_decode_sctp_prinfo, + &esock_atom_prinfo}, +#endif + +#if defined(SCTP_AUTHINFO) + /* + * Intended for: sendmsg() + * + * cmsg_level: IPPROTO_SCTP + * cmsg_type: SCTP_AUTHINFO + * cmsg_data: struct sctp_authinfo + * + * #{'$esock_name' => sctp_nxtinfo, + * key_number :: uint16() + * } + * + * We do not *really* need an encode function... + */ + {SCTP_AUTHINFO, + esock_cmsg_encode_sctp_authinfo, esock_cmsg_decode_sctp_authinfo, + &esock_atom_authinfo}, +#endif + +#if defined(SCTP_DSTADDRV4) + /* + * Intended for: ? + * + * sockaddr_in() + */ + {SCTP_DSTADDRV4, + esock_cmsg_encode_sctp_dstaddr_v4, esock_cmsg_decode_sctp_dstaddr_v4, + &esock_atom_dstaddrv4}, +#endif + +#if defined(SCTP_DSTADDRV6) + /* + * Intended for: ? + * + * sockaddr_in6() + */ + {SCTP_DSTADDRV6, + esock_cmsg_encode_sctp_dstaddr_v6, esock_cmsg_decode_sctp_dstaddr_v6, + &esock_atom_dstaddrv6} +#endif + }; +#endif + + static void initCmsgTables(void) { #if defined(HAVE_ESOCK_CMSG_SOCKET) @@ -11481,6 +15408,10 @@ static void initCmsgTables(void) #ifdef HAVE_IPV6 ESOCK_SORT_TABLE(cmsgLevelIPv6, cmpESockCmsgSpec); #endif + +#if defined(HAVE_SCTP) + ESOCK_SORT_TABLE(cmsgLevelSCTP, cmpESockCmsgSpec); +#endif } extern @@ -11494,6 +15425,7 @@ ESockCmsgSpec* esock_lookup_cmsg_table(int level, size_t *num) return cmsgLevelSocket; #endif + #ifndef __WIN32__ #ifdef SOL_IP case SOL_IP: @@ -11506,6 +15438,7 @@ ESockCmsgSpec* esock_lookup_cmsg_table(int level, size_t *num) *num = NUM(cmsgLevelIP); return cmsgLevelIP; + #ifdef HAVE_IPV6 #ifndef __WIN32__ #ifdef SOL_IPV6 @@ -11520,6 +15453,14 @@ ESockCmsgSpec* esock_lookup_cmsg_table(int level, size_t *num) return cmsgLevelIPv6; #endif + +#if defined(HAVE_SCTP) + case IPPROTO_SCTP: + *num = NUM(cmsgLevelSCTP); + return cmsgLevelSCTP; +#endif + + default: return NULL; } @@ -11909,7 +15850,7 @@ ERL_NIF_TERM encode_ip_tos(ErlNifEnv* env, int val) -#if defined(SCTP_ASSOCINFO) || defined(SCTP_RTOINOFO) +#if defined(SCTP_ASSOCINFO) || defined(SCTP_RTOINOFO) || defined(SCTP_STATUS) static BOOLEAN_T decode_sctp_assoc_t(ErlNifEnv* env, @@ -11951,8 +15892,113 @@ ERL_NIF_TERM encode_sctp_assoc_t(ErlNifEnv* env, sctp_assoc_t val) return MKI(env, val); } -#endif // #if defined(SCTP_ASSOCINFO) || defined(SCTP_RTOINOFO) +#endif // #if defined(SCTP_ASSOCINFO) || defined(SCTP_RTOINOFO) || defined(SCTP_STATUS) + + +#if defined(SCTP_STATUS) + +static +ERL_NIF_TERM encode_sctp_paddrinfo(ErlNifEnv* env, + ESockDescriptor* descP, + struct sctp_paddrinfo* infoP) +{ + ERL_NIF_TERM eaid, eaddr, estate, ecwnd, esrtt, erto, emtu; + + VOID(descP); + + PUSH_SUPPRESS_ADDRESS_OF_PACKED_MEMBER(); + eaid = encode_sctp_assoc_t(env, infoP->spinfo_assoc_id); + eaddr = encode_sockaddr(env, &infoP->spinfo_address); + estate = encode_sctp_spinfo_state(env, descP, infoP->spinfo_state); + ecwnd = MKUI(env, infoP->spinfo_cwnd); + esrtt = MKUI(env, infoP->spinfo_srtt); + erto = MKUI(env, infoP->spinfo_rto); + emtu = MKUI(env, infoP->spinfo_mtu); + POP_SUPPRESS_ADDRESS_OF_PACKED_MEMBER(); + + { + ERL_NIF_TERM eAddrInfo; + ERL_NIF_TERM keys[] = {esock_atom_esock_name, + atom_assoc_id, + atom_address, + esock_atom_state, + atom_cwnd, + atom_srtt, + atom_rto, + atom_mtu}; + ERL_NIF_TERM vals[] = {MKA(env, "sctp_paddrinfo"), + eaid, eaddr, estate, ecwnd, esrtt, erto, emtu}; + unsigned int numKeys = NUM(keys); + unsigned int numVals = NUM(vals); + + ESOCK_ASSERT( numKeys == numVals ); + ESOCK_ASSERT( MKMA(env, keys, vals, numKeys, &eAddrInfo) ); + + return eAddrInfo; + } + +} + +static +ERL_NIF_TERM encode_sctp_spinfo_state(ErlNifEnv* env, + ESockDescriptor* descP, + int state) +{ + ERL_NIF_TERM estate; + + VOID(descP); + + switch (state) { + case SCTP_INACTIVE: + estate = atom_inactive; + break; + +#if defined(SCTP_POTENTIALLY_FAILED) + case SCTP_POTENTIALLY_FAILED: + estate = atom_potentially_failed; + break; +#endif + + case SCTP_ACTIVE: + estate = atom_active; + break; + +#if defined(SCTP_UNCONFIRMED) + case SCTP_UNCONFIRMED: + estate = atom_unconfirmed; + break; +#endif + +#if defined(SCTP_UNKNOWN) + case SCTP_UNKNOWN: + estate = esock_atom_unknown; + break; +#endif + + default: + estate = MKI(env, state); + break; + } + + return estate; +} + +#endif // #if defined(SCTP_STATUS) + + +#if defined(SCTP_STATUS) || defined(SCTP_PEER_ADDR_PARAMS) +static +ERL_NIF_TERM encode_sockaddr(ErlNifEnv* env, struct sockaddr_storage* addrP) +{ + ERL_NIF_TERM eaddr; + + esock_encode_sockaddr(env, (ESockAddress*) addrP, + sizeof(ESockAddress), &eaddr); + return eaddr; +} + +#endif // #if defined(SCTP_STATUS) || defined(SCTP_PEER_ADDR_PARAMS) /* *** esock_alloc_descriptor *** @@ -13686,10 +17732,12 @@ ErlNifFunc esock_funcs[] = {"nif_open", 2, nif_open, 0}, {"nif_open", 4, nif_open, 0}, {"nif_bind", 2, nif_bind, 0}, + {"nif_bind", 3, nif_bind, 0}, {"nif_connect", 1, nif_connect, 0}, {"nif_connect", 3, nif_connect, 0}, {"nif_listen", 2, nif_listen, 0}, {"nif_accept", 2, nif_accept, 0}, + {"nif_peeloff", 2, nif_peeloff, 0}, {"nif_send", 4, nif_send, 0}, {"nif_sendto", 5, nif_sendto, 0}, {"nif_sendmsg", 5, nif_sendmsg, 0}, @@ -13704,9 +17752,11 @@ ErlNifFunc esock_funcs[] = {"nif_shutdown", 2, nif_shutdown, 0}, {"nif_setopt", 5, nif_setopt, 0}, {"nif_getopt", 3, nif_getopt, 0}, - {"nif_getopt", 4, nif_getopt, 0}, + {"nif_getopt", 5, nif_getopt, 0}, {"nif_sockname", 1, nif_sockname, 0}, + {"nif_socknames", 2, nif_socknames, 0}, {"nif_peername", 1, nif_peername, 0}, + {"nif_peernames", 2, nif_peernames, 0}, {"nif_ioctl", 2, nif_ioctl, ERL_NIF_DIRTY_JOB_IO_BOUND}, {"nif_ioctl", 3, nif_ioctl, ERL_NIF_DIRTY_JOB_IO_BOUND}, {"nif_ioctl", 4, nif_ioctl, ERL_NIF_DIRTY_JOB_IO_BOUND}, @@ -13750,29 +17800,89 @@ char* extract_debug_filename(ErlNifEnv* env, * load_info - A map of misc info (e.g global debug) */ +/* Extra debug stuff */ +// #define ESOCK_DISPLAY_ON_LOAD_DETAILS 1 +// #define ESOCK_DISPLAY_SOCKADDR_SIZES 1 +// #define ESOCK_DISPLAY_OPTION_TABLES 1 + static int on_load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info) { ErlNifSysInfo sysInfo; unsigned int ioNumThreads, ioNumThreadsDef; + +#if defined(ESOCK_DISPLAY_SOCKADDR_SIZES) + ESOCK_EPRINTF("\r\n[ESOCK] sockaddr sizes: " + "\r\n sizeof(ESockAddress): %d" + "\r\n sizeof(struct sockaddr): %d" + "\r\n [AF_INET] sizeof(struct sockaddr_in): %d" +#if defined(HAVE_IN6) && defined(AF_INET6) + "\r\n [AF_INET6] sizeof(struct sockaddr_in6): %d" +#else + "\r\n [AF_INET6] sizeof(struct sockaddr_in6): -" +#endif +#if defined(HAS_AF_LOCAL) + "\r\n [AF_LOCAL] sizeof(struct sockaddr_un): %d" +#else + "\r\n [AF_LOCAL] sizeof(struct sockaddr_un): -" +#endif +#if defined(HAVE_NETPACKET_PACKET_H) + "\r\n [AF_PACKET] sizeof(struct sockaddr_ll): %d" +#else + "\r\n [AF_PACKET] sizeof(struct sockaddr_ll): -" +#endif +#if defined(HAVE_NET_IF_DL_H) && defined(AF_LINK) + "\r\n [AF_LINK] sizeof(struct sockaddr_dl): %d" +#else + "\r\n [AF_LINK] sizeof(struct sockaddr_dl): -" +#endif + "\r\n [Max size] sizeof(struct sockaddr_storage): %d" + "\r\n", + sizeof(ESockAddress), + sizeof(struct sockaddr), + sizeof(struct sockaddr_in), +#if defined(HAVE_IN6) && defined(AF_INET6) + sizeof(struct sockaddr_in6), +#endif +#if defined(HAS_AF_LOCAL) + sizeof(struct sockaddr_un), +#endif +#if defined(HAVE_NETPACKET_PACKET_H) + sizeof(struct sockaddr_ll), +#endif +#if defined(HAVE_NET_IF_DL_H) && defined(AF_LINK) + sizeof(struct sockaddr_dl), +#endif + sizeof(struct sockaddr_storage) + ); +#endif + + /* +++ Local atoms and error reason atoms +++ */ - // ESOCK_EPRINTF("\r\n[ESOCK] create local atoms\r\n"); +#if defined(ESOCK_DISPLAY_ON_LOAD_DETAILS) + ESOCK_EPRINTF("\r\n[ESOCK] create local atoms\r\n"); +#endif #define LOCAL_ATOM_DECL(A) atom_##A = MKA(env, #A) LOCAL_ATOMS; // LOCAL_ERROR_REASON_ATOMS; #undef LOCAL_ATOM_DECL /* Global atom(s) and error reason atom(s) */ - // ESOCK_EPRINTF("\r\n[ESOCK] create global atoms\r\n"); +#if defined(ESOCK_DISPLAY_ON_LOAD_DETAILS) + ESOCK_EPRINTF("\r\n[ESOCK] create global atoms\r\n"); +#endif #define GLOBAL_ATOM_DECL(A) esock_atom_##A = MKA(env, #A) GLOBAL_ATOMS; GLOBAL_ERROR_REASON_ATOMS; #undef GLOBAL_ATOM_DECL esock_atom_socket_tag = MKA(env, "$socket"); + esock_atom_esock_name = MKA(env, "$esock_name"); // Used in maps - // ESOCK_EPRINTF("\r\n[ESOCK] get registry pid\r\n"); +#if defined(ESOCK_DISPLAY_ON_LOAD_DETAILS) + ESOCK_EPRINTF("\r\n[ESOCK] get registry pid\r\n"); +#endif if (! esock_extract_pid_from_map(env, load_info, atom_registry, &data.regPid)) { @@ -13781,21 +17891,27 @@ int on_load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info) } /* --esock-disable-registry */ - // ESOCK_EPRINTF("\r\n[ESOCK] get use-registry\r\n"); +#if defined(ESOCK_DISPLAY_ON_LOAD_DETAILS) + ESOCK_EPRINTF("\r\n[ESOCK] get use-registry\r\n"); +#endif data.useReg = esock_get_bool_from_map(env, load_info, esock_atom_use_registry, ESOCK_USE_SOCKET_REGISTRY); /* --esock-enable-iow */ - // ESOCK_EPRINTF("\r\n[ESOCK] get enable-iow\r\n"); +#if defined(ESOCK_DISPLAY_ON_LOAD_DETAILS) + ESOCK_EPRINTF("\r\n[ESOCK] get enable-iow\r\n"); +#endif data.iow = esock_get_bool_from_map(env, load_info, atom_iow, ESOCK_NIF_IOW_DEFAULT); /* --enable-extended-error-info */ - // ESOCK_EPRINTF("\r\n[ESOCK] maybe enable eei\r\n"); +#if defined(ESOCK_DISPLAY_ON_LOAD_DETAILS) + ESOCK_EPRINTF("\r\n[ESOCK] maybe enable eei\r\n"); +#endif #if defined(ESOCK_USE_EXTENDED_ERROR_INFO) data.eei = TRUE; #else @@ -13803,7 +17919,9 @@ int on_load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info) #endif /* --esock-debug-file= */ - // ESOCK_EPRINTF("\r\n[ESOCK] debug filename\r\n"); +#if defined(ESOCK_DISPLAY_ON_LOAD_DETAILS) + ESOCK_EPRINTF("\r\n[ESOCK] debug filename\r\n"); +#endif { char *debug_filename; @@ -13818,7 +17936,7 @@ int on_load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info) ESOCK_GLOBAL_DEBUG_DEFAULT); data.sockDbg = esock_get_bool_from_map(env, load_info, - atom_socket_debug, + esock_atom_socket_debug, ESOCK_DEBUG_DEFAULT); } @@ -13826,11 +17944,15 @@ int on_load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info) FREE(debug_filename); } - // ESOCK_EPRINTF("\r\n[ESOCK] create protocols mutex\r\n"); +#if defined(ESOCK_DISPLAY_ON_LOAD_DETAILS) + ESOCK_EPRINTF("\r\n[ESOCK] create protocols mutex\r\n"); +#endif data.protocolsMtx = MCREATE("esock.protocols"); /* +++ Global Counters +++ */ - // ESOCK_EPRINTF("\r\n[ESOCK] create global counters mutex (and init counters)\r\n"); +#if defined(ESOCK_DISPLAY_ON_LOAD_DETAILS) + ESOCK_EPRINTF("\r\n[ESOCK] create global counters mutex (and init counters)\r\n"); +#endif data.cntMtx = MCREATE("esock.gcnt"); data.numSockets = 0; data.numTypeDGrams = 0; @@ -13845,12 +17967,13 @@ int on_load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info) data.numProtoSCTP = 0; - // ESOCK_EPRINTF("\r\n[ESOCK] init opts and cmsg tables\r\n"); +#if defined(ESOCK_DISPLAY_ON_LOAD_DETAILS) + ESOCK_EPRINTF("\r\n[ESOCK] init opts and cmsg tables\r\n"); +#endif initOpts(); initCmsgTables(); - // #define ESOCK_DISPLAY_OPTION_TABLES 1 #if defined(ESOCK_DISPLAY_OPTION_TABLES) { /* Display option table(s) after init */ @@ -13876,7 +17999,9 @@ int on_load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info) #endif - // ESOCK_EPRINTF("\r\n[ESOCK] init IOV max\r\n"); +#if defined(ESOCK_DISPLAY_ON_LOAD_DETAILS) + ESOCK_EPRINTF("\r\n[ESOCK] init IOV max\r\n"); +#endif data.iov_max = #if defined(NO_SYSCONF) || (! defined(_SC_IOV_MAX)) # ifdef IOV_MAX @@ -13892,7 +18017,9 @@ int on_load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info) /* This is (currently) intended for Windows use */ - // ESOCK_EPRINTF("\r\n[ESOCK] (win) system info\r\n"); +#if defined(ESOCK_DISPLAY_ON_LOAD_DETAILS) + ESOCK_EPRINTF("\r\n[ESOCK] system info\r\n"); +#endif enif_system_info(&sysInfo, sizeof(ErlNifSysInfo)); /* We should have a config options for this: @@ -13900,7 +18027,9 @@ int on_load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info) * * ESOCK_IO_NUM_THREADS */ - // ESOCK_EPRINTF("\r\n[ESOCK] (win) number of schedulers\r\n"); +#if defined(ESOCK_DISPLAY_ON_LOAD_DETAILS) + ESOCK_EPRINTF("\r\n[ESOCK] number of schedulers\r\n"); +#endif ioNumThreadsDef = (unsigned int) (sysInfo.scheduler_threads > 0) ? 2*sysInfo.scheduler_threads : 2; @@ -13909,23 +18038,28 @@ int on_load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info) atom_io_num_threads, ioNumThreadsDef); - // ESOCK_EPRINTF("\r\n[ESOCK] init I/O backend callbacks\r\n"); +#if defined(ESOCK_DISPLAY_ON_LOAD_DETAILS) + ESOCK_EPRINTF("\r\n[ESOCK] init I/O backend callbacks\r\n"); +#endif #ifdef __WIN32__ io_backend.init = esaio_init; io_backend.finish = esaio_finish; io_backend.info = esaio_info; - io_backend.cmd = esock_command; + io_backend.cmd = esaio_command; io_backend.supports_0 = esock_supports_0; io_backend.supports_1 = esock_supports_1; io_backend.open_with_fd = NULL; io_backend.open_plain = esaio_open_plain; io_backend.bind = esaio_bind; + io_backend.bindx = NULL; io_backend.connect = esaio_connect; + io_backend.connectx = NULL; io_backend.listen = esock_listen; io_backend.accept = esaio_accept; + io_backend.peeloff = NULL; io_backend.send = esaio_send; io_backend.sendto = esaio_sendto; io_backend.sendmsg = esaio_sendmsg; @@ -13940,7 +18074,9 @@ int on_load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info) io_backend.fin_close = esaio_fin_close; io_backend.shutdown = esock_shutdown; io_backend.sockname = esock_sockname; + io_backend.socknames = NULL; io_backend.peername = esock_peername; + io_backend.peernames = NULL; io_backend.cancel_connect = esaio_cancel_connect; io_backend.cancel_accept = esaio_cancel_accept; io_backend.cancel_send = esaio_cancel_send; @@ -13967,16 +18103,31 @@ int on_load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info) io_backend.finish = essio_finish; io_backend.info = essio_info; - io_backend.cmd = esock_command; + io_backend.cmd = essio_command; io_backend.supports_0 = esock_supports_0; io_backend.supports_1 = esock_supports_1; io_backend.open_with_fd = essio_open_with_fd; io_backend.open_plain = essio_open_plain; io_backend.bind = essio_bind; +#if defined(HAVE_SCTP) + io_backend.bindx = essio_bindx; +#else + io_backend.bindx = NULL; +#endif io_backend.connect = essio_connect; +#if defined(HAVE_SCTP) + io_backend.connectx = essio_connectx; +#else + io_backend.connectx = NULL; +#endif io_backend.listen = esock_listen; io_backend.accept = essio_accept; +#if defined(HAVE_SCTP) + io_backend.peeloff = essio_peeloff; +#else + io_backend.peeloff = NULL; +#endif io_backend.send = essio_send; io_backend.sendto = essio_sendto; io_backend.sendmsg = essio_sendmsg; @@ -13991,7 +18142,17 @@ int on_load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info) io_backend.fin_close = essio_fin_close; io_backend.shutdown = esock_shutdown; io_backend.sockname = esock_sockname; +#if defined(HAVE_SCTP) + io_backend.socknames = essio_socknames; +#else + io_backend.socknames = NULL; +#endif io_backend.peername = esock_peername; +#if defined(HAVE_SCTP) + io_backend.peernames = essio_peernames; +#else + io_backend.peernames = NULL; +#endif io_backend.cancel_connect = essio_cancel_connect; io_backend.cancel_accept = essio_cancel_accept; io_backend.cancel_send = essio_cancel_send; @@ -14014,21 +18175,27 @@ int on_load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info) #endif - // ESOCK_EPRINTF("\r\n[ESOCK] init I/O backend\r\n"); +#if defined(ESOCK_DISPLAY_ON_LOAD_DETAILS) + ESOCK_EPRINTF("\r\n[ESOCK] init I/O backend\r\n"); +#endif if (ESOCK_IO_INIT(ioNumThreads) != ESOCK_IO_OK) { esock_error_msg("Failed initiating I/O backend"); return 1; // Failure } - // ESOCK_EPRINTF("\r\n[ESOCK] open socket (nif) resource\r\n"); +#if defined(ESOCK_DISPLAY_ON_LOAD_DETAILS) + ESOCK_EPRINTF("\r\n[ESOCK] open socket (nif) resource\r\n"); +#endif esocks = enif_open_resource_type_x(env, "sockets", &esockInit, ERL_NIF_RT_CREATE, NULL); - /* ESOCK_EPRINTF("\r\n[ESOCK] open socket (nif) resource res: 0x%lX\r\n", */ - /* esocks); */ +#if defined(ESOCK_DISPLAY_ON_LOAD_DETAILS) + ESOCK_EPRINTF("\r\n[ESOCK] open socket (nif) resource res: 0x%lX\r\n", + esocks); +#endif if (esocks != NULL) { int ores; diff --git a/erts/emulator/nifs/common/socket_asyncio.h b/erts/emulator/nifs/common/socket_asyncio.h index b5c267b82a4c..a14258e67b6c 100644 --- a/erts/emulator/nifs/common/socket_asyncio.h +++ b/erts/emulator/nifs/common/socket_asyncio.h @@ -36,6 +36,9 @@ extern int esaio_init(unsigned int numThreads, const ESockData* dataP); extern void esaio_finish(void); extern ERL_NIF_TERM esaio_info(ErlNifEnv* env); +extern ERL_NIF_TERM esaio_command(ErlNifEnv* env, + ERL_NIF_TERM command, + ERL_NIF_TERM cdata); /* extern ERL_NIF_TERM esaio_open_with_fd(ErlNifEnv* env, @@ -53,6 +56,7 @@ extern ERL_NIF_TERM esaio_bind(ErlNifEnv* env, ESockDescriptor* descP, ESockAddress* sockAddrP, SOCKLEN_T addrLen); +/* No esaio_bindx */ extern ERL_NIF_TERM esaio_connect(ErlNifEnv* env, ESockDescriptor* descP, ERL_NIF_TERM sockRef, diff --git a/erts/emulator/nifs/common/socket_int.h b/erts/emulator/nifs/common/socket_int.h index c28cda8a82ab..acd193ea0bca 100644 --- a/erts/emulator/nifs/common/socket_int.h +++ b/erts/emulator/nifs/common/socket_int.h @@ -224,6 +224,36 @@ typedef long ssize_t; #endif +/* The linux kernel sctp include files have an alignment bug + that causes warnings of this type to appear: + + drivers/common/inet_drv.c:3196:47: error: taking address of packed member of +'struct sctp_paddr_change' may result in an unaligned pointer value [-Werror=add +ress-of-packed-member] + 3196 | i = load_inet_get_address(spec, i, desc, &sptr->spc_aaddr); + + So we need to suppress those, without disabling all warning + diagnostics of that type. + + See https://lore.kernel.org/patchwork/patch/1108122/ for the + patch that fixes this bug. In a few years we should be able to + remove this suppression. */ +#ifdef HAVE_GCC_DIAG_IGNORE_WADDRESS_OF_PACKED_MEMBER +#define PUSH_SUPPRESS_ADDRESS_OF_PACKED_MEMBER() \ + _Pragma("GCC diagnostic push") \ + _Pragma("GCC diagnostic ignored \"-Waddress-of-packed-member\"") \ + do { } while(0) +#define POP_SUPPRESS_ADDRESS_OF_PACKED_MEMBER() \ + _Pragma("GCC diagnostic pop") \ + do { } while(0) +#else +#define PUSH_SUPPRESS_ADDRESS_OF_PACKED_MEMBER() \ + do { } while(0) +#define POP_SUPPRESS_ADDRESS_OF_PACKED_MEMBER() \ + do { } while(0) +#endif + + /* ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ * "Global" atoms (esock_atom_...) * @@ -241,21 +271,40 @@ typedef long ssize_t; GLOBAL_ATOM_DEF(acc_fails); \ GLOBAL_ATOM_DEF(acc_tries); \ GLOBAL_ATOM_DEF(acc_waits); \ - GLOBAL_ATOM_DEF(adaption_layer); \ + GLOBAL_ATOM_DEF(action); \ + GLOBAL_ATOM_DEF(adaptation_event); \ + GLOBAL_ATOM_DEF(adaptation_indication); \ + GLOBAL_ATOM_DEF(adaptation_layer); \ + GLOBAL_ATOM_DEF(add); \ GLOBAL_ATOM_DEF(addr); \ GLOBAL_ATOM_DEF(addrform); \ + GLOBAL_ATOM_DEF(addr_added); \ + GLOBAL_ATOM_DEF(addr_available); \ + GLOBAL_ATOM_DEF(addr_confirmed); \ + GLOBAL_ATOM_DEF(addr_made_prim); \ + GLOBAL_ATOM_DEF(addr_over); \ + GLOBAL_ATOM_DEF(addr_potentially_failed); \ + GLOBAL_ATOM_DEF(addr_removed); \ + GLOBAL_ATOM_DEF(addr_unreachable); \ GLOBAL_ATOM_DEF(add_membership); \ GLOBAL_ATOM_DEF(add_source_membership); \ GLOBAL_ATOM_DEF(alen); \ GLOBAL_ATOM_DEF(allmulti); \ GLOBAL_ATOM_DEF(already); \ + GLOBAL_ATOM_DEF(altkeynumber); \ GLOBAL_ATOM_DEF(any); \ GLOBAL_ATOM_DEF(appletlk); \ GLOBAL_ATOM_DEF(arcnet); \ GLOBAL_ATOM_DEF(ax25); \ GLOBAL_ATOM_DEF(associnfo); \ + GLOBAL_ATOM_DEF(assoc_change); \ + GLOBAL_ATOM_DEF(assoc_id); \ + GLOBAL_ATOM_DEF(assoc_reset); \ GLOBAL_ATOM_DEF(atm); \ GLOBAL_ATOM_DEF(authhdr); \ + GLOBAL_ATOM_DEF(authinfo); \ + GLOBAL_ATOM_DEF(authkey); \ + GLOBAL_ATOM_DEF(authentication_event); \ GLOBAL_ATOM_DEF(auth_active_key); \ GLOBAL_ATOM_DEF(auth_asconf); \ GLOBAL_ATOM_DEF(auth_chunk); \ @@ -268,6 +317,7 @@ typedef long ssize_t; GLOBAL_ATOM_DEF(base_addr); \ GLOBAL_ATOM_DEF(bindtodevice); \ GLOBAL_ATOM_DEF(block_source); \ + GLOBAL_ATOM_DEF(bound); \ GLOBAL_ATOM_DEF(bridge); \ GLOBAL_ATOM_DEF(broadcast); \ GLOBAL_ATOM_DEF(bsp_state); \ @@ -280,6 +330,7 @@ typedef long ssize_t; GLOBAL_ATOM_DEF(cancel); \ GLOBAL_ATOM_DEF(cancelled); \ GLOBAL_ATOM_DEF(cantconfig); \ + GLOBAL_ATOM_DEF(cant_str_assoc); \ GLOBAL_ATOM_DEF(cellular); \ GLOBAL_ATOM_DEF(chaos); \ GLOBAL_ATOM_DEF(checksum); \ @@ -289,6 +340,8 @@ typedef long ssize_t; GLOBAL_ATOM_DEF(closing); \ GLOBAL_ATOM_DEF(cmsg_cloexec); \ GLOBAL_ATOM_DEF(command); \ + GLOBAL_ATOM_DEF(comm_lost); \ + GLOBAL_ATOM_DEF(comm_up); \ GLOBAL_ATOM_DEF(completion); \ GLOBAL_ATOM_DEF(completion_status); \ GLOBAL_ATOM_DEF(confirm); \ @@ -298,18 +351,24 @@ typedef long ssize_t; GLOBAL_ATOM_DEF(connecting); \ GLOBAL_ATOM_DEF(connection_time); \ GLOBAL_ATOM_DEF(context); \ + GLOBAL_ATOM_DEF(cookie_echoed); \ + GLOBAL_ATOM_DEF(cookie_wait); \ GLOBAL_ATOM_DEF(cork); \ GLOBAL_ATOM_DEF(counters); \ GLOBAL_ATOM_DEF(credentials); \ GLOBAL_ATOM_DEF(ctrl); \ GLOBAL_ATOM_DEF(ctrunc); \ + GLOBAL_ATOM_DEF(cum_tsn); \ GLOBAL_ATOM_DEF(cwnd); \ GLOBAL_ATOM_DEF(data); \ + GLOBAL_ATOM_DEF(data_sent); \ GLOBAL_ATOM_DEF(data_size); \ + GLOBAL_ATOM_DEF(data_unsent); \ GLOBAL_ATOM_DEF(debug); \ GLOBAL_ATOM_DEF(default); \ - GLOBAL_ATOM_DEF(default_send_params); \ + GLOBAL_ATOM_DEF(default_send_param); \ GLOBAL_ATOM_DEF(delayed_ack_time); \ + GLOBAL_ATOM_DEF(denied); \ GLOBAL_ATOM_DEF(dgram); \ GLOBAL_ATOM_DEF(dhcprunning); \ GLOBAL_ATOM_DEF(disabled); \ @@ -322,6 +381,8 @@ typedef long ssize_t; GLOBAL_ATOM_DEF(dormant); \ GLOBAL_ATOM_DEF(drop_membership); \ GLOBAL_ATOM_DEF(drop_source_membership); \ + GLOBAL_ATOM_DEF(dstaddrv4); \ + GLOBAL_ATOM_DEF(dstaddrv6); \ GLOBAL_ATOM_DEF(dstopts); \ GLOBAL_ATOM_DEF(dup); \ GLOBAL_ATOM_DEF(dup_acks_in); \ @@ -331,8 +392,10 @@ typedef long ssize_t; GLOBAL_ATOM_DEF(eether); \ GLOBAL_ATOM_DEF(efile); \ GLOBAL_ATOM_DEF(egp); \ + GLOBAL_ATOM_DEF(empty); \ GLOBAL_ATOM_DEF(enabled); \ GLOBAL_ATOM_DEF(enotsup); \ + GLOBAL_ATOM_DEF(eof); \ GLOBAL_ATOM_DEF(eor); \ GLOBAL_ATOM_DEF(error); \ GLOBAL_ATOM_DEF(errqueue); \ @@ -344,6 +407,7 @@ typedef long ssize_t; GLOBAL_ATOM_DEF(events); \ GLOBAL_ATOM_DEF(exclusiveaddruse); \ GLOBAL_ATOM_DEF(explicit_eor); \ + GLOBAL_ATOM_DEF(failed); \ GLOBAL_ATOM_DEF(faith); \ GLOBAL_ATOM_DEF(false); \ GLOBAL_ATOM_DEF(family); \ @@ -356,6 +420,7 @@ typedef long ssize_t; GLOBAL_ATOM_DEF(fragment_interleave); \ GLOBAL_ATOM_DEF(freebind); \ GLOBAL_ATOM_DEF(frelay); \ + GLOBAL_ATOM_DEF(get_assoc_stats); \ GLOBAL_ATOM_DEF(get_overlapped_result); \ GLOBAL_ATOM_DEF(get_peer_addr_info); \ GLOBAL_ATOM_DEF(gif); \ @@ -374,11 +439,15 @@ typedef long ssize_t; GLOBAL_ATOM_DEF(ifindex); \ GLOBAL_ATOM_DEF(igmp); \ GLOBAL_ATOM_DEF(implink); \ + GLOBAL_ATOM_DEF(inbound_streams); \ + GLOBAL_ATOM_DEF(incoming_ssn); \ GLOBAL_ATOM_DEF(index); \ + GLOBAL_ATOM_DEF(indication); \ GLOBAL_ATOM_DEF(inet); \ GLOBAL_ATOM_DEF(inet6); \ GLOBAL_ATOM_DEF(infiniband); \ GLOBAL_ATOM_DEF(info); \ + GLOBAL_ATOM_DEF(init); \ GLOBAL_ATOM_DEF(initmsg); \ GLOBAL_ATOM_DEF(invalid); \ GLOBAL_ATOM_DEF(integer_range); \ @@ -397,9 +466,12 @@ typedef long ssize_t; GLOBAL_ATOM_DEF(keepidle); \ GLOBAL_ATOM_DEF(keepintvl); \ GLOBAL_ATOM_DEF(kernel); \ + GLOBAL_ATOM_DEF(keynumber); \ + GLOBAL_ATOM_DEF(key_number); \ GLOBAL_ATOM_DEF(knowsepoch); \ GLOBAL_ATOM_DEF(last_ack); \ GLOBAL_ATOM_DEF(leave_group); \ + GLOBAL_ATOM_DEF(length); \ GLOBAL_ATOM_DEF(level); \ GLOBAL_ATOM_DEF(linger); \ GLOBAL_ATOM_DEF(link); \ @@ -410,6 +482,7 @@ typedef long ssize_t; GLOBAL_ATOM_DEF(local); \ GLOBAL_ATOM_DEF(localtlk); \ GLOBAL_ATOM_DEF(local_auth_chunks); \ + GLOBAL_ATOM_DEF(local_tsn); \ GLOBAL_ATOM_DEF(loop); \ GLOBAL_ATOM_DEF(loopback); \ GLOBAL_ATOM_DEF(lowdelay); \ @@ -420,6 +493,9 @@ typedef long ssize_t; GLOBAL_ATOM_DEF(maxburst); \ GLOBAL_ATOM_DEF(maxdg); \ GLOBAL_ATOM_DEF(maxseg); \ + GLOBAL_ATOM_DEF(max_attempts); \ + GLOBAL_ATOM_DEF(max_init_timeo); \ + GLOBAL_ATOM_DEF(max_instreams); \ GLOBAL_ATOM_DEF(max_msg_size); \ GLOBAL_ATOM_DEF(md5sig); \ GLOBAL_ATOM_DEF(mem_end); \ @@ -441,6 +517,7 @@ typedef long ssize_t; GLOBAL_ATOM_DEF(multicast_loop); \ GLOBAL_ATOM_DEF(multicast_ttl); \ GLOBAL_ATOM_DEF(name); \ + GLOBAL_ATOM_DEF(native); \ GLOBAL_ATOM_DEF(netns); \ GLOBAL_ATOM_DEF(netrom); \ GLOBAL_ATOM_DEF(nlen); \ @@ -452,17 +529,20 @@ typedef long ssize_t; GLOBAL_ATOM_DEF(noopt); \ GLOBAL_ATOM_DEF(nopush); \ GLOBAL_ATOM_DEF(nosignal); \ + GLOBAL_ATOM_DEF(notification); \ GLOBAL_ATOM_DEF(notrailers); \ GLOBAL_ATOM_DEF(not_bound); \ GLOBAL_ATOM_DEF(not_found); \ GLOBAL_ATOM_DEF(num_general_errors); \ GLOBAL_ATOM_DEF(not_owner); \ + GLOBAL_ATOM_DEF(num_ostreams); \ GLOBAL_ATOM_DEF(num_threads); \ GLOBAL_ATOM_DEF(num_unexpected_accepts); \ GLOBAL_ATOM_DEF(num_unexpected_connects); \ GLOBAL_ATOM_DEF(num_unexpected_reads); \ GLOBAL_ATOM_DEF(num_unexpected_writes); \ GLOBAL_ATOM_DEF(num_unknown_cmds); \ + GLOBAL_ATOM_DEF(nxtinfo); \ GLOBAL_ATOM_DEF(oactive); \ GLOBAL_ATOM_DEF(off); \ GLOBAL_ATOM_DEF(ok); \ @@ -473,13 +553,16 @@ typedef long ssize_t; GLOBAL_ATOM_DEF(origdstaddr); \ GLOBAL_ATOM_DEF(other); \ GLOBAL_ATOM_DEF(otherhost); \ + GLOBAL_ATOM_DEF(outbound_streams); \ GLOBAL_ATOM_DEF(outgoing); \ + GLOBAL_ATOM_DEF(outgoing_ssn); \ GLOBAL_ATOM_DEF(packet); \ - GLOBAL_ATOM_DEF(partial_delivery_point); \ + GLOBAL_ATOM_DEF(partial_delivery); \ GLOBAL_ATOM_DEF(passcred); \ GLOBAL_ATOM_DEF(path); \ GLOBAL_ATOM_DEF(peek); \ GLOBAL_ATOM_DEF(peek_off); \ + GLOBAL_ATOM_DEF(peer_addr_change); \ GLOBAL_ATOM_DEF(peer_addr_params); \ GLOBAL_ATOM_DEF(peer_auth_chunks); \ GLOBAL_ATOM_DEF(peercred); \ @@ -487,13 +570,16 @@ typedef long ssize_t; GLOBAL_ATOM_DEF(pktoptions); \ GLOBAL_ATOM_DEF(pkttype); \ GLOBAL_ATOM_DEF(pointopoint); \ + GLOBAL_ATOM_DEF(policy); \ GLOBAL_ATOM_DEF(port); \ GLOBAL_ATOM_DEF(portrange); \ GLOBAL_ATOM_DEF(portsel); \ - GLOBAL_ATOM_DEF(ppromisc); \ + GLOBAL_ATOM_DEF(ppid); \ GLOBAL_ATOM_DEF(ppp); \ + GLOBAL_ATOM_DEF(ppromisc); \ GLOBAL_ATOM_DEF(primary_addr); \ GLOBAL_ATOM_DEF(prim_file); \ + GLOBAL_ATOM_DEF(prinfo); \ GLOBAL_ATOM_DEF(priority); \ GLOBAL_ATOM_DEF(private); \ GLOBAL_ATOM_DEF(promisc); \ @@ -504,6 +590,7 @@ typedef long ssize_t; GLOBAL_ATOM_DEF(rawip); \ GLOBAL_ATOM_DEF(rcvbuf); \ GLOBAL_ATOM_DEF(rcvbufforce); \ + GLOBAL_ATOM_DEF(rcvinfo); \ GLOBAL_ATOM_DEF(rcvlowat); \ GLOBAL_ATOM_DEF(rcvtimeo); \ GLOBAL_ATOM_DEF(rcv_buf); \ @@ -528,8 +615,13 @@ typedef long ssize_t; GLOBAL_ATOM_DEF(recvtos); \ GLOBAL_ATOM_DEF(recvttl); \ GLOBAL_ATOM_DEF(reliability); \ + GLOBAL_ATOM_DEF(remote_causes); \ + GLOBAL_ATOM_DEF(remote_error); \ + GLOBAL_ATOM_DEF(remote_tsn); \ + GLOBAL_ATOM_DEF(remove); \ GLOBAL_ATOM_DEF(renaming); \ GLOBAL_ATOM_DEF(reset_streams); \ + GLOBAL_ATOM_DEF(restart); \ GLOBAL_ATOM_DEF(retopts); \ GLOBAL_ATOM_DEF(reuseaddr); \ GLOBAL_ATOM_DEF(reuseport); \ @@ -543,11 +635,13 @@ typedef long ssize_t; GLOBAL_ATOM_DEF(rxq_ovfl); \ GLOBAL_ATOM_DEF(scope_id); \ GLOBAL_ATOM_DEF(sctp); \ + GLOBAL_ATOM_DEF(sctp_notification); \ GLOBAL_ATOM_DEF(sec); \ GLOBAL_ATOM_DEF(select); \ GLOBAL_ATOM_DEF(select_failed); \ GLOBAL_ATOM_DEF(select_sent); \ GLOBAL_ATOM_DEF(send); \ + GLOBAL_ATOM_DEF(sender_dry); \ GLOBAL_ATOM_DEF(sendfile); \ GLOBAL_ATOM_DEF(sendfile_byte); \ GLOBAL_ATOM_DEF(sendfile_deferred_close); \ @@ -561,27 +655,44 @@ typedef long ssize_t; GLOBAL_ATOM_DEF(sendsrcaddr); \ GLOBAL_ATOM_DEF(sendto); \ GLOBAL_ATOM_DEF(sendv); \ + GLOBAL_ATOM_DEF(send_failed); \ + GLOBAL_ATOM_DEF(send_failed_event); \ + GLOBAL_ATOM_DEF(seq); \ GLOBAL_ATOM_DEF(seqpacket); \ GLOBAL_ATOM_DEF(setfib); \ GLOBAL_ATOM_DEF(set_peer_primary_addr); \ + GLOBAL_ATOM_DEF(shutdown_ack_sent); \ + GLOBAL_ATOM_DEF(shutdown_comp); \ + GLOBAL_ATOM_DEF(shutdown_event); \ + GLOBAL_ATOM_DEF(shutdown_pending); \ + GLOBAL_ATOM_DEF(shutdown_received); \ + GLOBAL_ATOM_DEF(shutdown_sent); \ + GLOBAL_ATOM_DEF(sid); \ GLOBAL_ATOM_DEF(simplex); \ GLOBAL_ATOM_DEF(slave); \ GLOBAL_ATOM_DEF(slen); \ GLOBAL_ATOM_DEF(sndbuf); \ GLOBAL_ATOM_DEF(sndbufforce); \ + GLOBAL_ATOM_DEF(sndinfo); \ GLOBAL_ATOM_DEF(sndlowat); \ + GLOBAL_ATOM_DEF(sndrcv); \ GLOBAL_ATOM_DEF(sndtimeo); \ GLOBAL_ATOM_DEF(snd_wnd); \ GLOBAL_ATOM_DEF(sockaddr); \ GLOBAL_ATOM_DEF(socket); \ + GLOBAL_ATOM_DEF(socket_debug); \ GLOBAL_ATOM_DEF(socket_tag); \ GLOBAL_ATOM_DEF(socktype); \ GLOBAL_ATOM_DEF(spec_dst); \ + GLOBAL_ATOM_DEF(ssn); \ GLOBAL_ATOM_DEF(state); \ GLOBAL_ATOM_DEF(status); \ GLOBAL_ATOM_DEF(staticarp); \ GLOBAL_ATOM_DEF(stf); \ GLOBAL_ATOM_DEF(stream); \ + GLOBAL_ATOM_DEF(streams); \ + GLOBAL_ATOM_DEF(stream_change); \ + GLOBAL_ATOM_DEF(stream_reset); \ GLOBAL_ATOM_DEF(syncnt); \ GLOBAL_ATOM_DEF(syn_rcvd); \ GLOBAL_ATOM_DEF(syn_retrans); \ @@ -595,9 +706,11 @@ typedef long ssize_t; GLOBAL_ATOM_DEF(timeout); \ GLOBAL_ATOM_DEF(timeout_episodes); \ GLOBAL_ATOM_DEF(timestamp_enabled); \ + GLOBAL_ATOM_DEF(time_to_live); \ GLOBAL_ATOM_DEF(time_wait); \ GLOBAL_ATOM_DEF(true); \ GLOBAL_ATOM_DEF(trunc); \ + GLOBAL_ATOM_DEF(tsn); \ GLOBAL_ATOM_DEF(ttl); \ GLOBAL_ATOM_DEF(tunnel); \ GLOBAL_ATOM_DEF(tunnel6); \ @@ -608,6 +721,7 @@ typedef long ssize_t; GLOBAL_ATOM_DEF(undefined); \ GLOBAL_ATOM_DEF(unicast_hops); \ GLOBAL_ATOM_DEF(unknown); \ + GLOBAL_ATOM_DEF(unordered); \ GLOBAL_ATOM_DEF(unspec); \ GLOBAL_ATOM_DEF(up); \ GLOBAL_ATOM_DEF(usec); \ @@ -648,6 +762,8 @@ typedef long ssize_t; GLOBAL_ATOM_DEFS; GLOBAL_ERROR_REASON_ATOM_DEFS; #undef GLOBAL_ATOM_DEF +extern ERL_NIF_TERM esock_atom_socket_tag; // This has a "special" name ('$socket') +extern ERL_NIF_TERM esock_atom_esock_name; // This has a "special" name ('$esock_name') /* ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ @@ -702,15 +818,18 @@ GLOBAL_ERROR_REASON_ATOM_DEFS; #define IS_IDENTICAL(A, B) enif_is_identical((A), (B)) #define IS_ZERO(T) (COMPARE((T), esock_atom_zero) == 0) #define IS_UNDEFINED(T) IS_IDENTICAL((T), esock_atom_undefined) +#define IS_INVALID(T) IS_IDENTICAL((T), esock_atom_invalid) #define IS_OK(T) IS_IDENTICAL((T), esock_atom_ok) #define IS_ATOM(E, TE) enif_is_atom((E), (TE)) #define IS_BIN(E, TE) enif_is_binary((E), (TE)) +#define IS_INTEGER(E, TE) esock_is_integer((E), (TE)) #define IS_LIST(E, TE) enif_is_list((E), (TE)) +#define IS_EMPTY_LIST(E, L) enif_is_empty_list((E), (L)) #define IS_MAP(E, TE) enif_is_map((E), (TE)) #define IS_NUM(E, TE) enif_is_number((E), (TE)) +#define IS_REF(E, TE) enif_is_ref((E), (TE)) #define IS_TUPLE(E, TE) enif_is_tuple((E), (TE)) -#define IS_INTEGER(E, TE) esock_is_integer((E), (TE)) #define IS_PID_UNDEF(P) enif_is_pid_undefined((P)) #define SET_PID_UNDEF(P) enif_set_pid_undefined((P)) @@ -759,4 +878,9 @@ GLOBAL_ERROR_REASON_ATOM_DEFS; #define ESOCK_PRINTF(...) enif_fprintf(stdout, __VA_ARGS__) #define ESOCK_EPRINTF(...) enif_fprintf(stderr, __VA_ARGS__) +#define ESOCK_DLOPEN(LIBSTR) esock_dlopen((LIBSTR)) +#define ESOCK_DLSYM(H, SYM) esock_dlsym((H), (SYM)) +// #define ESOCK_DLOPEN(LIBSTR) enif_dlopen((LIBSTR), NULL, NULL) +// #define ESOCK_DLSYM(H, SYM) enif_dlsym((H), (SYM), NULL, NULL) + #endif // SOCKET_INT_H__ diff --git a/erts/emulator/nifs/common/socket_io.h b/erts/emulator/nifs/common/socket_io.h index fad0a00fc754..1d20babdb88b 100644 --- a/erts/emulator/nifs/common/socket_io.h +++ b/erts/emulator/nifs/common/socket_io.h @@ -67,6 +67,12 @@ typedef ERL_NIF_TERM (*ESockIOBind)(ErlNifEnv* env, ESockAddress* sockAddrP, SOCKLEN_T addrLen); +typedef ERL_NIF_TERM (*ESockIOBindx)(ErlNifEnv* env, + ESockDescriptor* descP, + ESockAddress* sockAddrs, + int addrCnt, + int action); + typedef ERL_NIF_TERM (*ESockIOConnect)(ErlNifEnv* env, ESockDescriptor* descP, ERL_NIF_TERM sockRef, @@ -74,6 +80,13 @@ typedef ERL_NIF_TERM (*ESockIOConnect)(ErlNifEnv* env, ESockAddress* addrP, SOCKLEN_T addrLen); +typedef ERL_NIF_TERM (*ESockIOConnectx)(ErlNifEnv* env, + ESockDescriptor* descP, + ERL_NIF_TERM sockRef, + ERL_NIF_TERM connRef, + ESockAddress* sockAddrs, + int addrsCnt); + typedef ERL_NIF_TERM (*ESockIOListen)(ErlNifEnv* env, ESockDescriptor* descP, int backlog); @@ -83,6 +96,11 @@ typedef ERL_NIF_TERM (*ESockIOAccept)(ErlNifEnv* env, ERL_NIF_TERM sockRef, ERL_NIF_TERM accRef); +typedef ERL_NIF_TERM (*ESockIOPeelOff)(ErlNifEnv* env, + ESockDescriptor* descP, + ERL_NIF_TERM sockRef, + ESockAssocId assocId); + typedef ERL_NIF_TERM (*ESockIOSend)(ErlNifEnv* env, ESockDescriptor* descP, ERL_NIF_TERM sockRef, @@ -165,9 +183,15 @@ typedef ERL_NIF_TERM (*ESockIOShutdown)(ErlNifEnv* env, typedef ERL_NIF_TERM (*ESockIOSockName)(ErlNifEnv* env, ESockDescriptor* descP); +typedef ERL_NIF_TERM (*ESockIOSockNames)(ErlNifEnv* env, + ESockDescriptor* descP, + ESockAssocId assocId); typedef ERL_NIF_TERM (*ESockIOPeerName)(ErlNifEnv* env, ESockDescriptor* descP); +typedef ERL_NIF_TERM (*ESockIOPeerNames)(ErlNifEnv* env, + ESockDescriptor* descP, + ESockAssocId assocId); typedef ERL_NIF_TERM (*ESockIOCancelConnect)(ErlNifEnv* env, ESockDescriptor* descP, @@ -206,7 +230,8 @@ typedef ERL_NIF_TERM (*ESockIOSetoptOtp)(ErlNifEnv* env, typedef ERL_NIF_TERM (*ESockIOGetopt)(ErlNifEnv* env, ESockDescriptor* descP, int level, - int opt); + int opt, + ERL_NIF_TERM value); typedef ERL_NIF_TERM (*ESockIOGetoptNative)(ErlNifEnv* env, ESockDescriptor* descP, int level, diff --git a/erts/emulator/nifs/common/socket_syncio.h b/erts/emulator/nifs/common/socket_syncio.h index 1fccfa9bfd48..cd77595ddbe6 100644 --- a/erts/emulator/nifs/common/socket_syncio.h +++ b/erts/emulator/nifs/common/socket_syncio.h @@ -3,7 +3,6 @@ * * SPDX-License-Identifier: Apache-2.0 * - * Copyright Ericsson AB 2022-2025. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -36,6 +35,9 @@ extern int essio_init(unsigned int numThreads, const ESockData* dataP); extern void essio_finish(void); extern ERL_NIF_TERM essio_info(ErlNifEnv* env); +extern ERL_NIF_TERM essio_command(ErlNifEnv* env, + ERL_NIF_TERM command, + ERL_NIF_TERM cdata); extern ERL_NIF_TERM essio_open_with_fd(ErlNifEnv* env, int fd, @@ -51,12 +53,23 @@ extern ERL_NIF_TERM essio_bind(ErlNifEnv* env, ESockDescriptor* descP, ESockAddress* sockAddrP, SOCKLEN_T addrLen); +extern ERL_NIF_TERM essio_bindx(ErlNifEnv* env, + ESockDescriptor* descP, + ESockAddress* sockAddrs, + int addrCnt, + int action); extern ERL_NIF_TERM essio_connect(ErlNifEnv* env, ESockDescriptor* descP, ERL_NIF_TERM sockRef, ERL_NIF_TERM connRef, ESockAddress* addrP, SOCKLEN_T addrLen); +extern ERL_NIF_TERM essio_connectx(ErlNifEnv* env, + ESockDescriptor* descP, + ERL_NIF_TERM sockRef, + ERL_NIF_TERM connRef, + ESockAddress* sockAddrs, + int addrsCnt); /* extern ERL_NIF_TERM essio_listen(ErlNifEnv* env, ESockDescriptor* descP, @@ -66,6 +79,25 @@ extern ERL_NIF_TERM essio_accept(ErlNifEnv* env, ESockDescriptor* descP, ERL_NIF_TERM sockRef, ERL_NIF_TERM accRef); +#if defined(HAVE_SCTP) +extern ERL_NIF_TERM essio_peeloff(ErlNifEnv* env, + ESockDescriptor* descP, + ERL_NIF_TERM sockRef, + ESockAssocId assocId); +#endif + +#if defined(HAVE_SCTP) +extern ERL_NIF_TERM essio_socknames(ErlNifEnv* env, + ESockDescriptor* descP, + sctp_assoc_t assocId); +#endif + +#if defined(HAVE_SCTP) +extern ERL_NIF_TERM essio_peernames(ErlNifEnv* env, + ESockDescriptor* descP, + sctp_assoc_t assocId); +#endif + extern ERL_NIF_TERM essio_send(ErlNifEnv* env, ESockDescriptor* descP, ERL_NIF_TERM sockRef, diff --git a/erts/emulator/nifs/common/socket_util.c b/erts/emulator/nifs/common/socket_util.c index 68f689ac7424..e057f9296dc3 100644 --- a/erts/emulator/nifs/common/socket_util.c +++ b/erts/emulator/nifs/common/socket_util.c @@ -41,6 +41,9 @@ #if !defined(__WIN32__) #include #endif +#ifdef HAVE_DLFCN_H +#include +#endif #if !defined(__IOS__) && !defined(__WIN32__) #include @@ -73,7 +76,16 @@ #define UTIL_DEBUG FALSE #endif -#define UDBG( proto ) ESOCK_DBG_PRINTF( UTIL_DEBUG , proto ) +/* some systems do not have RTLD_NOW defined, and require the "mode" + * argument to dload() always be 1. + */ +#ifndef RTLD_NOW +# define RTLD_NOW 1 +#endif + +#define DO_UDBG( dbg, proto ) ESOCK_DBG_PRINTF( dbg , proto ) +#define UDBG( proto ) DO_UDBG( UTIL_DEBUG , proto ) +#define UDBG2( dbg, proto ) DO_UDBG( dbg , proto ) #if defined(__WIN32__) typedef u_short sa_family_t; @@ -105,11 +117,11 @@ static void esock_encode_sockaddr_dl(ErlNifEnv* env, SOCKLEN_T addrLen, ERL_NIF_TERM* eSockAddr); #endif -static void esock_encode_sockaddr_native(ErlNifEnv* env, - struct sockaddr* sa, - SOCKLEN_T len, - ERL_NIF_TERM eFamily, - ERL_NIF_TERM* eSockAddr); +/* static void esock_encode_sockaddr_native(ErlNifEnv* env, */ +/* struct sockaddr* sa, */ +/* SOCKLEN_T len, */ +/* ERL_NIF_TERM eFamily, */ +/* ERL_NIF_TERM* eSockAddr); */ static void esock_encode_sockaddr_broken(ErlNifEnv* env, struct sockaddr* sa, @@ -181,6 +193,7 @@ ErlNifMutex* esock_mutex_create(const char* pre, char* buf, SOCKET sock) } + /* *** esock_get_uint_from_map *** * * Simple utility function used to extract a unsigned int value from a map. @@ -411,43 +424,201 @@ BOOLEAN_T esock_decode_sockaddr(ErlNifEnv* env, efam) ); decode = esock_decode_domain(env, efam, &fam); if (0 >= decode) { - if (0 > decode) + if (0 > decode) { + UDBG( ("SUTIL", "esock_decode_sockaddr -> native (%d)\r\n", + decode) ); return esock_decode_sockaddr_native(env, eSockAddr, sockAddrP, fam, addrLenP); + } + UDBG( ("SUTIL", "esock_decode_sockaddr -> domain fail\r\n") ); return FALSE; } UDBG( ("SUTIL", "esock_decode_sockaddr -> fam: %d\r\n", fam) ); switch (fam) { case AF_INET: + UDBG( ("SUTIL", "esock_decode_sockaddr -> inet\r\n") ); return esock_decode_sockaddr_in(env, eSockAddr, &sockAddrP->in4, addrLenP); #if defined(HAVE_IN6) && defined(AF_INET6) case AF_INET6: + UDBG( ("SUTIL", "esock_decode_sockaddr -> inet6\r\n") ); return esock_decode_sockaddr_in6(env, eSockAddr, &sockAddrP->in6, addrLenP); #endif #ifdef HAS_AF_LOCAL case AF_LOCAL: + UDBG( ("SUTIL", "esock_decode_sockaddr -> local\r\n") ); return esock_decode_sockaddr_un(env, eSockAddr, &sockAddrP->un, addrLenP); #endif #ifdef AF_UNSPEC case AF_UNSPEC: + UDBG( ("SUTIL", "esock_decode_sockaddr -> unspec\r\n") ); return esock_decode_sockaddr_native(env, eSockAddr, sockAddrP, AF_UNSPEC, addrLenP); #endif default: + UDBG( ("SUTIL", "esock_decode_sockaddr -> UNKNOWN\r\n") ); return FALSE; } } +/* +++ esock_decode_sockaddrs +++ + * + * Decode a list of socket addresses: [socket:sockaddr()] + * The list must not be empty. + * This is intended to be used by SCTP functions (bindx and connectx). + */ + +#if defined(HAVE_SCTP) +extern +BOOLEAN_T esock_decode_sockaddrs(ErlNifEnv* env, + BOOLEAN_T dbg, + int family, + ERL_NIF_TERM eSockAddrs, + ESockAddress** sockAddrs, + int* addrCnt) +{ + unsigned int len; // Number of elements in eSockAddrs + + /* 1) We need to know how many addrs we have in the list */ + if (! GET_LIST_LEN(env, eSockAddrs, &len)) { + + UDBG2( dbg, ("SUTIL", + "esock_decode_sockaddrs -> " + "failed get (sockaddrs) list length\r\n") ); + + *sockAddrs = NULL; + *addrCnt = 0; + + return FALSE; + } + + /* 2) List must not be empty */ + if (len == 0) { + + UDBG2( dbg, ("SUTIL", + "esock_decode_sockaddrs -> " + "invalid (sockaddrs) list length (0)\r\n") ); + + *sockAddrs = NULL; + *addrCnt = 0; + + return FALSE; + } + + { + /* 3) Create the list of terms and calculate the needed memory size */ + ERL_NIF_TERM taddrs[len]; + unsigned int idx; + ERL_NIF_TERM esa, elem, tail, efam; + ESockAddress* addr; + ESockAddress* addrs; + SOCKLEN_T saLen; + + for (idx = 0, esa = eSockAddrs; idx < len; idx++) { + ESOCK_ASSERT( GET_LIST_ELEM(env, esa, &elem, &tail) ); + + if (! GET_MAP_VAL(env, elem, esock_atom_family, &efam)) { + /* Either not a map or the *mandatory* 'family' value + * is missing. Either way a fatal error! + */ + + UDBG2( dbg, ("SUTIL", + "esock_decode_sockaddrs -> " + "failed get map element (family): " + "\r\n element: %T" + "\r\n", elem) ); + + *sockAddrs = NULL; + *addrCnt = 0; + + return FALSE; + } + + /* Must be IPv4 or IPv6 */ + if (! + ( + ((family == AF_INET) && IS_IDENTICAL(efam, esock_atom_inet)) + || + ((family == AF_INET6) && (IS_IDENTICAL(efam, esock_atom_inet) || + IS_IDENTICAL(efam, esock_atom_inet6))) + ) + ) { + + UDBG2( dbg, ("SUTIL", + "esock_decode_sockaddrs -> " + "invalid family: %T" + "\r\n", efam) ); + + *sockAddrs = NULL; + *addrCnt = 0; + + return FALSE; + } + + taddrs[idx] = elem; + + esa = tail; + } + + + /* 4) Allocate memory for the socket address "array" */ + addrs = MALLOC(len * sizeof(ESockAddress)); + ESOCK_ASSERT( addrs != NULL ); + + /* 5) Iterate through the term array and decode each element into + * the socket address array. + * At this point we know that the addresses are either IPv4 or IPv6 + * (see loop at stage 3 above). + */ + for (idx = 0; idx < len; idx++) { + + elem = taddrs[idx]; + addr = &addrs[idx]; + + if (!esock_decode_sockaddr(env, elem, addr, &saLen)) { + + /* Ouch, failed to decode an element of the list */ + + UDBG2( dbg, ("SUTIL", + "esock_decode_sockaddrs -> " + "failed decode sockaddr %d:" + "\r\n %T" + "\r\n", idx, elem) ); + + FREE( addrs ); + + *sockAddrs = NULL; + *addrCnt = 0; + + return FALSE; + } + + (void) saLen; + + } + + *sockAddrs = addrs; + *addrCnt = len; + + } + + UDBG2( dbg, ("SUTIL", "esock_decode_sockaddrs -> done\r\n") ); + + return TRUE; + +} +#endif + + /* +++ esock_encode_sockaddr +++ * * Encode a socket address - sockaddr. In erlang its represented as @@ -1006,50 +1177,66 @@ BOOLEAN_T esock_decode_sockaddr_in6(ErlNifEnv* env, sockAddrP->sin6_family = AF_INET6; /* *** Extract (e) port number from map *** */ - if (! GET_MAP_VAL(env, eSockAddr, esock_atom_port, &eport)) + if (! GET_MAP_VAL(env, eSockAddr, esock_atom_port, &eport)) { + UDBG( ("SUTIL", "esock_decode_sockaddr_in6 -> failed extract port number\r\n") ); return FALSE; + } /* Decode port number */ - if (! GET_INT(env, eport, &port)) + if (! GET_INT(env, eport, &port)) { + UDBG( ("SUTIL", "esock_decode_sockaddr_in6 -> failed decode port number (%T)\r\n", eport) ); return FALSE; + } UDBG( ("SUTIL", "esock_decode_sockaddr_in6 -> port: %d\r\n", port) ); sockAddrP->sin6_port = htons(port); /* *** Extract (e) flowinfo from map *** */ - if (! GET_MAP_VAL(env, eSockAddr, esock_atom_flowinfo, &eflowInfo)) + if (! GET_MAP_VAL(env, eSockAddr, esock_atom_flowinfo, &eflowInfo)) { + UDBG( ("SUTIL", "esock_decode_sockaddr_in6 -> failed extract flowinfo\r\n") ); return FALSE; + } /* 4: Get the flowinfo */ - if (! GET_UINT(env, eflowInfo, &flowInfo)) + if (! GET_UINT(env, eflowInfo, &flowInfo)) { + UDBG( ("SUTIL", "esock_decode_sockaddr_in6 -> failed decode flowinfo (%T)\r\n", eport) ); return FALSE; + } UDBG( ("SUTIL", "esock_decode_sockaddr_in6 -> flowinfo: %d\r\n", flowInfo) ); sockAddrP->sin6_flowinfo = flowInfo; /* *** Extract (e) scope_id from map *** */ - if (! GET_MAP_VAL(env, eSockAddr, esock_atom_scope_id, &escopeId)) + if (! GET_MAP_VAL(env, eSockAddr, esock_atom_scope_id, &escopeId)) { + UDBG( ("SUTIL", "esock_decode_sockaddr_in6 -> failed extract scope_id\r\n") ); return FALSE; + } /* *** Get the scope_id *** */ - if (! GET_UINT(env, escopeId, &scopeId)) + if (! GET_UINT(env, escopeId, &scopeId)) { + UDBG( ("SUTIL", "esock_decode_sockaddr_in6 -> failed decode scope_id (%T)\r\n", escopeId) ); return FALSE; + } UDBG( ("SUTIL", "esock_decode_sockaddr_in6 -> scopeId: %d\r\n", scopeId) ); sockAddrP->sin6_scope_id = scopeId; /* *** Extract (e) address from map *** */ - if (! GET_MAP_VAL(env, eSockAddr, esock_atom_addr, &eaddr)) + if (! GET_MAP_VAL(env, eSockAddr, esock_atom_addr, &eaddr)) { + UDBG( ("SUTIL", "esock_decode_sockaddr_in6 -> failed extract address\r\n") ); return FALSE; + } /* Decode address */ if (!esock_decode_in6_addr(env, eaddr, - &sockAddrP->sin6_addr)) + &sockAddrP->sin6_addr)) { + UDBG( ("SUTIL", "esock_decode_sockaddr_in6 -> failed decode address (%T))\r\n", eaddr) ); return FALSE; + } *addrLen = sizeof(struct sockaddr_in6); @@ -1636,6 +1823,9 @@ BOOLEAN_T esock_decode_in6_addr(ErlNifEnv* env, "\r\n", eAddr) ); if (IS_ATOM(env, eAddr)) { + + UDBG( ("SUTIL", "esock_decode_in6_addr -> atom\r\n") ); + /* This is either 'any' or 'loopback' */ if (COMPARE(esock_atom_loopback, eAddr) == 0) { @@ -1647,32 +1837,50 @@ BOOLEAN_T esock_decode_in6_addr(ErlNifEnv* env, } } else { - /* This is an 8-tuple */ + /* This is (supposed to be) an 8-tuple */ const ERL_NIF_TERM* tuple; int arity; size_t n; struct in6_addr sa; + UDBG( ("SUTIL", "esock_decode_in6_addr -> tuple\r\n") ); + if (! GET_TUPLE(env, eAddr, &arity, &tuple)) return FALSE; + + UDBG( ("SUTIL", "esock_decode_in6_addr -> arity: %d\r\n", arity) ); + n = arity << 1; - if (n != sizeof(sa.s6_addr)) + if (n != sizeof(sa.s6_addr)) { + UDBG( ("SUTIL", + "esock_decode_in6_addr -> invalid size (%d /= %d)\r\n", + n, sizeof(sa.s6_addr)) ); return FALSE; + } for (n = 0; n < arity; n++) { int v; + UDBG( ("SUTIL", "esock_decode_in6_addr -> try get element %d\r\n", + n) ); + if (! GET_INT(env, tuple[n], &v) || - v < 0 || 65535 < v) + v < 0 || 65535 < v) { + + UDBG( ("SUTIL", + "esock_decode_in6_addr -> failed get part %d\r\n", n) ); return FALSE; + } put_int16(v, sa.s6_addr + (n << 1)); } *inAddrP = sa; } + UDBG( ("SUTIL", "esock_decode_in6_addr -> (success) done\r\n") ); + return TRUE; } #endif @@ -2462,7 +2670,7 @@ BOOLEAN_T esock_decode_sockaddr_native(ErlNifEnv* env, * assuming at least the ->family field can be accessed * and hence at least 0 bytes of address */ -static +extern void esock_encode_sockaddr_native(ErlNifEnv* env, struct sockaddr* addr, SOCKLEN_T len, @@ -3348,4 +3556,38 @@ BOOLEAN_T esock_is_integer(ErlNifEnv *env, ERL_NIF_TERM term) return FALSE; } + +extern +void* esock_dlopen(char* name) +{ +#if defined(HAVE_DLOPEN) + return dlopen(name, RTLD_NOW); +#else + return NULL; +#endif +} + +extern +void* esock_dlsym(void* handle, const char* symbolName) +{ +#if defined(HAVE_DLOPEN) + void* sym; + char* e; + + dlerror(); // Clear errors + + // The return of this cannot be trusted, + // we need to explicitly check errors! + sym = dlsym(handle, symbolName); + if ((e = dlerror()) != NULL) { + return NULL; + } else { + return sym; + } +#else + return NULL; +#endif +} + + #endif diff --git a/erts/emulator/nifs/common/socket_util.h b/erts/emulator/nifs/common/socket_util.h index 37f8a4b5bd51..aaf2da31fd78 100644 --- a/erts/emulator/nifs/common/socket_util.h +++ b/erts/emulator/nifs/common/socket_util.h @@ -107,6 +107,15 @@ BOOLEAN_T esock_decode_sockaddr(ErlNifEnv* env, ERL_NIF_TERM eSockAddr, ESockAddress* sockAddrP, SOCKLEN_T* addrLen); +#if defined(HAVE_SCTP_H) +extern +BOOLEAN_T esock_decode_sockaddrs(ErlNifEnv* env, + BOOLEAN_T dbg, + int family, + ERL_NIF_TERM eSockAddrs, + ESockAddress** sockAddrs, + int* addrCnt); +#endif extern void esock_encode_sockaddr(ErlNifEnv* env, ESockAddress* sockAddrP, @@ -159,6 +168,13 @@ void esock_encode_sockaddr_un(ErlNifEnv* env, ERL_NIF_TERM* eSockAddr); #endif +extern +void esock_encode_sockaddr_native(ErlNifEnv* env, + struct sockaddr* addr, + SOCKLEN_T len, + ERL_NIF_TERM eFamily, + ERL_NIF_TERM* eSockAddr); + #ifdef HAVE_NETPACKET_PACKET_H extern void esock_encode_sockaddr_ll(ErlNifEnv* env, @@ -336,4 +352,10 @@ MSG_FUNCS extern BOOLEAN_T esock_is_integer(ErlNifEnv *env, ERL_NIF_TERM term); +extern +void* esock_dlopen(char* name); + +extern +void* esock_dlsym(void* handle, const char* symbolName); + #endif // SOCKET_UTIL_H__ diff --git a/erts/emulator/nifs/unix/unix_socket_syncio.c b/erts/emulator/nifs/unix/unix_socket_syncio.c index 922b127ff9ea..63a33875e1ec 100644 --- a/erts/emulator/nifs/unix/unix_socket_syncio.c +++ b/erts/emulator/nifs/unix/unix_socket_syncio.c @@ -25,10 +25,13 @@ * * essio = ESock Synchronous I/O * + * + * + * */ #ifdef HAVE_CONFIG_H -# include "config.h" +#include "config.h" #endif #ifdef ESOCK_ENABLE @@ -69,6 +72,116 @@ #include + +/* SCTP support -- currently for UNIX platforms only: */ +#undef HAVE_SCTP + +#define ASSOC_ID_LEN 4 + +#if defined(HAVE_SCTP_H) + +#include + +/* SCTP Socket API Draft from version 11 on specifies that netinet/sctp.h must + * explicitly define HAVE_SCTP in case when SCTP is supported, but Solaris 10 + * still apparently uses Draft 10, and does not define that symbol, so we have + * to define it explicitly: + */ +#ifndef HAVE_SCTP +# define HAVE_SCTP +#endif + +/* These changed in draft 11, so SOLARIS10 uses the old MSG_* */ +#if ! HAVE_DECL_SCTP_UNORDERED +# define SCTP_UNORDERED MSG_UNORDERED +#endif +#if ! HAVE_DECL_SCTP_ADDR_OVER +# define SCTP_ADDR_OVER MSG_ADDR_OVER +#endif +#if ! HAVE_DECL_SCTP_ABORT +# define SCTP_ABORT MSG_ABORT +#endif +#if ! HAVE_DECL_SCTP_EOF +# define SCTP_EOF MSG_EOF +#endif + +/* More Solaris 10 fixes: */ +#if ! HAVE_DECL_SCTP_CLOSED && HAVE_DECL_SCTPS_IDLE +# define SCTP_CLOSED SCTPS_IDLE +# undef HAVE_DECL_SCTP_CLOSED +# define HAVE_DECL_SCTP_CLOSED 1 +#endif +#if ! HAVE_DECL_SCTP_BOUND && HAVE_DECL_SCTPS_BOUND +# define SCTP_BOUND SCTPS_BOUND +# undef HAVE_DECL_SCTP_BOUND +# define HAVE_DECL_SCTP_BOUND 1 +#endif +#if ! HAVE_DECL_SCTP_LISTEN && HAVE_DECL_SCTPS_LISTEN +# define SCTP_LISTEN SCTPS_LISTEN +# undef HAVE_DECL_SCTP_LISTEN +# define HAVE_DECL_SCTP_LISTEN 1 +#endif +#if ! HAVE_DECL_SCTP_COOKIE_WAIT && HAVE_DECL_SCTPS_COOKIE_WAIT +# define SCTP_COOKIE_WAIT SCTPS_COOKIE_WAIT +# undef HAVE_DECL_SCTP_COOKIE_WAIT +# define HAVE_DECL_SCTP_COOKIE_WAIT 1 +#endif +#if ! HAVE_DECL_SCTP_COOKIE_ECHOED && HAVE_DECL_SCTPS_COOKIE_ECHOED +# define SCTP_COOKIE_ECHOED SCTPS_COOKIE_ECHOED +# undef HAVE_DECL_SCTP_COOKIE_ECHOED +# define HAVE_DECL_SCTP_COOKIE_ECHOED 1 +#endif +#if ! HAVE_DECL_SCTP_ESTABLISHED && HAVE_DECL_SCTPS_ESTABLISHED +# define SCTP_ESTABLISHED SCTPS_ESTABLISHED +# undef HAVE_DECL_SCTP_ESTABLISHED +# define HAVE_DECL_SCTP_ESTABLISHED 1 +#endif +#if ! HAVE_DECL_SCTP_SHUTDOWN_PENDING && HAVE_DECL_SCTPS_SHUTDOWN_PENDING +# define SCTP_SHUTDOWN_PENDING SCTPS_SHUTDOWN_PENDING +# undef HAVE_DECL_SCTP_SHUTDOWN_PENDING +# define HAVE_DECL_SCTP_SHUTDOWN_PENDING 1 +#endif +#if ! HAVE_DECL_SCTP_SHUTDOWN_SENT && HAVE_DECL_SCTPS_SHUTDOWN_SENT +# define SCTP_SHUTDOWN_SENT SCTPS_SHUTDOWN_SENT +# undef HAVE_DECL_SCTP_SHUTDOWN_SENT +# define HAVE_DECL_SCTP_SHUTDOWN_SENT 1 +#endif +#if ! HAVE_DECL_SCTP_SHUTDOWN_RECEIVED && HAVE_DECL_SCTPS_SHUTDOWN_RECEIVED +# define SCTP_SHUTDOWN_RECEIVED SCTPS_SHUTDOWN_RECEIVED +# undef HAVE_DECL_SCTP_SHUTDOWN_RECEIVED +# define HAVE_DECL_SCTP_SHUTDOWN_RECEIVED 1 +#endif +#if ! HAVE_DECL_SCTP_SHUTDOWN_ACK_SENT && HAVE_DECL_SCTPS_SHUTDOWN_ACK_SENT +# define SCTP_SHUTDOWN_ACK_SENT SCTPS_SHUTDOWN_ACK_SENT +# undef HAVE_DECL_SCTP_SHUTDOWN_ACK_SENT +# define HAVE_DECL_SCTP_SHUTDOWN_ACK_SENT 1 +#endif +/* New spelling in lksctp 2.6.22 or maybe even earlier: + * adaption -> adaptation + */ +#if !defined(SCTP_ADAPTATION_LAYER) && defined (SCTP_ADAPTION_LAYER) +# define SCTP_ADAPTATION_LAYER SCTP_ADAPTION_LAYER +# define SCTP_ADAPTATION_INDICATION SCTP_ADAPTION_INDICATION +# define sctp_adaptation_event sctp_adaption_event +# define sctp_setadaptation sctp_setadaption +# define sn_adaptation_event sn_adaption_event +# define sai_adaptation_ind sai_adaption_ind +# define ssb_adaptation_ind ssb_adaption_ind +# define sctp_adaptation_layer_event sctp_adaption_layer_event +#endif + +/* Have Static SCTP */ + +#if defined(HAVE_SCTP_BINDX) || \ + defined(HAVE_SCTP_PEELOFF) || \ + defined(HAVE_SCTP_GETLADDRS) || defined(HAVE_SCTP_FREELADDRS) || \ + defined(HAVE_SCTP_GETPADDRS) || defined(HAVE_SCTP_FREEPADDRS) || \ + defined(HAVE_SCTP_CONNECTX) +#define HAVE_STATIC_SCTP TRUE +#endif + +#endif /* #if defined(HAVE_SCTP_H) */ + #include "prim_socket_int.h" #include "socket_util.h" #include "socket_io.h" @@ -90,14 +203,38 @@ #define sock_accept(s, addr, len) accept((s), (addr), (len)) #endif #define sock_bind(s, addr, len) bind((s), (addr), (len)) +#define sock_bindx(s, addr, acnt, a) \ + ctrl.sctp.bindx((s), (addr), (acnt), (a)) +#define sock_ensure_bindx(e) \ + if (ctrl.sctp.bindx == NULL) \ + return enif_raise_exception((e), MKA((e), "notsup")); #define sock_close(s) close((s)) // #define sock_close_event(e) /* do nothing */ #define sock_connect(s, addr, len) connect((s), (addr), (len)) +#define sock_connectx(s, addrs, acnt, aidp) \ + ctrl.sctp.connectx((s), (addrs), (acnt), (aidp)) +#define sock_ensure_connectx(e) \ + if (ctrl.sctp.connectx == NULL) \ + return enif_raise_exception((e), MKA((e), "notsup")); #define sock_errno() errno // #define sock_listen(s, b) listen((s), (b)) // #define sock_name(s, addr, len) getsockname((s), (addr), (len)) +#define sock_names(s, aid, ap) ctrl.sctp.getladdrs((s), (aid), (ap)) +#define sock_ensure_names(e) \ + if (ctrl.sctp.getladdrs == NULL) \ + return enif_raise_exception((e), MKA((e), "notsup")); +#define sock_ntohs(x) ntohs((x)) +#define sock_htonl(x) htonl((x)) #define sock_open(domain, type, proto) socket((domain), (type), (proto)) +#define sock_peeloff(s, aid) ctrl.sctp.peeloff((s), (aid)) +#define sock_ensure_peeloff(E) \ + if (ctrl.sctp.peeloff == NULL) \ + return enif_raise_exception((E), MKA((E), "notsup")) #define sock_peer(s, addr, len) getpeername((s), (addr), (len)) +#define sock_peers(s, aid, ap) ctrl.sctp.getpaddrs((s), (aid), (ap)) +#define sock_ensure_peers(e) \ + if (ctrl.sctp.getpaddrs == NULL) \ + return enif_raise_exception((e), MKA((e), "notsup")); #define sock_recv(s,buf,len,flag) recv((s),(buf),(len),(flag)) #define sock_recvfrom(s,buf,blen,flag,addr,alen) \ recvfrom((s),(buf),(blen),(flag),(addr),(alen)) @@ -126,14 +263,62 @@ * * * =================================================================== */ +typedef struct { + /* If we "have" sctp, this is TRUE otherwise FALSE */ + BOOLEAN_T have; + + /* If we do *not* "have" sctp, all these function pointers are set to NULL */ +#if defined(HAVE_SCTP) + int (*bindx) (int sd, + struct sockaddr *addrs, + int addrcnt, + int flags); + int (*connectx) (int sd, + const struct sockaddr *addrs, + int addrcnt, + sctp_assoc_t *id); + int (*peeloff) (int sd, + sctp_assoc_t assoc_id); + int (*getpaddrs) (int sd, + sctp_assoc_t id, + struct sockaddr **addrs); + void (*freepaddrs) (struct sockaddr *addrs); + int (*getladdrs) (int sd, + sctp_assoc_t id, + struct sockaddr **addrs); + void (*freeladdrs) (struct sockaddr *addrs); +#else + /* In this case we also do not have the SCTP types so we need "dummy" defs */ + int (*bindx) (int sd, + void *addrs, + int addrcnt, + int flags); + int (*connectx) (int sd, + const void *addrs, + int addrcnt, + int *id); + int (*peeloff) (int sd, + int assoc_id); + int (*getpaddrs) (int sd, + int id, + void **addrs); + void (*freepaddrs) (int *addrs); + int (*getladdrs) (int sd, + int id, + void **addrs); + void (*freeladdrs) (void *addrs); +#endif +} ESSIOSctp; + + typedef struct { /* Misc stuff */ BOOLEAN_T dbg; BOOLEAN_T sockDbg; + ESSIOSctp sctp; } ESSIOControl; - /* ======================================================================== * * Function Forwards * * ======================================================================== * @@ -228,6 +413,8 @@ static BOOLEAN_T essio_accept_accepted(ErlNifEnv* env, ErlNifPid pid, ERL_NIF_TERM* result); +static ERL_NIF_TERM essio_info_sctp(ErlNifEnv* env); + static BOOLEAN_T send_check_writer(ErlNifEnv* env, ESockDescriptor* descP, ERL_NIF_TERM ref, @@ -299,6 +486,11 @@ static void encode_cmsgs(ErlNifEnv* env, struct msghdr* msgHdrP, ERL_NIF_TERM* eCMsg); +static ERL_NIF_TERM esock_encode_msg_flags_convert2sctp(ErlNifEnv* env, + ESockDescriptor* descP, + ERL_NIF_TERM flags); + + #if defined(HAVE_SENDFILE) static int essio_sendfile(ErlNifEnv* env, ESockDescriptor* descP, @@ -386,6 +578,13 @@ static void recv_error_current_reader(ErlNifEnv* env, ERL_NIF_TERM sockRef, ERL_NIF_TERM reason); +#if defined(HAVE_SCTP) +static ERL_NIF_TERM essio_addrs_encode(ErlNifEnv* env, + ESockDescriptor* descP, + int n, + struct sockaddr* sa); +#endif + static ERL_NIF_TERM essio_ioctl_gifconf(ErlNifEnv* env, ESockDescriptor* descP); /* esock_ioctl_fionread */ @@ -706,6 +905,148 @@ static BOOLEAN_T do_stop(ErlNifEnv* env, ESockDescriptor* descP); +static void essio_sctp_init(void); + +#if !defined(HAVE_SCTP) +static void essio_sctp_init_false(void); +#endif + +static void essio_encode_sctp_notification(ErlNifEnv* env, + ESockDescriptor* descP, + ErlNifBinary* binP, + ERL_NIF_TERM* eEvent); + +#if defined(HAVE_SCTP) + +#if defined(HAVE_STATIC_SCTP) +static void essio_sctp_init_static(void); +#else +static void essio_sctp_init_dynamic(void); +#endif + +#if defined(SCTP_ASSOC_CHANGE) +static void essio_encode_sctp_notif_assoc_change(ErlNifEnv* env, + ESockDescriptor* descP, + struct sctp_assoc_change* acp, + ERL_NIF_TERM* eEvent); +#endif +#if defined(SCTP_PEER_ADDR_CHANGE) +static void essio_encode_sctp_notif_paddr_change(ErlNifEnv* env, + ESockDescriptor* descP, + struct sctp_paddr_change* p, + ERL_NIF_TERM* eEvent); +static void essio_encode_sockaddr_storage(ErlNifEnv* env, + ESockDescriptor* descP, + struct sockaddr_storage* addr, + ERL_NIF_TERM* eaddr); +#endif +#if defined(SCTP_SEND_FAILED) +static void essio_encode_sctp_notif_send_failed(ErlNifEnv* env, + ESockDescriptor* descP, + struct sctp_send_failed* p, + ERL_NIF_TERM* eEvent); +#endif +#if defined(SCTP_REMOTE_ERROR) +static void essio_encode_sctp_notif_remote_error(ErlNifEnv* env, + ESockDescriptor* descP, + struct sctp_remote_error* p, + ERL_NIF_TERM* eEvent); +static void essio_encode_sctp_notif_remote_causes(ErlNifEnv* env, + char* chunkP, + int chunkTLen, + ERL_NIF_TERM* eRCauses); +#endif +#if defined(SCTP_SHUTDOWN_EVENT) +static void essio_encode_sctp_notif_shutdown_event(ErlNifEnv* env, + ESockDescriptor* descP, + struct sctp_shutdown_event* p, + ERL_NIF_TERM* eEvent); +#endif +#if defined(SCTP_ADAPTATION_INDICATION) +static void essio_encode_sctp_notif_adapt_event(ErlNifEnv* env, + ESockDescriptor* descP, + struct sctp_adaptation_event* p, + ERL_NIF_TERM* eEvent); +#endif +#if defined(SCTP_PARTIAL_DELIVERY_EVENT) +static void essio_encode_sctp_notif_pdapi_event(ErlNifEnv* env, + ESockDescriptor* descP, + struct sctp_pdapi_event* p, + ERL_NIF_TERM* eEvent); +#endif +#if defined(SCTP_AUTHENTICATION_INDICATION) +static void essio_encode_sctp_notif_authkey(ErlNifEnv* env, + ESockDescriptor* descP, + struct sctp_authkey_event* p, + ERL_NIF_TERM* eEvent); +#endif +#if defined(SCTP_SENDER_DRY_EVENT) +static +void essio_encode_sctp_notif_sender_dry(ErlNifEnv* env, + ESockDescriptor* descP, + struct sctp_sender_dry_event* p, + ERL_NIF_TERM* eEvent); +#endif +#if defined(SCTP_SENDER_DRY_EVENT) || defined(SCTP_SEND_FAILED) +static +void essio_encode_sctp_sndrcvinfo(ErlNifEnv* env, + ESockDescriptor* descP, + struct sctp_sndrcvinfo* p, + ERL_NIF_TERM* einfo); + +#endif + +#if defined(SCTP_STREAM_RESET_EVENT) +static +void essio_encode_sctp_notif_stream_reset_event(ErlNifEnv* env, + ESockDescriptor* descP, + struct sctp_stream_reset_event* p, + ERL_NIF_TERM* eEvent); +#endif + +#if defined(SCTP_ASSOC_RESET_EVENT) +static +void essio_encode_sctp_notif_assoc_reset_event(ErlNifEnv* env, + ESockDescriptor* descP, + struct sctp_assoc_reset_event* p, + ERL_NIF_TERM* eEvent); +#endif + +#if defined(SCTP_STREAM_CHANGE_EVENT) +static +void essio_encode_sctp_notif_stream_change_event(ErlNifEnv* env, + ESockDescriptor* descP, + struct sctp_stream_change_event* p, + ERL_NIF_TERM* eEvent); +#endif + +#if defined(SCTP_SEND_FAILED_EVENT) +static +void essio_encode_sctp_notif_send_failed_event(ErlNifEnv* env, + ESockDescriptor* descP, + struct sctp_send_failed_event* p, + ERL_NIF_TERM* eEvent); +static +ERL_NIF_TERM essio_encode_sctp_notif_send_failed_event_flags(ErlNifEnv* env, + ESockDescriptor* descP, + unsigned int flags); + +static +void essio_encode_sctp_sndinfo(ErlNifEnv* env, + ESockDescriptor* descP, + struct sctp_sndinfo* p, + ERL_NIF_TERM* einfo); + +#endif +static void essio_encode_sctp_notif_generic(ErlNifEnv* env, + ESockDescriptor* descP, + ERL_NIF_TERM etype, + union sctp_notification* p, + ERL_NIF_TERM* eEvent); + +#endif // if defined(HAVE_SCTP) + + /* =================================================================== * * * * Local (global) variables * @@ -734,10 +1075,210 @@ int essio_init(unsigned int numThreads, ctrl.dbg = dataP->dbg; ctrl.sockDbg = dataP->sockDbg; + essio_sctp_init(); + return ESOCK_IO_OK; } + +static +void essio_sctp_init(void) +{ +#if defined(HAVE_SCTP) + + ctrl.sctp.have = TRUE; + + /* Check the size of SCTP AssocID -- currently both this nif and the + * Erlang part require 32 bit: + * ??? + * ??? + */ + ERTS_CT_ASSERT(sizeof(sctp_assoc_t)==ASSOC_ID_LEN); + +#if defined(HAVE_STATIC_SCTP) + + essio_sctp_init_static(); + +#else // if defined(HAVE_STATIC_SCTP) + + essio_sctp_init_dynamic(); + +#endif // if defined(HAVE_STATIC_SCTP) + +#else + + ctrl.sctp.have = FALSE; + + essio_sctp_init_false(); + +#endif +} + + + +#if defined(HAVE_SCTP) + +#if defined(HAVE_STATIC_SCTP) +static +void essio_sctp_init_static(void) +{ +#if defined(HAVE_SCTP_BINDX) + ctrl.sctp.bindx = sctp_bindx; +#else + ctrl.sctp.bindx = NULL; +#endif + +#if defined(HAVE_SCTP_PEELOFF) + ctrl.sctp.peeloff = sctp_peeloff; +#else + ctrl.sctp.peeloff = NULL; +#endif + +#if defined(HAVE_SCTP_GETLADDRS) && defined(HAVE_SCTP_FREELADDRS) + ctrl.sctp.getladdrs = sctp_getladdrs; + ctrl.sctp.freeladdrs = sctp_freeladdrs; +#else + ctrl.sctp.getladdrs = NULL; + ctrl.sctp.freeladdrs = NULL; +#endif + +#if defined(HAVE_SCTP_GETPADDRS) && defined(HAVE_SCTP_FREEPADDRS) + ctrl.sctp.getpaddrs = sctp_getpaddrs; + ctrl.sctp.freepaddrs = sctp_freepaddrs; +#else + ctrl.sctp.getpaddrs = NULL; + ctrl.sctp.freepaddrs = NULL; +#endif + +#if defined(HAVE_SCTP_CONNECTX) + ctrl.sctp.connectx = sctp_connectx; +#else + ctrl.sctp.connectx = NULL; +#endif +} + +#else // if defined(HAVE_STATIC_SCTP) + +static +void essio_sctp_init_dynamic(void) +{ + +#ifndef LIBSCTP +#error LIBSCTP not defined +#else +#define LIBSCTP_STR ESOCK_STRINGIFY(LIBSCTP) +#endif + + { + static void* handle; + + if ((handle = ESOCK_DLOPEN(LIBSCTP_STR)) != NULL) { + void* ptr; + + /* If bindx does not exist, we give up */ + + if ((ptr = ESOCK_DLSYM(handle, "sctp_bindx")) != NULL) { + + ctrl.sctp.bindx = ptr; + + if ((ptr = ESOCK_DLSYM(handle, "sctp_peeloff")) != NULL) { + ctrl.sctp.peeloff = ptr; + } else { + ctrl.sctp.peeloff = NULL; + } + + if ((ptr = ESOCK_DLSYM(handle, "sctp_getladdrs")) != NULL) { + ctrl.sctp.getladdrs = ptr; + } else { + ctrl.sctp.getladdrs = NULL; + } + + if ((ptr = ESOCK_DLSYM(handle, "sctp_freeladdrs")) != NULL) { + ctrl.sctp.freeladdrs = ptr; + } else { + ctrl.sctp.freeladdrs = NULL; + } + + if ((ptr = ESOCK_DLSYM(handle, "sctp_getpaddrs")) != NULL) { + ctrl.sctp.getpaddrs = ptr; + } else { + ctrl.sctp.getpaddrs = NULL; + } + + if ((ptr = ESOCK_DLSYM(handle, "sctp_freepaddrs")) != NULL) { + ctrl.sctp.freepaddrs = ptr; + } else { + ctrl.sctp.freepaddrs = NULL; + } + + if ((ptr = ESOCK_DLSYM(handle, "sctp_connectx")) != NULL) { + ctrl.sctp.connectx = ptr; + } else { + ctrl.sctp.connectx = NULL; + } + + } else { + + /* Failed to find the one mandatory symbol (function) ... + * write a warning / error message. */ + + esock_warning_msg("[UNIX-ESSIO] " + "Failed locating (mandatory) bindx " + "symbol in sctp dynamic library" + "\r\n"); + + ctrl.sctp.bindx = NULL; + ctrl.sctp.peeloff = NULL; + ctrl.sctp.getladdrs = NULL; + ctrl.sctp.freeladdrs = NULL; + ctrl.sctp.getpaddrs = NULL; + ctrl.sctp.freepaddrs = NULL; + ctrl.sctp.connectx = NULL; + + } + + } else { + + /* The lib supposedly exist but we failed to open ... + * write a warning / error message. */ + + esock_warning_msg("[UNIX-ESSIO] " + "Failed open sctp dynamic library: %s" + "\r\n", LIBSCTP_STR); + + ctrl.sctp.bindx = NULL; + ctrl.sctp.peeloff = NULL; + ctrl.sctp.getladdrs = NULL; + ctrl.sctp.freeladdrs = NULL; + ctrl.sctp.getpaddrs = NULL; + ctrl.sctp.freepaddrs = NULL; + ctrl.sctp.connectx = NULL; + + } + + } + +} + +#endif // if defined(HAVE_STATIC_SCTP) + +#endif // if defined(HAVE_SCTP) + +#if !defined(HAVE_SCTP) +static +void essio_sctp_init_false(void) +{ + ctrl.sctp.bindx = NULL; + ctrl.sctp.peeloff = NULL; + ctrl.sctp.getladdrs = NULL; + ctrl.sctp.freeladdrs = NULL; + ctrl.sctp.getpaddrs = NULL; + ctrl.sctp.freepaddrs = NULL; + ctrl.sctp.connectx = NULL; +} +#endif + /* * For "standard" (unix) synchronous I/O, this is just a dummy function. * Also, will we ever call this? @@ -758,8 +1299,9 @@ extern ERL_NIF_TERM essio_info(ErlNifEnv* env) { ERL_NIF_TERM info; - ERL_NIF_TERM keys[] = {esock_atom_name}; - ERL_NIF_TERM vals[] = {MKA(env, "unix_essio")}; + ERL_NIF_TERM sctp = essio_info_sctp(env); + ERL_NIF_TERM keys[] = {esock_atom_name, MKA(env, "sctp")}; + ERL_NIF_TERM vals[] = {MKA(env, "unix_essio"), sctp}; unsigned int numKeys = NUM(keys); unsigned int numVals = NUM(vals); @@ -770,7 +1312,90 @@ ERL_NIF_TERM essio_info(ErlNifEnv* env) } +static +ERL_NIF_TERM essio_info_sctp(ErlNifEnv* env) +{ +#if defined(HAVE_SCTP) + ERL_NIF_TERM sctp; + ERL_NIF_TERM sctpKeys[] = + {MKA(env, "bindx"), + MKA(env, "peeoff"), + MKA(env, "getladdrs"), + MKA(env, "freeladdrs"), + MKA(env, "getpaddrs"), + MKA(env, "freepaddrs"), + MKA(env, "connectx")}; + ERL_NIF_TERM sctpVals[] = + {BOOL2ATOM((ctrl.sctp.bindx != NULL)), + BOOL2ATOM((ctrl.sctp.peeloff != NULL)), + BOOL2ATOM((ctrl.sctp.getladdrs != NULL)), + BOOL2ATOM((ctrl.sctp.freeladdrs != NULL)), + BOOL2ATOM((ctrl.sctp.getpaddrs != NULL)), + BOOL2ATOM((ctrl.sctp.freepaddrs != NULL)), + BOOL2ATOM((ctrl.sctp.connectx != NULL)), + }; + unsigned int numSctpKeys = NUM(sctpKeys); + unsigned int numSctpVals = NUM(sctpVals); + + ESOCK_ASSERT( numSctpKeys == numSctpVals ); + ESOCK_ASSERT( MKMA(env, sctpKeys, sctpVals, numSctpKeys, &sctp) ); + + return sctp; +#else + VOID(env); + + return esock_atom_undefined; +#endif +} + + +/* ******************************************************************* + * essio_command - Handle command + * + * Special command for the backend or always pass the command(s) + * through and let the backend decide for itself? + * + * If we get this far we know that the command is ok as far as the + * esock_command function knows. But there may be commands unknown + * to esock_command, passed unchecked here. + */ + +extern +ERL_NIF_TERM essio_command(ErlNifEnv* env, + ERL_NIF_TERM command, + ERL_NIF_TERM cdata) +{ + ERL_NIF_TERM res; + + SGDBG( ("UNIX-ESSIO", "essio_command -> entry with %T\r\n", command) ); + + if (COMPARE(command, esock_atom_socket_debug) == 0) { + BOOLEAN_T dbg; + if (! esock_decode_bool(cdata, &dbg)) { + res = esock_atom_invalid; + } else { + ctrl.sockDbg = dbg; // We should really have a mutex for this... + res = esock_atom_ok; + } + } else if (COMPARE(command, esock_atom_debug) == 0) { + BOOLEAN_T dbg; + if (! esock_decode_bool(cdata, &dbg)) { + res = esock_atom_invalid; + } else { + ctrl.dbg = dbg; // We should really have a mutex for this... + res = esock_atom_ok; + } + } else { + res = esock_atom_invalid; + } + + SGDBG( ("UNIX-ESSIO", "essio_command -> done when res: %T\r\n", res) ); + + return esock_atom_ok; +} + + /* ======================================================================== * essio_open - create an endpoint (from an existing fd) for communication * @@ -1249,16 +1874,89 @@ ERL_NIF_TERM essio_bind(ErlNifEnv* env, ESockAddress* sockAddrP, SOCKLEN_T addrLen) { + SSDBG( descP, ("UNIX-ESSIO", "essio_bind {%d} -> entry\r\n", + descP->sock) ); + if (! IS_OPEN(descP->readState)) return esock_make_error_closed(env); + SSDBG( descP, ("UNIX-ESSIO", "essio_bind {%d} -> try bind\r\n", + descP->sock) ); + if (sock_bind(descP->sock, &sockAddrP->sa, addrLen) < 0) { return esock_make_error_errno(env, sock_errno()); } + SSDBG( descP, ("UNIX-ESSIO", "essio_bind {%d} -> bound\r\n", + descP->sock) ); + + descP->readState |= ESOCK_STATE_BOUND; + + return esock_atom_ok; +} + + +/* ======================================================================== + */ +extern +ERL_NIF_TERM essio_bindx(ErlNifEnv* env, + ESockDescriptor* descP, + ESockAddress* sockAddrP, + int addrCnt, + int action) +{ +#if defined(HAVE_SCTP) + + SSDBG( descP, + ("UNIX-ESSIO", "essio_bindx {%d} -> entry with" + "\r\n address count: %d" + "\r\n action: 0x%X" + "\r\nwhen" + "\r\n bindx: %s" + "\r\n", + descP->sock, addrCnt, action, + ((ctrl.sctp.bindx == NULL) ? "not supported" : "supported")) ); + + sock_ensure_bindx(env); + + if (! IS_OPEN(descP->readState)) + return esock_make_error_closed(env); + + SSDBG( descP, ("UNIX-ESSIO", "essio_bindx {%d} -> try bindx\r\n", + descP->sock) ); + + /* + * Add additional addresses by calling bindx with one address + * at a time, since this is what some OSes promise will work. + */ + for (int i = 0; i < addrCnt; i++) { + + SSDBG( descP, + ("UNIX-ESSIO", + "essio_bindx(%d) -> try bindx for address %d (of %d)\r\n", + descP->sock, i, addrCnt) ); + + if (sock_bindx(descP->sock, &sockAddrP[i].sa, 1, action) < 0) { + return esock_make_error_errno(env, sock_errno()); + } + + } + + SSDBG( descP, ("UNIX-ESSIO", "essio_bindx {%d} -> bound\r\n", + descP->sock) ); + descP->readState |= ESOCK_STATE_BOUND; return esock_atom_ok; + +#else + + /* We should never be called in this case, + * but just to be on the safe side... + */ + return enif_raise_exception(env, MKA(env, "notsup")); + +#endif } @@ -1277,6 +1975,15 @@ ERL_NIF_TERM essio_connect(ErlNifEnv* env, ESOCK_ASSERT( enif_self(env, &self) != NULL ); + SSDBG( descP, + ("UNIX-ESSIO", + "essio_connect(%d) -> entry with" + "\r\n sockRef: %T" + "\r\n connRef: %T" + "\r\n writeState: %d" + "\r\n", + descP->sock, sockRef, connRef, descP->writeState) ); + /* * Verify that we are in the proper state */ @@ -1293,6 +2000,11 @@ ERL_NIF_TERM essio_connect(ErlNifEnv* env, if (descP->connectorP != NULL) { /* Connect in progress */ + SSDBG( descP, + ("UNIX-ESSIO", + "essio_connect(%d) -> connect in progress" + "\r\n", descP->sock) ); + if (COMPARE_PIDS(&self, &descP->connector.pid) != 0) { /* Other process has connect in progress */ if (addrP != NULL) { @@ -1313,12 +2025,29 @@ ERL_NIF_TERM essio_connect(ErlNifEnv* env, descP->connectorP = NULL; descP->writeState &= ~ESOCK_STATE_CONNECTING; + SSDBG( descP, + ("UNIX-ESSIO", + "essio_connect(%d) -> try verify connected" + "\r\n", descP->sock) ); + if (! verify_is_connected(descP, &save_errno)) { + + SSDBG( descP, + ("UNIX-ESSIO", + "essio_connect(%d) -> connect verification failed" + "\r\n errno: %d" + "\r\n", descP->sock, save_errno) ); + return esock_make_error_errno(env, save_errno); } descP->writeState |= ESOCK_STATE_CONNECTED; + SSDBG( descP, + ("UNIX-ESSIO", + "essio_connect(%d) -> connection verified" + "\r\n", descP->sock) ); + return esock_atom_ok; } @@ -1332,6 +2061,15 @@ ERL_NIF_TERM essio_connect(ErlNifEnv* env, return esock_raise_invalid(env, esock_atom_state); /* Initial connect call, with address */ + + SSDBG( descP, + ("UNIX-ESSIO", + "essio_connect(%d) -> try connect with" + "\r\n port: %d" + "\r\n addr: %d" + "\r\n", + descP->sock, + addrP->in4.sin_port, addrP->in4.sin_addr) ); if (sock_connect(descP->sock, (struct sockaddr*) addrP, addrLen) == 0) { /* Success already! */ @@ -1388,6 +2126,156 @@ ERL_NIF_TERM essio_connect(ErlNifEnv* env, } +/* ======================================================================== + */ +extern +ERL_NIF_TERM essio_connectx(ErlNifEnv* env, + ESockDescriptor* descP, + ERL_NIF_TERM sockRef, + ERL_NIF_TERM connRef, + ESockAddress* sockAddrs, + int addrsCnt) +{ +#if defined(HAVE_SCTP) + int save_errno; + ErlNifPid self; + sctp_assoc_t assocId; + + SSDBG( descP, + ("UNIX-ESSIO", "essio_connectx {%d} -> entry with" + "\r\n address count: %d" + "\r\nwhen" + "\r\n connectx: %s" + "\r\n", + descP->sock, addrsCnt, + ((ctrl.sctp.connectx == NULL) ? "not supported" : "supported")) ); + + sock_ensure_connectx(env); + + ESOCK_ASSERT( enif_self(env, &self) != NULL ); + + /* + * Verify that we are in the proper state + */ + + if (! IS_OPEN(descP->writeState)) + return esock_make_error_closed(env); + + /* Connect and Write uses the same select flag + * so they can not be simultaneous + */ + if (descP->currentWriterP != NULL) + return esock_make_error_invalid(env, esock_atom_state); + + if (descP->connectorP != NULL) { + + /* Connect in progress */ + + if (COMPARE_PIDS(&self, &descP->connector.pid) != 0) { + /* Other process has connect in progress. + * This is a bad call sequence + * - connect without an address is only allowed + * for the connecting process + */ + return esock_raise_invalid(env, esock_atom_state); + + } + + /* Finalize after received select message */ + + esock_requestor_release("essio_connect finalize -> connected", + env, descP, &descP->connector); + descP->connectorP = NULL; + descP->writeState &= ~ESOCK_STATE_CONNECTING; + + /* Does this work for an SCTP socket? */ + if (! verify_is_connected(descP, &save_errno)) { + return esock_make_error_errno(env, save_errno); + } + + descP->writeState |= ESOCK_STATE_CONNECTED; + + return esock_atom_ok; + } + + + /* No connect in progress */ + + /* If socket domain is IPv4: + * Sock addresses must all be family IPv4 + * If socket domain is IPv6: + * Sock addresses must all be family IPv4 *or* IPv6 + */ + + /* Has this not already been checked? */ + if ((descP->domain != AF_INET) && (descP->domain != AF_INET6)) + return enif_make_badarg(env); + + /* Initial connect call, with address(s) */ + + if (sock_connectx(descP->sock, + (struct sockaddr*) sockAddrs, addrsCnt, + &assocId) == 0) { + /* Success already! */ + SSDBG( descP, ("UNIX-ESSIO", "essio_connectx {%d} -> connected\r\n", + descP->sock) ); + + descP->writeState |= ESOCK_STATE_CONNECTED; + + return esock_make_ok2(env, MKI(env, assocId)); + } + + /* Connect returned error */ + save_errno = sock_errno(); + + switch (save_errno) { + + case EINPROGRESS: /* Unix & OSE!! */ + SSDBG( descP, + ("UNIX-ESSIO", "essio_connect {%d} -> would block => select\r\n", + descP->sock) ); + { + int sres; + + if ((sres = + esock_select_write(env, descP->sock, descP, NULL, + sockRef, connRef)) < 0) + return + enif_raise_exception(env, + MKT2(env, esock_atom_select_write, + MKI(env, sres))); + /* Initiate connector */ + descP->connector.pid = self; + ESOCK_ASSERT( MONP("essio_connect -> conn", + env, descP, + &self, &descP->connector.mon) == 0 ); + descP->connector.env = esock_alloc_env("connector"); + descP->connector.ref = CP_TERM(descP->connector.env, connRef); + descP->connectorP = &descP->connector; + descP->writeState |= + (ESOCK_STATE_CONNECTING | ESOCK_STATE_SELECTED); + + return esock_atom_select; + } + break; + + default: + SSDBG( descP, + ("UNIX-ESSIO", "essio_connect {%d} -> error: %d\r\n", + descP->sock, save_errno) ); + + return esock_make_error_errno(env, save_errno); + + } // switch(save_errno) +#else + /* We should never be called in this case, + * but just to be on the safe side... + */ + return enif_raise_exception(env, MKA(env, "notsup")); +#endif +} + + /* *** verify_is_connected *** * Check if a connection has been established. */ @@ -1560,7 +2448,7 @@ ERL_NIF_TERM essio_accept_listening_error(ErlNifEnv* env, descP->sock) ); descP->currentAcceptor.pid = caller; - ESOCK_ASSERT( MONP("essio_accept_listening -> current acceptor", + ESOCK_ASSERT( MONP("essio_accept_listening_error -> current acceptor", env, descP, &descP->currentAcceptor.pid, &descP->currentAcceptor.mon) == 0 ); @@ -1586,7 +2474,7 @@ ERL_NIF_TERM essio_accept_listening_error(ErlNifEnv* env, SSDBG( descP, ("UNIX-ESSIO", - "essio_accept_listening {%d} -> errno: %d\r\n", + "essio_accept_listening_error {%d} -> errno: %d\r\n", descP->sock, save_errno) ); ESOCK_CNT_INC(env, descP, sockRef, @@ -1924,6 +2812,7 @@ BOOLEAN_T essio_accept_accepted(ErlNifEnv* env, enif_release_resource(accDescP); accDescP->ctrlPid = pid; + /* pid has actually been compared equal to self() * in this code path just a little while ago */ @@ -1947,6 +2836,113 @@ BOOLEAN_T essio_accept_accepted(ErlNifEnv* env, } +#define ESOCK_ENSURE_OPEN(E, D) \ + if (! IS_OPEN((D)->readState) ) \ + return esock_make_error_closed((E)) + + +/* ======================================================================== + * peeloff - create a new one-to-one socket from an existing one-to-many + * socket. + */ +#if defined(HAVE_SCTP) +extern +ERL_NIF_TERM essio_peeloff(ErlNifEnv* env, + ESockDescriptor* descP, + ERL_NIF_TERM sockRef, + ESockAssocId assocId) +{ + ESockDescriptor* poDescP; + SOCKET sock; + ErlNifPid self; + ERL_NIF_TERM poRef; + + sock_ensure_peeloff(env); + ESOCK_ENSURE_OPEN(env, descP); + + ESOCK_ASSERT( enif_self(env, &self) != NULL ); + + /* SHOULD ANYBODY BE ALLOWED TO DO PEELOFF? + * OR SHOULD IT BE JUST THE "OWNER"? + */ + + SSDBG( descP, + ("UNIX-ESSIO", + "%s(%d) -> try peeloff for assoc-id %d:" + "\r\n Owner: %T" + "\r\n Self: %T" + "\r\n", + __FUNCTION__, descP->sock, assocId, + MKPID(env, &descP->ctrlPid), MKPID(env, &self)) ); + + if (ESOCK_IS_ERROR(sock = sock_peeloff(descP->sock, assocId))) { + int save_errno = sock_errno(); + + return esock_make_error_errno(env, save_errno); + } + + SSDBG( descP, + ("UNIX-ESSIO", + "%s(%d) -> " + "set non-blocking for new sock %d and alloc descriptor\r\n", + __FUNCTION__, descP->sock, sock) ); + + SET_NONBLOCKING(sock); + + poDescP = esock_alloc_descriptor(sock); + poDescP->domain = descP->domain; + poDescP->type = SOCK_STREAM; + poDescP->protocol = descP->protocol; + + MLOCK(descP->writeMtx); + + SSDBG( descP, + ("UNIX-ESSIO", + "%s(%d) -> inherit various options\r\n", + __FUNCTION__, descP->sock, sock) ); + + poDescP->rBufSz = descP->rBufSz; // Inherit buffer size + poDescP->rNum = descP->rNum; // Inherit buffer uses + poDescP->rNumCnt = 0; + poDescP->rCtrlSz = descP->rCtrlSz; // Inherit buffer size + poDescP->wCtrlSz = descP->wCtrlSz; // Inherit buffer size + poDescP->iow = descP->iow; // Inherit iow + poDescP->dbg = descP->dbg; // Inherit debug flag + poDescP->useReg = descP->useReg; // Inherit useReg flag + esock_inc_socket(poDescP->domain, poDescP->type, poDescP->protocol); + + poRef = enif_make_resource(env, poDescP); + enif_release_resource(poDescP); + + poDescP->ctrlPid = self; + + /* pid has actually been compared equal to self() + * in this code path just a little while ago + */ + ESOCK_ASSERT( MONP("essio_peeloff -> ctrl", + env, poDescP, + &poDescP->ctrlPid, + &poDescP->ctrlMon) == 0 ); + + poDescP->readState = ESOCK_STATE_CONNECTED; + poDescP->writeState = ESOCK_STATE_CONNECTED; + poDescP->type = SOCK_STREAM; + + MUNLOCK(descP->writeMtx); + + SSDBG( descP, + ("UNIX-ESSIO", + "%s(%d) -> (maybe) register (new) socket: %T\r\n", + __FUNCTION__, descP->sock, poRef) ); + + /* And finally (maybe) update the registry */ + if (descP->useReg) esock_send_reg_add_msg(env, descP, poRef); + + return esock_make_ok2(env, poRef); +} +#endif + + /* ======================================================================== * Do the actual send. @@ -2067,12 +3063,22 @@ ERL_NIF_TERM essio_sendmsg(ErlNifEnv* env, { ERL_NIF_TERM res, eAddr, eCtrl; ESockAddress addr; - struct msghdr msgHdr; - ErlNifIOVec *iovec = NULL; + struct msghdr msgHdr = {0}; + ErlNifIOVec* iovec = NULL; char* ctrlBuf; size_t ctrlBufLen, ctrlBufUsed; ssize_t dataSize, sendmsg_result; ERL_NIF_TERM writerCheck, tail; +#if defined(ESOCK_NON_EMPTY_IOV) + int dummy; + SysIOVec dummyIOV[1]; +#endif + + SSDBG( descP, ("UNIX-ESSIO", + "essio_sendmsg(%d) -> entry with" + "\r\n eMsg: %T" + "\r\n eIOV: %T" + "\r\n", descP->sock, eMsg, eIOV) ); if (! IS_OPEN(descP->writeState)) return esock_make_error_closed(env); @@ -2087,7 +3093,7 @@ ERL_NIF_TERM essio_sendmsg(ErlNifEnv* env, * or enqueue this process if there is a current writer */ if (! send_check_writer(env, descP, sendRef, &writerCheck)) { SSDBG( descP, - ("UNIX-ESSIO", "essio_sendmsg {%d} -> writer check failed: " + ("UNIX-ESSIO", "essio_sendmsg(%d) -> writer check failed: " "\r\n %T\r\n", descP->sock, writerCheck) ); return writerCheck; } @@ -2098,7 +3104,7 @@ ERL_NIF_TERM essio_sendmsg(ErlNifEnv* env, if (! GET_MAP_VAL(env, eMsg, esock_atom_addr, &eAddr)) { SSDBG( descP, ("UNIX-ESSIO", - "essio_sendmsg {%d} -> no address\r\n", descP->sock) ); + "essio_sendmsg(%d) -> no address\r\n", descP->sock) ); msgHdr.msg_name = NULL; msgHdr.msg_namelen = 0; @@ -2107,7 +3113,7 @@ ERL_NIF_TERM essio_sendmsg(ErlNifEnv* env, msgHdr.msg_namelen = sizeof(addr); sys_memzero((char *) msgHdr.msg_name, msgHdr.msg_namelen); - SSDBG( descP, ("UNIX-ESSIO", "essio_sendmsg {%d} ->" + SSDBG( descP, ("UNIX-ESSIO", "essio_sendmsg(%d) ->" "\r\n address: %T" "\r\n", descP->sock, eAddr) ); @@ -2115,7 +3121,7 @@ ERL_NIF_TERM essio_sendmsg(ErlNifEnv* env, msgHdr.msg_name, &msgHdr.msg_namelen)) { SSDBG( descP, ("UNIX-ESSIO", - "essio_sendmsg {%d} -> invalid address\r\n", + "essio_sendmsg(%d) -> invalid address\r\n", descP->sock) ); return esock_make_invalid(env, esock_atom_addr); } @@ -2126,13 +3132,13 @@ ERL_NIF_TERM essio_sendmsg(ErlNifEnv* env, */ if ((! enif_inspect_iovec(NULL, dataP->iov_max, eIOV, &tail, &iovec))) { SSDBG( descP, ("UNIX-ESSIO", - "essio_sendmsg {%d} -> not an iov\r\n", + "essio_sendmsg(%d) -> not an iov\r\n", descP->sock) ); return esock_make_invalid(env, esock_atom_iov); } - SSDBG( descP, ("UNIX-ESSIO", "essio_sendmsg {%d} ->" + SSDBG( descP, ("UNIX-ESSIO", "essio_sendmsg(%d) ->" "\r\n iovcnt: %lu" "\r\n tail: %s" "\r\n", descP->sock, @@ -2151,7 +3157,7 @@ ERL_NIF_TERM essio_sendmsg(ErlNifEnv* env, } else { /* We can not send the whole packet in one sendmsg() call */ SSDBG( descP, ("UNIX-ESSIO", - "essio_sendmsg {%d} -> iovcnt > iov_max\r\n", + "essio_sendmsg(%d) -> iovcnt > iov_max\r\n", descP->sock) ); res = esock_make_invalid(env, esock_atom_iov); goto done_free_iovec; @@ -2170,6 +3176,7 @@ ERL_NIF_TERM essio_sendmsg(ErlNifEnv* env, * there was no more data. Otherwise there is more * data or the 'iov' is invalid. */ + for (;;) { if (enif_get_list_cell(env, tail, &h, &t) && enif_inspect_binary(env, h, &bin) && @@ -2179,12 +3186,12 @@ ERL_NIF_TERM essio_sendmsg(ErlNifEnv* env, } else break; } - + if ((! enif_is_empty_list(env, tail)) && (descP->type != SOCK_STREAM)) { /* We can not send the whole packet in one sendmsg() call */ SSDBG( descP, ("UNIX-ESSIO", - "essio_sendmsg {%d} -> invalid tail\r\n", + "essio_sendmsg(%d) -> invalid tail\r\n", descP->sock) ); res = esock_make_invalid(env, esock_atom_iov); goto done_free_iovec; @@ -2197,7 +3204,7 @@ ERL_NIF_TERM essio_sendmsg(ErlNifEnv* env, dataSize += len; if (dataSize < len) { /* Overflow */ - SSDBG( descP, ("UNIX-ESSIO", "essio_sendmsg {%d} -> Overflow" + SSDBG( descP, ("UNIX-ESSIO", "essio_sendmsg(%d) -> Overflow" "\r\n i: %lu" "\r\n len: %lu" "\r\n dataSize: %ld" @@ -2210,15 +3217,39 @@ ERL_NIF_TERM essio_sendmsg(ErlNifEnv* env, } SSDBG( descP, ("UNIX-ESSIO", - "essio_sendmsg {%d} -> iovec size verified" + "essio_sendmsg(%d) -> iovec size verified" + "\r\n iov: 0x%lX" "\r\n iov length: %lu" "\r\n data size: %u" "\r\n", descP->sock, + iovec->iov, (unsigned long) iovec->iovcnt, (long) dataSize) ); - msgHdr.msg_iovlen = iovec->iovcnt; +#if defined(ESOCK_NON_EMPTY_IOV) + /* + * *** HACKETI HACK HACK *** + * + * Solaris do not seem to allow empty iov, + * so, if size == 0, fake it. + */ + SSDBG( descP, ("UNIX-ESSIO", "essio_sendmsg(%d) -> do not allow empty iov" + "\r\n", descP->sock) ); + if (iovec->iovcnt > 0) { + msgHdr.msg_iov = iovec->iov; + msgHdr.msg_iovlen = iovec->iovcnt; + } else { + dummyIOV[0].iov_len = 0; + dummyIOV[0].iov_base = &dummy; + msgHdr.msg_iov = dummyIOV; + msgHdr.msg_iovlen = 1; + } +#else + SSDBG( descP, ("UNIX-ESSIO", "essio_sendmsg(%d) -> allow empty iov" + "\r\n", descP->sock) ); msgHdr.msg_iov = iovec->iov; + msgHdr.msg_iovlen = iovec->iovcnt; +#endif /* Extract the *optional* 'ctrl' */ if (GET_MAP_VAL(env, eMsg, esock_atom_ctrl, &eCtrl)) { @@ -2226,7 +3257,8 @@ ERL_NIF_TERM essio_sendmsg(ErlNifEnv* env, ctrlBuf = (char*) MALLOC(ctrlBufLen); ESOCK_ASSERT( ctrlBuf != NULL ); } - SSDBG( descP, ("UNIX-ESSIO", "essio_sendmsg {%d} -> optional ctrl: " + + SSDBG( descP, ("UNIX-ESSIO", "essio_sendmsg(%d) -> optional ctrl: " "\r\n ctrlBuf: %p" "\r\n ctrlBufLen: %lu" "\r\n eCtrl: %T" @@ -2240,7 +3272,7 @@ ERL_NIF_TERM essio_sendmsg(ErlNifEnv* env, eCtrl, ctrlBuf, ctrlBufLen, &ctrlBufUsed)) { SSDBG( descP, ("UNIX-ESSIO", - "essio_sendmsg {%d} -> invalid ctrl\r\n", + "essio_sendmsg(%d) -> invalid ctrl\r\n", descP->sock) ); res = esock_make_invalid(env, esock_atom_ctrl); goto done_free_iovec; @@ -2270,8 +3302,8 @@ ERL_NIF_TERM essio_sendmsg(ErlNifEnv* env, if (ctrlBuf != NULL) FREE(ctrlBuf); SSDBG( descP, - ("UNIX-ESSIO", "essio_sendmsg {%d} -> done" - "\r\n %T" + ("UNIX-ESSIO", "essio_sendmsg(%d) -> done" + "\r\n res: %T" "\r\n", descP->sock, res) ); return res; @@ -2948,7 +3980,7 @@ ERL_NIF_TERM essio_recvmsg(ErlNifEnv* env, int saveErrno; size_t bufSz = (bufLen != 0 ? bufLen : descP->rBufSz); size_t ctrlSz = (ctrlLen != 0 ? ctrlLen : descP->rCtrlSz); - struct msghdr msgHdr; + struct msghdr msgHdr = {0}; SysIOVec iov[1]; // Shall we always use 1? ErlNifBinary data[1]; // Shall we always use 1? ErlNifBinary ctrl; @@ -3379,11 +4411,102 @@ ERL_NIF_TERM essio_fin_close(ErlNifEnv* env, */ +/* ======================================================================== + * *** essio_socknames *** + */ +#if defined(HAVE_SCTP) +extern +ERL_NIF_TERM essio_socknames(ErlNifEnv* env, + ESockDescriptor* descP, + sctp_assoc_t assocId) +{ + struct sockaddr* sa; + int n; + ERL_NIF_TERM eaddrs, res; + + sock_ensure_names(env); + + n = sock_names(descP->sock, assocId, &sa); + if (n > 0) { + eaddrs = essio_addrs_encode(env, descP, n, sa); + res = esock_make_ok2(env, eaddrs); + ctrl.sctp.freeladdrs(sa); + } else if (n == 0) { + eaddrs = MKEL(env); + res = esock_make_ok2(env, eaddrs); + } else { + int save_errno = sock_errno(); + + res = esock_make_error_errno(env, save_errno); + } + + return res; +} + + +static +ERL_NIF_TERM essio_addrs_encode(ErlNifEnv* env, + ESockDescriptor* descP, + int n, + struct sockaddr* sa) +{ + ERL_NIF_TERM esa; + SocketTArray esat = TARRAY_CREATE(n); + + for (int i = 0; i < n; i++) { + ERL_NIF_TERM ea; + + esock_encode_sockaddr(env, (ESockAddress*) &sa[i], -1, &ea); + + TARRAY_ADD(esat, ea); + } + + TARRAY_TOLIST(esat, env, &esa); + + return esa; +} +#endif + + /* ======================================================================== * *** essio_peername should go here - if we need one *** */ +/* ======================================================================== + * *** essio_peernames *** + */ +#if defined(HAVE_SCTP) +extern +ERL_NIF_TERM essio_peernames(ErlNifEnv* env, + ESockDescriptor* descP, + sctp_assoc_t assocId) +{ + struct sockaddr* sa; + int n; + ERL_NIF_TERM eaddrs, res; + + sock_ensure_peers(env); + + n = sock_peers(descP->sock, assocId, &sa); + if (n > 0) { + eaddrs = essio_addrs_encode(env, descP, n, sa); + res = esock_make_ok2(env, eaddrs); + ctrl.sctp.freepaddrs(sa); + } else if (n == 0) { + eaddrs = MKEL(env); + res = esock_make_ok2(env, eaddrs); + } else { + int save_errno = sock_errno(); + + res = esock_make_error_errno(env, save_errno); + } + + return res; +} +#endif + + /* ======================================================================== * Cancel a connect request. */ @@ -5283,7 +6406,7 @@ ERL_NIF_TERM send_check_result(ErlNifEnv* env, err = send_error ? sock_errno() : 0; SSDBG( descP, - ("UNIX-ESSIO", "send_check_result(%T) {%d} -> entry with" + ("UNIX-ESSIO", "send_check_result(%T, %d) -> entry with" "\r\n send_result: %ld" "\r\n dataSize: %ld" "\r\n err: %d" @@ -5300,7 +6423,7 @@ ERL_NIF_TERM send_check_result(ErlNifEnv* env, SSDBG( descP, ("UNIX-ESSIO", - "send_check_result(%T) {%d} -> try again" + "send_check_result(%T, %d) -> try again" "\r\n", sockRef, descP->sock) ); res = send_check_retry(env, descP, -1, sockRef, sendRef); @@ -5313,7 +6436,7 @@ ERL_NIF_TERM send_check_result(ErlNifEnv* env, /* Not the entire package */ SSDBG( descP, ("UNIX-ESSIO", - "send_check_result(%T) {%d} -> " + "send_check_result(%T, %d) -> " "not entire package written (%d of %d)" "\r\n", sockRef, descP->sock, written, dataSize) ); @@ -5323,7 +6446,7 @@ ERL_NIF_TERM send_check_result(ErlNifEnv* env, /* We sent all we could, but not everything (data in tail) */ SSDBG( descP, ("UNIX-ESSIO", - "send_check_result(%T) {%d} -> " + "send_check_result(%T, %d) -> " "not entire package written (%d but data in tail)" "\r\n", sockRef, descP->sock, written) ); @@ -5338,7 +6461,7 @@ ERL_NIF_TERM send_check_result(ErlNifEnv* env, SSDBG( descP, ("UNIX-ESSIO", - "send_check_result(%T) {%d} -> done:" + "send_check_result(%T, %d) -> done:" "\r\n res: %T" "\r\n", sockRef, descP->sock, res) ); @@ -5626,8 +6749,8 @@ BOOLEAN_T decode_cmsghdrs(ErlNifEnv* env, unsigned int len; int i; - SSDBG( descP, ("UNIX-ESSIO", "decode_cmsghdrs {%d} -> entry with" - "\r\n eCMsg: %T" + SSDBG( descP, ("UNIX-ESSIO", "decode_cmsghdrs(%d) -> entry with" + "\r\n eCMsg: %T" "\r\n cmsgHdrBufP: 0x%lX" "\r\n cmsgHdrBufLen: %d" "\r\n", descP->sock, @@ -5638,13 +6761,13 @@ BOOLEAN_T decode_cmsghdrs(ErlNifEnv* env, SSDBG( descP, ("UNIX-ESSIO", - "decode_cmsghdrs {%d} -> list length: %d\r\n", + "decode_cmsghdrs(%d) -> list length: %d\r\n", descP->sock, len) ); for (i = 0, list = eCMsg, rem = cmsgHdrBufLen, bufP = cmsgHdrBufP; i < len; i++) { - SSDBG( descP, ("UNIX-ESSIO", "decode_cmsghdrs {%d} -> process elem %d:" + SSDBG( descP, ("UNIX-ESSIO", "decode_cmsghdrs(%d) -> process elem %d:" "\r\n (buffer) rem: %u" "\r\n (buffer) totUsed: %u" "\r\n", descP->sock, i, rem, totUsed) ); @@ -5666,8 +6789,8 @@ BOOLEAN_T decode_cmsghdrs(ErlNifEnv* env, *cmsgHdrBufUsed = totUsed; - SSDBG( descP, ("UNIX-ESSIO", "decode_cmsghdrs {%d} -> done" - "\r\n all %u ctrl headers processed" + SSDBG( descP, ("UNIX-ESSIO", "decode_cmsghdrs(%d) -> done when" + "\r\n all (%u) ctrl headers processed" "\r\n totUsed = %lu\r\n", descP->sock, len, (unsigned long) totUsed) ); @@ -5705,26 +6828,26 @@ BOOLEAN_T decode_cmsghdr(ErlNifEnv* env, ERL_NIF_TERM eLevel, eType, eData, eValue; int level; - SSDBG( descP, ("UNIX-ESSIO", "decode_cmsghdr {%d} -> entry with" + SSDBG( descP, ("UNIX-ESSIO", "decode_cmsghdr(%d) -> entry with" "\r\n eCMsg: %T" "\r\n", descP->sock, eCMsg) ); // Get 'level' field if (! GET_MAP_VAL(env, eCMsg, esock_atom_level, &eLevel)) return FALSE; - SSDBG( descP, ("UNIX-ESSIO", "decode_cmsghdr {%d} -> eLevel: %T" + SSDBG( descP, ("UNIX-ESSIO", "decode_cmsghdr(%d) -> eLevel: %T" "\r\n", descP->sock, eLevel) ); // Get 'type' field if (! GET_MAP_VAL(env, eCMsg, esock_atom_type, &eType)) return FALSE; - SSDBG( descP, ("UNIX-ESSIO", "decode_cmsghdr {%d} -> eType: %T" + SSDBG( descP, ("UNIX-ESSIO", "decode_cmsghdr(%d) -> eType: %T" "\r\n", descP->sock, eType) ); // Decode Level if (! esock_decode_level(env, eLevel, &level)) return FALSE; - SSDBG( descP, ("UNIX-ESSIO", "decode_cmsghdr {%d}-> level: %d\r\n", + SSDBG( descP, ("UNIX-ESSIO", "decode_cmsghdr(%d) -> level: %d\r\n", descP->sock, level) ); // Get 'data' field @@ -5733,7 +6856,7 @@ BOOLEAN_T decode_cmsghdr(ErlNifEnv* env, // Get 'value' field if (! GET_MAP_VAL(env, eCMsg, esock_atom_value, &eValue)) return FALSE; - SSDBG( descP, ("UNIX-ESSIO", "decode_cmsghdr {%d} -> eValue: %T" + SSDBG( descP, ("UNIX-ESSIO", "decode_cmsghdr(%d) -> eValue: %T" "\r\n", descP->sock, eValue) ); // Decode Value @@ -5747,7 +6870,7 @@ BOOLEAN_T decode_cmsghdr(ErlNifEnv* env, if (GET_MAP_VAL(env, eCMsg, esock_atom_value, &eValue)) return FALSE; - SSDBG( descP, ("UNIX-ESSIO", "decode_cmsghdr {%d} -> eData: %T" + SSDBG( descP, ("UNIX-ESSIO", "decode_cmsghdr(%d) -> eData: %T" "\r\n", descP->sock, eData) ); // Decode Data @@ -5756,8 +6879,9 @@ BOOLEAN_T decode_cmsghdr(ErlNifEnv* env, return FALSE; } - SSDBG( descP, ("UNIX-ESSIO", "decode_cmsghdr {%d}-> used: %lu\r\n", - descP->sock, (unsigned long) *used) ); + SSDBG( descP, ("UNIX-ESSIO", "decode_cmsghdr(%d)-> done when" + "\r\n used: %lu" + "\r\n", descP->sock, (unsigned long) *used) ); return TRUE; } @@ -5781,265 +6905,1531 @@ BOOLEAN_T decode_cmsghdr_value(ErlNifEnv* env, SSDBG( descP, ("UNIX-ESSIO", - "decode_cmsghdr_value {%d} -> entry \r\n" + "decode_cmsghdr_value(%d) -> entry \r\n" " eType: %T\r\n" " eValue: %T\r\n", descP->sock, eType, eValue) ); - // We have decode functions only for symbolic (atom) types - if (! IS_ATOM(env, eType)) { - SSDBG( descP, - ("UNIX-ESSIO", - "decode_cmsghdr_value {%d} -> FALSE:\r\n" - " eType not an atom\r\n", - descP->sock) ); - return FALSE; + // We have decode functions only for symbolic (atom) types + if (! IS_ATOM(env, eType)) { + SSDBG( descP, + ("UNIX-ESSIO", + "decode_cmsghdr_value(%d) -> FALSE:" + "\r\n eType not an atom\r\n", + descP->sock) ); + return FALSE; + } + + /* Try to look up the symbolic type + */ + if (((cmsgTable = esock_lookup_cmsg_table(level, &num)) == NULL) || + ((cmsgSpecP = esock_lookup_cmsg_spec(cmsgTable, num, eType)) == NULL) || + (cmsgSpecP->decode == NULL)) { + /* We found no table for this level, + * we found no symbolic type in the level table, + * or no decode function for this type + */ + + SSDBG( descP, + ("UNIX-ESSIO", + "decode_cmsghdr_value(%d) -> FALSE:" + "\r\n cmsgTable: %p" + "\r\n num: %d" + "\r\n cmsgSpecP: %p", + descP->sock, cmsgTable, num, cmsgSpecP) ); + return FALSE; + } + + if (! cmsgSpecP->decode(env, eValue, cmsgP, rem, usedP)) { + // Decode function failed + SSDBG( descP, + ("UNIX-ESSIO", + "decode_cmsghdr_value(%d) -> FALSE:" + "\r\n decode function failed" + "\r\n", + descP->sock) ); + return FALSE; + } + + // Successful decode + + type = cmsgSpecP->type; + + SSDBG( descP, + ("UNIX-ESSIO", + "decode_cmsghdr_value(%d) -> TRUE:" + "\r\n level: %d" + "\r\n type: %d" + "\r\n *usedP: %lu" + "\r\n", + descP->sock, level, type, (unsigned long) *usedP) ); + + cmsgP->cmsg_level = level; + cmsgP->cmsg_type = type; + + return TRUE; +} + + +static +BOOLEAN_T decode_cmsghdr_data(ErlNifEnv* env, + ESockDescriptor* descP, + int level, + ERL_NIF_TERM eType, + ERL_NIF_TERM eData, + char* bufP, + size_t rem, + size_t* usedP) +{ + int type; + ErlNifBinary bin; + struct cmsghdr* cmsgP = (struct cmsghdr *) bufP; + ESockCmsgSpec* cmsgSpecP = NULL; + + SSDBG( descP, + ("UNIX-ESSIO", + "decode_cmsghdr_data {%d} -> entry \r\n" + " eType: %T\r\n" + " eData: %T\r\n", + descP->sock, eType, eData) ); + + // Decode Type + if (! GET_INT(env, eType, &type)) { + ESockCmsgSpec* cmsgTable = NULL; + size_t num = 0; + + /* Try to look up the symbolic (atom) type + */ + if ((! IS_ATOM(env, eType)) || + ((cmsgTable = esock_lookup_cmsg_table(level, &num)) == NULL) || + ((cmsgSpecP = esock_lookup_cmsg_spec(cmsgTable, num, eType)) == NULL)) { + /* Type was not an atom, + * we found no table for this level, + * or we found no symbolic type in the level table + */ + + SSDBG( descP, + ("UNIX-ESSIO", + "decode_cmsghdr_data {%d} -> FALSE:\r\n" + " cmsgTable: %p\r\n" + " cmsgSpecP: %p\r\n", + descP->sock, cmsgTable, cmsgSpecP) ); + return FALSE; + } + + type = cmsgSpecP->type; + } + + // Decode Data + if (GET_BIN(env, eData, &bin)) { + void *p; + + p = esock_init_cmsghdr(cmsgP, rem, bin.size, usedP); + if (p == NULL) { + /* No room for the data + */ + + SSDBG( descP, + ("UNIX-ESSIO", + "decode_cmsghdr_data {%d} -> FALSE:\r\n" + " rem: %lu\r\n" + " bin.size: %lu\r\n", + descP->sock, + (unsigned long) rem, + (unsigned long) bin.size) ); + return FALSE; + } + + // Copy the binary data + sys_memcpy(p, bin.data, bin.size); + + } else if ((! esock_cmsg_decode_int(env, eData, cmsgP, rem, usedP)) && + (! esock_cmsg_decode_bool(env, eData, cmsgP, rem, usedP))) { + SSDBG( descP, + ("UNIX-ESSIO", + "decode_cmsghdr_data {%d} -> FALSE\r\n", + descP->sock) ); + return FALSE; + } + + // Successful decode + + SSDBG( descP, + ("UNIX-ESSIO", + "decode_cmsghdr_data {%d} -> TRUE:\r\n" + " level: %d\r\n" + " type: %d\r\n" + " *usedP: %lu\r\n", + descP->sock, level, type, (unsigned long) *usedP) ); + + cmsgP->cmsg_level = level; + cmsgP->cmsg_type = type; + return TRUE; +} + + +/* +++ encode_msg +++ + * + * Encode a msg() (recvmsg). In erlang its represented as + * a map, which has a specific set of attributes: + * + * addr (source address) - sockaddr() + * iov - [binary()] | sctp_event() + * ctrl - [cmsg()] + * flags - msg_flags() + */ + +#if defined(HAVE_SCTP) + +#if defined(IPPROTO_SCTP) +#define IS_SCTP(D) ((D)->protocol == IPPROTO_SCTP) +#else +#define IS_SCTP(D) FALSE +#endif + +#if defined(MSG_NOTIFICATION) +#define IS_SCTP_NOTIFICATION(F) ((F) & MSG_NOTIFICATION) +#else +#define IS_SCTP_NOTIFICATION(F) FALSE +#endif + +#else + +#define IS_SCTP(D) FALSE +#define IS_SCTP_NOTIFICATION(F) FALSE + +#endif + +static +void encode_msg(ErlNifEnv* env, + ESockDescriptor* descP, + ssize_t read, + struct msghdr* msgHdrP, + ErlNifBinary* dataBufP, + ErlNifBinary* ctrlBufP, + ERL_NIF_TERM* eMsg) +{ + ERL_NIF_TERM addr, dataOrNotif, dataOrNotifKey, ctrl, flags; + + SSDBG( descP, + ("UNIX-ESSIO", "encode_msg {%d} -> entry with" + "\r\n read: %ld" + "\r\n", descP->sock, (long) read) ); + + /* +++ Time for address stuff +++ */ + + /* The address is not used if we are connected (unless, maybe, + * family is 'local'), so check (length = 0) before we try to encode. + */ + if (msgHdrP->msg_namelen != 0) { + esock_encode_sockaddr(env, + (ESockAddress*) msgHdrP->msg_name, + msgHdrP->msg_namelen, + &addr); + } else { + addr = esock_atom_undefined; + } + + + /* +++ Time for flag stuff +++ */ + + SSDBG( descP, + ("UNIX-ESSIO", + "encode_msg {%d} -> try encode flags\r\n", + descP->sock) ); + + esock_encode_msg_flags(env, descP, msgHdrP->msg_flags, &flags); + + + /* +++ Time for control messages stuff +++ */ + + SSDBG( descP, + ("UNIX-ESSIO", + "encode_msg {%d} -> try encode cmsgs\r\n", + descP->sock) ); + + encode_cmsgs(env, descP, ctrlBufP, msgHdrP, &ctrl); + + + SSDBG( descP, + ("UNIX-ESSIO", "encode_msg {%d} -> components encoded:" + "\r\n addr: %T" + "\r\n ctrl: %T" + "\r\n flags: %T" + "\r\n", descP->sock, addr, ctrl, flags) ); + + + /* +++ Time for data (iov or an sctp event) stuff +++ */ + + /* Check if this is an (SCTP) event */ + if (IS_SCTP(descP) && IS_SCTP_NOTIFICATION(msgHdrP->msg_flags)) { + + /* We get back an sctp event in the form of a (erlang) term, + * so we reuse the iov variable, even though its not an I/O vector. + * Note: + * MSG_NOTIFICATION has actually the same value as MSG_MORE. + * So the (flags) encode above has actually put 'more' into the + * the list... + * In order to fix this we iterate through the list and copy each + * element *other* than 'more', which we instead replace with + * 'notification'. Ugly, but this will have to do for now... + * + * Note that ownership of the (data) binary is not + * transfered to an (erlang) term in this case, but decoded + * here and therefor we need to explicitly free the memory. + */ + + flags = esock_encode_msg_flags_convert2sctp(env, descP, flags); + + SSDBG( descP, + ("UNIX-ESSIO", "encode_msg {%d} -> encode sctp event" + "\r\n", descP->sock) ); + + essio_encode_sctp_notification(env, descP, dataBufP, &dataOrNotif); + dataOrNotifKey = esock_atom_notification; + + /* Ownership of the data buffer is not *in this case* transfered + * to a (erlang) term: explicit FREE + */ + FREE_BIN(dataBufP); + + } else { + + SSDBG( descP, + ("UNIX-ESSIO", "encode_msg {%d} -> encode iov" + "\r\n msg_iovlen: %lu" + "\r\n", + descP->sock, + (unsigned long) msgHdrP->msg_iovlen) ); + + esock_encode_iov(env, read, + msgHdrP->msg_iov, msgHdrP->msg_iovlen, dataBufP, + &dataOrNotif); + dataOrNotifKey = esock_atom_iov; + } + + { + ERL_NIF_TERM keys[] = {dataOrNotifKey, + esock_atom_ctrl, + esock_atom_flags, + esock_atom_addr}; + ERL_NIF_TERM vals[] = {dataOrNotif, ctrl, flags, addr}; + size_t numKeys = NUM(keys); + + ESOCK_ASSERT( numKeys == NUM(vals) ); + + SSDBG( descP, + ("UNIX-ESSIO", + "encode_msg {%d} -> create map\r\n", + descP->sock) ); + + if (msgHdrP->msg_namelen == 0) + numKeys--; // No addr + ESOCK_ASSERT( MKMA(env, keys, vals, numKeys, eMsg) ); + + SSDBG( descP, + ("UNIX-ESSIO", + "encode_msg {%d}-> map encoded\r\n", + descP->sock) ); + } + + SSDBG( descP, + ("UNIX-ESSIO", "encode_msg {%d} -> done\r\n", descP->sock) ); +} + +static +ERL_NIF_TERM esock_encode_msg_flags_convert2sctp(ErlNifEnv* env, + ESockDescriptor* descP, + ERL_NIF_TERM flags) +{ + ERL_NIF_TERM elem, tail, list, sctpMsgFlags; + unsigned int i, len; + SocketTArray ta; + + ESOCK_ASSERT( GET_LIST_LEN(env, flags, &len) ); + + ta = TARRAY_CREATE(len); + + for (i = 0, list = flags; + i < len; i++) { + + ESOCK_ASSERT( GET_LIST_ELEM(env, list, &elem, &tail) ); + + /* + * MSG_NOTIFICATION has the same value as MSG_MORE + * but since MORE is ahead of NOTIFICATION in our + * flags array, its MORE that will be encoded. + * So for sctp, we "fix it in post"... + */ + if (IS_IDENTICAL(elem, esock_atom_more)) { + TARRAY_ADD(ta, esock_atom_notification); + } else { + TARRAY_ADD(ta, elem); + } + + list = tail; + } + + TARRAY_TOLIST(ta, env, &sctpMsgFlags); + + return sctpMsgFlags; + +} + + +static +void essio_encode_sctp_notification(ErlNifEnv* env, + ESockDescriptor* descP, + ErlNifBinary* binP, + ERL_NIF_TERM* eEvent) +{ +#if defined(HAVE_SCTP) + union sctp_notification* np = (union sctp_notification*) binP->data; + + switch (np->sn_header.sn_type) { +#if defined(SCTP_ASSOC_CHANGE) + case SCTP_ASSOC_CHANGE: + essio_encode_sctp_notif_assoc_change(env, descP, + &(np->sn_assoc_change), + eEvent); + break; +#endif + +#if defined(SCTP_PEER_ADDR_CHANGE) + case SCTP_PEER_ADDR_CHANGE: + essio_encode_sctp_notif_paddr_change(env, descP, + &(np->sn_paddr_change), + eEvent); + break; +#endif + +#if defined(SCTP_SEND_FAILED) + case SCTP_SEND_FAILED: + essio_encode_sctp_notif_send_failed(env, descP, + &(np->sn_send_failed), + eEvent); + break; +#endif + +#if defined(SCTP_REMOTE_ERROR) + case SCTP_REMOTE_ERROR: + essio_encode_sctp_notif_remote_error(env, descP, + &(np->sn_remote_error), + eEvent); + break; +#endif + +#if defined(SCTP_SHUTDOWN_EVENT) + case SCTP_SHUTDOWN_EVENT: + essio_encode_sctp_notif_shutdown_event(env, descP, + &(np->sn_shutdown_event), + eEvent); + break; +#endif + +#if defined(SCTP_ADAPTATION_INDICATION) + case SCTP_ADAPTATION_INDICATION: + essio_encode_sctp_notif_adapt_event(env, descP, + &(np->sn_adaptation_event), + eEvent); + break; +#endif + +#if defined(SCTP_PARTIAL_DELIVERY_EVENT) + case SCTP_PARTIAL_DELIVERY_EVENT: + essio_encode_sctp_notif_pdapi_event(env, descP, + &(np->sn_pdapi_event), + eEvent); + break; +#endif + +#if defined(SCTP_AUTHENTICATION_INDICATION) + case SCTP_AUTHENTICATION_INDICATION: + essio_encode_sctp_notif_authkey(env, descP, + &(np->sn_authkey_event), + eEvent); + break; +#endif + +#if defined(SCTP_SENDER_DRY_EVENT) + case SCTP_SENDER_DRY_EVENT: + essio_encode_sctp_notif_sender_dry(env, descP, + &(np->sn_sender_dry_event), + eEvent); + break; +#endif + +#if defined(SCTP_STREAM_RESET_EVENT) + case SCTP_STREAM_RESET_EVENT: + essio_encode_sctp_notif_stream_reset_event(env, descP, + &(np->sn_strreset_event), + eEvent); + break; +#endif + +#if defined(SCTP_ASSOC_RESET_EVENT) + case SCTP_ASSOC_RESET_EVENT: + essio_encode_sctp_notif_assoc_reset_event(env, descP, + &(np->sn_assocreset_event), + eEvent); + break; +#endif + +#if defined(SCTP_STREAM_CHANGE_EVENT) + case SCTP_STREAM_CHANGE_EVENT: + essio_encode_sctp_notif_stream_change_event(env, descP, + &(np->sn_strchange_event), + eEvent); + break; +#endif + +#if defined(SCTP_SEND_FAILED_EVENT) + case SCTP_SEND_FAILED_EVENT: + essio_encode_sctp_notif_send_failed_event(env, descP, + &(np->sn_send_failed_event), + eEvent); + break; +#endif + + default: + essio_encode_sctp_notif_generic(env, descP, + MKUI(env, np->sn_header.sn_type), + np, + eEvent); + break; + } +#else + /* We should never be called in this case, + * but just to be on the safe side... + */ + *eEvent = esock_atom_undefined; +#endif +} + + +/* + * eEvent :: #{'$esock_name' := sctp_notfication, + * type := assoc_change, + * flags := integer(), + * state := assoc_change_state(), + * error := atom() | integer(), %% RFC4960 + * outbound_streams := integer(), + * inbound_streams := integer(), + * assoc_id := assoc_id()} + */ + +#if defined(SCTP_ASSOC_CHANGE) +static +void essio_encode_sctp_notif_assoc_change(ErlNifEnv* env, + ESockDescriptor* descP, + struct sctp_assoc_change* acp, + ERL_NIF_TERM* eEvent) +{ + ERL_NIF_TERM eflags, estate, eerr, eouts, eins, eaid; + + eflags = MKUI(env, acp->sac_flags); // We should translate this also... + + switch (acp->sac_state) { + case SCTP_COMM_UP: + estate = esock_atom_comm_up; + break; + case SCTP_COMM_LOST: + estate = esock_atom_comm_lost; + break; + case SCTP_RESTART: + estate = esock_atom_restart; + break; + case SCTP_SHUTDOWN_COMP: + estate = esock_atom_shutdown_comp; + break; + case SCTP_CANT_STR_ASSOC: + estate = esock_atom_cant_str_assoc; + break; + default: + estate = MKUI(env, acp->sac_state); + break; + } + + // We should translate this also... + switch (acp->sac_error) { +#if defined(SCTP_ERR_UNKNOWN) + case SCTP_ERR_UNKNOWN: + eerr = esock_atom_unknown; + break; +#endif +#if defined(SCTP_ERR_BAD_SID) + case SCTP_ERR_BAD_SID: + eerr = MKA(env, "bad_sid"); + break; +#endif +#if defined(SCTP_ERR_MISSING_PARM) + case SCTP_ERR_MISSING_PARM: + // eerr = esock_atom_missing_parm; + eerr = MKA(env, "missing_parm"); + break; +#endif +#if defined(SCTP_ERR_STALE_COOKIE) + case SCTP_ERR_STALE_COOKIE: + // eerr = esock_atom_stale_cookie; + eerr = MKA(env, "stale_cookie"); + break; +#endif +#if defined(SCTP_ERR_NO_RESOURCES) + case SCTP_ERR_NO_RESOURCES: + // eerr = esock_atom_no_resource; + eerr = MKA(env, "no_resource"); + break; +#endif +#if defined(SCTP_ERR_BAD_ADDR) + case SCTP_ERR_BAD_ADDR: + // eerr = esock_atom_bad_addr; + eerr = MKA(env, "bad_addr"); + break; +#endif +#if defined(SCTP_ERR_UNREC_CHUNK) + case SCTP_ERR_UNREC_CHUNK: + // eerr = esock_atom_unrec_chunk; + eerr = MKA(env, "unrec_chunk"); + break; +#endif +#if defined(SCTP_ERR_BAD_MANDPARM) + case SCTP_ERR_BAD_MANDPARM: + // eerr = esock_atom_bad_mandparm; + eerr = MKA(env, "bad_mandparm"); + break; +#endif +#if defined(SCTP_ERR_UNREC_PARM) + case SCTP_ERR_UNREC_PARM: + // eerr = esock_atom_unrec_parm; + eerr = MKA(env, "unrec_parm"); + break; +#endif +#if defined(SCTP_ERR_NO_USR_DATA) + case SCTP_ERR_NO_USR_DATA: + // eerr = esock_atom_no_usr_data; + eerr = MKA(env, "no_usr_data"); + break; +#endif +#if defined(SCTP_ERR_COOKIE_SHUT) + case SCTP_ERR_COOKIE_SHUT: + // eerr = esock_atom_cookie_shut; + eerr = MKA(env, "cookie_shut"); + break; +#endif +#if defined(SCTP_ERR_RESTART_NEW_ADDRS) + case SCTP_ERR_RESTART_NEW_ADDRS: + // eerr = esock_atom_restart_new_addrs; + eerr = MKA(env, "restart_new_addrs"); + break; +#endif +#if defined(SCTP_ERR_USER_ABORT) + case SCTP_ERR_USER_ABORT: + // eerr = esock_atom_user_abort; + eerr = MKA(env, "user_abort"); + break; +#endif +#if defined(SCTP_ERR_DELETE_LASTADDR) + case SCTP_ERR_DELETE_LASTADDR: + // eerr = esock_atom_delete_lastaddr; + eerr = MKA(env, "delete_lastaddr"); + break; +#endif +#if defined(SCTP_ERR_RESOURCE_SHORTAGE) + case SCTP_ERR_RESOURCE_SHORTAGE: + // eerr = esock_atom_resource_shortage; + eerr = MKA(env, "resource_shortage"); + break; +#endif +#if defined(SCTP_ERR_DELETE_SRCADDR) + case SCTP_ERR_DELETE_SRCADDR: + // eerr = esock_atom_delete_srcaddr; + eerr = MKA(env, "delete_srcaddr"); + break; +#endif +#if defined(SCTP_ERR_AUTH_ERR) + case SCTP_ERR_AUTH_ERR: + // eerr = esock_atom_auth_err; + eerr = MKA(env, "auth_err"); + break; +#endif + default: + eerr = MKUI(env, acp->sac_error); + break; + } + + eouts = MKUI(env, acp->sac_outbound_streams); + eins = MKUI(env, acp->sac_inbound_streams); + eaid = MKUI(env, acp->sac_assoc_id); + + { + ERL_NIF_TERM keys[] = {esock_atom_esock_name, + esock_atom_type, esock_atom_flags, + esock_atom_state, esock_atom_error, esock_atom_outbound_streams, + esock_atom_inbound_streams, esock_atom_assoc_id}; + ERL_NIF_TERM vals[] = {esock_atom_sctp_notification, + esock_atom_assoc_change, eflags, + estate, eerr, eouts, + eins, eaid}; + size_t numKeys = NUM(keys); + + ESOCK_ASSERT( numKeys == NUM(vals) ); + ESOCK_ASSERT( MKMA(env, keys, vals, numKeys, eEvent) ); + } +} +#endif + + +/* + * eEvent :: #{'$esock_name' := sctp_notfication, + * type := peer_addr_change, + * flags := integer(), + * addr := socket:sockaddr(), + * state := peer_addr_change_state(), + * error := integer(), + * assoc_id := assoc_id()} + */ +#if defined(SCTP_PEER_ADDR_CHANGE) +static +void essio_encode_sctp_notif_paddr_change(ErlNifEnv* env, + ESockDescriptor* descP, + struct sctp_paddr_change* p, + ERL_NIF_TERM* eEvent) +{ + ERL_NIF_TERM eflags, eaddr, estate, eerr, eaid; + + eflags = MKUI(env, p->spc_flags); // We should translate this also... + + /* */ + PUSH_SUPPRESS_ADDRESS_OF_PACKED_MEMBER(); + essio_encode_sockaddr_storage(env, descP, &p->spc_aaddr, &eaddr); + POP_SUPPRESS_ADDRESS_OF_PACKED_MEMBER(); + /* */ + + switch (p->spc_state) { + case SCTP_ADDR_AVAILABLE: + estate = esock_atom_addr_available; + break; + case SCTP_ADDR_UNREACHABLE: + estate = esock_atom_addr_unreachable; + break; + case SCTP_ADDR_REMOVED: + estate = esock_atom_addr_removed; + break; + case SCTP_ADDR_ADDED: + estate = esock_atom_addr_added; + break; + case SCTP_ADDR_MADE_PRIM: + estate = esock_atom_addr_made_prim; + break; +#if defined(SCTP_ADDR_CONFIRMED) + case SCTP_ADDR_CONFIRMED: + estate = esock_atom_addr_confirmed; + break; +#endif +#if defined(SCTP_ADDR_POTENTIALLY_FAILED) + case SCTP_ADDR_POTENTIALLY_FAILED: + estate = esock_atom_addr_potentially_failed; + break; +#endif + default: + estate = MKUI(env, p->spc_state); + break; + } + + eerr = MKUI(env, p->spc_error); // We should translate this also... + eaid = MKUI(env, p->spc_assoc_id); + + { + ERL_NIF_TERM keys[] = {esock_atom_esock_name, + esock_atom_type, esock_atom_flags, + esock_atom_state, esock_atom_error, + esock_atom_assoc_id}; + ERL_NIF_TERM vals[] = {esock_atom_sctp_notification, + esock_atom_peer_addr_change, eflags, + estate, eerr, + eaid}; + size_t numKeys = NUM(keys); + + ESOCK_ASSERT( numKeys == NUM(vals) ); + ESOCK_ASSERT( MKMA(env, keys, vals, numKeys, eEvent) ); + } +} + +static +void essio_encode_sockaddr_storage(ErlNifEnv* env, + ESockDescriptor* descP, + struct sockaddr_storage* addr, + ERL_NIF_TERM* eaddr) +{ + ERL_NIF_TERM tmp; + unsigned int len = sizeof(struct sockaddr_storage); + int fam = addr->ss_family; + + switch (fam) { + case AF_INET: + esock_encode_sockaddr_in(env, (struct sockaddr_in*) addr, len, &tmp); + break; + +#if defined(HAVE_IN6) && defined(AF_INET6) + case AF_INET6: + esock_encode_sockaddr_in6(env, (struct sockaddr_in6*) addr, len, &tmp); + break; +#endif + +#if defined(HAVE_SYS_UN_H) + case AF_UNIX: + esock_encode_sockaddr_un(env, (struct sockaddr_un*) addr, len, &tmp); + break; +#endif + +#if defined(AF_UNSPEC) + case AF_UNSPEC: + esock_encode_sockaddr_native(env, (struct sockaddr *) addr, len, + esock_atom_unspec, + &tmp); + break; +#endif + + default: + tmp = esock_atom_undefined; + break; + } + + *eaddr = tmp; +} +#endif + + +/* + * Deprecated! + * + * eEvent :: #{'$esock_name' := sctp_notfication, + * type := send_failed, + * flags := integer(), + * error := integer(), + * info := snd_rcv_info(), + * assoc_id := assoc_id(), + * data := binary()} + * + * snd_rcv_info() :: #{stream := uint16(), + * ssn := uint16(), + * flags := snd_rcv_info_flags(), + * ppid := uint16(), + * context := uint32(), + * time_to_live := uint32(), + * tsn := uint32(), + * cum_tsn := uint32(), + * assoc_id := int32()} + * + * snd_rcv_info_flags() :: [unordered | addr_over | abort | eof] + */ +#if defined(SCTP_SEND_FAILED) +static +void essio_encode_sctp_notif_send_failed(ErlNifEnv* env, + ESockDescriptor* descP, + struct sctp_send_failed* p, + ERL_NIF_TERM* eEvent) +{ + char* chunkP; + int chunkLen; + ERL_NIF_TERM eflags, eerr, einfo, eaid, edata; + + eflags = MKUI(env, p->ssf_flags); // We should translate this also... + eerr = MKUI(env, p->ssf_error); // We should translate this also... + eaid = MKUI(env, p->ssf_assoc_id); + + essio_encode_sctp_sndrcvinfo(env, descP, &p->ssf_info, &einfo); + +#if defined(HAVE_STRUCT_SCTP_SEND_FAILED_SSF_DATA) + chunkP = (char*) (&(p->ssf_data)); +#else + chunkP = ((char*) &(p->ssf_assoc_id)) + sizeof(p->ssf_assoc_id); +#endif + chunkLen = p->ssf_length - (chunkP - (char*) p); + edata = esock_make_new_binary(env, chunkP, chunkLen); + + { + ERL_NIF_TERM keys[] = {esock_atom_esock_name, + esock_atom_type, esock_atom_flags, esock_atom_error, + esock_atom_assoc_id, esock_atom_data}; + ERL_NIF_TERM vals[] = {esock_atom_sctp_notification, + esock_atom_send_failed, eflags, eerr, eaid, + eaid, edata}; + size_t numKeys = NUM(keys); + + ESOCK_ASSERT( numKeys == NUM(vals) ); + ESOCK_ASSERT( MKMA(env, keys, vals, numKeys, eEvent) ); + } +} + +static +void essio_encode_sctp_sndrcvinfo(ErlNifEnv* env, + ESockDescriptor* descP, + struct sctp_sndrcvinfo* p, + ERL_NIF_TERM* einfo) +{ +#if defined(SCTP_SNDRCV) + BOOLEAN_T tmp; + + tmp = esock_cmsg_encode_sctp_sndrcv(env, + (unsigned char*) p, + sizeof(struct sctp_sndrcvinfo), + einfo); + if (!tmp) + *einfo = esock_atom_undefined; + + VOID(descP); + +#else + + VOID(env); + VOID(descP); + VOID(p); + + *einfo = esock_atom_undefined; + +#endif +} + +#endif + + +/* + * eEvent :: #{'$esock_name' := sctp_notfication, + * type := send_failed_event, + * flags := integer(), % Should be [flag()] + * error := integer(), + * info := snd_info(), + * assoc_id := assoc_id(), + * data := binary()} + * + * snd_info() :: #{sid := uint16(), + * flags := uint16(), % Should be [flag()] + * ppid := uint16(), + * context := uint32(), + * assic_id := assoc_id()} + */ +#if defined(SCTP_SEND_FAILED_EVENT) +/* + * On linux (at least) the members of 'struct sctp_send_failed_event' + * is for some reason (copy-paste most likely) prefixed 'ssf_' instead + * of 'ssfe_' (which is the case on FreeBSD). + * This test only tests for the data field but we assume its the same + * prefix for all fields. + * EXCEPT FOR THE FIELD 'ssfe_info'... EFFING F!!#&&&!!%!%!% + */ +#if defined(HAVE_STRUCT_SCTP_SEND_FAILED_EVENT_SSFE_DATA) +#define SSFE_MEMBER(M) ssfe_ ## M +#else +#define SSFE_MEMBER(M) ssf_ ## M +#endif +static +void essio_encode_sctp_notif_send_failed_event(ErlNifEnv* env, + ESockDescriptor* descP, + struct sctp_send_failed_event* p, + ERL_NIF_TERM* eEvent) +{ + char* chunkP; + int chunkLen; + ERL_NIF_TERM eflags, eerr, einfo, eaid, edata; + + eflags = essio_encode_sctp_notif_send_failed_event_flags(env, descP, + p->SSFE_MEMBER(flags)); + eerr = MKUI(env, p->SSFE_MEMBER(error)); // Should translate this... + eaid = MKUI(env, p->SSFE_MEMBER(assoc_id)); + + essio_encode_sctp_sndinfo(env, descP, &p->ssfe_info, &einfo); + + chunkP = (char*) (&(p->SSFE_MEMBER(data))); + chunkLen = p->SSFE_MEMBER(length) - (chunkP - (char*) p); + edata = esock_make_new_binary(env, chunkP, chunkLen); + + { + ERL_NIF_TERM keys[] = {esock_atom_esock_name, + esock_atom_type, esock_atom_flags, esock_atom_error, + esock_atom_info, esock_atom_assoc_id, esock_atom_data}; + ERL_NIF_TERM vals[] = {esock_atom_sctp_notification, + esock_atom_send_failed_event, eflags, eerr, einfo, + eaid, edata}; + size_t numKeys = NUM(keys); + + ESOCK_ASSERT( numKeys == NUM(vals) ); + ESOCK_ASSERT( MKMA(env, keys, vals, numKeys, eEvent) ); + } +} + +static +ERL_NIF_TERM essio_encode_sctp_notif_send_failed_event_flags(ErlNifEnv* env, + ESockDescriptor* descP, + unsigned int flags) +{ + SocketTArray ta = TARRAY_CREATE(2); + ERL_NIF_TERM eflags; + +#if defined(SCTP_DATA_UNSENT) + if (flags & SCTP_DATA_UNSENT) + TARRAY_ADD(ta, esock_atom_data_unsent); +#endif + +#if defined(SCTP_DATA_SENT) + if (flags & SCTP_DATA_SENT) + TARRAY_ADD(ta, esock_atom_data_sent); +#endif + + TARRAY_TOLIST(ta, env, &eflags); + + return eflags; +} + +static +void essio_encode_sctp_sndinfo(ErlNifEnv* env, + ESockDescriptor* descP, + struct sctp_sndinfo* p, + ERL_NIF_TERM* einfo) +{ + ERL_NIF_TERM esid = MKUI(env, p->snd_sid); + ERL_NIF_TERM eflags = MKUI(env, p->snd_flags); // We should translate this also... + ERL_NIF_TERM eppid = MKUI(env, p->snd_ppid); + ERL_NIF_TERM econtext = MKUI(env, p->snd_context); + ERL_NIF_TERM eaid = MKUI(env, p->snd_assoc_id); + + VOID(descP); + + { + ERL_NIF_TERM keys[] = {esock_atom_sid, esock_atom_flags, + esock_atom_ppid, esock_atom_context, esock_atom_assoc_id}; + ERL_NIF_TERM vals[] = {esid, eflags, + eppid, econtext, eaid}; + size_t numKeys = NUM(keys); + + ESOCK_ASSERT( numKeys == NUM(vals) ); + ESOCK_ASSERT( MKMA(env, keys, vals, numKeys, einfo) ); + } +} +#endif + + +/* + * eEvent :: #{'$esock_name' := sctp_notfication, + * type := remote_error, + * flags := integer(), % Should be [flag()] + * error := integer(), + * assoc_id := assoc_id(), + * remote_causes := [integer()]} + */ +#if defined(SCTP_REMOTE_ERROR) +static +void essio_encode_sctp_notif_remote_error(ErlNifEnv* env, + ESockDescriptor* descP, + struct sctp_remote_error* p, + ERL_NIF_TERM* eEvent) +{ + char* chunkP; + int chunkLen; + ERL_NIF_TERM eflags, eerr, eaid, eremcauses; + + eflags = MKUI(env, p->sre_flags); // We should translate this also... + eerr = MKUI(env, p->sre_error); // We should translate this also... + eaid = MKUI(env, p->sre_assoc_id); + +#if defined(HAVE_STRUCT_SCTP_REMOTE_ERROR_SRE_DATA) + chunkP = (char*) (&(p->sre_data)); +#else + chunkP = ((char*) &(p->sre_assoc_id)) + sizeof(p->sre_assoc_id); +#endif + chunkLen = p->sre_length - (chunkP - (char *) p); + + /* Need to improve this + * and also find a description of the remote causes + * so that we can translate them into atoms + * among other things... + */ + essio_encode_sctp_notif_remote_causes(env, + chunkP, chunkLen, + &eremcauses); + + { + ERL_NIF_TERM keys[] = {esock_atom_esock_name, + esock_atom_type, esock_atom_flags, esock_atom_error, + esock_atom_assoc_id, esock_atom_remote_causes}; + ERL_NIF_TERM vals[] = {esock_atom_sctp_notification, + esock_atom_remote_error, eflags, eerr, + eaid, eremcauses}; + size_t numKeys = NUM(keys); + + ESOCK_ASSERT( numKeys == NUM(vals) ); + ESOCK_ASSERT( MKMA(env, keys, vals, numKeys, eEvent) ); + } +} + + +static +void essio_encode_sctp_notif_remote_causes(ErlNifEnv* env, + char* chunkP, + int chunkTLen, + ERL_NIF_TERM* eRCauses) +{ + /* The "chunk" itself contains its length, which must not be greater than + * the "chunkTLen" (total chunk length) derived from the over-all msg size. + */ + int len = sock_ntohs(*((uint16_t*)(chunkP+2))); + + if ((len >= 4) && (len <= chunkTLen)) { + char* causes; + char* cause; + int chunkOff, chunkCode, chunkLen; + SocketTArray ta = TARRAY_CREATE(20); // Just to be on the safe side + + causes = chunkP + 4; + cause = causes; + chunkOff = 0; + len -= 4; /* Total length of the "causes" fields */ + + while (chunkOff < len) { + chunkCode = sock_ntohs (*((uint16_t*)(cause))); + chunkLen = sock_ntohs (*((uint16_t*)(cause + 2))); + if (chunkLen <= 0) + /* Strange, but must guard against that! */ + break; + + TARRAY_ADD(ta, MKI(env, chunkCode)); + + cause += chunkLen; + chunkOff += chunkLen; + } + + TARRAY_TOLIST(ta, env, eRCauses); + + } else { + + *eRCauses = MKEL(env); + + } +} +#endif + + +/* + * eEvent :: #{'$esock_name' := sctp_notfication, + * type := shutdown_event, + * flags := integer(), % Should be [flag()] + * assoc_id := assoc_id()} + */ +#if defined(SCTP_SHUTDOWN_EVENT) +static +void essio_encode_sctp_notif_shutdown_event(ErlNifEnv* env, + ESockDescriptor* descP, + struct sctp_shutdown_event* p, + ERL_NIF_TERM* eEvent) +{ + ERL_NIF_TERM eflags, eaid; + + eflags = MKUI(env, p->sse_flags); // We should translate this also... + eaid = MKUI(env, p->sse_assoc_id); + + { + ERL_NIF_TERM keys[] = {esock_atom_esock_name, + esock_atom_type, esock_atom_flags, esock_atom_assoc_id}; + ERL_NIF_TERM vals[] = {esock_atom_sctp_notification, + esock_atom_shutdown_event, eflags, eaid}; + size_t numKeys = NUM(keys); + + ESOCK_ASSERT( numKeys == NUM(vals) ); + ESOCK_ASSERT( MKMA(env, keys, vals, numKeys, eEvent) ); + } +} +#endif + + +/* + * eEvent :: #{'$esock_name' := sctp_notfication, + * type := adaptation_event, + * flags := integer(), + * adaption_ind := integer(), + * assoc_id := assoc_id()} + */ +#if defined(SCTP_ADAPTATION_INDICATION) +static +void essio_encode_sctp_notif_adapt_event(ErlNifEnv* env, + ESockDescriptor* descP, + struct sctp_adaptation_event* p, + ERL_NIF_TERM* eEvent) +{ + ERL_NIF_TERM eflags, eandi, eaid; + + eflags = MKUI(env, p->sai_flags); // We should translate this also... + eandi = MKUI(env, p->sai_adaptation_ind); + eaid = MKUI(env, p->sai_assoc_id); + + { + ERL_NIF_TERM keys[] = {esock_atom_esock_name, + esock_atom_type, esock_atom_flags, + esock_atom_adaptation_indication, esock_atom_assoc_id}; + ERL_NIF_TERM vals[] = {esock_atom_sctp_notification, + esock_atom_adaptation_event, eflags, + eandi, eaid}; + size_t numKeys = NUM(keys); + + ESOCK_ASSERT( numKeys == NUM(vals) ); + ESOCK_ASSERT( MKMA(env, keys, vals, numKeys, eEvent) ); + } +} +#endif + + +/* + * eEvent :: #{'$esock_name' := sctp_notfication, + * type := partial_delivery_event, + * flags := integer(), % Should be [flag()] + * indication := integer(), + * assoc_id := assoc_id(), + * stream := undefined | integer(), + * seq := undefined | integer()} + */ +#if defined(SCTP_PARTIAL_DELIVERY_EVENT) +static +void essio_encode_sctp_notif_pdapi_event(ErlNifEnv* env, + ESockDescriptor* descP, + struct sctp_pdapi_event* p, + ERL_NIF_TERM* eEvent) +{ + ERL_NIF_TERM eflags, eind, eaid, estream, eseq; + + eflags = MKUI(env, p->pdapi_flags); // We should translate this also... + eind = MKUI(env, p->pdapi_indication); + eaid = MKUI(env, p->pdapi_assoc_id); +#if defined(HAVE_STRUCT_SCTP_PDAPI_EVENT_PDAPI_STREAM) + estream = MKUI(env, p->pdapi_stream); +#else + estream = esock_atom_undefined; +#endif +#if defined(HAVE_STRUCT_SCTP_PDAPI_EVENT_PDAPI_SEQ) + eseq = MKUI(env, p->pdapi_seq); +#else + eseq = esock_atom_undefined; +#endif + + { + ERL_NIF_TERM keys[] = {esock_atom_esock_name, + esock_atom_type, + esock_atom_flags, esock_atom_indication, + esock_atom_assoc_id, esock_atom_stream, esock_atom_seq}; + ERL_NIF_TERM vals[] = {esock_atom_sctp_notification, + esock_atom_partial_delivery, + eflags, eind, + eaid, estream, eseq}; + size_t numKeys = NUM(keys); + + ESOCK_ASSERT( numKeys == NUM(vals) ); + ESOCK_ASSERT( MKMA(env, keys, vals, numKeys, eEvent) ); } +} +#endif - /* Try to look up the symbolic type - */ - if (((cmsgTable = esock_lookup_cmsg_table(level, &num)) == NULL) || - ((cmsgSpecP = esock_lookup_cmsg_spec(cmsgTable, num, eType)) == NULL) || - (cmsgSpecP->decode == NULL)) { - /* We found no table for this level, - * we found no symbolic type in the level table, - * or no decode function for this type - */ - SSDBG( descP, - ("UNIX-ESSIO", - "decode_cmsghdr_value {%d} -> FALSE:\r\n" - " cmsgTable: %p\r\n" - " cmsgSpecP: %p\r\n", - descP->sock, cmsgTable, cmsgSpecP) ); - return FALSE; - } +/* + * eEvent :: #{'$esock_name' := sctp_notfication, + * type := authkey, + * flags := integer(), % Should be [flag()] + * keynumber := integer(), + * altkeynumber := integer(), + * indication := integer(), + * assoc_id := assoc_id()} + */ +#if defined(SCTP_AUTHENTICATION_INDICATION) +static +void essio_encode_sctp_notif_authkey(ErlNifEnv* env, + ESockDescriptor* descP, + struct sctp_authkey_event* p, + ERL_NIF_TERM* eEvent) +{ + ERL_NIF_TERM eflags = MKUI(env, p->auth_flags); // We should translate this also... + ERL_NIF_TERM ekeynum = MKUI(env, p->auth_keynumber); + ERL_NIF_TERM ealtkeynum = MKUI(env, p->auth_altkeynumber); + ERL_NIF_TERM eind = MKUI(env, p->auth_indication); + ERL_NIF_TERM eaid = MKUI(env, p->auth_assoc_id); - if (! cmsgSpecP->decode(env, eValue, cmsgP, rem, usedP)) { - // Decode function failed - SSDBG( descP, - ("UNIX-ESSIO", - "decode_cmsghdr_value {%d} -> FALSE:\r\n" - " decode function failed\r\n", - descP->sock) ); - return FALSE; + { + ERL_NIF_TERM keys[] = {esock_atom_esock_name, + esock_atom_type, + esock_atom_flags, esock_atom_keynumber, esock_atom_altkeynumber, + esock_atom_indication, esock_atom_assoc_id}; + ERL_NIF_TERM vals[] = {esock_atom_sctp_notification, + esock_atom_authkey, + eflags, ekeynum, ealtkeynum, + eind, eaid}; + size_t numKeys = NUM(keys); + + ESOCK_ASSERT( numKeys == NUM(vals) ); + ESOCK_ASSERT( MKMA(env, keys, vals, numKeys, eEvent) ); } +} +#endif - // Successful decode - type = cmsgSpecP->type; +/* + * eEvent :: #{'$esock_name' := sctp_notfication, + * type := sender_dry, + * flags := integer(), % Should be [flag()] + * assoc_id := assoc_id()} + */ +#if defined(SCTP_SENDER_DRY_EVENT) +static +void essio_encode_sctp_notif_sender_dry(ErlNifEnv* env, + ESockDescriptor* descP, + struct sctp_sender_dry_event* p, + ERL_NIF_TERM* eEvent) +{ + ERL_NIF_TERM eflags = MKUI(env, p->sender_dry_flags); // We should translate this also... + ERL_NIF_TERM eaid = MKUI(env, p->sender_dry_assoc_id); - SSDBG( descP, - ("UNIX-ESSIO", - "decode_cmsghdr_value {%d} -> TRUE:\r\n" - " level: %d\r\n" - " type: %d\r\n", - " *usedP: %lu\r\n", - descP->sock, level, type, (unsigned long) *usedP) ); + { + ERL_NIF_TERM keys[] = {esock_atom_esock_name, + esock_atom_type, + esock_atom_flags, esock_atom_assoc_id}; + ERL_NIF_TERM vals[] = {esock_atom_sctp_notification, + esock_atom_sender_dry, + eflags, eaid}; + size_t numKeys = NUM(keys); - cmsgP->cmsg_level = level; - cmsgP->cmsg_type = type; - return TRUE; + ESOCK_ASSERT( numKeys == NUM(vals) ); + ESOCK_ASSERT( MKMA(env, keys, vals, numKeys, eEvent) ); + } } +#endif +#if defined(SCTP_STREAM_RESET_EVENT) +/* + * eEvent :: #{'$esock_name' := sctp_notfication, + * type := stream_reset, + * flags := [incoming_ssn | outgoing_ssn | denied | failed] + * streams := [uint16()]} + */ + static -BOOLEAN_T decode_cmsghdr_data(ErlNifEnv* env, - ESockDescriptor* descP, - int level, - ERL_NIF_TERM eType, - ERL_NIF_TERM eData, - char* bufP, - size_t rem, - size_t* usedP) +void essio_encode_sctp_notif_stream_reset_event(ErlNifEnv* env, + ESockDescriptor* descP, + struct sctp_stream_reset_event* p, + ERL_NIF_TERM* eEvent) { - int type; - ErlNifBinary bin; - struct cmsghdr* cmsgP = (struct cmsghdr *) bufP; - ESockCmsgSpec* cmsgSpecP = NULL; + uint16_t* streams; + char* chunkP; + unsigned int chunkLen; + ERL_NIF_TERM eaid = MKUI(env, p->strreset_assoc_id); + ERL_NIF_TERM eflags, estreams; + unsigned int flags = p->strreset_flags; + SocketTArray ta = TARRAY_CREATE(4); + + /* *** flags *** */ + +#if defined(SCTP_STREAM_RESET_INCOMING_SSN) + if (flags & SCTP_STREAM_RESET_INCOMING_SSN) + TARRAY_ADD(ta, esock_atom_incoming_ssn); +#endif - SSDBG( descP, - ("UNIX-ESSIO", - "decode_cmsghdr_data {%d} -> entry \r\n" - " eType: %T\r\n" - " eData: %T\r\n", - descP->sock, eType, eData) ); +#if defined(SCTP_STREAM_RESET_OUTCOMING_SSN) + if (flags & SCTP_STREAM_RESET_OUTCOMING_SSN) + TARRAY_ADD(ta, esock_atom_outgoing_ssn); +#endif - // Decode Type - if (! GET_INT(env, eType, &type)) { - ESockCmsgSpec* cmsgTable = NULL; - size_t num = 0; +#if defined(SCTP_STREAM_RESET_DENIED) + if (flags & SCTP_STREAM_RESET_DENIED) + TARRAY_ADD(ta, esock_atom_denied); +#endif - /* Try to look up the symbolic (atom) type - */ - if ((! IS_ATOM(env, eType)) || - ((cmsgTable = esock_lookup_cmsg_table(level, &num)) == NULL) || - ((cmsgSpecP = esock_lookup_cmsg_spec(cmsgTable, num, eType)) == NULL)) { - /* Type was not an atom, - * we found no table for this level, - * or we found no symbolic type in the level table - */ +#if defined(SCTP_STREAM_RESET_FAILED) + if (flags & SCTP_STREAM_RESET_FAILED) + TARRAY_ADD(ta, esock_atom_failed); +#endif - SSDBG( descP, - ("UNIX-ESSIO", - "decode_cmsghdr_data {%d} -> FALSE:\r\n" - " cmsgTable: %p\r\n" - " cmsgSpecP: %p\r\n", - descP->sock, cmsgTable, cmsgSpecP) ); - return FALSE; - } + TARRAY_TOLIST(ta, env, &eflags); - type = cmsgSpecP->type; - } - // Decode Data - if (GET_BIN(env, eData, &bin)) { - void *p; + /* *** streams *** */ - p = esock_init_cmsghdr(cmsgP, rem, bin.size, usedP); - if (p == NULL) { - /* No room for the data - */ + streams = p->strreset_stream_list; + chunkP = (char*) streams; + chunkLen = p->strreset_length - (chunkP - (char*) p); + chunkLen = chunkLen / 2; // array is of 16-bit values + if (chunkLen >= 1) { + unsigned int i; + SocketTArray sta = TARRAY_CREATE(20); // Just to be on the safe side - SSDBG( descP, - ("UNIX-ESSIO", - "decode_cmsghdr_data {%d} -> FALSE:\r\n" - " rem: %lu\r\n" - " bin.size: %lu\r\n", - descP->sock, - (unsigned long) rem, - (unsigned long) bin.size) ); - return FALSE; + for (i = 0; i < chunkLen; i++) { + TARRAY_ADD(sta, MKUI(env, sock_ntohs( streams[i] ) )); } - // Copy the binary data - sys_memcpy(p, bin.data, bin.size); + TARRAY_TOLIST(sta, env, &estreams); - } else if ((! esock_cmsg_decode_int(env, eData, cmsgP, rem, usedP)) && - (! esock_cmsg_decode_bool(env, eData, cmsgP, rem, usedP))) { - SSDBG( descP, - ("UNIX-ESSIO", - "decode_cmsghdr_data {%d} -> FALSE\r\n", - descP->sock) ); - return FALSE; + } else { + estreams = MKEL(env); } - // Successful decode - SSDBG( descP, - ("UNIX-ESSIO", - "decode_cmsghdr_data {%d} -> TRUE:\r\n" - " level: %d\r\n" - " type: %d\r\n" - " *usedP: %lu\r\n", - descP->sock, level, type, (unsigned long) *usedP) ); + /* *** And finally put it all together *** */ - cmsgP->cmsg_level = level; - cmsgP->cmsg_type = type; - return TRUE; + { + ERL_NIF_TERM keys[] = {esock_atom_esock_name, + esock_atom_type, + esock_atom_flags, + esock_atom_assoc_id, + esock_atom_streams}; + ERL_NIF_TERM vals[] = {esock_atom_sctp_notification, + esock_atom_stream_reset, + eflags, + eaid, + estreams}; + size_t numKeys = NUM(keys); + + ESOCK_ASSERT( numKeys == NUM(vals) ); + ESOCK_ASSERT( MKMA(env, keys, vals, numKeys, eEvent) ); + } } +#endif -/* +++ encode_msg +++ - * - * Encode a msg() (recvmsg). In erlang its represented as - * a map, which has a specific set of attributes: - * - * addr (source address) - sockaddr() - * iov - [binary()] - * ctrl - [cmsg()] - * flags - msg_flags() + +#if defined(SCTP_ASSOC_RESET_EVENT) +/* + * eEvent :: #{'$esock_name' := sctp_notfication, + * type := assoc_reset, + * flags := [denied | failed] + * local_tsn := uint32(), + * remote_tsn := uint32()} */ static -void encode_msg(ErlNifEnv* env, - ESockDescriptor* descP, - ssize_t read, - struct msghdr* msgHdrP, - ErlNifBinary* dataBufP, - ErlNifBinary* ctrlBufP, - ERL_NIF_TERM* eMsg) +void essio_encode_sctp_notif_assoc_reset_event(ErlNifEnv* env, + ESockDescriptor* descP, + struct sctp_assoc_reset_event* p, + ERL_NIF_TERM* eEvent) { - ERL_NIF_TERM addr, iov, ctrl, flags; - - SSDBG( descP, - ("UNIX-ESSIO", "encode_msg {%d} -> entry with" - "\r\n read: %ld" - "\r\n", descP->sock, (long) read) ); + ERL_NIF_TERM eaid = MKUI(env, p->assocreset_assoc_id); + ERL_NIF_TERM eltsn = MKUI(env, p->assocreset_local_tsn); + ERL_NIF_TERM ertsn = MKUI(env, p->assocreset_remote_tsn); + ERL_NIF_TERM eflags; + unsigned int flags = p->assocreset_flags; + SocketTArray ta = TARRAY_CREATE(2); + +#if defined(SCTP_ASSOC_RESET_DENIED) + if (flags & SCTP_ASSOC_RESET_DENIED) + TARRAY_ADD(ta, esock_atom_denied); +#endif - /* The address is not used if we are connected (unless, maybe, - * family is 'local'), so check (length = 0) before we try to encodel - */ - if (msgHdrP->msg_namelen != 0) { - esock_encode_sockaddr(env, - (ESockAddress*) msgHdrP->msg_name, - msgHdrP->msg_namelen, - &addr); - } else { - addr = esock_atom_undefined; - } +#if defined(SCTP_ASSOC_RESET_FAILED) + if (flags & SCTP_ASSOC_RESET_FAILED) + TARRAY_ADD(ta, esock_atom_failed); +#endif + + TARRAY_TOLIST(ta, env, &eflags); - SSDBG( descP, - ("UNIX-ESSIO", "encode_msg {%d} -> encode iov" - "\r\n msg_iovlen: %lu" - "\r\n", - descP->sock, - (unsigned long) msgHdrP->msg_iovlen) ); + { + ERL_NIF_TERM keys[] = {esock_atom_esock_name, + esock_atom_type, + esock_atom_flags, + esock_atom_assoc_id, + esock_atom_local_tsn, + esock_atom_remote_tsn}; + ERL_NIF_TERM vals[] = {esock_atom_sctp_notification, + esock_atom_assoc_reset, + eflags, + eaid, + eltsn, + ertsn}; + size_t numKeys = NUM(keys); - esock_encode_iov(env, read, - msgHdrP->msg_iov, msgHdrP->msg_iovlen, dataBufP, - &iov); + ESOCK_ASSERT( numKeys == NUM(vals) ); + ESOCK_ASSERT( MKMA(env, keys, vals, numKeys, eEvent) ); + } +} - SSDBG( descP, - ("UNIX-ESSIO", - "encode_msg {%d} -> try encode cmsgs\r\n", - descP->sock) ); +#endif - encode_cmsgs(env, descP, ctrlBufP, msgHdrP, &ctrl); - SSDBG( descP, - ("UNIX-ESSIO", - "encode_msg {%d} -> try encode flags\r\n", - descP->sock) ); +#if defined(SCTP_STREAM_CHANGE_EVENT) +/* + * eEvent :: #{'$esock_name' := sctp_notfication, + * type := stream_change, + * flags := [denied | failed] + * in_streams := uint16(), + * out_streams := uint16()} + */ - esock_encode_msg_flags(env, descP, msgHdrP->msg_flags, &flags); +static +void essio_encode_sctp_notif_stream_change_event(ErlNifEnv* env, + ESockDescriptor* descP, + struct sctp_stream_change_event* p, + ERL_NIF_TERM* eEvent) +{ + ERL_NIF_TERM eaid = MKUI(env, p->strchange_assoc_id); + ERL_NIF_TERM einstr = MKUI(env, p->strchange_instrms); + ERL_NIF_TERM eoutstr = MKUI(env, p->strchange_outstrms); + ERL_NIF_TERM eflags; + unsigned int flags = p->strchange_flags; + SocketTArray ta = TARRAY_CREATE(2); + +#if defined(SCTP_STREAM_CHANGE_DENIED) + if (flags & SCTP_STREAM_CHANGE_DENIED) + TARRAY_ADD(ta, esock_atom_denied); +#endif - SSDBG( descP, - ("UNIX-ESSIO", "encode_msg {%d} -> components encoded:" - "\r\n addr: %T" - "\r\n ctrl: %T" - "\r\n flags: %T" - "\r\n", descP->sock, addr, ctrl, flags) ); +#if defined(SCTP_STREAM_CHANGE_FAILED) + if (flags & SCTP_STREAM_CHANGE_FAILED) + TARRAY_ADD(ta, esock_atom_failed); +#endif + + TARRAY_TOLIST(ta, env, &eflags); { - ERL_NIF_TERM keys[] = {esock_atom_iov, - esock_atom_ctrl, + ERL_NIF_TERM keys[] = {esock_atom_esock_name, + esock_atom_type, esock_atom_flags, - esock_atom_addr}; - ERL_NIF_TERM vals[] = {iov, ctrl, flags, addr}; + esock_atom_assoc_id, + esock_atom_inbound_streams, + esock_atom_outbound_streams}; + ERL_NIF_TERM vals[] = {esock_atom_sctp_notification, + esock_atom_stream_change, + eflags, + eaid, + einstr, + eoutstr}; size_t numKeys = NUM(keys); - + ESOCK_ASSERT( numKeys == NUM(vals) ); - - SSDBG( descP, - ("UNIX-ESSIO", - "encode_msg {%d} -> create map\r\n", - descP->sock) ); + ESOCK_ASSERT( MKMA(env, keys, vals, numKeys, eEvent) ); + } +} - if (msgHdrP->msg_namelen == 0) - numKeys--; // No addr - ESOCK_ASSERT( MKMA(env, keys, vals, numKeys, eMsg) ); +#endif - SSDBG( descP, - ("UNIX-ESSIO", - "encode_msg {%d}-> map encoded\r\n", - descP->sock) ); - } - SSDBG( descP, - ("UNIX-ESSIO", "encode_msg {%d} -> done\r\n", descP->sock) ); -} +/* + * eEvent :: #{'$esock_name' := sctp_notfication, + * type := sender_dry, + * flags := integer()} + */ +#if defined(HAVE_SCTP) +static +void essio_encode_sctp_notif_generic(ErlNifEnv* env, + ESockDescriptor* descP, + ERL_NIF_TERM etype, + union sctp_notification* p, + ERL_NIF_TERM* eEvent) +{ + ERL_NIF_TERM eflags = MKUI(env, p->sn_header.sn_flags); // We should translate this also... + { + ERL_NIF_TERM keys[] = {esock_atom_esock_name, + esock_atom_type, esock_atom_flags}; + ERL_NIF_TERM vals[] = {esock_atom_sctp_notification, etype, eflags}; + size_t numKeys = NUM(keys); + + ESOCK_ASSERT( numKeys == NUM(vals) ); + ESOCK_ASSERT( MKMA(env, keys, vals, numKeys, eEvent) ); + } +} +#endif /* +++ encode_cmsgs +++ @@ -7712,4 +10102,4 @@ void essio_down_reader(ErlNifEnv* env, } -#endif +#endif // ESOCK_ENABLE diff --git a/erts/emulator/nifs/win32/win_socket_asyncio.c b/erts/emulator/nifs/win32/win_socket_asyncio.c index 803cb6507a2d..a00955889f09 100644 --- a/erts/emulator/nifs/win32/win_socket_asyncio.c +++ b/erts/emulator/nifs/win32/win_socket_asyncio.c @@ -1679,6 +1679,54 @@ ERL_NIF_TERM esaio_info(ErlNifEnv* env) +/* ******************************************************************* + * esaio_command - Handle command + * + * Special command for the backend or always pass the command(s) + * through and let the backend decide for itself? + * + * If we get this far we know that the command is ok as far as the + * esock_command function knows. But there may be commands unknown + * to esock_command, passed unchecked here. + */ + +extern +ERL_NIF_TERM esaio_command(ErlNifEnv* env, + ERL_NIF_TERM command, + ERL_NIF_TERM cdata) +{ + ERL_NIF_TERM res; + + SGDBG( ("WIN-ESAIO", "esaio_command -> entry with %T\r\n", command) ); + + if (COMPARE(command, esock_atom_socket_debug) == 0) { + BOOLEAN_T dbg; + if (! esock_decode_bool(cdata, &dbg)) { + res = esock_atom_invalid; + } else { + ctrl.sockDbg = dbg; // We should really have a mutex for this... + res = esock_atom_ok; + } + } else if (COMPARE(command, esock_atom_debug) == 0) { + BOOLEAN_T dbg; + if (! esock_decode_bool(cdata, &dbg)) { + res = esock_atom_invalid; + } else { + ctrl.dbg = dbg; // We should really have a mutex for this... + res = esock_atom_ok; + } + } else { + res = esock_atom_invalid; + } + + SGDBG( ("WIN-ESAIO", "esaio_command -> done when res: %T\r\n", res) ); + + return esock_atom_ok; + +} + + + /* ******************************************************************* * esaio_open_plain - create an endpoint (from an existing fd) for * communication. diff --git a/erts/preloaded/ebin/prim_socket.beam b/erts/preloaded/ebin/prim_socket.beam index e9bc4fedf64a..beea2ff9a569 100644 Binary files a/erts/preloaded/ebin/prim_socket.beam and b/erts/preloaded/ebin/prim_socket.beam differ diff --git a/erts/preloaded/src/prim_socket.erl b/erts/preloaded/src/prim_socket.erl index 73d3b9687372..8308b26412b2 100644 --- a/erts/preloaded/src/prim_socket.erl +++ b/erts/preloaded/src/prim_socket.erl @@ -38,33 +38,44 @@ connect/1, connect/3, listen/2, accept/2, + peeloff/2, send/4, sendto/4, sendto/5, sendmsg/4, sendmsg/5, sendv/3, sendfile/4, sendfile/5, sendfile_deferred_close/1, recv/4, recvfrom/4, recvmsg/5, close/1, finalize_close/1, shutdown/2, setopt/3, setopt_native/3, - getopt/2, getopt_native/3, - sockname/1, peername/1, + getopt/2, getopt/3, getopt_native/3, + sockname/1, socknames/2, peername/1, peernames/2, ioctl/2, ioctl/3, ioctl/4, cancel/3 ]). -export([enc_sockaddr/1, p_get/1, rest_iov/2]). --nifs([nif_info/0, nif_info/1, nif_supports/0, nif_supports/1, nif_command/1, - nif_open/2, nif_open/4, nif_bind/2, nif_connect/1, nif_connect/3, +-nifs([nif_info/0, nif_info/1, + nif_supports/0, nif_supports/1, + nif_command/1, + nif_open/2, nif_open/4, + nif_bind/2, nif_bind/3, + nif_connect/1, nif_connect/3, nif_listen/2, nif_accept/2, + nif_peeloff/2, nif_send/4, nif_sendto/5, nif_sendmsg/5, nif_sendv/3, - nif_sendfile/5, nif_sendfile/4, nif_sendfile/1, nif_recv/4, - nif_recvfrom/4, nif_recvmsg/5, nif_close/1, nif_shutdown/2, - nif_setopt/5, nif_getopt/3, nif_getopt/4, nif_sockname/1, - nif_peername/1, nif_ioctl/2, nif_ioctl/3, nif_ioctl/4, nif_cancel/3, - nif_finalize_close/1]). + nif_sendfile/5, nif_sendfile/4, nif_sendfile/1, + nif_recv/4, nif_recvfrom/4, nif_recvmsg/5, + nif_close/1, nif_shutdown/2, + nif_setopt/5, nif_getopt/3, nif_getopt/5, + nif_sockname/1, nif_socknames/2, nif_peername/1, nif_peernames/2, + nif_ioctl/2, nif_ioctl/3, nif_ioctl/4, + nif_cancel/3, nif_finalize_close/1]). %% Also in socket -define(REGISTRY, socket_registry). +%% -define(DBG(T), +%% erlang:display({{self(), ?MODULE, ?LINE, ?FUNCTION_NAME}, T})). + %% =========================================================================== %% @@ -83,6 +94,7 @@ -define(ESOCK_SOCKADDR_NATIVE_DEFAULTS, (#{family => 0, addr => <<>>})). + %% =========================================================================== %% %% Constants common to prim_socket_nif.c - has to be "identical" @@ -478,22 +490,31 @@ bind(SockRef, Addrs, Action) when is_list(Addrs) -> %% ---------------------------------- +connect(SockRef, ConnectRef, SockAddrs) when is_list(SockAddrs) -> + try [enc_sockaddr(SA) || SA <- SockAddrs] of + ESockAddrs -> + do_connect(SockRef, ConnectRef, SockAddrs, ESockAddrs) + catch + throw : Reason -> + {error, Reason} + end; connect(SockRef, ConnectRef, SockAddr) -> - try - enc_sockaddr(SockAddr) - of + try enc_sockaddr(SockAddr) of ESockAddr -> - case nif_connect(SockRef, ConnectRef, ESockAddr) of - {invalid, Reason} -> - case Reason of - sockaddr -> - {error, {invalid, {Reason, SockAddr}}} - end; - Result -> Result - end + do_connect(SockRef, ConnectRef, SockAddr, ESockAddr) catch throw : Reason -> {error, Reason} + end. + +do_connect(SockRef, ConnectRef, SockAddr, ESockAddr) -> + case nif_connect(SockRef, ConnectRef, ESockAddr) of + {invalid, Reason} -> + case Reason of + sockaddr -> + {error, {invalid, {Reason, SockAddr}}} + end; + Result -> Result end. connect(SockRef) -> @@ -509,6 +530,12 @@ listen(SockRef, Backlog) -> accept(ListenSockRef, AccRef) -> nif_accept(ListenSockRef, AccRef). + +%% ---------------------------------- + +peeloff(SockRef, AssocId) -> + nif_peeloff(SockRef, AssocId). + %% ---------------------------------- send(SockRef, Bin, EFlags, SendRef) when is_integer(EFlags) -> @@ -872,6 +899,24 @@ getopt(SockRef, Option) -> end end. +getopt(SockRef, Option, Value) -> + case enc_sockopt(Option, Value) of + undefined -> + {error, {invalid, {socket_option, Option}}}; + invalid -> + {error, {invalid, {socket_option, Option}}}; + {NumLevel, NumOpt} -> + case nif_getopt(SockRef, NumLevel, NumOpt, value, Value) of + {invalid, Reason} -> + case Reason of + socket_option -> + {error, {invalid, {socket_option, Option}}} + end; + Result -> + getopt_result(Result, Option) + end + end. + getopt_result({ok, Val} = Result, Option) -> case Option of {socket,protocol} -> @@ -900,7 +945,7 @@ getopt_native(SockRef, Option, ValueSpec) -> invalid -> {error, {invalid, {socket_option, Option}}}; {NumLevel,NumOpt} -> - case nif_getopt(SockRef, NumLevel, NumOpt, ValueSpec) of + case nif_getopt(SockRef, NumLevel, NumOpt, native, ValueSpec) of {invalid, Reason} -> case Reason of value -> @@ -917,9 +962,15 @@ getopt_native(SockRef, Option, ValueSpec) -> sockname(Ref) -> nif_sockname(Ref). +socknames(Ref, AssocId) -> + nif_socknames(Ref, AssocId). + peername(Ref) -> nif_peername(Ref). +peernames(Ref, AssocId) -> + nif_peernames(Ref, AssocId). + %% ---------------------------------- @@ -1183,12 +1234,12 @@ enc_sockopt({Level,NumOpt}, NativeValue) invalid end end; -enc_sockopt({Level,Opt} = Option, _NativeValue) +enc_sockopt({Level,Opt} = Option, _Value) when is_atom(Level), is_atom(Opt) -> case p_get(options) of #{Option := NumOpt} -> NumOpt; - #{} -> + #{} = _Opts -> invalid end; enc_sockopt(Option, _NativeValue) -> @@ -1252,6 +1303,8 @@ nif_listen(_SockRef, _Backlog) -> erlang:nif_error(notsup). nif_accept(_SockRef, _Ref) -> erlang:nif_error(notsup). +nif_peeloff(_SockRef, _AssocId) -> erlang:nif_error(notsup). + nif_send(_SockRef, _Bin, _Flags, _SendRef) -> erlang:nif_error(notsup). nif_sendto(_SockRef, _Bin, _Dest, _Flags, _SendRef) -> erlang:nif_error(notsup). nif_sendmsg(_SockRef, _Msg, _Flags, _SendRef, _IOV) -> erlang:nif_error(notsup). @@ -1274,10 +1327,12 @@ nif_shutdown(_SockRef, _How) -> erlang:nif_error(notsup). nif_setopt(_SockRef, _Lev, _Opt, _Val, _NativeVal) -> erlang:nif_error(notsup). nif_getopt(_SockRef, _Lev, _Opt) -> erlang:nif_error(notsup). -nif_getopt(_SockRef, _Lev, _Opt, _ValSpec) -> erlang:nif_error(notsup). +nif_getopt(_SockRef, _Lev, _Opt, _Kind, _Val) -> erlang:nif_error(notsup). nif_sockname(_SockRef) -> erlang:nif_error(notsup). +nif_socknames(_SockRef, _AssocId) -> erlang:nif_error(notsup). nif_peername(_SockRef) -> erlang:nif_error(notsup). +nif_peernames(_SockRef, _AssocId) -> erlang:nif_error(notsup). nif_ioctl(_SockRef, _GReq) -> erlang:nif_error(notsup). nif_ioctl(_SockRef, _GReq, _Arg) -> erlang:nif_error(notsup). diff --git a/lib/kernel/examples/Makefile b/lib/kernel/examples/Makefile index 2356bbf602cb..1c49e443d9cb 100644 --- a/lib/kernel/examples/Makefile +++ b/lib/kernel/examples/Makefile @@ -1,4 +1,11 @@ -# ``Licensed under the Apache License, Version 2.0 (the "License"); +# +# %CopyrightBegin% +# +# SPDX-License-Identifier: Apache-2.0 +# +# Copyright Ericsson AB 1999-2025. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # @@ -9,13 +16,10 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -# -# The Initial Developer of the Original Code is Ericsson Utvecklings AB. -# Portions created by Ericsson are Copyright 1999, Ericsson Utvecklings -# AB. All Rights Reserved.'' -# -# $Id$ # +# %CopyrightEnd% +# + include $(ERL_TOP)/make/target.mk include $(ERL_TOP)/make/$(TARGET)/otp.mk @@ -45,7 +49,7 @@ RELSYSDIR = $(RELEASE_PATH)/lib/kernel-$(KERNEL_VSN)/examples # Pack and install the complete directory structure from # here (CWD) and down, for all examples. -EXAMPLES = uds_dist erl_uds_dist gen_tcp_dist +EXAMPLES = uds_dist erl_uds_dist gen_tcp_dist socket_sctp release_spec: $(INSTALL_DIR) "$(RELSYSDIR)" diff --git a/lib/kernel/examples/socket_sctp/ebin/.gitignore b/lib/kernel/examples/socket_sctp/ebin/.gitignore new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/lib/kernel/examples/socket_sctp/src/Makefile b/lib/kernel/examples/socket_sctp/src/Makefile new file mode 100644 index 000000000000..e00717306580 --- /dev/null +++ b/lib/kernel/examples/socket_sctp/src/Makefile @@ -0,0 +1,58 @@ +# +# %CopyrightBegin% +# +# SPDX-License-Identifier: Apache-2.0 +# +# Copyright Ericsson AB 2024-2025. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# %CopyrightEnd% +# + +RM=rm -f +CP=cp +EBIN=../ebin +EMULATOR=beam +ERLC=erlc +# Works if building in open source source tree +KERNEL_INCLUDE=$(ERL_TOP)/lib/kernel/src +ERLCFLAGS+= -W -b$(EMULATOR) -I$(KERNEL_INCLUDE) + +MODULES = \ + socket_sctp_lib \ + socket_sctp_server \ + socket_sctp_client + +TARGET_FILES=$(MODULES:%=$(EBIN)/%.$(EMULATOR)) + +opt: $(TARGET_FILES) + +$(EBIN)/%.$(EMULATOR): %.erl + $(ERLC) $(ERLCFLAGS) -o$(EBIN) $< + +clean: + $(RM) $(TARGET_FILES) + +$(EBIN)/socket_sctp_lib.ebin: \ + socket_sctp_lib.erl \ + socket_sctp_lib.hrl + +$(EBIN)/socket_sctp_client.ebin: \ + socket_sctp_client.erl \ + socket_sctp_lib.hrl + +$(EBIN)/socket_sctp_server.ebin: \ + socket_sctp_server.erl \ + socket_sctp_lib.hrl + diff --git a/lib/kernel/examples/socket_sctp/src/socket_sctp_client.erl b/lib/kernel/examples/socket_sctp/src/socket_sctp_client.erl new file mode 100644 index 000000000000..dd4b1c28a314 --- /dev/null +++ b/lib/kernel/examples/socket_sctp/src/socket_sctp_client.erl @@ -0,0 +1,441 @@ +%% +%% %CopyrightBegin% +%% +%% SPDX-License-Identifier: Apache-2.0 +%% +%% Copyright Ericsson AB 2024-2025. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% +%% +%% Note that this (client) module is *not* an example! +%% This module is just intended to test the actual example: +%% the echo server. +%% + + +-module(socket_sctp_client). + +-export([start/2, start/3]). + +-include("socket_sctp_lib.hrl"). + +-define(MAX_RESEND, 5). +-define(ECHO_TIMEOUT, timer:seconds(1)). + + +-spec start(ServerSA :: socket:sockaddr(), + MessageOrMessages :: string() | [string() | {string(), pos_integer()}]) -> + any(). + +start(ServerSA, MessageOrMessages) when is_list(MessageOrMessages) -> + start(ServerSA, MessageOrMessages, false). + +start(ServerSA, MessageOrMessages, Debug) + when is_list(MessageOrMessages) andalso is_boolean(Debug) -> + do_start(ServerSA, process_messages(MessageOrMessages), Debug). + +do_start(#{family := _, + addr := _, + port := _} = ServerSA, Messages, Debug) -> + + put(sname, "client-starter"), + put(dbg, Debug), + + Self = self(), + {Client, MRef} = + spawn_monitor( + fun() -> + init(#{parent => Self, + server_sa => ServerSA, + msgs => Messages, + dbg => Debug}) + end), + receive + {'DOWN', MRef, process, Client, Reason} -> + ?ERROR("Client start failure: " + "~n ~p", [Reason]), + {error, {client_start_failed, Reason}}; + + {?MODULE, Client, started} -> + ?INFO("Client (~p) started", [Client]), + {ok, Client} + end. + +process_messages(MessageOrMessages) -> + case ?IS_STR(MessageOrMessages) of + true -> + %% Ok, this was actually only one message + [{MessageOrMessages, 0}]; + false -> % Maybe messages + process_messages2(MessageOrMessages) + end. + +process_messages2([]) -> + []; +process_messages2([{Message, TO}|Messages]) + when is_list(Message) andalso is_integer(TO) andalso (TO > 0) -> + case ?IS_STR(Message) of + true -> + [{Message, TO} | process_messages2(Messages)]; + false -> + ?WARNING("Skip invalid message: " + "~n '~p'", [Message]), + process_messages2(Messages) + end; +process_messages2([{Message, TO}|Messages]) + when is_list(Message) -> + case ?IS_STR(Message) of + true -> + ?WARNING("Skip invalid post echo timeout for: " + "~n Message: ~p" + "~n (invalid) Timeout: ~p", [Message, TO]), + [{Message, 0} | process_messages2(Messages)]; + false -> + ?WARNING("Skip invalid message: " + "~n '~p'", [Message]), + process_messages2(Messages) + end; +process_messages2([{Message, _TO}|Messages]) -> + ?WARNING("Skip invalid message: " + "~n '~p'", [Message]), + process_messages2(Messages); +process_messages2([Message|Messages]) + when is_list(Message) -> + case ?IS_STR(Message) of + true -> + [{Message, 0} | process_messages2(Messages)]; + false -> + ?ERROR("Skip invalid message: " + "~n '~p'", [Message]), + process_messages2(Messages) + end; +process_messages2([Message|Messages]) -> + ?WARNING("Skip invalid message: " + "~n '~p'", [Message]), + process_messages2(Messages). + + +init(#{parent := Parent, + server_sa := #{family := Domain} = ServerSA, + msgs := Messages, + dbg := Debug}) -> + + put(sname, "client"), + put(dbg, Debug), + + ?DEBUG("~s -> try open (~w) socket", [?FUNCTION_NAME, Domain]), + Sock = case socket:open(Domain, seqpacket, sctp) of + {ok, S} -> + S; + {error, OReason} -> + ?ERROR("Failed open socket: " + "~n ~p", [OReason]), + ?STOP() + end, + + ?DEBUG("~s -> try get (local) address", [?FUNCTION_NAME]), + Addr = case ?WHICH_ADDR(Domain) of + {ok, A} -> + A; + {error, AReason} -> + ?ERROR("failed get (local) address: " + "~n ~p", [AReason]), + ?STOP() + end, + + ?DEBUG("~s -> try bind socket (to ~p)", [?FUNCTION_NAME, Addr]), + OwnSA = #{family => Domain, + addr => Addr, + port => 0}, + case socket:bind(Sock, OwnSA) of + ok -> + ok; + {error, BReason} -> + ?ERROR("Failed bind socket: " + "~n ~p", [BReason]), + ?STOP() + end, + + ?DEBUG("~s -> try subscribe to events", [?FUNCTION_NAME]), + ok = socket:setopt(Sock, ?MK_SCTP_SOCKOPT(events), ?SCTP_EVENTS(true)), + + ?DEBUG("~s -> try connect to: " + "~n ~p", [?FUNCTION_NAME, ServerSA]), + ok = socket:connect(Sock, ServerSA), + + + ?DEBUG("~s -> await connect confirmation", [?FUNCTION_NAME]), + {AssocID, OutStreams, InStreams} = + case socket:recvmsg(Sock) of + {ok, #{flags := Flags, + addr := SA, + notification := #{type := assoc_change, + state := comm_up, + assoc_id := AID, + outbound_streams := OS, + inbound_streams := IS}}} -> + ?INFO("Received expected connect confirmation:" + "~n Flags: ~p" + "~n SA: ~p" + "~n AssocID: ~p" + "~n OS: ~p" + "~n IS: ~p", + [Flags, SA, AID, OS, IS]), + {AID, OS, IS}; + + {ok, #{flags := Flags, + addr := SA, + notification := #{type := assoc_change, + state := State}}} -> + ?ERROR("Received unexpected connect failure: ~p" + "~n Flags: ~p" + "~n SA: ~p", + [?FUNCTION_NAME, State, Flags, SA]), + exit({unexpected, State}); + + {ok, Msg} -> + ?ERROR("Received unexpected msg: " + "~n ~p", + [?FUNCTION_NAME, Msg]), + exit({unexpected_msg, Msg}); + + {error, ReasonC} -> + ?ERROR("Unexpected recvmsg failure: " + "~n ~p", + [?FUNCTION_NAME, ReasonC]), + exit({unexpected_recvmsg_failure, ReasonC}) + end, + + ?DEBUG("~s -> monitor parent", [?FUNCTION_NAME]), + MRef = erlang:monitor(process, Parent), + + Parent ! {?MODULE, self(), started}, + + loop(#{mode => send, + parent => Parent, + parent_mref => MRef, + sock => Sock, + assoc_id => AssocID, + stream => 0, + ostream => OutStreams, + istreams => InStreams, + select => undefined, + resend => {0, undefined}}, + Messages). + +loop(#{mode := shutdown, + sock := Sock, + assoc_id := AID}, Messages) -> + ?INFO("shutting down with ~w messages", [length(Messages)]), + EofSRI = #{assoc_id => AID, + stream => 0, + ssn => 0, + flags => [eof], + ppid => 0, + context => 0, + time_to_live => 0, + tsn => 0, + cum_tsn => 0}, + EofMsg = #{iov => [], + ctrl => [#{level => sctp, + type => sndrcv, + value => EofSRI}]}, + _ = socket:sendmsg(Sock, EofMsg), + (catch socket:close(Sock)); + +loop(#{sock := Sock, + assoc_id := AID, + stream := Stream} = State, [] = Messages) + when (Sock =/= undefined) andalso + (AID =/= undefined) andalso + (Stream =/= undefined) -> + ?INFO("all messages sent and ackwnoledged - send eof"), + EofSRI = #{assoc_id => AID, + stream => 0, + ssn => 0, + flags => [eof], + ppid => 0, + context => 0, + time_to_live => 0, + tsn => 0, + cum_tsn => 0}, + EofMsg = #{iov => [], + ctrl => [#{level => sctp, + type => sndrcv, + value => EofSRI}]}, + case socket:sendmsg(Sock, EofMsg) of + ok -> + ?INFO("~s -> eof message sent", [?FUNCTION_NAME]), + loop(State#{assoc_id => undefined, + stream => undefined}, + Messages); + {error, Reason} -> + ?ERROR("Failed issue (send) eof (graceful shutdown):" + "~n ~p", [Reason]), + exit({eof, Reason}) + end; +loop(#{sock := Sock}, [] = _Messages) -> + ?INFO("done"), + (catch socket:close(Sock)), + exit(normal); + +%% Send the message +loop(#{mode := send, + sock := Sock, + assoc_id := AID, + stream := Stream, + ostream := OutStreams, + select := undefined} = State, + [{Message, _}|_] = Messages) -> + ?DEBUG("~s(~w,~w) -> try send message", [?FUNCTION_NAME, AID, Stream]), + SRI = #{assoc_id => AID, stream => Stream}, + CtrlSRI = #{level => sctp, + type => sndrcv, + value => SRI}, + Msg = #{iov => [list_to_binary(Message)], + ctrl => [CtrlSRI]}, + case socket:sendmsg(Sock, Msg) of + ok -> + ?INFO("message sent: " + "~n ~p", [Message]), + %% 'Message' will be removed from list when it has been acknowledged + NewState = start_echo_timer(State), + loop(NewState#{stream => next_stream_id(Stream, OutStreams), + mode => recv}, Messages); + + {select, SelectInfo} -> + ?DEBUG("~s -> select", [?FUNCTION_NAME]), + loop(State#{select => SelectInfo}, Messages); + + {error, Reason} -> + ?ERROR("failed sending message:" + "~n ~p", [Reason]), + exit({sendmsg, Reason}) + end; + +%% Await the acknowledgement +loop(#{mode := recv, + sock := Sock, + assoc_id := AID, + select := undefined} = State, + [{Message, TO}|RestMessages] = Messages) + when (Sock =/= undefined) andalso (AID =/= undefined) -> + ?DEBUG("~s -> try recvmsg", [?FUNCTION_NAME]), + case socket:recvmsg(Sock, nowait) of + {ok, #{flags := _, + addr := SA, + iov := [Data], + ctrl := [#{level := sctp, + type := sndrcv, + value := #{assoc_id := AID, + stream := Stream}}]}} -> + ?DEBUG("~s -> received data:" + "~n From: ~p" + "~n AssocID: ~p" + "~n Stream: ~p", [?FUNCTION_NAME, SA, AID, Stream]), + case binary_to_list(Data) of + Message -> + ?INFO("received echo:" + "~n ~p", [Message]), + NewState = cancel_echo_timer(State), + maybe_sleep(TO), + loop(NewState#{mode => send}, RestMessages); + UnExpMessage -> + ?ERROR("Received unexpected massage: " + "~n Expected: ~p" + "~n Received: ~p", [Message, UnExpMessage]), + exit({unexpected_message, {Message, UnExpMessage}}) + end; + + {ok, #{notification := Notification}} -> + ?WARNING("Received unexpected notification:" + "~n ~p", [Notification]), + loop(State, Messages); + + {select, SelectInfo} -> + ?DEBUG("~s -> select", [?FUNCTION_NAME]), + loop(State#{select => SelectInfo}, Messages); + + {error, Reason} -> + ?ERROR("Receive failed:" + "~n ~p", [Reason]), + exit({recvmsg, Reason}) + end; + +loop(#{mode := Mode, + parent := Parent, + parent_mref := MRef, + sock := Sock, + select := {select_info, _, SelectHandle}} = State, + Messages) -> + ?DEBUG("~s -> await ~w select message", [?FUNCTION_NAME, Mode]), + receive + {'DOWN', MRef, process, Parent, Info} -> + ?ERROR("Received unexpected DOWN from parent:" + "~n ~p", [Info]), + (catch socket:close(Sock)), + exit({parent_died, Info}); + + {'$socket', Sock, select, SelectHandle} -> + ?DEBUG("~s -> received select message", [?FUNCTION_NAME]), + loop(State#{select => undefined}, Messages); + + {'$socket', Sock, abort, Info} -> + ?ERROR("~s(~w) -> received unexpected abort message:" + "~n ~p", [?FUNCTION_NAME, Mode, Info]), + exit({abort, Info}); + + {timeout, TRef, echo_timeout} -> + ?DEBUG("~s -> received (echo-) timeout message", [?FUNCTION_NAME]), + NewState = handle_echo_timeout(State, TRef), + loop(NewState, Messages) + end. + + +next_stream_id(Stream, NumStreams) when Stream < NumStreams -> + Stream + 1; +next_stream_id(_, _) -> + 0. + +%% This timer is used to handle that we do not get an echo in time +start_echo_timer(#{resend := {NumResend, undefined}} = State) -> + TRef = erlang:start_timer(?ECHO_TIMEOUT, self(), echo_timeout), + State#{resend => {NumResend+1, TRef}}. + +cancel_echo_timer(#{resend := {_, TRef}} = State) -> + _ = erlang:cancel_timer(TRef), + State#{resend => {0, undefined}}. + +%% We are waiting for an echo, but got a echo timeout: resend +handle_echo_timeout(#{mode := recv, + resend := {NumResend, TRef}, + sock := Sock, + select := SelectInfo} = State, TRef) + when (NumResend < ?MAX_RESEND) andalso (SelectInfo =/= undefined) -> + ?WARNING("echo timeout - issue resend"), + (catch socket:cancel(Sock, SelectInfo)), + State#{mode => send, + resend => {NumResend, undefined}, + select => undefined}; +handle_echo_timeout(State, _TRef) -> + ?ERROR("echo timeout - shut down"), + State#{mode => shutdown, resend => timeout}. + + +maybe_sleep(TO) when (TO > 0) -> + ?SLEEP(TO); +maybe_sleep(_) -> + ok. diff --git a/lib/kernel/examples/socket_sctp/src/socket_sctp_lib.erl b/lib/kernel/examples/socket_sctp/src/socket_sctp_lib.erl new file mode 100644 index 000000000000..186b63177789 --- /dev/null +++ b/lib/kernel/examples/socket_sctp/src/socket_sctp_lib.erl @@ -0,0 +1,115 @@ +%% +%% %CopyrightBegin% +%% +%% SPDX-License-Identifier: Apache-2.0 +%% +%% Copyright Ericsson AB 2024-2025. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% + +-module(socket_sctp_lib). + +-export([stop/0, + mq/1, + f/2, is_string/1, + which_domain/1, which_local_addr/1, + dmsg/2, imsg/2, wmsg/2, emsg/2]). + + +%% =========================================================================== + +which_domain(IP) when is_tuple(IP) andalso (tuple_size(IP) =:= 8) -> + inet6; +which_domain(_) -> + inet. + + +%% This gets the local "proper" address +%% (not {127, ...} or {169,254, ...} or {0, ...} or {16#fe80, ...}) +which_local_addr(Domain) -> + Filter = fun(#{flags := Flags, + addr := #{family := Fam}}) when (Fam =:= Domain) -> + (not lists:member(loopback, Flags)) andalso + lists:member(up, Flags); + (_) -> + false + end, + case net:getifaddrs(Filter) of + {ok, [#{addr := #{addr := Addr}}|_]} -> + {ok, Addr}; + {ok, _} -> + {ok, any}; + {error, _} = ERROR -> + ERROR + end. + + +%% =========================================================================== + +mq(Pid) when is_pid(Pid) -> + {messages, MQ} = process_info(Pid, messages), + MQ. + + +%% =========================================================================== + +is_string(MaybeStr) -> + io_lib:char_list(MaybeStr). + + +f(F, A) -> + lists:flatten(io_lib:format(F, A)). + + +%% =========================================================================== + +stop() -> + erlang:halt(). + + +%% =========================================================================== + +formated_timestamp() -> + format_timestamp(os:timestamp()). + +format_timestamp({_N1, _N2, N3} = TS) -> + {_Date, Time} = calendar:now_to_local_time(TS), + {Hour, Min, Sec} = Time, + FormatTS = io_lib:format("~.2.0w:~.2.0w:~.2.0w.~.3.0w", + [Hour, Min, Sec, N3 div 1000]), + lists:flatten(FormatTS). + +dmsg(F, A) -> + print(get(dbg), "", F, A). + +imsg(F, A) -> + print(true, "", F, A). + +wmsg(F, A) -> + print(true, "", F, A). + +wmsg(F, A) -> + print(true, "", F, A). + +emsg(F, A) -> + print(true, "", F, A). + +print(true, Pre, F, A) -> + io:format("~s [ ~s ][ ~s ] "++ F ++ "~n", + [Pre, formated_timestamp(), get(sname) | A]); +print(_, _, _, _) -> + ok. + diff --git a/lib/kernel/examples/socket_sctp/src/socket_sctp_lib.hrl b/lib/kernel/examples/socket_sctp/src/socket_sctp_lib.hrl new file mode 100644 index 000000000000..15ccd1db38a1 --- /dev/null +++ b/lib/kernel/examples/socket_sctp/src/socket_sctp_lib.hrl @@ -0,0 +1,82 @@ +%% +%% %CopyrightBegin% +%% +%% SPDX-License-Identifier: Apache-2.0 +%% +%% Copyright Ericsson AB 2024-2025. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% + +-ifndef(SOCKET_SCTP_LIB_HRL_). + +-define(SOCKET_SCTP_LIB_HRL__, true). + +-define(LIB, socket_sctp_lib). + +-define(WHICH_DOMAIN(IP), ?LIB:which_domain((IP))). +-define(WHICH_ADDR(D), ?LIB:which_local_addr((D))). + +-define(SCTP_EVENTS(DATA_IO), + #{data_io => (DATA_IO), + association => true, + address => true, + send_failure => true, + peer_error => true, + shutdown => true, + partial_delivery => true, + adaptation_layer => false, + authentication => false, + sender_dry => false}). + +-define(MK_SOCKOPT(Lvl, Opt), {(Lvl), (Opt)}). +-define(MK_OTP_SOCKOPT(Opt), ?MK_SOCKOPT(otp, (Opt))). +-define(MK_SOCK_SOCKOPT(Opt), ?MK_SOCKOPT(socket, (Opt))). +-define(MK_IP_SOCKOPT(Opt), ?MK_SOCKOPT(ip, (Opt))). +-define(MK_IPV6_SOCKOPT(Opt), ?MK_SOCKOPT(ipv6, (Opt))). +-define(MK_SCTP_SOCKOPT(Opt), ?MK_SOCKOPT(sctp, (Opt))). + +-define(WHICH_STATUS(Sock, AID), + fun() -> + case socket:getopt((Sock), + ?MK_SCTP_SOCKOPT(status), + #{assoc_id => (AID)}) of + {ok, Status} -> Status; + {error, closed} -> closed; + {error, _} -> undefined + end + end()). + +-define(STOP(), ?LIB:stop()). + +-define(MQ(), ?LIB:mq(self())). +-define(MQ(P), ?LIB:mq((P))). + +-define(F(FORMAT, ARGS), ?LIB:f((FORMAT), (ARGS))). +-define(IS_STR(STR), ?LIB:is_string((STR))). + + +-define(DEBUG(F), ?DEBUG(F, [])). +-define(DEBUG(F, A), ?LIB:dmsg((F), (A))). +-define(INFO(F), ?INFO(F, [])). +-define(INFO(F, A), ?LIB:imsg((F), (A))). +-define(WARNING(F), ?WARNING(F, [])). +-define(WARNING(F, A), ?LIB:wmsg((F), (A))). +-define(ERROR(F), ?ERROR(F, [])). +-define(ERROR(F, A), ?LIB:emsg((F), (A))). +-define(SLEEP(T), + receive after trunc(T) -> ok end). + +-endif. diff --git a/lib/kernel/examples/socket_sctp/src/socket_sctp_server.erl b/lib/kernel/examples/socket_sctp/src/socket_sctp_server.erl new file mode 100644 index 000000000000..c9e006054668 --- /dev/null +++ b/lib/kernel/examples/socket_sctp/src/socket_sctp_server.erl @@ -0,0 +1,576 @@ +%% +%% %CopyrightBegin% +%% +%% SPDX-License-Identifier: Apache-2.0 +%% +%% Copyright Ericsson AB 2024-2025. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% + +%% ======================================================================== +%% +%% This is a simple example of an echo server. +%% This is *not* an example of how to write an application. +%% Its simply examplifies what kind of messages needs to +%% be handled for an server using socket SCTP. +%% +%% The example consists of three parts: +%% 1) Server starter: +%% Starts the acceptor. +%% 2) Acceptor: +%% Listens for "connect messages" and when one arrives +%% spawns a handler process. +%% 3) Handler: +%% The handler performs a peeloff, and handles all communication +%% on the new socket. +%% +%% ======================================================================== + +-module(socket_sctp_server). + +-export([start/0, start/1]). + +-include("socket_sctp_lib.hrl"). + +start() -> + start(false). + +%% This is when started from a shell: +%% erl -s socket_sctp_server start true +start([Debug]) when is_boolean(Debug) -> + start(Debug); + +%% This is when started from a shell: +%% erl -run socket_sctp_server start true +start([DebugStr]) when is_list(DebugStr) -> + Debug = + try list_to_atom(DebugStr) of + Dbg when is_boolean(Dbg) -> + Dbg; + _ -> + ?ERROR("Invalid debug argument: '~s'", [DebugStr]), + ?STOP() + catch + _:_:_ -> + ?ERROR("Failed convert debug string ('~s')", + [DebugStr]), + ?STOP() + end, + start(Debug); + +start(Debug) when is_boolean(Debug) -> + case ?WHICH_ADDR(inet) of + {ok, Addr} -> + start_acceptor(Addr, Debug); + {error, Reason} -> + ?ERROR("Failed get (default) address: " + "~n ~p", [Reason]), + ?STOP() + end; + +start(Invalid) -> + ?ERROR("invalid start commands: " + "~n Invalid: ~p" + "~n", [Invalid]), + ?STOP(). + + +start_acceptor(IP, Debug) + when is_tuple(IP) andalso is_boolean(Debug) -> + + put(sname, "server-starter"), + put(dbg, Debug), + + ?DEBUG("~w -> entry with" + "~n IP: ~p", [?FUNCTION_NAME, IP]), + + Self = self(), + {Acceptor, MRef} = + spawn_monitor(fun() -> + acceptor_init(#{parent => Self, + ip => IP, + port => 0, + debug => Debug}) + end), + receive + {'DOWN', MRef, process, Acceptor, Reason} -> + ?ERROR("Received unexpected DOWN from starting acceptor:" + "~n ~p", [Reason]), + {error, {acceptor_start, Reason}}; + + {?MODULE, Acceptor, {started, Addr, Port}} -> + ?INFO("acceptor started:" + "~n Acceptor: ~p" + "~n Addr: ~p" + "~n Port: ~p", [Acceptor, Addr, Port]), + {ok, {Acceptor, Addr, Port}} + end. + +acceptor_init(#{parent := Parent, + ip := IP, + port := Port0, + debug := Debug}) -> + + ?DEBUG("~s -> entry with" + "~n Parent: ~p" + "~n IP: ~p" + "~n Port: ~p", [?FUNCTION_NAME, Parent, IP, Port0]), + + put(sname, "acceptor"), + put(dbg, Debug), + + Domain = ?WHICH_DOMAIN(IP), + ?DEBUG("~s -> open socket (with Domain = ~w)", [?FUNCTION_NAME, Domain]), + Sock = + case socket:open(Domain, seqpacket, sctp) of + {ok, S} -> + S; + {error, OReason} -> + ?ERROR("Failed open socket: " + "~n ~p", [OReason]), + ?STOP() + end, + + SA = #{family => Domain, + addr => IP, + port => Port0}, + ?DEBUG("bind socket to: " + "~n ~p", [SA]), + case socket:bind(Sock, SA) of + ok -> + ok; + {error, BReason} -> + ?ERROR("Failed bind socket: " + "~n SA: ~p" + "~n ~p", [SA, BReason]), + ?STOP() + end, + + ?DEBUG("listen"), + case socket:listen(Sock, true) of + ok -> + ok; + {error, LReason} -> + ?ERROR("Failed listen: " + "~n ~p", [LReason]), + ?STOP() + end, + + Evs = ?SCTP_EVENTS(false), + ?DEBUG("subscribe to (sctp) events"), + case socket:setopt(Sock, {sctp, events}, Evs) of + ok -> + ok; + {error, Reason4} -> + ?ERROR("Failed SCTP events:" + "~n ~p", [Reason4]), + ?STOP() + end, + + ?DEBUG("monitor parent"), + MRef = erlang:monitor(process, Parent), + + ?DEBUG("get sockname"), + {BAddr, BPort} = + case socket:sockname(Sock) of + {ok, #{addr := BA, + port := BP}} -> + {BA, BP}; + {error, SNReason} -> + ?ERROR("Failed get sockname: " + "~n ~p", [SNReason]), + ?STOP() + end, + + ?INFO("Socket bound to: " + "~n Addr: ~s (~p)" + "~n Port: ~w", [inet:ntoa(BAddr), BAddr, BPort]), + Parent ! {?MODULE, self(), {started, BAddr, BPort}}, + + ?INFO("init done"), + acceptor_loop(#{parent => Parent, + parent_mref => MRef, + sock => Sock, + addr => BAddr, + port => BPort, + select => undefined, + %% Next handler ID, should wrap but we ignore this + handler_id => 1, + connections => #{}}). + + +acceptor_loop(#{parent := Parent, + sock := Sock, + select := undefined, + handler_id := NextHandlerID, + connections := Connections} = State) -> + ?DEBUG("~s -> try accept connection (recv)", [?FUNCTION_NAME]), + case socket:recvmsg(Sock, nowait) of + {ok, #{flags := Flags} = Msg} -> + ?DEBUG("~s -> received message - verify connection attempt", + [?FUNCTION_NAME]), + case lists:member(notification, Flags) of + true -> + ?DEBUG("~s -> is notification - make sure its comm-up", + [?FUNCTION_NAME]), + case Msg of + #{notification := + #{type := assoc_change, + state := comm_up, + assoc_id := AID, + '$esock_name' := sctp_notification}} -> + %% Create handler + ?INFO("Received assoc-change:comm-up " + "notification regarding assoc ~w", [AID]), + case handler_start(NextHandlerID, + Parent, Sock, AID) of + {Handler, HMRef} when is_pid(Handler) -> + NewConnections = + Connections#{Handler => #{id => NextHandlerID, + mref => HMRef, + assoc_id => AID}, + NextHandlerID => Handler}, + ?DEBUG("~s -> handler started:" + "~n Connections: ~p" + "~n NewConnections: ~p", + [?FUNCTION_NAME, + Connections, NewConnections]), + NewState = State#{handler_id => NextHandlerID + 1, + connections => NewConnections}, + acceptor_loop(NewState); + {error, Reason} -> + ?WARNING("Failed starting handler: " + "~n ~p", [Reason]), + acceptor_loop(State) + end; + #{notification := Notif} -> + ?WARNING("Received unexpected notification: " + "~n ~p", [Notif]), + acceptor_loop(State); + _ -> + ?WARNING("Received unexpected notication msg:" + "~n ~p", [Msg]), + acceptor_loop(State) + end; + false -> + ?WARNING("Received unexpected msg:" + "~n ~p", [Msg]), + acceptor_loop(State) + end; + + {select, SelectInfo} -> + ?DEBUG("~s -> received select message", [?FUNCTION_NAME]), + acceptor_loop(State#{select => SelectInfo}); + + {error, Reason} -> + ?ERROR("recvmsg failed (accept):" + "~n ~p", [Reason]), + exit({recvmsg, accept, Reason}) + end; +acceptor_loop(#{parent := Parent, + parent_mref := ParentMRef, + sock := Sock, + select := {select_info, _, SelectHandle}, + connections := Connections} = State) -> + ?DEBUG("~s -> await select message (for accept)", [?FUNCTION_NAME]), + receive + {'DOWN', ParentMRef, process, Parent, Info} -> + ?ERROR("Received unexpected DOWN from parent " + "when awaiting connection:" + "~n ~p", [Info]), + acceptor_stop_all_handlers(Connections), + (catch socket:close(Sock)), + exit({parent_died, Info}); + + {'DOWN', MRef, process, Pid, Info} -> + ?DEBUG("~s -> received DOWN message:" + "~n MRef: ~p" + "~n Pid: ~p" + "~n Info: ~p" + "~nwhen" + "~n Connections: ~p", + [?FUNCTION_NAME, MRef, Pid, Info, Connections]), + NewConnections = + acceptor_handle_down(Connections, Pid, MRef, Info), + NewState = State#{connections => NewConnections}, + acceptor_loop(NewState); + + {'$socket', Sock, select, SelectHandle} -> + ?DEBUG("~s -> received select message", [?FUNCTION_NAME]), + acceptor_loop(State#{select => undefined}) + + end. + + +acceptor_stop_all_handlers(Connections) -> + F = fun(Handler, #{assoc_id := AID}) when is_pid(Handler) -> + ?WARNING("terminate handler ~p (~w)", [Handler, AID]), + Handler ! {msg, self(), stop}, + ok; + (_Key, _Value) -> % AID -> Handler + ignore + end, + maps:foreach(F, Connections). + + +acceptor_handle_down(Connections, + Handler, _MRef, Info) -> + case Connections of + #{Handler := #{id := HandlerID, assoc_id := AID}} -> + maybe_inform(Handler, HandlerID, AID, Info), + C1 = maps:remove(Handler, Connections), + maps:remove(AID, C1); + _ -> + ?WARNING("Received 'DOWN' from unknown process ~p:" + "~n ~p", [Handler, Info]), + Connections + end. + +maybe_inform(Handler, HandlerID, AID, normal) -> + ?INFO("Handler ~w (~p) handling assoc ~w terminated normally", + [HandlerID, Handler, AID]), + ok; +maybe_inform(Handler, HandlerID, AID, Reason) -> + ?WARNING("Received unexpected 'DOWN' message from Handler ~w (~p) (~w):" + "~n ~p", [HandlerID, Handler, AID, Reason]), + ok. + + +%% ================================================================== +%% +%% Note that on some platforms (Solaris) the Assoc ID is basically +%% not useful *after* a peeloff. Instead, we use 0 (or whatever is +%% assigned in the message). +%% + +handler_start(HandlerID, + Parent, LSock, AID) -> + Self = self(), + Debug = get(dbg), + {Handler, MRef} = + spawn_monitor(fun() -> + handler_init(HandlerID, + #{parent => Self, + lsock => LSock, + aid => AID, + debug => Debug}) + end), + receive + {'DOWN', _PMRef, process, Parent, Reason} -> + ?ERROR("received unexpected down from parent:" + "~n ~p", [Reason]), + exit({parent, Reason}); + + {'DOWN', MRef, process, Handler, Reason} -> + ?ERROR("Received unexpected DOWN from starting handler:" + "~n ~p", [Reason]), + %% How to shut down an association + {error, {handler_start, Reason}}; + + {?MODULE, Handler, started} -> + ?INFO("handler started:" + "~n Handler: ~p", [Handler]), + {Handler, MRef} + end. + + +handler_init(ID, + #{parent := Parent, + lsock := Sock, + aid := AID, + debug := Debug}) -> + + put(sname, ?F("handler[~w,~w]", [ID,AID])), + put(dbg, Debug), + + Evs = ?SCTP_EVENTS(true), + + ?DEBUG("~s -> try peeloff", [?FUNCTION_NAME]), + InheritOpts = [{ip, tos}, + {ip, ttl}, + {sctp, nodelay}, + {socket, linger}, + {socket, reuseaddr}], + NewSock = + case socket:peeloff(Sock, AID, InheritOpts) of + {ok, S} -> + ?DEBUG("~s(~w) -> peel off success:" + "~n ~p", [?FUNCTION_NAME, AID, S]), + S; + {ok, S, InheritErrs} -> + ?WARNING("Peel off failed inherit options:" + "~n ~p", [InheritErrs]), + S; + {error, Reason1} -> + ?ERROR("Peel off failure:" + "~n ~p", [Reason1]), + exit({peeloff, Reason1}) + end, + + ?DEBUG("~s -> set events option", [?FUNCTION_NAME]), + ok = socket:setopt(NewSock, ?MK_SCTP_SOCKOPT(events), Evs), + + ?DEBUG("~s -> monitor processes", [?FUNCTION_NAME]), + MRef = erlang:monitor(process, Parent, []), + + ?DEBUG("~s -> inform parent started", [?FUNCTION_NAME]), + Parent ! {?MODULE, self(), started}, + + ?DEBUG("~s -> started", [?FUNCTION_NAME]), + handler_loop(#{sock => NewSock, + assoc_id => AID, + parent => Parent, + parent_mref => MRef, + select => undefined, + shutdown => undefined}). + + +handler_loop(#{sock := Sock, + assoc_id := undefined, + shutdown := complete} = _State) -> + ?DEBUG("~s -> shutdown complete", [?FUNCTION_NAME]), + (catch socket:close(Sock)), + exit(normal); + +handler_loop(#{sock := Sock, + assoc_id := AssignedAID, + select := undefined, + shutdown := Shutdown} = State) + when (AssignedAID =/= undefined) -> + ?DEBUG("~s -> try recv when" + "~n Shutdown: ~w" + "~n Status: ~p", + [?FUNCTION_NAME, Shutdown, ?WHICH_STATUS(Sock, AssignedAID)]), + case socket:recvmsg(Sock, nowait) of + {ok, #{flags := _, + addr := SA, + iov := [Data], + ctrl := [#{level := sctp, + type := sndrcv, + value := #{assoc_id := AID, + stream := Stream}}]}} -> + ?DEBUG("~s -> received data message: " + "~n From: ~p" + "~n Assoc ID: ~p" + "~n Stream: ~p", + [?FUNCTION_NAME, SA, AID, Stream]), + handler_handle_data(Sock, AID, Stream, Data), + handler_loop(State); + + %% On some platforms (Solaris) we may not get the SRI + %% How do we know which stream? Just pick one? + {ok, #{flags := _, + addr := SA, + iov := [_Data], + ctrl := []}} -> + ?WARNING("Received data message without SRI from (ignore): " + "~n ~p", [SA]), + %% handler_handle_data(Sock, AssignedAID, 0, Data), + handler_loop(State); + + {ok, #{flags := _, % Should contain the 'notification' flag + notification := #{type := assoc_change, + state := shutdown_comp, + assoc_id := AID}}} -> + %% Shutdown is now complete + ?INFO("Received shutdown-complete event (for assoc ~w)", [AID]), + handler_loop(State#{assoc_id => undefined, + shutdown => complete}); + + {ok, #{flags := _, % Should contain the 'notification' flag + notification := #{type := shutdown_event, + assoc_id := AID}}} -> + ?INFO("Received shutdown-event (for assoc ~w)", [AID]), + handler_loop(State#{shutdown => 'begin'}); + + {ok, #{flags := _, % Should contain the 'notification' flag + notification := #{type := peer_addr_change, + state := NState, + assoc_id := AID}}} + when (NState =:= addr_confirmed) orelse + (NState =:= addr_available) -> + ?INFO("Received peer-addr-change (for assoc ~w): ~w", + [AID, NState]), + handler_loop(State); + + {ok, #{flags := _, % Should contain the 'notification' flag + notification := Notif}} -> + ?WARNING("Received unexpected notification:" + "~n Notification: ~p", [Notif]), + handler_loop(State); + + {ok, Msg} -> + ?WARNING("Received unexpected message:" + "~n ~p", [Msg]), + handler_loop(State); + + {select, SelectInfo} -> + ?DEBUG("~s -> select", [?FUNCTION_NAME]), + handler_loop(State#{select => SelectInfo}); + + {error, Reason} -> + ?ERROR("Failed recvmsg:" + "~n ~p", [Reason]), + exit({recvmsg, Reason}) + end; +handler_loop(#{parent := Parent, + parent_mref := MRef, + sock := Sock, + assoc_id := AssignedAID, + select := {select_info, _, SelectHandle}, + shutdown := Shutdown} = State) -> + ?DEBUG("~s -> await select when" + "~n Shutdown: ~p" + "~n Status: ~p", + [?FUNCTION_NAME, Shutdown, ?WHICH_STATUS(Sock, AssignedAID)]), + receive + {'DOWN', MRef, process, Parent, Reason} -> + ?ERROR("Received unexpected down from parent:" + "~n Reason: ~p", [Reason]), + (catch socket:close(Sock)), + exit({server, Reason}); + + {'$socket', Sock, select, SelectHandle} -> + ?DEBUG("~s -> received select message", [?FUNCTION_NAME]), + handler_loop(State#{select => undefined}); + + {?MODULE, Parent, terminate} -> + ?INFO("Received terminate request"), + (catch socket:close(Sock)), + exit(normal) + end. + + +handler_handle_data(Sock, AID, Stream, Data) + when (Sock =/= undefined) andalso + (AID =/= undefined) andalso + is_binary(Data) -> + ?INFO("RECV: ~p", [binary_to_list(Data)]), + SRI = #{assoc_id => AID, stream => Stream}, + CtrlSRI = #{level => sctp, + type => sndrcv, + value => SRI}, + Msg = #{iov => [Data], + ctrl => [CtrlSRI]}, + case socket:sendmsg(Sock, Msg) of + ok -> + ?INFO("data echo done"), + ok; + {error, Reason} -> + ?ERROR("failed sending (echo) data back: " + "~n ~p", [Reason]), + exit({sendmsg, Reason}) + end. diff --git a/lib/kernel/src/gen_tcp_socket.erl b/lib/kernel/src/gen_tcp_socket.erl index fac8b37801ff..db76baf8b17b 100644 --- a/lib/kernel/src/gen_tcp_socket.erl +++ b/lib/kernel/src/gen_tcp_socket.erl @@ -1120,7 +1120,7 @@ socket_setopt(Socket, DomainProps, Value) when is_list(DomainProps) -> %% We need to lookup the domain of the socket, %% so we can select which one to use. %% ?DBG(Opt0), - case socket:getopt(Socket, otp, domain) of + case socket:getopt(Socket, {otp, domain}) of {ok, Domain} -> case lists:keysearch(Domain, 1, DomainProps) of {value, {Domain, Opt}} -> @@ -1178,7 +1178,7 @@ socket_getopt(Socket, DomainProps, _) when is_list(DomainProps) -> %% ?DBG([{domain_props, DomainProps}]), %% We need to lookup the domain of the socket, %% so we can select which one to use. - case socket:getopt(Socket, otp, domain) of + case socket:getopt(Socket, {otp, domain}) of {ok, Domain} -> %% ?DBG({'socket_getopt - domain', Tag, Domain}), case lists:keysearch(Domain, 1, DomainProps) of diff --git a/lib/kernel/src/gen_udp_socket.erl b/lib/kernel/src/gen_udp_socket.erl index 383568f384a5..55c38aefc413 100644 --- a/lib/kernel/src/gen_udp_socket.erl +++ b/lib/kernel/src/gen_udp_socket.erl @@ -916,7 +916,7 @@ socket_setopt_opts([{_Level, _OptKey} = Opt|Opts], Socket, Tag, Value) -> %% We need to lookup the domain of the socket, %% so we can select which one to use. socket_setopt_opts(Opts, Socket, Tag, Value) -> - case socket:getopt(Socket, otp, domain) of + case socket:getopt(Socket, {otp, domain}) of {ok, Domain} -> case lists:keysearch(Domain, 1, Opts) of {value, {Domain, Level, OptKey}} -> @@ -994,7 +994,7 @@ socket_getopt_opts([{_Domain, _} = Opt|_], Socket, Tag) -> socket_getopt_opt(Socket, Opt, Tag); socket_getopt_opts(Opts, Socket, Tag) -> - case socket:getopt(Socket, otp, domain) of + case socket:getopt(Socket, {otp, domain}) of {ok, Domain} -> %% ?DBG([{'domain', Domain}]), case lists:keysearch(Domain, 1, Opts) of diff --git a/lib/kernel/src/inet.erl b/lib/kernel/src/inet.erl index fe0aaa165a22..b29c07e95ff9 100644 --- a/lib/kernel/src/inet.erl +++ b/lib/kernel/src/inet.erl @@ -2807,7 +2807,7 @@ getll(Socket) when is_port(Socket) -> getfd(?module_socket(GenSocketMod, ESock) = _Socket) when is_atom(GenSocketMod) -> - socket:getopt(ESock, otp, fd); + socket:getopt(ESock, {otp, fd}); getfd(Socket) -> prim_inet:getfd(Socket). diff --git a/lib/kernel/src/kernel.app.src b/lib/kernel/src/kernel.app.src index 831cf1948be8..fae1e4d765a7 100644 --- a/lib/kernel/src/kernel.app.src +++ b/lib/kernel/src/kernel.app.src @@ -168,7 +168,7 @@ {shell_history_drop,[]} ]}, {mod, {kernel, []}}, - {runtime_dependencies, ["erts-15.2.5", "stdlib-6.0", + {runtime_dependencies, ["erts-@OTP-19834@", "stdlib-6.0", "sasl-3.0", "crypto-5.0"]} ] }. diff --git a/lib/kernel/src/socket.erl b/lib/kernel/src/socket.erl index 93237bf418ef..d42837c2a46e 100644 --- a/lib/kernel/src/socket.erl +++ b/lib/kernel/src/socket.erl @@ -191,7 +191,7 @@ has been received. > > Support for IPv6 has been implemented but not _fully_ tested. > -> SCTP has only been partly implemented (and not tested). +> SCTP has only been partly implemented and tested. This module was introduced in OTP 22.0, as experimental code. * In OTP 22.1, the `nowait` argument was added for many functions, @@ -220,6 +220,8 @@ This module was introduced in OTP 22.0, as experimental code. * In OTP 27.0, the Windows flavored ([completion handle](`t:completion_handle/0`)) API features could be considered no longer experimental. +* In @OTP-19834@, (experimental) complete support for SCTP was added + (functionally feature compatible with inet). ## Examples @@ -280,6 +282,7 @@ server(Addr, Port) -> connect/1, connect/2, connect/3, listen/1, listen/2, accept/1, accept/2, + peeloff/2, peeloff/3, send/2, send/3, send/4, sendto/3, sendto/4, sendto/5, @@ -296,10 +299,10 @@ server(Addr, Port) -> shutdown/2, setopt/3, setopt_native/3, setopt/4, - getopt/2, getopt_native/3, getopt/3, + getopt/2, getopt/3, getopt_native/3, - sockname/1, - peername/1, + sockname/1, socknames/2, + peername/1, peernames/2, ioctl/2, ioctl/3, ioctl/4, @@ -310,7 +313,8 @@ server(Addr, Port) -> -export([ which_socket_kind/1, options/0, options/1, options/2, option/1, option/2, - protocols/0, protocol/1 + protocols/0, protocol/1, + mk_sockaddr/2, mk_sockaddr/3 ]). -export_type([ @@ -369,10 +373,21 @@ server(Addr, Port) -> ipv6_hops/0, ipv6_pktinfo/0, + sctp_assoc_id/0, sctp_assocparams/0, sctp_event_subscribe/0, sctp_initmsg/0, sctp_rtoinfo/0, + sctp_snd_rcv_info/0, + sctp_set_peer_primary_address/0, + sctp_set_primary_address/0, + sctp_set_adaptation_layer_ind/0, + sctp_peer_address_parameters/0, + sctp_pap_flags/0, sctp_pap_flag/0, + sctp_assoc_stats/0, + sctp_status/0, + sctp_peer_address_info/0, + sctp_peer_address_state/0, msg/0, msg_send/0, msg_recv/0, cmsg/0, cmsg_send/0, cmsg_recv/0, @@ -615,7 +630,7 @@ multicast source filtering (RFC 3376). -doc """ C: `IP_PMTUDISC_*` values. - +=> Lowercase `t:atom/0` values corresponding to the C library constants `IP_PMTUDISC_*`. Some constant(s) may be unsupported by the platform. """. @@ -676,14 +691,17 @@ The value `default` is only valid to _set_ and is translated to the C value ifindex := integer() }. +-doc "C: `sctp_assoc_t`". +-type sctp_assoc_id() :: integer(). + -doc "C: `struct sctp_assocparams`". -type sctp_assocparams() :: - #{assoc_id := integer(), - asocmaxrxt := 0..16#ffff, - numbe_peer_destinations := 0..16#ffff, - peer_rwnd := 0..16#ffffffff, - local_rwnd := 0..16#ffffffff, - cookie_life := 0..16#ffffffff}. + #{assoc_id := sctp_assoc_id(), + asocmaxrxt := 0..16#ffff, + number_peer_destinations := 0..16#ffff, + peer_rwnd := 0..16#ffffffff, + local_rwnd := 0..16#ffffffff, + cookie_life := 0..16#ffffffff}. -doc """ C: `struct sctp_event_subscribe`. @@ -701,7 +719,10 @@ have been stripped from the C struct field names, for convenience. shutdown := boolean(), partial_delivery := boolean(), adaptation_layer => boolean(), - sender_dry => boolean()}. + sender_dry => boolean(), + stream_reset => boolean(), + assoc_reset => boolean(), + stream_change => boolean()}. -doc "C: `struct sctp_initmsg`.". -type sctp_initmsg() :: @@ -712,11 +733,101 @@ have been stripped from the C struct field names, for convenience. -doc "C: `struct sctp_rtoinfo`.". -type sctp_rtoinfo() :: - #{assoc_id := integer(), + #{assoc_id := sctp_assoc_id(), initial := 0..16#ffffffff, max := 0..16#ffffffff, min := 0..16#ffffffff}. +-doc "C: `struct sctp_setpeerprim`.". +-type sctp_set_peer_primary_address() :: + #{assoc_id := sctp_assoc_id(), + addr := sockaddr()}. + +-doc "C: `struct sctp_prim`.". +-type sctp_set_primary_address() :: + #{assoc_id := sctp_assoc_id(), + addr := sockaddr()}. + +-doc "C: `struct sctp_setadaptation`.". +-type sctp_set_adaptation_layer_ind() :: + #{ind := 0..16#ffffffff}. + +-doc "C: `struct sctp_paddrparams`.". +-type sctp_peer_address_parameters() :: + #{assoc_id := sctp_assoc_id(), + addr := sockaddr(), + heatbeat_interval := 0..16#FFFFFFFF, + path_max_rxt := 0..16#FFFF, + path_mtu := 0..16#FFFFFFFF, + sack_delay := 0..16#FFFFFFFF, + flags := sctp_pap_flags(), + ipv6_flowlabel := 0..16#FFFFFFFF, + dscp := 0..16#FF}. + +-doc """ +There are three pairs of flags that cannot be both be set (maybe obviously) +at the same time: +- `enable_heartbeats` and `disable_heartbeats` +- `enable_pmtu_discovery` and `disable_pmtu_discovery` +- `enable_sack` and `disable_sack` +""". +-type sctp_pap_flags() :: integer() | [sctp_pap_flag()]. +-doc "C: `enum sctp_spp_flags`.". +-type sctp_pap_flag() :: enable_heartbeats | disable_heartbeats | + send_heartbeat_immediately | + enable_pmtu_discovery | disable_pmtu_discovery | + enable_sack | disable_sack | + set_heartbeat_delay_to_zero | + ipv6_flowlabel | + dscp. + + +-doc "C: `struct sctp_status`.". +-type sctp_status() :: + #{assoc_id := sctp_assoc_id(), + state := sctp_peer_address_state(), + rwnd := 0..16#FFFFFFFF, + unacked_data := 0..16#FFFF, + pending_data := 0..16#FFFF, + in_streams := 0..16#FFFF, + out_streams := 0..16#FFFF, + fragmentation_point := 0..16#FFFFFFFF, + primary := sctp_peer_address_info()}. + +-doc "C: `struct sctp_assoc_stats`.". +-type sctp_assoc_stats() :: + #{assoc_id := sctp_assoc_id(), + max_rto_addr := sockaddr(), + max_rto := 0..16#FFFFFFFFFFFFFFFF, + in_sacks := 0..16#FFFFFFFFFFFFFFFF, + out_sacks := 0..16#FFFFFFFFFFFFFFFF, + in_packets := 0..16#FFFFFFFFFFFFFFFF, + out_packets := 0..16#FFFFFFFFFFFFFFFF, + rtx_chunks := 0..16#FFFFFFFFFFFFFFFF, + out_of_seq_tsns := 0..16#FFFFFFFFFFFFFFFF, + in_dup_chunks := 0..16#FFFFFFFFFFFFFFFF, + gap_ack_recv := 0..16#FFFFFFFFFFFFFFFF, + in_unordered_chunks := 0..16#FFFFFFFFFFFFFFFF, + out_unordered_chunks := 0..16#FFFFFFFFFFFFFFFF, + in_ordered_chunks := 0..16#FFFFFFFFFFFFFFFF, + out_ordered_chunks := 0..16#FFFFFFFFFFFFFFFF, + in_ctrl_chunks := 0..16#FFFFFFFFFFFFFFFF, + out_ctrl_chunks := 0..16#FFFFFFFFFFFFFFFF}. + +-doc "C: `struct sctp_paddrinfo`.". +-type sctp_peer_address_info() :: + #{assoc_id := sctp_assoc_id(), + address := sockaddr(), + state := sctp_peer_address_state(), + cwnd := 0..16#FFFFFFFF, + srtt := 0..16#FFFFFFFF, + rto := 0..16#FFFFFFFF, + mtu := 0..16#FFFFFFFF}. + +-doc "C: `enum sctp_spinfo_state`.". +-type sctp_peer_address_state() :: inactive | potentially_failed | + active | unconfirmed | unknown. + -type packet_type() :: host | broadcast | multicast | otherhost | outgoing | loopback | user | kernel | fastroute | non_neg_integer(). @@ -1256,6 +1367,19 @@ _Options for protocol level_ [_`sctp`_](`t:level/0`). See also RFC 6458. - **`{sctp, rtoinfo}`** - `Value =` `t:sctp_rtoinfo/0` +- **`{sctp, get_peer_addr_info}`** - `Value =` `t:sctp_peer_address_info/0` + + Only valid for _get_. + Also, requires an OptValue (containing `t:sctp_assoc_id/0` and + `t:sockaddr/0`) specifying the peer. See [`getopt/3`](`getopt/3`) + for more info. + +- **`{sctp, status}`** - `Value =` `t:sctp_status/0` + + Only valid for _get_. + Also, requires an OptValue (containing `t:sctp_assoc_id/0`) + specifying the association. See [`getopt/3`](`getopt/3`) for more info. + _Options for protocol level_ [_`tcp`:_](`t:level/0`) - **`{tcp, congestion}`** - `Value = string()` @@ -1405,7 +1529,7 @@ _Options for protocol level_ [_`udp`:_](`t:level/0`) auth_delete_key | autoclose | context | - default_send_params | + default_send_param | delayed_ack_time | disable_fragments | hmac_ident | @@ -1543,30 +1667,301 @@ Corresponds to a C `struct msghdr`, see your platform documentation for `struct msghdr`. Unknown flags, if any, are returned in one `t:integer/0`, last in the containing list. """. --type msg_recv() :: +-type msg_data_recv() :: #{ - %% *Optional* target address - %% Used on an unconnected socket to return the - %% source address for a message. - addr => sockaddr_recv(), + %% *Optional* target address + %% Used on an unconnected socket to return the + %% source address for a message. + addr => sockaddr_recv(), + + iov := erlang:iovec(), + + %% Control messages (ancillary data). + %% The maximum size of the control buffer is platform + %% specific. It is the users responsibility to ensure + %% that its not exceeded. + %% + ctrl := + ([cmsg_recv() | + #{level := level() | integer(), + type := integer(), + data := binary()}]), + + %% Received message flags + flags := [msg_flag() | integer()] + }. - iov := erlang:iovec(), +-doc(#{since => ~"OTP @OTP-19834@"}). +-doc """ +Notifications can be received on a SCTP socket (type = seqpacket and +protocol = sctp). - %% Control messages (ancillary data). - %% The maximum size of the control buffer is platform - %% specific. It is the users responsibility to ensure - %% that its not exceeded. - %% - ctrl := - ([cmsg_recv() | - #{level := level() | integer(), - type := integer(), - data := binary()}]), +TBD. + +""". +-type msg_notification_recv() :: + #{ + %% *Optional* target address + %% Used on an unconnected socket to return the + %% source address for a message. + addr => sockaddr_recv(), + + notification := sctp_notification(), + + ctrl := [#{level := level() | integer(), + type := integer(), + data := binary()}], - %% Received message flags - flags := [msg_flag() | integer()] + %% Received message flags + %% Will contain the 'notification' flag + flags := [notification | [msg_flag() | integer()]] }. +-doc """ +Message returned by [`recvmsg/1,2,3,5`](`recvmsg/1`). +""". +-type msg_recv() :: msg_data_recv() | msg_notification_recv(). + + +-type sctp_notification() :: sctp_notif_assoc_change() | + sctp_notif_paddr_change() | + sctp_notif_send_failed() | + sctp_notif_remote_error() | + sctp_notif_shutdown_event() | + sctp_notif_adapt_event() | + sctp_notif_pdapi_event() | + sctp_notif_authkey() | + sctp_notif_sender_dry() | + sctp_notif_stream_reset_event() | + sctp_notif_assoc_reset_event() | + sctp_notif_stream_change_event() | + sctp_notif_generic() | + sctp_notif_send_failed_event(). + +-doc """ +An SCTP association has either begun or ended. + +C: `struct sctp_assoc_change` +""". +-type sctp_notif_assoc_change() :: + #{type := assoc_change, + flags := integer(), + state := sctp_assoc_change_state(), + error := sctp_rfc4960_error(), + outbound_streams := integer(), + inbound_streams := integer(), + assoc_id := sctp_assoc_id()}. + +-type sctp_assoc_change_state() :: comm_up | + comm_lost | + restart | + shutdown_comp | + cant_str_assoc | + integer(). + +-type sctp_rfc4960_error() :: unknown | + bad_sid | + missing_parm | + stale_cookie | + no_resources | + bad_addr | + unrec_chunk | + bad_mandparm | + unrec_parm | + no_usr_data | + cookie_shut | + restart_new_addrs | + user_abort | + delete_lastaddr | + resource_shortage | + delete_srcaddr | + auth_err | + integer(). + +-doc """ +A destination address on a multi-homed peer has encountered a change. + +C: `struct sctp_paddr_change` +""". +-type sctp_notif_paddr_change() :: + #{type := peer_addr_change, + flags := integer(), + addr := socket:sockaddr(), + state := sctp_peer_addr_change_state(), + error := integer(), + assoc_id := sctp_assoc_id()}. + +-type sctp_peer_addr_change_state() :: + addr_available | + addr_unreachable | + addr_removed | + addr_added | + addr_made_prim | + addr_confirmed | + addr_potentially_failed | + integer(). + +%% DEPRECATED +-doc """ +SCTP cannot deliver a message. + +C: `struct sctp_send_failed` + +Deprecated. +""". +-type sctp_notif_send_failed() :: + #{type := send_failed, + flags := 0..16#FFFF, + error := 0..16#FFFFFFFF, + info := sctp_snd_rcv_info(), + assoc_id := sctp_assoc_id(), + data := binary()}. + +-doc """ +A remote peer may send an operational error message to its peer. + +C: `struct sctp_remote_error` +""". +-type sctp_notif_remote_error() :: + #{type := remote_error, + flags := 0..16#FFFF, % Should be [flag()] + error := integer(), + assoc_id := sctp_assoc_id(), + remote_causes := [integer()]}. + +-doc """ +A peer has sent a SHUTDOWN. + +C: `struct sctp_shutdown_event` +""". +-type sctp_notif_shutdown_event() :: + #{type := shutdown_event, + flags := 0..16#FFFF, % Should be [flag()] + assoc_id := sctp_assoc_id()}. + +-doc """ +A peer has sent a Adaptation Layer Indication parameter. + +C: `struct sctp_adaptation_event` +""". +-type sctp_notif_adapt_event() :: + #{type := adaptation_event, + flags := 0..16#FFFF, + adaption_ind := 0..16#FFFFFFFF, + assoc_id := sctp_assoc_id()}. + +-doc """ +A receiver is engaged in a partial delivery. + +C: `struct sctp_pdapi_event` +""". +-type sctp_notif_pdapi_event() :: + #{type := partial_delivery_event, + flags := 0..16#FFFF, % Should be [flag()] + indication := 0..16#FFFFFFFF, + assoc_id := sctp_assoc_id(), + stream := undefined | 0..16#FFFFFFFF, + seq := undefined | 0..16#FFFFFFFF}. + +-doc """ +When a receiver is using authentication, info about new keys and errors are +provided in this notification. + +C: `struct sctp_authkey_event` +""". +-type sctp_notif_authkey() :: + #{type := authkey, + flags := 0..16#FFFF, % Should be [flag()] + keynumber := 0..16#FFFF, + altkeynumber := 0..16#FFFF, + indication := 0..16#FFFFFFFF, + assoc_id := sctp_assoc_id()}. + +-doc """ +The SCTP stack has no more user data to send or retransmit. + +C: `struct sctp_sender_dry_event` +""". +-type sctp_notif_sender_dry() :: + #{type := sender_dry, + flags := integer(), % Should be [flag()] + assoc_id := sctp_assoc_id()}. + +-doc """ +C: `struct sctp_stream_reset_event` +""". +-type sctp_notif_stream_reset_event() :: + #{type := stream_reset, + flags := [incoming_ssn | outgoing_ssn | denied | failed], + assoc_id := sctp_assoc_id(), + stream_list := [0..16#FFFF]}. + +-doc """ +C: `struct sctp_assoc_reset_event` +""". +-type sctp_notif_assoc_reset_event() :: + #{type := assoc_reset, + flags := [denied | failed], + assoc_id := sctp_assoc_id(), + local_tsn := 0..16#FFFFFFFF, + remote_tsn := 0..16#FFFFFFFF}. + +-doc """ +C: `struct sctp_stream_change_event` +""". +-type sctp_notif_stream_change_event() :: + #{type := stream_change, + flags := [denied | failed], + assoc_id := sctp_assoc_id(), + inbound_streams := 0..16#FFFF, + outbound_streams := 0..16#FFFF}. + +-doc """ +C: `union sctp_notification` +""". +-type sctp_notif_generic() :: + #{type := stream_reset_event | integer(), + flags := integer()}. + +-doc """ +SCTP cannot deliver a message. + +C: `struct sctp_send_failed_event` +""". +-type sctp_notif_send_failed_event() :: + #{type := send_failed_event, + flags := [data_unsent | data_sent], + error := 0..16#FFFFFFFF, + info := sctp_snd_info(), + assoc_id := sctp_assoc_id(), + data := binary()}. + +-doc """ +C: `struct sctp_sndinfo` +""". +-type sctp_snd_info() :: + #{sid := 0..16#FFFF, + flags := 0..16#FFFF, % Should be [flag()] + ppid := 0..16#FFFF, + context := 0..16#FFFFFFFF, + assic_id := sctp_assoc_id()}. + +-doc """ +C: `struct sctp_sndrcvinfo` +""". +-type sctp_snd_rcv_info() :: + #{stream := 0..16#FFFF, + ssn => 0..16#FFFF, + flags => sctp_snd_rcv_info_flags(), + ppid => 0..16#FFFFFFFF, + context => 0..16#FFFFFFFF, + time_to_live => 0..16#FFFFFFFF, + tsn => 0..16#FFFFFFFF, + cum_tsn => 0..16#FFFFFFFF, + assoc_id := sctp_assoc_id()}. + +-type sctp_snd_rcv_info_flags() :: [unordered | addr_over | abort | eof]. + %% 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 @@ -1625,6 +2020,8 @@ symbolic value, or a `data` field with a native value, that has to be binary compatible what is defined in the platform's header files. """. -type cmsg_send() :: + #{level := sctp, type := sndrcv, + value := sctp_snd_rcv_info()} | #{level := socket, type := timestamp, data => native_value(), value => timeval()} | #{level := socket, type := rights, data := native_value()} | @@ -2256,7 +2653,7 @@ i_socket_info(Proto, _Socket, #{type := Type} = _Info, protocol) -> Proto end)); i_socket_info(_Proto, Socket, _Info, fd) -> - try socket:getopt(Socket, otp, fd) of + try socket:getopt(Socket, {otp, fd}) of {ok, FD} -> integer_to_list(FD); {error, _} -> " " catch @@ -2970,18 +3367,30 @@ bind(Socket, Addr) -> %% If the domain is inet6, the addresses can be either IPv4 or IPv6. %% --doc false. +-doc(#{since => <<"OTP @OTP-19834@">>}). +-doc """ +Bind a list of socket addreses to a socket. + +When a socket is created (with [`open`](`open/2`)), it has no address assigned +to it. This `bind` assigns the address specified by the `Addr` argument. + +Calling this function is only valid if the socket is +`type` = `seqpacket` and `protocol` = `sctp`. + +If the domain is inet, then all addresses *must* be IPv4. +If the domain is inet6, the addresses can be *either* IPv4 or IPv6. + +""". -spec bind(Socket, Addrs, Action) -> 'ok' | {'error', Reason} when Socket :: socket(), - Addrs :: [sockaddr()], + Addrs :: [sockaddr_in()] | [sockaddr_in() | sockaddr_in6()], Action :: 'add' | 'remove', Reason :: posix() | 'closed'. bind(?socket(SockRef), Addrs, Action) when is_reference(SockRef) andalso is_list(Addrs) - andalso (Action =:= add - orelse Action =:= remove) -> + andalso ((Action =:= add) orelse (Action =:= remove)) -> prim_socket:bind(SockRef, Addrs, Action); bind(Socket, Addrs, Action) -> erlang:error(badarg, [Socket, Addrs, Action]). @@ -3029,20 +3438,23 @@ connect(Socket) -> -doc(#{since => <<"OTP 22.0">>}). -doc """ Equivalent to -[`connect(Socket, SockAddr, infinity)`](#connect-infinity). +[`connect(Socket, SockAddr, infinity)`](#connect-infinity) or +[`connect(Socket, SockAddrs, undefined, infinity)`](#connect-infinity). """. --spec connect(Socket :: socket(), SockAddr :: sockaddr()) -> 'ok' | {'error', Reason :: dynamic()}. +-spec connect(Socket :: socket(), SockAddr :: sockaddr() | [SockAddr :: term()]) -> 'ok' | {'error', Reason :: dynamic()}. +connect(Socket, SockAddrs) when is_list(SockAddrs) -> + connect(Socket, SockAddrs, infinity); connect(Socket, SockAddr) -> connect(Socket, SockAddr, infinity). -doc(#{since => <<"OTP 22.0">>}). -doc """ -Connect the socket to the given address. +Connect the socket to the given address(s). -This function connects the socket to the address specified -by the `SockAddr` argument. +This function connects the socket to the address(s) specified +by the `SockAddr`|`SockAddrs` argument. If a connection attempt is already in progress (by another process), `{error, already}` is returned. @@ -3098,8 +3510,26 @@ to complete the operation. If cancelling the operation with `cancel/2` see the note above about [connection time-out](#connect-timeout). + +> #### Note {: .info } +> +> Note that calling with a list of socket addresses only works for +> SCTP sockets (type = `seqpacket`). And that the family of *all* +> addresses in the list is either IPv4 (`inet`) or IPv6 (`inet6`). + """. --spec connect(Socket, SockAddr, Timeout :: 'infinity') -> +-spec connect(Socket, SockAddrs, TimeoutOrHandle) -> + {'ok', AssocId} | + {'error', Reason} when + Socket :: socket(), + SockAddrs :: [sockaddr_in()] | [sockaddr_in6()], + TimeoutOrHandle :: infinity | Timeout | 'nowait' | Handle, + Timeout :: non_neg_integer(), + Handle :: select_handle(), + AssocId :: sctp_assoc_id(), + Reason :: posix() | 'closed' | invalid() | 'already'; + + (Socket, SockAddr, Timeout :: 'infinity') -> 'ok' | {'error', Reason} when Socket :: socket(), @@ -3137,25 +3567,26 @@ about [connection time-out](#connect-timeout). %% %% Is it possible to connect with family = local for the (dest) sockaddr? %% -connect(?socket(SockRef), SockAddr, TimeoutOrHandle) +connect(?socket(SockRef), SockAddrOrAddrs, TimeoutOrHandle) when is_reference(SockRef) -> case deadline(TimeoutOrHandle) of invalid -> erlang:error({invalid, {timeout, TimeoutOrHandle}}); nowait -> Handle = make_ref(), - connect_nowait(SockRef, SockAddr, Handle); + connect_nowait(SockRef, SockAddrOrAddrs, Handle); handle -> Handle = TimeoutOrHandle, - connect_nowait(SockRef, SockAddr, Handle); + connect_nowait(SockRef, SockAddrOrAddrs, Handle); Deadline -> - connect_deadline(SockRef, SockAddr, Deadline) + connect_deadline(SockRef, SockAddrOrAddrs, Deadline) end; -connect(Socket, SockAddr, Timeout) -> - erlang:error(badarg, [Socket, SockAddr, Timeout]). +connect(Socket, SockAddrOrAddrs, Timeout) -> + erlang:error(badarg, [Socket, SockAddrOrAddrs, Timeout]). -connect_nowait(SockRef, SockAddr, Handle) -> - case prim_socket:connect(SockRef, Handle, SockAddr) of + +connect_nowait(SockRef, SockAddrOrAddrs, Handle) -> + case prim_socket:connect(SockRef, Handle, SockAddrOrAddrs) of select -> {select, ?SELECT_INFO(connect, Handle)}; completion -> @@ -3164,31 +3595,31 @@ connect_nowait(SockRef, SockAddr, Handle) -> Result end. -connect_deadline(SockRef, SockAddr, Deadline) -> - Ref = make_ref(), - case prim_socket:connect(SockRef, Ref, SockAddr) of +connect_deadline(SockRef, SockAddrOrAddrs, Deadline) -> + Handle = make_ref(), + case prim_socket:connect(SockRef, Handle, SockAddrOrAddrs) of select -> %% Connecting... Timeout = timeout(Deadline), receive - ?socket_msg(_Socket, select, Ref) -> + ?socket_msg(_Socket, select, Handle) -> prim_socket:connect(SockRef); - ?socket_msg(_Socket, abort, {Ref, Reason}) -> + ?socket_msg(_Socket, abort, {Handle, Reason}) -> {error, Reason} after Timeout -> - _ = cancel(SockRef, connect, Ref), + _ = cancel(SockRef, connect, Handle), {error, timeout} end; completion -> %% Connecting... Timeout = timeout(Deadline), receive - ?socket_msg(_Socket, completion, {Ref, CompletionStatus}) -> + ?socket_msg(_Socket, completion, {Handle, CompletionStatus}) -> CompletionStatus; - ?socket_msg(_Socket, abort, {Ref, Reason}) -> + ?socket_msg(_Socket, abort, {Handle, Reason}) -> {error, Reason} after Timeout -> - _ = cancel(SockRef, connect, Ref), + _ = cancel(SockRef, connect, Handle), {error, timeout} end; Result -> @@ -3196,6 +3627,7 @@ connect_deadline(SockRef, SockAddr, Deadline) -> end. + %% =========================================================================== %% %% listen - listen for connections on a socket @@ -3217,6 +3649,7 @@ listen(Socket) -> -doc """ Make a socket listen for connections. +The `IsServer` clauses are intended to be used for SCTP sockets. The `Backlog` argument states the length of the queue for incoming not yet accepted connections. Exactly how that number is interpreted is up to the OS' @@ -3227,8 +3660,21 @@ will most probably be perceived as at least that long. > > On _Windows_ the socket has to be _bound_. """. --spec listen(Socket :: socket(), Backlog :: integer()) -> 'ok' | {'error', Reason :: posix() | 'closed'}. +-spec listen(Socket, IsServer) -> ok | {error, Reason} when + Socket :: socket(), + IsServer :: boolean(), + Reason :: posix() | 'closed'; + (Socket, Backlog) -> 'ok' | {'error', Reason} when + Socket :: socket(), + Backlog :: pos_integer(), + Reason :: posix() | 'closed'. +listen(?socket(SockRef), true = _IsServer) + when is_reference(SockRef) -> + prim_socket:listen(SockRef, ?ESOCK_LISTEN_BACKLOG_DEFAULT); +listen(?socket(SockRef), false = _IsServer) + when is_reference(SockRef) -> + prim_socket:listen(SockRef, 0); listen(?socket(SockRef), Backlog) when is_reference(SockRef), is_integer(Backlog) -> prim_socket:listen(SockRef, Backlog); @@ -3271,8 +3717,8 @@ and return the new connection socket. [](){: #accept-timeout } If the `Timeout` argument is a time-out value (`t:non_neg_integer/0`); -returns `{error, timeout}` if no connection has arrived -after `Timeout` milliseconds. +returns `{error, timeout}` if no connection has arrived after `Timeout` +milliseconds. [](){: #accept-nowait } @@ -3280,10 +3726,9 @@ If the `Handle` argument `nowait` *(since OTP 22.1)*, starts an [asynchronous call](#asynchronous-calls) if the operation couldn't be completed immediately. -If the `Handle` argument is a `t:select_handle/0`, -*(since OTP 24.0)*, or on _Windows_, the equivalent -`t:completion_handle/0` *(since OTP 26.0)*, starts -an [asynchronous call](#asynchronous-calls) like for `nowait`. +If the `Handle` argument is a `t:select_handle/0`, *(since OTP 24.0)*, +or on _Windows_, the equivalent `t:completion_handle/0` *(since OTP 26.0)*, +starts an [asynchronous call](#asynchronous-calls) like for `nowait`. See the note [Asynchronous Calls](#asynchronous-calls) at the start of this module reference manual page. @@ -3399,6 +3844,104 @@ accept_result(LSockRef, AccRef, Result) -> end. +%% =========================================================================== +%% +%% peeloff - Branch off an association into a separate socket +%% + +-doc(#{since => <<"OTP @OTP-19834@">>}). +-doc """ +Branch off an association into a separate socket. + +Equivalent to [`peeloff(Socket, AssocId, [])`](`peeloff/3`) + +""". +-spec peeloff(Socket, AssocId) -> + {'ok', NewSock} | + {'ok', NewSock, InheritErrs} | + {'error', Reason} when + Socket :: socket(), + AssocId :: sctp_assoc_id(), + NewSock :: socket(), + InheritErrs :: [{SockOpt, get | set, Reason}], + SockOpt :: socket_option(), + Reason :: posix() | 'closed'. + +peeloff(Sock, AssocId) -> + peeloff(Sock, AssocId, []). + +-doc(#{since => <<"OTP @OTP-19834@">>}). +-doc """ +Branch off an association into a separate socket. + +Create a new one-to-one socket from an existing one-to-many socket. +The specified `InheritOpts` will be inherited by the new socket (get from +existing socket and then set on the new socket). + +If the peeloff operation is successful but some (or all) of the `InheritOpts` +failed to transfer to the new socket, `{'ok', NewSock, InheritErrs}` is +returned. It is then up to the caller to decide if this is acceptable. + +""". +-spec peeloff(Socket, AssocId, InheritOpts) -> + {'ok', NewSock} | + {'ok', NewSock, InheritErrs} | + {'error', Reason} when + Socket :: socket(), + AssocId :: sctp_assoc_id(), + InheritOpts :: [socket_option()], + NewSock :: socket(), + InheritErrs :: [{SockOpt, get | set, Reason}], + SockOpt :: socket_option(), + Reason :: posix() | 'closed'. + +peeloff(?socket(SockRef) = Sock, AssocId, InheritOpts) + when is_reference(SockRef) andalso + is_integer(AssocId) andalso + is_list(InheritOpts) -> + case prim_socket:peeloff(SockRef, AssocId) of + {ok, NewSockRef} -> + NewSock = ?socket(NewSockRef), + inherit_opts(Sock, NewSock, InheritOpts); + {error, _} = ERROR -> + ERROR + end; +peeloff(Socket, AssocId, InheritOpts) -> + erlang:error(badarg, [Socket, AssocId, InheritOpts]). + +inherit_opts(FromSock, ToSock, InheritOpts) -> + inherit_opts(FromSock, ToSock, InheritOpts, []). + +inherit_opts(_FromSock, ToSock, [], []) -> + {ok, ToSock}; +inherit_opts(_FromSock, ToSock, [], Errs) -> + {ok, ToSock, Errs}; +inherit_opts(FromSock, ToSock, [SockOpt | SockOpts], Errs) -> + case socket:getopt(FromSock, SockOpt) of + {ok, Value} -> + case socket:setopt(ToSock, SockOpt, Value) of + ok -> + inherit_opts(FromSock, ToSock, SockOpts, Errs); + {error, Reason} -> + inherit_opts(FromSock, ToSock, SockOpts, + [{SockOpt, set, Reason}|Errs]) + end; + {error, Reason} -> + inherit_opts(FromSock, ToSock, SockOpts, + [{SockOpt, get, Reason}|Errs]) + end. + +%% peeloff_inherit_opts() -> +%% [{socket, priority}, +%% {sctp, nodelay}, +%% {socket, linger}, +%% {socket, reuseaddr}, +%% {ip, tos}, +%% {ip, ttl}, +%% {ip, recvtos}, +%% {ip, recvttl}]. +%% + %% =========================================================================== %% %% send, sendto, sendmsg - send a message on a socket @@ -5527,6 +6070,7 @@ starts an [asynchronous call](#asynchronous-calls) like for `nowait`. See the note [Asynchronous Calls](#asynchronous-calls) at the start of this module reference manual page. """. + -spec recvfrom(Socket, BufSz, Flags, Timeout :: 'infinity') -> {'ok', {Source, Data}} | {'error', Reason} when @@ -6164,23 +6708,29 @@ in the User's Guide for more info. SocketOption :: {Level :: 'otp', Opt :: otp_socket_option()}) -> - {'ok', Value :: term()} | - {'error', invalid() | 'closed'}; + {'ok', Value :: term()} | + {'error', invalid() | 'closed'}; (socket(), SocketOption :: socket_option()) -> - {'ok', Value :: term()} | - {'error', posix() | invalid() | 'closed'}. + {'ok', Value :: term()} | + {'error', posix() | invalid() | 'closed'}. getopt(?socket(SockRef), SocketOption) when is_reference(SockRef) -> prim_socket:getopt(SockRef, SocketOption). -%% Backwards compatibility -doc(#{since => <<"OTP 22.0">>}). -doc """ -Get a socket option _(backwards compatibility function)_. +Get a socket option, with a specifier (extra input) `OptValue`. -Equivalent to [`getopt(Socket, {Level, Opt})`](`getopt/2`), +This function is used when the option takes an input 'value' argument. +We only support this for a limited set of options: +`{sctp, get_peer_addr_info}` and `{sctp, status}`. + +Some uses of this function is for _backwards compatibility reasons_. + +For instance, `getopt(Socket, Level, Opt)` is +equivalent to [`getopt(Socket, {Level, Opt})`](`getopt/2`), or as a special case if `Opt = {NativeOpt :: `[`integer/0`](`t:integer/0`)`, ValueSpec}` equivalent to @@ -6190,13 +6740,44 @@ Use `getopt/2` or `getopt_native/3` instead to handle the option level and name as a single term, and to make the difference between known options and native options clear. """. --spec getopt(Socket :: term(), Level :: term(), Opt :: term()) -> _. + +-spec getopt(Socket :: socket(), + SocketOption :: {sctp, get_peer_addr_info}, + OptValue :: #{assoc_id := sctp_assoc_id(), + addr := sockaddr()}) -> + {'ok', Value :: sctp_peer_address_info()} | + {'error', posix() | invalid() | 'closed'}; + + (Socket :: socket(), + SocketOption :: {sctp, status}, + OptValue :: #{assoc_id := sctp_assoc_id()}) -> + {'ok', Value :: sctp_status()} | + {'error', posix() | invalid() | 'closed'}; + + (Socket :: socket(), + Level :: level(), + Opt :: term()) -> + {'ok', Value :: term()} | + {'error', posix() | invalid() | 'closed'}. + +getopt(?socket(SockRef), + {sctp, get_peer_addr_info} = SocketOption, + #{assoc_id := _, addr := SA} = OptValue) % Sparse peer-addr-info + when is_reference(SockRef) -> + prim_socket:getopt(SockRef, SocketOption, + OptValue#{addr => prim_socket:enc_sockaddr(SA)}); +getopt(?socket(SockRef), + {sctp, status} = SocketOption, + #{assoc_id := _} = OptValue) % Sparse status + when is_reference(SockRef) -> + prim_socket:getopt(SockRef, SocketOption, OptValue); getopt(Socket, Level, {NativeOpt, ValueSpec}) when is_integer(NativeOpt) -> getopt_native(Socket, {Level,NativeOpt}, ValueSpec); getopt(Socket, Level, Opt) -> getopt(Socket, {Level,Opt}). + -doc(#{since => <<"OTP 24.0">>}). -doc """ Get a "native" socket option. @@ -6291,6 +6872,41 @@ sockname(Socket) -> erlang:error(badarg, [Socket]). +%% =========================================================================== +%% +%% socknames - Return all the current (locally bound) address(s) of the socket. +%% +%% + +-doc(#{since => <<"OTP @OTP-19834@">>}). +-doc """ +Get the socket's address. + +Returns all the locally bound addresses to which the socket is currently bound. + +If the socket is IPv4 then all returned addresess will be IPv4. +If the socket is IPv6, then the returned addresess can be a mix of +IPv4 and IPv6. + +For a one-to-many socket, AssocId specifies the association. +For a one-to-one socket, AssocId is ignored. + +If AssocId is 0 (zero), then the returned addresses are without any +particular association. +""". +-spec socknames(Socket :: socket(), AssocId :: sctp_assoc_id()) -> + {'ok', [SockAddr]} | {'error', Reason} when + SockAddr :: sockaddr_recv(), + Reason :: posix() | 'closed'. + +socknames(?socket(SockRef), AssocId) + when is_reference(SockRef) andalso is_integer(AssocId) -> + prim_socket:socknames(SockRef, AssocId); +socknames(Socket, AssocId) -> + erlang:error(badarg, [Socket, AssocId]). + + + %% =========================================================================== %% %% peername - return the address of the peer *connected* to the socket. @@ -6317,6 +6933,38 @@ peername(Socket) -> +%% =========================================================================== +%% +%% peernames - Return the address(s) of the peer *connected* to the socket. +%% +%% + +-doc(#{since => <<"OTP @OTP-19834@">>}). +-doc """ +Return the remote (peer) address(s) of an association of a socket. + +If the socket is IPv4 then all returned addresess will be IPv4. +If the socket is IPv6, then the returned addresess can be a mix of +IPv4 and IPv6. + +For a one-to-many socket, AssocId specifies the association. +For a one-to-one socket, AssocId is ignored. + +The behaviour if AssocId is 0 (zero), is undefined for one-to-many sockets. +""". +-spec peernames(Socket :: socket(), AssocId :: sctp_assoc_id()) -> + {'ok', [SockAddr]} | {'error', Reason} when + SockAddr :: sockaddr_recv(), + Reason :: posix() | 'closed'. + +peernames(?socket(SockRef), AssocId) + when is_reference(SockRef) andalso is_integer(AssocId) -> + prim_socket:peernames(SockRef, AssocId); +peernames(Socket, AssocId) -> + erlang:error(badarg, [Socket, AssocId]). + + + %% =========================================================================== %% %% ioctl - control device - get requests @@ -6879,6 +7527,16 @@ flush_abort_msg(SockRef, Ref) -> %% %% =========================================================================== +-doc false. +mk_sockaddr(Fam, Addr) when (Fam =:= inet) orelse (Fam =:= inet6) -> + mk_sockaddr(Fam, Addr, 0); +mk_sockaddr(Fam, Path) when (Fam =:= local) -> + prim_socket:enc_sockaddr(#{family => Fam, path => Path}). + +-doc false. +mk_sockaddr(Fam, Addr, Port) when (Fam =:= inet) orelse (Fam =:= inet6) -> + prim_socket:enc_sockaddr(#{family => Fam, addr => Addr, port => Port}). + deadline(Timeout) -> case Timeout of nowait -> diff --git a/lib/kernel/test/Makefile b/lib/kernel/test/Makefile index fffec4531485..be5c194254ab 100644 --- a/lib/kernel/test/Makefile +++ b/lib/kernel/test/Makefile @@ -45,13 +45,17 @@ SOCKET_MODULES = \ socket_test_ttest_tcp_server_gen \ socket_test_ttest_tcp_server_gs \ socket_test_ttest_tcp_server_socket \ + socket_sctp_traffic_lib \ + socket_sctp_traffic_client \ + socket_sctp_traffic_server \ socket_suites \ - socket_SUITE \ socket_api_SUITE \ + socket_sctp_SUITE \ socket_traffic_SUITE \ - socket_ttest_SUITE + socket_ttest_SUITE \ + socket_SUITE -MODULES= \ +MODULES = \ kernel_test_lib \ kernel_test_global_sys_monitor \ kernel_test_sys_monitor \ @@ -157,6 +161,7 @@ HRL_FILES= \ socket_test_evaluator.hrl \ socket_test_ttest.hrl \ socket_test_ttest_client.hrl \ + socket_sctp_traffic_lib.hrl \ shell_test_lib.hrl EXTRA_FILES= $(ERL_TOP)/otp_versions.table diff --git a/lib/kernel/test/gen_sctp_SUITE.erl b/lib/kernel/test/gen_sctp_SUITE.erl index ea2f7c2647c6..023ffd3b2948 100644 --- a/lib/kernel/test/gen_sctp_SUITE.erl +++ b/lib/kernel/test/gen_sctp_SUITE.erl @@ -2101,7 +2101,9 @@ get_addrs_by_family_aux(Family, NumAddrs) when Family =:= inet; ?P("IfAddrs: ~p", [IfAddrs]), case filter_addrs_by_family(IfAddrs, Family) of Addrs when length(Addrs) >= NumAddrs -> - {ok, lists:sublist(Addrs, NumAddrs)}; + Addrs2 = lists:sublist(Addrs, NumAddrs), + ?P("Addrs2: ~p", [Addrs2]), + {ok, Addrs2}; [] -> {error, ?F("Need ~p ~p address(es) found none~n", [NumAddrs, Family])}; @@ -2113,8 +2115,10 @@ get_addrs_by_family_aux(Family, NumAddrs) when Family =:= inet; end; get_addrs_by_family_aux(inet_and_inet6, NumAddrs) -> try {ok, [case get_addrs_by_family_aux(Family, NumAddrs) of - {ok, Addrs} -> Addrs; - {error, Reason} -> throw({error, Reason}) + {ok, Addrs} -> + Addrs; + {error, Reason} -> + throw({error, Reason}) end || Family <- [inet, inet6]]} catch throw:{error, _} = ERROR -> @@ -2156,9 +2160,8 @@ do_open_and_connect(ServerAddresses, AddressToConnectTo, Fun) -> [ServerFamily|ServerOpts])), ok = gen_sctp:listen(S1, true), P1 = ok(inet:port(S1)), + ?P("S1: ~p", [inet:sockname(S1)]), ClientFamily = get_family_by_addr(AddressToConnectTo), - io:format("Connecting to ~p ~p~n", - [ClientFamily, AddressToConnectTo]), ClientOpts = [ClientFamily | case ClientFamily of @@ -2168,7 +2171,10 @@ do_open_and_connect(ServerAddresses, AddressToConnectTo, Fun) -> [] end], S2 = ok(gen_sctp:open(0, ClientOpts)), + ?P("S2: ~p", [inet:sockname(S2)]), log(open), + io:format("Connecting to ~p ~p~n", + [ClientFamily, AddressToConnectTo]), %% Verify client can connect #sctp_assoc_change{state=comm_up} = S2Assoc = ok(gen_sctp:connect(S2, AddressToConnectTo, P1, [])), diff --git a/lib/kernel/test/kernel_test_lib.erl b/lib/kernel/test/kernel_test_lib.erl index cd41c3e9ef0b..36de7df49dc9 100644 --- a/lib/kernel/test/kernel_test_lib.erl +++ b/lib/kernel/test/kernel_test_lib.erl @@ -44,7 +44,7 @@ lookup/3]). -export([ os_cmd/1, os_cmd/2, - mq/0, mq/1, + mq/1, ts/0, ts/1 ]). @@ -60,7 +60,7 @@ has_support_unix_domain_socket/0, which_local_host_info/1, which_local_host_info/2, - which_local_addr/1, which_link_local_addr/1, + which_local_addr/1, which_local_addrs/2, which_link_local_addr/1, %% Skipping not_yet_implemented/0, @@ -2321,6 +2321,13 @@ os_cond_skip_check(OsName, OsNames) -> end. +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +mq(Pid) when is_pid(Pid) -> + {messages, MQ} = process_info(Pid, messages), + MQ. + + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% lookup(Key, Config, Default) -> @@ -2595,16 +2602,6 @@ os_cmd(Cmd, Timeout) when is_integer(Timeout) andalso (Timeout > 0) -> proxy_call(fun() -> {ok, os:cmd(Cmd)} end, Timeout, {error, timeout}). -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -mq() -> - mq(self()). - -mq(Pid) when is_pid(Pid) -> - {messages, MQ} = process_info(Pid, messages), - MQ. - - %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% socket_type(Config) -> @@ -2768,6 +2765,23 @@ which_local_addr(Domain) -> ERROR end. +which_local_addrs(Domain, NumAddrs) + when (is_integer(NumAddrs) andalso (NumAddrs > 0)) orelse + (NumAddrs =:= any) -> + %% put(debug, true), + case which_local_host_info(false, Domain) of + {ok, Addrs} = OK when is_list(Addrs) andalso (NumAddrs =:= any) -> + OK; + {ok, Addrs} when (length(Addrs) >= NumAddrs) -> + %% put(debug, false), + Addrs2 = [Addr || #{addr := Addr} <- Addrs] , + {ok, lists:sublist(Addrs2, NumAddrs)}; + {ok, Addrs} -> + {error, {too_few_addrs, NumAddrs, length(Addrs)}}; + {error, _Reason} = ERROR -> + ERROR + end. + %% This function returns the link local address of the local host %% IPv4: 169.254.0.0/16 (169.254.0.0 - 169.254.255.255) diff --git a/lib/kernel/test/kernel_test_lib.hrl b/lib/kernel/test/kernel_test_lib.hrl index 372df79e8023..821505418fe5 100644 --- a/lib/kernel/test/kernel_test_lib.hrl +++ b/lib/kernel/test/kernel_test_lib.hrl @@ -63,6 +63,7 @@ -define(HAS_SUPPORT_IPV4(), ?LIB:has_support_ipv4()). -define(HAS_SUPPORT_IPV6(), ?LIB:has_support_ipv6()). -define(WHICH_LOCAL_ADDR(D), ?LIB:which_local_addr((D))). +-define(WHICH_LOCAL_ADDRS(D,N), ?LIB:which_local_addrs((D), (N))). -define(UNIQ_NODE_NAME, list_to_atom(?MODULE_STRING ++ "__" ++ @@ -76,9 +77,6 @@ -define(STOP_NODE(__N__), ?LIB:stop_node((__N__))). --define(MQ(), ?LIB:mq()). --define(MQ(P), ?LIB:mq((P))). - -define(F(FORMAT, ARGS), ?LIB:f((FORMAT), (ARGS))). -define(P(F), ?LIB:print(F)). -define(P(F,A), ?LIB:print(F, A)). @@ -93,4 +91,7 @@ -define(TS(), ?LIB:ts()). -define(TS(TU), ?LIB:ts((TU))). +-define(MQ(), ?LIB:mq(self())). +-define(MQ(P), ?LIB:mq((P))). + -endif. % -ifdef(kernel_test_lib_hrl). diff --git a/lib/kernel/test/socket_SUITE.erl b/lib/kernel/test/socket_SUITE.erl index acda4aab1b65..c72366f39f89 100644 --- a/lib/kernel/test/socket_SUITE.erl +++ b/lib/kernel/test/socket_SUITE.erl @@ -190,6 +190,9 @@ -define(DATA, <<"HOPPSAN">>). % Temporary -define(FAIL(R), exit(R)). +-define(ENABLE_DEBUG(S), socket:setopt(S, {otp, debug}, true)). +-define(DISABLE_DEBUG(S), socket:setopt(S, {otp, debug}, false)). + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -6417,17 +6420,16 @@ sc_lc_receive_response_tcp(InitState) -> case socket:accept(LSock) of {ok, Sock} -> ?SEV_IPRINT("connection accepted: " - "~n ~p", [socket:sockname(Sock)]), + "~n ~p", + [case socket:sockname(Sock) of + {ok, SA} -> SA; + {error, _} -> undefined + end]), {ok, State#{csock => Sock}}; {error, _} = ERROR -> ERROR end end}, - #{desc => "announce ready (accept)", - cmd => fun(#{tester := Tester}) -> - ?SEV_ANNOUNCE_READY(Tester, accept), - ok - end}, #{desc => "transfer connection to handler 1", cmd => fun(#{handler1 := Handler, csock := Sock}) -> ?SEV_ANNOUNCE_CONTINUE(Handler, transfer, Sock), @@ -6443,6 +6445,11 @@ sc_lc_receive_response_tcp(InitState) -> ?SEV_ANNOUNCE_CONTINUE(Handler, transfer, Sock), ok end}, + #{desc => "announce ready (accept)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, accept), + ok + end}, #{desc => "await continue (close)", cmd => fun(#{tester := Tester} = _State) -> ?SEV_AWAIT_CONTINUE(Tester, tester, close), @@ -6530,8 +6537,10 @@ sc_lc_receive_response_tcp(InitState) -> %% The actual test #{desc => "await continue (transfer)", cmd => fun(#{acceptor := Pid} = State) -> + ?SEV_IPRINT("wait for socket transfer"), case ?SEV_AWAIT_CONTINUE(Pid, acceptor, transfer) of {ok, Sock} -> + ?SEV_IPRINT("socket transfered"), {ok, State#{sock => Sock}}; {error, _} = ERROR -> ERROR @@ -6544,17 +6553,20 @@ sc_lc_receive_response_tcp(InitState) -> end}, #{desc => "attempt recv (=> closed)", cmd => fun(#{sock := Sock, recv := Recv} = State) -> - %% ok = socket:setopt(Sock, otp, debug, true), + %% ok = ?ENABLE_DEBUG(Sock), case Recv(Sock) of {ok, _Data} -> + %% ok = ?DISABLE_DEBUG(Sock), ?SEV_EPRINT("Unexpected data received"), {error, unexpected_success}; {error, closed} -> + %% ok = ?DISABLE_DEBUG(Sock), ?SEV_IPRINT("received expected 'closed' " "result"), State1 = maps:remove(sock, State), {ok, State1}; {error, Reason} = ERROR -> + %% ok = ?DISABLE_DEBUG(Sock), ?SEV_EPRINT("Unexpected read failure: " "~n ~p", [Reason]), ERROR @@ -6655,7 +6667,20 @@ sc_lc_receive_response_tcp(InitState) -> end}, #{desc => "connect to server", cmd => fun(#{sock := Sock, server_sa := ServerSA}) -> - socket:connect(Sock, ServerSA) + ?SEV_IPRINT("try connect to: " + "~n ~p", [ServerSA]), + %% ?ENABLE_DEBUG(Sock), + case socket:connect(Sock, ServerSA) of + ok -> + %% ?DISABLE_DEBUG(Sock), + ?SEV_IPRINT("connected"), + ok; + {error, Reason} = ERROR -> + %% ?DISABLE_DEBUG(Sock), + ?SEV_EPRINT("connect failed: " + "~n ~p", [Reason]), + ERROR + end end}, #{desc => "announce ready (connect)", cmd => fun(#{tester := Tester} = _State) -> diff --git a/lib/kernel/test/socket_sctp_SUITE.erl b/lib/kernel/test/socket_sctp_SUITE.erl new file mode 100644 index 000000000000..7bb75b2586dd --- /dev/null +++ b/lib/kernel/test/socket_sctp_SUITE.erl @@ -0,0 +1,6999 @@ +%% +%% %CopyrightBegin% +%% +%% SPDX-License-Identifier: Apache-2.0 +%% +%% Copyright Ericsson AB 2024-2025. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% + +%% There are some environment variables that can be used to "manipulate" +%% the test suite: +%% +%% Variable that controls which 'groups' are to run (with default values) +%% +%% ESOCK_TEST_SCTP_MISC include +%% ESOCK_TEST_SCTP_BASIC include +%% +%% Variable that controls "verbosity" of the test case(s): +%% +%% ESOCK_TEST_QUIET: true (default) | false +%% + +%% Run the entire test suite: +%% ts:run(kernel, socket_sctp_SUITE, [batch]). +%% +%% Run a specific group: +%% ts:run(kernel, socket_sctp_SUITE, {group, foo}, [batch]). +%% +%% Run a specific test case: +%% ts:run(kernel, socket_sctp_SUITE, foo, [batch]). +%% +%% (cd /mnt/c/$LOCAL_TESTS/26/kernel_test/ && $ERL_TOP/bin/win32/erl.exe -sname kernel-26-tester -pa c:$LOCAL_TESTS/26/test_server) +%% application:set_env(kernel, test_inet_backends, true). +%% S = fun() -> ts:run(kernel, socket_sctp_SUITE, [batch]) end. +%% S = fun(SUITE) -> ts:run(kernel, SUITE, [batch]) end. +%% S = fun() -> ct:run_test([{suite, socket_sctp_SUITE}]) end. +%% S = fun(SUITE) -> ct:run_test([{suite, SUITE}]) end. +%% G = fun(GROUP) -> ts:run(kernel, socket_sctp_SUITE, {group, GROUP}, [batch]) end. +%% G = fun(SUITE, GROUP) -> ts:run(kernel, SUITE, {group, GROUP}, [batch]) end. +%% G = fun(GROUP) -> ct:run_test([{suite, socket_sctp_SUITE}, {group, GROUP}]) end. +%% G = fun(SUITE, GROUP) -> ct:run_test([{suite, SUITE}, {group, GROUP}]) end. +%% T = fun(TC) -> ts:run(kernel, socket_SUITE, TC, [batch]) end. +%% T = fun(TC) -> ct:run_test([{suite, socket_sctp_SUITE}, {testcase, TC}]) end. +%% T = fun(S, TC) -> ct:run_test([{suite, S}, {testcase, TC}]) end. +%% T = fun(S, G, TC) -> ct:run_test([{suite, S}, {group, G}, {testcase, TC}]) end. + + + +-module(socket_sctp_SUITE). + +-include_lib("common_test/include/ct.hrl"). +-include_lib("common_test/include/ct_event.hrl"). +-include("socket_test_evaluator.hrl"). +-include("kernel_test_lib.hrl"). + +%% Suite exports +-export([suite/0, all/0, groups/0]). +-export([init_per_suite/1, end_per_suite/1, + init_per_group/2, end_per_group/2, + init_per_testcase/2, end_per_testcase/2]). + +%% Test cases +-export([ + %% *** Basic *** + b_simple_open_and_close_ipv4/1, + b_simple_open_and_close_ipv6/1, + b_open_and_info_ipv4/1, + b_open_and_info_ipv6/1, + b_open_and_close_ipv4/1, + b_open_and_close_ipv6/1, + b_open_listen_and_close_ipv4/1, + b_open_listen_and_close_ipv6/1, + b_open_bind_and_close_ipv4/1, + b_open_bind_and_close_ipv6/1, + b_open_connect_and_close_ipv4/1, + b_open_connect_and_close_ipv6/1, + b_stream/1, + + %% *** Misc *** + m_min_data_exchange_ipv4/1, + m_min_data_exchange_ipv6/1, + m_stream_min_data_exchange_ipv4/1, + m_stream_min_data_exchange_ipv6/1, + m_multihoming_ipv4/1, + m_multihoming_ipv6/1, + m_unihoming_ipv6/1, + m_multihoming_ipv4_and_ipv6/1, + m_peeloff_ipv4/1, + m_peeloff_ipv6/1, + m_recv_close/1, + m_buffers/1, + + %% *** Traffic *** + t_exchange_st_ipv4/1, + t_exchange_st_ipv6/1, + t_exchange_mt_ipv4/1, + t_exchange_mt_ipv6/1, + + %% *** Options *** + o_default_sri_ipv4/1 + ]). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +-define(SLIB, socket_test_lib). +-define(KLIB, kernel_test_lib). +-define(LOGGER, socket_test_logger). + +-define(BASIC_REQ, <<"hejsan">>). +-define(BASIC_REP, <<"hoppsan">>). + +-define(DATA, <<"The quick brown fox jumps over a lazy dog 0123456789">>). +%% -define(FAIL(R), exit(R)). + +-define(SCTP_EVENTS(DataIO), + #{data_io => (DataIO), + association => true, + address => true, + send_failure => true, + peer_error => true, + shutdown => true, + partial_delivery => true, + adaptation_layer => false, + authentication => false, + sender_dry => false}). + +-define(MK_SOCKOPT(Lvl, Opt), {(Lvl), (Opt)}). +-define(MK_OTP_SOCKOPT(Opt), ?MK_SOCKOPT(otp, (Opt))). +-define(MK_SOCK_SOCKOPT(Opt), ?MK_SOCKOPT(socket, (Opt))). +-define(MK_IP_SOCKOPT(Opt), ?MK_SOCKOPT(ip, (Opt))). +-define(MK_IPV6_SOCKOPT(Opt), ?MK_SOCKOPT(ipv6, (Opt))). +-define(MK_SCTP_SOCKOPT(Opt), ?MK_SOCKOPT(sctp, (Opt))). + +-define(WHICH_SCTP_STATUS(Sock, AID), + case socket:getopt(Sock, + ?MK_SCTP_SOCKOPT(status), + #{assoc_id => AID}) of + {ok, Status} -> Status; + {error, closed} -> closed; + {error, _} -> undefined + end). +-define(WHICH_SOCKNAME(Sock), + fun() -> + case socket:sockname(Sock) of + {ok, SockAddr} -> SockAddr; + {error, closed} -> closed; + {error, _} -> undefined + end + end()). +-define(ENABLE_SOCK_DEBUG(S), socket:setopt(S, ?MK_OTP_SOCKOPT(debug), true)). +-define(DISABLE_SOCK_DEBUG(S), socket:setopt(S, ?MK_OTP_SOCKOPT(debug), false)). + +-define(TRAFFIC_RUN_TIME, ?MINS(1)). +-define(TRAFFIC_DATA_M1, <<"The quick brown fox jumps over a lazy dog">>). +-define(TRAFFIC_DATA_M2, <<"The quick brown fox jumps over a lazy dog." + "The quick brown fox jumps over a lazy dog." + "The quick brown fox jumps over a lazy dog.">>). +-define(TRAFFIC_DATA_M3, <<"The quick brown fox jumps over a lazy dog." + "The quick brown fox jumps over a lazy dog." + "The quick brown fox jumps over a lazy dog." + "The quick brown fox jumps over a lazy dog." + "The quick brown fox jumps over a lazy dog." + "The quick brown fox jumps over a lazy dog." + "The quick brown fox jumps over a lazy dog." + "The quick brown fox jumps over a lazy dog." + "The quick brown fox jumps over a lazy dog." + "The quick brown fox jumps over a lazy dog.">>). +-define(TRAFFIC_DATA, [?TRAFFIC_DATA_M1, + ?TRAFFIC_DATA_M2, + ?TRAFFIC_DATA_M3]). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +suite() -> + [{ct_hooks, [ts_install_cth]}, + {timetrap, {minutes,1}}]. + +all() -> + Groups = [ + {basic, "ESOCK_TEST_SCTP_BASIC", include}, + {misc, "ESOCK_TEST_SCTP_MISC", include}, + {traffic, "ESOCK_TEST_SCTP_TRAFFIC", include}, + {options, "ESOCK_TEST_SCTP_OPTS", include} + ], + [use_group(Group, Env, Default) || {Group, Env, Default} <- Groups]. + +use_group(_Group, undefined, exclude) -> + []; +use_group(Group, undefined, _Default) -> + [{group, Group}]; +use_group(Group, Env, Default) -> + case os:getenv(Env) of + false when (Default =:= include) -> + [{group, Group}]; + false -> + []; + Val -> + case list_to_atom(string:to_lower(Val)) of + Use when (Use =:= include) orelse + (Use =:= enable) orelse + (Use =:= true) -> + [{group, Group}]; + _ -> + [] + end + end. + + +groups() -> + [ + {basic, [], basic_cases()}, + {misc, [], misc_cases()}, + {homing, [], homing_cases()}, + {traffic, [], traffic_cases()}, + {options, [], options_cases()} + ]. + +basic_cases() -> + [ + b_simple_open_and_close_ipv4, + b_simple_open_and_close_ipv6, + b_open_and_info_ipv4, + b_open_and_info_ipv6, + b_open_and_close_ipv4, + b_open_and_close_ipv6, + b_open_listen_and_close_ipv4, + b_open_listen_and_close_ipv6, + b_open_bind_and_close_ipv4, + b_open_bind_and_close_ipv6, + b_open_connect_and_close_ipv4, + b_open_connect_and_close_ipv6, + b_stream + ]. + +misc_cases() -> + [ + m_min_data_exchange_ipv4, + m_min_data_exchange_ipv6, + m_stream_min_data_exchange_ipv4, + m_stream_min_data_exchange_ipv6, + + {group, homing}, + + m_peeloff_ipv4, + m_peeloff_ipv6, + + m_recv_close, + + m_buffers + ]. + +homing_cases() -> + [ + m_multihoming_ipv4, + m_multihoming_ipv6, + m_unihoming_ipv6, + m_multihoming_ipv4_and_ipv6 + ]. + +traffic_cases() -> + [ + t_exchange_st_ipv4, + t_exchange_st_ipv6, + t_exchange_mt_ipv4, + t_exchange_mt_ipv6 + ]. + +options_cases() -> + [ + o_default_sri_ipv4 + ]. + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +init_per_suite(Config0) -> + ?P("init_per_suite -> entry with" + "~n Config: ~p" + "~n Nodes: ~p", [Config0, erlang:nodes()]), + + try socket:info() of + #{} -> + has_support_sctp(), + case ?KLIB:init_per_suite(Config0) of + {skip, _} = SKIP -> + SKIP; + + Config1 when is_list(Config1) -> + + ?P("init_per_suite -> end when " + "~n Config: ~p", [Config1]), + + %% We need a monitor on this node also + kernel_test_sys_monitor:start(), + + socket:use_registry(false), + case quiet_mode(Config1) of + default -> + case ?LOGGER:start() of + ok -> + Config1; + {error, Reason} -> + ?P("init_per_suite -> " + "Failed starting logger" + "~n Reason: ~p" + "~n", [Reason]), + {skip, "Failed starting logger"} + end; + Quiet -> + case ?LOGGER:start(Quiet) of + ok -> + [{esock_test_quiet, Quiet} | Config1]; + {error, Reason} -> + ?P("init_per_suite -> " + "Failed starting logger" + "~n Reason: ~p" + "~n", [Reason]), + {skip, "Failed starting logger"} + end + end + end + catch + error : notsup -> + {skip, "ESock Not Supported"}; + error : undef -> + {skip, "ESock Not Configured"} + end. + +end_per_suite(Config0) -> + + ?P("end_per_suite -> entry with" + "~n Config: ~p" + "~n Nodes: ~p", [Config0, erlang:nodes()]), + + %% Stop the local monitor + kernel_test_sys_monitor:stop(), + + (catch ?LOGGER:stop()), + + Config1 = ?KLIB:end_per_suite(Config0), + + ?P("end_per_suite -> " + "~n Nodes: ~p", [erlang:nodes()]), + + Config1. + + +init_per_group(_GroupName, Config) -> + Config. + +end_per_group(_GroupName, Config) -> + Config. + + +init_per_testcase(_TC, Config) -> + io:format("init_per_testcase(~w) -> entry with" + "~n Config: ~p" + "~n", [_TC, Config]), + Config. + +end_per_testcase(_TC, Config) -> + Config. + + +quiet_mode(Config) -> + case lists:keysearch(esock_test_quiet, 1, Config) of + {value, {esock_test_quiet, Quiet}} -> + Quiet; + false -> + case os:getenv("ESOCK_TEST_QUIET") of + "true" -> true; + "false" -> false; + _ -> default + end + end. + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% %% +%% API BASIC %% +%% %% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Basically open (create) and then close. +b_simple_open_and_close_ipv4(_Config) when is_list(_Config) -> + ?TT(?SECS(5)), + tc_try(?FUNCTION_NAME, + fun() -> + has_support_ipv4() + end, + fun() -> + InitState = #{domain => inet, + type => seqpacket, + protocol => sctp}, + ok = b_simple_open_and_close(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Basically open (create) and then close. +b_simple_open_and_close_ipv6(_Config) when is_list(_Config) -> + ?TT(?SECS(5)), + tc_try(?FUNCTION_NAME, + fun() -> + has_support_ipv6() + end, + fun() -> + InitState = #{domain => inet6, + type => seqpacket, + protocol => sctp}, + ok = b_simple_open_and_close(InitState) + end). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +b_simple_open_and_close(InitState) -> + Seq = + [ + #{desc => "open", + cmd => fun(#{domain := Domain, + type := Type, + protocol := Protocol} = State) -> + case socket:open(Domain, Type, Protocol) of + {ok, Sock} -> + {ok, State#{sock => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + + ?SEV_SLEEP(?SECS(1)), + + #{desc => "close socket", + cmd => fun(#{sock := Sock} = _State) -> + socket:close(Sock) + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + Evaluator = ?SEV_START("tester", Seq, InitState), + ok = ?SEV_AWAIT_FINISH([Evaluator]). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Basically open (create) and info of an IPv4 SCTP socket. +%% With some extra checks... +b_open_and_info_ipv4(_Config) when is_list(_Config) -> + ?TT(?SECS(5)), + tc_try(?FUNCTION_NAME, + fun() -> + has_support_ipv4() + end, + fun() -> + InitState = #{domain => inet, + type => seqpacket, + protocol => sctp}, + ok = b_open_and_info(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Basically open (create) and info of an IPv6 SCTP socket. +%% With some extra checks... +b_open_and_info_ipv6(_Config) when is_list(_Config) -> + ?TT(?SECS(5)), + tc_try(?FUNCTION_NAME, + fun() -> + has_support_ipv6() + end, + fun() -> + InitState = #{domain => inet6, + type => seqpacket, + protocol => sctp}, + ok = b_open_and_info(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +b_open_and_info(InitState) -> + Seq = + [ + #{desc => "open", + cmd => fun(#{domain := Domain, + type := Type, + protocol := Protocol} = State) -> + case socket:open(Domain, Type, Protocol) of + {ok, Sock} -> + {ok, State#{sock => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + + #{desc => "get socket info", + cmd => fun(#{sock := Sock} = State) -> + Info = socket:info(Sock), + ?SEV_IPRINT("Got (some) Info: " + "~n ~p", [Info]), + {ok, State#{info => Info}} + end}, + + #{desc => "validate socket info", + cmd => fun(#{domain := Domain, + type := Type, + protocol := Protocol, + info := #{domain := Domain, + type := Type, + protocol := Protocol, + counters := _, + num_readers := 0, + num_writers := 0, + num_acceptors := 0}}) -> + ok; + (#{domain := Domain, + type := Type, + protocol := Protocol, + info := Info}) -> + ?SEV_EPRINT("Unexpected Info: " + "~n (expected) Domain: ~p" + "~n (expected) Type: ~p" + "~n (expected) Protocol: ~p" + "~n ~p", + [Domain, Type, Protocol, Info]), + {error, unexpected_infio} + end}, + #{desc => "close socket", + cmd => fun(#{sock := Sock} = _State) -> + socket:close(Sock) + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + Evaluator = ?SEV_START("tester", Seq, InitState), + ok = ?SEV_AWAIT_FINISH([Evaluator]). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Basically open (create) and close an IPv4 SCTP socket. +%% With some extra checks... +b_open_and_close_ipv4(_Config) when is_list(_Config) -> + ?TT(?SECS(5)), + tc_try(?FUNCTION_NAME, + fun() -> has_support_ipv4() end, + fun() -> + InitState = #{domain => inet, + type => seqpacket, + protocol => sctp}, + ok = b_open_and_close(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Basically open (create) and close an IPv6 SCTP socket. +%% With some extra checks... +b_open_and_close_ipv6(_Config) when is_list(_Config) -> + ?TT(?SECS(5)), + tc_try(?FUNCTION_NAME, + fun() -> has_support_ipv6() end, + fun() -> + InitState = #{domain => inet6, + type => seqpacket, + protocol => sctp}, + ok = b_open_and_close(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +b_open_and_close(InitState) -> + Seq = + [ + #{desc => "open", + cmd => fun(#{domain := Domain, + type := Type, + protocol := Protocol} = S) -> + Res = socket:open(Domain, Type, Protocol), + {ok, {S, Res}} + end}, + #{desc => "validate open", + cmd => fun({S, {ok, Sock}}) -> + NewS = S#{socket => Sock}, + {ok, NewS}; + ({_, {error, epfnosupport = Reason}}) -> + {skip, Reason}; + ({_, {error, eprotonosupport = Reason}}) -> + {skip, Reason}; + ({_, {error, esocktnosupport = Reason}}) -> + {skip, Reason}; + ({_, {error, _} = ERROR}) -> + ERROR + end}, + + #{desc => "get domain (maybe)", + cmd => fun(#{socket := Sock} = S) -> + Res = socket:getopt(Sock, socket, domain), + {ok, {S, Res}} + end}, + #{desc => "validate domain (maybe)", + cmd => fun({#{domain := Domain} = S, {ok, Domain}}) -> + ?SEV_IPRINT("expected domain: ~p", [Domain]), + {ok, S}; + ({#{domain := ExpDomain}, {ok, Domain}}) -> + {error, {unexpected_domain, ExpDomain, Domain}}; + %% Some platforms do not support this option + ({S, {error, {invalid, _}} = ERROR}) -> + case + socket:is_supported(options, socket, domain) + of + true -> + ERROR; + false -> + ?SEV_IPRINT("socket option 'domain' " + "not supported"), + {ok, S} + end; + ({_, {error, _} = ERROR}) -> + ERROR + end}, + + #{desc => "get type", + cmd => fun(#{socket := Sock} = State) -> + Res = socket:getopt(Sock, socket, type), + {ok, {State, Res}} + end}, + #{desc => "validate type", + cmd => fun({#{type := Type} = State, {ok, Type}}) -> + ?SEV_IPRINT("expected type: ~p", [Type]), + {ok, State}; + ({#{type := ExpType}, {ok, Type}}) -> + {error, {unexpected_type, ExpType, Type}}; + ({_, {error, _} = ERROR}) -> + ERROR + end}, + + #{desc => "get protocol (maybe)", + cmd => fun(#{socket := Sock} = State) -> + Res = socket:getopt(Sock, socket, protocol), + {ok, {State, Res}} + end}, + #{desc => "validate protocol", + cmd => fun({#{protocol := Protocol} = State, {ok, Protocol}}) -> + ?SEV_IPRINT("expected protocol: ~p", [Protocol]), + {ok, State}; + ({#{domain := Domain, + protocol := ExpProtocol}, {ok, Protocol}}) -> + %% On OpenBSD (at least 6.6) something screwy happens + %% when domain = local. + %% It will report a completely different protocol + %% (icmp) but everything still works. + %% So we skip if this happens on OpenBSD... + case os:type() of + {unix, openbsd} when (Domain =:= local) -> + {skip, ?F("Unexpected protocol: ~p instead of ~p", + [Protocol, ExpProtocol])}; + _ -> + {error, {unexpected_protocol, + ExpProtocol, Protocol}} + end; + %% Some platforms do not support this option + ({State, {error, {invalid, _}} = ERROR}) -> + case socket:is_supported(options, socket, protocol) of + true -> + ERROR; + false -> + ?SEV_IPRINT("socket option 'protocol' " + "not supported"), + {ok, State} + end; + ({_, {error, _} = ERROR}) -> + ERROR + end}, + + #{desc => "get controlling-process", + cmd => fun(#{socket := Sock} = State) -> + Res = socket:getopt(Sock, otp, controlling_process), + {ok, {State, Res}} + end}, + #{desc => "validate controlling-process", + cmd => fun({State, {ok, Pid}}) -> + case self() of + Pid -> + {ok, State}; + _ -> + {error, {unexpected_owner, Pid}} + end; + ({_, {error, _} = ERROR}) -> + ERROR + end}, + + #{desc => "close socket", + cmd => fun(#{socket := Sock} = State) -> + Res = socket:close(Sock), + {ok, {State, Res}} + end}, + #{desc => "validate socket close", + cmd => fun({_, ok}) -> + ok; + ({_, {error, _} = ERROR}) -> + ERROR + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + Evaluator = ?SEV_START("tester", Seq, InitState), + ok = ?SEV_AWAIT_FINISH([Evaluator]). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Basically open (create), make listen and close an IPv4 SCTP socket. +%% With some extra checks... +b_open_listen_and_close_ipv4(_Config) when is_list(_Config) -> + ?TT(?SECS(5)), + tc_try(?FUNCTION_NAME, + fun() -> has_support_ipv4() end, + fun() -> + InitState = #{domain => inet, + type => seqpacket, + protocol => sctp}, + ok = b_open_listen_and_close(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Basically open (create), make listen and close an IPv4 SCTP socket. +%% With some extra checks... +b_open_listen_and_close_ipv6(_Config) when is_list(_Config) -> + ?TT(?SECS(5)), + tc_try(?FUNCTION_NAME, + fun() -> has_support_ipv6() end, + fun() -> + InitState = #{domain => inet6, + type => seqpacket, + protocol => sctp}, + ok = b_open_listen_and_close(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +b_open_listen_and_close(InitState) -> + Seq = + [ + #{desc => "open", + cmd => fun(#{domain := Domain, + type := Type, + protocol := Protocol} = S) -> + case socket:open(Domain, Type, Protocol) of + {ok, Sock} -> + {ok, S#{sock => Sock}}; + {error, epfnosupport = Reason} -> + {skip, Reason}; + {error, eprotonosupport = Reason} -> + {skip, Reason}; + {error, esocktnosupport = Reason} -> + {skip, Reason}; + {error, Reason} = ERROR -> + ?SEV_EPRINT("Failed open socket: " + "~n ~p", [Reason]), + ERROR + end + end}, + + #{desc => "info socket (pre listen)", + cmd => fun(#{sock := Sock} = _State) -> + ?SEV_IPRINT("socket info:" + "~n ~p", [socket:info(Sock)]), + ok + end}, + + #{desc => "make listen socket", + cmd => fun(#{sock := Sock} = _State) -> + case socket:listen(Sock) of + ok -> + ok; + {error, Reason} = ERROR -> + ?SEV_EPRINT("Failed make listen socket: " + "~n ~p", [Reason]), + ERROR + end + end}, + + #{desc => "info socket (post listen)", + cmd => fun(#{sock := Sock} = _State) -> + case socket:info(Sock) of + #{rstates := [listening]} = OkInfo -> + ?SEV_IPRINT("OK socket info:" + "~n ~p", [OkInfo]), + ok; + #{rstates := RStates} = ErrInfo -> + ?SEV_EPRINT("Erronous (rstates) socket info:" + "~n ~p", [ErrInfo]), + {error, RStates} + end + end}, + + #{desc => "close socket", + cmd => fun(#{sock := Sock} = State) -> + ok = socket:close(Sock), + {ok, State#{sock => undefined}} + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + Evaluator = ?SEV_START("tester", Seq, InitState), + ok = ?SEV_AWAIT_FINISH([Evaluator]). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Basically open (create), bind and close an IPv4 SCTP socket. +%% With some extra checks... +b_open_bind_and_close_ipv4(_Config) when is_list(_Config) -> + ?TT(?SECS(5)), + Cond = fun() -> has_support_ipv4() end, + Pre = fun() -> + Domain = inet, + case ?WHICH_LOCAL_ADDR(Domain) of + {ok, Addr} -> + #{addr => Addr, + domain => Domain, + type => seqpacket, + protocol => sctp}; + {error, Reason} -> + throw({skip, Reason}) + end + end, + TC = fun(State) -> + ok = b_open_bind_and_close(State) + end, + Post = fun(_) -> ok end, + tc_try(?FUNCTION_NAME, Cond, Pre, TC, Post). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Basically open (create), bind and close an IPv6 SCTP socket. +%% With some extra checks... +b_open_bind_and_close_ipv6(_Config) when is_list(_Config) -> + ?TT(?SECS(5)), + Cond = fun() -> has_support_ipv6() end, + Pre = fun() -> + Domain = inet6, + case ?WHICH_LOCAL_ADDR(Domain) of + {ok, Addr} -> + #{addr => Addr, + domain => Domain, + type => seqpacket, + protocol => sctp}; + {error, Reason} -> + throw({skip, Reason}) + end + end, + TC = fun(State) -> + ok = b_open_bind_and_close(State) + end, + Post = fun(_) -> ok end, + tc_try(?FUNCTION_NAME, Cond, Pre, TC, Post). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +b_open_bind_and_close(InitState) -> + Seq = + [ + #{desc => "open", + cmd => fun(#{domain := Domain, + type := Type, + protocol := Protocol} = S) -> + case socket:open(Domain, Type, Protocol) of + {ok, Sock} -> + {ok, S#{sock => Sock}}; + {error, epfnosupport = Reason} -> + {skip, Reason}; + {error, eprotonosupport = Reason} -> + {skip, Reason}; + {error, esocktnosupport = Reason} -> + {skip, Reason}; + {error, Reason} = ERROR -> + ?SEV_EPRINT("Failed open socket: " + "~n ~p", [Reason]), + ERROR + end + end}, + + #{desc => "socket info (pre bind)", + cmd => fun(#{sock := Sock} = _State) -> + ?SEV_IPRINT("socket info:" + "~n ~p", [socket:info(Sock)]), + ok + end}, + + #{desc => "bind socket", + cmd => fun(#{sock := Sock, + domain := Domain, + addr := Addr} = _State) -> + SockAddr = #{family => Domain, + addr => Addr, + port => 0}, + case socket:bind(Sock, SockAddr) of + ok -> + ok; + {error, Reason} = ERROR -> + ?SEV_EPRINT("Failed bind socket: " + "~n ~p", [Reason]), + ERROR + end + end}, + + #{desc => "socket info (post bind)", + cmd => fun(#{sock := Sock} = _State) -> + case socket:info(Sock) of + #{rstates := [bound]} = OkInfo -> + {ok, SN} = socket:sockname(Sock), + ?SEV_IPRINT("OK socket info:" + "~n Info: ~p" + "~n SockName: ~p", + [OkInfo, SN]), + ok; + #{rstates := RStates} = ErrInfo -> + ?SEV_EPRINT("Erronous (rstates) socket info:" + "~n ~p", [ErrInfo]), + {error, RStates} + end + end}, + + #{desc => "close socket", + cmd => fun(#{sock := Sock} = State) -> + ok = socket:close(Sock), + {ok, State#{sock => undefined}} + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + Evaluator = ?SEV_START("tester", Seq, InitState), + ok = ?SEV_AWAIT_FINISH([Evaluator]). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Basically open (create) a listen socket and then create another +%% socket that connects to it: IPv4 SCTP socket(s). +b_open_connect_and_close_ipv4(_Config) when is_list(_Config) -> + ?TT(?SECS(5)), + Cond = fun() -> has_support_ipv4() end, + Pre = fun() -> + Domain = inet, + case ?WHICH_LOCAL_ADDR(Domain) of + {ok, Addr} -> + #{addr => Addr, + domain => Domain, + type => seqpacket, + protocol => sctp}; + {error, Reason} -> + throw({skip, Reason}) + end + end, + TC = fun(State) -> + ok = b_open_connect_and_close(State) + end, + Post = fun(_) -> ok end, + tc_try(?FUNCTION_NAME, Cond, Pre, TC, Post). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Basically open (create) a listen socket and then create another +%% socket that connects to it: IPv6 SCTP socket(s). +b_open_connect_and_close_ipv6(_Config) when is_list(_Config) -> + ?TT(?SECS(5)), + Cond = fun() -> has_support_ipv6() end, + Pre = fun() -> + Domain = inet6, + case ?WHICH_LOCAL_ADDR(Domain) of + {ok, Addr} -> + #{addr => Addr, + domain => Domain, + type => seqpacket, + protocol => sctp}; + {error, Reason} -> + throw({skip, Reason}) + end + end, + TC = fun(State) -> + ok = b_open_connect_and_close(State) + end, + Post = fun(_) -> ok end, + tc_try(?FUNCTION_NAME, Cond, Pre, TC, Post). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +b_open_connect_and_close(InitState) -> + Seq = + [ + #{desc => "open listen socket", + cmd => fun(#{domain := Domain, + protocol := Protocol} = S) -> + case socket:open(Domain, seqpacket, Protocol) of + {ok, Sock} -> + {ok, S#{lsock => Sock}}; + {error, epfnosupport = Reason} -> + {skip, Reason}; + {error, eprotonosupport = Reason} -> + {skip, Reason}; + {error, esocktnosupport = Reason} -> + {skip, Reason}; + {error, Reason} = ERROR -> + ?SEV_EPRINT("Failed open socket: " + "~n ~p", [Reason]), + ERROR + end + end}, + + #{desc => "(listen) socket info (pre bind)", + cmd => fun(#{lsock := Sock} = _State) -> + ?SEV_IPRINT("socket info:" + "~n ~p", [socket:info(Sock)]), + ok + end}, + + #{desc => "bind (listen) socket", + cmd => fun(#{lsock := Sock, + domain := Domain, + addr := Addr} = _State) -> + SockAddr = #{family => Domain, + addr => Addr, + port => 0}, + case socket:bind(Sock, SockAddr) of + ok -> + ok; + {error, Reason} = ERROR -> + ?SEV_EPRINT("Failed bind socket: " + "~n ~p", [Reason]), + ERROR + end + end}, + + #{desc => "socket info (post bind)", + cmd => fun(#{lsock := Sock} = State) -> + case socket:info(Sock) of + #{rstates := [bound]} = OkInfo -> + {ok, #{port := Port}} = SN = + socket:sockname(Sock), + ?SEV_IPRINT("OK socket info:" + "~n Info: ~p" + "~n SockName: ~p", + [OkInfo, SN]), + {ok, State#{lport => Port}}; + #{rstates := RStates} = ErrInfo -> + ?SEV_EPRINT("Erronous (rstates) socket info:" + "~n ~p", [ErrInfo]), + {error, RStates} + end + end}, + + #{desc => "subscribe to (sctp) events", + cmd => fun(#{lsock := Sock, + events := Evs} = _State) -> + socket:setopt(Sock, sctp, events, Evs) + end}, + + #{desc => "make listen socket", + cmd => fun(#{lsock := Sock} = _State) -> + socket:listen(Sock, true) + end}, + + #{desc => "socket info (post listen)", + cmd => fun(#{lsock := Sock} = _State) -> + case socket:info(Sock) of + #{rstates := RStates} = OkInfo -> + ?SEV_IPRINT("OK socket info:" + "~n Info: ~p", [OkInfo]), + case lists:member(listening, RStates) of + true -> + ok; + false -> + {error, RStates} + end + end + end}, + + %% *** CONNECTING *** + + #{desc => "open (connecting) socket", + cmd => fun(#{domain := Domain, + type := Type, + protocol := Protocol} = S) -> + case socket:open(Domain, Type, Protocol) of + {ok, Sock} -> + {ok, S#{csock => Sock}}; + {error, epfnosupport = Reason} -> + {skip, Reason}; + {error, eprotonosupport = Reason} -> + {skip, Reason}; + {error, esocktnosupport = Reason} -> + {skip, Reason}; + {error, Reason} = ERROR -> + ?SEV_EPRINT("Failed open socket: " + "~n ~p", [Reason]), + ERROR + end + end}, + + #{desc => "(connecting) socket info (pre bind)", + cmd => fun(#{csock := Sock} = _State) -> + ?SEV_IPRINT("socket info:" + "~n ~p", [socket:info(Sock)]), + ok + end}, + + #{desc => "bind (connecting) socket", + cmd => fun(#{csock := Sock, + domain := Domain, + addr := Addr} = _State) -> + SockAddr = #{family => Domain, + addr => Addr, + port => 0}, + case socket:bind(Sock, SockAddr) of + ok -> + ok; + {error, Reason} = ERROR -> + ?SEV_EPRINT("Failed bind socket: " + "~n ~p", [Reason]), + ERROR + end + end}, + + #{desc => "(connecting) socket info (post bind)", + cmd => fun(#{csock := Sock} = _State) -> + case socket:info(Sock) of + #{rstates := [bound]} = OkInfo -> + ?SEV_IPRINT("OK socket info:" + "~n Info: ~p", [OkInfo]), + ok; + #{rstates := RStates} = ErrInfo -> + ?SEV_EPRINT("Erronous (rstates) socket info:" + "~n ~p", [ErrInfo]), + {error, RStates} + end + end}, + + #{desc => "subscribe to (sctp) events", + cmd => fun(#{csock := Sock, + events := Evs} = _State) -> + socket:setopt(Sock, sctp, events, Evs) + end}, + + + #{desc => "connect to server", + cmd => fun(#{csock := Sock, + domain := Domain, + addr := Addr, + lport := Port} = _State) -> + ServerSA = #{family => Domain, + addr => Addr, + port => Port}, + case socket:connect(Sock, ServerSA) of + ok -> + ?SEV_IPRINT("connected"), + ok; + {error, Reason} = ERROR -> + ?SEV_EPRINT("Failed connect:" + "~n ~p", [Reason]), + ERROR + end + end}, + + #{desc => "await the connect confirmation: assoc-change:comm-up", + cmd => fun(#{csock := Sock} = State) -> + ?SEV_IPRINT("try read assoc-change:comm-up notification message"), + case socket:recvmsg(Sock) of + {ok, #{flags := Flags} = Msg} -> + ?SEV_IPRINT("try verify notification"), + case lists:member(notification, Flags) of + true -> + ?SEV_IPRINT("notification"), + case Msg of + #{notification := + #{type := assoc_change, + state := comm_up, + assoc_id := CAID, + '$esock_name' := sctp_notification}} -> + ?SEV_IPRINT("expected connect confirmation:" + "~n AssocID: ~p", [CAID]), + {ok, State#{caid => CAID}}; + #{notification := Notif} -> + ?SEV_IPRINT("unexpected (connect) notification:" + "~n Notif: ~p", [Notif]), + error; + _ -> + ?SEV_EPRINT("unexpected connect confirmation :" + "~n ~p", [Msg]), + error + end; + false -> + ?SEV_EPRINT("no notification flag detected:" + "~n ~p", [Msg]), + error + end; + {error, Reason} = ERROR -> + ?SEV_EPRINT("Failed read connect :" + "~n ~p", [Reason]), + ERROR + end + end}, + + #{desc => "(connected) socket info (post connect)", + cmd => fun(#{csock := Sock} = _State) -> + case socket:info(Sock) of + #{rstates := RStates, + wstates := WStates} = Info -> + case lists:member(bound, RStates) andalso + lists:member(connected, WStates) of + true -> + ?SEV_IPRINT("OK socket info:" + "~n Info: ~p", [Info]), + ok; + false -> + ?SEV_EPRINT("Erronous states:" + "~n Read States: ~p" + "~n Write States: ~p", + [RStates, WStates]), + {error, {RStates, WStates}} + end + end + end}, + + #{desc => "(connecting) socket misc (ioctl) info", + cmd => fun(#{csock := Sock} = _State) -> + ?SEV_IPRINT("misc (ioctl) socket info: " + "~n nread: ~p" + "~n atmark: ~p", + [case socket:ioctl(Sock, nread) of + {ok, NRead} -> NRead; + {error, _} -> undefined + end, + case socket:ioctl(Sock, atmark) of + {ok, Bool} -> Bool; + {error, _} -> undefined + end]), + ok + end}, + + #{desc => "accept connection (recvmsg get assoc-change:comm-up)", + cmd => fun(#{lsock := Sock} = State) -> + ?SEV_IPRINT("try recv assoc-change:comm-up message"), + case socket:recvmsg(Sock) of + {ok, #{flags := Flags} = Msg} -> + ?SEV_IPRINT("try verify notification"), + case lists:member(notification, Flags) of + true -> + ?SEV_IPRINT("notification"), + case Msg of + #{notification := + #{type := assoc_change, + state := comm_up, + assoc_id := AAID, + '$esock_name' := sctp_notification}} -> + ?SEV_IPRINT("expected accept notification:" + "~n AssocID: ~p", [AAID]), + {ok, State#{aaid => AAID}}; + #{notification := Notif} -> + ?SEV_IPRINT("unexpected (accept) notification:" + "~n Notif: ~p", [Notif]), + error; + _ -> + ?SEV_EPRINT("unexpected accept msg :" + "~n ~p", [Msg]), + error + end; + false -> + ?SEV_EPRINT("no notification flag detected:" + "~n ~p", [Msg]), + error + end + end + end}, + + + #{desc => "client assoc status", + cmd => fun(#{csock := Sock, + caid := AID} = _State) -> + SockOpt = ?MK_SCTP_SOCKOPT(status), + SparseStatus = #{assoc_id => AID}, + case socket:getopt(Sock, SockOpt, SparseStatus) of + {ok, Status} -> + ?SEV_IPRINT("assoc ~w status:" + "~n ~p", [AID, Status]), + ok; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "client socket send buffer size", + cmd => fun(#{csock := Sock, + caid := _AID} = _State) -> + SockOpt = ?MK_SOCK_SOCKOPT(sndbuf), + case socket:getopt(Sock, SockOpt) of + {ok, Sz} -> + ?SEV_IPRINT("send buffer size:" + "~n ~p", [Sz]), + ok; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "client socket receive buffer size", + cmd => fun(#{csock := Sock, + caid := _AID} = _State) -> + SockOpt = ?MK_SOCK_SOCKOPT(rcvbuf), + case socket:getopt(Sock, SockOpt) of + {ok, Sz} -> + ?SEV_IPRINT("receive buffer size:" + "~n ~p", [Sz]), + ok; + {error, _} = ERROR -> + ERROR + end + end}, + + + #{desc => "initiate gracefully close client assoc socket (eof)", + cmd => fun(#{csock := Sock, + caid := AID} = State) -> + ?SEV_IPRINT("try gracefully close assoc (eof)"), + EofSRI = #{assoc_id => AID, + stream => 0, + ssn => 0, + flags => [eof], + ppid => 0, + context => 0, + time_to_live => 0, + tsn => 0, + cum_tsn => 0}, + EofMsg = #{iov => [], + ctrl => [#{level => sctp, + type => sndrcv, + value => EofSRI}]}, + %% ?ENABLE_SOCK_DEBUG(Sock), + case socket:sendmsg(Sock, EofMsg) of + ok -> + %% ?DISABLE_SOCK_DEBUG(Sock), + ?SEV_IPRINT("eof message sent"), + {ok, State#{caid => undefined}}; + Error -> + %% ?DISABLE_SOCK_DEBUG(Sock), + Error + end + end}, + + #{desc => "await server side assoc-change:shutdown or peer-addr-change", + cmd => fun(#{lsock := Sock, + aaid := AID} = State) -> + ?SEV_IPRINT("await assoc shutdown"), + case await_shutdown_or_maybe_peer_addr_change(Sock, AID) of + ok -> + ?SEV_IPRINT("shutdown received"), + {ok, maps:remove(aaid, State)}; + {error, _} -> + ?SEV_EPRINT("no shutdown received"), + error + end + end}, + + #{desc => "maybe await server side assoc-change:shutdown ", + cmd => fun(#{lsock := Sock, + aaid := AID} = State) -> + ?SEV_IPRINT("try recv assoc shutdown message"), + case socket:recvmsg(Sock) of + {ok, #{flags := Flags} = Msg} -> + ?SEV_IPRINT("try verify notification"), + case lists:member(notification, Flags) of + true -> + ?SEV_IPRINT("notification"), + case Msg of + #{notification := + #{type := shutdown_event, + assoc_id := AID, + '$esock_name' := sctp_notification}} -> + ?SEV_IPRINT("expected assoc shutdown notification"), + {ok, maps:remove(aaid, State)}; + _ -> + ?SEV_EPRINT("unexpected assoc shutdown msg:" + "~n ~p", [Msg]), + error + end; + false -> + ?SEV_EPRINT("no notification flag detected:" + "~n ~p", [Msg]), + error + end + end; + (_) -> + ?SEV_IPRINT("NOP"), + ok + end}, + + + #{desc => "close (connecting) socket", + cmd => fun(#{csock := Sock} = State) -> + ok = socket:close(Sock), + {ok, State#{csock => undefined}} + end}, + + #{desc => "close (listen) socket", + cmd => fun(#{lsock := Sock} = State) -> + ok = socket:close(Sock), + {ok, State#{lsock => undefined}} + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + Evaluator = ?SEV_START("tester", Seq, + InitState#{events => ?SCTP_EVENTS(true)}), + ok = ?SEV_AWAIT_FINISH([Evaluator]). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Hello world stream socket. +b_stream(_Config) when is_list(_Config) -> + ?TT(?SECS(5)), + Cond = fun() -> has_support_ipv4() end, + Pre = fun() -> + Domain = inet, + case ?WHICH_LOCAL_ADDR(Domain) of + {ok, Addr} -> + #{addr => Addr, + domain => Domain, + type => stream, + protocol => sctp}; + {error, Reason} -> + throw({skip, Reason}) + end + end, + TC = fun(State) -> + ok = do_b_stream(State) + end, + Post = fun(_) -> ok end, + tc_try(?FUNCTION_NAME, Cond, Pre, TC, Post). + +do_b_stream(#{domain := Domain, + type := Type, + protocol := sctp = Proto, + addr := Addr}) -> + {ok, S} = socket:open(Domain, Type, Proto), + ok = socket:bind(S, #{family => Domain, + addr => Addr, + port => 0}), + ok = socket:listen(S, true), + ok = do_from_other_process(fun() -> socket:listen(S, 10) end), + ok = socket:close(S), + ok. + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Basically open (create) a listen socket and then create another +%% socket that connects to it and then exchange one message. +%% IPv4 SCTP socket(s). +m_min_data_exchange_ipv4(_Config) when is_list(_Config) -> + ?TT(?SECS(5)), + Cond = fun() -> has_support_ipv4() end, + Pre = fun() -> + Domain = inet, + case ?WHICH_LOCAL_ADDR(Domain) of + {ok, Addr} -> + #{addr => Addr, + domain => Domain, + type => seqpacket, + protocol => sctp}; + {error, Reason} -> + throw({skip, Reason}) + end + end, + TC = fun(State) -> + ok = m_min_data_exchange(State) + end, + Post = fun(_) -> ok end, + tc_try(?FUNCTION_NAME, Cond, Pre, TC, Post). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Basically open (create) a listen socket and then create another +%% socket that connects to it and then exchange one message. +%% IPv6 SCTP socket(s). +m_min_data_exchange_ipv6(_Config) when is_list(_Config) -> + ?TT(?SECS(5)), + Cond = fun() -> has_support_ipv6() end, + Pre = fun() -> + Domain = inet6, + case ?WHICH_LOCAL_ADDR(Domain) of + {ok, Addr} -> + #{addr => Addr, + domain => Domain, + type => seqpacket, + protocol => sctp}; + {error, Reason} -> + throw({skip, Reason}) + end + end, + TC = fun(State) -> + ok = m_min_data_exchange(State) + end, + Post = fun(_) -> ok end, + tc_try(?FUNCTION_NAME, Cond, Pre, TC, Post). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +m_min_data_exchange(InitState) -> + process_flag(trap_exit, true), + + ServerSeq = + [ + %% *** Wait for start order part *** + #{desc => "await start (from tester)", + cmd => fun(State) -> + Tester = ?SEV_AWAIT_START(), + {ok, State#{tester => Tester}} + end}, + #{desc => "monitor tester", + cmd => fun(#{tester := Tester} = _State) -> + _MRef = erlang:monitor(process, Tester), + ok + end}, + + %% *** Init part *** + #{desc => "create (listen) socket", + cmd => fun(#{domain := Domain, + type := Type, + protocol := Proto} = State) -> + case socket:open(Domain, Type, Proto) of + {ok, Sock} -> + {ok, State#{sock => Sock}}; + {error, epfnosupport = Reason} -> + {skip, Reason}; + {error, eprotonosupport = Reason} -> + {skip, Reason}; + {error, esocktnosupport = Reason} -> + {skip, Reason}; + {error, Reason} = ERROR -> + ?SEV_EPRINT("Failed open socket: " + "~n ~p", [Reason]), + ERROR + end + end}, + #{desc => "bind to local address", + cmd => fun(#{sock := Sock, + domain := Domain, + addr := Addr} = State) -> + LSA = #{family => Domain, + addr => Addr, + port => 0}, + case socket:bind(Sock, LSA) of + ok -> + case socket:sockname(Sock) of + {ok, #{port := Port}} -> + ?SEV_IPRINT("bound to port: ~w", + [Port]), + {ok, State#{port => Port}}; + {error, _} = ERROR -> + ERROR + end; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "subscribe to sctp events", + cmd => fun(#{sock := Sock, + events := Evs}) -> + socket:setopt(Sock, sctp, events, Evs) + end}, + #{desc => "make listen socket", + cmd => fun(#{sock := Sock}) -> + socket:listen(Sock, true) + end}, + #{desc => "announce ready (init)", + cmd => fun(#{tester := Tester, port := Port}) -> + ?SEV_ANNOUNCE_READY(Tester, init, Port), + ok + end}, + + + %% The actual test + #{desc => "await continue (accept connection)", + cmd => fun(#{tester := Tester}) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, accept) + end}, + #{desc => "accept connection: (recvmsg of assoc-change:comm-up)", + cmd => fun(#{sock := Sock} = State) -> + case accept_connection(Sock) of + {ok, {_, Assoc}} -> + {ok, State#{assoc => Assoc}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (accept)", + cmd => fun(#{tester := Tester, assoc := Assoc}) -> + ?SEV_IPRINT("announce assoc accepted: " + "~n Assoc: ~p", [Assoc]), + ?SEV_ANNOUNCE_READY(Tester, accept), + ok + end}, + + #{desc => "await continue (recv)", + cmd => fun(#{tester := Tester}) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, recv) + end}, + #{desc => "await first data (recvmsg)", + cmd => fun(#{sock := Sock, + assoc := #{assoc_id := AID} = _Assoc} = State) -> + case recv_first_data(Sock) of + {ok, {_SA, ?DATA, AID, Stream}} -> + ?SEV_IPRINT("expected data received on" + "~n Stream: ~p", [Stream]), + {ok, State#{stream => Stream}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (recv)", + cmd => fun(#{tester := Tester}) -> + ?SEV_IPRINT("announce recv"), + ?SEV_ANNOUNCE_READY(Tester, recv), + ok + end}, + #{desc => "await continue (send)", + cmd => fun(#{tester := Tester}) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, send) + end}, + #{desc => "send data back", + cmd => fun(#{sock := Sock, + assoc := #{assoc_id := AID}, + stream := Stream} = _State) -> + ?SEV_IPRINT("build sparse SRI " + "(only assoc-id and stream)"), + SRI = #{assoc_id => AID, stream => Stream}, + Msg = #{iov => [?DATA], + ctrl => [#{level => sctp, + type => sndrcv, + value => SRI}]}, + case socket:sendmsg(Sock, Msg) of + ok -> + ?SEV_IPRINT("msg sent"), + ok; + {error, Reason} = ERROR -> + ?SEV_EPRINT("Failed send msg:" + "~n ~p", [Reason]), + ERROR + end + end}, + #{desc => "announce ready (send)", + cmd => fun(#{tester := Tester}) -> + ?SEV_IPRINT("announce message sent"), + ?SEV_ANNOUNCE_READY(Tester, send), + ok + end}, + + #{desc => "await continue (await_shutdown)", + cmd => fun(#{tester := Tester}) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, await_shutdown) + end}, + #{desc => "await shutdown", + cmd => fun(#{sock := Sock, + assoc := #{assoc_id := AID}} = State) -> + case await_shutdown(Sock, AID) of + ok -> + {ok, maps:remove(assoc, State)}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (await_shutdown)", + cmd => fun(#{tester := Tester}) -> + ?SEV_IPRINT("announce shutdown received"), + ?SEV_ANNOUNCE_READY(Tester, await_shutdown), + ok + end}, + + #{desc => "await continue (stats)", + cmd => fun(#{tester := Tester}) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, stats) + end}, + #{desc => "statistics", + cmd => fun(#{sock := Sock} = _State) -> + #{counters := Counters} = socket:info(Sock), + ?SEV_IPRINT("Counters: " + "~n ~p", [Counters]), + ok + end}, + + + %% *** Terminate *** + #{desc => "await terminate", + cmd => fun(#{tester := Tester} = State) -> + case ?SEV_AWAIT_TERMINATE(Tester, tester) of + ok -> + {ok, maps:remove(tester, State)}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "close listen socket", + cmd => fun(#{sock := Sock} = State) -> + case socket:close(Sock) of + ok -> + State1 = maps:remove(sock, State), + State2 = maps:remove(port, State1), + {ok, State2}; + {error, _} = ERROR -> + ERROR + end + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + ClientSeq = + [ + %% *** Wait for start order part *** + #{desc => "await start (from tester)", + cmd => fun(State) -> + {Tester, ServerPort} = ?SEV_AWAIT_START(), + ?SEV_IPRINT("starting with" + "~n ServerPort: ~w", [ServerPort]), + {ok, State#{tester => Tester, + server_port => ServerPort}} + end}, + #{desc => "monitor tester", + cmd => fun(#{tester := Tester} = _State) -> + _MRef = erlang:monitor(process, Tester), + ok + end}, + + %% *** Init part *** + #{desc => "create socket", + cmd => fun(#{domain := Domain, + type := Type, + protocol := Proto} = State) -> + case socket:open(Domain, Type, Proto) of + {ok, Sock} -> + {ok, State#{sock => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "bind to local address", + cmd => fun(#{sock := Sock, + domain := Domain, + addr := Addr} = State) -> + LSA = #{family => Domain, + addr => Addr, + port => 0}, + case socket:bind(Sock, LSA) of + ok -> + case socket:sockname(Sock) of + {ok, #{port := Port}} -> + ?SEV_IPRINT("bound to port: ~w", + [Port]), + {ok, State#{port => Port}}; + {error, _} = ERROR -> + ERROR + end; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "subscribe to sctp events", + cmd => fun(#{sock := Sock, + events := Evs}) -> + socket:setopt(Sock, sctp, events, Evs) + end}, + #{desc => "announce ready (init)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, init), + ok + end}, + + + %% The actual test + #{desc => "await continue (connect)", + cmd => fun(#{tester := Tester}) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, connect) + end}, + #{desc => "accept connection: (recvmsg of assoc-change:comm-up)", + cmd => fun(#{sock := Sock, + domain := Domain, + addr := Addr, + server_port := Port} = _State) -> + ServerSA = #{family => Domain, + addr => Addr, + port => Port}, + case socket:connect(Sock, ServerSA) of + ok -> + ?SEV_IPRINT("connected"), + ok; + {error, Reason} = ERROR -> + ?SEV_EPRINT("Failed connect:" + "~n ~p", [Reason]), + ERROR + end + end}, + #{desc => "await connect confirmation: (recvmsg of assoc-change:comm-up)", + cmd => fun(#{sock := Sock} = State) -> + case connect_confirmation(Sock) of + {ok, {_, Assoc}} -> + {ok, State#{assoc => Assoc}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (connect)", + cmd => fun(#{tester := Tester, assoc := Assoc}) -> + ?SEV_IPRINT("announce connected: " + "~n Assoc: ~p", [Assoc]), + ?SEV_ANNOUNCE_READY(Tester, connect), + ok + end}, + + #{desc => "await continue (send message)", + cmd => fun(#{tester := Tester}) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, send) + end}, + #{desc => "send (initial) message", + cmd => fun(#{sock := Sock, + assoc := #{assoc_id := AID}} = _State) -> + ?SEV_IPRINT("build sparse SRI " + "(only assoc-id and stream)"), + SRI = #{assoc_id => AID, stream => 0}, + Msg = #{iov => [?DATA], + ctrl => [#{level => sctp, + type => sndrcv, + value => SRI}]}, + case socket:sendmsg(Sock, Msg) of + ok -> + ?SEV_IPRINT("msg sent"), + ok; + {error, Reason} = ERROR -> + ?SEV_EPRINT("Failed send msg:" + "~n ~p", [Reason]), + ERROR + end + end}, + #{desc => "announce ready (send)", + cmd => fun(#{tester := Tester}) -> + ?SEV_IPRINT("announce message sent"), + ?SEV_ANNOUNCE_READY(Tester, send), + ok + end}, + + #{desc => "await continue (recv)", + cmd => fun(#{tester := Tester}) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, recv) + end}, + #{desc => "await data (recvmsg)", + cmd => fun(#{sock := Sock, + assoc := #{assoc_id := AID} = _Assoc} = _State) -> + case recv_data(Sock) of + {ok, {_SA, ?DATA, AID, 0}} -> + ?SEV_IPRINT("expected data received"), + ok; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (recv)", + cmd => fun(#{tester := Tester}) -> + ?SEV_IPRINT("announce recv"), + ?SEV_ANNOUNCE_READY(Tester, recv), + ok + end}, + + #{desc => "await continue (eof)", + cmd => fun(#{tester := Tester}) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, eof) + end}, + #{desc => "gracefully close assoc (eof)", + cmd => fun(#{sock := Sock, + assoc := #{assoc_id := AID} = _Assoc} = _State) -> + SRI = #{assoc_id => AID, + stream => 0, + flags => [eof]}, + Msg = #{iov => [<<>>], + ctrl => [#{level => sctp, + type => sndrcv, + value => SRI}]}, + case socket:sendmsg(Sock, Msg) of + ok -> + ?SEV_IPRINT("eof sent"), + ok; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (eof)", + cmd => fun(#{tester := Tester}) -> + ?SEV_IPRINT("announce eof done"), + ?SEV_ANNOUNCE_READY(Tester, eof), + ok + end}, + + #{desc => "await continue (stats)", + cmd => fun(#{tester := Tester}) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, stats) + end}, + #{desc => "statistics", + cmd => fun(#{sock := Sock} = _State) -> + #{counters := Counters} = socket:info(Sock), + ?SEV_IPRINT("Counters: " + "~n ~p", [Counters]), + ok + end}, + + + + %% *** Terminate *** + #{desc => "await terminate", + cmd => fun(#{tester := Tester} = State) -> + case ?SEV_AWAIT_TERMINATE(Tester, tester) of + ok -> + ?SEV_IPRINT("received termination " + "from tester"), + {ok, maps:remove(tester, State)}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "close listen socket", + cmd => fun(#{sock := Sock} = State0) -> + ?SEV_IPRINT("try close socket"), + case socket:close(Sock) of + ok -> + State1 = maps:remove(sock, State0), + State2 = maps:remove(port, State1), + State3 = maps:remove(server_port, State2), + ?SEV_IPRINT("socket closed and " + "cleanup done"), + {ok, State3}; + {error, _} = ERROR -> + ERROR + end + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + TesterSeq = + [ + %% *** Init part *** + #{desc => "monitor 'server'", + cmd => fun(#{server := Server} = _State) -> + _MRef = erlang:monitor(process, Server), + ok + end}, + #{desc => "order server start", + cmd => fun(#{server := Pid} = _State) -> + ?SEV_ANNOUNCE_START(Pid), + ok + end}, + #{desc => "await (server) ready", + cmd => fun(#{server := Pid} = State) -> + case ?SEV_AWAIT_READY(Pid, acceptor, init) of + {ok, Port} -> + {ok, State#{server_port => Port}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "monitor 'client'", + cmd => fun(#{client := Client} = _State) -> + _MRef = erlang:monitor(process, Client), + ok + end}, + #{desc => "order client start", + cmd => fun(#{client := Pid, server_port := Port} = _State) -> + ?SEV_ANNOUNCE_START(Pid, Port), + ok + end}, + #{desc => "await client ready (init)", + cmd => fun(#{client := Pid} = _State) -> + ok = ?SEV_AWAIT_READY(Pid, client, init) + end}, + + + %% *** The actual test *** + #{desc => "order server to continue (with accept)", + cmd => fun(#{server := Server} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Server, accept), + ok + end}, + ?SEV_SLEEP(?SECS(1)), + #{desc => "order client to continue (with connect)", + cmd => fun(#{client := Client} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Client, connect), + ok + end}, + #{desc => "await client ready (connect)", + cmd => fun(#{client := Client} = _State) -> + ?SEV_AWAIT_READY(Client, client, connect) + end}, + #{desc => "await server ready (accept)", + cmd => fun(#{server := Server} = _State) -> + ?SEV_AWAIT_READY(Server, server, accept) + end}, + + #{desc => "order server to continue (with recv)", + cmd => fun(#{server := Server} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Server, recv), + ok + end}, + ?SEV_SLEEP(?SECS(1)), + #{desc => "order client to continue (with send)", + cmd => fun(#{client := Client} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Client, send), + ok + end}, + #{desc => "await client ready (send)", + cmd => fun(#{client := Client} = _State) -> + ?SEV_AWAIT_READY(Client, client, send) + end}, + #{desc => "await server ready (recv)", + cmd => fun(#{server := Server} = _State) -> + ?SEV_AWAIT_READY(Server, server, recv) + end}, + + #{desc => "order client to continue (with recv)", + cmd => fun(#{client := Client} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Client, recv), + ok + end}, + ?SEV_SLEEP(?SECS(1)), + #{desc => "order server to continue (with send)", + cmd => fun(#{server := Server} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Server, send), + ok + end}, + #{desc => "await server ready (send)", + cmd => fun(#{server := Server} = _State) -> + ?SEV_AWAIT_READY(Server, server, send) + end}, + #{desc => "await client ready (recv)", + cmd => fun(#{client := Client} = _State) -> + ?SEV_AWAIT_READY(Client, client, recv) + end}, + + #{desc => "order server to continue (with await-shutdown)", + cmd => fun(#{server := Server} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Server, await_shutdown), + ok + end}, + ?SEV_SLEEP(?SECS(1)), + #{desc => "order client to continue (with eof)", + cmd => fun(#{client := Client} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Client, eof), + ok + end}, + #{desc => "await client ready (eof)", + cmd => fun(#{client := Client} = _State) -> + ?SEV_AWAIT_READY(Client, client, eof) + end}, + #{desc => "await server ready (await_shutdown)", + cmd => fun(#{server := Server} = _State) -> + ?SEV_AWAIT_READY(Server, server, await_shutdown) + end}, + + #{desc => "order server to continue (stats)", + cmd => fun(#{server := Server} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Server, stats), + ok + end}, + #{desc => "order client to continue (stats)", + cmd => fun(#{client := Client} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Client, stats), + ok + end}, + + + %% *** Termination *** + #{desc => "order client to terminate", + cmd => fun(#{client := Client} = _State) -> + ?SEV_IPRINT("announce 'terminate' to client (~p)", + [Client]), + ?SEV_ANNOUNCE_TERMINATE(Client), + ok + end}, + #{desc => "await client termination", + cmd => fun(#{client := Client} = State) -> + case ?SEV_AWAIT_TERMINATION(Client) of + ok -> + ?SEV_IPRINT("client (~p) terminated", + [Client]), + State1 = maps:remove(client, State), + {ok, State1}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "order server to terminate", + cmd => fun(#{server := Server} = _State) -> + ?SEV_IPRINT("announce 'terminate' to server (~p)", + [Server]), + ?SEV_ANNOUNCE_TERMINATE(Server), + ok + end}, + #{desc => "await server termination", + cmd => fun(#{server := Server} = State) -> + case ?SEV_AWAIT_TERMINATION(Server) of + ok -> + ?SEV_IPRINT("server (~p) terminated", + [Server]), + State1 = maps:remove(server, State), + {ok, State1}; + {error, _} = ERROR -> + ERROR + end + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + SctpEvs = ?SCTP_EVENTS(true), + + i("start server evaluator"), + Server = ?SEV_START("server", ServerSeq, InitState#{events => SctpEvs}), + + i("start client evaluator"), + Client = ?SEV_START("client", ClientSeq, InitState#{events => SctpEvs}), + + i("start tester evaluator"), + TesterInitState = #{server => Server#ev.pid, + client => Client#ev.pid}, + Tester = ?SEV_START("tester", TesterSeq, TesterInitState), + + ok = ?SEV_AWAIT_FINISH([Server, Client, Tester]). + + +accept_connection(Sock) -> + ?SEV_IPRINT("await connection message (recvmsg assoc-change:comm-up)"), + assoc_confirmation(Sock). + + +connect_confirmation(Sock) -> + ?SEV_IPRINT("await connect confirmation (recvmsg assoc-change:comm-up)"), + assoc_confirmation(Sock). + + +assoc_confirmation(Sock) -> + case socket:recvmsg(Sock) of + {ok, #{flags := Flags} = Msg} -> + ?SEV_IPRINT("message received - try verify is notification"), + case lists:member(notification, Flags) of + true -> + ?SEV_IPRINT("is notification"), + case Msg of + #{addr := Addr, + ctrl := [], + notification := + #{type := assoc_change, + state := comm_up, + assoc_id := AID, + inbound_streams := IS, + outbound_streams := OS, + '$esock_name' := sctp_notification}} -> + ?SEV_IPRINT("expected (assoc-change:comm-up) " + "notification:" + "~n Addr: ~p" + "~n AssocID: ~p", [Addr, AID]), + {ok, {Addr, #{assoc_id => AID, + inbound_streams => IS, + outbound_streams => OS}}}; + #{addr := Addr, + notification := Notif} -> + ?SEV_IPRINT("unexpected notification:" + "~n Addr: ~p" + "~n Notif: ~p", [Addr, Notif]), + {error, {unexpected_notification, Addr, Notif}}; + _ -> + ?SEV_EPRINT("unexpected message :" + "~n ~p", [Msg]), + {error, {unexpected_msg, Msg}} + end; + false -> + ?SEV_EPRINT("not notification:" + "~n ~p", [Msg]), + {error, not_notification} + end; + {error, Reason} = ERROR -> + ?SEV_EPRINT("recvmsg failed:" + "~n ~p", [Reason]), + throw(ERROR) + end. + + +%% The reason for this (function) is that on some platforms +%% its possible that another messages +%% peer-addr-change:addr-confirmation|addr-available +%% before the data message. +recv_first_data(Sock) -> + recv_first_data(Sock, false). +recv_first_data(Sock, false) -> + case socket:recvmsg(Sock) of + {ok, #{flags := _, + addr := SA, + iov := [Data], + ctrl := [#{level := sctp, + type := sndrcv, + value := #{assoc_id := AID, + stream := Stream, + tsn := TSN, + '$esock_name' := sctp_sndrcvinfo}}]}} -> + ?SEV_IPRINT("received expected message: " + "~n SA: ~p" + "~n AID: ~p" + "~n Stream: ~p" + "~n TSN: ~p", [SA, AID, Stream, TSN]), + {ok, {SA, Data, AID, Stream}}; + + {ok, #{flags := Flags} = Msg} -> + case lists:member(notification, Flags) of + true -> + case Msg of + #{addr := SA, + ctrl := [], + notification := + #{type := peer_addr_change, + state := NState, + assoc_id := AID, + '$esock_name' := sctp_notification}} + when (NState =:= addr_confirmed) orelse + (NState =:= addr_available) -> + ?SEV_IPRINT("received " + "peer-addr-change:" + "addr-confirmed|addr-available " + "message: " + "~n SA: ~p" + "~n NState: ~p" + "~n AID: ~p", + [SA, NState, AID]), + recv_data(Sock, AID); + #{addr := Addr, + notification := Notif} -> + ?SEV_EPRINT("unexpected notification:" + "~n Addr: ~p" + "~n Notif: ~p", [Addr, Notif]), + Reason = {unexpected_notification, Addr, Notif}, + Error = {error, Reason}, + throw(Error) + end; + false -> + ?SEV_EPRINT("received unexpected message: " + "~n Msg: ~p", [Msg]), + {error, unexpected_msg} + end; + {error, Reason} = ERROR -> + ?SEV_EPRINT("unexpected recv failure: " + "~n Reason: ~p", [Reason]), + ERROR + end. + + +recv_data(Sock) -> + recv_data(Sock, any). +recv_data(Sock, AID) -> + ?SEV_IPRINT("try recvmsg for AID: ~p", [AID]), + case socket:recvmsg(Sock) of + {ok, #{flags := _, + addr := SA, + iov := [Data], + ctrl := [#{level := sctp, + type := sndrcv, + value := #{assoc_id := RecvAID, + stream := Stream, + tsn := TSN, + '$esock_name' := sctp_sndrcvinfo}}]}} + when (RecvAID =:= AID) orelse (AID =:= any) -> + ?SEV_IPRINT("received expected message: " + "~n SA: ~p" + "~n AID: ~p" + "~n Stream: ~p" + "~n TSN: ~p", [SA, RecvAID, Stream, TSN]), + {ok, {SA, Data, RecvAID, Stream}}; + {ok, Msg} -> + ?SEV_EPRINT("received unexpected message: " + "~n Msg: ~p", [Msg]), + {error, unexpected_msg}; + {error, Reason} = ERROR -> + ?SEV_EPRINT("unexpected recv failure: " + "~n Reason: ~p", [Reason]), + ERROR + end. + + +await_shutdown(Sock, AID) -> + try + begin + await_shutdown_or_maybe_peer_addr_change(Sock, AID), + await_shutdown_complete(Sock, AID) + end + catch + throw:Error -> + Error + end. + +await_shutdown_or_maybe_peer_addr_change(Sock, AID) -> + await_shutdown_or_maybe_peer_addr_change(Sock, AID, false). +await_shutdown_or_maybe_peer_addr_change(Sock, AID, PAC) -> + ?SEV_IPRINT("try recv shutdown event"), + case socket:recvmsg(Sock, infinity) of + {ok, Msg} -> + case shutdown_or_maybe_peer_addr_change(Msg, AID, PAC) of + {ok, peer_addr_change} -> + await_shutdown_or_maybe_peer_addr_change(Sock, AID, true); + {ok, _} -> + ok + end; + {error, _} = ERROR -> + ERROR + end. + +shutdown_or_maybe_peer_addr_change(Msg, AID, PAC) -> + case Msg of + #{flags := Flags} -> + case lists:member(notification, Flags) of + true -> + case Msg of + #{ctrl := [], + notification := + #{type := shutdown_event, + assoc_id := AID, + '$esock_name' := sctp_notification} = Notif} -> + ?SEV_IPRINT("received expected shutdown event:" + "~n ~p", [Notif]), + {ok, shutdown_event}; + #{ctrl := [], + notification := #{type := peer_addr_change, + state := NState, + assoc_id := AID}} + when (PAC =:= false) andalso + ((NState =:= addr_confirmed) orelse + (NState =:= addr_available)) -> + ?SEV_IPRINT("received expected peer-addr-change event"), + {ok, peer_addr_change}; + #{addr := Addr, + notification := Notif} -> + ?SEV_EPRINT("unexpected notification:" + "~n Addr: ~p" + "~n Notif: ~p", [Addr, Notif]), + Reason = {unexpected_notification, Addr, Notif}, + Error = {error, Reason}, + throw(Error); + _ -> + ?SEV_EPRINT("unexpected message :" + "~n ~p", [Msg]), + throw({error, {unexpected_msg, Msg}}) + end; + false -> + ?SEV_EPRINT("not notification:" + "~n ~p", [Msg]), + throw({error, not_notification}) + end; + {error, Reason} = ERROR -> + ?SEV_EPRINT("receive error:" + "~n ~p", [Reason]), + throw(ERROR) + end. + +await_shutdown_complete(Sock, AID) -> + ?SEV_IPRINT("try recv shutdown-complete"), + case socket:recvmsg(Sock, infinity) of + {ok, #{flags := Flags} = Msg} -> + case lists:member(notification, Flags) of + true -> + case Msg of + #{ctrl := [], + notification := + #{type := assoc_change, + state := shutdown_comp, + assoc_id := AID, + '$esock_name' := sctp_notification}} -> + ?SEV_IPRINT("received expected shutdown-complete"), + ok; + #{addr := Addr, + notification := Notif} -> + ?SEV_EPRINT("unexpected notification:" + "~n Addr: ~p" + "~n Notif: ~p", [Addr, Notif]), + Reason = {unexpected_notification, Addr, Notif}, + Error = {error, Reason}, + throw(Error); + _ -> + ?SEV_EPRINT("unexpected message :" + "~n ~p", [Msg]), + throw({error, {unexpected_msg, Msg}}) + end; + false -> + ?SEV_EPRINT("not notification:" + "~n ~p", [Msg]), + throw({error, not_notification}) + + end; + + {error, closed} = ERROR -> + %% This seems to happen on old linux systems, + %% so check if this is such...if not fail + case os:type() of + {unix, linux} -> + Version = os:version(), + if + (Version > {4,4,74}) -> + ?SEV_EPRINT("socket already closed"), + throw(ERROR); + true -> + ?SEV_IPRINT("socket already closed"), + ok + end; + _ -> + ?SEV_EPRINT("socket already closed"), + throw(ERROR) + end; + + {error, Reason} = ERROR -> + ?SEV_EPRINT("receive error:" + "~n ~p", [Reason]), + throw(ERROR) + end. + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Basically open (create) a 'seqpacket' listen socket and then create +%% another 'stream' socket that connects to it and then exchange one +%% message. IPv4 SCTP socket(s). +m_stream_min_data_exchange_ipv4(_Config) when is_list(_Config) -> + ?TT(?SECS(10)), + Cond = fun() -> has_support_ipv4() end, + Pre = fun() -> + Domain = inet, + case ?WHICH_LOCAL_ADDR(Domain) of + {ok, Addr} -> + #{domain => Domain, + addr => Addr, + server_type => seqpacket, + client_type => stream, + protocol => sctp}; + {error, Reason} -> + throw({skip, Reason}) + end + end, + TC = fun(State) -> + ok = m_stream_min_data_exchange(State) + end, + Post = fun(_) -> ok end, + tc_try(?FUNCTION_NAME, Cond, Pre, TC, Post). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Basically open (create) a 'seqpacket' listen socket and then create +%% another 'stream' socket that connects to it and then exchange one +%% message. IPv6 SCTP socket(s). +m_stream_min_data_exchange_ipv6(_Config) when is_list(_Config) -> + ?TT(?SECS(10)), + Cond = fun() -> has_support_ipv6() end, + Pre = fun() -> + Domain = inet6, + case ?WHICH_LOCAL_ADDR(Domain) of + {ok, Addr} -> + #{domain => Domain, + addr => Addr, + server_type => seqpacket, + client_type => stream, + protocol => sctp}; + {error, Reason} -> + throw({skip, Reason}) + end + end, + TC = fun(State) -> + ok = m_stream_min_data_exchange(State) + end, + Post = fun(_) -> ok end, + tc_try(?FUNCTION_NAME, Cond, Pre, TC, Post). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +m_stream_min_data_exchange(InitState) -> + process_flag(trap_exit, true), + + ServerSeq = + [ + %% *** Wait for start order part *** + #{desc => "await start (from tester)", + cmd => fun(State) -> + Tester = ?SEV_AWAIT_START(), + {ok, State#{tester => Tester}} + end}, + #{desc => "monitor tester", + cmd => fun(#{tester := Tester} = _State) -> + _MRef = erlang:monitor(process, Tester), + ok + end}, + + %% *** Init part *** + #{desc => "create (listen) socket", + cmd => fun(#{domain := Domain, + type := Type, + protocol := Proto} = State) -> + case socket:open(Domain, Type, Proto) of + {ok, Sock} -> + {ok, State#{sock => Sock}}; + {error, epfnosupport = Reason} -> + {skip, Reason}; + {error, eprotonosupport = Reason} -> + {skip, Reason}; + {error, esocktnosupport = Reason} -> + {skip, Reason}; + {error, Reason} = ERROR -> + ?SEV_EPRINT("Failed open socket: " + "~n ~p", [Reason]), + ERROR + end + end}, + #{desc => "bind to local address", + cmd => fun(#{sock := Sock, + domain := Domain, + addr := Addr} = State) -> + LSA = #{family => Domain, + addr => Addr, + port => 0}, + case socket:bind(Sock, LSA) of + ok -> + case socket:sockname(Sock) of + {ok, #{port := Port}} -> + ?SEV_IPRINT("bound to port: ~w", + [Port]), + {ok, State#{port => Port}}; + {error, _} = ERROR -> + ERROR + end; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "subscribe to sctp events", + cmd => fun(#{sock := Sock, + events := Evs}) -> + socket:setopt(Sock, sctp, events, Evs) + end}, + #{desc => "make listen socket", + cmd => fun(#{sock := Sock}) -> + socket:listen(Sock, true) + end}, + #{desc => "announce ready (init)", + cmd => fun(#{tester := Tester, port := Port}) -> + ?SEV_ANNOUNCE_READY(Tester, init, Port), + ok + end}, + + + %% The actual test + #{desc => "await continue (accept connection)", + cmd => fun(#{tester := Tester}) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, accept) + end}, + #{desc => "accept connection: (recvmsg of assoc-change:comm-up)", + cmd => fun(#{sock := Sock} = State) -> + case accept_connection(Sock) of + {ok, {_, Assoc}} -> + {ok, State#{assoc => Assoc}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (accept)", + cmd => fun(#{tester := Tester, assoc := Assoc}) -> + ?SEV_IPRINT("announce assoc accepted: " + "~n Assoc: ~p", [Assoc]), + #{inbound_streams := IS, + outbound_streams := OS} = Assoc, + ?SEV_ANNOUNCE_READY(Tester, accept, {IS, OS}), + ok + end}, + + + %% GET SCTP-STATUS + #{desc => "await continue (status)", + cmd => fun(#{tester := Tester}) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, status) + end}, + #{desc => "status", + cmd => fun(#{sock := Sock, + assoc := #{assoc_id := AID} = _Assoc} = _State) -> + ?SEV_IPRINT("try get status"), + SockOpt = ?MK_SCTP_SOCKOPT(status), + SparseStatus = #{assoc_id => AID}, + case socket:getopt(Sock, SockOpt, SparseStatus) of + {ok, Status} -> + ?SEV_IPRINT("status:" + "~n ~p", [Status]), + ok; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (status)", + cmd => fun(#{tester := Tester}) -> + ?SEV_IPRINT("announce status"), + ?SEV_ANNOUNCE_READY(Tester, status), + ok + end}, + + + #{desc => "await continue (send message)", + cmd => fun(#{tester := Tester}) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, send) + end}, + #{desc => "send (initial) message", + cmd => fun(#{sock := Sock, + assoc := #{assoc_id := AID}} = _State) -> + ?SEV_IPRINT("build sparse SRI " + "(only assoc-id and stream)"), + SRI = #{assoc_id => AID, stream => 0}, + Msg = #{iov => [?DATA], + ctrl => [#{level => sctp, + type => sndrcv, + value => SRI}]}, + case socket:sendmsg(Sock, Msg) of + ok -> + ?SEV_IPRINT("msg sent"), + ok; + {error, Reason} = ERROR -> + ?SEV_EPRINT("Failed send msg:" + "~n ~p", [Reason]), + ERROR + end + end}, + #{desc => "announce ready (send)", + cmd => fun(#{tester := Tester}) -> + ?SEV_IPRINT("announce message sent"), + ?SEV_ANNOUNCE_READY(Tester, send), + ok + end}, + + #{desc => "await continue (recv)", + cmd => fun(#{tester := Tester}) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, recv) + end}, + #{desc => "await data (recvmsg)", + cmd => fun(#{sock := Sock, + assoc := #{assoc_id := AID} = _Assoc} = _State) -> + case recv_first_data(Sock) of + {ok, {_SA, ?DATA, AID, 0}} -> + ?SEV_IPRINT("expected data received"), + ok; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (recv)", + cmd => fun(#{tester := Tester}) -> + ?SEV_IPRINT("announce recv"), + ?SEV_ANNOUNCE_READY(Tester, recv), + ok + end}, + + + %% GET SCTP-STATUS + #{desc => "await continue (status)", + cmd => fun(#{tester := Tester}) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, status) + end}, + #{desc => "status", + cmd => fun(#{sock := Sock, + assoc := #{assoc_id := AID} = _Assoc} = _State) -> + ?SEV_IPRINT("try get status"), + SockOpt = ?MK_SCTP_SOCKOPT(status), + SparseStatus = #{assoc_id => AID}, + case socket:getopt(Sock, SockOpt, SparseStatus) of + {ok, Status} -> + ?SEV_IPRINT("status:" + "~n ~p", [Status]), + ok; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (status)", + cmd => fun(#{tester := Tester}) -> + ?SEV_IPRINT("announce status"), + ?SEV_ANNOUNCE_READY(Tester, status), + ok + end}, + + #{desc => "await continue (stats)", + cmd => fun(#{tester := Tester}) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, stats) + end}, + #{desc => "statistics", + cmd => fun(#{sock := Sock} = _State) -> + #{counters := Counters} = socket:info(Sock), + ?SEV_IPRINT("Counters: " + "~n ~p", [Counters]), + ok + end}, + #{desc => "announce ready (stats)", + cmd => fun(#{tester := Tester}) -> + ?SEV_IPRINT("announce status"), + ?SEV_ANNOUNCE_READY(Tester, stats), + ok + end}, + + + #{desc => "await continue (await_shutdown)", + cmd => fun(#{tester := Tester}) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, await_shutdown) + end}, + #{desc => "await shutdown", + cmd => fun(#{sock := Sock, + assoc := #{assoc_id := AID}} = State) -> + case await_shutdown(Sock, AID) of + ok -> + {ok, maps:remove(assoc, State)}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (await_shutdown)", + cmd => fun(#{tester := Tester}) -> + ?SEV_IPRINT("announce shutdown received"), + ?SEV_ANNOUNCE_READY(Tester, await_shutdown), + ok + end}, + + #{desc => "await continue (stats)", + cmd => fun(#{tester := Tester}) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, stats) + end}, + #{desc => "statistics", + cmd => fun(#{sock := Sock} = _State) -> + #{counters := Counters} = socket:info(Sock), + ?SEV_IPRINT("Counters: " + "~n ~p", [Counters]), + ok + end}, + #{desc => "announce ready (stats)", + cmd => fun(#{tester := Tester}) -> + ?SEV_IPRINT("announce stats"), + ?SEV_ANNOUNCE_READY(Tester, stats), + ok + end}, + + + %% *** Terminate *** + #{desc => "await terminate", + cmd => fun(#{tester := Tester} = State) -> + case ?SEV_AWAIT_TERMINATE(Tester, tester) of + ok -> + {ok, maps:remove(tester, State)}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "close (server) socket", + cmd => fun(#{sock := Sock} = State) -> + case socket:close(Sock) of + ok -> + State1 = maps:remove(sock, State), + State2 = maps:remove(port, State1), + {ok, State2}; + {error, _} = ERROR -> + ERROR + end + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + ClientSeq = + [ + %% *** Wait for start order part *** + #{desc => "await start (from tester)", + cmd => fun(State) -> + {Tester, ServerPort} = ?SEV_AWAIT_START(), + ?SEV_IPRINT("starting with" + "~n ServerPort: ~w", [ServerPort]), + {ok, State#{tester => Tester, + server_port => ServerPort}} + end}, + #{desc => "monitor tester", + cmd => fun(#{tester := Tester} = _State) -> + _MRef = erlang:monitor(process, Tester), + ok + end}, + + %% *** Init part *** + #{desc => "create socket", + cmd => fun(#{domain := Domain, + type := Type, + protocol := Proto} = State) -> + case socket:open(Domain, Type, Proto) of + {ok, Sock} -> + {ok, State#{sock => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "bind to local address", + cmd => fun(#{sock := Sock, + domain := Domain, + addr := Addr} = State) -> + LSA = #{family => Domain, + addr => Addr, + port => 0}, + case socket:bind(Sock, LSA) of + ok -> + case socket:sockname(Sock) of + {ok, #{port := Port}} -> + ?SEV_IPRINT("bound to port: ~w", + [Port]), + {ok, State#{port => Port}}; + {error, _} = ERROR -> + ERROR + end; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "subscribe to sctp events", + cmd => fun(#{sock := Sock, + events := Evs}) -> + socket:setopt(Sock, sctp, events, Evs) + end}, + #{desc => "announce ready (init)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, init), + ok + end}, + + + %% The actual test + #{desc => "await continue (connect)", + cmd => fun(#{tester := Tester}) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, connect) + end}, + #{desc => "accept connection: (recvmsg of assoc-change:comm-up)", + cmd => fun(#{sock := Sock, + domain := Domain, + addr := Addr, + server_port := Port} = _State) -> + ServerSA = #{family => Domain, + addr => Addr, + port => Port}, + case socket:connect(Sock, ServerSA) of + ok -> + ?SEV_IPRINT("connected"), + ok; + {error, Reason} = ERROR -> + ?SEV_EPRINT("Failed connect:" + "~n ~p", [Reason]), + ERROR + end + end}, + #{desc => "await connect confirmation: (recvmsg of assoc-change:comm-up)", + cmd => fun(#{sock := Sock} = State) -> + case connect_confirmation(Sock) of + {ok, {_, Assoc}} -> + {ok, State#{assoc => Assoc}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "try verify assoc (peer-addr-info)", + cmd => fun(#{sock := Sock, + domain := Domain, + protocol := Proto, + addr := Addr, + server_port := Port, + assoc := #{assoc_id := AID}} = State) -> + ServerSA = #{family => Domain, + addr => Addr, + port => Port}, + ?SEV_IPRINT("try get peer-addr-info"), + %% Create sparse peer-addr-info + OptValue = #{assoc_id => AID, + addr => ServerSA}, + SockOpt = {Proto, get_peer_addr_info}, + case socket:getopt(Sock, SockOpt, OptValue) of + {ok, #{assoc_id := PAID, + state := active}} -> + ?SEV_IPRINT("try verify assoc-id"), + match_unless_solaris(AID, PAID); + {ok, #{assoc_id := PAID, + state := State}} -> + ?SEV_EPRINT("invalid assoc state:" + "~n AID: ~p" + "~n State: ~p", + [PAID, State]), + Reason = {invalid_assoc_state, State}, + {error, Reason}; + {error, Reason0} -> + ?SEV_EPRINT("failed get " + "peer-addr-info:" + "~n ~p", [Reason0]), + Reason = + {failed_get_peer_addr_info, Reason0}, + {error, Reason} + end + end}, + #{desc => "announce ready (connect)", + cmd => fun(#{tester := Tester, assoc := Assoc}) -> + ?SEV_IPRINT("announce connected: " + "~n Assoc: ~p", [Assoc]), + #{inbound_streams := IS, + outbound_streams := OS} = Assoc, + ?SEV_ANNOUNCE_READY(Tester, connect, {IS, OS}), + ok + end}, + + + %% GET SCTP-STATUS + #{desc => "await continue (status)", + cmd => fun(#{tester := Tester}) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, status) + end}, + #{desc => "status", + cmd => fun(#{sock := Sock, + assoc := #{assoc_id := _AID} = _Assoc} = _State) -> + case socket:getopt(Sock, sctp, status) of + {ok, Status} -> + ?SEV_IPRINT("status:" + "~n ~p", [Status]), + ok; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (status)", + cmd => fun(#{tester := Tester}) -> + ?SEV_IPRINT("announce status"), + ?SEV_ANNOUNCE_READY(Tester, status), + ok + end}, + + + #{desc => "await continue (recv initial message)", + cmd => fun(#{tester := Tester}) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, recv) + end}, + #{desc => "await first data (recvmsg)", + cmd => fun(#{sock := Sock, + assoc := #{assoc_id := AID} = _Assoc} = State) -> + case recv_first_data(Sock) of + {ok, {_SA, ?DATA, AID, Stream}} -> + ?SEV_IPRINT("expected data received on" + "~n Stream: ~p", [Stream]), + {ok, State#{stream => Stream}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (recv)", + cmd => fun(#{tester := Tester}) -> + ?SEV_IPRINT("announce recv"), + ?SEV_ANNOUNCE_READY(Tester, recv), + ok + end}, + + #{desc => "await continue (send message)", + cmd => fun(#{tester := Tester}) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, send) + end}, + #{desc => "send data back from other process", + cmd => fun(#{sock := Sock, + assoc := #{assoc_id := AID}} = _State) -> + Sender = + fun() -> + ?SEV_IPRINT("build sparse SRI " + "(only assoc-id and " + "stream)"), + SRI = #{assoc_id => AID, + stream => 0}, + Msg = #{iov => [?DATA], + ctrl => [#{level => sctp, + type => sndrcv, + value => SRI}]}, + case socket:sendmsg(Sock, Msg) of + ok -> + ?SEV_IPRINT("msg sent"), + ok; + {error, Reason} = ERROR -> + ?SEV_EPRINT("Failed send msg:" + "~n ~p", [Reason]), + ERROR + end + end, + do_from_other_process(Sender) + end}, + #{desc => "announce ready (send)", + cmd => fun(#{tester := Tester}) -> + ?SEV_IPRINT("announce message sent"), + ?SEV_ANNOUNCE_READY(Tester, send), + ok + end}, + + + %% GET SCTP-STATUS + #{desc => "await continue (status)", + cmd => fun(#{tester := Tester}) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, status) + end}, + #{desc => "status", + cmd => fun(#{sock := Sock, + assoc := #{assoc_id := _AID} = _Assoc} = _State) -> + case socket:getopt(Sock, sctp, status) of + {ok, Status} -> + ?SEV_IPRINT("status:" + "~n ~p", [Status]), + ok; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (status)", + cmd => fun(#{tester := Tester}) -> + ?SEV_IPRINT("announce status"), + ?SEV_ANNOUNCE_READY(Tester, status), + ok + end}, + + #{desc => "await continue (stats)", + cmd => fun(#{tester := Tester}) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, stats) + end}, + #{desc => "statistics", + cmd => fun(#{sock := Sock} = _State) -> + #{counters := Counters} = socket:info(Sock), + ?SEV_IPRINT("Counters: " + "~n ~p", [Counters]), + ok + end}, + #{desc => "announce ready (stats)", + cmd => fun(#{tester := Tester}) -> + ?SEV_IPRINT("announce stats"), + ?SEV_ANNOUNCE_READY(Tester, stats), + ok + end}, + + %% *** Terminate *** + #{desc => "await terminate", + cmd => fun(#{tester := Tester} = State) -> + case ?SEV_AWAIT_TERMINATE(Tester, tester) of + ok -> + ?SEV_IPRINT("received termination " + "from tester"), + {ok, maps:remove(tester, State)}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "close socket", + cmd => fun(#{sock := Sock} = State0) -> + ?SEV_IPRINT("try close socket"), + case socket:close(Sock) of + ok -> + State1 = maps:remove(sock, State0), + State2 = maps:remove(port, State1), + State3 = maps:remove(server_port, State2), + ?SEV_IPRINT("socket closed and " + "cleanup done"), + {ok, State3}; + {error, _} = ERROR -> + ERROR + end + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + TesterSeq = + [ + %% *** Init part *** + #{desc => "monitor 'server'", + cmd => fun(#{server := Server} = _State) -> + _MRef = erlang:monitor(process, Server), + ok + end}, + #{desc => "order server start", + cmd => fun(#{server := Pid} = _State) -> + ?SEV_ANNOUNCE_START(Pid), + ok + end}, + #{desc => "await (server) ready", + cmd => fun(#{server := Pid} = State) -> + case ?SEV_AWAIT_READY(Pid, acceptor, init) of + {ok, Port} -> + {ok, State#{server_port => Port}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "monitor 'client'", + cmd => fun(#{client := Client} = _State) -> + _MRef = erlang:monitor(process, Client), + ok + end}, + #{desc => "order client start", + cmd => fun(#{client := Pid, server_port := Port} = _State) -> + ?SEV_ANNOUNCE_START(Pid, Port), + ok + end}, + #{desc => "await client ready (init)", + cmd => fun(#{client := Pid} = _State) -> + ok = ?SEV_AWAIT_READY(Pid, client, init) + end}, + + + %% *** The actual test *** + #{desc => "order server to continue (with accept)", + cmd => fun(#{server := Server} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Server, accept), + ok + end}, + ?SEV_SLEEP(?SECS(1)), + #{desc => "order client to continue (with connect)", + cmd => fun(#{client := Client} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Client, connect), + ok + end}, + #{desc => "await client ready (connect)", + cmd => fun(#{client := Client} = State) -> + {ok, {IS, OS}} = + ?SEV_AWAIT_READY(Client, client, connect), + ?SEV_IPRINT("client streams:" + "~n Inbound: ~w" + "~n Outbound: ~w", [IS, OS]), + {ok, State#{client_is => IS, + client_os => OS}} + + + end}, + #{desc => "await server ready (accept)", + cmd => fun(#{server := Server} = State) -> + {ok, {IS, OS}} = + ?SEV_AWAIT_READY(Server, server, accept), + ?SEV_IPRINT("server streams:" + "~n Inbound: ~w" + "~n Outbound: ~w", [IS, OS]), + {ok, State#{server_is => IS, + server_os => OS}} + end}, + + #{desc => "verify streams", + cmd => fun(#{server_is := SIS, + server_os := SOS, + client_is := CIS, + client_os := COS} = _State) -> + if + (SOS =:= CIS) andalso (SIS =:= COS) -> + ?SEV_IPRINT("Streams verified"), + ok; + (SOS =:= CIS) -> + ?SEV_EPRINT("Stream verification failed: " + "Server inbound not equal to Client outbound" + "~n ~w =/= ~w", [SIS, COS]), + {error, stream_verification_failed}; + (SIS =:= COS) -> + ?SEV_EPRINT("Stream verification failed: " + "Server outbound not equal to Client inbound" + "~n ~w =/= ~w", [SOS, CIS]), + {error, stream_verification_failed}; + true -> + ?SEV_EPRINT("Stream verification failed: " + "~n Server Inbound: ~w" + "~n Client Outbound: ~w" + "~n Server Outbound: ~w" + "~n Client Inbound: ~w", + [SIS, COS, SOS, CIS]), + {error, stream_verification_failed} + end + end}, + + + #{desc => "order client to continue (with status)", + cmd => fun(#{client := Client} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Client, status), + ok + end}, + #{desc => "await client ready (status)", + cmd => fun(#{client := Client} = _State) -> + ?SEV_AWAIT_READY(Client, client, status) + end}, + + #{desc => "order server to continue (with status)", + cmd => fun(#{server := Server} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Server, status), + ok + end}, + #{desc => "await server ready (status)", + cmd => fun(#{server := Server} = _State) -> + ?SEV_AWAIT_READY(Server, server, status) + end}, + + + #{desc => "order client to continue (with recv)", + cmd => fun(#{client := Client} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Client, recv), + ok + end}, + ?SEV_SLEEP(?SECS(1)), + #{desc => "order server to continue (with send)", + cmd => fun(#{server := Server} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Server, send), + ok + end}, + #{desc => "await server ready (send)", + cmd => fun(#{server := Server} = _State) -> + ?SEV_AWAIT_READY(Server, server, send) + end}, + #{desc => "await client ready (recv)", + cmd => fun(#{client := Client} = _State) -> + ?SEV_AWAIT_READY(Client, client, recv) + end}, + + #{desc => "order server to continue (with recv)", + cmd => fun(#{server := Server} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Server, recv), + ok + end}, + ?SEV_SLEEP(?SECS(1)), + #{desc => "order client to continue (with send)", + cmd => fun(#{client := Client} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Client, send), + ok + end}, + #{desc => "await client ready (send)", + cmd => fun(#{client := Client} = _State) -> + ?SEV_AWAIT_READY(Client, client, send) + end}, + #{desc => "await sever ready (recv)", + cmd => fun(#{server := Server} = _State) -> + ?SEV_AWAIT_READY(Server, server, recv) + end}, + + + #{desc => "order client to continue (with status)", + cmd => fun(#{client := Client} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Client, status), + ok + end}, + #{desc => "await client ready (status)", + cmd => fun(#{client := Client} = _State) -> + ?SEV_AWAIT_READY(Client, client, status) + end}, + + #{desc => "order server to continue (with status)", + cmd => fun(#{server := Server} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Server, status), + ok + end}, + #{desc => "await server ready (status)", + cmd => fun(#{server := Server} = _State) -> + ?SEV_AWAIT_READY(Server, server, status) + end}, + + + #{desc => "order server to continue (stats)", + cmd => fun(#{server := Server} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Server, stats), + ok + end}, + #{desc => "await server ready (stats)", + cmd => fun(#{server := Server} = _State) -> + ?SEV_AWAIT_READY(Server, server, stats) + end}, + + #{desc => "order client to continue (stats)", + cmd => fun(#{client := Client} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Client, stats), + ok + end}, + #{desc => "await client ready (stats)", + cmd => fun(#{client := Client} = _State) -> + ?SEV_AWAIT_READY(Client, client, stats) + end}, + + + #{desc => "order server to continue (with await-shutdown)", + cmd => fun(#{server := Server} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Server, await_shutdown), + ok + end}, + ?SEV_SLEEP(?SECS(1)), + #{desc => "order client to terminate", + cmd => fun(#{client := Client} = _State) -> + ?SEV_IPRINT("order client (~p) to 'terminate'", + [Client]), + ?SEV_ANNOUNCE_TERMINATE(Client), + ok + end}, + #{desc => "await client termination", + cmd => fun(#{client := Client} = State) -> + case ?SEV_AWAIT_TERMINATION(Client) of + ok -> + ?SEV_IPRINT("client (~p) terminated", + [Client]), + State1 = maps:remove(client, State), + {ok, State1}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "await server ready (await_shutdown)", + cmd => fun(#{server := Server} = _State) -> + ?SEV_AWAIT_READY(Server, server, await_shutdown) + end}, + + #{desc => "order server to continue (stats)", + cmd => fun(#{server := Server} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Server, stats), + ok + end}, + #{desc => "await server ready (stats)", + cmd => fun(#{server := Server} = _State) -> + ?SEV_AWAIT_READY(Server, server, stats) + end}, + + + %% *** Termination *** + #{desc => "order server to terminate", + cmd => fun(#{server := Server} = _State) -> + ?SEV_IPRINT("order server (~p) to 'terminate'", + [Server]), + ?SEV_ANNOUNCE_TERMINATE(Server), + ok + end}, + #{desc => "await server termination", + cmd => fun(#{server := Server} = State) -> + case ?SEV_AWAIT_TERMINATION(Server) of + ok -> + ?SEV_IPRINT("server (~p) terminated", + [Server]), + State1 = maps:remove(server, State), + {ok, State1}; + {error, _} = ERROR -> + ERROR + end + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + %% Only for seqpacket? + SctpEvs = ?SCTP_EVENTS(true), + + i("start server evaluator"), + ServerState = #{events => SctpEvs, + domain => maps:get(domain, InitState), + type => maps:get(server_type, InitState), + protocol => maps:get(protocol, InitState), + addr => maps:get(addr, InitState)}, + Server = ?SEV_START("server", ServerSeq, ServerState), + + i("start client evaluator"), + ClientState = #{events => SctpEvs, + domain => maps:get(domain, InitState), + type => maps:get(client_type, InitState), + protocol => maps:get(protocol, InitState), + addr => maps:get(addr, InitState)}, + Client = ?SEV_START("client", ClientSeq, ClientState), + + i("start tester evaluator"), + TesterInitState = #{server => Server#ev.pid, + client => Client#ev.pid}, + Tester = ?SEV_START("tester", TesterSeq, TesterInitState), + + ok = ?SEV_AWAIT_FINISH([Server, Client, Tester]). + + +match_unless_solaris(A, B) -> + case os:type() of + {ok, sunos} -> + ?SEV_IPRINT("skip assoc-id verification: " + "~n ~w, ~w", [A, B]), + ok; + _ -> + if + (A =:= B) -> + ?SEV_IPRINT("assoc-id (~w) verified", [A]), + ok; + true -> + ?SEV_IPRINT("assoc-id verification failed:" + "~n ~w =/= ~w", [A, B]), + {error, {assoc_id_verification_failed, A, B}} + end + end. + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Basic multihoming + +m_multihoming_ipv4(_Config) when is_list(_Config) -> + ?TT(?SECS(5)), + Cond = fun() -> has_support_ipv4() end, + Pre = fun() -> + Domain = inet, + case get_addrs_by_family(Domain, 2) of + {ok, Addrs} -> + #{domain => Domain, + addrs => lists:sort(Addrs), + type => seqpacket, + protocol => sctp}; + {error, Reason} -> + throw({skip, Reason}) + end + end, + TC = fun(State) -> + ok = m_xhoming(State) + end, + Post = fun(_) -> ok end, + tc_try(?FUNCTION_NAME, Cond, Pre, TC, Post). + + +m_xhoming(#{domain := inet_and_inet6 = Domain, + addrs := [[Addr41, Addr42], [Addr61, Addr62]], + type := seqpacket = _Type, + protocol := sctp = _Proto}) -> + ?P("~s -> entry with" + "~n Domain: ~p" + "~n Addr41: ~p" + "~n Addr42: ~p" + "~n Addr61: ~p" + "~n Addr62: ~p", [?FUNCTION_NAME, + Domain, Addr41, Addr42, Addr61, Addr62]), + %% Connect to the first address to test bind + ?P("~s -> connect to first addr to test bind", [?FUNCTION_NAME]), + basic_open_and_connect([Addr41, Addr61, Addr42], Addr41), + basic_open_and_connect([Addr61, Addr41], Addr61), + + %% Connect an address, not the first, to test bindx + ?P("~s -> connect to addr to test bindx", [?FUNCTION_NAME]), + basic_open_and_connect([Addr61, Addr62, Addr41], Addr62), + basic_open_and_connect([Addr61, Addr62, Addr41], Addr41), + ?P("~s -> done", [?FUNCTION_NAME]), + ok; +m_xhoming(#{domain := Domain, + addrs := [Addr|_] = Addrs, + type := seqpacket = _Type, + protocol := sctp = _Proto}) + when (Domain =:= inet) orelse (Domain =:= inet6) -> + basic_open_and_connect(Addrs, Addr). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +m_multihoming_ipv6(_Config) when is_list(_Config) -> + ?TT(?SECS(5)), + Cond = fun() -> has_support_ipv6() end, + Pre = fun() -> + Domain = inet6, + case get_addrs_by_family(Domain, 2) of + {ok, Addrs} -> + #{domain => Domain, + addrs => Addrs, + type => seqpacket, + protocol => sctp}; + {error, Reason} -> + throw({skip, Reason}) + end + end, + TC = fun(State) -> + ok = m_xhoming(State) + end, + Post = fun(_) -> ok end, + tc_try(?FUNCTION_NAME, Cond, Pre, TC, Post). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +m_unihoming_ipv6(_Config) when is_list(_Config) -> + ?TT(?SECS(5)), + Cond = fun() -> has_support_ipv6() end, + Pre = fun() -> + Domain = inet6, + case get_addrs_by_family(Domain, 1) of + {ok, Addrs} -> + #{domain => Domain, + addrs => Addrs, + type => seqpacket, + protocol => sctp}; + {error, Reason} -> + throw({skip, Reason}) + end + end, + TC = fun(State) -> + ok = m_xhoming(State) + end, + Post = fun(_) -> ok end, + tc_try(?FUNCTION_NAME, Cond, Pre, TC, Post). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +m_multihoming_ipv4_and_ipv6(_Config) when is_list(_Config) -> + ?TT(?SECS(5)), + Cond = fun() -> + has_support_ipv4(), + has_support_ipv6() + end, + Pre = fun() -> + Domain = inet_and_inet6, + case get_addrs_by_family(Domain, 2) of + {ok, Addrs} -> + #{domain => Domain, + addrs => Addrs, + type => seqpacket, + protocol => sctp}; + {error, Reason} -> + throw({skip, Reason}) + end + end, + TC = fun(State) -> + ok = m_xhoming(State) + end, + Post = fun(_) -> ok end, + tc_try(?FUNCTION_NAME, Cond, Pre, TC, Post). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Testing the peeloff function. +%% IPv4 SCTP socket(s). +m_peeloff_ipv4(_Config) when is_list(_Config) -> + ?TT(?SECS(10)), + Cond = fun() -> + %% has_support_socket_priority(), + has_support_sctp_nodelay(), + has_support_socket_linger(), + has_support_ipv4() + end, + Pre = fun() -> + Domain = inet, + Evs = ?SCTP_EVENTS(true), + case ?WHICH_LOCAL_ADDR(Domain) of + {ok, Addr} -> + #{domain => Domain, + addr => Addr, + events => Evs}; + {error, Reason} -> + throw({skip, Reason}) + end + end, + TC = fun(State) -> + ok = m_peeloff(State) + end, + Post = fun(_) -> ok end, + tc_try(?FUNCTION_NAME, Cond, Pre, TC, Post). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Testing the peeloff function. +%% IPv6 SCTP socket(s). +m_peeloff_ipv6(_Config) when is_list(_Config) -> + ?TT(?SECS(10)), + Cond = fun() -> + %% has_support_socket_priority(), + has_support_sctp_nodelay(), + has_support_socket_linger(), + has_support_ipv6() + end, + Pre = fun() -> + Domain = inet6, + Evs = ?SCTP_EVENTS(true), + case ?WHICH_LOCAL_ADDR(Domain) of + {ok, Addr} -> + #{domain => Domain, + addr => Addr, + events => Evs}; + {error, Reason} -> + throw({skip, Reason}) + end + end, + TC = fun(State) -> + ok = m_peeloff(State) + end, + Post = fun(_) -> ok end, + tc_try(?FUNCTION_NAME, Cond, Pre, TC, Post). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +-define(MPO_REQUEST(Pid, Ref, REQ), + {?MODULE, Pid, {request, Ref, REQ}}). +-define(MPO_REPLY(Pid, Ref, REPLY), + {?MODULE, Pid, {reply, Ref, REPLY}}). +-define(MPO_MSG(Pid, MSG), + {?MODULE, Pid, {msg, MSG}}). + +mpo_call({Pid, _}, Req) when is_pid(Pid) -> + mpo_call(Pid, Req); +mpo_call(Pid, Req) when is_pid(Pid) -> + Ref = erlang:make_ref(), + Pid ! ?MPO_REQUEST(self(), Ref, Req), + receive + ?MPO_REPLY(Pid, Ref, Reply) -> + Reply + end. + +mpo_cast(Pid, Info) -> + Pid ! ?MPO_MSG(self(), Info), + ok. + +m_peeloff(#{domain := Domain, + addr := Addr} = State) -> + Stream = 3, + Timeout = 501, + %% On some platforms, 'socket:priority' is not supported, + %% so only include it on platforms that support it. + InheritOpts = + case is_supported_socket_priority() of + true -> + ?P("~s -> socket:priority *supported* - include in inherit test", [?FUNCTION_NAME]), + [{{socket, priority}, 3}]; + false -> + ?P("~s -> socket:priority *not* supported - exclude from inherit test", [?FUNCTION_NAME]), + [] + end ++ [{{sctp, nodelay}, true}, + {{socket, linger}, #{onoff => true, + linger => 7}}], + InheritSockOpts = [SockOpt || {SockOpt, _} <- InheritOpts], + + ?P("~s -> try start server", [?FUNCTION_NAME]), + {{Server, SMRef} = ServerInfo, SPort} = + m_peeloff_server_start(State), + ?P("~s -> try start client", [?FUNCTION_NAME]), + {{Client, CMRef} = ClientInfo, CPort, CAID} = + m_peeloff_client_start(State, SPort), + ?P("~s -> server and client started: " + "~n Server Info: ~p" + "~n Server Port: ~p" + "~n Client Info: ~p" + "~n Client Port: ~p" + "~n Client AID: ~p", + [?FUNCTION_NAME, + ServerInfo, SPort, + ClientInfo, CPort, CAID]), + + %% Client started implies connected to the server, + %% but we also need (accept-) confirmation from the server. + ?P("~s -> await server accept notification", [?FUNCTION_NAME]), + SAID = + receive + {'DOWN', SMRef, process, Server, Reason1} -> + ?P("~s -> received unexpected DOWN from server: " + "~n ~p", [?FUNCTION_NAME]), + mpo_cast(Client, terminate), + exit({server_failed, Reason1}); + + {'DOWN', CMRef, process, Client, Reason2} -> + ?P("~s -> received unexpected DOWN from client: " + "~n ~p", [?FUNCTION_NAME]), + mpo_cast(Server, terminate), + exit({client_failed, Reason2}); + + ?MPO_MSG(Server, {accepted, SAID0}) -> + ?P("~s -> expected (accept) response from server: " + "~n AssocID: ~p", [?FUNCTION_NAME, SAID0]), + SAID0; + + ?MPO_MSG(Pid1, Msg1) -> + ?P("~s -> unexpected message: " + "~n From: ~p" + "~n Msg: ~p", [?FUNCTION_NAME, Pid1, Msg1]), + exit({unexpected_msg, Pid1, Msg1}) + + after Timeout -> + ?P("~s -> unexpected timeout", [?FUNCTION_NAME]), + mpo_cast(Client, terminate), + mpo_cast(Server, terminate), + exit({timeout, server_accepted}) + end, + + + %% ================== + %% Message Transfer: client -> server -> ack to us from server + %% Instruct the client to send a message and wait for the server + %% pass it to us (when it gets it). + ?P("~s -> issue 'send' to client", [?FUNCTION_NAME]), + case mpo_call(Client, {send, Stream, ?DATA}) of + ok -> + ok; + {error, Reason3a} -> + ?P("~s -> (handler) send request failed: " + "~n ~p", [?FUNCTION_NAME, Reason3a]), + mpo_cast(Client, terminate), + mpo_cast(Server, terminate), + exit({client_send_request_failed, Reason3a}) + end, + ?P("~s -> await (received-) message from server", [?FUNCTION_NAME]), + receive + {'DOWN', SMRef, process, Server, Reason4a} -> + ?P("~s -> received unexpected DOWN from server: " + "~n ~p", [?FUNCTION_NAME, Reason4a]), + mpo_cast(Client, terminate), + exit({server_failed, Reason4a}); + + {'DOWN', CMRef, process, Client, Reason5a} -> + ?P("~s -> received unexpected DOWN from client: " + "~n ~p", [?FUNCTION_NAME, Reason5a]), + mpo_cast(Server, terminate), + exit({client_failed, Reason5a}); + + ?MPO_MSG(Server, {received, {data, {SAID, Stream, ?DATA}}}) -> + ?P("~s -> expected (received-) response from server", + [?FUNCTION_NAME]), + ok; + + ?MPO_MSG(Pid2a, Msg2a) -> + ?P("~s -> unexpected message: " + "~n From: ~p" + "~n Msg: ~p", [?FUNCTION_NAME, Pid2a, Msg2a]), + exit({unexpected_msg, Pid2a, Msg2a}) + + after Timeout -> + ?P("~s -> unexpected timeout", [?FUNCTION_NAME]), + mpo_cast(Client, terminate), + mpo_cast(Server, terminate), + exit({timeout, client_send_completion}) + end, + + + + %% ================== + %% Message Transfer: server -> client -> ack to us from client + %% Instruct the server to send a message and wait for the client + %% pass it to us (when it gets it). + ?P("~s -> issue 'send' to server", [?FUNCTION_NAME]), + case mpo_call(Server, {send, Stream, ?DATA}) of + ok -> + ok; + {error, Reason3b} -> + ?P("~s -> (handler) send request failed: " + "~n ~p", [?FUNCTION_NAME, Reason3b]), + mpo_cast(Client, terminate), + mpo_cast(Server, terminate), + exit({client_send_request_failed, Reason3b}) + end, + ?P("~s -> await (received-) message from server", [?FUNCTION_NAME]), + receive + {'DOWN', SMRef, process, Server, Reason4b} -> + ?P("~s -> received unexpected DOWN from server: " + "~n ~p", [?FUNCTION_NAME, Reason4b]), + mpo_cast(Client, terminate), + exit({server_failed, Reason4b}); + + {'DOWN', CMRef, process, Client, Reason5b} -> + ?P("~s -> received unexpected DOWN from client: " + "~n ~p", [?FUNCTION_NAME, Reason5b]), + mpo_cast(Server, terminate), + exit({client_failed, Reason5b}); + + ?MPO_MSG(Client, {received, {data, {CAID, Stream, ?DATA}}}) -> + ?P("~s -> expected (received-) response from client", + [?FUNCTION_NAME]), + ok; + + ?MPO_MSG(Pid2b, Msg2b) -> + ?P("~s -> unexpected message: " + "~n From: ~p" + "~n Msg: ~p" + "~nwhen" + "~n Client: ~p" + "~n CAID: ~p" + "~n Stream: ~p", + [?FUNCTION_NAME, Pid2b, Msg2b, Client, CAID, Stream]), + exit({unexpected_msg, Pid2b, Msg2b}) + + after Timeout -> + ?P("~s -> unexpected timeout", [?FUNCTION_NAME]), + mpo_cast(Client, terminate), + mpo_cast(Server, terminate), + exit({timeout, client_send_completion}) + end, + + + ?P("~s -> try verify server peer-addr-info", [?FUNCTION_NAME]), + CSA = #{family => Domain, + addr => Addr, + port => CPort}, + case mpo_call(Server, + {getopt, {sctp, get_peer_addr_info}, #{assoc_id => CAID, + addr => CSA}}) of + {ok, #{assoc_id := _, state := active} = SPAI} -> + ?P("~s -> received expected PAI: " + "~n ~p", [?FUNCTION_NAME, SPAI]), + ok; + {error, Reason6} -> + ?P("~s -> failed get PAI from handler: " + "~n ~p", [?FUNCTION_NAME, Reason6]), + exit({failed_get_handler_pai, Reason6}) + end, + + + ?P("~s -> try verify client peer-addr-info", [?FUNCTION_NAME]), + SSA = #{family => Domain, + addr => Addr, + port => SPort}, + case mpo_call(Client, + {getopt, {sctp, get_peer_addr_info}, #{assoc_id => SAID, + addr => SSA}}) of + {ok, #{assoc_id := _, state := active} = CPAI} -> + ?P("~s -> received expected PAI: " + "~n ~p", [?FUNCTION_NAME, CPAI]), + ok; + {error, Reason6b} -> + ?P("~s -> failed get PAI from handler: " + "~n ~p", [?FUNCTION_NAME, Reason6b]), + exit({failed_get_handler_pai, Reason6b}) + end, + + + + ?P("~s -> issue set \"inherit\" opts to server", [?FUNCTION_NAME]), + lists:foreach( + fun({SockOpt, Value}) -> + case mpo_call(Server, {setopt, SockOpt, Value}) of + ok -> + ok; + {error, Reason7} -> + ?P("~s -> setopt request failed:" + "~n SockOpt: ~p" + "~n Value: ~p" + "~n Reason: ~p", + [?FUNCTION_NAME, SockOpt, Value, Reason7]), + exit({server_setopt_failed, SockOpt, Reason7}) + end + end, + InheritOpts), + + ?P("~s -> verify \"inherit\" opts from server", [?FUNCTION_NAME]), + case [case mpo_call(Server, {getopt, SockOpt}) of + {ok, Value} -> + ?P("~s -> got sockopt from server:" + "~n SockOpt: ~p" + "~n Value: ~p", [?FUNCTION_NAME, SockOpt, Value]), + {SockOpt, Value}; + {error, Reason8} -> + ?P("~s -> failed get sockopt from server:" + "~n SockOpt: ~p" + "~n Reason: ~p", [?FUNCTION_NAME, SockOpt, Reason8]), + exit({server_getopt_failed, SockOpt, Reason8}) + end || SockOpt <- InheritSockOpts] of + InheritOpts -> + ?P("~s -> \"inherit\" opts from server verified", [?FUNCTION_NAME]), + ok; + UnexpectedInheritOpts1 -> + ?P("~s -> verification of \"inherit\" opts from server failed:" + "~n Expected: ~p" + "~n Actual: ~p", + [?FUNCTION_NAME, InheritOpts, UnexpectedInheritOpts1]), + exit(inherit_opts_set_verification_failed) + end, + + ?P("~s -> try server peeloff", [?FUNCTION_NAME]), + {Handler, HPort} = + case mpo_call(Server, {peeloff, InheritSockOpts}) of + {ok, {H, 0}} -> + ?P("~s -> peeloff success with port = 0 - use (server) ~p", + [?FUNCTION_NAME, SPort]), + {H, SPort}; + {ok, {H, HP}} -> + ?P("~s -> peeloff success with port = ~p", + [?FUNCTION_NAME, HP]), + {H, HP}; + {error, Reason9} -> + ?P("~s -> peeloff failed:" + "~n ~p", [?FUNCTION_NAME, Reason9]), + exit({peeloff, Reason9}) + end, + ?P("~s -> monitor handler ~p (~w)", [?FUNCTION_NAME, Handler, HPort]), + HMRef = erlang:monitor(process, Handler), + + + ?P("~s -> verify \"inherit\" opts from handler", [?FUNCTION_NAME]), + case [case mpo_call(Handler, {getopt, SockOpt}) of + {ok, Value} -> + ?P("~s -> got sockopt from handler:" + "~n SockOpt: ~p" + "~n Value: ~p", [?FUNCTION_NAME, SockOpt, Value]), + {SockOpt, Value}; + {error, Reason10} -> + ?P("~s -> failed get sockopt from handler:" + "~n ~p", [?FUNCTION_NAME, Reason10]), + exit({handler_getopt_failed, SockOpt, Reason10}) + end || SockOpt <- InheritSockOpts] of + InheritOpts -> + ?P("~s -> \"inherit\" opts from handler verified", + [?FUNCTION_NAME]), + ok; + UnexpectedInheritOpts2 -> + ?P("~s -> verification of \"inherit\" opts from handler failed:" + "~n Expected: ~p" + "~n Actual: ~p", + [?FUNCTION_NAME, InheritOpts, UnexpectedInheritOpts2]), + exit(inherit_opts_verification_failed) + end, + + + ?P("~s -> try verify handler peer-addr-info", [?FUNCTION_NAME]), + case mpo_call(Handler, + {getopt, {sctp, get_peer_addr_info}, #{assoc_id => CAID, + addr => CSA}}) of + {ok, #{assoc_id := _, state := active} = HPAI} -> + ?P("~s -> received expected PAI: " + "~n ~p", [?FUNCTION_NAME, HPAI]), + ok; + {error, Reason11} -> + ?P("~s -> failed get PAI from handler: " + "~n ~p", [?FUNCTION_NAME, Reason11]), + exit({failed_get_handler_pai, Reason11}) + end, + + ?P("~s -> issue 'send' to handler", [?FUNCTION_NAME]), + case mpo_call(Handler, {send, Stream, ?DATA}) of + ok -> + ok; + {error, Reason12} -> + ?P("~s -> (handler) send request failed: " + "~n ~p", [?FUNCTION_NAME, Reason12]), + mpo_cast(Client, terminate), + mpo_cast(Handler, terminate), + mpo_cast(Server, terminate), + exit({handler_send_request_failed, Reason12}) + end, + + ?P("~s -> await (received-) message from client", [?FUNCTION_NAME]), + receive + {'DOWN', SMRef, process, Server, Reason13} -> + ?P("~s -> received unexpected DOWN from server: " + "~n ~p", [?FUNCTION_NAME, Reason13]), + mpo_cast(Handler, terminate), + mpo_cast(Client, terminate), + exit({server_failed, Reason13}); + + {'DOWN', HMRef, process, Handler, Reason14} -> + ?P("~s -> received unexpected DOWN from server: " + "~n ~p", [?FUNCTION_NAME, Reason14]), + mpo_cast(Client, terminate), + mpo_cast(Server, terminate), + exit({handler_failed, Reason14}); + + {'DOWN', CMRef, process, Client, Reason15} -> + ?P("~s -> received unexpected DOWN from client: " + "~n ~p", [?FUNCTION_NAME, Reason15]), + mpo_cast(Handler, terminate), + mpo_cast(Server, terminate), + exit({client_failed, Reason15}); + + ?MPO_MSG(Client, {received, {data, {CAID, Stream, ?DATA}}}) -> + ?P("~s -> expected (received-) response from client", + [?FUNCTION_NAME]), + ok + + after Timeout -> + ?P("~s -> unexpected timeout:" + "~n MQ: ~p", [?FUNCTION_NAME, ?MQ()]), + mpo_cast(Client, terminate), + mpo_cast(Handler, terminate), + mpo_cast(Server, terminate), + exit({timeout, client_data_response}) + end, + + ?P("~s -> instruct the handler to await shutdown", [?FUNCTION_NAME]), + mpo_cast(Handler, expect_shutdown), + + ?P("~s -> close server and client sockets", [?FUNCTION_NAME]), + mpo_cast(Server, close), + mpo_cast(Client, close), + + ?P("~s -> await handler socket shutdown start confirmation", + [?FUNCTION_NAME]), + receive + {'DOWN', HMRef, process, Handler, Reason16a} -> + ?P("~s -> received unexpected DOWN from server: " + "~n ~p", [?FUNCTION_NAME, Reason16a]), + mpo_cast(Client, terminate), + mpo_cast(Server, terminate), + exit({handler_failed, Reason16a}); + + ?MPO_MSG(Handler, + {received, {shutdown_event, SAID}}) -> + ?P("~s -> expected shutdown-start from handler", + [?FUNCTION_NAME]), + ok + + after Timeout -> + ?P("~s -> unexpected timeout while waiting for " + "(handler) shutdown start confirmation:" + "~n MQ: ~p" + "~nwhen" + "~n Handler: ~p" + "~n AssocID: ~p", [?FUNCTION_NAME, ?MQ(), Handler, SAID]), + mpo_cast(Client, terminate), + mpo_cast(Handler, terminate), + mpo_cast(Server, terminate), + exit({timeout, handler_shutdown_start}) + end, + + ?P("~s -> await handler socket shutdown complete confirmation", + [?FUNCTION_NAME]), + receive + {'DOWN', HMRef, process, Handler, Reason16b} -> + ?P("~s -> received unexpected DOWN from server: " + "~n ~p", [?FUNCTION_NAME, Reason16b]), + mpo_cast(Client, terminate), + mpo_cast(Server, terminate), + exit({handler_failed, Reason16b}); + + ?MPO_MSG(Handler, {received, {assoc_change, shutdown_comp, SAID}}) -> + ?P("~s -> expected shutdown-complete from handler", + [?FUNCTION_NAME]), + ok + + after Timeout -> + ?P("~s -> unexpected timeout while waiting for (handler) shutdown confirmation:" + "~n MQ: ~p", [?FUNCTION_NAME, ?MQ()]), + mpo_cast(Client, terminate), + mpo_cast(Handler, terminate), + mpo_cast(Server, terminate), + exit({timeout, handler_shutdown}) + end, + + ?P("~s -> terminate client", [?FUNCTION_NAME]), + mpo_cast(Client, terminate), + receive + {'DOWN', CMRef, process, Client, Reason17} -> + ?P("~s -> received expected DOWN from client: " + "~n ~p", [?FUNCTION_NAME, Reason17]), + ok + + after Timeout -> + ?P("~s -> unexpected (client termination) timeout", + [?FUNCTION_NAME]), + %% No more mister nice guy + exit(Client, kill), + exit(Handler, kill), + exit(Server, kill), + exit(failed_terminate_client) + end, + + ?P("~s -> terminate handler", [?FUNCTION_NAME]), + mpo_cast(Handler, terminate), + receive + {'DOWN', HMRef, process, Handler, Reason18} -> + ?P("~s -> received expected DOWN from handler: " + "~n ~p", [?FUNCTION_NAME, Reason18]), + ok + + after Timeout -> + ?P("~s -> unexpected (handler termination) timeout", + [?FUNCTION_NAME]), + %% No more mister nice guy + exit(Handler, kill), + exit(Server, kill), + exit(failed_terminate_handler) + end, + + ?P("~s -> terminate server", [?FUNCTION_NAME]), + mpo_cast(Server, terminate), + receive + {'DOWN', SMRef, process, Server, Reason19} -> + ?P("~s -> received expected DOWN from server: " + "~n ~p", [?FUNCTION_NAME, Reason19]), + ok + + after Timeout -> + ?P("~s -> unexpected (server termination) timeout", + [?FUNCTION_NAME]), + %% No more mister nice guy + exit(Server, kill), + exit(failed_terminate_server) + end, + + ?P("~s -> done", [?FUNCTION_NAME]), + ok. + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +m_peeloff_server_start(State) -> + Self = self(), + ServerInfo = {Server, MRef} = + spawn_monitor(fun() -> + m_peeloff_server_init(State#{ctrl => Self}) + end), + receive + {'DOWN', MRef, process, Server, Reason} -> + ?P("~s -> received unexpected DOWN from starting server:" + "~n ~p", [?FUNCTION_NAME, Reason]), + exit({server_start_failed, Reason}); + + {?MODULE, Server, {started, Port}} -> + ?P("~s -> server started:" + "~n PortNo: ~p", [?FUNCTION_NAME, Port]), + {ServerInfo, Port} + end. + +m_peeloff_server_init(#{ctrl := CTRL, + domain := Domain, + addr := Addr, + events := Evs} = State) -> + ?P("~s -> try open", [?FUNCTION_NAME]), + Sock = case socket:open(Domain, seqpacket, sctp) of + {ok, S} -> + S; + {error, Reason1} -> + ?P("~s -> open failed:" + "~n ~p", [?FUNCTION_NAME, Reason1]), + exit({open_failed, Reason1}) + end, + + ?P("~s -> try bind", [?FUNCTION_NAME]), + SockAddr = #{family => Domain, + addr => Addr, + port => 0}, + case socket:bind(Sock, SockAddr) of + ok -> + ok; + {error, Reason2} -> + ?P("~s -> bind failed:" + "~n ~p", [?FUNCTION_NAME, Reason2]), + exit({bind_failed, Reason2}) + end, + + ?P("~s -> try make listen socket", [?FUNCTION_NAME]), + case socket:listen(Sock, true) of + ok -> + ok; + {error, Reason3} -> + ?P("~s -> listen failed:" + "~n ~p", [?FUNCTION_NAME, Reason3]), + exit({listen, Reason3}) + end, + + ?P("~s -> try set 'events' (sctp) socket option", [?FUNCTION_NAME]), + case socket:setopt(Sock, ?MK_SCTP_SOCKOPT(events), Evs) of + ok -> + ok; + {error, Reason4} -> + ?P("~s -> setopt events failed:" + "~n ~p", [?FUNCTION_NAME, Reason4]), + exit({setopt_events, Reason4}) + end, + + ?P("~s -> try sockname (get port number)", [?FUNCTION_NAME]), + Port = case socket:sockname(Sock) of + {ok, #{port := P}} -> + P; + {error, Reason5} -> + ?P("~s -> sockname failed:" + "~n ~p", [?FUNCTION_NAME, Reason5]), + exit({sockname, Reason5}) + end, + + ?P("~s -> monitor ctrl", [?FUNCTION_NAME]), + MRef = erlang:monitor(process, CTRL), + + ?P("~s -> inform ctrl (port number: ~p)", [?FUNCTION_NAME, Port]), + CTRL ! {?MODULE, self(), {started, Port}}, + + ?P("~s -> init done", [?FUNCTION_NAME]), + m_peeloff_server_loop(State#{ctrl_mref => MRef, + sock => Sock, + port => Port, + connection => undefined, + select => undefined, + inherit_opts => []}). + + +%% m_peeloff_server_options(_Sock, []) -> +%% ?P("~s -> all socket options successfully set", [?FUNCTION_NAME]), +%% ok; +%% m_peeloff_server_options(Sock, [{SockOpt, Value}|Opts]) -> +%% case socket:setopt(Sock, SockOpt, Value) of +%% ok -> +%% m_peeloff_server_options(Sock, Opts); +%% {error, Reason} -> +%% ?P("~s -> setopt events failed:" +%% "~n SockOpt: ~p" +%% "~n Value: ~p" +%% "~n Reason: ~p", [?FUNCTION_NAME, SockOpt, Value, Reason]), +%% exit({setopt, SockOpt, Value, Reason}) +%% end. + + +m_peeloff_server_loop(#{ctrl := CTRL, + connection := undefined, + select := undefined, + sock := Sock} = State) + when (Sock =/= undefined) -> + ?P("~s -> try accept connection", [?FUNCTION_NAME]), + case socket:recvmsg(Sock, nowait) of + {ok, #{flags := Flags} = Msg} -> + ?P("~s -> received message - verify connection attempt", + [?FUNCTION_NAME]), + case lists:member(notification, Flags) of + true -> + ?SEV_IPRINT("is notification"), + case Msg of + #{notification := + #{type := assoc_change, + state := comm_up, + assoc_id := AID, + '$esock_name' := sctp_notification}} -> + ?P("~s -> " + "expected notification - " + "assoc-change:comm-up:" + "~n AssocID: ~p", [?FUNCTION_NAME, AID]), + NewState = State#{connection => #{assoc_id => AID}}, + mpo_cast(CTRL, {accepted, AID}), + m_peeloff_server_loop(NewState); + #{notification := Notif} -> + ?P("~s -> unexpected notification:" + "~n ~p", [?FUNCTION_NAME, Notif]), + exit({unexpected_notification, Notif}); + _ -> + ?P("~s -> unexpected notication msg :" + "~n ~p", [?FUNCTION_NAME, Msg]), + exit({unexpected_notif_message, Msg}) + end; + false -> + ?P("~s -> unexpected msg :" + "~n ~p", [?FUNCTION_NAME, Msg]), + exit({unexpected_message, Msg}) + end; + + {select, SelectInfo} -> + ?P("~s -> select", [?FUNCTION_NAME]), + m_peeloff_server_loop(State#{select => SelectInfo}); + + {error, Reason} -> + ?P("~s -> recvmsg failed (accept):" + "~n ~p", [?FUNCTION_NAME, Reason]), + exit({recvmsg, accept, Reason}) + end; +m_peeloff_server_loop(#{ctrl := CTRL, + ctrl_mref := MRef, + connection := undefined, + select := {select_info, _, SelectHandle}, + sock := Sock} = State) -> + ?P("~s -> await select message (for accept)", [?FUNCTION_NAME]), + receive + {'DOWN', MRef, process, CTRL, Info} -> + ?P("~s -> " + "received unexpected DOWN from parent when awaiting connection:" + "~n ~p", [?FUNCTION_NAME, Info]), + (catch socket:close(Sock)), + exit({parent, Info}); + + {'$socket', Sock, select, SelectHandle} -> + ?P("~s -> received select message", [?FUNCTION_NAME]), + m_peeloff_server_loop(State#{select => undefined}); + + ?MPO_MSG(CTRL, close) -> + ?P("~s -> received close request", [?FUNCTION_NAME]), + (catch socket:close(Sock)), + m_peeloff_server_loop(State#{select => undefined, + sock => undefined}); + + ?MPO_MSG(CTRL, terminate) -> + ?P("~s -> received terminate request", [?FUNCTION_NAME]), + (catch socket:close(Sock)), + case maps:get(handler, State, undefined) of + undefined -> + ok; + {Handler, _} -> + mpo_cast(Handler, terminate) + end, + exit(normal) + + end; +m_peeloff_server_loop(#{ctrl := CTRL, + connection := #{assoc_id := AID}, + select := undefined, + sock := Sock} = State) + when (Sock =/= undefined) -> + ?P("~s -> try recv data when" + "~n Socket Info: ~p" + "~n Socket Status: ~p", + [?FUNCTION_NAME, socket:info(Sock), ?WHICH_SCTP_STATUS(Sock, AID)]), + case socket:recvmsg(Sock, nowait) of + {ok, #{flags := _, + iov := [Data], + ctrl := [#{level := sctp, + type := sndrcv, + value := #{assoc_id := AID, + stream := Stream, + '$esock_name' := sctp_sndrcvinfo}}]}} -> + ?P("~s -> got data", [?FUNCTION_NAME]), + Received = {data, {AID, Stream, Data}}, + mpo_cast(CTRL, {received, Received}), + m_peeloff_server_loop(State); + + %% Solaris and ? + {ok, #{notification := #{type := peer_addr_change, + state := addr_available, + assoc_id := AID}}} -> + ?P("~s -> " + "received expected peer-addr-change:addr-available for assoc ~w", + [?FUNCTION_NAME, AID]), + m_peeloff_server_loop(State); + + %% FreeBSD and ? + {ok, #{notification := #{type := peer_addr_change, + state := addr_confirmed, + assoc_id := AID}}} -> + ?P("~s -> " + "received expected peer-addr-change:addr-confirmed for assoc ~w", + [?FUNCTION_NAME, AID]), + m_peeloff_server_loop(State); + + {ok, #{notification := #{type := shutdown_event, + assoc_id := AID}}} -> + ?P("~s -> received unexpected shutdown event for ~w", + [?FUNCTION_NAME, AID]), + exit({unexpected_shutdown, AID}); + + {select, SelectInfo} -> + ?P("~s -> select", [?FUNCTION_NAME]), + m_peeloff_server_loop(State#{select => SelectInfo}); + + {error, Reason} -> + ?P("~s -> recvmsg failed (data):" + "~n ~p", [?FUNCTION_NAME, Reason]), + exit({recvmsg, data, Reason}) + end; +m_peeloff_server_loop(#{ctrl := CTRL, + ctrl_mref := MRef, + connection := #{assoc_id := AID}, + select := {select_info, _, SelectHandle}, + sock := Sock} = State) -> + ?P("~s -> await select message (for data) when" + "~n Socket Info: ~p" + "~n Socket Status: ~p", + [?FUNCTION_NAME, socket:info(Sock), ?WHICH_SCTP_STATUS(Sock, AID)]), + receive + {'DOWN', MRef, process, CTRL, Info} -> + ?P("~s -> " + "received unexpected DOWN from parent when awaiting data:" + "~n ~p", [?FUNCTION_NAME, Info]), + (catch socket:close(Sock)), + exit({parent, Info}); + + {'$socket', Sock, select, SelectHandle} -> + ?P("~s -> received select message", [?FUNCTION_NAME]), + m_peeloff_server_loop(State#{select => undefined}); + + ?MPO_REQUEST(From, Ref, Req) -> + NewState = m_peeloff_server_request(State, From, Ref, Req), + m_peeloff_server_loop(NewState); + + ?MPO_MSG(CTRL, close) -> + ?P("~s -> received close request", [?FUNCTION_NAME]), + (catch socket:close(Sock)), + m_peeloff_server_loop(State#{connection => undefined, + select => undefined, + sock => undefined}); + + ?MPO_MSG(CTRL, terminate) -> + ?P("~s -> received terminate request", [?FUNCTION_NAME]), + (catch socket:close(Sock)), + case maps:get(handler, State, undefined) of + undefined -> + ok; + {Handler, _} -> + mpo_cast(Handler, terminate) + end, + exit(normal) + + end; +m_peeloff_server_loop(#{ctrl := CTRL, + ctrl_mref := MRef} = State) -> + ?P("~s -> await message (no connection, no socket)", [?FUNCTION_NAME]), + receive + {'DOWN', MRef, process, CTRL, Info} -> + ?P("~s -> " + "received unexpected DOWN from parent when awaiting data:" + "~n ~p", [?FUNCTION_NAME, Info]), + exit({parent, Info}); + + ?MPO_REQUEST(From, Ref, Req) -> + NewState = m_peeloff_server_request(State, From, Ref, Req), + m_peeloff_server_loop(NewState); + + ?MPO_MSG(CTRL, terminate) -> + ?P("~s -> received terminate request", [?FUNCTION_NAME]), + case maps:get(handler, State, undefined) of + undefined -> + ok; + {Handler, _} -> + mpo_cast(Handler, terminate) + end, + exit(normal) + + end. + + +m_peeloff_server_request(#{sock := Sock} = State, From, + Ref, {setopt, SockOpt, Value}) -> + ?P("~s -> setopt when" + "~n SockOpt: ~p", [?FUNCTION_NAME, SockOpt]), + Result = socket:setopt(Sock, SockOpt, Value), + case Result of + ok -> + ?P("~s -> setopt success", [?FUNCTION_NAME]), + ok; + {error, Reason} -> + ?P("~s -> setopt failure: " + "~n Reason: ~p" + "~nwhen" + "~n New Value: ~p" + "~n Current Value: ~p", + [?FUNCTION_NAME, Reason, + Value, + case socket:getopt(Sock, SockOpt) of + {ok, CurrentValue} -> + CurrentValue; + {error, _} -> + undefined + end]), + ok + end, + From ! ?MPO_REPLY(self(), Ref, Result), + State; +m_peeloff_server_request(#{sock := Sock} = State, From, + Ref, {getopt, SockOpt}) -> + ?P("~s -> getopt(2) when" + "~n SockOpt: ~p", [?FUNCTION_NAME, SockOpt]), + Result = socket:getopt(Sock, SockOpt), + From ! ?MPO_REPLY(self(), Ref, Result), + State; +m_peeloff_server_request(#{sock := Sock} = State, From, + Ref, {getopt, SockOpt, Value}) -> + ?P("~s -> getopt(3) when" + "~n SockOpt: ~p", [?FUNCTION_NAME, SockOpt]), + Result = socket:getopt(Sock, SockOpt, Value), + From ! ?MPO_REPLY(self(), Ref, Result), + State; +m_peeloff_server_request(#{sock := Sock, + connection := #{assoc_id := AID}} = State, From, + Ref, {send, Stream, Data}) -> + ?P("~s -> send (~w bytes on ~w:~w)", + [?FUNCTION_NAME, byte_size(Data), AID, Stream]), + SRI = #{assoc_id => AID, stream => Stream}, + Msg = #{iov => [Data], + ctrl => [#{level => sctp, + type => sndrcv, + value => SRI}]}, + Result = socket:sendmsg(Sock, Msg), + From ! ?MPO_REPLY(self(), Ref, Result), + State; +m_peeloff_server_request( + #{connection := #{assoc_id := AID}, + select := {select_info, _, _} = SelectInfo, + sock := Sock} = State, From, + Ref, {peeloff, InheritOpts}) -> + ?P("~s -> peeloff (cancel current select)", [?FUNCTION_NAME]), + case socket:cancel(Sock, SelectInfo) of + ok -> + %% We start a handler that performs the actual peeloff! + ?P("~s -> selected recv request cancelled - spawn handler", + [?FUNCTION_NAME]), + {{Handler, _} = HandlerInfo, Port} = + m_peeloff_server_handler_start(State, Sock, AID, InheritOpts), + ?P("~s -> handler started", + [?FUNCTION_NAME]), + From ! ?MPO_REPLY(self(), Ref, {ok, {Handler, Port}}), + State#{connection => undefined, + select => undefined, + handler => HandlerInfo}; + {error, Reason} -> + ?P("~s -> cancel failed:" + "~n ~p", [?FUNCTION_NAME, Reason]), + From ! ?MPO_REPLY(self(), Ref, {error, {cancel, Reason}}), + State + end; +m_peeloff_server_request(State, From, Ref, Req) -> + ?P("~s -> unknown request: " + "~n State: ~p" + "~n From: ~p" + "~n Ref: ~p" + "~n Req: ~p", [?FUNCTION_NAME, State, From, Ref, Req]), + From ! ?MPO_REPLY(self(), Ref, {error, {unknown_request, Req}}), + State. + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +m_peeloff_server_handler_start(#{ctrl := CTRL, + ctrl_mref := MRef} = State0, + Sock, AID, InheritOpts) -> + ?P("~s -> start handler", [?FUNCTION_NAME]), + State1 = maps:remove(connection, State0), + State2 = State1#{select => undefined}, + Self = self(), + HandlerInfo = {Handler, HMRef} = + erlang:spawn_monitor( + fun() -> + m_peeloff_server_handler_init(State2, Self, + Sock, AID, InheritOpts) + end), + receive + {'DOWN', MRef, process, CTRL, Reason} -> + ?P("~s -> received unexpected down from CTRL:" + "~n ~p", [?FUNCTION_NAME, Reason]), + exit({ctrl, Reason}); + + {'DOWN', HMRef, process, Handler, Reason} -> + ?P("~s -> received unexpected down from handler:" + "~n ~p", [?FUNCTION_NAME, Reason]), + exit({handler, Reason}); + + {?MODULE, Handler, {started, Port}} -> + ?P("~s -> received started: ~p", [?FUNCTION_NAME, Port]), + {HandlerInfo, Port}; + + ?MPO_MSG(CTRL, terminate) -> + ?P("~s -> received terminate request", [?FUNCTION_NAME]), + (catch socket:close(Sock)), + exit(Handler, kill), + exit(normal) + + end. + +m_peeloff_server_handler_init(#{ctrl := CTRL} = State, + Parent, + Sock, AssocID, InheritOpts) -> + ?P("~s -> try peeloff", [?FUNCTION_NAME]), + NewSock = + case socket:peeloff(Sock, AssocID, InheritOpts) of + {ok, S} -> + ?P("~s -> peel off success:" + "~n ~p", [?FUNCTION_NAME, S]), + S; + {error, Reason1} -> + ?P("~s -> peel off failure:" + "~n ~p", [?FUNCTION_NAME, Reason1]), + exit({peeloff, Reason1}) + end, + + ?P("~s -> try sockname", [?FUNCTION_NAME]), + Port = + case socket:sockname(NewSock) of + {ok, #{port := P}} -> + ?P("~s -> sockname success: ~p", [?FUNCTION_NAME, P]), + P; + {error, Reason2} -> + ?P("~s -> sockname failure:" + "~n ~p", [?FUNCTION_NAME, Reason2]), + exit({sockname, Reason2}) + end, + + ?P("~s -> inform parent started", [?FUNCTION_NAME]), + Parent ! {?MODULE, self(), {started, Port}}, + + ?P("~s -> monitor processes", [?FUNCTION_NAME]), + CMRef = erlang:monitor(process, CTRL, []), + PMRef = erlang:monitor(process, Parent, []), + + ?P("~s -> started", [?FUNCTION_NAME]), + m_peeloff_server_handler_loop(State#{sock => NewSock, + assoc_id => AssocID, + ctrl_mref => CMRef, + server => Parent, + server_mref => PMRef}). + +m_peeloff_server_handler_loop(#{ctrl := CTRL, + sock := Sock, + assoc_id := AssocID, + select := undefined} = State) + when (Sock =/= undefined) -> + ?P("~s -> try recvmsg when" + "~n Socket Info: ~p" + "~n Socket Status: ~p", + [?FUNCTION_NAME, socket:info(Sock), ?WHICH_SCTP_STATUS(Sock, AssocID)]), + case socket:recvmsg(Sock, nowait) of + {ok, #{flags := _, + addr := SA, + iov := [Data], + ctrl := [#{level := sctp, + type := sndrcv, + value := #{assoc_id := AssocID, + stream := Stream, + '$esock_name' := sctp_sndrcvinfo}}]}} -> + ?P("~s -> received data:" + "~n From: ~p" + "~n Stream: ~p" + "~n AssocID: ~p", [?FUNCTION_NAME, SA, Stream, AssocID]), + mpo_cast(CTRL, {received, {data, {AssocID, Stream, Data}}}), + m_peeloff_server_handler_loop(State); + + {ok, #{flags := _, % Should contain the 'notification' flag + notification := #{type := assoc_change, + state := shutdown_comp, + assoc_id := AssocID, + '$esock_name' := sctp_notification}}} -> + %% Shutdown is now complete + ?P("~s -> shutdown-complete (~w) event", [?FUNCTION_NAME, AssocID]), + mpo_cast(CTRL, {received, {assoc_change, shutdown_comp, AssocID}}), + m_peeloff_server_handler_loop(State#{sock => undefined, + assoc_id => undefined, + shutdown => complete}); + + {ok, #{flags := _, % Should contain the 'notification' flag + notification := #{type := shutdown_event, + assoc_id := AssocID, + '$esock_name' := sctp_notification}}} -> + ?P("~s -> received shutdown-event (~w)", + [?FUNCTION_NAME, AssocID]), + mpo_cast(CTRL, {received, {shutdown_event, AssocID}}), + m_peeloff_server_handler_loop(State#{shutdown => 'begin'}); + + {ok, #{flags := _, % Should contain the 'notification' flag + notification := #{type := peer_addr_change, + state := NState, + assoc_id := AssocID, + '$esock_name' := sctp_notification}}} + when (NState =:= addr_confirmed) orelse + (NState =:= addr_available) -> + ?P("~s -> received " + "peer-addr-change:~w" + "for assoc ~w", + [?FUNCTION_NAME, NState, AssocID]), + mpo_cast(CTRL, {received, + {peer_addr_change, NState, AssocID}}), + m_peeloff_server_handler_loop(State); + + {select, SelectInfo} -> + ?P("~s -> select", [?FUNCTION_NAME]), + m_peeloff_server_handler_loop(State#{select => SelectInfo}); + + {error, Reason} -> + ?P("~s -> recvmsg failed:" + "~n ~p", [?FUNCTION_NAME, Reason]), + exit({recvmsg, Reason}) + end; +m_peeloff_server_handler_loop( + #{ctrl := CTRL, + ctrl_mref := CMRef, + server := Server, + server_mref := SMRef, + sock := Sock, + assoc_id := AID, + select := {select_info, _, SelectHandle}} = State) + when (Sock =/= undefined) -> + ?P("~s -> await select when" + "~n Socket Info: ~p" + "~n Socket Status: ~p", + [?FUNCTION_NAME, socket:info(Sock), ?WHICH_SCTP_STATUS(Sock, AID)]), + receive + {'DOWN', CMRef, process, CTRL, Reason} -> + ?P("~s -> received unexpected down from ctrl:" + "~n ~p", [?FUNCTION_NAME, Reason]), + (catch socket:close(Sock)), + exit({parent, Reason}); + + {'DOWN', SMRef, process, Server, Reason} -> + ?P("~s -> received unexpected down from server:" + "~n ~p", [?FUNCTION_NAME, Reason]), + (catch socket:close(Sock)), + exit({server, Reason}); + + {'$socket', Sock, select, SelectHandle} -> + ?P("~s -> received select message", [?FUNCTION_NAME]), + m_peeloff_server_handler_loop(State#{select => undefined}); + + ?MPO_REQUEST(CTRL, Ref, Req) -> + ?P("~s -> received request:" + "~n ~p", [?FUNCTION_NAME, Req]), + NewState = m_peeloff_server_handler_request(State, + CTRL, Ref, Req), + m_peeloff_server_handler_loop(NewState); + + ?MPO_MSG(CTRL, expect_shutdown) -> + ?P("~s -> received expect-shutdown request", [?FUNCTION_NAME]), + m_peeloff_server_handler_loop(State#{shutdown => expect}); + + ?MPO_MSG(CTRL, terminate) -> + ?P("~s -> received terminate request", [?FUNCTION_NAME]), + (catch socket:close(Sock)), + exit(normal) + end; +m_peeloff_server_handler_loop(#{ctrl := CTRL, + ctrl_mref := CMRef, + server := Server, + server_mref := SMRef} = State) -> + ?P("~s -> await message (no socket)", [?FUNCTION_NAME]), + receive + {'DOWN', CMRef, process, CTRL, Reason} -> + ?P("~s -> received unexpected down from ctrl:" + "~n ~p", [?FUNCTION_NAME, Reason]), + exit({parent, Reason}); + + {'DOWN', SMRef, process, Server, Reason} -> + ?P("~s -> received unexpected down from server:" + "~n ~p", [?FUNCTION_NAME, Reason]), + exit({server, Reason}); + + ?MPO_REQUEST(CTRL, Ref, Req) -> + ?P("~s -> received request:" + "~n ~p", [?FUNCTION_NAME, Req]), + NewState = m_peeloff_server_handler_request(State, + CTRL, Ref, Req), + m_peeloff_server_handler_loop(NewState); + + ?MPO_MSG(CTRL, terminate) -> + ?P("~s -> received terminate request", [?FUNCTION_NAME]), + exit(normal) + end. + + + +m_peeloff_server_handler_request(#{sock := Sock} = State, + From, + Ref, + {getopt, SockOpt}) + when (Sock =/= undefined) -> + ?P("~s -> getopt(2) when" + "~n SockOpt: ~p", [?FUNCTION_NAME, SockOpt]), + Result = socket:getopt(Sock, SockOpt), + From ! ?MPO_REPLY(self(), Ref, Result), + State; +m_peeloff_server_handler_request(#{sock := Sock} = State, + From, + Ref, + {getopt, SockOpt, OptValue}) + when (Sock =/= undefined) -> + ?P("~s -> getopt(3) when" + "~n SockOpt: ~p" + "~n OptValue: ~p", [?FUNCTION_NAME, SockOpt, OptValue]), + Result = socket:getopt(Sock, SockOpt, OptValue), + From ! ?MPO_REPLY(self(), Ref, Result), + State; +m_peeloff_server_handler_request(#{sock := Sock, + assoc_id := AID} = State, + From, + Ref, + {send, Stream, Data}) + when (Sock =/= undefined) -> + ?P("~s -> send (~w bytes on ~w:~w)", + [?FUNCTION_NAME, byte_size(Data), AID, Stream]), + SRI = #{assoc_id => AID, stream => Stream}, + CtrlSRI = #{level => sctp, + type => sndrcv, + value => SRI}, + Msg = #{iov => [Data], + ctrl => [CtrlSRI]}, + Result = socket:sendmsg(Sock, Msg), + From ! ?MPO_REPLY(self(), Ref, Result), + State; +m_peeloff_server_handler_request(State, From, Ref, Req) -> + ?P("~s -> unknown request: " + "~n State: ~p" + "~n From: ~p" + "~n Ref: ~p" + "~n Req: ~p", [?FUNCTION_NAME, State, From, Ref, Req]), + From ! ?MPO_REPLY(self(), Ref, {error, {unknown_request, Req}}), + State. + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +m_peeloff_client_start(State, ServerPort) -> + Self = self(), + ClientInfo = {Client, MRef} = + spawn_monitor( + fun() -> + m_peeloff_client_init(State#{ctrl => Self, + server_port => ServerPort}) + end), + receive + {'DOWN', MRef, process, Client, Reason} -> + exit({client_start_failed, Reason}); + + {?MODULE, Client, {started, Port, AID}} -> + {ClientInfo, Port, AID} + end. + +m_peeloff_client_init(#{ctrl := CTRL, + domain := Domain, + addr := Addr, + server_port := ServerPort, + events := Evs} = State) -> + ?P("~s -> try open", [?FUNCTION_NAME]), + Sock = case socket:open(Domain, seqpacket, sctp) of + {ok, S} -> + S; + {error, Reason1} -> + ?P("~s -> open failed: " + "~n ~p", [?FUNCTION_NAME, Reason1]), + exit({open_failed, Reason1}) + end, + + ?P("~s -> try bind", [?FUNCTION_NAME]), + SockAddr = #{family => Domain, + addr => Addr, + port => 0}, + case socket:bind(Sock, SockAddr) of + ok -> + ok; + {error, Reason2} -> + ?P("~s -> bind failed: " + "~n ~p", [?FUNCTION_NAME, Reason2]), + exit({bind_failed, Reason2}) + end, + + ?P("~s -> try setopt events", [?FUNCTION_NAME]), + case socket:setopt(Sock, {sctp, events}, Evs) of + ok -> + ok; + {error, Reason4} -> + ?P("~s -> setopt events failed: " + "~n ~p", [?FUNCTION_NAME, Reason4]), + exit({setopt_events, Reason4}) + end, + + ?P("~s -> try sockname (get port number)", [?FUNCTION_NAME]), + Port = case socket:sockname(Sock) of + {ok, #{port := P}} -> + P; + {error, Reason5} -> + ?P("~s -> sockname failed: " + "~n ~p", [?FUNCTION_NAME, Reason5]), + exit({sockname, Reason5}) + end, + + ?P("~s -> try connect to server", [?FUNCTION_NAME]), + SSA = #{family => Domain, + addr => Addr, + port => ServerPort}, + case socket:connect(Sock, SSA) of + ok -> + ?P("~s -> connect ok", [?FUNCTION_NAME]), + ok; + {error, Reason6} -> + ?P("~s -> failed connect: " + "~n ~p", [?FUNCTION_NAME, Reason6]), + (catch socket:close(Sock)), + exit({connect_failed, Reason6}) + end, + + ?P("~s -> await connect confirmation", [?FUNCTION_NAME]), + AssocID = + case socket:recvmsg(Sock) of + {ok, #{flags := Flags, + addr := SA, + notification := #{type := assoc_change, + state := comm_up, + assoc_id := AID}}} -> + ?P("~s -> received expected connect confirmation:" + "~n Flags: ~p" + "~n SA: ~p" + "~n AssocID: ~p", + [?FUNCTION_NAME, Flags, SA, AID]), + AID; + {ok, #{flags := Flags, + addr := SA, + notification := #{type := assoc_change, + state := cant_str_assoc = State}}} -> + ?P("~s -> received unexpected connect failure: ~p" + "~n Flags: ~p" + "~n SA: ~p", + [?FUNCTION_NAME, State, Flags, SA]), + exit({unexpected, State}); + {ok, Msg} -> + ?P("~s -> received unexpected msg: " + "~n ~p", + [?FUNCTION_NAME, Msg]), + exit({unexpected_msg, Msg}); + {error, Reason7} -> + ?P("~s -> unexpected recvmsg failure: " + "~n ~p", + [?FUNCTION_NAME, Reason7]), + exit({unexpected_recvmsg_failure, Reason7}) + end, + + ?P("~s -> monitor ctrl", [?FUNCTION_NAME]), + MRef = erlang:monitor(process, CTRL), + + ?P("~s -> inform ctrl (port number ~p, assoc id ~w)", + [?FUNCTION_NAME, Port, AssocID]), + CTRL ! {?MODULE, self(), {started, Port, AssocID}}, + + ?P("~s -> init done", [?FUNCTION_NAME]), + m_peeloff_client_loop(State#{ctrl_mref => MRef, + sock => Sock, + port => Port, + assoc_id => AssocID, + select => undefined}). + +m_peeloff_client_loop(#{ctrl := CTRL, + sock := Sock, + select := undefined} = State) + when (Sock =/= undefined) -> + ?P("~s -> try recvmsg", [?FUNCTION_NAME]), + case socket:recvmsg(Sock, nowait) of + {ok, #{flags := _, + addr := SA, + iov := [Data], + ctrl := [#{level := sctp, + type := sndrcv, + value := #{assoc_id := AID, + stream := Stream, + '$esock_name' := sctp_sndrcvinfo}}]}} -> + ?P("~s -> received data:" + "~n From: ~p" + "~n Stream: ~p" + "~n AssocID: ~p", [?FUNCTION_NAME, SA, Stream, AID]), + mpo_cast(CTRL, {received, {data, {AID, Stream, Data}}}), + m_peeloff_client_loop(State); + + {ok, #{flags := _, % Should contain the 'notification' flag + notification := #{type := assoc_change, + state := comm_up, + assoc_id := AID, + '$esock_name' := sctp_notification}}} -> + ?P("~s -> connect confirmation:" + "~n AssocID: ~p", [?FUNCTION_NAME, AID]), + mpo_cast(CTRL, {received, {assoc_change, comm_up, AID}}), + m_peeloff_client_loop(State#{assoc_id => AID}); + + {ok, #{flags := _, % Should contain the 'notification' flag + notification := #{type := assoc_change, + state := shutdown_comp, + assoc_id := AID, + '$esock_name' := sctp_notification}}} -> + ?P("~s -> connect confirmation:" + "~n AssocID: ~p", [?FUNCTION_NAME, AID]), + mpo_cast(CTRL, {received, {assoc_change, shutdown_comp, AID}}), + m_peeloff_client_loop(State#{assoc_id => AID}); + + {ok, #{flags := _, % Should contain the 'notification' flag + notification := #{type := shutdown_event, + assoc_id := AID, + '$esock_name' := sctp_notification}}} -> + ?P("~s -> received assoc (~w) shutdown notification", + [?FUNCTION_NAME, AID]), + mpo_cast(CTRL, {received, {shutdown_event, AID}}), + m_peeloff_client_loop(State#{select => undefined}); + + {ok, #{flags := _, % Should contain the 'notification' flag + notification := #{type := peer_addr_change, + state := addr_confirmed, + assoc_id := AID, + '$esock_name' := sctp_notification}}} -> + ?P("~s -> received peer-addr-change:addr-confirmed for assoc ~w", + [?FUNCTION_NAME, AID]), + mpo_cast(CTRL, {received, {peer_addr_change, addr_confirmed, AID}}), + m_peeloff_client_loop(State#{select => undefined}); + + {select, SelectInfo} -> + ?P("~s -> select", [?FUNCTION_NAME]), + m_peeloff_client_loop(State#{select => SelectInfo}); + + {error, Reason} -> + ?P("~s -> recvmsg failed:" + "~n ~p", [?FUNCTION_NAME, Reason]), + exit({recvmsg, Reason}) + end; +m_peeloff_client_loop( + #{ctrl := CTRL, + ctrl_mref := MRef, + sock := Sock, + select := {select_info, _, SelectHandle} = SelectInfo} = State) -> + ?P("~s -> await select message", [?FUNCTION_NAME]), + receive + {'DOWN', MRef, process, CTRL, Info} -> + ?P("~s -> " + "received unexpected DOWN from ctrl:" + "~n ~p", [?FUNCTION_NAME, Info]), + (catch socket:close(Sock)), + exit({ctrl_died, Info}); + + {'$socket', Sock, select, SelectHandle} -> + ?P("~s -> received select message", [?FUNCTION_NAME]), + m_peeloff_client_loop(State#{select => undefined}); + + ?MPO_REQUEST(From, Ref, Req) -> + ?P("~s -> received request ~p from ~p", + [?FUNCTION_NAME, Ref, From]), + NewState = m_peeloff_client_request(State, From, Ref, Req), + m_peeloff_client_loop(NewState); + + ?MPO_MSG(CTRL, close) -> + ?P("~s -> received close request", [?FUNCTION_NAME]), + (catch socket:cancel(SelectInfo)), + (catch socket:close(Sock)), + m_peeloff_client_loop(State#{select => undefined, + sock => undefined, + port => undefined, + assoc_id => undefined}); + + ?MPO_MSG(CTRL, terminate) -> + ?P("~s -> received terminate request", [?FUNCTION_NAME]), + (catch socket:close(Sock)), + exit(normal) + + + end; +m_peeloff_client_loop(#{ctrl := CTRL, + ctrl_mref := MRef} = State) -> + ?P("~s -> await message (no socket)", [?FUNCTION_NAME]), + receive + {'DOWN', MRef, process, CTRL, Info} -> + ?P("~s -> " + "received unexpected DOWN from ctrl:" + "~n ~p", [?FUNCTION_NAME, Info]), + exit({ctrl_died, Info}); + + ?MPO_REQUEST(From, Ref, Req) -> + ?P("~s -> received request ~p from ~p", + [?FUNCTION_NAME, Ref, From]), + NewState = m_peeloff_client_request(State, From, Ref, Req), + m_peeloff_client_loop(NewState); + + ?MPO_MSG(CTRL, terminate) -> + ?P("~s -> received terminate request", [?FUNCTION_NAME]), + exit(normal) + + + end. + + +m_peeloff_client_request(#{sock := Sock, + assoc_id := AID} = State, + From, + Ref, + {send, Stream, Data}) when (Sock =/= undefined) -> + ?P("~s -> send request", [?FUNCTION_NAME]), + SRI = #{assoc_id => AID, stream => Stream}, + CtrlSRI = #{level => sctp, + type => sndrcv, + value => SRI}, + Msg = #{iov => [Data], + ctrl => [CtrlSRI]}, + Result = socket:sendmsg(Sock, Msg), + From ! ?MPO_REPLY(self(), Ref, Result), + State; +m_peeloff_client_request(#{sock := Sock} = State, From, + Ref, {setopt, SockOpt, Value}) + when (Sock =/= undefined) -> + ?P("~s -> setopt", [?FUNCTION_NAME]), + Result = socket:setopt(Sock, SockOpt, Value), + From ! ?MPO_REPLY(self(), Ref, Result), + State; +m_peeloff_client_request(#{sock := Sock} = State, From, + Ref, {getopt, SockOpt}) + when (Sock =/= undefined) -> + ?P("~s -> getopt(2)", [?FUNCTION_NAME]), + Result = socket:getopt(Sock, SockOpt), + From ! ?MPO_REPLY(self(), Ref, Result), + State; +m_peeloff_client_request(#{sock := Sock} = State, From, + Ref, {getopt, SockOpt, Value}) + when (Sock =/= undefined) -> + ?P("~s -> getopt(3)", [?FUNCTION_NAME]), + Result = socket:getopt(Sock, SockOpt, Value), + From ! ?MPO_REPLY(self(), Ref, Result), + State; +m_peeloff_client_request(State, From, Ref, Req) -> + ?P("~s -> unknown request:" + "~n State: ~p" + "~n From: ~p" + "~n Ref: ~p" + "~n Req: ~p", [?FUNCTION_NAME, State, From, Ref, Req]), + From ! ?MPO_REPLY(self(), Ref, {error, {unknown_request, Req}}), + State. + + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Testing the peeloff function. +%% IPv6 SCTP socket(s). +m_recv_close(_Config) when is_list(_Config) -> + ?TT(?SECS(60)), + Cond = fun() -> + has_support_ipv4() + end, + Pre = fun() -> + Domain = inet, + Evs = ?SCTP_EVENTS(true), + case ?WHICH_LOCAL_ADDR(Domain) of + {ok, Addr} -> + #{addr => Addr, + domain => Domain, + events => Evs}; + {error, Reason} -> + throw({skip, Reason}) + end + end, + TC = fun(State) -> + ok = do_recv_close(State) + end, + Post = fun(_) -> ok end, + tc_try(?FUNCTION_NAME, Cond, Pre, TC, Post). + + +do_recv_close(#{domain := Domain, + addr := Addr, + events := Evs}) -> + ?P("~s -> create server socket (and listen)", [?FUNCTION_NAME]), + {ok, SSock} = socket:open(Domain, seqpacket, sctp), + ok = socket:setopt(SSock, ?MK_SCTP_SOCKOPT(events), Evs), + ok = socket:bind(SSock, #{family => Domain, addr => Addr, port => 0}), + {ok, #{port := SPort}} = socket:sockname(SSock), + ok = socket:listen(SSock, true), + + ?P("~s -> create client socket (and connect to ~w)", + [?FUNCTION_NAME, SPort]), + {ok, CSock} = socket:open(Domain, seqpacket, sctp), + ok = socket:connect(CSock, + #{family => Domain, addr => Addr, port => SPort}), + + TC = self(), + RECV = fun() -> + ?P("~s:reader -> await connection", [?FUNCTION_NAME]), + rc_await_connections(SSock), + ?P("~s:reader -> announce ready", [?FUNCTION_NAME]), + TC ! {?MODULE, self(), ready}, + ?P("~s:reader -> try read", [?FUNCTION_NAME]), + Result = socket:recvmsg(SSock), + ?P("~s:reader -> read result:" + "~n ~p", [?FUNCTION_NAME, Result]), + exit(Result) + end, + + ?P("~s -> spawn reader", [?FUNCTION_NAME]), + {ReaderPid, ReaderMRef} = erlang:spawn_monitor(RECV), + receive + {'DOWN', ReaderMRef, process, ReaderPid, ReaderPreReason} -> + ?P("~s -> unexpected reader termination:" + "~n ~p", [?FUNCTION_NAME, ReaderPreReason]), + (catch socket:close(CSock)), + (catch socket:close(SSock)), + ct:fail("Unexpected pre close from reader (~p): ~p", + [ReaderPid, ReaderPreReason]); + + {?MODULE, ReaderPid, ready} -> + ?P("~s -> reader ready", [?FUNCTION_NAME]), + ok + after 30000 -> + %% This is **extreme**, but there is no way to know + %% how long it will take to iterate through all the + %% addresses of a host... + ?P("~s -> reader ready timeout", [?FUNCTION_NAME]), + (catch socket:close(SSock)), + (catch socket:close(CSock)), + ct:fail("Unexpected pre close timeout (~p)", [ReaderPid]) + end, + + ?P("~s -> \"ensure\" reader reading...", [?FUNCTION_NAME]), + receive + Any -> + ?P("~s -> Received unexpected message: " + "~n ~p", [Any]), + (catch socket:close(SSock)), + (catch socket:close(CSock)), + ct:fail("Unexpected message: ~p", [Any]) + + after 5000 -> + ok + end, + + ?P("~s -> close server socket", [?FUNCTION_NAME]), + ok = socket:close(SSock), + ?P("~s -> await reader termination", [?FUNCTION_NAME]), + receive + {'DOWN', ReaderMRef, process, ReaderPid, {error, closed}} -> + ?P("~s -> expected reader termination result", [?FUNCTION_NAME]), + ok; + + {'DOWN', ReaderMRef, process, ReaderPid, ReaderPostReason} -> + ?P("~s -> unexpected reader termination: " + "~n ~p", [?FUNCTION_NAME, ReaderPostReason]), + (catch socket:close(CSock)), + ct:fail("Unexpected post close from reader (~p): ~p", + [ReaderPid, ReaderPostReason]) + + after 5000 -> + ?P("~s -> unexpected reader termination timeout", [?FUNCTION_NAME]), + demonitor(ReaderMRef, [flush]), + (catch socket:close(CSock)), + exit(ReaderPid, kill), + ct:fail("Reader (~p) termination timeout", [ReaderPid]) + end, + + ?P("~s -> close client socket", [?FUNCTION_NAME]), + (catch socket:close(CSock)), + + ?P("~s -> done", [?FUNCTION_NAME]), + ok. + + +rc_await_connections(S) -> + rc_await_connections(S, 1). + +rc_await_connections(S, N) -> + ?P("~s(~w) -> await connection", [?FUNCTION_NAME, N]), + case socket:recvmsg(S, 5000) of + {ok, #{flags := _, + addr := Sender, + ctrl := _, + notification := Notif}} -> + ?P("~s(~w) -> connection notification received:" + "~n From: ~p" + "~n Notification: ~p", [?FUNCTION_NAME, N, Sender, Notif]), + rc_await_connections(S, N+1); + {error, timeout} -> + ok + end. + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Check sndbuf and recbuf behaviour. +%% This is the 'socket' variant of the gen_sctp_SUITE:buffers/1 +%% test case. It does not do exactly the same things, because +%% 'socket' behaves slightly differently than inet. +%% + +m_buffers(_Config) when is_list(_Config) -> + ?TT(?SECS(60)), + Cond = fun() -> + has_support_ipv4() + end, + Pre = fun() -> + Domain = inet, + Evs = ?SCTP_EVENTS(true), + Stream = 1, + Timeout = 3333, + Limit = 4096, + case ?WHICH_LOCAL_ADDR(Domain) of + {ok, Addr} -> + #{addr => Addr, + domain => Domain, + events => Evs, + stream => Stream, + timeout => Timeout, + limit => Limit}; + {error, Reason} -> + throw({skip, Reason}) + end + end, + TC = fun(State) -> + ok = do_buffers(State) + end, + Post = fun(_) -> ok end, + tc_try(?FUNCTION_NAME, Cond, Pre, TC, Post). + +do_buffers(InitState) -> + process_flag(trap_exit, true), + + ServerSeq = + [ + %% *** Wait for start order part *** + #{desc => "await start (from tester)", + cmd => fun(State) -> + Tester = ?SEV_AWAIT_START(), + {ok, State#{tester => Tester}} + end}, + #{desc => "monitor tester", + cmd => fun(#{tester := Tester} = _State) -> + _MRef = erlang:monitor(process, Tester), + ok + end}, + + %% *** Init part *** + #{desc => "create (listen) socket", + cmd => fun(#{domain := Domain} = State) -> + case socket:open(Domain, seqpacket, sctp) of + {ok, Sock} -> + {ok, State#{sock => Sock}}; + {error, epfnosupport = Reason} -> + {skip, Reason}; + {error, eprotonosupport = Reason} -> + {skip, Reason}; + {error, esocktnosupport = Reason} -> + {skip, Reason}; + {error, Reason} = ERROR -> + ?SEV_EPRINT("Failed open socket: " + "~n ~p", [Reason]), + ERROR + end + end}, + #{desc => "bind to local address", + cmd => fun(#{sock := Sock, + domain := Domain, + addr := Addr} = State) -> + LSA = #{family => Domain, + addr => Addr, + port => 0}, + case socket:bind(Sock, LSA) of + ok -> + case socket:sockname(Sock) of + {ok, #{port := Port}} -> + ?SEV_IPRINT("bound to port: ~w", + [Port]), + {ok, State#{port => Port}}; + {error, _} = ERROR -> + ERROR + end; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "subscribe to sctp events", + cmd => fun(#{sock := Sock, + events := Evs}) -> + socket:setopt(Sock, sctp, events, Evs) + end}, + #{desc => "make listen socket", + cmd => fun(#{sock := Sock}) -> + socket:listen(Sock, true) + end}, + #{desc => "announce ready (init)", + cmd => fun(#{tester := Tester, port := Port}) -> + ?SEV_ANNOUNCE_READY(Tester, init, Port), + ok + end}, + + + %% The actual test + #{desc => "await continue (accept connection)", + cmd => fun(#{tester := Tester}) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, accept) + end}, + #{desc => "accept connection: (recvmsg of assoc-change:comm-up)", + cmd => fun(#{sock := Sock} = State) -> + case accept_connection(Sock) of + {ok, {_, Assoc}} -> + {ok, State#{assoc => Assoc}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (accept)", + cmd => fun(#{tester := Tester, assoc := Assoc}) -> + ?SEV_IPRINT("announce assoc accepted: " + "~n Assoc: ~p", [Assoc]), + #{inbound_streams := IS, + outbound_streams := OS} = Assoc, + ?SEV_ANNOUNCE_READY(Tester, accept, {IS, OS}), + ok + end}, + + + %% GET SCTP-STATUS + #{desc => "await continue (status)", + cmd => fun(#{tester := Tester}) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, status) + end}, + #{desc => "status", + cmd => fun(#{sock := Sock, + assoc := #{assoc_id := AID} = _Assoc} = _State) -> + ?SEV_IPRINT("try get status"), + SockOpt = {sctp, status}, + SparseStatus = #{assoc_id => AID}, + case socket:getopt(Sock, SockOpt, SparseStatus) of + {ok, Status} -> + ?SEV_IPRINT("status:" + "~n ~p", [Status]), + ok; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (status)", + cmd => fun(#{tester := Tester}) -> + ?SEV_IPRINT("announce status"), + ?SEV_ANNOUNCE_READY(Tester, status), + ok + end}, + + + #{desc => "await continue (rcvbuf)", + cmd => fun(#{tester := Tester}) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, rcvbuf) + end}, + #{desc => "setopt rcvbuf", + cmd => fun(#{sock := Sock, + limit := Limit} = _State) -> + ?SEV_IPRINT("try set rcvbuf (to ~w)", [Limit]), + SockOpt = {socket, rcvbuf}, + case socket:setopt(Sock, SockOpt, Limit) of + ok -> + ?SEV_IPRINT("setopt success"), + ok; + {error, Reason} = ERROR -> + ?SEV_EPRINT("Failed set rcvbuf: " + "~n Reason: ~p", [Reason]), + ERROR + end + end}, + #{desc => "getopt rcvbuf", + cmd => fun(#{sock := Sock, + limit := Limit} = State) -> + ?SEV_IPRINT("try get rcvbuf for verification"), + SockOpt = {socket, rcvbuf}, + case socket:getopt(Sock, SockOpt) of + {ok, RcvBuf} when (RcvBuf >= Limit) -> + ?SEV_IPRINT("rcvbuf verified: " + "~n Limit: ~w" + "~n RcvBuf: ~w", + [Limit, RcvBuf]), + {ok, State#{rcvbuf => RcvBuf}}; + {ok, Invalid} -> + ?SEV_EPRINT("Invalid value for rcvbuf: " + "~n Limit: ~w" + "~n Invalid: ~w", + [Limit, Invalid]), + {error, {invalid_rcvbuf, Invalid, Limit}}; + {error, Reason} = ERROR -> + ?SEV_EPRINT("Failed set rcvbuf: " + "~n Reason: ~p", [Reason]), + ERROR + end + end}, + #{desc => "announce ready (rcvbuf)", + cmd => fun(#{tester := Tester, + rcvbuf := RcvBuf}) -> + ?SEV_IPRINT("announce rcvbuf"), + ?SEV_ANNOUNCE_READY(Tester, rcvbuf, RcvBuf), + ok + end}, + + + #{desc => "await continue (recv)", + cmd => fun(#{tester := Tester} = State) -> + {ok, ToRead} = + ?SEV_AWAIT_CONTINUE(Tester, tester, recv), + {ok, State#{to_read => ToRead}} + end}, + #{desc => "await data (recvmsg 1)", + cmd => fun(#{sock := Sock, + to_read := _ToRead, + stream := _Stream, + timeout := Timeout} = State) -> + case socket:recvmsg(Sock, Timeout) of + {ok, #{addr := SenderSA, + ctrl := _, + iov := [Data1]}} -> + ?SEV_IPRINT("Received" + "~n sz(Data1): ~w" + "~n From: ~p", + [byte_size(Data1), SenderSA]), + {ok, State#{data => Data1}}; + {ok, Msg} -> + ?SEV_EPRINT("Received unexpected message: " + "~n Msg: ~p", [Msg]), + {error, unexpected_msg}; + {error, Reason} = ERROR -> + ?SEV_EPRINT("Failed recv: " + "~n Reason: ~p", [Reason]), + ERROR + end + end}, + #{desc => "await data (recvmsg 2)", + cmd => fun(#{sock := Sock, + to_read := ToRead, + stream := _Stream, + timeout := Timeout, + data := Data1} = State) -> + case socket:recvmsg(Sock, Timeout) of + {ok, #{addr := SenderSA, + ctrl := _, + iov := [Data2]}} -> + Data = <>, + ?SEV_IPRINT("Received" + "~n sz(Data2): ~w" + "~n From: ~p" + "~nwhen" + "~n ToRead: ~w" + "~n sz(Data): ~w", + [byte_size(Data2), SenderSA, + ToRead, byte_size(Data)]), + {ok, State#{data => Data}}; + {ok, Msg} -> + ?SEV_EPRINT("Received unexpected message: " + "~n Msg: ~p", [Msg]), + {error, unexpected_msg}; + {error, Reason} = ERROR -> + ?SEV_EPRINT("Failed recv: " + "~n Reason: ~p", [Reason]), + ERROR + end + end}, + #{desc => "announce ready (recv)", + cmd => fun(#{tester := Tester, + data := Data}) -> + ?SEV_IPRINT("announce recv"), + ?SEV_ANNOUNCE_READY(Tester, recv, Data), + ok + end}, + + + %% GET SCTP-STATUS + #{desc => "await continue (status)", + cmd => fun(#{tester := Tester}) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, status) + end}, + #{desc => "status", + cmd => fun(#{sock := Sock, + assoc := #{assoc_id := AID} = _Assoc} = _State) -> + ?SEV_IPRINT("try get status"), + SockOpt = {sctp, status}, + SparseStatus = #{assoc_id => AID}, + case socket:getopt(Sock, SockOpt, SparseStatus) of + {ok, Status} -> + ?SEV_IPRINT("status:" + "~n ~p", [Status]), + ok; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (status)", + cmd => fun(#{tester := Tester}) -> + ?SEV_IPRINT("announce status"), + ?SEV_ANNOUNCE_READY(Tester, status), + ok + end}, + + #{desc => "await continue (stats)", + cmd => fun(#{tester := Tester}) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, stats) + end}, + #{desc => "statistics", + cmd => fun(#{sock := Sock} = _State) -> + #{counters := Counters} = socket:info(Sock), + ?SEV_IPRINT("Counters: " + "~n ~p", [Counters]), + ok + end}, + #{desc => "announce ready (stats)", + cmd => fun(#{tester := Tester}) -> + ?SEV_IPRINT("announce status"), + ?SEV_ANNOUNCE_READY(Tester, stats), + ok + end}, + + + #{desc => "await continue (await_shutdown)", + cmd => fun(#{tester := Tester}) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, await_shutdown) + end}, + #{desc => "await shutdown", + cmd => fun(#{sock := Sock, + assoc := #{assoc_id := AID}} = State) -> + case await_shutdown(Sock, AID) of + ok -> + {ok, maps:remove(assoc, State)}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (await_shutdown)", + cmd => fun(#{tester := Tester}) -> + ?SEV_IPRINT("announce shutdown received"), + ?SEV_ANNOUNCE_READY(Tester, await_shutdown), + ok + end}, + + #{desc => "await continue (stats)", + cmd => fun(#{tester := Tester}) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, stats) + end}, + #{desc => "statistics", + cmd => fun(#{sock := Sock} = _State) -> + #{counters := Counters} = socket:info(Sock), + ?SEV_IPRINT("Counters: " + "~n ~p", [Counters]), + ok + end}, + #{desc => "announce ready (stats)", + cmd => fun(#{tester := Tester}) -> + ?SEV_IPRINT("announce stats"), + ?SEV_ANNOUNCE_READY(Tester, stats), + ok + end}, + + + %% *** Terminate *** + #{desc => "await terminate", + cmd => fun(#{tester := Tester} = State) -> + case ?SEV_AWAIT_TERMINATE(Tester, tester) of + ok -> + {ok, maps:remove(tester, State)}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "close (server) socket", + cmd => fun(#{sock := Sock} = State) -> + case socket:close(Sock) of + ok -> + State1 = maps:remove(sock, State), + State2 = maps:remove(port, State1), + {ok, State2}; + {error, _} = ERROR -> + ERROR + end + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + ClientSeq = + [ + %% *** Wait for start order part *** + #{desc => "await start (from tester)", + cmd => fun(State) -> + {Tester, ServerPort} = ?SEV_AWAIT_START(), + ?SEV_IPRINT("starting with" + "~n ServerPort: ~w", [ServerPort]), + {ok, State#{tester => Tester, + server_port => ServerPort}} + end}, + #{desc => "monitor tester", + cmd => fun(#{tester := Tester} = _State) -> + _MRef = erlang:monitor(process, Tester), + ok + end}, + + %% *** Init part *** + #{desc => "create socket", + cmd => fun(#{domain := Domain} = State) -> + case socket:open(Domain, seqpacket, sctp) of + {ok, Sock} -> + {ok, State#{sock => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "bind to local address", + cmd => fun(#{sock := Sock, + domain := Domain, + addr := Addr} = State) -> + LSA = #{family => Domain, + addr => Addr, + port => 0}, + case socket:bind(Sock, LSA) of + ok -> + case socket:sockname(Sock) of + {ok, #{port := Port}} -> + ?SEV_IPRINT("bound to port: ~w", + [Port]), + {ok, State#{port => Port}}; + {error, _} = ERROR -> + ERROR + end; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "subscribe to sctp events", + cmd => fun(#{sock := Sock, + events := Evs}) -> + socket:setopt(Sock, sctp, events, Evs) + end}, + #{desc => "announce ready (init)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, init), + ok + end}, + + + %% The actual test + #{desc => "await continue (connect)", + cmd => fun(#{tester := Tester}) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, connect) + end}, + #{desc => "try connect", + cmd => fun(#{sock := Sock, + domain := Domain, + addr := Addr, + server_port := Port} = _State) -> + ServerSA = #{family => Domain, + addr => Addr, + port => Port}, + case socket:connect(Sock, ServerSA) of + ok -> + ?SEV_IPRINT("connected"), + ok; + {error, Reason} = ERROR -> + ?SEV_EPRINT("Failed connect:" + "~n ~p", [Reason]), + ERROR + end + end}, + #{desc => "await connect confirmation: (recvmsg of assoc-change:comm-up)", + cmd => fun(#{sock := Sock} = State) -> + case connect_confirmation(Sock) of + {ok, {_, Assoc}} -> + {ok, State#{assoc => Assoc}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "try verify assoc (peer-addr-info)", + cmd => fun(#{sock := Sock, + domain := Domain, + addr := Addr, + server_port := Port, + assoc := #{assoc_id := AID}} = State) -> + ServerSA = #{family => Domain, + addr => Addr, + port => Port}, + ?SEV_IPRINT("try get peer-addr-info"), + %% Create sparse peer-addr-info + OptValue = #{assoc_id => AID, + addr => ServerSA}, + SockOpt = {sctp, get_peer_addr_info}, + case socket:getopt(Sock, SockOpt, OptValue) of + {ok, #{assoc_id := PAID, + state := active}} -> + ?SEV_IPRINT("try verify assoc-id"), + match_unless_solaris(AID, PAID); + {ok, #{assoc_id := PAID, + state := State}} -> + ?SEV_EPRINT("invalid assoc state:" + "~n AID: ~p" + "~n State: ~p", + [PAID, State]), + Reason = {invalid_assoc_state, State}, + {error, Reason}; + {error, Reason0} -> + ?SEV_EPRINT("failed get " + "peer-addr-info:" + "~n ~p", [Reason0]), + Reason = + {failed_get_peer_addr_info, Reason0}, + {error, Reason} + end + end}, + #{desc => "announce ready (connect)", + cmd => fun(#{tester := Tester, assoc := Assoc}) -> + ?SEV_IPRINT("announce connected: " + "~n Assoc: ~p", [Assoc]), + #{inbound_streams := IS, + outbound_streams := OS} = Assoc, + ?SEV_ANNOUNCE_READY(Tester, connect, {IS, OS}), + ok + end}, + + + %% GET SCTP-STATUS + #{desc => "await continue (status)", + cmd => fun(#{tester := Tester}) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, status) + end}, + #{desc => "status", + cmd => fun(#{sock := Sock, + assoc := #{assoc_id := AID} = _Assoc} = _State) -> + SockOpt = {sctp, status}, + SparseStatus = #{assoc_id => AID}, + case socket:getopt(Sock, SockOpt, SparseStatus) of + {ok, Status} -> + ?SEV_IPRINT("status:" + "~n ~p", [Status]), + ok; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (status)", + cmd => fun(#{tester := Tester}) -> + ?SEV_IPRINT("announce status"), + ?SEV_ANNOUNCE_READY(Tester, status), + ok + end}, + + + #{desc => "await continue (sndbuf)", + cmd => fun(#{tester := Tester} = State) -> + {ok, SndBuf} = + ?SEV_AWAIT_CONTINUE(Tester, tester, sndbuf), + ?SEV_IPRINT("continue with SndBuf: ~w", [SndBuf]), + {ok, State#{sndbuf => SndBuf}} + end}, + #{desc => "update sndbuf", + cmd => fun(#{sock := Sock, + sndbuf := SndBuf} = _State) -> + case socket:setopt(Sock, {socket, sndbuf}, SndBuf) of + ok -> + ok; + {error, Reason} = ERROR -> + ?SEV_EPRINT("Failed set socket:sndbuf: " + "~n Reason: ~p", [Reason]), + ERROR + end + end}, + #{desc => "announce ready (sndbuf)", + cmd => fun(#{tester := Tester}) -> + ?SEV_IPRINT("announce sndbuf"), + ?SEV_ANNOUNCE_READY(Tester, sndbuf), + ok + end}, + + #{desc => "await continue (send message)", + cmd => fun(#{tester := Tester} = State) -> + {ok, Data} = + ?SEV_AWAIT_CONTINUE(Tester, tester, send), + {ok, State#{data => Data}} + end}, + #{desc => "send data back from other process", + cmd => fun(#{sock := Sock, + assoc := #{assoc_id := AID}, + data := Data, + stream := Stream} = _State) -> + ?SEV_IPRINT("build sparse SRI " + "(only assoc-id and " + "stream)"), + SRI = #{assoc_id => AID, + stream => Stream}, + Msg = #{iov => [Data], + ctrl => [#{level => sctp, + type => sndrcv, + value => SRI}]}, + case socket:sendmsg(Sock, Msg) of + ok -> + ?SEV_IPRINT("msg sent"), + ok; + {error, Reason} = ERROR -> + ?SEV_EPRINT("Failed send msg:" + "~n ~p", [Reason]), + ERROR + end + end}, + #{desc => "announce ready (send)", + cmd => fun(#{tester := Tester}) -> + ?SEV_IPRINT("announce message sent"), + ?SEV_ANNOUNCE_READY(Tester, send), + ok + end}, + + + %% GET SCTP-STATUS + #{desc => "await continue (status)", + cmd => fun(#{tester := Tester}) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, status) + end}, + #{desc => "status", + cmd => fun(#{sock := Sock, + assoc := #{assoc_id := AID}} = _State) -> + case socket:getopt(Sock, {sctp, status}, + #{assoc_id => AID}) of + {ok, Status} -> + ?SEV_IPRINT("status:" + "~n ~p", [Status]), + ok; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (status)", + cmd => fun(#{tester := Tester}) -> + ?SEV_IPRINT("announce status"), + ?SEV_ANNOUNCE_READY(Tester, status), + ok + end}, + + #{desc => "await continue (stats)", + cmd => fun(#{tester := Tester}) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, stats) + end}, + #{desc => "statistics", + cmd => fun(#{sock := Sock} = _State) -> + #{counters := Counters} = socket:info(Sock), + ?SEV_IPRINT("Counters: " + "~n ~p", [Counters]), + ok + end}, + #{desc => "announce ready (stats)", + cmd => fun(#{tester := Tester}) -> + ?SEV_IPRINT("announce stats"), + ?SEV_ANNOUNCE_READY(Tester, stats), + ok + end}, + + %% *** Terminate *** + #{desc => "await terminate", + cmd => fun(#{tester := Tester} = State) -> + case ?SEV_AWAIT_TERMINATE(Tester, tester) of + ok -> + ?SEV_IPRINT("received termination " + "from tester"), + {ok, maps:remove(tester, State)}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "close socket", + cmd => fun(#{sock := Sock} = State0) -> + ?SEV_IPRINT("try close socket"), + case socket:close(Sock) of + ok -> + State1 = maps:remove(sock, State0), + State2 = maps:remove(port, State1), + State3 = maps:remove(server_port, State2), + ?SEV_IPRINT("socket closed and " + "cleanup done"), + {ok, State3}; + {error, _} = ERROR -> + ERROR + end + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + TesterSeq = + [ + %% *** Init part *** + #{desc => "monitor 'server'", + cmd => fun(#{server := Server} = _State) -> + _MRef = erlang:monitor(process, Server), + ok + end}, + #{desc => "order server start", + cmd => fun(#{server := Pid} = _State) -> + ?SEV_ANNOUNCE_START(Pid), + ok + end}, + #{desc => "await (server) ready", + cmd => fun(#{server := Pid} = State) -> + case ?SEV_AWAIT_READY(Pid, acceptor, init) of + {ok, Port} -> + {ok, State#{server_port => Port}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "monitor 'client'", + cmd => fun(#{client := Client} = _State) -> + _MRef = erlang:monitor(process, Client), + ok + end}, + #{desc => "order client start", + cmd => fun(#{client := Pid, server_port := Port} = _State) -> + ?SEV_ANNOUNCE_START(Pid, Port), + ok + end}, + #{desc => "await client ready (init)", + cmd => fun(#{client := Pid} = _State) -> + ok = ?SEV_AWAIT_READY(Pid, client, init) + end}, + + + %% *** The actual test *** + #{desc => "order server to continue (with accept)", + cmd => fun(#{server := Server} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Server, accept), + ok + end}, + ?SEV_SLEEP(?SECS(1)), + #{desc => "order client to continue (with connect)", + cmd => fun(#{client := Client} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Client, connect), + ok + end}, + #{desc => "await client ready (connect)", + cmd => fun(#{client := Client} = State) -> + {ok, {IS, OS}} = + ?SEV_AWAIT_READY(Client, client, connect), + ?SEV_IPRINT("client streams:" + "~n Inbound: ~w" + "~n Outbound: ~w", [IS, OS]), + {ok, State#{client_is => IS, + client_os => OS}} + + + end}, + #{desc => "await server ready (accept)", + cmd => fun(#{server := Server} = State) -> + {ok, {IS, OS}} = + ?SEV_AWAIT_READY(Server, server, accept), + ?SEV_IPRINT("server streams:" + "~n Inbound: ~w" + "~n Outbound: ~w", [IS, OS]), + {ok, State#{server_is => IS, + server_os => OS}} + end}, + + #{desc => "verify streams", + cmd => fun(#{server_is := SIS, + server_os := SOS, + client_is := CIS, + client_os := COS} = _State) -> + if + (SOS =:= CIS) andalso (SIS =:= COS) -> + ?SEV_IPRINT("Streams verified"), + ok; + (SOS =:= CIS) -> + ?SEV_EPRINT("Stream verification failed: " + "Server inbound not equal to Client outbound" + "~n ~w =/= ~w", [SIS, COS]), + {error, stream_verification_failed}; + (SIS =:= COS) -> + ?SEV_EPRINT("Stream verification failed: " + "Server outbound not equal to Client inbound" + "~n ~w =/= ~w", [SOS, CIS]), + {error, stream_verification_failed}; + true -> + ?SEV_EPRINT("Stream verification failed: " + "~n Server Inbound: ~w" + "~n Client Outbound: ~w" + "~n Server Outbound: ~w" + "~n Client Inbound: ~w", + [SIS, COS, SOS, CIS]), + {error, stream_verification_failed} + end + end}, + + + #{desc => "order client to continue (with status)", + cmd => fun(#{client := Client} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Client, status), + ok + end}, + #{desc => "await client ready (status)", + cmd => fun(#{client := Client} = _State) -> + ?SEV_AWAIT_READY(Client, client, status) + end}, + + #{desc => "order server to continue (with status)", + cmd => fun(#{server := Server} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Server, status), + ok + end}, + #{desc => "await server ready (status)", + cmd => fun(#{server := Server} = _State) -> + ?SEV_AWAIT_READY(Server, server, status) + end}, + + + + #{desc => "order server to continue (with rcvbuf)", + cmd => fun(#{server := Server} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Server, rcvbuf), + ok + end}, + #{desc => "await server ready (status)", + cmd => fun(#{server := Server, + limit := Limit} = State) -> + {ok, RcvBuf} = + ?SEV_AWAIT_READY(Server, server, rcvbuf), + ?SEV_IPRINT("server (rcvbuf) ready with: ~w", + [RcvBuf]), + BufSz = RcvBuf + Limit, + Data = buf_mk_data(BufSz), + {ok, State#{bufsz => BufSz, + data => Data}} + end}, + #{desc => "order server to continue (with recv)", + cmd => fun(#{server := Server, + data := Data} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Server, + recv, byte_size(Data)), + ok + end}, + + ?SEV_SLEEP(?SECS(1)), + #{desc => "order client to continue (with sndbuf)", + cmd => fun(#{client := Client, + bufsz := BufSz} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Client, sndbuf, BufSz), + ok + end}, + #{desc => "await client ready (sndbuf)", + cmd => fun(#{client := Client} = _State) -> + ?SEV_AWAIT_READY(Client, client, sndbuf) + end}, + #{desc => "order client to continue (with send)", + cmd => fun(#{client := Client, + data := Data} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Client, send, Data), + ok + end}, + #{desc => "await client ready (send)", + cmd => fun(#{client := Client} = _State) -> + ?SEV_AWAIT_READY(Client, client, send) + end}, + + #{desc => "await server ready (recv)", + cmd => fun(#{server := Server, + data := Data} = _State) -> + case ?SEV_AWAIT_READY(Server, server, recv) of + {ok, Data} -> + ?SEV_IPRINT("Expected data received"), + ok; + {ok, WrongData} -> + ?SEV_EPRINT("Unexpected data received:" + "~n sz(Data): ~w" + "~n sz(WrongData): ~w", + [byte_size(Data), + byte_size(WrongData)]), + {error, unexpected_data}; + Any -> + ?SEV_EPRINT("Unexpected response: " + "~n ~p", [Any]), + {error, unexpected_response} + end + end}, + + + #{desc => "order client to continue (with status)", + cmd => fun(#{client := Client} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Client, status), + ok + end}, + #{desc => "await client ready (status)", + cmd => fun(#{client := Client} = _State) -> + ?SEV_AWAIT_READY(Client, client, status) + end}, + + #{desc => "order server to continue (with status)", + cmd => fun(#{server := Server} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Server, status), + ok + end}, + #{desc => "await server ready (status)", + cmd => fun(#{server := Server} = _State) -> + ?SEV_AWAIT_READY(Server, server, status) + end}, + + + #{desc => "order server to continue (stats)", + cmd => fun(#{server := Server} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Server, stats), + ok + end}, + #{desc => "await server ready (stats)", + cmd => fun(#{server := Server} = _State) -> + ?SEV_AWAIT_READY(Server, server, stats) + end}, + + #{desc => "order client to continue (stats)", + cmd => fun(#{client := Client} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Client, stats), + ok + end}, + #{desc => "await client ready (stats)", + cmd => fun(#{client := Client} = _State) -> + ?SEV_AWAIT_READY(Client, client, stats) + end}, + + + #{desc => "order server to continue (with await-shutdown)", + cmd => fun(#{server := Server} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Server, await_shutdown), + ok + end}, + ?SEV_SLEEP(?SECS(1)), + #{desc => "order client to terminate", + cmd => fun(#{client := Client} = _State) -> + ?SEV_IPRINT("order client (~p) to 'terminate'", + [Client]), + ?SEV_ANNOUNCE_TERMINATE(Client), + ok + end}, + #{desc => "await client termination", + cmd => fun(#{client := Client} = State) -> + case ?SEV_AWAIT_TERMINATION(Client) of + ok -> + ?SEV_IPRINT("client (~p) terminated", + [Client]), + State1 = maps:remove(client, State), + {ok, State1}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "await server ready (await_shutdown)", + cmd => fun(#{server := Server} = _State) -> + ?SEV_AWAIT_READY(Server, server, await_shutdown) + end}, + + #{desc => "order server to continue (stats)", + cmd => fun(#{server := Server} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Server, stats), + ok + end}, + #{desc => "await server ready (stats)", + cmd => fun(#{server := Server} = _State) -> + ?SEV_AWAIT_READY(Server, server, stats) + end}, + + + %% *** Termination *** + #{desc => "order server to terminate", + cmd => fun(#{server := Server} = _State) -> + ?SEV_IPRINT("order server (~p) to 'terminate'", + [Server]), + ?SEV_ANNOUNCE_TERMINATE(Server), + ok + end}, + #{desc => "await server termination", + cmd => fun(#{server := Server} = State) -> + case ?SEV_AWAIT_TERMINATION(Server) of + ok -> + ?SEV_IPRINT("server (~p) terminated", + [Server]), + State1 = maps:remove(server, State), + {ok, State1}; + {error, _} = ERROR -> + ERROR + end + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + i("start server evaluator"), + Server = ?SEV_START("server", ServerSeq, InitState), + + i("start client evaluator"), + Client = ?SEV_START("client", ClientSeq, InitState), + + i("start tester evaluator"), + TesterInitState = #{server => Server#ev.pid, + client => Client#ev.pid, + limit => maps:get(limit, InitState)}, + Tester = ?SEV_START("tester", TesterSeq, TesterInitState), + + ok = ?SEV_AWAIT_FINISH([Server, Client, Tester]). + + +buf_mk_data(Bytes) -> + buf_mk_data(0, Bytes, <<>>). + +buf_mk_data(N, Bytes, Bin) when N < Bytes -> + buf_mk_data(N+4, Bytes, <>); +buf_mk_data(_, _, Bin) -> + Bin. + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Make sure that we can set (default) SRI + +o_default_sri_ipv4(_Config) when is_list(_Config) -> + ?TT(?SECS(5)), + Cond = fun() -> has_support_ipv4() end, + Pre = fun() -> + Domain = inet, + case ?WHICH_LOCAL_ADDR(Domain) of + {ok, Addr} -> + #{addr => Addr, + domain => Domain, + type => seqpacket, + protocol => sctp}; + {error, Reason} -> + throw({skip, Reason}) + end + end, + TC = fun(State) -> + ok = o_default_sri(State) + end, + Post = fun(_) -> ok end, + tc_try(?FUNCTION_NAME, Cond, Pre, TC, Post). + + +o_default_sri(#{domain := Domain, + type := seqpacket = Type, + protocol := sctp = Proto}) -> + {ok, S1} = socket:open(Domain, Type, Proto), + {ok, SRI0} = socket:getopt(S1, Proto, default_send_param), + + %% Just puck two likely candidates (stream and context) + Stream0 = maps:get(stream, SRI0), + Stream1 = Stream0 + 1, + Context0 = maps:get(context, SRI0), + Context1 = Context0 + 2, + SRI1 = SRI0#{stream => Stream1, context => Context1}, + ok = socket:setopt(S1, Proto, default_send_param, SRI1), + case socket:getopt(S1, Proto, default_send_param) of + {ok, #{stream := Stream1, context := Context1}} -> + ok; + {ok, UnexpectedSRI} -> + ?P("Unexpected default SRI value: " + "~n ~p", [UnexpectedSRI]), + exit({unexpected_sri, UnexpectedSRI}); + {error, Reason} -> + ?P("Unexpected getopt failure: " + "~n ~p", [Reason]), + exit({unexpected_getopt, Reason}) + end, + socket:close(S1). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +basic_open_and_connect(ServerAddresses, ConnectToAddress) -> + Verify = fun(_, _, _, _, _, _) -> ok end, + basic_open_and_connect(ServerAddresses, ConnectToAddress, Verify). + +basic_open_and_connect(ServerAddresses, ConnectToAddress, Verify) + when is_list(ServerAddresses) andalso is_function(Verify, 6) -> + Evs = ?SCTP_EVENTS(true), + ServerFamily = fam_from_addrs(ServerAddresses), + ?P("~s -> Serving ~p addresses: " + "~n ~p", [?FUNCTION_NAME, ServerFamily, ServerAddresses]), + LSock = case socket:open(ServerFamily, seqpacket, sctp) of + {ok, LS} -> + LS; + {error, SOReason} -> + throw({sopen, SOReason}) + end, + ?P("~s -> [server] subscribe to (sctp) events", [?FUNCTION_NAME]), + ok = socket:setopt(LSock, sctp, events, Evs), + ?P("~s -> [server] bind to:" + "~n ~p", [?FUNCTION_NAME, ServerAddresses]), + {ok, LPort} = bind(LSock, ServerAddresses), + ?P("~s -> [server] sockname:" + "~n ~p", [?FUNCTION_NAME, ?WHICH_SOCKNAME(LSock)]), + ?P("~s -> [server] make listen socket", [?FUNCTION_NAME]), + ok = socket:listen(LSock, true), + + ?P("~s -> [client] create (open) socket", [?FUNCTION_NAME]), + ConnectToFam = fam_from_addr(ConnectToAddress), + ClientFamily = ConnectToFam, + CSock = case socket:open(ClientFamily, seqpacket, sctp) of + {ok, CS} -> + CS; + {error, COReason} -> + throw({copen, COReason}) + end, + ?P("~s -> [client] subscribe to (sctp) events", [?FUNCTION_NAME]), + ok = socket:setopt(CSock, sctp, events, Evs), + + ?P("~s -> [client] try connect", [?FUNCTION_NAME]), + case socket:connect(CSock, + #{family => fam_from_addr(ConnectToAddress), + addr => ConnectToAddress, + port => LPort}) of + ok -> + ok; + {error, ConnectReason} -> + ?P("~s -> failed connect: " + "~n ~p", [?FUNCTION_NAME, ConnectReason]), + ?P("~s -> try close client socket", [?FUNCTION_NAME]), + _ = socket:close(CSock), + ?P("~s -> try close server socket", [?FUNCTION_NAME]), + _ = socket:close(LSock), + ?P("~s -> sockets closed - done with failure", [?FUNCTION_NAME]), + exit({connect_failed, ConnectReason}) + end, + ?P("~s -> [client] await connect response", [?FUNCTION_NAME]), + ClientAID = + case socket:recvmsg(CSock) of + {ok, #{notification := #{type := assoc_change, + state := comm_up, + assoc_id := CAID}}} -> + ?P("~s -> [client] expected connect response received: ~w", + [?FUNCTION_NAME, CAID]), + CAID; + {ok, CUnexpMsg} -> + ?P("~s -> [client] unexpected connect response received: ~p", + [?FUNCTION_NAME, CUnexpMsg]), + exit({unexpected_recvmsg_msg, CUnexpMsg}); + {error, CReason} -> + ?P("~s -> [client] unexpected connect failure: ~p", + [?FUNCTION_NAME, CReason]), + exit({unexpected_recvmsg_failure, CReason}) + end, + + ?P("~s -> [server] try accept connection", [?FUNCTION_NAME]), + ServerAID = + case socket:recvmsg(LSock) of + {ok, #{notification := #{type := assoc_change, + state := comm_up, + assoc_id := SAID}}} -> + ?P("~s -> [server] expected connection received: ~w", + [?FUNCTION_NAME, SAID]), + SAID; + {ok, SUnexpMsg} -> + ?P("~s -> [server] unexpected accept response: ~p", + [?FUNCTION_NAME, SUnexpMsg]), + exit({unexpected_s_recvmsg_msg, SUnexpMsg}); + {error, SReason} -> + ?P("~s -> [server] unexpected accept failure: ~p", + [?FUNCTION_NAME, SReason]), + exit({unexpected_s_recvmsg_failure, SReason}) + end, + + ?P("~s -> " + "~n [client] sockname:" + "~n ~p" + "~n [client] (assoc ~w) status:" + "~n ~p" + "~n [server] (assoc ~w) status:" + "~n ~p", + [?FUNCTION_NAME, + ?WHICH_SOCKNAME(CSock), + ClientAID, ?WHICH_SCTP_STATUS(CSock, ClientAID), + ServerAID, ?WHICH_SCTP_STATUS(LSock, ServerAID)]), + + + ?P("~s -> try verify result", [?FUNCTION_NAME]), + Result = Verify(LSock, ServerFamily, ServerAID, + CSock, ClientFamily, ClientAID), + + ?P("~s -> close sockets", [?FUNCTION_NAME]), + ok = socket:close(CSock), + ok = socket:close(LSock), + + ?P("~s -> done when result: ~p", [?FUNCTION_NAME, Result]), + Result. + +%% Any inet6 address => inet6 otherwise inet. +fam_from_addrs(Addrs) -> + ?P("~s -> entry with" + "~n Addrs: ~p", [?FUNCTION_NAME, Addrs]), + case lists:usort([fam_from_addr(Addr) || Addr <- Addrs]) of + [inet, inet6] -> inet6; + [inet] -> inet; + [inet6] -> inet6 + end. + +fam_from_addr(Addr) when tuple_size(Addr) =:= 4 -> inet; +fam_from_addr(Addr) when tuple_size(Addr) =:= 8 -> inet6. + + +bind(Sock, [Addr|Addrs]) -> + SA = #{family => fam_from_addr(Addr), + addr => Addr, + port => 0}, + ?P("~s -> try bind socket to (primary) address", [?FUNCTION_NAME]), + case socket:bind(Sock, SA) of + ok when (length(Addrs) > 0) -> + ?P("~s -> try get sockname", [?FUNCTION_NAME]), + case socket:sockname(Sock) of + {ok, #{port := Port}} -> + Addrs2 = [#{family => fam_from_addr(A), + addr => A, + port => Port} || A <- Addrs], + ?P("~s -> try bind rest addrs: " + "~n ~p", [?FUNCTION_NAME, Addrs2]), + case socket:bind(Sock, Addrs2, add) of + ok -> + {ok, Port}; + {error, Reason} -> + exit({bind3, Reason}) + end; + {error, Reason} -> + exit({sockname, Reason}) + end; + ok -> + case socket:sockname(Sock) of + {ok, #{port := Port}} -> + {ok, Port}; + {error, Reason} -> + exit({sockname, Reason}) + end; + {error, Reason} -> + exit({bind2, Reason}) + end; +bind(Sock, Addr) -> + SA = #{family => fam_from_addr(Addr), + addr => Addr, + port => 0}, + socket:bind(Sock, SA). + + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +get_addrs_by_family(Fam, NumAddrs) -> + case os:type() of + {unix, OS} when (OS =:= linux) orelse (OS =:= freebsd) -> + get_addrs_by_family2(Fam, NumAddrs); + {unix, sunos} -> + case get_addrs_by_family2(Fam, NumAddrs) of + {ok, _} = OK when (Fam =:= inet) orelse (Fam =:= inet6) -> + OK; + {ok, [Addrs4, Addrs6]} when (Fam =:= inet_and_inet6) -> + {ok, [ipv4_map_addrs(Addrs4), Addrs6]}; + {error, _} = ERROR -> + ERROR + end; + OS -> + {error, {unsupported_os, OS}} + end. + +get_addrs_by_family2(Fam, NumAddrs) + when (Fam =:= inet) orelse (Fam =:= inet6) -> + {ok, get_addrs_by_family3(Fam, NumAddrs)}; +get_addrs_by_family2(inet_and_inet6, NumAddrs) -> + Addrs4 = get_addrs_by_family3(inet, NumAddrs), + Addrs6 = get_addrs_by_family3(inet6, NumAddrs), + {ok, [Addrs4, Addrs6]}. + +get_addrs_by_family3(Fam, NumAddrs) -> + case net:getifaddrs(Fam) of + {ok, IFs} -> + case get_addrs_by_family4(Fam, IFs) of + Addrs when length(Addrs) >= NumAddrs -> + lists:sublist(Addrs, NumAddrs); + Addrs -> + throw({too_few_addrs, length(Addrs), NumAddrs}) + end; + {error, Reason} -> + throw({no_addresses, Reason}) + end. + +get_addrs_by_family4(Fam, IFs) -> + lists:flatten([Addr || #{flags := Flags, + addr := #{addr := Addr}} <- IFs, + lists:member(up, Flags) andalso + lists:member(running, Flags) andalso + is_good_addr(Fam, Addr)]). + +is_good_addr(inet, Addr) when tuple_size(Addr) =:= 4 -> + true; +is_good_addr(inet6, {0,0,0,0,0,16#ffff,_,_}) -> + false; %% ipv4 mapped +is_good_addr(inet6, {16#fe80,_,_,_,_,_,_,_}) -> + false; %% link-local +is_good_addr(inet6, Addr) when tuple_size(Addr) =:= 8 -> + true; +is_good_addr(_Family, _Addr) -> + false. + + +ipv4_map_addrs(InetAddrs) -> + [begin + <> = <>, + <> = <>, + {0, 0, 0, 0, 0, 16#ffff, AB, CD} + end || {A,B,C,D} <- InetAddrs]. + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +do_from_other_process(Fun) when is_function(Fun, 0) -> + Parent = self(), + Ref = make_ref(), + {_, MRef} = + spawn_monitor(fun() -> + try Fun() of + Result -> + Parent ! {Ref, Result} + catch + Class:Reason:Stacktrace -> + Parent ! {Ref, Class, Reason, Stacktrace} + end + end), + receive + {Ref, Result} -> + receive + {'DOWN', MRef, _, _, _} -> + Result + end; + {Ref, Class, Reason, Stacktrace} -> + receive + {'DOWN', MRef, _, _, _} -> + erlang:raise(Class, Reason, Stacktrace) + end; + {'DOWN',MRef, _, _, Reason} -> + erlang:exit(Reason) + end. + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +t_exchange_st_ipv4(_Config) when is_list(_Config) -> + ?TT(?MINS(2)), + Cond = fun() -> has_support_ipv4() end, + Pre = fun() -> + #{domain => inet, + num_clients => 3} + end, + TC = fun(Conf) -> + t_exchange_st(Conf) + end, + Post = fun(_) -> ok end, + tc_try(?FUNCTION_NAME, Cond, Pre, TC, Post). + +t_exchange_st_ipv6(_Config) when is_list(_Config) -> + ?TT(?MINS(2)), + Cond = fun() -> has_support_ipv6() end, + Pre = fun() -> + #{domain => inet6, + num_clients => 3} + end, + TC = fun(Conf) -> + t_exchange_st(Conf) + end, + Post = fun(_) -> ok end, + tc_try(?FUNCTION_NAME, Cond, Pre, TC, Post). + +t_exchange_mt_ipv4(_Config) when is_list(_Config) -> + ?TT(?MINS(2)), + Cond = fun() -> has_support_ipv4() end, + Pre = fun() -> + #{domain => inet, + num_clients => 3} + end, + TC = fun(Conf) -> + t_exchange_mt(Conf) + end, + Post = fun(_) -> ok end, + tc_try(?FUNCTION_NAME, Cond, Pre, TC, Post). + +t_exchange_mt_ipv6(_Config) when is_list(_Config) -> + ?TT(?MINS(2)), + Cond = fun() -> has_support_ipv6() end, + Pre = fun() -> + #{domain => inet6, + num_clients => 3} + end, + TC = fun(Conf) -> + t_exchange_mt(Conf) + end, + Post = fun(_) -> ok end, + tc_try(?FUNCTION_NAME, Cond, Pre, TC, Post). + +t_exchange_st(Conf) -> + t_exchange(Conf#{threaded => false}). + +t_exchange_mt(Conf) -> + t_exchange(Conf#{threaded => true}). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +t_exchange(Conf) -> + put(sname, "TC"), + + ?P("~s -> start server", [?FUNCTION_NAME]), + {ok, {Server, ServerSA}} = t_exc_start_server(Conf), + + ?P("~s -> start clients", [?FUNCTION_NAME]), + Clients = t_exc_start_clients(Conf, ServerSA), + + ?P("~s -> release clients", [?FUNCTION_NAME]), + t_exc_release_clients(Clients, ?TRAFFIC_RUN_TIME), + + ?P("~s -> release clients", [?FUNCTION_NAME]), + Result = t_exc_await_clients(Server, Clients, ?TRAFFIC_RUN_TIME), + + ?P("~s -> stop server", [?FUNCTION_NAME]), + t_exc_stop_server(Server), + + ?P("~s -> done when" + "~n Result: ~p msgs/msec", [?FUNCTION_NAME, Result]), + {comment, ?F("~w msgs/msec", [Result])}. + + +t_exc_start_clients(#{num_clients := NumClients}, ServerSA) -> + t_exc_start_clients(NumClients, 1, ServerSA, []). + +t_exc_start_clients(N, ID, ServerSA, Acc) when (N > 0) -> + ?P("~s -> try start client ~w", [?FUNCTION_NAME, ID]), + case socket_sctp_traffic_client:start(ID, + ServerSA, + ?TRAFFIC_DATA, + #{debug => false}) of + {ok, {{Pid, MRef}, {PortNo, AssocID}}} -> + ?P("~s -> client ~w started", [?FUNCTION_NAME, ID]), + t_exc_start_clients(N-1, ID+1, ServerSA, + [#{id => ID, + pid => Pid, + mref => MRef, + port => PortNo, + assoc_id => AssocID} | Acc]); + {error, Reason} -> + ?P("~s -> Failed starting agent ~w: " + "~n Reason: ~p", [?FUNCTION_NAME, ID, Reason]), + t_exc_stop_clients(Acc), + ct:fail("Failed starting client ~w", [ID]) + end; +t_exc_start_clients(0, _, _ServerSA, Acc) -> + lists:reverse(Acc). + +t_exc_stop_clients([]) -> + ok; +t_exc_stop_clients([#{id := ID, + pid := Pid} | Clients]) -> + ?P("~s -> try stop client ~p (~p)", [?FUNCTION_NAME, ID, Pid]), + socket_sctp_traffic_client:stop(Pid), + t_exc_stop_clients(Clients). + +t_exc_release_clients([], _) -> + ?P("~s -> all client(s) released", [?FUNCTION_NAME]), + ok; +t_exc_release_clients([#{id := ID, + pid := Pid} | Clients], RunTime) -> + ?P("~s -> release client ~p (~p)", [?FUNCTION_NAME, ID, Pid]), + socket_sctp_traffic_client:start_run(Pid, RunTime), + t_exc_release_clients(Clients, RunTime). + +t_exc_await_clients(Server, Clients, RunTime) -> + t_exc_await_clients(Server, Clients, RunTime, RunTime+?SECS(10), []). + +t_exc_await_clients(_Server, [], RunTime, _Timeout, Acc) -> + {TotCnt, _OK, 0} = + lists:foldl(fun({ok, _ID, Cnt}, {Sum, Ok, Err}) -> + {Cnt + Sum, Ok+1, Err}; + ({error, _ID, _Error}, {Sum, Ok, Err}) -> + {Sum, Ok, Err+1} + end, + {0, 0, 0}, Acc), + TotCnt div RunTime; +t_exc_await_clients({ServerPid, ServerMRef} = Server, Clients, + RunTime, Timeout, Acc) when (Timeout > 0) -> + T1 = ?TS(), + receive + {'DOWN', ServerMRef, process, ServerPid, Info} -> + ?P("~s -> Unexpected DOWN from server: " + "~n Reason: ~p", [?FUNCTION_NAME, Info]), + t_exc_stop_clients(Clients), + ct:fail("Unexpected server failure", []); + + {'DOWN', _MRef, process, Pid, {result, Cnt, Mode}} -> + {Clients2, ID} = t_exc_process_client_down(Clients, Pid), + ?P("~s -> Got result from client ~w:" + "~n Cnt: ~w" + "~n Mode: ~w", [?FUNCTION_NAME, ID, Cnt, Mode]), + T2 = ?TS(), + Timeout2 = t_exc_await_clients_next_timeout(Timeout, T1, T2), + t_exc_await_clients(Server, Clients2, RunTime, + Timeout2, + [{ok, ID, Cnt} | Acc]); + + {'DOWN', _MRef, process, Pid, Info} -> + {Clients2, ID} = t_exc_process_client_down(Clients, Pid), + ?P("~s -> Unexpected down from client ~w:" + "~n Info: ~w", [?FUNCTION_NAME, ID, Info]), + T2 = ?TS(), + Timeout2 = t_exc_await_clients_next_timeout(Timeout, T1, T2), + t_exc_await_clients(Server, Clients2, RunTime, Timeout2, + [{error, ID, Info} | Acc]) + + after Timeout -> + t_exc_stop_clients(Clients), + ct:fail("Client run timeout", []) + end; +t_exc_await_clients(Server, Clients, _RunTime, _Timeout, Acc) -> + ?P("~s -> Timeout while waiting for client results:" + "~n (remaining) Clients: ~p" + "~n Results: ~p", [?FUNCTION_NAME, Clients, Acc]), + t_exc_stop_server(Server), + t_exc_stop_clients(Clients), + ct:fail("Client timeout", []). + + +t_exc_await_clients_next_timeout(Timeout, T1, T2) -> + Timeout2 = Timeout - (T2-T1), + if + (Timeout2 > 0) -> + Timeout2; + true -> + 0 + end. + +t_exc_process_client_down(Clients, DownPid) -> + {Acc, #{id := ID}} = + lists:foldl(fun(#{pid := Pid} = Client, {Acc, undefined}) + when (Pid =:= DownPid) -> + {Acc, Client}; + (Client, {Acc, C}) -> + {[Client|Acc], C} + end, + {[], undefined}, + Clients), + {lists:reverse(Acc), ID}. + + +t_exc_start_server(Conf) -> + socket_sctp_traffic_server:start(Conf#{debug => false}). + +t_exc_stop_server({Pid, _}) -> + socket_sctp_traffic_server:stop(Pid). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Here are all the *general* test case condition functions. + +has_support_sctp() -> + case os:type() of + {win32, _} -> + skip("SCTP Not Supported"); + {unix, netbsd} -> + %% XXX We will have to investigate this later... + skip("SCTP Not Supported"); + _ -> + case socket:is_supported(sctp) of + true -> + ok; + false -> + skip("SCTP Not Supported") + end + end. + + +%% The idea is that this function shall test if the test host has +%% support for IPv4 or IPv6. If not, there is no point in running +%% corresponding tests. +%% Currently we just skip. +has_support_ipv4() -> + ?KLIB:has_support_ipv4(). + +has_support_ipv6() -> + ?KLIB:has_support_ipv6(). + +%% has_support_socket_priority() -> +%% has_support_socket_option_sock(priority). + +is_supported_socket_priority() -> + is_supported_socket_option_sock(priority). + +has_support_socket_linger() -> + has_support_socket_option_sock(linger). + +has_support_sctp_nodelay() -> + has_support_socket_option_sctp(nodelay). + + +has_support_socket_option_sock(Opt) -> + has_support_socket_option(socket, Opt). + +is_supported_socket_option_sock(Opt) -> + is_supported_socket_option(socket, Opt). + +has_support_socket_option_sctp(Opt) -> + has_support_socket_option(sctp, Opt). + + +has_support_socket_option(Level, Option) -> + case socket:is_supported(options, Level, Option) of + true -> + ok; + false -> + skip(?F("Not Supported: ~w option ~w", [Level, Option])) + end. + + +is_supported_socket_option(Level, Option) -> + socket:is_supported(options, Level, Option). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% not_supported(What) -> +%% skip({not_supported, What}). + +%% not_yet_implemented() -> +%% skip("not yet implemented"). + +skip(Reason) -> + throw({skip, Reason}). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% *** tc_try/2,3 *** +%% Case: Basically the test case name +%% TCCondFun: A fun that is evaluated before the actual test case +%% The point of this is that it can performs checks to +%% see if we shall run the test case at all. +%% For instance, the test case may only work in specific +%% conditions. +%% FCFun: The test case fun +%% tc_try(Case, TCFun) -> +%% ?TC_TRY(Case, TCFun). + +tc_try(Case, TCCondFun, TCFun) -> + ?TC_TRY(Case, TCCondFun, TCFun). + +tc_try(Case, TCCondFun, TCPreFun, TCFun, TCPostFun) -> + ?TC_TRY(Case, TCCondFun, TCPreFun, TCFun, TCPostFun). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% start_node(Name) -> +%% start_node(Name, 5000). + +%% start_node(Name, Timeout) when is_integer(Timeout) andalso (Timeout > 0) -> +%% Pa = filename:dirname(code:which(?MODULE)), +%% Args = ["-pa", Pa, +%% "-s", atom_to_list(?PROXY), "start", atom_to_list(node()), +%% "-s", "global", "sync"], +%% try ?CT_PEER(#{name => Name, +%% wait_boot => Timeout, +%% args => Args}) of +%% {ok, Peer, Node} -> +%% ?SEV_IPRINT("Started node ~p - now (global) sync", [Name]), +%% global:sync(), % Again, just in case... +%% ?SEV_IPRINT("ping proxy"), +%% pong = ?PPING(Node), +%% {Peer, Node}; +%% {error, Reason} -> +%% ?SEV_EPRINT("failed starting node ~p (=> SKIP):" +%% "~n ~p", [Name, Reason]), +%% skip(Reason) +%% catch +%% Class:Reason:Stack -> +%% ?SEV_EPRINT("Failed starting node: " +%% "~n Class: ~p" +%% "~n Reason: ~p" +%% "~n Stack: ~p", +%% [Class, Reason, Stack]), +%% skip({node_start, Class, Reason}) +%% end. + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% nowait(Config) -> +%% case lists:member({select_handle, true}, Config) of +%% true -> +%% make_ref(); +%% false -> +%% nowait +%% end. + +i(F) -> + i(F, []). + +i(F, A) -> + FStr = ?F("[~s] " ++ F, [?FTS()|A]), + io:format(user, FStr ++ "~n", []), + io:format(FStr, []). + diff --git a/lib/kernel/test/socket_sctp_traffic_client.erl b/lib/kernel/test/socket_sctp_traffic_client.erl new file mode 100644 index 000000000000..9002ea3b8ec6 --- /dev/null +++ b/lib/kernel/test/socket_sctp_traffic_client.erl @@ -0,0 +1,516 @@ +%% +%% %CopyrightBegin% +%% +%% SPDX-License-Identifier: Apache-2.0 +%% +%% Copyright Ericsson AB 2025-2025. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% + +-module(socket_sctp_traffic_client). + +-include("socket_sctp_traffic_lib.hrl"). + +-export([ + start/2, start/3, start/4, + stop/1, + start_run/2 + ]). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +-define(MSG(Pid, Info), {?MODULE, (Pid), (Info)}). + +-define(TIMEOUT, 5000). +-define(STATUS_TIMEOUT, 5000). + +-define(DEFAULT_OPTS, #{debug => false}). +-define(DEFAULT_TRAFFIC_DATA, [<<"FOOBAR">>]). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +start(ID, ServerSA) -> + start(ID, ServerSA, ?DEFAULT_TRAFFIC_DATA, ?DEFAULT_OPTS). + +start(ID, ServerSA, TrafficData) when is_list(TrafficData) -> + start(ID, ServerSA, TrafficData, ?DEFAULT_OPTS); +start(ID, ServerSA, Opts) when is_map(Opts) -> + start(ID, ServerSA, ?DEFAULT_TRAFFIC_DATA, Opts). + +start(ID, ServerSA, TrafficData, Opts0) + when is_integer(ID) andalso (ID > 0) andalso + is_map(ServerSA) andalso + is_list(TrafficData) andalso (length(TrafficData) > 0) andalso + is_map(Opts0) -> + Opts = (maps:merge(?DEFAULT_OPTS, Opts0))#{parent => self()}, + Client = {Pid, MRef} = + spawn_monitor(fun() -> + init(ID, ServerSA, TrafficData, Opts) + end), + receive + {'DOWN', MRef, process, Pid, Reason} -> + {error, Reason}; + + ?MSG(Pid, {started, PortNo, AssocID}) -> + {ok, {Client, {PortNo, AssocID}}} + + after ?TIMEOUT -> + ?ERROR("Client start timeout"), + exit(Pid, kill), + {error, timeout} + end. + +stop(Pid) when is_pid(Pid) -> + Pid ! ?MSG(self(), stop), + receive + {'DOWN', _MRef, process, Pid, {result, C, _} = RESULT} -> + if + (C =:= 0) -> + ok; + true -> + RESULT + end; + {'DOWN', _MRef, process, Pid, _} -> + ok + after ?TIMEOUT -> + ?ERROR("Client stop timeout"), + exit(Pid, kill), + ok + end. + + +start_run(Pid, RunTime) + when is_pid(Pid) andalso is_integer(RunTime) andalso (RunTime > 0) -> + Pid ! ?MSG(self(), {start_run, RunTime}), + ok. + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +init(ID, + #{family := Domain} = ServerSA, + TrafficData, + #{parent := Parent} = Opts) -> + + set_debug(Opts), + ?SET_SNAME(?F("client[~w]", [ID])), + + ?DBG("try find local (~w) addr", [Domain]), + Addr = + case ?WHICH_LOCAL_ADDR(Domain) of + {ok, A} -> + A; + {error, AReason} -> + ?ERROR("Failed find local addr:" + "~n ~p", [AReason]), + exit({which_local_addr, AReason}) + end, + + ?DBG("try open socket"), + Sock = + case socket:open(Domain, seqpacket, sctp) of + {ok, S} -> + S; + {error, OReason} -> + ?ERROR("Failed open socket" + "~n ~p", [OReason]), + exit({open, OReason}) + end, + + ?DBG("try bind"), + SockAddr = #{family => Domain, + addr => Addr, + port => 0}, + case socket:bind(Sock, SockAddr) of + ok -> + ok; + {error, BReason} -> + ?ERROR("Failed bind: " + "~n SockAddr: ~p" + "~n Reason: ~p", [BReason]), + (catch socket:close(Sock)), + exit({bind, BReason}) + end, + + ?DBG("try setopt events"), + case socket:setopt(Sock, ?MK_SCTP_SOCKOPT(events), ?SCTP_EVENTS(true)) of + ok -> + ok; + {error, SReason} -> + ?ERROR("Failed setopt 'events': " + "~n ~p", [SReason]), + (catch socket:close(Sock)), + exit({setopt, events, SReason}) + end, + + ?DBG("try sockname (get port number)"), + Port = + case socket:sockname(Sock) of + {ok, #{port := P}} -> + P; + {error, PReason} -> + ?ERROR("Failed sockname: " + "~n ~p", [PReason]), + (catch socket:close(Sock)), + exit({sockname, PReason}) + end, + + ?DBG("try connect to server:" + "~n Server SockAddr: ~p", [ServerSA]), + case socket:connect(Sock, ServerSA) of + ok -> + ?DBG("connect ok"), + ok; + {error, CReason} -> + ?ERROR("Failed connect: " + "~n Server SockAddr: ~p" + "~n Reason: ~p", [CReason]), + (catch socket:close(Sock)), + exit({connect, CReason}) + end, + + ?DBG("await connect confirmation"), + AssocID = + case socket:recvmsg(Sock) of + {ok, #{flags := Flags, + addr := SA, + notification := #{type := assoc_change, + state := comm_up, + assoc_id := AID}}} -> + ?DBG("received expected connect confirmation:" + "~n Flags: ~p" + "~n SA: ~p" + "~n AssocID: ~p", + [Flags, SA, AID]), + AID; + {ok, #{flags := Flags, + addr := SA, + notification := Notif}} -> + ?ERROR("Received unexpected notification: " + "~n Flags: ~p" + "~n SA: ~p" + "~n Notif: ~p", + [Flags, SA, Notif]), + (catch socket:close(Sock)), + exit({unexpected_notif, Flags, SA, Notif}); + {ok, Msg} -> + ?ERROR("Received unexpected msg: " + "~n ~p", [Msg]), + (catch socket:close(Sock)), + exit({unexpected_msg, Msg}); + {error, RReason} -> + ?ERROR("Unexpected recvmsg failure: " + "~n ~p", [RReason]), + (catch socket:close(Sock)), + exit({unexpected_recvmsg_failure, RReason}) + end, + + ?DBG("monitor parent"), + MRef = erlang:monitor(process, Parent), + + ?DBG("started - inform parent (port number ~p, assoc id ~w)", + [Port, AssocID]), + Parent ! ?MSG(self(), {started, Port, AssocID}), + + ?DBG("init done"), + loop(Opts#{parent_mref => MRef, + msgs_data => TrafficData, + mode => ready, + sock => Sock, + port => Port, + assoc_id => AssocID, + stream => 3, + select => undefined, + status => start_status_timer(), + %% We do nothing until we get the 'start' order + %% (except check socket status) + run => undefined, + seq => 1, + cnt => 0, + msg => undefined}). + + +loop(#{mode := ready = Mode, + sock := Sock, + assoc_id := AssocID, + parent := Parent, + parent_mref := MRef, + run := undefined} = State) -> + receive + ?MSG(Parent, {start_run, RunTime}) -> + ?INFO("start run: ~w", [RunTime]), + TRef = start_run_timer(RunTime), + loop(State#{run => TRef, mode => send}); + + {'DOWN', MRef, process, Parent, Reason} -> + ?ERROR("Unexpected parent termination: " + "~n ~p" + "~nwhen" + "~n Mode: ~p", [Reason, Mode]), + (catch socket:close(Sock)), + exit({parent, Reason, Mode}); + + {timeout, _OldTRef, status_check} -> + ?INFO("Socket Status: " + "~n ~p", [?WHICH_SCTP_STATUS(Sock, AssocID)]), + loop(State#{status => start_status_timer()}); + + + ?MSG(Parent, stop) -> + ?INFO("Received stop command"), + graceful_assoc_shutdown(Sock, AssocID), + (socket:close(Sock)), + exit(normal) + + end; + +%% Time to (try to) send a request +loop(#{mode := send, + msgs_data := [MsgData|MsgsData], + sock := Sock, + assoc_id := AssocID, + stream := Stream, + select := undefined, + cnt := Cnt, + seq := Seq, + msg := undefined} = State) -> + ?DBG("try (build message and) send"), + SRI = #{assoc_id => AssocID, stream => Stream}, + CtrlSRI = #{level => sctp, + type => sndrcv, + value => SRI}, + Msg = #{iov => [?MK_REQUEST(Seq, MsgData)], + ctrl => [CtrlSRI]}, + case socket:sendmsg(Sock, Msg, nowait) of + ok -> + ?DBG("sent -> enter recv mode"), + loop(State#{msgs_data => lists:append(MsgsData, [MsgData]), + mode => recv}); + + {select, SelectInfo} -> + ?DBG("select"), + loop(State#{msgs_data => lists:append(MsgsData, [MsgData]), + select => SelectInfo, + msg => Msg}); + + {error, Reason} -> + ?ERROR("Failed sending request: " + "~n Cnt: ~w" + "~n Seq: ~w" + "~n Reason: ~p", [Cnt, Seq, Reason]), + exit({sendmsg, Reason}) + end; +loop(#{mode := send, + sock := Sock, + assoc_id := _AssocID, + stream := _Stream, + select := undefined, + cnt := Cnt, + seq := Seq, + msg := Msg} = State) -> + ?DBG("try send"), + case socket:sendmsg(Sock, Msg, nowait) of + ok -> + ?DBG("sent -> enter recv mode"), + loop(State#{mode => recv, + msg => undefined}); + + {select, SelectInfo} -> + ?DBG("select"), + loop(State#{select => SelectInfo}); + + {error, Reason} -> + ?ERROR("Failed sending request: " + "~n Cnt: ~w" + "~n Seq: ~w" + "~n Reason: ~p", [Cnt, Seq, Reason]), + exit({sendmsg, Reason}) + end; + +loop(#{mode := recv, + sock := Sock, + assoc_id := AssocID, + select := undefined, + cnt := Cnt, + seq := Seq} = State) -> + ?DBG("try recv"), + case socket:recvmsg(Sock, nowait) of + {ok, #{flags := _, + addr := _, + iov := [Data], + ctrl := [#{level := sctp, + type := sndrcv, + value := #{assoc_id := AssocID, + stream := _}}]}} -> + ?DBG("data message received - verify"), + case verify_reply(Seq, Data) of + ok -> + loop(State#{mode => send, + cnt => Cnt + 1, + seq => next_seq(Seq)}); + {error, Reason} -> + ?ERROR("Received invalid message: " + "~n Cnt: ~p" + "~n Reason: ~p", [Cnt, Reason]), + exit({recvmsg, verification, Reason}) + end; + + {ok, #{notification := #{type := shutdown_event, + assoc_id := AssocID}}} -> + ?ERROR("Received unexpected shutdown event for assoc ~w", + [AssocID]), + exit(unexpected_shutdown); + + {select, SelectInfo} -> + ?DBG("select"), + loop(State#{select => SelectInfo}); + + {error, Reason} -> + ?ERROR("Failed sending request: " + "~n Cnt: ~w" + "~n Seq: ~w" + "~n Reason: ~p", [Cnt, Seq, Reason]), + exit({sendmsg, Reason}) + end; + +loop(#{mode := Mode, + parent := Parent, + parent_mref := MRef, + sock := Sock, + assoc_id := AssocID, + select := {select_info, _, SelectHandle} = SelectInfo, + cnt := Cnt, + seq := Seq} = State) -> + ?DBG("await select message"), + receive + {'DOWN', MRef, process, Parent, Reason} -> + ?ERROR("Unexpected parent termination: " + "~n ~p" + "~nwhen" + "~n Mode: ~p", [Reason, Mode]), + (catch socket:close(Sock)), + exit({parent, Reason, Mode}); + + {'$socket', Sock, select, SelectHandle} -> + ?DBG("received select message"), + loop(State#{select => undefined}); + + {timeout, _OldTRef, run} -> + ?INFO("Received run timeout when: " + "~n Mode: ~p" + "~n Cnt: ~p" + "~n Seq: ~p" + "~n Socket Status: ~p", + [Mode, Cnt, Seq, ?WHICH_SCTP_STATUS(Sock, AssocID)]), + socket:cancel(Sock, SelectInfo), + graceful_assoc_shutdown(Sock, AssocID), + (socket:close(Sock)), + exit({result, Cnt, Mode}); + + {timeout, _OldTRef, status_check} -> + ?INFO("Socket Status: " + "~n ~p", [?WHICH_SCTP_STATUS(Sock, AssocID)]), + loop(State#{status => start_status_timer()}); + + + ?MSG(Parent, stop) -> + ?INFO("Received stop command"), + socket:cancel(Sock, SelectInfo), + graceful_assoc_shutdown(Sock, AssocID), + (socket:close(Sock)), + exit({result, Cnt, Mode}) + + end. + + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +verify_reply(Seq, + <<(?PROTO_TAG):32, + (?REPLY_TAG):32, + (Seq):32, + PayloadSz:32, + CheckSum:32, + Payload:PayloadSz/binary>>) -> + case erlang:crc32(Payload) of + CheckSum -> + ok; + BadCheckSum -> + {error, {bad_checksum, CheckSum, BadCheckSum}} + end; +verify_reply(_, _) -> + {error, bad_reply}. + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +graceful_assoc_shutdown(Sock, AssocID) -> + EofSRI = #{assoc_id => AssocID, + stream => 0, + ssn => 0, + flags => [eof], + ppid => 0, + context => 0, + time_to_live => 0, + tsn => 0, + cum_tsn => 0}, + EofMsg = #{iov => [], + ctrl => [#{level => sctp, + type => sndrcv, + value => EofSRI}]}, + case socket:sendmsg(Sock, EofMsg) of + ok -> + ok; + {error, Reason} -> + ?ERROR("Failed sending (graceful shutdown) eof message:" + "~n ~p", [Reason]), + error + end. + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +start_run_timer(Time) -> + start_timer(Time, run). + +start_status_timer() -> + start_timer(?STATUS_TIMEOUT, status_check). + +start_timer(Time, Msg) -> + erlang:start_timer(Time, self(), Msg). + +%% stop_timer(TRef) -> +%% erlang:cancel_timer(TRef). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +next_seq(CurrSeq) when CurrSeq < 16#FFFFFFFF -> + CurrSeq; +next_seq(_) -> + 1. + + +set_debug(#{debug := Debug}) when is_boolean(Debug) -> + ?SET_DEBUG(Debug); +set_debug(_) -> + ?SET_DEBUG(false). + diff --git a/lib/kernel/test/socket_sctp_traffic_lib.erl b/lib/kernel/test/socket_sctp_traffic_lib.erl new file mode 100644 index 000000000000..9f62e0e64a3a --- /dev/null +++ b/lib/kernel/test/socket_sctp_traffic_lib.erl @@ -0,0 +1,58 @@ +%% +%% %CopyrightBegin% +%% +%% SPDX-License-Identifier: Apache-2.0 +%% +%% Copyright Ericsson AB 2025-2025. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% + +-module(socket_sctp_traffic_lib). + +-export([ + set_debug/1, get_debug/0, + print_dbg/2, print_info/2, print_warning/2, print_error/2 + ]). + +-include("kernel_test_lib.hrl"). + +set_debug(D) when is_boolean(D) -> + put({debug, ?MODULE}, (D)). + +get_debug() -> + get({debug, ?MODULE}). + + +print_dbg(F, A) -> + print(get_debug(), "DEBUG", F, A). + +print_info(F, A) -> + print(true, "INFO", F, A). + +print_warning(F, A) -> + print(true, "WARNING", F, A). + +print_error(F, A) -> + print(true, "ERROR", F, A). + +print(true, Pre, F, A) -> + SName = get(sname), + io:format("~s [~s, ~p, ~s] " ++ F ++ "~n", + [Pre, ?FTS(), self(), SName | A]); +print(_, _, _, _) -> + ok. + + diff --git a/lib/kernel/test/socket_sctp_traffic_lib.hrl b/lib/kernel/test/socket_sctp_traffic_lib.hrl new file mode 100644 index 000000000000..4211f591107f --- /dev/null +++ b/lib/kernel/test/socket_sctp_traffic_lib.hrl @@ -0,0 +1,97 @@ +%% +%% %CopyrightBegin% +%% +%% SPDX-License-Identifier: Apache-2.0 +%% +%% Copyright Ericsson AB 2025-2025. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% + +-ifndef(socket_sctp_traffic_lib_hrl). +-define(socket_sctp_traffic_lib_hrl, true). + +-include("kernel_test_lib.hrl"). + +-define(TLIB, socket_sctp_traffic_lib). + + +%% Message: +%% <> +-define(PROTO_TAG, 16#42004200). +-define(REQUEST_TAG, 16#0000FFFF). +-define(REPLY_TAG, 16#FFFF0000). + +-define(MK_MSG(Type, Seq, Payload), + <<(?PROTO_TAG):32, + (Type):32, + (Seq):32, + (byte_size(Payload)):32, + (erlang:crc32(Payload)):32, + Payload/binary>>). +-define(MK_REQUEST(Seq, Payload), ?MK_MSG(?REQUEST_TAG, Seq, Payload)). +-define(MK_REPLY(Seq, Payload), ?MK_MSG(?REPLY_TAG, Seq, Payload)). + +-define(SET_SNAME(STR), put(sname, (STR))). +-define(SET_DEBUG(D), ?TLIB:set_debug((D))). + +-define(DBG(F), ?DBG(F, [])). +-define(DBG(F, A), ?TLIB:print_dbg("~s -> " ++ F, [?FUNCTION_NAME | A])). +-define(INFO(F), ?INFO(F, [])). +-define(INFO(F, A), ?TLIB:print_info(F, A)). +-define(WARNING(F), ?WARNING(F, [])). +-define(WARNING(F, A), ?TLIB:print_warning(F, A)). +-define(ERROR(F), ?ERROR(F, [])). +-define(ERROR(F, A), ?TLIB:print_error(F, A)). + + +-define(MK_SOCKOPT(Lvl, Opt), {(Lvl), (Opt)}). +-define(MK_OTP_SOCKOPT(Opt), ?MK_SOCKOPT(otp, (Opt))). +-define(MK_SOCK_SOCKOPT(Opt), ?MK_SOCKOPT(socket, (Opt))). +-define(MK_IP_SOCKOPT(Opt), ?MK_SOCKOPT(ip, (Opt))). +-define(MK_IPV6_SOCKOPT(Opt), ?MK_SOCKOPT(ipv6, (Opt))). +-define(MK_SCTP_SOCKOPT(Opt), ?MK_SOCKOPT(sctp, (Opt))). + +-define(WHICH_SCTP_STATUS(Sock, AID), + case socket:getopt(Sock, + ?MK_SCTP_SOCKOPT(status), + #{assoc_id => AID}) of + {ok, Status} -> Status; + {error, closed} -> closed; + {error, _} -> undefined + end). + +-define(SCTP_EVENTS(DataIO), + #{data_io => (DataIO), + association => true, + address => true, + send_failure => true, + peer_error => true, + shutdown => true, + partial_delivery => true, + adaptation_layer => false, + authentication => false, + sender_dry => false}). + + +%% -define(WHICH_LOCAL_ADDR(D), kernel_test_lib:which_local_addr((D))). + + +-endif. % -ifdef(socket_sctp_traffic_lib_hrl). diff --git a/lib/kernel/test/socket_sctp_traffic_server.erl b/lib/kernel/test/socket_sctp_traffic_server.erl new file mode 100644 index 000000000000..f0b238429a3b --- /dev/null +++ b/lib/kernel/test/socket_sctp_traffic_server.erl @@ -0,0 +1,557 @@ +%% +%% %CopyrightBegin% +%% +%% SPDX-License-Identifier: Apache-2.0 +%% +%% Copyright Ericsson AB 2025-2025. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% + +-module(socket_sctp_traffic_server). + +-include("socket_sctp_traffic_lib.hrl"). + +-export([ + start/0, start/1, start/2, + stop/1 + ]). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +-define(MSG(Pid, Info), {?MODULE, (Pid), (Info)}). + +-define(TIMEOUT, 5000). + +-define(DEFAULT_OPTS, #{domain => inet, + debug => false, + %% Threaded: + %% true: The server spawns a handler for every + %% connection (who perform peeloff) + %% false: The server handles all traffic itself. + threaded => true}). +-define(STATUS_TIMEOUT, 5000). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +start() -> + start(0). + +start(PortNo) when is_integer(PortNo) andalso (PortNo >= 0) -> + start(PortNo, ?DEFAULT_OPTS); +start(Opts) when is_map(Opts) -> + start(0, Opts). + +start(PortNo, Opts0) + when is_integer(PortNo) andalso (PortNo >= 0) andalso is_map(Opts0) -> + Opts = (maps:merge(?DEFAULT_OPTS, Opts0))#{parent => self()}, + Server = {Pid, MRef} = spawn_monitor(fun() -> init(Opts) end), + receive + {'DOWN', MRef, process, Pid, Reason} -> + {error, Reason}; + + ?MSG(Pid, {started, ServerSA}) -> + {ok, {Server, ServerSA}} + + after ?TIMEOUT -> + ?ERROR("Server start timeout: " + "~n MQ: ~p", [?MQ()]), + exit(Pid, kill), + {error, timeout} + end. + +stop(Pid) when is_pid(Pid) -> + Pid ! ?MSG(self(), stop), + receive + {'DOWN', _MRef, process, Pid, _} -> + ok + after ?TIMEOUT -> + ?ERROR("Server stop timeout"), + exit(Pid, kill), + ok + end. + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +init(#{parent := Parent, + domain := Domain, + threaded := Threaded} = State) -> + + set_debug(State), + ?SET_SNAME("server"), + + ?DBG("try find local (~w) addr", [Domain]), + Addr = + case ?WHICH_LOCAL_ADDR(Domain) of + {ok, A} -> + ?DBG("A: ~p", [A]), + A; + {error, AReason} -> + ?ERROR("Failed find local addr:" + "~n ~p", [AReason]), + exit({local_addr, AReason}) + end, + + ?DBG("try open"), + Sock = case socket:open(Domain, seqpacket, sctp) of + {ok, S} -> + S; + {error, OReason} -> + ?ERROR("Failed open:" + "~n ~p", [OReason]), + exit({open_failed, OReason}) + end, + + SockAddr0 = #{family => Domain, + addr => Addr, + port => 0}, + ?DBG("try bind to:" + "~n ~p", [SockAddr0]), + case socket:bind(Sock, SockAddr0) of + ok -> + ok; + {error, BReason} -> + ?ERROR("Failed bind:" + "~n ~p", [BReason]), + exit({bind_failed, BReason}) + end, + + + ?DBG("try make listen socket"), + case socket:listen(Sock, true) of + ok -> + ok; + {error, LReason} -> + ?ERROR("Failed listen:" + "~n ~p", [LReason]), + exit({listen, LReason}) + end, + + ?DBG("try set 'events' (sctp) socket option"), + case socket:setopt(Sock, + ?MK_SCTP_SOCKOPT(events), + ?SCTP_EVENTS(not Threaded)) of + ok -> + ok; + {error, SReason} -> + ?ERROR("Failed setopt events:" + "~n ~p", [SReason]), + exit({setopt_events, SReason}) + end, + + ?DBG("try sockname (get port number)"), + SockAddr = + case socket:sockname(Sock) of + {ok, SA} -> + SA; + {error, NReason} -> + ?ERROR("Failed sockname:" + "~n ~p", [NReason]), + exit({sockname, NReason}) + end, + + ?DBG("monitor parent"), + MRef = erlang:monitor(process, Parent), + + ?DBG("inform parent (sockaddr: ~p)", [SockAddr]), + Parent ! ?MSG(self(), {started, SockAddr}), + + ?INFO("started"), + loop(State#{parent_mref => MRef, + sock => Sock, + connections => #{}, + select => undefined, + %% Only use the id if threaded + id => 1}). + + +loop(#{sock := Sock, + select := undefined} = State) when (Sock =/= undefined) -> + ?DBG("try recv"), + case socket:recvmsg(Sock, nowait) of + {ok, Msg} -> + NewState = handle_msg(State, Msg), + loop(NewState); + + {select, SelectInfo} -> + loop(State#{select => SelectInfo}); + + {error, Reason} -> + ?ERROR("Failed recvmsg: " + "~n ~p", [Reason]), + exit({recvmsg, Reason}) + end; + +loop(#{parent := Parent, + parent_mref := MRef, + sock := Sock, + select := {select_info, _, SelectHandle} = SelectInfo} = State) -> + ?DBG("await select message"), + receive + {'$socket', Sock, select, SelectHandle} -> + ?DBG("received select message"), + loop(State#{select => undefined}); + + {'DOWN', MRef, process, Parent, Info} -> + ?ERROR("Received unexpected DOWN from parent:" + "~n ~p", [Info]), + (catch socket:cancel(Sock, SelectInfo)), + (catch socket:close(Sock)), + exit({parent, Info}); + + ?MSG(Parent, stop) -> + ?INFO("Received close request"), + (catch socket:cancel(Sock, SelectInfo)), + (catch socket:close(Sock)), + exit(normal) + end. + + +handle_msg(State, + #{notification := Notif}) -> + handle_notification(State, Notif); + +handle_msg(#{threaded := false} = State, + #{iov := [Data], + ctrl := [#{level := sctp, + type := sndrcv, + value := #{assoc_id := AssocID, + stream := Stream}}]}) -> + handle_data(State, Data, AssocID, Stream); +handle_msg(_State, Msg) -> + ?ERROR("Received unexpected message:" + "~n ~p", [Msg]), + exit(unexpected_msg). + + +handle_notification(#{threaded := false = _Threaded, + connections := Connections} = State, + #{type := assoc_change, + state := comm_up, + assoc_id := AssocID}) -> + ?INFO("Received assoc-change(comm-up) for assoc ~w", [AssocID]), + NewConnections = Connections#{AssocID => #{state => comm_up}}, + State#{connections => NewConnections}; +handle_notification(#{sock := Sock, + threaded := true = _Threaded, + connections := Connections, + id := ID} = State, + #{type := assoc_change, + state := comm_up, + assoc_id := AssocID}) -> + ?INFO("Received assoc-change(comm-up) for assoc ~w", [AssocID]), + {Pid, MRef} = handler_start(State, ID, Sock, AssocID), + NewConnections = Connections#{AssocID => #{state => comm_up, + handler => Pid}, + Pid => #{id => ID, + mref => MRef, + assoc_id => AssocID}}, + State#{connections => NewConnections, + id => ID + 1}; +%% Is this just a race? +handle_notification(#{connections := Connections} = State, + #{type := peer_addr_change, + state := addr_available, + assoc_id := AssocID}) -> + case Connections of + #{AssocID := _} -> + ?INFO("Received expected peer-addr-change(available) for assoc ~w", + [AssocID]), + State; + _ -> + ?INFO("Received peer-addr-change(available) for unknown assoc ~w", + [AssocID]), + State + end; +handle_notification(#{connections := Connections} = State, + #{type := peer_addr_change, + state := addr_confirmed, + assoc_id := AssocID}) -> + case Connections of + #{AssocID := _} -> + ?INFO("Received peer-addr-change(confirmed) for assoc ~w", + [AssocID]), + State; + _ -> + ?WARNING("Received peer-addr-change(confirmed) " + "for unknown assoc ~w", [AssocID]), + State + end; +handle_notification(#{threaded := false, + connections := Connections} = State, + #{type := shutdown_event, + assoc_id := AssocID}) -> + case Connections of + #{AssocID := Conn} -> + ?INFO("Received shutdown event for assoc ~w", [AssocID]), + NewConn = Conn#{state => shutdown_begin}, + NewConnections = Connections#{AssocID => NewConn}, + State#{connections => NewConnections}; + _ -> + ?WARNING("Received shutdown event for unknown assoc ~w", [AssocID]), + State + end; +handle_notification(#{threaded := false, + connections := Connections} = State, + #{type := assoc_change, + state := shutdown_comp, + assoc_id := AssocID}) -> + case Connections of + #{AssocID := Conn} -> + %% We should really delete the assoc here + ?INFO("Received assoc-change(shutdown-complete) event for assoc ~w", + [AssocID]), + NewConn = Conn#{state => shutdown_complete}, + NewConnections = Connections#{AssocID => NewConn}, + State#{connections => NewConnections}; + _ -> + ?WARNING("Received assoc-change(shutdown-complete) " + "event for unknown assoc ~w", [AssocID]), + State + end; + +handle_notification(#{threaded := false, + connections := Connections} = State, + #{type := assoc_change, + state := comm_lost, + assoc_id := AssocID, + error := Error}) -> + case Connections of + #{AssocID := Conn} -> + %% We should really delete the assoc here + ?INFO("Received assoc-change(comm-lost) event for assoc ~w", + [AssocID]), + NewConn = Conn#{state => comm_lost}, + NewConnections = Connections#{AssocID => NewConn}, + State#{connections => NewConnections}; + _ -> + ?WARNING("Received assoc-change(comm-lost) " + "event for unknown assoc ~w:" + "~n Error: ~w", [AssocID, Error]), + State + end; + +handle_notification(#{sock := Sock} = _State, Notif) -> + ?ERROR("Received unexpected notification:" + "~n ~p", [Notif]), + (catch socket:close(Sock)), + exit(unexpected_notification). + + +%% ======================================================================== + +handler_start(#{parent := Parent, + parent_mref := PMRef} = State0, ID, Sock, AssocID) -> + + Self = self(), + State1 = maps:remove(threaded, State0), + State2 = maps:remove(connections, State1), + State3 = State2#{select => undefined}, + Handler = {Pid, MRef} = + erlang:spawn_monitor( + fun() -> + handler_init(State3, ID, Sock, AssocID, Self) + end), + receive + ?MSG(Pid, started) -> + ?DBG("Handler started"), + Handler; + + {'DOWN', PMRef, process, Parent, Reason} -> + ?ERROR("Received unexpected down from parent:" + "~n ~p", [Reason]), + exit({parent, Reason}); + + {'DOWN', MRef, process, Pid, Reason} -> + ?ERROR("Received unexpected down from handler:" + "~n ~p", [Reason]), + exit({handler, AssocID, Reason}) + + end. + + +handler_init(State, ID, Sock, AssocID, Parent) -> + + set_debug(State), + ?SET_SNAME(?F("handler[~w,~w]", [ID, AssocID])), + + ?DBG("try peeloff"), + NewSock = + case socket:peeloff(Sock, AssocID) of + {ok, S} -> + S; + {error, Reason} -> + ?ERROR("Failure peeloff:" + "~n ~p", [Reason]), + exit({peeloff, Reason}) + end, + + ?DBG("inform parent started"), + Parent ! ?MSG(self(), started), + + ?DBG("monitor parent"), + MRef = erlang:monitor(process, Parent, []), + + ?INFO("started"), + handler_loop(State#{id => ID, + parent => Parent, + parent_mref => MRef, + sock => NewSock, + assoc_id => AssocID, + select => undefined}). + + +handler_loop(#{sock := Sock, + select := undefined} = State) -> + case socket:recvmsg(Sock, nowait) of + {ok, Msg} -> + NewState = handler_handle_msg(State, Msg), + handler_loop(NewState); + + {select, SelectInfo} -> + ?DBG("select"), + handler_loop(State#{select => SelectInfo}); + + {error, closed} -> + ?INFO("Socket closed - terminating"), + exit(normal); + + {error, Reason} -> + ?ERROR("Failed recvmsg:" + "~n ~p", [Reason]), + exit({recvmsg, Reason}) + end; + +handler_loop( + #{parent := Parent, + parent_mref := MRef, + sock := Sock, + select := {select_info, _, SelectHandle} = SelectInfo} = State) -> + ?DBG("await select"), + receive + {'$socket', Sock, select, SelectHandle} -> + ?DBG("Received select message"), + handler_loop(State#{select => undefined}); + + {'$socket', Sock, abort, Info} -> + ?WARNING("Received unexpected abort message: " + "~n ~p", [Info]), + handler_loop(State#{select => undefined}); + + {'DOWN', MRef, process, Parent, Reason} -> + ?ERROR("Received unexpected down from server:" + "~n ~p", [Reason]), + (catch socket:cancel(Sock, SelectInfo)), + (catch socket:close(Sock)), + exit({server, Reason}) + + end. + +handler_handle_msg( + State, + #{flags := _, + addr := _SA, + iov := [Data], + ctrl := [#{level := sctp, + type := sndrcv, + value := #{assoc_id := AssocID, + stream := Stream}}]}) -> + handle_data(State, Data, AssocID, Stream); +handler_handle_msg( + #{assoc_id := AssocID} = State, + #{flags := _, + addr := _SA, + iov := [Data], + ctrl := []}) -> + ?DBG("Received data message without SRI"), + handle_data(State, Data, AssocID, 0); +handler_handle_msg( + State, + #{flags := _, % Should contain the 'notification' flag + notification := Notif}) -> + handler_handle_notification(State, Notif), + State; +handler_handle_msg(_State, Msg) -> + ?ERROR("Received unknown message:" + "~n ~p", [Msg]), + exit(unexpected_msg). + +handler_handle_notification(#{sock := Sock} = _State, + #{type := assoc_change, + state := shutdown_comp, + assoc_id := AssocID}) -> + ?INFO("Received assoc-change(shutdown-comp) for assoc ~w", [AssocID]), + %% We should really wait for the socket close... + (catch socket:close(Sock)), + exit(normal); +handler_handle_notification(State, + #{type := shutdown_event, + assoc_id := AssocID}) -> + ?INFO("Received shutdown-event for assoc ~w", [AssocID]), + State#{state => shutdown}; +handler_handle_notification(State, + #{type := peer_addr_change, + state := PState, + assoc_id := AssocID}) -> + ?INFO("Received peer-addr-change(~w) for assoc ~w", [PState, AssocID]), + State. + + +%% ======================================================================== + +handle_data(#{sock := Sock} = State, + <<(?PROTO_TAG):32, + (?REQUEST_TAG):32, + Seq:32, + PayloadSz:32, + CheckSum:32, + Payload:PayloadSz/binary>>, + AssocID, Stream) -> + case erlang:crc32(Payload) of + CheckSum -> + %% We should really do something more creative, + %% but will have to do for now... + Data = <<(?PROTO_TAG):32, + (?REPLY_TAG):32, + Seq:32, + PayloadSz:32, + CheckSum:32, + Payload:PayloadSz/binary>>, + SRI = #{assoc_id => AssocID, stream => Stream}, + CtrlSRI = #{level => sctp, + type => sndrcv, + value => SRI}, + Msg = #{iov => [Data], + ctrl => [CtrlSRI]}, + %% We should change mode here + %% (to send, and put the msg in a "buffer") + %% but we make it easy on ourselves and just send... + ok = socket:sendmsg(Sock, Msg), + State; + BadCheckSum -> + exit({bad_checksum, CheckSum, BadCheckSum}) + end. + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +set_debug(#{debug := Debug}) when is_boolean(Debug) -> + ?SET_DEBUG(Debug); +set_debug(_) -> + ?SET_DEBUG(false). + diff --git a/lib/kernel/test/socket_test_lib.erl b/lib/kernel/test/socket_test_lib.erl index 6bdbb1470054..e97e89fb2a16 100644 --- a/lib/kernel/test/socket_test_lib.erl +++ b/lib/kernel/test/socket_test_lib.erl @@ -45,6 +45,22 @@ %% Generic 'has support' test function(s) has_support_ipv4/0, has_support_ipv6/0, + has_support_socket_option/2, + has_support_socket_option_sock/1, + has_support_socket_option_ip/1, + has_support_socket_option_ipv6/1, + has_support_socket_option_tcp/1, + has_support_socket_option_udp/1, + has_support_socket_option_sctp/1, + is_any_options_supported/1, + + %% OS/Platform checks + is_good_enough_platform/3, + is_not_freebsd/0, + is_not_openbsd/0, + is_not_netbsd/0, + is_not_darwin/0, + is_not_platform/2, mk_unique_path/0, which_local_host_info/1, @@ -211,6 +227,82 @@ has_support_ipv6() -> end. +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% --- General purpose socket option test functions --- + +has_support_socket_option_sock(Opt) -> + has_support_socket_option(socket, Opt). + +has_support_socket_option_ip(Opt) -> + has_support_socket_option(ip, Opt). + +has_support_socket_option_ipv6(Opt) -> + has_support_socket_option(ipv6, Opt). + +has_support_socket_option_tcp(Opt) -> + has_support_socket_option(tcp, Opt). + +has_support_socket_option_udp(Opt) -> + has_support_socket_option(udp, Opt). + +has_support_socket_option_sctp(Opt) -> + has_support_socket_option(sctp, Opt). + + +has_support_socket_option(Level, Option) -> + case socket:is_supported(options, Level, Option) of + true -> + ok; + false -> + skip(f("Not Supported: ~w option ~w", [Level, Option])) + end. + +is_any_options_supported(Options) -> + Pred = fun({Level, Option}) -> + socket:is_supported(options, Level, Option) + end, + lists:any(Pred, Options). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +is_good_enough_platform(Family, Name, CondVsn) -> + case os:type() of + {Family, Name} -> + ID = fun() -> f("~w:~w", [Family, Name]) end, + is_good_enough_platform2(os:version(), CondVsn, ID); + _ -> + ok + end. + +is_good_enough_platform2(Vsn, CondVsn, _) when (Vsn > CondVsn) -> + ok; +is_good_enough_platform2(Vsn, CondVsn, ID) -> + skip(f("Not 'good enough' ~s (~p <= ~p)", [ID(), Vsn, CondVsn])). + + +is_not_freebsd() -> + is_not_platform(freebsd, "FreeBSD"). + +is_not_openbsd() -> + is_not_platform(openbsd, "OpenBSD"). + +is_not_netbsd() -> + is_not_platform(netbsd, "NetBSD"). + +is_not_darwin() -> + is_not_platform(darwin, "Darwin"). + +is_not_platform(Platform, PlatformStr) + when is_atom(Platform) andalso is_list(PlatformStr) -> + case os:type() of + {unix, Platform} -> + skip("This does not work on " ++ PlatformStr); + _ -> + ok + end. + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% diff --git a/lib/observer/test/observer_SUITE.erl b/lib/observer/test/observer_SUITE.erl index bcde07c736eb..5c66c448afcb 100644 --- a/lib/observer/test/observer_SUITE.erl +++ b/lib/observer/test/observer_SUITE.erl @@ -249,7 +249,8 @@ basic(Config) when is_list(Config) -> ?P("basic -> try verify observer stopped"), ProcsAfter = processes(), NumProcsAfter = length(ProcsAfter), - if NumProcsAfter =/= NumProcsBefore -> + if + (NumProcsAfter > NumProcsBefore) -> BeforeNotAfter = ProcsBefore -- ProcsAfter, AfterNotBefore = ProcsAfter -- ProcsBefore, ?P("basic -> *not* fully stopped:" @@ -408,7 +409,7 @@ test_page(Title, Window) -> process_win(suite) -> []; process_win(doc) -> [""]; process_win(Config) when is_list(Config) -> - ?P("process_win -> entry"), + ?P("~s -> entry", [?FUNCTION_NAME]), % Stop SASL if already started SaslStart = case whereis(sasl_sup) of undefined -> false; @@ -416,13 +417,27 @@ process_win(Config) when is_list(Config) -> true end, % Define custom sasl and log_mf_h app vars + ?P("~s -> (application) env setup", [?FUNCTION_NAME]), Privdir=?config(priv_dir,Config), application:set_env(sasl, sasl_error_logger, tty), application:set_env(sasl, error_logger_mf_dir, Privdir), application:set_env(sasl, error_logger_mf_maxbytes, 1000), application:set_env(sasl, error_logger_mf_maxfiles, 5), application:start(sasl), + ?P("~s -> try start observer when" + "~n whereis(observer): ~p" + "~n observer info: " + "~n ~p", + [?FUNCTION_NAME, + erlang:whereis(observer), + case erlang:whereis(observer) of + undefined -> + undefined; + Pid when is_pid(Pid) -> + erlang:process_info(Pid) + end]), ok = observer:start(), + ?P("~s -> whitebox testing setup", [?FUNCTION_NAME]), ObserverNB = setup_whitebox_testing(), Parent = get_top_level_parent(ObserverNB), % Activate log view @@ -440,15 +455,17 @@ process_win(Config) when is_list(Config) -> end, [_|_] = [Check(N) || N <- lists:seq(1, Count)], PIPid ! #wx{event=#wxClose{type=close_window}}, + ?P("~s -> stop observer", [?FUNCTION_NAME]), observer:stop(), + ?P("~s -> stop sasl", [?FUNCTION_NAME]), application:stop(sasl), case SaslStart of true -> application:start(sasl); false -> ok end, - ?P("ensure observer stopped"), + ?P("~s -> ensure observer stopped", [?FUNCTION_NAME]), ensure_observer_stopped(?SECS(2)), - ?P("process_win -> done"), + ?P("~s -> done", [?FUNCTION_NAME]), ok. table_win(suite) -> []; @@ -560,11 +577,11 @@ ensure_observer_stopped() -> ensure_observer_stopped(T) when is_integer(T) andalso (T > 0) -> case erlang:whereis(observer) of undefined -> - ?P("observer *not* running"), + ?P("~s(~w) -> observer *not* running", [?FUNCTION_NAME, ?LINE]), ok; Pid when is_pid(Pid) -> - ?P("observer process still running: " - "~n ~p", [erlang:process_info(Pid)]), + ?P("~s(~w) -> observer process still running: " + "~n ~p", [?FUNCTION_NAME, ?LINE, erlang:process_info(Pid)]), ct:sleep(?SECS(1)), ensure_observer_stopped(T - 1000), ok @@ -572,11 +589,11 @@ ensure_observer_stopped(T) when is_integer(T) andalso (T > 0) -> ensure_observer_stopped(_) -> case erlang:whereis(observer) of undefined -> - ?P("observer *not* running"), + ?P("~s(~w) -> observer *not* running", [?FUNCTION_NAME, ?LINE]), ok; Pid when is_pid(Pid) -> - ?P("observer process still running: kill" - "~n ~p", [erlang:process_info(Pid)]), + ?P("~s(~w) -> observer process still running: kill" + "~n ~p", [?FUNCTION_NAME, ?LINE, erlang:process_info(Pid)]), exit(kill, Pid), ok end. diff --git a/lib/runtime_tools/src/observer_backend.erl b/lib/runtime_tools/src/observer_backend.erl index 0817781ca19b..cd4ab479cc08 100644 --- a/lib/runtime_tools/src/observer_backend.erl +++ b/lib/runtime_tools/src/observer_backend.erl @@ -268,8 +268,18 @@ get_sock_opts(Port, [Opt|Opts], Acc) -> get_socket_list() -> GetOpt = fun(_Sock, {Opt, false}) -> + %% d("~s -> ~p Not Supported", [?FUNCTION_NAME, Opt]), {Opt, "Not Supported"}; + (_Sock, {Opt, require_value}) -> + %% d("~s -> ~p Require Value", [?FUNCTION_NAME, Opt]), + {Opt, "Require Value (e.g. AssocID)"}; (Sock, {Opt, true}) -> + %% d("~s -> try getopt for" + %% "~n Opt: ~p", [?FUNCTION_NAME, Opt]), + %% We should use try catch here, but then we might + %% miss if there is a fatal error (catched). + %% If we implement more sctp options that are + %% actually for associations (require a value part)... case socket:getopt(Sock, Opt) of %% Convert to string? {ok, Value0} -> @@ -301,11 +311,19 @@ get_socket_list() -> %% "~n Option: ~p" %% "~n Reason: ~p", [Opt, _Reason]), {Opt, f("error:~p", [Reason])} - end + %% catch + %% C:E:_S -> + %% %% d("~s -> catched: " + %% %% "~n Class: ~p" + %% %% "~n Error: ~p" + %% %% "~n Stack: ~p", [?FUNCTION_NAME, C, E, _S]), + %% {Opt, f("catched:~w,~p", [C, E])} + end + end, [begin Kind = socket:which_socket_kind(S), - FD = case socket:getopt(S, otp, fd) of + FD = case socket:getopt(S, {otp, fd}) of {ok, FD0} -> FD0; _ -> @@ -362,6 +380,23 @@ get_socket_list() -> socket:supports(options, ip), ?ESOCK_KEEP_UNSUPPORTED_OPT(Supported)] end, + %% Some of the SCTP options are for associations, + %% which means that getopt require an extra value + %% argument (with the assoc-id and maybe other stuff). + %% These options can not be included + SctpRequireValue = + fun(_Opt, false = Supported) -> + Supported; + (Opt, Supported) -> + case lists:member(Opt, + [get_peer_addr_info, + status]) of + true -> + require_value; + false -> + Supported + end + end, ProtoOpts = case Info7 of #{domain := Domain, @@ -384,7 +419,7 @@ get_socket_list() -> type := seqpacket, protocol := sctp} when (Domain =:= inet) orelse (Domain =:= inet6) -> - [{{sctp, Opt}, Supported} || + [{{sctp, Opt}, SctpRequireValue(Opt, Supported)} || {Opt, Supported} <- socket:supports(options, sctp), ?ESOCK_KEEP_UNSUPPORTED_OPT(Supported)]; @@ -1059,4 +1094,7 @@ f(F, A) -> %% d(_, _, _) -> %% ok. +%% d(F, A) -> +%% io:format("[ob] " ++ F ++ "~n", A). +