Compare commits

..
4 Commits
Author SHA1 Message Date
hyung-hwan 1ed8665d15 added association id for sctp.
enhanced the SEQPACKET support for sctp
2026-08-21 00:52:57 +09:00
hyung-hwan 1369ff1614 enhanced the sctp support 2026-08-21 00:14:53 +09:00
hyung-hwan 13a14ad700 added HIO_READ_BUFFER_SIZE for hio_setoption for read buffer sizing 2026-08-20 21:31:52 +09:00
hyung-hwan e45f82d49e per-device CWQ list implemented 2026-08-20 19:32:07 +09:00
19 changed files with 1892 additions and 173 deletions
+3 -2
View File
@@ -165,8 +165,8 @@ am__DIST_COMMON = $(srcdir)/Dockerfile.in $(srcdir)/Makefile.in \
$(top_srcdir)/ac/install-sh $(top_srcdir)/ac/ltmain.sh \ $(top_srcdir)/ac/install-sh $(top_srcdir)/ac/ltmain.sh \
$(top_srcdir)/ac/missing $(top_srcdir)/ac/tap-driver.sh \ $(top_srcdir)/ac/missing $(top_srcdir)/ac/tap-driver.sh \
$(top_srcdir)/pkgs/hio.spec.in ac/ar-lib ac/compile \ $(top_srcdir)/pkgs/hio.spec.in ac/ar-lib ac/compile \
ac/config.guess ac/config.sub ac/install-sh ac/ltmain.sh \ ac/config.guess ac/config.sub ac/depcomp ac/install-sh \
ac/missing ac/ltmain.sh ac/missing
DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
distdir = $(PACKAGE)-$(VERSION) distdir = $(PACKAGE)-$(VERSION)
top_distdir = $(distdir) top_distdir = $(distdir)
@@ -294,6 +294,7 @@ PTHREAD_CXX = @PTHREAD_CXX@
PTHREAD_LIBS = @PTHREAD_LIBS@ PTHREAD_LIBS = @PTHREAD_LIBS@
QUADMATH_LIBS = @QUADMATH_LIBS@ QUADMATH_LIBS = @QUADMATH_LIBS@
RANLIB = @RANLIB@ RANLIB = @RANLIB@
SCTP_ENABLED = @SCTP_ENABLED@
SED = @SED@ SED = @SED@
SENDFILE_LIBS = @SENDFILE_LIBS@ SENDFILE_LIBS = @SENDFILE_LIBS@
SET_MAKE = @SET_MAKE@ SET_MAKE = @SET_MAKE@
+1
View File
@@ -328,6 +328,7 @@ PTHREAD_CXX = @PTHREAD_CXX@
PTHREAD_LIBS = @PTHREAD_LIBS@ PTHREAD_LIBS = @PTHREAD_LIBS@
QUADMATH_LIBS = @QUADMATH_LIBS@ QUADMATH_LIBS = @QUADMATH_LIBS@
RANLIB = @RANLIB@ RANLIB = @RANLIB@
SCTP_ENABLED = @SCTP_ENABLED@
SED = @SED@ SED = @SED@
SENDFILE_LIBS = @SENDFILE_LIBS@ SENDFILE_LIBS = @SENDFILE_LIBS@
SET_MAKE = @SET_MAKE@ SET_MAKE = @SET_MAKE@
Vendored
+187 -2
View File
@@ -689,6 +689,7 @@ ax_pthread_config
CPP CPP
SENDFILE_LIBS SENDFILE_LIBS
SOCKET_LIBS SOCKET_LIBS
SCTP_ENABLED
LIBM LIBM
MACOSX_FALSE MACOSX_FALSE
MACOSX_TRUE MACOSX_TRUE
@@ -830,6 +831,7 @@ with_gnu_ld
with_sysroot with_sysroot
enable_libtool_lock enable_libtool_lock
enable_largefile enable_largefile
enable_sctp
enable_all_static enable_all_static
with_all_static_libs with_all_static_libs
enable_quadmath enable_quadmath
@@ -1496,6 +1498,8 @@ Optional Features:
provide on AIX, [default=aix]. provide on AIX, [default=aix].
--disable-libtool-lock avoid locking (might break parallel builds) --disable-libtool-lock avoid locking (might break parallel builds)
--disable-largefile omit support for large files --disable-largefile omit support for large files
--enable-sctp build with sctp support if the system provides it
(default. yes)
--enable-all-static build the full static binaries(default. no) --enable-all-static build the full static binaries(default. no)
--enable-quadmath attempt to support 128-bit floating point number if --enable-quadmath attempt to support 128-bit floating point number if
available(default. no) available(default. no)
@@ -1802,6 +1806,60 @@ printf "%s\n" "$ac_res" >&6; }
} # ac_fn_c_check_func } # ac_fn_c_check_func
# ac_fn_check_decl LINENO SYMBOL VAR INCLUDES EXTRA-OPTIONS FLAG-VAR
# ------------------------------------------------------------------
# Tests whether SYMBOL is declared in INCLUDES, setting cache variable VAR
# accordingly. Pass EXTRA-OPTIONS to the compiler, using FLAG-VAR.
ac_fn_check_decl ()
{
as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
as_decl_name=`echo $2|sed 's/ *(.*//'`
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether $as_decl_name is declared" >&5
printf %s "checking whether $as_decl_name is declared... " >&6; }
if eval test \${$3+y}
then :
printf %s "(cached) " >&6
else case e in #(
e) as_decl_use=`echo $2|sed -e 's/(/((/' -e 's/)/) 0&/' -e 's/,/) 0& (/g'`
eval ac_save_FLAGS=\$$6
as_fn_append $6 " $5"
cat confdefs.h - <<_ACEOF >conftest.$ac_ext
/* end confdefs.h. */
$4
int
main (void)
{
#ifndef $as_decl_name
#ifdef __cplusplus
(void) $as_decl_use;
#else
(void) $as_decl_name;
#endif
#endif
;
return 0;
}
_ACEOF
if ac_fn_c_try_compile "$LINENO"
then :
eval "$3=yes"
else case e in #(
e) eval "$3=no" ;;
esac
fi
rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext
eval $6=\$ac_save_FLAGS
;;
esac
fi
eval ac_res=\$$3
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5
printf "%s\n" "$ac_res" >&6; }
eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno
} # ac_fn_check_decl
# ac_fn_c_try_cpp LINENO # ac_fn_c_try_cpp LINENO
# ---------------------- # ----------------------
# Try to preprocess conftest.$ac_ext, and return whether this succeeded. # Try to preprocess conftest.$ac_ext, and return whether this succeeded.
@@ -16117,13 +16175,131 @@ then :
printf "%s\n" "#define HAVE_NETINET_IN_H 1" >>confdefs.h printf "%s\n" "#define HAVE_NETINET_IN_H 1" >>confdefs.h
fi fi
ac_fn_c_check_header_compile "$LINENO" "netinet/sctp.h" "ac_cv_header_netinet_sctp_h" "$ac_includes_default"
# Check whether --enable-sctp was given.
if test ${enable_sctp+y}
then :
enableval=$enable_sctp; enable_sctp_is=$enableval
else case e in #(
e) enable_sctp_is=yes
;;
esac
fi
SCTP_ENABLED="no"
if test "x$enable_sctp_is" = "xyes"
then
ac_fn_c_check_header_compile "$LINENO" "netinet/sctp.h" "ac_cv_header_netinet_sctp_h" "$ac_includes_default"
if test "x$ac_cv_header_netinet_sctp_h" = xyes if test "x$ac_cv_header_netinet_sctp_h" = xyes
then : then :
printf "%s\n" "#define HAVE_NETINET_SCTP_H 1" >>confdefs.h printf "%s\n" "#define HAVE_NETINET_SCTP_H 1" >>confdefs.h
fi fi
if test "x$ac_cv_header_netinet_sctp_h" = "xyes"
then
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $CC options needed to detect all undeclared functions" >&5
printf %s "checking for $CC options needed to detect all undeclared functions... " >&6; }
if test ${ac_cv_c_undeclared_builtin_options+y}
then :
printf %s "(cached) " >&6
else case e in #(
e) ac_save_CFLAGS=$CFLAGS
ac_cv_c_undeclared_builtin_options='cannot detect'
for ac_arg in '' -fno-builtin; do
CFLAGS="$ac_save_CFLAGS $ac_arg"
# This test program should *not* compile successfully.
cat confdefs.h - <<_ACEOF >conftest.$ac_ext
/* end confdefs.h. */
int
main (void)
{
(void) strchr;
;
return 0;
}
_ACEOF
if ac_fn_c_try_compile "$LINENO"
then :
else case e in #(
e) # This test program should compile successfully.
# No library function is consistently available on
# freestanding implementations, so test against a dummy
# declaration. Include always-available headers on the
# off chance that they somehow elicit warnings.
cat confdefs.h - <<_ACEOF >conftest.$ac_ext
/* end confdefs.h. */
#include <float.h>
#include <limits.h>
#include <stdarg.h>
#include <stddef.h>
extern void ac_decl (int, char *);
int
main (void)
{
(void) ac_decl (0, (char *) 0);
(void) ac_decl;
;
return 0;
}
_ACEOF
if ac_fn_c_try_compile "$LINENO"
then :
if test x"$ac_arg" = x
then :
ac_cv_c_undeclared_builtin_options='none needed'
else case e in #(
e) ac_cv_c_undeclared_builtin_options=$ac_arg ;;
esac
fi
break
fi
rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ;;
esac
fi
rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext
done
CFLAGS=$ac_save_CFLAGS
;;
esac
fi
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_c_undeclared_builtin_options" >&5
printf "%s\n" "$ac_cv_c_undeclared_builtin_options" >&6; }
case $ac_cv_c_undeclared_builtin_options in #(
'cannot detect') :
{ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5
printf "%s\n" "$as_me: error: in '$ac_pwd':" >&2;}
as_fn_error $? "cannot make $CC report undeclared builtins
See 'config.log' for more details" "$LINENO" 5; } ;; #(
'none needed') :
ac_c_undeclared_builtin_options='' ;; #(
*) :
ac_c_undeclared_builtin_options=$ac_cv_c_undeclared_builtin_options ;;
esac
ac_fn_check_decl "$LINENO" "IPPROTO_SCTP" "ac_cv_have_decl_IPPROTO_SCTP" "
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
" "$ac_c_undeclared_builtin_options" "CFLAGS"
if test "x$ac_cv_have_decl_IPPROTO_SCTP" = xyes
then :
printf "%s\n" "#define HIO_ENABLE_SCTP 1" >>confdefs.h
SCTP_ENABLED="yes"
fi
fi
fi
ac_fn_c_check_header_compile "$LINENO" "net/if.h" "ac_cv_header_net_if_h" " ac_fn_c_check_header_compile "$LINENO" "net/if.h" "ac_cv_header_net_if_h" "
#include <sys/types.h> #include <sys/types.h>
#include <sys/socket.h> #include <sys/socket.h>
@@ -20787,7 +20963,9 @@ else case e in #(
struct sockaddr_extra_t struct sockaddr_extra_t
{ {
unsigned short chan; unsigned short chan;
} unsigned int ppid;
int assoc;
};
struct sockaddr_in_x { struct sockaddr_in_x {
struct sockaddr_in a; struct sockaddr_in a;
struct sockaddr_extra_t u; struct sockaddr_extra_t u;
@@ -20834,6 +21012,8 @@ else case e in #(
struct sockaddr_extra_t struct sockaddr_extra_t
{ {
unsigned short chan; unsigned short chan;
unsigned int ppid;
int assoc;
}; };
struct sockaddr_in6_x { struct sockaddr_in6_x {
struct sockaddr_in6 a; struct sockaddr_in6 a;
@@ -21267,6 +21447,8 @@ else case e in #(
struct sockaddr_extra_t struct sockaddr_extra_t
{ {
unsigned short chan; unsigned short chan;
unsigned int ppid;
int assoc;
}; };
struct sockaddr_in_x { struct sockaddr_in_x {
struct sockaddr_in a; struct sockaddr_in a;
@@ -21315,6 +21497,8 @@ else case e in #(
struct sockaddr_extra_t struct sockaddr_extra_t
{ {
unsigned short chan; unsigned short chan;
unsigned int ppid;
int assoc;
}; };
struct sockaddr_in6_x { struct sockaddr_in6_x {
struct sockaddr_in6 a; struct sockaddr_in6 a;
@@ -24719,6 +24903,7 @@ echo " Math library: ${LIBM}"
echo " Socket library: ${SOCKET_LIBS}" echo " Socket library: ${SOCKET_LIBS}"
echo " Sendfile library: ${SENDFILE_LIBS}" echo " Sendfile library: ${SENDFILE_LIBS}"
echo " SSL library: ${SSL_LIBS}" echo " SSL library: ${SSL_LIBS}"
echo " SCTP support: ${SCTP_ENABLED}"
echo "-------------------------------------------------------------------------" echo "-------------------------------------------------------------------------"
+38 -2
View File
@@ -116,7 +116,34 @@ AC_CHECK_HEADERS([time.h sys/time.h utime.h spawn.h execinfo.h ucontext.h])
AC_CHECK_HEADERS([sys/resource.h sys/wait.h sys/syscall.h sys/ioctl.h]) AC_CHECK_HEADERS([sys/resource.h sys/wait.h sys/syscall.h sys/ioctl.h])
AC_CHECK_HEADERS([sys/sendfile.h sys/epoll.h sys/event.h sys/poll.h sys/select.h]) AC_CHECK_HEADERS([sys/sendfile.h sys/epoll.h sys/event.h sys/poll.h sys/select.h])
AC_CHECK_HEADERS([sys/sysctl.h sys/socket.h sys/sockio.h sys/un.h]) AC_CHECK_HEADERS([sys/sysctl.h sys/socket.h sys/sockio.h sys/un.h])
AC_CHECK_HEADERS([ifaddrs.h tiuser.h linux/netfilter_ipv4.h netinet/in.h netinet/sctp.h]) AC_CHECK_HEADERS([ifaddrs.h tiuser.h linux/netfilter_ipv4.h netinet/in.h])
dnl sctp support. it used to be switched on purely by whether netinet/sctp.h
dnl happened to be installed, which meant two machines could build libraries
dnl with different capabilities and nothing said so. make it a decision.
AC_ARG_ENABLE([sctp],
[AS_HELP_STRING([--enable-sctp],[build with sctp support if the system provides it (default. yes)])],
enable_sctp_is=$enableval,
enable_sctp_is=yes
)
SCTP_ENABLED="no"
if test "x$enable_sctp_is" = "xyes"
then
AC_CHECK_HEADERS([netinet/sctp.h])
if test "x$ac_cv_header_netinet_sctp_h" = "xyes"
then
dnl the header alone is not enough - IPPROTO_SCTP has to be defined too
AC_CHECK_DECL([IPPROTO_SCTP], [
AC_DEFINE([HIO_ENABLE_SCTP], [1], [enable sctp support])
SCTP_ENABLED="yes"
], [], [[
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
]])
fi
fi
AC_SUBST(SCTP_ENABLED)
AC_CHECK_HEADERS([net/if.h net/if_dl.h netinet/if_ether.h netpacket/packet.h net/bpf.h], [], [], [ AC_CHECK_HEADERS([net/if.h net/if_dl.h netinet/if_ether.h netpacket/packet.h net/bpf.h], [], [], [
#include <sys/types.h> #include <sys/types.h>
#include <sys/socket.h>]) #include <sys/socket.h>])
@@ -497,7 +524,9 @@ then
struct sockaddr_extra_t struct sockaddr_extra_t
{ {
unsigned short chan; unsigned short chan;
} unsigned int ppid;
int assoc;
};
struct sockaddr_in_x { struct sockaddr_in_x {
struct sockaddr_in a; struct sockaddr_in a;
struct sockaddr_extra_t u; struct sockaddr_extra_t u;
@@ -509,6 +538,8 @@ then
struct sockaddr_extra_t struct sockaddr_extra_t
{ {
unsigned short chan; unsigned short chan;
unsigned int ppid;
int assoc;
}; };
struct sockaddr_in6_x { struct sockaddr_in6_x {
struct sockaddr_in6 a; struct sockaddr_in6 a;
@@ -575,6 +606,8 @@ else
struct sockaddr_extra_t struct sockaddr_extra_t
{ {
unsigned short chan; unsigned short chan;
unsigned int ppid;
int assoc;
}; };
struct sockaddr_in_x { struct sockaddr_in_x {
struct sockaddr_in a; struct sockaddr_in a;
@@ -588,6 +621,8 @@ else
struct sockaddr_extra_t struct sockaddr_extra_t
{ {
unsigned short chan; unsigned short chan;
unsigned int ppid;
int assoc;
}; };
struct sockaddr_in6_x { struct sockaddr_in6_x {
struct sockaddr_in6 a; struct sockaddr_in6 a;
@@ -911,5 +946,6 @@ echo " Math library: ${LIBM}"
echo " Socket library: ${SOCKET_LIBS}" echo " Socket library: ${SOCKET_LIBS}"
echo " Sendfile library: ${SENDFILE_LIBS}" echo " Sendfile library: ${SENDFILE_LIBS}"
echo " SSL library: ${SSL_LIBS}" echo " SSL library: ${SSL_LIBS}"
echo " SCTP support: ${SCTP_ENABLED}"
echo "-------------------------------------------------------------------------" echo "-------------------------------------------------------------------------"
] ]
+1
View File
@@ -373,6 +373,7 @@ PTHREAD_CXX = @PTHREAD_CXX@
PTHREAD_LIBS = @PTHREAD_LIBS@ PTHREAD_LIBS = @PTHREAD_LIBS@
QUADMATH_LIBS = @QUADMATH_LIBS@ QUADMATH_LIBS = @QUADMATH_LIBS@
RANLIB = @RANLIB@ RANLIB = @RANLIB@
SCTP_ENABLED = @SCTP_ENABLED@
SED = @SED@ SED = @SED@
SENDFILE_LIBS = @SENDFILE_LIBS@ SENDFILE_LIBS = @SENDFILE_LIBS@
SET_MAKE = @SET_MAKE@ SET_MAKE = @SET_MAKE@
+8 -9
View File
@@ -29,7 +29,6 @@
struct hio_svc_dhcs_t struct hio_svc_dhcs_t
{ {
HIO_SVC_HEADER; HIO_SVC_HEADER;
int stopping; int stopping;
hio_dev_sck_t* sck; hio_dev_sck_t* sck;
}; };
@@ -79,9 +78,9 @@ static hio_dev_sck_t* open_socket (hio_t* hio, hio_svc_dhcs_cfg_t* cfg)
/* /*
#if defined(IPV6_RECVPKTINFO) #if defined(IPV6_RECVPKTINFO)
hio_dev_sck_setsockopt (dhcs->sck, IPPROTO_IPV6, IPV6_RECVPKTINFO, &v); hio_dev_sck_setsockopt(dhcs->sck, IPPROTO_IPV6, IPV6_RECVPKTINFO, &v);
#elif defined(IPV6_PKTINFO) #elif defined(IPV6_PKTINFO)
hio_dev_sck_setsockopt (dhcs->sck, IPPROTO_IPV6, IPV6_PKTINFO, &v); hio_dev_sck_setsockopt(dhcs->sck, IPPROTO_IPV6, IPV6_PKTINFO, &v);
#else #else
//# error no ipv6 pktinfo //# error no ipv6 pktinfo
#endif #endif
@@ -104,7 +103,7 @@ static hio_dev_sck_t* open_socket (hio_t* hio, hio_svc_dhcs_cfg_t* cfg)
return sck; return sck;
oops: oops:
if (sck) hio_dev_sck_kill (sck); if (sck) hio_dev_sck_kill(sck);
return HIO_NULL; return HIO_NULL;
} }
@@ -137,13 +136,13 @@ hio_svc_dhcs_t* hio_svc_dhcs_start (hio_t* hio, const hio_skad_t* local_binds, h
/* TODO: remember this in dhcs... */ /* TODO: remember this in dhcs... */
} }
HIO_SVCL_APPEND_SVC (&hio->actsvc, (hio_svc_t*)dhcs); HIO_SVCL_APPEND_SVC(&hio->actsvc, (hio_svc_t*)dhcs);
return dhcs; return dhcs;
oops: oops:
if (dhcs) if (dhcs)
{ {
if (sck) hio_dev_sck_kill (sck); if (sck) hio_dev_sck_kill(sck);
/*TODO: /*TODO:
for (i = 0; i < local_nbinds; i++) for (i = 0; i < local_nbinds; i++)
{ {
@@ -158,11 +157,11 @@ void hio_svc_dhcs_stop (hio_svc_dhcs_t* dhcs)
{ {
hio_t* hio = dhcs->hio; hio_t* hio = dhcs->hio;
HIO_DEBUG1 (hio, "FCGIC - STOPPING SERVICE %p\n", dhcs); HIO_DEBUG1(hio, "FCGIC - STOPPING SERVICE %p\n", dhcs);
dhcs->stopping = 1; dhcs->stopping = 1;
HIO_SVCL_UNLINK_SVC (dhcs); HIO_SVCL_UNLINK_SVC(dhcs);
hio_freemem(hio, dhcs); hio_freemem(hio, dhcs);
HIO_DEBUG1 (hio, "FCGIC - STOPPED SERVICE %p\n", dhcs); HIO_DEBUG1(hio, "FCGIC - STOPPED SERVICE %p\n", dhcs);
} }
+3
View File
@@ -591,6 +591,9 @@
/* build the mariadb device */ /* build the mariadb device */
#undef HIO_ENABLE_MARIADB #undef HIO_ENABLE_MARIADB
/* enable sctp support */
#undef HIO_ENABLE_SCTP
/* Use the wide-character type as the default character type */ /* Use the wide-character type as the default character type */
#undef HIO_ENABLE_WIDE_CHAR #undef HIO_ENABLE_WIDE_CHAR
+30
View File
@@ -251,6 +251,23 @@ typedef void (*hio_dev_sck_on_connect_t) (
hio_dev_sck_t* dev hio_dev_sck_t* dev
); );
/**
* Called for an SCTP notification - an association coming up or going down, a
* path changing state, a send failing. These arrive interleaved with data on
* the same socket and are flagged MSG_NOTIFICATION; they are not payload, so
* they are never handed to on_read().
*
* 'data' points at a union sctp_notification of 'dlen' octets. It belongs to
* the loop's read buffer and is only valid for the duration of this call.
*
* Leave it unset to have notifications discarded.
*/
typedef void (*hio_dev_sck_on_notification_t) (
hio_dev_sck_t* dev,
const void* data,
hio_iolen_t dlen
);
typedef void (*hio_dev_sck_on_raw_accept_t) ( typedef void (*hio_dev_sck_on_raw_accept_t) (
hio_dev_sck_t* dev, hio_dev_sck_t* dev,
hio_syshnd_t syshnd, hio_syshnd_t syshnd,
@@ -321,6 +338,14 @@ struct hio_dev_sck_make_t
hio_dev_sck_on_connect_t on_connect; hio_dev_sck_on_connect_t on_connect;
hio_dev_sck_on_disconnect_t on_disconnect; hio_dev_sck_on_disconnect_t on_disconnect;
hio_dev_sck_on_raw_accept_t on_raw_accept; /* optional */ hio_dev_sck_on_raw_accept_t on_raw_accept; /* optional */
hio_dev_sck_on_notification_t on_notification; /* optional. sctp only */
/** number of outbound streams to ask for on an SCTP association.
* 0 leaves it to the system default, which is what every release before
* this field did. only meaningful for the SCTP types. */
hio_uint16_t sctp_ostreams;
/** maximum number of inbound streams to accept. 0 for the default. */
hio_uint16_t sctp_instreams;
}; };
enum hio_dev_sck_bind_option_t enum hio_dev_sck_bind_option_t
@@ -414,6 +439,7 @@ struct hio_dev_sck_t
hio_dev_sck_on_connect_t on_connect; hio_dev_sck_on_connect_t on_connect;
hio_dev_sck_on_disconnect_t on_disconnect; hio_dev_sck_on_disconnect_t on_disconnect;
hio_dev_sck_on_raw_accept_t on_raw_accept; hio_dev_sck_on_raw_accept_t on_raw_accept;
hio_dev_sck_on_notification_t on_notification;
/* timer job index for handling /* timer job index for handling
* - connect() timeout for a connecting socket. * - connect() timeout for a connecting socket.
@@ -429,6 +455,10 @@ struct hio_dev_sck_t
void* ssl; void* ssl;
hio_syshnd_t side_chan; /* side-channel for HIO_DEV_SCK_QX */ hio_syshnd_t side_chan; /* side-channel for HIO_DEV_SCK_QX */
/* set while a message too large for the read buffer is being thrown away.
* see the MSG_EOR handling in the sctp seqpacket read method. */
int sctp_discarding;
}; };
enum hio_dev_sck_shutdown_how_t enum hio_dev_sck_shutdown_how_t
+33
View File
@@ -222,7 +222,40 @@ HIO_EXPORT void hio_skad_set_scope_id (
int scope_id int scope_id
); );
/**
* The SCTP payload protocol identifier carried alongside the address. SCTP
* passes it through unchanged; it means whatever the two ends agree it means.
* Rides in the same extra area as the channel - see hio_skad_get_chan().
*/
/* for sctp */ /* for sctp */
/**
* The SCTP association identifier carried alongside the address. It is the
* stable name for an association - the addresses of one can change under
* multi-homing, this does not - so a reply is best addressed by it rather than
* by the address it happened to arrive from.
*
* On a one-to-many socket, setting it on the destination directs the message
* to that association; a value of 0 falls back to addressing by address.
*/
HIO_EXPORT hio_int32_t hio_skad_get_assoc (
const hio_skad_t* skad
);
HIO_EXPORT void hio_skad_set_assoc (
hio_skad_t* skad,
hio_int32_t assoc
);
HIO_EXPORT hio_uint32_t hio_skad_get_ppid (
const hio_skad_t* skad
);
/* for sctp */
HIO_EXPORT void hio_skad_set_ppid (
hio_skad_t* skad,
hio_uint32_t ppid
);
HIO_EXPORT hio_uint16_t hio_skad_get_chan ( HIO_EXPORT hio_uint16_t hio_skad_get_chan (
const hio_skad_t* skad const hio_skad_t* skad
); );
+170 -92
View File
@@ -32,6 +32,7 @@ static void clear_unneeded_cfmbs (hio_t* hio);
static int schedule_kill_zombie_job (hio_dev_t* dev); static int schedule_kill_zombie_job (hio_dev_t* dev);
static int kill_and_free_device (hio_dev_t* dev, int force); static int kill_and_free_device (hio_dev_t* dev, int force);
static void free_dead_devices (hio_t* hio); static void free_dead_devices (hio_t* hio);
static void unlink_cwq (hio_t* hio, hio_cwq_t* cwq);
static void on_read_timeout (hio_t* hio, const hio_ntime_t* now, hio_tmrjob_t* job); static void on_read_timeout (hio_t* hio, const hio_ntime_t* now, hio_tmrjob_t* job);
static void on_write_timeout (hio_t* hio, const hio_ntime_t* now, hio_tmrjob_t* job); static void on_write_timeout (hio_t* hio, const hio_ntime_t* now, hio_tmrjob_t* job);
@@ -135,6 +136,11 @@ int hio_init (hio_t* hio, hio_mmgr_t* mmgr, hio_cmgr_t* cmgr, hio_bitmask_t feat
hio->tmr.capa = tmrcapa; hio->tmr.capa = tmrcapa;
/* the shared read buffer. resizable later through HIO_READ_BUFFER_SIZE. */
hio->bigbuf.ptr = hio_allocmem(hio, HIO_DFL_READ_BUFFER_SIZE);
if (HIO_UNLIKELY(!hio->bigbuf.ptr)) goto oops;
hio->bigbuf.capa = HIO_DFL_READ_BUFFER_SIZE;
HIO_CFMBL_INIT(&hio->cfmb); HIO_CFMBL_INIT(&hio->cfmb);
HIO_DEVL_INIT(&hio->actdev); HIO_DEVL_INIT(&hio->actdev);
HIO_DEVL_INIT(&hio->hltdev); HIO_DEVL_INIT(&hio->hltdev);
@@ -147,6 +153,13 @@ int hio_init (hio_t* hio, hio_mmgr_t* mmgr, hio_cmgr_t* cmgr, hio_bitmask_t feat
return 0; return 0;
oops: oops:
if (hio->bigbuf.ptr)
{
hio_freemem(hio, hio->bigbuf.ptr);
hio->bigbuf.ptr = HIO_NULL;
hio->bigbuf.capa = 0;
}
if (hio->tmr.jobs) hio_freemem(hio, hio->tmr.jobs); if (hio->tmr.jobs) hio_freemem(hio, hio->tmr.jobs);
if (sys_inited) hio_sys_fini(hio); if (sys_inited) hio_sys_fini(hio);
@@ -165,6 +178,13 @@ void hio_fini (hio_t* hio)
hio->_fini_in_progress = 1; hio->_fini_in_progress = 1;
if (hio->bigbuf.ptr)
{
hio_freemem(hio, hio->bigbuf.ptr);
hio->bigbuf.ptr = HIO_NULL;
hio->bigbuf.capa = 0;
}
/* clean up free cwq list */ /* clean up free cwq list */
for (i = 0; i < HIO_COUNTOF(hio->cwqfl); i++) for (i = 0; i < HIO_COUNTOF(hio->cwqfl); i++)
{ {
@@ -176,12 +196,14 @@ void hio_fini (hio_t* hio)
} }
} }
/* clean up unfired cwq entries - calling fire_cwq_handlers() might not be good here. */ /* clean up unfired cwq entries - calling fire_cwq_handlers() might not be good here.
* unlink_cwq() rather than a bare HIO_CWQ_UNLINK: the devices are killed further
* down and hio_dev_kill() consults cwq_head, which must not be left dangling. */
while (!HIO_CWQ_IS_EMPTY(&hio->cwq)) while (!HIO_CWQ_IS_EMPTY(&hio->cwq))
{ {
hio_cwq_t* cwq; hio_cwq_t* cwq;
cwq = HIO_CWQ_HEAD(&hio->cwq); cwq = HIO_CWQ_HEAD(&hio->cwq);
HIO_CWQ_UNLINK (cwq); unlink_cwq(hio, cwq);
hio_freemem(hio, cwq); hio_freemem(hio, cwq);
} }
@@ -396,6 +418,49 @@ int hio_setoption (hio_t* hio, hio_option_t id, const void* value)
hio->option.log_writer = (hio_log_writer_t)value; hio->option.log_writer = (hio_log_writer_t)value;
break; break;
case HIO_READ_BUFFER_SIZE:
{
hio_oow_t newcapa = *(const hio_oow_t*)value;
hio_uint8_t* p;
if (newcapa < HIO_MIN_READ_BUFFER_SIZE) goto einval;
if (newcapa == hio->bigbuf.capa) break; /* nothing to do */
if (newcapa < hio->bigbuf.capa)
{
/* shrinking. a device already running may need more than the
* new size, and it has no way to object after the fact. */
hio_dev_t* d;
for (d = HIO_DEVL_FIRST_DEV(&hio->actdev); !HIO_DEVL_IS_NIL_DEV(&hio->actdev, d); d = d->dev_next)
{
if (d->dev_rdmin > newcapa)
{
hio_seterrbfmt(hio, HIO_EPERM, "a running device requires a read buffer of at least %zu octets", d->dev_rdmin);
return -1;
}
}
}
/* on_read() is handed a pointer into this buffer, so it must not
* move while a callback might be holding one. mux_depth is
* non-zero for exactly the window in which that is possible. */
if (hio->mux_depth > 0)
{
hio_seterrbfmt(hio, HIO_EBUSY, "unable to resize the read buffer while dispatching events");
return -1;
}
/* the contents are scratch, so there is nothing to carry over.
* allocate first all the same - a failure must leave the existing
* buffer in place rather than leave the loop without one. */
p = hio_allocmem(hio, newcapa);
if (HIO_UNLIKELY(!p)) return -1;
hio_freemem(hio, hio->bigbuf.ptr);
hio->bigbuf.ptr = p;
hio->bigbuf.capa = newcapa;
break;
}
default: default:
goto einval; goto einval;
} }
@@ -444,6 +509,10 @@ int hio_getoption (hio_t* hio, hio_option_t id, void* value)
case HIO_LOG_WRITER: case HIO_LOG_WRITER:
*(hio_log_writer_t*)value = hio->option.log_writer; *(hio_log_writer_t*)value = hio->option.log_writer;
return 0; return 0;
case HIO_READ_BUFFER_SIZE:
*(hio_oow_t*)value = hio->bigbuf.capa;
return 0;
}; };
hio_seterrnum(hio, HIO_EINVAL); hio_seterrnum(hio, HIO_EINVAL);
@@ -473,43 +542,69 @@ static HIO_INLINE void unlink_wq (hio_t* hio, hio_wq_t* q)
* for one dropped on a timeout or a device kill it is the remainder. */ * for one dropped on a timeout or a device kill it is the remainder. */
HIO_ASSERT(hio, q->dev->wq_len >= (hio_oow_t)q->len); HIO_ASSERT(hio, q->dev->wq_len >= (hio_oow_t)q->len);
q->dev->wq_len -= q->len; q->dev->wq_len -= q->len;
HIO_WQ_UNLINK (q); HIO_WQ_UNLINK(q);
}
/* take an entry off both queues it belongs to and off its device's count.
* an entry is only ever removed from the head of its device's queue - both
* firing paths consume in order - so a singly-linked device queue suffices. */
static void unlink_cwq (hio_t* hio, hio_cwq_t* cwq)
{
hio_dev_t* dev = cwq->dev;
HIO_ASSERT(hio, dev->cwq_head == cwq);
dev->cwq_head = cwq->d_next;
if (!dev->cwq_head) dev->cwq_tail = HIO_NULL;
HIO_ASSERT(hio, dev->cw_count > 0);
dev->cw_count--;
HIO_CWQ_UNLINK(cwq);
}
/* hand the entry back to the size-bucketed free list, or release it */
static HIO_INLINE void recycle_cwq (hio_t* hio, hio_cwq_t* cwq)
{
hio_oow_t cwqfl_index = HIO_ALIGN_POW2(cwq->dstaddr.len, HIO_CWQFL_ALIGN) / HIO_CWQFL_SIZE;
if (cwqfl_index < HIO_COUNTOF(hio->cwqfl))
{
/* reuse the cwq object if dstaddr is 0 in size. chain it to the free list */
cwq->q_next = hio->cwqfl[cwqfl_index];
hio->cwqfl[cwqfl_index] = cwq;
}
else
{
/* TODO: more reuse of objects of different size? */
hio_freemem(hio, cwq);
}
}
/* fire one entry and dispose of it. returns the device to halt, or null. */
static HIO_INLINE hio_dev_t* fire_one_cwq (hio_t* hio, hio_cwq_t* cwq)
{
hio_dev_t* dev_to_halt;
if (cwq->dev->dev_evcb->on_write(cwq->dev, cwq->olen, cwq->ctx, &cwq->dstaddr) <= -1)
{
dev_to_halt = cwq->dev;
}
else
{
dev_to_halt = HIO_NULL;
}
unlink_cwq(hio, cwq);
recycle_cwq(hio, cwq);
return dev_to_halt;
} }
static void fire_cwq_handlers (hio_t* hio) static void fire_cwq_handlers (hio_t* hio)
{ {
/* execute callbacks for completed write operations */ /* execute callbacks for completed write operations, in the order they
* completed, across every device */
while (!HIO_CWQ_IS_EMPTY(&hio->cwq)) while (!HIO_CWQ_IS_EMPTY(&hio->cwq))
{ {
hio_cwq_t* cwq; hio_dev_t* dev_to_halt = fire_one_cwq(hio, HIO_CWQ_HEAD(&hio->cwq));
hio_oow_t cwqfl_index;
hio_dev_t* dev_to_halt;
cwq = HIO_CWQ_HEAD(&hio->cwq);
if (cwq->dev->dev_evcb->on_write(cwq->dev, cwq->olen, cwq->ctx, &cwq->dstaddr) <= -1)
{
dev_to_halt = cwq->dev;
}
else
{
dev_to_halt = HIO_NULL;
}
cwq->dev->cw_count--;
HIO_CWQ_UNLINK (cwq);
cwqfl_index = HIO_ALIGN_POW2(cwq->dstaddr.len, HIO_CWQFL_ALIGN) / HIO_CWQFL_SIZE;
if (cwqfl_index < HIO_COUNTOF(hio->cwqfl))
{
/* reuse the cwq object if dstaddr is 0 in size. chain it to the free list */
cwq->q_next = hio->cwqfl[cwqfl_index];
hio->cwqfl[cwqfl_index] = cwq;
}
else
{
/* TODO: more reuse of objects of different size? */
hio_freemem(hio, cwq);
}
if (dev_to_halt) if (dev_to_halt)
{ {
HIO_DEBUG2(hio, "DEV(%p) - halting a device for on_write error upon write completion[1] - %js\n", dev_to_halt, hio_geterrmsg(hio)); HIO_DEBUG2(hio, "DEV(%p) - halting a device for on_write error upon write completion[1] - %js\n", dev_to_halt, hio_geterrmsg(hio));
@@ -520,53 +615,20 @@ static void fire_cwq_handlers (hio_t* hio)
static void fire_cwq_handlers_for_dev (hio_t* hio, hio_dev_t* dev, int for_kill) static void fire_cwq_handlers_for_dev (hio_t* hio, hio_dev_t* dev, int for_kill)
{ {
hio_cwq_t* cwq, * next;
HIO_ASSERT(hio, dev->cw_count > 0); /* Ensure to check dev->cw_count before calling this function */ HIO_ASSERT(hio, dev->cw_count > 0); /* Ensure to check dev->cw_count before calling this function */
cwq = HIO_CWQ_HEAD(&hio->cwq); /* this used to walk the loop-wide queue looking for entries belonging to
while (cwq != &hio->cwq) * this device, which made it cost the length of everything queued rather
* than the length of this device's own share. */
while (dev->cwq_head)
{ {
next = HIO_CWQ_NEXT(cwq); hio_dev_t* dev_to_halt = fire_one_cwq(hio, dev->cwq_head);
if (cwq->dev == dev) /* TODO: THIS LOOP TOO INEFFICIENT??? MAINTAIN PER-DEVICE LIST OF CWQ? */ if (!for_kill && dev_to_halt)
{ {
hio_dev_t* dev_to_halt; HIO_DEBUG2(hio, "DEV(%p) - halting a device for on_write error upon write completion[2] - %js\n", dev_to_halt, hio_geterrmsg(hio));
hio_oow_t cwqfl_index; hio_dev_halt(dev_to_halt);
if (cwq->dev->dev_evcb->on_write(cwq->dev, cwq->olen, cwq->ctx, &cwq->dstaddr) <= -1)
{
dev_to_halt = cwq->dev;
}
else
{
dev_to_halt = HIO_NULL;
}
cwq->dev->cw_count--;
HIO_CWQ_UNLINK (cwq);
cwqfl_index = HIO_ALIGN_POW2(cwq->dstaddr.len, HIO_CWQFL_ALIGN) / HIO_CWQFL_SIZE;
if (cwqfl_index < HIO_COUNTOF(hio->cwqfl))
{
/* reuse the cwq object if dstaddr is 0 in size. chain it to the free list */
cwq->q_next = hio->cwqfl[cwqfl_index];
hio->cwqfl[cwqfl_index] = cwq;
}
else
{
/* TODO: more reuse of objects of different size? */
hio_freemem(hio, cwq);
}
if (!for_kill && dev_to_halt)
{
HIO_DEBUG2(hio, "DEV(%p) - halting a device for on_write error upon write completion[2] - %js\n", dev_to_halt, hio_geterrmsg(hio));
hio_dev_halt(dev_to_halt);
}
} }
cwq = next;
} }
} }
static HIO_INLINE void clear_read_pending (hio_dev_t* dev) static HIO_INLINE void clear_read_pending (hio_dev_t* dev)
@@ -765,8 +827,8 @@ static HIO_INLINE void handle_event (hio_t* hio, hio_dev_t* dev, int events, int
* if the on_read calllback returns 0. */ * if the on_read calllback returns 0. */
while (1) while (1)
{ {
len = HIO_COUNTOF(hio->bigbuf); len = hio->bigbuf.capa;
x = dev->dev_mth->read(dev, hio->bigbuf, &len, &srcaddr); x = dev->dev_mth->read(dev, hio->bigbuf.ptr, &len, &srcaddr);
if (x <= -1) if (x <= -1)
{ {
HIO_DEBUG2(hio, "DEV(%p) - halting a device for read failure - %js\n", dev, hio_geterrmsg(hio)); HIO_DEBUG2(hio, "DEV(%p) - halting a device for read failure - %js\n", dev, hio_geterrmsg(hio));
@@ -807,18 +869,16 @@ static HIO_INLINE void handle_event (hio_t* hio, hio_dev_t* dev, int events, int
* is started from within on_read() callback, and the input data is available * is started from within on_read() callback, and the input data is available
* in the next iteration of this loop, the on_read() callback is triggered * in the next iteration of this loop, the on_read() callback is triggered
* before the on_write() callbacks scheduled before that on_read() callback. */ * before the on_write() callbacks scheduled before that on_read() callback. */
#if 0 /* only this device's completions, so the ordering the comment
* above describes is the ordering that actually happens. firing
* every device's here was a workaround for the old cost of
* finding one device's entries. */
if (dev->cw_count > 0) if (dev->cw_count > 0)
{ {
fire_cwq_handlers_for_dev (hio, dev); fire_cwq_handlers_for_dev(hio, dev, 0);
/* it will still invoke the on_read() callbak below even if /* it will still invoke the on_read() callback below even if
* the device gets halted inside fire_cwq_handlers_for_dev() */ * the device gets halted inside fire_cwq_handlers_for_dev() */
} }
#else
/* currently fire_cwq_handlers_for_dev() scans the entire cwq list.
* i might as well triggger handlers for all devices */
fire_cwq_handlers (hio);
#endif
if (len <= 0 && (dev->dev_cap & HIO_DEV_CAP_STREAM)) if (len <= 0 && (dev->dev_cap & HIO_DEV_CAP_STREAM))
{ {
@@ -828,7 +888,7 @@ static HIO_INLINE void handle_event (hio_t* hio, hio_dev_t* dev, int events, int
dev->dev_cap |= HIO_DEV_CAP_RENEW_REQUIRED; dev->dev_cap |= HIO_DEV_CAP_RENEW_REQUIRED;
/* call the on_read callback to report EOF */ /* call the on_read callback to report EOF */
if (dev->dev_evcb->on_read(dev, hio->bigbuf, len, &srcaddr) <= -1 || if (dev->dev_evcb->on_read(dev, hio->bigbuf.ptr, len, &srcaddr) <= -1 ||
(dev->dev_cap & HIO_DEV_CAP_OUT_CLOSED)) (dev->dev_cap & HIO_DEV_CAP_OUT_CLOSED))
{ {
/* 1. input ended and its reporting failed or /* 1. input ended and its reporting failed or
@@ -847,11 +907,9 @@ static HIO_INLINE void handle_event (hio_t* hio, hio_dev_t* dev, int events, int
else else
{ {
int y; int y;
/* TODO: for a stream device, merge received data if bigbuf isn't full and fire the on_read callback
* when x == 0 or <= -1. you can */
/* data available */ /* data available */
y = dev->dev_evcb->on_read(dev, hio->bigbuf, len, &srcaddr); y = dev->dev_evcb->on_read(dev, hio->bigbuf.ptr, len, &srcaddr);
if (y <= -1) if (y <= -1)
{ {
HIO_DEBUG2(hio, "DEV(%p) - halting a non-stream device for on_read failure while output is closed - %js\n", dev, hio_geterrmsg(hio)); HIO_DEBUG2(hio, "DEV(%p) - halting a non-stream device for on_read failure while output is closed - %js\n", dev, hio_geterrmsg(hio));
@@ -1133,6 +1191,21 @@ hio_dev_t* hio_dev_make (hio_t* hio, hio_oow_t dev_size, hio_dev_mth_t* dev_mth,
HIO_ASSERT(hio, dev->dev_prev == HIO_NULL); HIO_ASSERT(hio, dev->dev_prev == HIO_NULL);
HIO_ASSERT(hio, dev->dev_next == HIO_NULL); HIO_ASSERT(hio, dev->dev_next == HIO_NULL);
/* make() has settled dev_rdmin by now, so this is the first point at
* which the device's appetite for buffer space is known.
*
* a device carrying whole messages gets one attempt at each: whatever
* does not fit is discarded and nothing says so. refuse the device rather
* than let it lose data quietly. a stream device leaves dev_rdmin at 0 -
* a short read simply leaves the remainder for the next one. */
if (dev->dev_rdmin > hio->bigbuf.capa)
{
hio_seterrbfmt(hio, HIO_ENOCAPA,
"read buffer of %zu octets too small for this device - %zu required",
hio->bigbuf.capa, dev->dev_rdmin);
goto oops_after_make;
}
/* set some internal capability bits according to the capabilities /* set some internal capability bits according to the capabilities
* removed by the device making callback for convenience sake. */ * removed by the device making callback for convenience sake. */
dev->dev_cap &= HIO_DEV_CAP_ALL_MASK; /* keep valid capability bits only. drop all internal-use bits */ dev->dev_cap &= HIO_DEV_CAP_ALL_MASK; /* keep valid capability bits only. drop all internal-use bits */
@@ -1321,7 +1394,7 @@ void hio_dev_kill (hio_dev_t* dev)
clear_read_pending (dev); clear_read_pending (dev);
/* clear completed write event queues */ /* clear completed write event queues */
if (dev->cw_count > 0) fire_cwq_handlers_for_dev (hio, dev, 1); if (dev->cw_count > 0) fire_cwq_handlers_for_dev(hio, dev, 1);
/* clear pending write requests - won't fire on_write for pending write requests */ /* clear pending write requests - won't fire on_write for pending write requests */
while (!HIO_WQ_IS_EMPTY(&dev->wq)) while (!HIO_WQ_IS_EMPTY(&dev->wq))
@@ -1676,7 +1749,12 @@ static HIO_INLINE int __enqueue_completed_write (hio_dev_t* dev, hio_iolen_t len
cwq->olen = len; cwq->olen = len;
HIO_CWQ_ENQ (&dev->hio->cwq, cwq); cwq->d_next = HIO_NULL;
if (dev->cwq_tail) dev->cwq_tail->d_next = cwq;
else dev->cwq_head = cwq;
dev->cwq_tail = cwq;
HIO_CWQ_ENQ(&dev->hio->cwq, cwq);
dev->cw_count++; /* increment the number of complete write operations */ dev->cw_count++; /* increment the number of complete write operations */
return 0; return 0;
} }
@@ -1743,7 +1821,7 @@ static HIO_INLINE int __enqueue_pending_write (hio_dev_t* dev, hio_iolen_t olen,
} }
} }
HIO_WQ_ENQ (&dev->wq, q); HIO_WQ_ENQ(&dev->wq, q);
dev->wq_len += urem; dev->wq_len += urem;
if (!(dev->dev_cap & HIO_DEV_CAP_OUT_WATCHED)) if (!(dev->dev_cap & HIO_DEV_CAP_OUT_WATCHED))
{ {
@@ -1818,7 +1896,7 @@ static HIO_INLINE int __enqueue_pending_sendfile (hio_dev_t* dev, hio_iolen_t ol
} }
} }
HIO_WQ_ENQ (&dev->wq, q); HIO_WQ_ENQ(&dev->wq, q);
dev->wq_len += urem; dev->wq_len += urem;
if (!(dev->dev_cap & HIO_DEV_CAP_OUT_WATCHED)) if (!(dev->dev_cap & HIO_DEV_CAP_OUT_WATCHED))
{ {
+55 -2
View File
@@ -134,6 +134,23 @@ enum hio_feature_t
}; };
typedef enum hio_feature_t hio_feature_t; typedef enum hio_feature_t hio_feature_t;
/** default size of the shared read buffer, in octets. large enough to hold
* any udp datagram, which is why it was this figure before it became
* configurable. */
#define HIO_DFL_READ_BUFFER_SIZE (65536)
/** smallest read buffer that is safe for any stream device. one maximum-size
* tls record must fit, or a single SSL_read() stops draining the record and
* the surplus becomes unreachable - see the note in sck.c. */
#define HIO_MIN_READ_BUFFER_SIZE (16384 + 2048)
/** smallest read buffer that is safe for a device carrying datagrams of
* arbitrary size. such a message is delivered once: a short buffer takes
* what fits and the kernel discards the rest, with nothing reported. a
* device declares its own requirement in dev_rdmin - this is what the
* network datagram socket types use. */
#define HIO_DGRAM_READ_BUFFER_SIZE (65535)
enum hio_option_t enum hio_option_t
{ {
HIO_TRAIT, HIO_TRAIT,
@@ -156,7 +173,25 @@ enum hio_option_t
#endif #endif
/* user-defined log writer */ /* user-defined log writer */
HIO_LOG_WRITER HIO_LOG_WRITER,
/** hio_oow_t. size of the shared read buffer, in octets.
*
* bigger means fewer read calls for the same traffic - a bulk transfer
* saturates the buffer, so the count falls in proportion. smaller means
* less memory per loop, which matters when a process runs one loop per
* thread.
*
* it cannot be set below #HIO_MIN_READ_BUFFER_SIZE, nor below the largest
* dev_rdmin among the devices already running, and a device whose
* dev_rdmin exceeds it refuses to start. a datagram socket asks for
* #HIO_DGRAM_READ_BUFFER_SIZE that way, because a truncated datagram is
* lost silently rather than reported.
*
* setting it is refused with #HIO_EBUSY while the loop is dispatching
* events, since on_read() is handed a pointer into the buffer and moving
* it under a callback would leave that pointer dangling. */
HIO_READ_BUFFER_SIZE
}; };
typedef enum hio_option_t hio_option_t; typedef enum hio_option_t hio_option_t;
@@ -326,6 +361,14 @@ struct hio_cwq_t
hio_cwq_t* q_next; hio_cwq_t* q_next;
hio_cwq_t* q_prev; hio_cwq_t* q_prev;
/* every entry sits on two queues at once. q_next/q_prev thread the
* loop-wide queue, which is what fixes the order callbacks run in.
* d_next threads the owning device's own queue, which is what makes
* finding one device's entries cheap instead of a walk of everything.
* both are filled in the same order, so the head of one is the head of
* the other for any given device. */
hio_cwq_t* d_next;
hio_iolen_t olen; hio_iolen_t olen;
void* ctx; void* ctx;
hio_dev_t* dev; hio_dev_t* dev;
@@ -387,9 +430,12 @@ struct hio_wq_t
hio_dev_mth_t* dev_mth; \ hio_dev_mth_t* dev_mth; \
hio_dev_evcb_t* dev_evcb; \ hio_dev_evcb_t* dev_evcb; \
hio_ntime_t rtmout; \ hio_ntime_t rtmout; \
hio_oow_t dev_rdmin; /* smallest read buffer this device can work with. 0 means it does not care. set by the make() method */ \
hio_tmridx_t rtmridx; \ hio_tmridx_t rtmridx; \
int dev_extra_events; /* events the transport needs watched regardless of the device's own i/o state. see hio_dev_watch() */ \ int dev_extra_events; /* events the transport needs watched regardless of the device's own i/o state. see hio_dev_watch() */ \
hio_wq_t wq; \ hio_wq_t wq; \
hio_cwq_t* cwq_head; /* this device's completed-write queue, oldest first */ \
hio_cwq_t* cwq_tail; \
hio_oow_t wq_len; /* number of bytes sitting in the write queue, not yet handed to the transport */ \ hio_oow_t wq_len; /* number of bytes sitting in the write queue, not yet handed to the transport */ \
hio_oow_t wq_lim; /* soft cap on wq_len. 0 means no cap. see hio_dev_setwqlimit() */ \ hio_oow_t wq_lim; /* soft cap on wq_len. 0 means no cap. see hio_dev_setwqlimit() */ \
hio_oow_t cw_count; \ hio_oow_t cw_count; \
@@ -810,7 +856,14 @@ struct hio_t
} xbuf; /* buffer to support sprintf */ } xbuf; /* buffer to support sprintf */
} sprintf; } sprintf;
hio_uint8_t bigbuf[65535]; /* TODO: make this dynamic depending on devices added. device may indicate a buffer size required??? */ /* the one read buffer every device reads through. the pointer handed to
* on_read() points into this, and is only valid for the duration of that
* call - see HIO_READ_BUFFER_SIZE for how the size is chosen. */
struct
{
hio_uint8_t* ptr;
hio_oow_t capa;
} bigbuf;
hio_cfmb_t cfmb; /* list head of cfmbs */ hio_cfmb_t cfmb; /* list head of cfmbs */
hio_dev_t actdev; /* list head of active devices */ hio_dev_t actdev; /* list head of active devices */
+381 -56
View File
@@ -42,7 +42,12 @@
# include <netinet/if_ether.h> # include <netinet/if_ether.h>
#endif #endif
#if defined(HAVE_NETINET_SCTP_H) /* HIO_ENABLE_SCTP is settled by configure (--enable-sctp, on by default where
* the system provides it) and reported in its summary. it used to be derived
* here from nothing but the presence of the header, so the feature came and
* went with whatever happened to be installed and no one was told - which is
* how the SCTP_INITMSG call below stayed wrong for so long. */
#if defined(HIO_ENABLE_SCTP) && defined(HAVE_NETINET_SCTP_H)
# include <netinet/sctp.h> # include <netinet/sctp.h>
# if defined(IPPROTO_SCTP) # if defined(IPPROTO_SCTP)
# define ENABLE_SCTP # define ENABLE_SCTP
@@ -134,9 +139,12 @@
/* see the note at SSL_set_read_ahead() in do_ssl(). the core reads through /* see the note at SSL_set_read_ahead() in do_ssl(). the core reads through
* hio->bigbuf, and that buffer being at least one record wide is what keeps * hio->bigbuf, and that buffer being at least one record wide is what keeps
* SSL_pending() at zero. this fires the day the TODO on bigbuf in hio.h gets * SSL_pending() at zero.
* acted on and the buffer shrinks below a record. */ *
HIO_STATIC_ASSERT(HIO_COUNTOF(((hio_t*)0)->bigbuf) >= HIO_SSL_MAX_READ_RECORD); * the buffer is sized at run time now, so what is checked here is that the
* floor the core refuses to go below is itself wide enough. the runtime
* enforcement comes from that floor - see HIO_READ_BUFFER_SIZE. */
HIO_STATIC_ASSERT(HIO_MIN_READ_BUFFER_SIZE >= HIO_SSL_MAX_READ_RECORD);
#endif #endif
/* ========================================================================= */ /* ========================================================================= */
@@ -314,13 +322,13 @@ static struct sck_type_map_t sck_type_map[] =
/* HIO_DEV_SCK_SCTP6 */ /* HIO_DEV_SCK_SCTP6 */
{ AF_INET6, SOCK_STREAM, IPPROTO_SCTP, 1, 1, HIO_DEV_CAP_STREAM }, { AF_INET6, SOCK_STREAM, IPPROTO_SCTP, 1, 1, HIO_DEV_CAP_STREAM },
/* HIO_DEV_SCK_SCTP4_SP - not implemented */ /* HIO_DEV_SCK_SCTP4_SP - one-to-many. listen() is called but hio never
/*{ AF_INET, SOCK_SEQPACKET, IPPROTO_SCTP, 1, 1, 0 },*/ * accepts: associations are not devices in this model, they are told apart
{ -1, 0, 0, 0, 0, 0 }, * by the source address on each message. */
{ AF_INET, SOCK_SEQPACKET, IPPROTO_SCTP, 1, 1, 0 },
/* HIO_DEV_SCK_SCTP6_SP - not implemented */ /* HIO_DEV_SCK_SCTP6_SP */
/*{ AF_INET6, SOCK_SEQPACKET, IPPROTO_SCTP, 1, 1, 0 },*/ { AF_INET6, SOCK_SEQPACKET, IPPROTO_SCTP, 1, 1, 0 },
{ -1, 0, 0, 0, 0, 0 },
#else #else
{ -1, 0, 0, 0, 0, 0 }, { -1, 0, 0, 0, 0, 0 },
{ -1, 0, 0, 0, 0, 0 }, { -1, 0, 0, 0, 0, 0 },
@@ -367,6 +375,24 @@ static struct sck_type_map_t sck_type_map[] =
{ __AF_BPF, 0, 0, 0, 0, 0 } /* not implemented yet */ { __AF_BPF, 0, 0, 0, 0, 0 } /* not implemented yet */
}; };
/* how much read buffer one device of this type needs in a single read.
*
* a stream is content to be read in pieces and asks for nothing - a short read
* leaves the remainder for the next one. a datagram socket is not: whatever
* does not fit in one recvfrom() is discarded with nothing reported, so it asks
* for enough to hold the largest datagram it could ever see. the qx channel is
* message-oriented too, but its message is a fixed struct, so it asks for only
* that much.
*
* both the listening/connecting path and the accepted path go through here, so
* the two cannot drift apart. */
static hio_oow_t sck_type_rdmin (hio_dev_sck_type_t type)
{
if (sck_type_map[type].domain == HIO_AF_QX) return HIO_SIZEOF(hio_dev_sck_qxmsg_t);
if (!(sck_type_map[type].extra_dev_cap & HIO_DEV_CAP_STREAM)) return HIO_DGRAM_READ_BUFFER_SIZE;
return 0;
}
/* ======================================================================== */ /* ======================================================================== */
static void connect_timedout (hio_t* hio, const hio_ntime_t* now, hio_tmrjob_t* job) static void connect_timedout (hio_t* hio, const hio_ntime_t* now, hio_tmrjob_t* job)
@@ -465,14 +491,48 @@ static int dev_sck_make (hio_dev_t* dev, void* ctx)
if (hnd == HIO_SYSHND_INVALID) goto oops; if (hnd == HIO_SYSHND_INVALID) goto oops;
#if defined(ENABLE_SCTP) #if defined(ENABLE_SCTP)
if (sck_type_map[arg->type].type == SOCK_SEQPACKET && if (sck_type_map[arg->type].proto == IPPROTO_SCTP)
sck_type_map[arg->type].proto == IPPROTO_SCTP)
{ {
struct sctp_event_subscribe sctp_ev_s; struct sctp_event_subscribe ev;
HIO_MEMSET(&sctp_ev_s, 0, HIO_SIZEOF(sctp_ev_s)); /* [NOTE] this used to hand a struct sctp_initmsg to SCTP_EVENTS,
sctp_ev_s.sctp_data_io_event = 1; * which takes a struct sctp_event_subscribe. eight zero octets
setsockopt (hnd, IPPROTO_SCTP, SCTP_EVENTS, &sctp_ev_s, HIO_SIZEOF(sctp_ev_s)); * read as "subscribe to nothing", so it returned success and did
* nothing at all - and the SCTP_INITMSG that was meant to request
* streams never happened. */
if (arg->sctp_ostreams > 0 || arg->sctp_instreams > 0)
{
struct sctp_initmsg im;
HIO_MEMSET(&im, 0, HIO_SIZEOF(im));
im.sinit_num_ostreams = arg->sctp_ostreams;
im.sinit_max_instreams = arg->sctp_instreams;
if (setsockopt(hnd, IPPROTO_SCTP, SCTP_INITMSG, &im, HIO_SIZEOF(im)) <= -1)
{
hio_seterrwithsyserr(hio, 0, errno);
goto oops;
}
}
/* sctp_data_io_event is what delivers the per-message ancillary
* data - the stream number among it - so it is required for any
* of the stream handling to work.
*
* the rest are notifications: association up/down, a path
* changing state, a send that failed. they arrive interleaved
* with data on this same socket, flagged MSG_NOTIFICATION, and
* are routed to on_notification() rather than on_read(). */
HIO_MEMSET(&ev, 0, HIO_SIZEOF(ev));
ev.sctp_data_io_event = 1;
ev.sctp_association_event = 1;
ev.sctp_address_event = 1;
ev.sctp_send_failure_event = 1;
ev.sctp_peer_error_event = 1;
ev.sctp_shutdown_event = 1;
if (setsockopt(hnd, IPPROTO_SCTP, SCTP_EVENTS, &ev, HIO_SIZEOF(ev)) <= -1)
{
hio_seterrwithsyserr(hio, 0, errno);
goto oops;
}
} }
#endif #endif
} }
@@ -480,10 +540,13 @@ static int dev_sck_make (hio_dev_t* dev, void* ctx)
rdev->hnd = hnd; rdev->hnd = hnd;
rdev->side_chan = side_chan; rdev->side_chan = side_chan;
rdev->dev_cap = HIO_DEV_CAP_IN | HIO_DEV_CAP_OUT | sck_type_map[arg->type].extra_dev_cap; rdev->dev_cap = HIO_DEV_CAP_IN | HIO_DEV_CAP_OUT | sck_type_map[arg->type].extra_dev_cap;
rdev->dev_rdmin = sck_type_rdmin(arg->type);
rdev->on_write = arg->on_write; rdev->on_write = arg->on_write;
rdev->on_read = arg->on_read; rdev->on_read = arg->on_read;
rdev->on_connect = arg->on_connect; rdev->on_connect = arg->on_connect;
rdev->on_disconnect = arg->on_disconnect; rdev->on_disconnect = arg->on_disconnect;
rdev->on_notification = arg->on_notification;
rdev->on_raw_accept = arg->on_raw_accept; rdev->on_raw_accept = arg->on_raw_accept;
rdev->type = arg->type; rdev->type = arg->type;
@@ -497,11 +560,19 @@ oops:
return -1; return -1;
} }
/* what make_accepted_client_connection() hands to dev_sck_make_client() */
typedef struct sck_make_client_ctx_t sck_make_client_ctx_t;
struct sck_make_client_ctx_t
{
hio_syshnd_t hnd;
hio_dev_sck_type_t type;
};
static int dev_sck_make_client (hio_dev_t* dev, void* ctx) static int dev_sck_make_client (hio_dev_t* dev, void* ctx)
{ {
hio_t* hio = dev->hio; hio_t* hio = dev->hio;
hio_dev_sck_t* rdev = (hio_dev_sck_t*)dev; hio_dev_sck_t* rdev = (hio_dev_sck_t*)dev;
hio_syshnd_t* clisckhnd = (hio_syshnd_t*)ctx; sck_make_client_ctx_t* mc = (sck_make_client_ctx_t*)ctx;
/* create a socket device that is made of a socket connection /* create a socket device that is made of a socket connection
* on a listening socket. * on a listening socket.
@@ -509,10 +580,17 @@ static int dev_sck_make_client (hio_dev_t* dev, void* ctx)
* most of the initialization is done by the listening socket device * most of the initialization is done by the listening socket device
* after a client socket has been created. */ * after a client socket has been created. */
rdev->hnd = *clisckhnd; rdev->hnd = mc->hnd;
rdev->tmrjob_index = HIO_TMRIDX_INVALID; rdev->tmrjob_index = HIO_TMRIDX_INVALID;
rdev->side_chan = HIO_SYSHND_INVALID; rdev->side_chan = HIO_SYSHND_INVALID;
/* the type is settled again by the caller, but dev_rdmin has to be known
* before this method returns: hio_dev_make() checks it against the loop's
* read buffer the moment make() is done, and there is no second chance to
* refuse the device after that. */
rdev->type = mc->type;
rdev->dev_rdmin = sck_type_rdmin(mc->type);
if (hio_makesyshndasync(hio, rdev->hnd) <= -1 || if (hio_makesyshndasync(hio, rdev->hnd) <= -1 ||
hio_makesyshndcloexec(hio, rdev->hnd) <= -1) goto oops; hio_makesyshndcloexec(hio, rdev->hnd) <= -1) goto oops;
@@ -537,6 +615,10 @@ static int dev_sck_kill (hio_dev_t* dev, int force)
{ {
hio_t* hio = dev->hio; hio_t* hio = dev->hio;
hio_dev_sck_t* rdev = (hio_dev_sck_t*)dev; hio_dev_sck_t* rdev = (hio_dev_sck_t*)dev;
/* remembered for the trace at the end, by which point rdev->hnd has been
* closed and reset. HIO_UNUSED because that trace is the only reader and
* it compiles away in a release build. */
hio_syshnd_t hnd HIO_UNUSED = rdev->hnd;
HIO_DEBUG2(hio, "SCK(%p) - being killed [%d]\n", rdev, rdev->hnd); HIO_DEBUG2(hio, "SCK(%p) - being killed [%d]\n", rdev, rdev->hnd);
#if 0 #if 0
@@ -547,7 +629,7 @@ static int dev_sck_kill (hio_dev_t* dev, int force)
/* for HIO_DEV_SCK_CONNECTING, HIO_DEV_SCK_CONNECTING_SSL, and HIO_DEV_SCK_ACCEPTING_SSL /* for HIO_DEV_SCK_CONNECTING, HIO_DEV_SCK_CONNECTING_SSL, and HIO_DEV_SCK_ACCEPTING_SSL
* on_disconnect() is called without corresponding on_connect(). * on_disconnect() is called without corresponding on_connect().
* it is the same if connect or accept has not been called. */ * it is the same if connect or accept has not been called. */
if (rdev->on_disconnect) rdev->on_disconnect (rdev); if (rdev->on_disconnect) rdev->on_disconnect(rdev);
/*}*/ /*}*/
} }
else else
@@ -759,14 +841,22 @@ static int recvmsg_sctp(
} }
static int sendmsg_sctp( static int sendmsg_sctp(
int s, const hio_iovec_t* iov, hio_oow_t iovcnt, struct sockaddr* dstaddr, hio_scklen_t dstaddrlen, int s, const hio_iovec_t* iov, hio_oow_t iovcnt,struct sockaddr* dstaddr, hio_scklen_t dstaddrlen,
hio_uint32_t ppid, hio_uint32_t flags, hio_uint16_t stream_no, hio_uint32_t ttl, hio_uint32_t context) hio_uint32_t ppid, hio_uint32_t flags, hio_uint16_t stream_no, hio_uint32_t ttl, hio_uint32_t context,
hio_int32_t assoc_id)
{ {
struct sctp_sndrcvinfo* sinfo; struct sctp_sndrcvinfo* sinfo;
struct msghdr msg; struct msghdr msg;
struct cmsghdr* cmsg; struct cmsghdr* cmsg;
hio_uint8_t cmsg_buf[CMSG_SPACE(HIO_SIZEOF(*sinfo))]; hio_uint8_t cmsg_buf[CMSG_SPACE(HIO_SIZEOF(*sinfo))];
/* both are handed to the kernel, so neither may carry anything
* uninitialised. recvmsg_sctp() above already zeroes its msghdr and this
* one did not - struct msghdr has padding members on some ABIs. the
* control buffer is fully written below, so zeroing it is belt and braces. */
HIO_MEMSET(&msg, 0, HIO_SIZEOF(msg));
HIO_MEMSET(cmsg_buf, 0, HIO_SIZEOF(cmsg_buf));
msg.msg_name = dstaddr; msg.msg_name = dstaddr;
msg.msg_namelen = dstaddrlen; msg.msg_namelen = dstaddrlen;
msg.msg_iov = (struct iovec*)iov; msg.msg_iov = (struct iovec*)iov;
@@ -789,10 +879,42 @@ static int sendmsg_sctp(
sinfo->sinfo_stream = stream_no; sinfo->sinfo_stream = stream_no;
sinfo->sinfo_timetolive = ttl; sinfo->sinfo_timetolive = ttl;
sinfo->sinfo_context = context; sinfo->sinfo_context = context;
/* on a one-to-many socket a non-zero association id names the association
* directly, which survives a peer changing address; zero falls back to
* addressing by msg_name. it is ignored on a one-to-one socket. */
sinfo->sinfo_assoc_id = (sctp_assoc_t)assoc_id;
return sendmsg(s, &msg, 0); return sendmsg(s, &msg, 0);
} }
/* the core passes no destination for a connected socket - hio_dev_sck_write()
* with a null dstaddr - so these must not be dereferenced blindly. an absent
* destination means "the peer we are associated with", stream 0. */
static HIO_INLINE struct sockaddr* dstaddr_sockaddr (const hio_devaddr_t* dstaddr)
{
return (dstaddr && dstaddr->ptr)? (struct sockaddr*)dstaddr->ptr: HIO_NULL;
}
static HIO_INLINE hio_scklen_t dstaddr_len (const hio_devaddr_t* dstaddr)
{
return (dstaddr && dstaddr->ptr)? (hio_scklen_t)dstaddr->len: 0;
}
static HIO_INLINE hio_uint16_t dstaddr_chan (const hio_devaddr_t* dstaddr)
{
return (dstaddr && dstaddr->ptr)? hio_skad_get_chan((const hio_skad_t*)dstaddr->ptr): 0;
}
static HIO_INLINE hio_uint32_t dstaddr_ppid (const hio_devaddr_t* dstaddr)
{
return (dstaddr && dstaddr->ptr)? hio_skad_get_ppid((const hio_skad_t*)dstaddr->ptr): 0;
}
static HIO_INLINE hio_int32_t dstaddr_assoc (const hio_devaddr_t* dstaddr)
{
return (dstaddr && dstaddr->ptr)? hio_skad_get_assoc((const hio_skad_t*)dstaddr->ptr): 0;
}
static int dev_sck_read_sctp_sp (hio_dev_t* dev, void* buf, hio_iolen_t* len, hio_devaddr_t* srcaddr) static int dev_sck_read_sctp_sp (hio_dev_t* dev, void* buf, hio_iolen_t* len, hio_devaddr_t* srcaddr)
{ {
/* NOTE: sctp support is far away from complete */ /* NOTE: sctp support is far away from complete */
@@ -819,12 +941,47 @@ static int dev_sck_read_sctp_sp (hio_dev_t* dev, void* buf, hio_iolen_t* len, hi
return -1; return -1;
} }
/* if (msg_flags & MSG_NOTIFICATION)
if (msg_flags & MSG_EOR) end of message?? {
else push to buffer??? /* an association or path event, not payload. handing this to
*/ * on_read() would splice an event record into the data stream. */
hio_skad_set_chan (&rdev->remoteaddr, sri.sinfo_stream); if (rdev->on_notification) rdev->on_notification(rdev, buf, x);
/* TODO: how to store sri.sinfo_ppid or sri.sinfo_context? */ return 0; /* nothing readable for the caller this time round */
}
/* SOCK_SEQPACKET keeps message boundaries. MSG_EOR says this read reached
* the end of one; without it, what we hold is the front of a message too
* large for the read buffer, and the rest is still queued.
*
* such a fragment must not go to on_read(): it would arrive looking like a
* whole message and the remainder would arrive as another, quietly turning
* one message into two. reassembling here is not possible either - one
* device carries every association, so partial messages from different
* associations interleave and the buffer would have to be keyed by
* association. that belongs with the peel-off model, where an association
* is a device of its own and reassembly is per-device.
*
* so the message is dropped whole and the loss is reported. one oversized
* message from one peer costs that message and nothing else - the socket
* keeps serving everyone else. the remedy is a larger read buffer, which
* HIO_READ_BUFFER_SIZE now makes possible. */
if (rdev->sctp_discarding || !(msg_flags & MSG_EOR))
{
if (!rdev->sctp_discarding)
{
rdev->sctp_discarding = 1;
HIO_INFO2 (hio, "SCK(%p) - discarding an sctp message too large for the read buffer of %zu octets\n", rdev, hio->bigbuf.capa);
}
/* keep swallowing fragments until the one that ends the message */
if (msg_flags & MSG_EOR) rdev->sctp_discarding = 0;
return 0;
}
hio_skad_set_chan(&rdev->remoteaddr, sri.sinfo_stream);
hio_skad_set_ppid(&rdev->remoteaddr, sri.sinfo_ppid);
/* the stable name for the association this arrived on. a reply addressed
* by it reaches the same association even if the peer's address changed. */
hio_skad_set_assoc(&rdev->remoteaddr, (hio_int32_t)sri.sinfo_assoc_id);
srcaddr->ptr = &rdev->remoteaddr; srcaddr->ptr = &rdev->remoteaddr;
srcaddr->len = srcaddrlen; srcaddr->len = srcaddrlen;
@@ -1114,12 +1271,13 @@ static int dev_sck_write_sctp_sp (hio_dev_t* dev, const void* data, hio_iolen_t*
iov.iov_ptr = (void*)data; iov.iov_ptr = (void*)data;
iov.iov_len = *len; iov.iov_len = *len;
x = sendmsg_sctp(rdev->hnd, x = sendmsg_sctp(rdev->hnd,
&iov, 1, dstaddr->ptr, dstaddr->len, &iov, 1, dstaddr_sockaddr(dstaddr), dstaddr_len(dstaddr),
0, /* ppid - opaque */ dstaddr_ppid(dstaddr), /* ppid - opaque, carried on the address */
0, /* flags (e.g. SCTP_UNORDERED, SCTP_EOF, SCT_ABORT, ...) */ 0, /* flags (e.g. SCTP_UNORDERED, SCTP_EOF, SCT_ABORT, ...) */
hio_skad_get_chan(dstaddr->ptr), /* stream number */ dstaddr_chan(dstaddr), /* stream number */
0, /* ttl */ 0, /* ttl */
0 /* context*/); 0, /* context*/
dstaddr_assoc(dstaddr));
if (x <= -1) if (x <= -1)
{ {
if (errno == EINPROGRESS || errno == EWOULDBLOCK || errno == EAGAIN) return 0; /* no data can be written */ if (errno == EINPROGRESS || errno == EWOULDBLOCK || errno == EAGAIN) return 0; /* no data can be written */
@@ -1132,6 +1290,119 @@ static int dev_sck_write_sctp_sp (hio_dev_t* dev, const void* data, hio_iolen_t*
return 1; return 1;
} }
/* the one-to-one sctp types carry HIO_DEV_CAP_STREAM, so the core treats them
* like a tcp socket: a zero-length write is the writing-end shutdown, and a
* zero-length read is EOF. the seqpacket methods above answer neither of those
* conventions - a zero-length sendmsg() puts an empty message on the wire
* rather than closing anything - so the stream variants need their own pair.
*
* what they add over the plain stream methods is the ancillary data: the
* stream number and ppid, which is the whole point of using sctp. */
static int dev_sck_read_sctp_stream (hio_dev_t* dev, void* buf, hio_iolen_t* len, hio_devaddr_t* srcaddr)
{
hio_t* hio = dev->hio;
hio_dev_sck_t* rdev = (hio_dev_sck_t*)dev;
hio_scklen_t srcaddrlen;
ssize_t x;
int msg_flags = 0;
struct sctp_sndrcvinfo sri;
HIO_MEMSET(&sri, 0, HIO_SIZEOF(sri));
srcaddrlen = HIO_SIZEOF(rdev->remoteaddr);
x = recvmsg_sctp(rdev->hnd, buf, *len, (struct sockaddr*)&rdev->remoteaddr, &srcaddrlen, &sri, &msg_flags);
if (x <= -1)
{
int eno = errno;
if (eno == EINPROGRESS || eno == EWOULDBLOCK || eno == EAGAIN) return 0;
if (eno == EINTR) return 0;
hio_seterrwithsyserr(hio, 0, eno);
return -1;
}
if (msg_flags & MSG_NOTIFICATION)
{
/* an association or path event, not payload */
if (rdev->on_notification) rdev->on_notification(rdev, buf, x);
return 0;
}
/* x of 0 falls through as a zero length, which is what the core reads as
* EOF on a stream device */
hio_skad_set_chan (&rdev->remoteaddr, sri.sinfo_stream);
hio_skad_set_ppid (&rdev->remoteaddr, sri.sinfo_ppid);
hio_skad_set_assoc (&rdev->remoteaddr, (hio_int32_t)sri.sinfo_assoc_id);
*len = x;
return 1;
}
static int dev_sck_write_sctp_stream (hio_dev_t* dev, const void* data, hio_iolen_t* len, const hio_devaddr_t* dstaddr)
{
hio_t* hio = dev->hio;
hio_dev_sck_t* rdev = (hio_dev_sck_t*)dev;
ssize_t x;
hio_iovec_t iov;
if (*len <= 0)
{
/* the writing-end shutdown indicator, as for any stream device */
if (shutdown(rdev->hnd, SHUT_WR) <= -1)
{
hio_seterrwithsyserr(hio, 0, errno);
return -1;
}
return 1; /* must be non-zero, or the core queues the request */
}
iov.iov_ptr = (void*)data;
iov.iov_len = *len;
x = sendmsg_sctp(rdev->hnd,
&iov, 1, dstaddr_sockaddr(dstaddr), dstaddr_len(dstaddr),
dstaddr_ppid(dstaddr), 0, dstaddr_chan(dstaddr), 0, 0, dstaddr_assoc(dstaddr));
if (x <= -1)
{
if (errno == EINPROGRESS || errno == EWOULDBLOCK || errno == EAGAIN) return 0;
if (errno == EINTR) return 0;
hio_seterrwithsyserr(hio, 0, errno);
return -1;
}
*len = x;
return 1;
}
static int dev_sck_writev_sctp_stream (hio_dev_t* dev, const hio_iovec_t* iov, hio_iolen_t* iovcnt, const hio_devaddr_t* dstaddr)
{
hio_t* hio = dev->hio;
hio_dev_sck_t* rdev = (hio_dev_sck_t*)dev;
ssize_t x;
if (*iovcnt <= 0)
{
if (shutdown(rdev->hnd, SHUT_WR) <= -1)
{
hio_seterrwithsyserr(hio, 0, errno);
return -1;
}
return 1;
}
x = sendmsg_sctp(rdev->hnd,
iov, *iovcnt, dstaddr_sockaddr(dstaddr), dstaddr_len(dstaddr),
dstaddr_ppid(dstaddr), 0, dstaddr_chan(dstaddr), 0, 0, dstaddr_assoc(dstaddr));
if (x <= -1)
{
if (errno == EINPROGRESS || errno == EWOULDBLOCK || errno == EAGAIN) return 0;
if (errno == EINTR) return 0;
hio_seterrwithsyserr(hio, 0, errno);
return -1;
}
*iovcnt = x;
return 1;
}
static int dev_sck_writev_sctp_sp (hio_dev_t* dev, const hio_iovec_t* iov, hio_iolen_t* iovcnt, const hio_devaddr_t* dstaddr) static int dev_sck_writev_sctp_sp (hio_dev_t* dev, const hio_iovec_t* iov, hio_iolen_t* iovcnt, const hio_devaddr_t* dstaddr)
{ {
hio_t* hio = dev->hio; hio_t* hio = dev->hio;
@@ -1139,12 +1410,13 @@ static int dev_sck_writev_sctp_sp (hio_dev_t* dev, const hio_iovec_t* iov, hio_i
ssize_t x; ssize_t x;
x = sendmsg_sctp(rdev->hnd, x = sendmsg_sctp(rdev->hnd,
iov, *iovcnt, dstaddr->ptr, dstaddr->len, iov, *iovcnt, dstaddr_sockaddr(dstaddr), dstaddr_len(dstaddr),
0, /* ppid - opaque */ dstaddr_ppid(dstaddr), /* ppid - opaque, carried on the address */
0, /* flags (e.g. SCTP_UNORDERED, SCTP_EOF, SCT_ABORT, ...) */ 0, /* flags (e.g. SCTP_UNORDERED, SCTP_EOF, SCT_ABORT, ...) */
hio_skad_get_chan(dstaddr->ptr), /* stream number */ dstaddr_chan(dstaddr), /* stream number */
0, /* ttl */ 0, /* ttl */
0 /* context*/); 0, /* context*/
dstaddr_assoc(dstaddr));
if (x <= -1) if (x <= -1)
{ {
if (errno == EINPROGRESS || errno == EWOULDBLOCK || errno == EAGAIN) return 0; /* no data can be written */ if (errno == EINPROGRESS || errno == EWOULDBLOCK || errno == EAGAIN) return 0; /* no data can be written */
@@ -1430,7 +1702,7 @@ static int dev_sck_ioctl (hio_dev_t* dev, int cmd, void* arg)
{ {
if (!(bnd->options & HIO_DEV_SCK_BIND_IGNERR)) if (!(bnd->options & HIO_DEV_SCK_BIND_IGNERR))
{ {
hio_seterrbfmtwithsyserr (hio, 0, errno, "unable to set IPV6_V6ONLY"); hio_seterrbfmtwithsyserr(hio, 0, errno, "unable to set IPV6_V6ONLY");
return -1; return -1;
} }
} }
@@ -1443,7 +1715,7 @@ static int dev_sck_ioctl (hio_dev_t* dev, int cmd, void* arg)
if (setsockopt(rdev->hnd, SOL_SOCKET, SO_BROADCAST, &v, HIO_SIZEOF(v)) <= -1) if (setsockopt(rdev->hnd, SOL_SOCKET, SO_BROADCAST, &v, HIO_SIZEOF(v)) <= -1)
{ {
/* not affected by HIO_DEV_SCK_BIND_IGNERR */ /* not affected by HIO_DEV_SCK_BIND_IGNERR */
hio_seterrbfmtwithsyserr (hio, 0, errno, "unable to set SO_BROADCAST"); hio_seterrbfmtwithsyserr(hio, 0, errno, "unable to set SO_BROADCAST");
return -1; return -1;
} }
} }
@@ -1456,7 +1728,7 @@ static int dev_sck_ioctl (hio_dev_t* dev, int cmd, void* arg)
{ {
if (!(bnd->options & HIO_DEV_SCK_BIND_IGNERR)) if (!(bnd->options & HIO_DEV_SCK_BIND_IGNERR))
{ {
hio_seterrbfmtwithsyserr (hio, 0, errno, "unable to set SO_REUSEADDR"); hio_seterrbfmtwithsyserr(hio, 0, errno, "unable to set SO_REUSEADDR");
return -1; return -1;
} }
} }
@@ -1476,7 +1748,7 @@ static int dev_sck_ioctl (hio_dev_t* dev, int cmd, void* arg)
{ {
if (!(bnd->options & HIO_DEV_SCK_BIND_IGNERR)) if (!(bnd->options & HIO_DEV_SCK_BIND_IGNERR))
{ {
hio_seterrbfmtwithsyserr (hio, 0, errno, "unable to set SO_REUSEPORT"); hio_seterrbfmtwithsyserr(hio, 0, errno, "unable to set SO_REUSEPORT");
return -1; return -1;
} }
} }
@@ -1494,7 +1766,7 @@ static int dev_sck_ioctl (hio_dev_t* dev, int cmd, void* arg)
int v = 1; int v = 1;
if (setsockopt(rdev->hnd, SOL_IP, IP_TRANSPARENT, &v, HIO_SIZEOF(v)) <= -1) if (setsockopt(rdev->hnd, SOL_IP, IP_TRANSPARENT, &v, HIO_SIZEOF(v)) <= -1)
{ {
hio_seterrbfmtwithsyserr (hio, 0, errno, "unable to set IP_TRANSPARENT"); hio_seterrbfmtwithsyserr(hio, 0, errno, "unable to set IP_TRANSPARENT");
return -1; return -1;
} }
/* ignore it if not available /* ignore it if not available
@@ -1639,13 +1911,13 @@ static int dev_sck_ioctl (hio_dev_t* dev, int cmd, void* arg)
#endif #endif
/* the socket is already non-blocking */ /* the socket is already non-blocking */
/*{ /*{
int flags = fcntl (rdev->hnd, F_GETFL); int flags = fcntl(rdev->hnd, F_GETFL);
fcntl (rdev->hnd, F_SETFL, flags & ~O_NONBLOCK); fcntl(rdev->hnd, F_SETFL, flags & ~O_NONBLOCK);
}*/ }*/
x = connect(rdev->hnd, sa, sl); x = connect(rdev->hnd, sa, sl);
/*{ /*{
int flags = fcntl (rdev->hnd, F_GETFL); int flags = fcntl(rdev->hnd, F_GETFL);
fcntl (rdev->hnd, F_SETFL, flags | O_NONBLOCK); fcntl(rdev->hnd, F_SETFL, flags | O_NONBLOCK);
}*/ }*/
if (x <= -1) if (x <= -1)
{ {
@@ -1794,6 +2066,43 @@ static hio_dev_mth_t dev_mth_sck_stateless =
}; };
#if defined(ENABLE_SCTP)
/* one-to-one sctp. a stream device like tcp, but reads and writes carry the
* per-message ancillary data, so the stream number and ppid are reachable.
* no sendfile: it would bypass sendmsg() and lose that data. */
static hio_dev_mth_t dev_mth_sck_sctp_stream =
{
dev_sck_make,
dev_sck_kill,
HIO_NULL,
dev_sck_getsyshnd,
HIO_NULL,
dev_sck_ioctl,
dev_sck_read_sctp_stream,
dev_sck_write_sctp_stream,
dev_sck_writev_sctp_stream,
HIO_NULL, /* sendfile */
HIO_NULL /* readpending */
};
static hio_dev_mth_t dev_mth_clisck_sctp_stream =
{
dev_sck_make_client,
dev_sck_kill,
HIO_NULL,
dev_sck_getsyshnd,
HIO_NULL,
dev_sck_ioctl,
dev_sck_read_sctp_stream,
dev_sck_write_sctp_stream,
dev_sck_writev_sctp_stream,
HIO_NULL, /* sendfile */
HIO_NULL /* readpending */
};
#endif
static hio_dev_mth_t dev_mth_sck_stream = static hio_dev_mth_t dev_mth_sck_stream =
{ {
dev_sck_make, dev_sck_make,
@@ -1921,7 +2230,7 @@ static int harvest_outgoing_connection (hio_dev_sck_t* rdev)
len = HIO_SIZEOF(errcode); len = HIO_SIZEOF(errcode);
if (getsockopt(rdev->hnd, SOL_SOCKET, SO_ERROR, (char*)&errcode, &len) <= -1) if (getsockopt(rdev->hnd, SOL_SOCKET, SO_ERROR, (char*)&errcode, &len) <= -1)
{ {
hio_seterrbfmtwithsyserr (hio, 0, errno, "unable to get SO_ERROR"); hio_seterrbfmtwithsyserr(hio, 0, errno, "unable to get SO_ERROR");
return -1; return -1;
} }
else if (errcode == 0) else if (errcode == 0)
@@ -1929,11 +2238,13 @@ static int harvest_outgoing_connection (hio_dev_sck_t* rdev)
hio_skad_t localaddr; hio_skad_t localaddr;
hio_scklen_t addrlen; hio_scklen_t addrlen;
HIO_MEMSET(&localaddr, 0, HIO_SIZEOF(localaddr)); /* see hio_dev_sck_getsockaddr() */
/* connected */ /* connected */
if (rdev->tmrjob_index != HIO_TMRIDX_INVALID) if (rdev->tmrjob_index != HIO_TMRIDX_INVALID)
{ {
hio_deltmrjob (hio, rdev->tmrjob_index); hio_deltmrjob(hio, rdev->tmrjob_index);
HIO_ASSERT(hio, rdev->tmrjob_index == HIO_TMRIDX_INVALID); HIO_ASSERT(hio, rdev->tmrjob_index == HIO_TMRIDX_INVALID);
} }
@@ -1984,7 +2295,7 @@ static int harvest_outgoing_connection (hio_dev_sck_t* rdev)
ssl_connected: ssl_connected:
#endif #endif
HIO_DEV_SCK_SET_PROGRESS(rdev, HIO_DEV_SCK_CONNECTED); HIO_DEV_SCK_SET_PROGRESS(rdev, HIO_DEV_SCK_CONNECTED);
if (rdev->on_connect) rdev->on_connect (rdev); if (rdev->on_connect) rdev->on_connect(rdev);
#if defined(USE_SSL) #if defined(USE_SSL)
} }
#endif #endif
@@ -2005,6 +2316,7 @@ static int harvest_outgoing_connection (hio_dev_sck_t* rdev)
static int make_accepted_client_connection (hio_dev_sck_t* rdev, hio_syshnd_t clisck, hio_skad_t* remoteaddr, hio_dev_sck_type_t clisck_type) static int make_accepted_client_connection (hio_dev_sck_t* rdev, hio_syshnd_t clisck, hio_skad_t* remoteaddr, hio_dev_sck_type_t clisck_type)
{ {
sck_make_client_ctx_t mc;
hio_t* hio = rdev->hio; hio_t* hio = rdev->hio;
hio_dev_sck_t* clidev; hio_dev_sck_t* clidev;
hio_scklen_t addrlen; hio_scklen_t addrlen;
@@ -2029,12 +2341,15 @@ static int make_accepted_client_connection (hio_dev_sck_t* rdev, hio_syshnd_t cl
* device capability. currently, stream or non-stream is supported. * device capability. currently, stream or non-stream is supported.
*/ */
#if defined(ENABLE_SCTP) #if defined(ENABLE_SCTP)
dev_mth = (sck_type_map[clisck_type].extra_dev_cap & HIO_DEV_CAP_STREAM)? &dev_mth_clisck_stream: dev_mth = (sck_type_map[clisck_type].extra_dev_cap & HIO_DEV_CAP_STREAM)?
((sck_type_map[clisck_type].proto == IPPROTO_SCTP)? &dev_mth_clisck_sctp_stream: &dev_mth_clisck_stream):
(sck_type_map[clisck_type].proto == IPPROTO_SCTP)? &dev_mth_clisck_sctp_sp: &dev_mth_clisck_stateless; (sck_type_map[clisck_type].proto == IPPROTO_SCTP)? &dev_mth_clisck_sctp_sp: &dev_mth_clisck_stateless;
#else #else
dev_mth = (sck_type_map[clisck_type].extra_dev_cap & HIO_DEV_CAP_STREAM)? &dev_mth_clisck_stream: &dev_mth_clisck_stateless; dev_mth = (sck_type_map[clisck_type].extra_dev_cap & HIO_DEV_CAP_STREAM)? &dev_mth_clisck_stream: &dev_mth_clisck_stateless;
#endif #endif
clidev = (hio_dev_sck_t*)hio_dev_make(hio, rdev->dev_size, dev_mth, rdev->dev_evcb, &clisck); mc.hnd = clisck;
mc.type = clisck_type;
clidev = (hio_dev_sck_t*)hio_dev_make(hio, rdev->dev_size, dev_mth, rdev->dev_evcb, &mc);
if (HIO_UNLIKELY(!clidev)) if (HIO_UNLIKELY(!clidev))
{ {
/* [NOTE] 'clisck' is closed by callback methods called by hio_dev_make() upon failure */ /* [NOTE] 'clisck' is closed by callback methods called by hio_dev_make() upon failure */
@@ -2273,12 +2588,12 @@ static int dev_evcb_sck_ready_stream (hio_dev_t* dev, int events)
if (rdev->tmrjob_index != HIO_TMRIDX_INVALID) if (rdev->tmrjob_index != HIO_TMRIDX_INVALID)
{ {
hio_deltmrjob (rdev->hio, rdev->tmrjob_index); hio_deltmrjob(rdev->hio, rdev->tmrjob_index);
rdev->tmrjob_index = HIO_TMRIDX_INVALID; rdev->tmrjob_index = HIO_TMRIDX_INVALID;
} }
HIO_DEV_SCK_SET_PROGRESS(rdev, HIO_DEV_SCK_CONNECTED); HIO_DEV_SCK_SET_PROGRESS(rdev, HIO_DEV_SCK_CONNECTED);
if (rdev->on_connect) rdev->on_connect (rdev); if (rdev->on_connect) rdev->on_connect(rdev);
return 0; return 0;
} }
else else
@@ -2345,12 +2660,12 @@ static int dev_evcb_sck_ready_stream (hio_dev_t* dev, int events)
if (rdev->tmrjob_index != HIO_TMRIDX_INVALID) if (rdev->tmrjob_index != HIO_TMRIDX_INVALID)
{ {
hio_deltmrjob (rdev->hio, rdev->tmrjob_index); hio_deltmrjob(rdev->hio, rdev->tmrjob_index);
rdev->tmrjob_index = HIO_TMRIDX_INVALID; rdev->tmrjob_index = HIO_TMRIDX_INVALID;
} }
HIO_DEV_SCK_SET_PROGRESS(rdev, HIO_DEV_SCK_ACCEPTED); HIO_DEV_SCK_SET_PROGRESS(rdev, HIO_DEV_SCK_ACCEPTED);
if (rdev->on_connect) rdev->on_connect (rdev); if (rdev->on_connect) rdev->on_connect(rdev);
return 0; return 0;
} }
@@ -2617,7 +2932,12 @@ hio_dev_sck_t* hio_dev_sck_make (hio_t* hio, hio_oow_t xtnsize, const hio_dev_sc
{ {
rdev = (hio_dev_sck_t*)hio_dev_make( rdev = (hio_dev_sck_t*)hio_dev_make(
hio, HIO_SIZEOF(hio_dev_sck_t) + xtnsize, hio, HIO_SIZEOF(hio_dev_sck_t) + xtnsize,
&dev_mth_sck_stream, &dev_sck_event_callbacks_stream, (void*)info); #if defined(ENABLE_SCTP)
(sck_type_map[info->type].proto == IPPROTO_SCTP)? &dev_mth_sck_sctp_stream: &dev_mth_sck_stream,
#else
&dev_mth_sck_stream,
#endif
&dev_sck_event_callbacks_stream, (void*)info);
} }
#if defined(ENABLE_SCTP) #if defined(ENABLE_SCTP)
else if (sck_type_map[info->type].proto == IPPROTO_SCTP) else if (sck_type_map[info->type].proto == IPPROTO_SCTP)
@@ -2699,9 +3019,13 @@ int hio_dev_sck_getsockopt (hio_dev_sck_t* dev, int level, int optname, void* op
int hio_dev_sck_getsockaddr (hio_dev_sck_t* dev, hio_skad_t* skad) int hio_dev_sck_getsockaddr (hio_dev_sck_t* dev, hio_skad_t* skad)
{ {
hio_scklen_t addrlen = HIO_SIZEOF(*skad); hio_scklen_t addrlen = HIO_SIZEOF(*skad);
/* the system fills the sockaddr and nothing else. the extra area past it -
* chan, ppid, assoc - would keep whatever the caller's memory happened to
* hold, and those are read back later and passed to the kernel. */
HIO_MEMSET(skad, 0, HIO_SIZEOF(*skad));
if (dev->type == HIO_DEV_SCK_QX) if (dev->type == HIO_DEV_SCK_QX)
{ {
hio_skad_init_for_qx (skad); hio_skad_init_for_qx(skad);
} }
else if (getsockname(dev->hnd, (struct sockaddr*)skad, &addrlen) <= -1) else if (getsockname(dev->hnd, (struct sockaddr*)skad, &addrlen) <= -1)
{ {
@@ -2714,9 +3038,10 @@ int hio_dev_sck_getsockaddr (hio_dev_sck_t* dev, hio_skad_t* skad)
int hio_dev_sck_getpeeraddr (hio_dev_sck_t* dev, hio_skad_t* skad) int hio_dev_sck_getpeeraddr (hio_dev_sck_t* dev, hio_skad_t* skad)
{ {
hio_scklen_t addrlen = HIO_SIZEOF(*skad); hio_scklen_t addrlen = HIO_SIZEOF(*skad);
HIO_MEMSET(skad, 0, HIO_SIZEOF(*skad)); /* see hio_dev_sck_getsockaddr() */
if (dev->type == HIO_DEV_SCK_QX) if (dev->type == HIO_DEV_SCK_QX)
{ {
hio_skad_init_for_qx (skad); hio_skad_init_for_qx(skad);
} }
else if (getpeername(dev->hnd, (struct sockaddr*)skad, &addrlen) <= -1) else if (getpeername(dev->hnd, (struct sockaddr*)skad, &addrlen) <= -1)
{ {
+70
View File
@@ -45,6 +45,8 @@
struct sockaddr_extra_t struct sockaddr_extra_t
{ {
hio_uint16_t chan; hio_uint16_t chan;
hio_uint32_t ppid;
hio_int32_t assoc;
}; };
typedef struct sockaddr_extra_t sockaddr_extra_t; typedef struct sockaddr_extra_t sockaddr_extra_t;
@@ -57,6 +59,12 @@ typedef struct sockaddr_extra_t sockaddr_extra_t;
* extra fields: * extra fields:
* chan - used as a stream number for SCTP PACKETSEQ sockets. * chan - used as a stream number for SCTP PACKETSEQ sockets.
* use hio_skad_get_chan() and hio_skad_set_chan() for safe access. * use hio_skad_get_chan() and hio_skad_set_chan() for safe access.
* ppid - the SCTP payload protocol identifier, opaque to SCTP itself and
* passed through unchanged. use hio_skad_get_ppid()/set_ppid().
* assoc - the SCTP association identifier (sctp_assoc_t, an int32 on the
* systems that have it). the stable name for an association: its
* addresses can change under multi-homing, this does not. use
* hio_skad_get_assoc()/set_assoc().
*/ */
struct sockaddr_in_x struct sockaddr_in_x
{ {
@@ -1475,6 +1483,68 @@ void hio_skad_set_scope_id (hio_skad_t* _skad, int scope_id)
#endif #endif
} }
hio_int32_t hio_skad_get_assoc (const hio_skad_t* _skad)
{
const hio_skad_alt_t* skad = (const hio_skad_alt_t*)_skad;
switch (skad->sa.sa_family)
{
#if defined(AF_INET) && (HIO_SIZEOF_STRUCT_SOCKADDR_IN > 0)
case AF_INET: return skad->in4.x.assoc;
#endif
#if defined(AF_INET6) && (HIO_SIZEOF_STRUCT_SOCKADDR_IN6 > 0)
case AF_INET6: return skad->in6.x.assoc;
#endif
}
return 0;
}
void hio_skad_set_assoc (hio_skad_t* _skad, hio_int32_t assoc)
{
hio_skad_alt_t* skad = (hio_skad_alt_t*)_skad;
switch (skad->sa.sa_family)
{
#if defined(AF_INET) && (HIO_SIZEOF_STRUCT_SOCKADDR_IN > 0)
case AF_INET: skad->in4.x.assoc = assoc; break;
#endif
#if defined(AF_INET6) && (HIO_SIZEOF_STRUCT_SOCKADDR_IN6 > 0)
case AF_INET6: skad->in6.x.assoc = assoc; break;
#endif
}
}
hio_uint32_t hio_skad_get_ppid (const hio_skad_t* _skad)
{
const hio_skad_alt_t* skad = (const hio_skad_alt_t*)_skad;
switch (skad->sa.sa_family)
{
#if defined(AF_INET) && (HIO_SIZEOF_STRUCT_SOCKADDR_IN > 0)
case AF_INET: return skad->in4.x.ppid;
#endif
#if defined(AF_INET6) && (HIO_SIZEOF_STRUCT_SOCKADDR_IN6 > 0)
case AF_INET6: return skad->in6.x.ppid;
#endif
}
return 0;
}
void hio_skad_set_ppid (hio_skad_t* _skad, hio_uint32_t ppid)
{
hio_skad_alt_t* skad = (hio_skad_alt_t*)_skad;
switch (skad->sa.sa_family)
{
#if defined(AF_INET) && (HIO_SIZEOF_STRUCT_SOCKADDR_IN > 0)
case AF_INET: skad->in4.x.ppid = ppid; break;
#endif
#if defined(AF_INET6) && (HIO_SIZEOF_STRUCT_SOCKADDR_IN6 > 0)
case AF_INET6: skad->in6.x.ppid = ppid; break;
#endif
}
}
hio_uint16_t hio_skad_get_chan (const hio_skad_t* _skad) hio_uint16_t hio_skad_get_chan (const hio_skad_t* _skad)
{ {
const hio_skad_alt_t* skad = (const hio_skad_alt_t*)_skad; const hio_skad_alt_t* skad = (const hio_skad_alt_t*)_skad;
+9 -1
View File
@@ -18,7 +18,7 @@ check_SCRIPTS = s-001.sh s-002.sh
EXTRA_DIST = $(check_SCRIPTS) tap.inc t-cgi.sh EXTRA_DIST = $(check_SCRIPTS) tap.inc t-cgi.sh
## programs run as tests ## programs run as tests
TESTPROGS = t-001 t-002 t-003 t-004 t-005 t-006 t-007 t-008 t-009 t-010 t-011 TESTPROGS = t-001 t-002 t-003 t-004 t-005 t-006 t-007 t-008 t-009 t-010 t-011 t-012
## helpers the test scripts drive. built with the tests, but not tests ## helpers the test scripts drive. built with the tests, but not tests
## themselves - they are servers and would never exit. ## themselves - they are servers and would never exit.
@@ -99,6 +99,14 @@ t_011_CFLAGS = $(CFLAGS_COMMON)
t_011_LDFLAGS = $(LDFLAGS_COMMON) t_011_LDFLAGS = $(LDFLAGS_COMMON)
t_011_LDADD = $(LIBADD_COMMON) t_011_LDADD = $(LIBADD_COMMON)
## t-012 drives an sctp association. it skips itself where the build has no
## sctp support, which it discovers the same way a caller would.
t_012_SOURCES = t-012.c tap.h
t_012_CPPFLAGS = $(CPPFLAGS_COMMON)
t_012_CFLAGS = $(CFLAGS_COMMON)
t_012_LDFLAGS = $(LDFLAGS_COMMON)
t_012_LDADD = $(LIBADD_COMMON)
## a minimal FastCGI responder, driven by s-002.sh ## a minimal FastCGI responder, driven by s-002.sh
fcgis_SOURCES = fcgis.c fcgis_SOURCES = fcgis.c
fcgis_CPPFLAGS = $(CPPFLAGS_COMMON) fcgis_CPPFLAGS = $(CPPFLAGS_COMMON)
+48 -5
View File
@@ -108,7 +108,8 @@ CONFIG_CLEAN_FILES =
CONFIG_CLEAN_VPATH_FILES = CONFIG_CLEAN_VPATH_FILES =
am__EXEEXT_1 = t-001$(EXEEXT) t-002$(EXEEXT) t-003$(EXEEXT) \ am__EXEEXT_1 = t-001$(EXEEXT) t-002$(EXEEXT) t-003$(EXEEXT) \
t-004$(EXEEXT) t-005$(EXEEXT) t-006$(EXEEXT) t-007$(EXEEXT) \ t-004$(EXEEXT) t-005$(EXEEXT) t-006$(EXEEXT) t-007$(EXEEXT) \
t-008$(EXEEXT) t-009$(EXEEXT) t-010$(EXEEXT) t-011$(EXEEXT) t-008$(EXEEXT) t-009$(EXEEXT) t-010$(EXEEXT) t-011$(EXEEXT) \
t-012$(EXEEXT)
am__EXEEXT_2 = fcgis$(EXEEXT) httpecho$(EXEEXT) httssvr$(EXEEXT) \ am__EXEEXT_2 = fcgis$(EXEEXT) httpecho$(EXEEXT) httssvr$(EXEEXT) \
slowclient$(EXEEXT) slowclient$(EXEEXT)
am_fcgis_OBJECTS = fcgis-fcgis.$(OBJEXT) am_fcgis_OBJECTS = fcgis-fcgis.$(OBJEXT)
@@ -207,6 +208,12 @@ t_011_DEPENDENCIES = $(am__DEPENDENCIES_2)
t_011_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ t_011_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
$(LIBTOOLFLAGS) --mode=link $(CCLD) $(t_011_CFLAGS) $(CFLAGS) \ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(t_011_CFLAGS) $(CFLAGS) \
$(t_011_LDFLAGS) $(LDFLAGS) -o $@ $(t_011_LDFLAGS) $(LDFLAGS) -o $@
am_t_012_OBJECTS = t_012-t-012.$(OBJEXT)
t_012_OBJECTS = $(am_t_012_OBJECTS)
t_012_DEPENDENCIES = $(am__DEPENDENCIES_2)
t_012_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
$(LIBTOOLFLAGS) --mode=link $(CCLD) $(t_012_CFLAGS) $(CFLAGS) \
$(t_012_LDFLAGS) $(LDFLAGS) -o $@
AM_V_P = $(am__v_P_@AM_V@) AM_V_P = $(am__v_P_@AM_V@)
am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
am__v_P_0 = false am__v_P_0 = false
@@ -231,7 +238,7 @@ am__depfiles_remade = ./$(DEPDIR)/fcgis-fcgis.Po \
./$(DEPDIR)/t_005-t-005.Po ./$(DEPDIR)/t_006-t-006.Po \ ./$(DEPDIR)/t_005-t-005.Po ./$(DEPDIR)/t_006-t-006.Po \
./$(DEPDIR)/t_007-t-007.Po ./$(DEPDIR)/t_008-t-008.Po \ ./$(DEPDIR)/t_007-t-007.Po ./$(DEPDIR)/t_008-t-008.Po \
./$(DEPDIR)/t_009-t-009.Po ./$(DEPDIR)/t_010-t-010.Po \ ./$(DEPDIR)/t_009-t-009.Po ./$(DEPDIR)/t_010-t-010.Po \
./$(DEPDIR)/t_011-t-011.Po ./$(DEPDIR)/t_011-t-011.Po ./$(DEPDIR)/t_012-t-012.Po
am__mv = mv -f am__mv = mv -f
COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
$(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
@@ -255,12 +262,14 @@ SOURCES = $(fcgis_SOURCES) $(httpecho_SOURCES) $(httssvr_SOURCES) \
$(slowclient_SOURCES) $(t_001_SOURCES) $(t_002_SOURCES) \ $(slowclient_SOURCES) $(t_001_SOURCES) $(t_002_SOURCES) \
$(t_003_SOURCES) $(t_004_SOURCES) $(t_005_SOURCES) \ $(t_003_SOURCES) $(t_004_SOURCES) $(t_005_SOURCES) \
$(t_006_SOURCES) $(t_007_SOURCES) $(t_008_SOURCES) \ $(t_006_SOURCES) $(t_007_SOURCES) $(t_008_SOURCES) \
$(t_009_SOURCES) $(t_010_SOURCES) $(t_011_SOURCES) $(t_009_SOURCES) $(t_010_SOURCES) $(t_011_SOURCES) \
$(t_012_SOURCES)
DIST_SOURCES = $(fcgis_SOURCES) $(httpecho_SOURCES) $(httssvr_SOURCES) \ DIST_SOURCES = $(fcgis_SOURCES) $(httpecho_SOURCES) $(httssvr_SOURCES) \
$(slowclient_SOURCES) $(t_001_SOURCES) $(t_002_SOURCES) \ $(slowclient_SOURCES) $(t_001_SOURCES) $(t_002_SOURCES) \
$(t_003_SOURCES) $(t_004_SOURCES) $(t_005_SOURCES) \ $(t_003_SOURCES) $(t_004_SOURCES) $(t_005_SOURCES) \
$(t_006_SOURCES) $(t_007_SOURCES) $(t_008_SOURCES) \ $(t_006_SOURCES) $(t_007_SOURCES) $(t_008_SOURCES) \
$(t_009_SOURCES) $(t_010_SOURCES) $(t_011_SOURCES) $(t_009_SOURCES) $(t_010_SOURCES) $(t_011_SOURCES) \
$(t_012_SOURCES)
am__can_run_installinfo = \ am__can_run_installinfo = \
case $$AM_UPDATE_INFO_DIR in \ case $$AM_UPDATE_INFO_DIR in \
n|no|NO) false;; \ n|no|NO) false;; \
@@ -572,6 +581,7 @@ PTHREAD_CXX = @PTHREAD_CXX@
PTHREAD_LIBS = @PTHREAD_LIBS@ PTHREAD_LIBS = @PTHREAD_LIBS@
QUADMATH_LIBS = @QUADMATH_LIBS@ QUADMATH_LIBS = @QUADMATH_LIBS@
RANLIB = @RANLIB@ RANLIB = @RANLIB@
SCTP_ENABLED = @SCTP_ENABLED@
SED = @SED@ SED = @SED@
SENDFILE_LIBS = @SENDFILE_LIBS@ SENDFILE_LIBS = @SENDFILE_LIBS@
SET_MAKE = @SET_MAKE@ SET_MAKE = @SET_MAKE@
@@ -650,7 +660,7 @@ LDFLAGS_COMMON = -L$(abs_builddir)/../lib -L$(libdir)
LIBADD_COMMON = ../lib/libhio.la $(LIBM) LIBADD_COMMON = ../lib/libhio.la $(LIBM)
check_SCRIPTS = s-001.sh s-002.sh check_SCRIPTS = s-001.sh s-002.sh
EXTRA_DIST = $(check_SCRIPTS) tap.inc t-cgi.sh EXTRA_DIST = $(check_SCRIPTS) tap.inc t-cgi.sh
TESTPROGS = t-001 t-002 t-003 t-004 t-005 t-006 t-007 t-008 t-009 t-010 t-011 TESTPROGS = t-001 t-002 t-003 t-004 t-005 t-006 t-007 t-008 t-009 t-010 t-011 t-012
HELPERPROGS = fcgis httpecho httssvr slowclient HELPERPROGS = fcgis httpecho httssvr slowclient
t_001_SOURCES = t-001.c tap.h t_001_SOURCES = t-001.c tap.h
t_001_CPPFLAGS = $(CPPFLAGS_COMMON) t_001_CPPFLAGS = $(CPPFLAGS_COMMON)
@@ -707,6 +717,11 @@ t_011_CPPFLAGS = $(CPPFLAGS_COMMON)
t_011_CFLAGS = $(CFLAGS_COMMON) t_011_CFLAGS = $(CFLAGS_COMMON)
t_011_LDFLAGS = $(LDFLAGS_COMMON) t_011_LDFLAGS = $(LDFLAGS_COMMON)
t_011_LDADD = $(LIBADD_COMMON) t_011_LDADD = $(LIBADD_COMMON)
t_012_SOURCES = t-012.c tap.h
t_012_CPPFLAGS = $(CPPFLAGS_COMMON)
t_012_CFLAGS = $(CFLAGS_COMMON)
t_012_LDFLAGS = $(LDFLAGS_COMMON)
t_012_LDADD = $(LIBADD_COMMON)
fcgis_SOURCES = fcgis.c fcgis_SOURCES = fcgis.c
fcgis_CPPFLAGS = $(CPPFLAGS_COMMON) fcgis_CPPFLAGS = $(CPPFLAGS_COMMON)
fcgis_CFLAGS = $(CFLAGS_COMMON) fcgis_CFLAGS = $(CFLAGS_COMMON)
@@ -830,6 +845,10 @@ t-011$(EXEEXT): $(t_011_OBJECTS) $(t_011_DEPENDENCIES) $(EXTRA_t_011_DEPENDENCIE
@rm -f t-011$(EXEEXT) @rm -f t-011$(EXEEXT)
$(AM_V_CCLD)$(t_011_LINK) $(t_011_OBJECTS) $(t_011_LDADD) $(LIBS) $(AM_V_CCLD)$(t_011_LINK) $(t_011_OBJECTS) $(t_011_LDADD) $(LIBS)
t-012$(EXEEXT): $(t_012_OBJECTS) $(t_012_DEPENDENCIES) $(EXTRA_t_012_DEPENDENCIES)
@rm -f t-012$(EXEEXT)
$(AM_V_CCLD)$(t_012_LINK) $(t_012_OBJECTS) $(t_012_LDADD) $(LIBS)
mostlyclean-compile: mostlyclean-compile:
-rm -f *.$(OBJEXT) -rm -f *.$(OBJEXT)
@@ -851,6 +870,7 @@ distclean-compile:
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/t_009-t-009.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/t_009-t-009.Po@am__quote@ # am--include-marker
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/t_010-t-010.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/t_010-t-010.Po@am__quote@ # am--include-marker
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/t_011-t-011.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/t_011-t-011.Po@am__quote@ # am--include-marker
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/t_012-t-012.Po@am__quote@ # am--include-marker
$(am__depfiles_remade): $(am__depfiles_remade):
@$(MKDIR_P) $(@D) @$(MKDIR_P) $(@D)
@@ -1092,6 +1112,20 @@ t_011-t-011.obj: t-011.c
@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(t_011_CPPFLAGS) $(CPPFLAGS) $(t_011_CFLAGS) $(CFLAGS) -c -o t_011-t-011.obj `if test -f 't-011.c'; then $(CYGPATH_W) 't-011.c'; else $(CYGPATH_W) '$(srcdir)/t-011.c'; fi` @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(t_011_CPPFLAGS) $(CPPFLAGS) $(t_011_CFLAGS) $(CFLAGS) -c -o t_011-t-011.obj `if test -f 't-011.c'; then $(CYGPATH_W) 't-011.c'; else $(CYGPATH_W) '$(srcdir)/t-011.c'; fi`
t_012-t-012.o: t-012.c
@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(t_012_CPPFLAGS) $(CPPFLAGS) $(t_012_CFLAGS) $(CFLAGS) -MT t_012-t-012.o -MD -MP -MF $(DEPDIR)/t_012-t-012.Tpo -c -o t_012-t-012.o `test -f 't-012.c' || echo '$(srcdir)/'`t-012.c
@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/t_012-t-012.Tpo $(DEPDIR)/t_012-t-012.Po
@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='t-012.c' object='t_012-t-012.o' libtool=no @AMDEPBACKSLASH@
@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(t_012_CPPFLAGS) $(CPPFLAGS) $(t_012_CFLAGS) $(CFLAGS) -c -o t_012-t-012.o `test -f 't-012.c' || echo '$(srcdir)/'`t-012.c
t_012-t-012.obj: t-012.c
@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(t_012_CPPFLAGS) $(CPPFLAGS) $(t_012_CFLAGS) $(CFLAGS) -MT t_012-t-012.obj -MD -MP -MF $(DEPDIR)/t_012-t-012.Tpo -c -o t_012-t-012.obj `if test -f 't-012.c'; then $(CYGPATH_W) 't-012.c'; else $(CYGPATH_W) '$(srcdir)/t-012.c'; fi`
@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/t_012-t-012.Tpo $(DEPDIR)/t_012-t-012.Po
@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='t-012.c' object='t_012-t-012.obj' libtool=no @AMDEPBACKSLASH@
@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(t_012_CPPFLAGS) $(CPPFLAGS) $(t_012_CFLAGS) $(CFLAGS) -c -o t_012-t-012.obj `if test -f 't-012.c'; then $(CYGPATH_W) 't-012.c'; else $(CYGPATH_W) '$(srcdir)/t-012.c'; fi`
mostlyclean-libtool: mostlyclean-libtool:
-rm -f *.lo -rm -f *.lo
@@ -1393,6 +1427,13 @@ t-011.log: t-011$(EXEEXT)
--log-file $$b.log --trs-file $$b.trs \ --log-file $$b.log --trs-file $$b.trs \
$(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
"$$tst" $(AM_TESTS_FD_REDIRECT) "$$tst" $(AM_TESTS_FD_REDIRECT)
t-012.log: t-012$(EXEEXT)
@p='t-012$(EXEEXT)'; \
b='t-012'; \
$(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
--log-file $$b.log --trs-file $$b.trs \
$(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
"$$tst" $(AM_TESTS_FD_REDIRECT)
.sh.log: .sh.log:
@p='$<'; \ @p='$<'; \
$(am__set_b); \ $(am__set_b); \
@@ -1502,6 +1543,7 @@ distclean: distclean-am
-rm -f ./$(DEPDIR)/t_009-t-009.Po -rm -f ./$(DEPDIR)/t_009-t-009.Po
-rm -f ./$(DEPDIR)/t_010-t-010.Po -rm -f ./$(DEPDIR)/t_010-t-010.Po
-rm -f ./$(DEPDIR)/t_011-t-011.Po -rm -f ./$(DEPDIR)/t_011-t-011.Po
-rm -f ./$(DEPDIR)/t_012-t-012.Po
-rm -f Makefile -rm -f Makefile
distclean-am: clean-am distclean-compile distclean-generic \ distclean-am: clean-am distclean-compile distclean-generic \
distclean-tags distclean-tags
@@ -1562,6 +1604,7 @@ maintainer-clean: maintainer-clean-am
-rm -f ./$(DEPDIR)/t_009-t-009.Po -rm -f ./$(DEPDIR)/t_009-t-009.Po
-rm -f ./$(DEPDIR)/t_010-t-010.Po -rm -f ./$(DEPDIR)/t_010-t-010.Po
-rm -f ./$(DEPDIR)/t_011-t-011.Po -rm -f ./$(DEPDIR)/t_011-t-011.Po
-rm -f ./$(DEPDIR)/t_012-t-012.Po
-rm -f Makefile -rm -f Makefile
maintainer-clean-am: distclean-am maintainer-clean-generic maintainer-clean-am: distclean-am maintainer-clean-generic
+156
View File
@@ -133,3 +133,159 @@ static void test_wq_limit_zero_is_unlimited (void)
hio_dev_kill ((hio_dev_t*)dev); hio_dev_kill ((hio_dev_t*)dev);
close (peerfd); close (peerfd);
} }
/* ------------------------------------------------------------------ */
/* completion firing inside the greedy read loop.
*
* when on_read asks for another read, the loop fires the completions queued
* so far before calling on_read again - otherwise a write started from inside
* on_read would be reported after a later on_read, which is the reordering
* the comment in handle_event() warns about. the firing is per device, so an
* unrelated connection's callbacks are not dragged into this device's pass. */
static void test_completion_fires_within_read_loop (void)
{
tdev_t* dev;
int peerfd;
hio_uint8_t chunk[30];
obs_reset ();
g_wr_chunk = 0;
g_wr_calls_left = 100; /* every write completes at once */
g_rd_chunk = 10; /* so 30 octets take three read iterations */
g_write_on_read = 1;
dev = make_tdev(&peerfd);
if (!dev) { skip ("device creation failed", 3); return; }
HIO_MEMSET (chunk, 'x', HIO_SIZEOF(chunk));
if (write(peerfd, chunk, HIO_SIZEOF(chunk)) != (ssize_t)HIO_SIZEOF(chunk))
{
skip ("peer write failed", 3);
hio_dev_kill ((hio_dev_t*)dev);
close (peerfd);
return;
}
pump ();
/* iteration 1 reads and writes; iteration 2 fires that write's completion
* before reading again. hence R, then W and R alternating. */
OK (g_seq_n >= 3, "the greedy read loop ran more than once");
OK (g_seq[0] == 'R', "no completion is fired before the first read");
OK (strncmp(g_seq, "RWRWR", 5) == 0,
"each iteration's write completion is reported before the next on_read");
g_write_on_read = 0;
hio_dev_kill ((hio_dev_t*)dev);
close (peerfd);
}
/* ------------------------------------------------------------------ */
/* the shared read buffer's configurable size.
*
* every device reads through one buffer owned by the loop. its size decides
* how much a single read can take, so it is the ceiling on read size for a
* bulk transfer - and it is memory held per loop, which matters when a
* process runs one loop per thread. */
static void test_read_buffer_size_option (void)
{
hio_oow_t capa = 0, want;
int rc;
obs_reset ();
OK (hio_getoption(g_hio, HIO_READ_BUFFER_SIZE, &capa) == 0 && capa == HIO_DFL_READ_BUFFER_SIZE,
"the read buffer starts at HIO_DFL_READ_BUFFER_SIZE");
want = HIO_MIN_READ_BUFFER_SIZE - 1;
rc = hio_setoption(g_hio, HIO_READ_BUFFER_SIZE, &want);
OK (rc <= -1 && hio_geterrnum(g_hio) == HIO_EINVAL,
"a size below HIO_MIN_READ_BUFFER_SIZE is refused with HIO_EINVAL");
OK (hio_getoption(g_hio, HIO_READ_BUFFER_SIZE, &capa) == 0 && capa == HIO_DFL_READ_BUFFER_SIZE,
"a refused set leaves the existing buffer alone");
want = HIO_DFL_READ_BUFFER_SIZE * 4;
OK (hio_setoption(g_hio, HIO_READ_BUFFER_SIZE, &want) == 0 &&
hio_getoption(g_hio, HIO_READ_BUFFER_SIZE, &capa) == 0 && capa == want,
"a larger size is accepted and reads back");
want = HIO_DFL_READ_BUFFER_SIZE;
OK (hio_setoption(g_hio, HIO_READ_BUFFER_SIZE, &want) == 0, "and it can be set back");
}
static void test_read_buffer_size_is_honoured (void)
{
/* the size has to reach the read path, not merely be stored. the stub
* device records the length the core offers it. */
tdev_t* dev;
int peerfd;
hio_oow_t want, restore = HIO_DFL_READ_BUFFER_SIZE;
hio_uint8_t buf[4096];
obs_reset ();
g_wr_calls_left = 100;
want = HIO_MIN_READ_BUFFER_SIZE; /* the smallest the core allows */
if (hio_setoption(g_hio, HIO_READ_BUFFER_SIZE, &want) <= -1) { skip ("cannot set the buffer size", 2); return; }
dev = make_tdev(&peerfd);
if (!dev) { skip ("device creation failed", 2); hio_setoption(g_hio, HIO_READ_BUFFER_SIZE, &restore); return; }
HIO_MEMSET (buf, 'z', HIO_SIZEOF(buf));
if (write(peerfd, buf, HIO_SIZEOF(buf)) != (ssize_t)HIO_SIZEOF(buf))
{
skip ("peer write failed", 2);
hio_dev_kill ((hio_dev_t*)dev);
close (peerfd);
hio_setoption (g_hio, HIO_READ_BUFFER_SIZE, &restore);
return;
}
pump ();
OK (g_rd_calls > 0, "the device was read from");
OK (g_rd_offered == (hio_iolen_t)HIO_MIN_READ_BUFFER_SIZE,
"the core offers the read method exactly the configured buffer size");
hio_dev_kill ((hio_dev_t*)dev);
close (peerfd);
hio_setoption (g_hio, HIO_READ_BUFFER_SIZE, &restore);
}
static void test_read_buffer_resize_refused_in_callback (void)
{
/* on_read receives a pointer into the buffer. resizing it there would
* leave that pointer dangling, so the attempt has to fail. */
tdev_t* dev;
int peerfd;
hio_uint8_t buf[64];
obs_reset ();
g_wr_calls_left = 100;
g_setopt_from_on_read = 1;
dev = make_tdev(&peerfd);
if (!dev) { skip ("device creation failed", 2); return; }
HIO_MEMSET (buf, 'q', HIO_SIZEOF(buf));
if (write(peerfd, buf, HIO_SIZEOF(buf)) != (ssize_t)HIO_SIZEOF(buf))
{
skip ("peer write failed", 2);
hio_dev_kill ((hio_dev_t*)dev);
close (peerfd);
return;
}
pump ();
OK (g_setopt_rc <= -1 && g_setopt_errnum == HIO_EBUSY,
"resizing the read buffer from inside on_read fails with HIO_EBUSY");
OK (hio_getoption(g_hio, HIO_READ_BUFFER_SIZE, &(hio_oow_t){0}) == 0,
"and the option is still readable afterwards");
hio_dev_kill ((hio_dev_t*)dev);
close (peerfd);
}
+57 -2
View File
@@ -34,12 +34,41 @@ static hio_iolen_t g_cw_len[MAX_CW];
static void* g_cw_ctx[MAX_CW]; static void* g_cw_ctx[MAX_CW];
static int g_cw_n = 0; static int g_cw_n = 0;
/* the greedy-read interleaving cases below. g_rd_chunk caps how much one read
* delivers; g_write_on_read makes on_read issue a write and ask for another
* read, which is the only shape that reaches the completion firing inside the
* read loop. g_seq records 'R' per on_read and 'W' per on_write. */
static hio_iolen_t g_rd_chunk = 0;
static hio_iolen_t g_rd_offered = 0; /* the buffer size the core last offered a read */
static int g_rd_calls = 0;
static int g_setopt_from_on_read = 0;
static int g_setopt_rc = 0;
static int g_setopt_errnum = 0;
static int g_write_on_read = 0;
static hio_bch_t g_seq[64];
static int g_seq_n = 0;
static void seq_put (hio_bch_t c)
{
if (g_seq_n < (int)HIO_COUNTOF(g_seq) - 1) g_seq[g_seq_n++] = c;
g_seq[g_seq_n] = '\0';
}
static hio_uint8_t g_pattern[PATLEN]; static hio_uint8_t g_pattern[PATLEN];
static void obs_reset (void) static void obs_reset (void)
{ {
g_wr_calls = 0; g_wr_calls = 0;
g_cw_n = 0; g_cw_n = 0;
g_rd_chunk = 0;
g_rd_offered = 0;
g_rd_calls = 0;
g_setopt_from_on_read = 0;
g_setopt_rc = 0;
g_setopt_errnum = 0;
g_write_on_read = 0;
g_seq_n = 0;
g_seq[0] = '\0';
} }
/* ------------------------------------------------------------------ */ /* ------------------------------------------------------------------ */
@@ -73,7 +102,11 @@ static hio_syshnd_t tdev_getsyshnd (hio_dev_t* dev)
static int tdev_read (hio_dev_t* dev, void* buf, hio_iolen_t* len, hio_devaddr_t* srcaddr) static int tdev_read (hio_dev_t* dev, void* buf, hio_iolen_t* len, hio_devaddr_t* srcaddr)
{ {
ssize_t n = recv(((tdev_t*)dev)->fd, buf, *len, 0); ssize_t n;
g_rd_offered = *len; /* what the core is willing to take in one go */
g_rd_calls++;
if (g_rd_chunk > 0 && *len > g_rd_chunk) *len = g_rd_chunk; /* force several iterations */
n = recv(((tdev_t*)dev)->fd, buf, *len, 0);
if (n <= -1) if (n <= -1)
{ {
if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) return 0; if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) return 0;
@@ -206,11 +239,29 @@ static hio_dev_mth_t tdev_mth =
static int tdev_on_read (hio_dev_t* dev, const void* data, hio_iolen_t len, const hio_devaddr_t* srcaddr) static int tdev_on_read (hio_dev_t* dev, const void* data, hio_iolen_t len, const hio_devaddr_t* srcaddr)
{ {
return 0; if (g_setopt_from_on_read)
{
/* the data pointer we were just handed points into the buffer being
* resized, so this must be refused rather than honoured */
hio_oow_t want = HIO_DFL_READ_BUFFER_SIZE * 2;
g_setopt_from_on_read = 0;
g_setopt_rc = hio_setoption(dev->hio, HIO_READ_BUFFER_SIZE, &want);
g_setopt_errnum = hio_geterrnum(dev->hio);
}
if (!g_write_on_read) return 0;
seq_put ('R');
if (len <= 0) return 0;
/* completes straight away, so a completion is queued for the next
* iteration of the read loop to fire */
if (hio_dev_write(dev, g_pattern, 1, HIO_NULL, HIO_NULL) <= -1) return -1;
return 1; /* be greedy - keep reading in this same pass */
} }
static int tdev_on_write (hio_dev_t* dev, hio_iolen_t wrlen, void* wrctx, const hio_devaddr_t* dstaddr) static int tdev_on_write (hio_dev_t* dev, hio_iolen_t wrlen, void* wrctx, const hio_devaddr_t* dstaddr)
{ {
if (g_write_on_read) seq_put ('W');
if (g_cw_n < MAX_CW) if (g_cw_n < MAX_CW)
{ {
g_cw_len[g_cw_n] = wrlen; g_cw_len[g_cw_n] = wrlen;
@@ -742,6 +793,10 @@ int main (void)
test_wq_size_released_on_kill (); test_wq_size_released_on_kill ();
test_wq_limit (); test_wq_limit ();
test_wq_limit_zero_is_unlimited (); test_wq_limit_zero_is_unlimited ();
test_completion_fires_within_read_loop ();
test_read_buffer_size_option ();
test_read_buffer_size_is_honoured ();
test_read_buffer_resize_refused_in_callback ();
hio_close (g_hio); hio_close (g_hio);
return exit_status(); return exit_status();
+169
View File
@@ -11,6 +11,10 @@
* is. the race is made deterministic here: the peer is accepted and written * is. the race is made deterministic here: the peer is accepted and written
* to before the loop is ever run, so the first wakeup is guaranteed to carry * to before the loop is ever run, so the first wakeup is guaranteed to carry
* both bits. * both bits.
*
* the second group of cases covers what a socket device asks of the loop's
* shared read buffer, which is the other place the socket layer and the core
* have to agree on something.
*/ */
#include <hio-sck.h> #include <hio-sck.h>
@@ -30,6 +34,8 @@
static hio_t* g_hio = HIO_NULL; static hio_t* g_hio = HIO_NULL;
static int g_connected; /* on_connect saw HIO_DEV_SCK_CONNECTED */ static int g_connected; /* on_connect saw HIO_DEV_SCK_CONNECTED */
static int g_disconnected; static int g_disconnected;
static int g_accepted; /* on_connect saw an ACCEPTED device */
static hio_dev_sck_t* g_qx; /* cleared by on_disconnect - the device is freed with it */
static int g_read_len; /* octets delivered to on_read */ static int g_read_len; /* octets delivered to on_read */
static hio_bch_t g_read_buf[128]; static hio_bch_t g_read_buf[128];
static int g_done; static int g_done;
@@ -67,6 +73,7 @@ static int cli_on_write (hio_dev_sck_t* sck, hio_iolen_t wrlen, void* wrctx, con
static void cli_on_connect (hio_dev_sck_t* sck) static void cli_on_connect (hio_dev_sck_t* sck)
{ {
if (sck->state & HIO_DEV_SCK_CONNECTED) g_connected = 1; if (sck->state & HIO_DEV_SCK_CONNECTED) g_connected = 1;
if (sck->state & HIO_DEV_SCK_ACCEPTED) g_accepted = 1;
} }
static void cli_on_disconnect (hio_dev_sck_t* sck) static void cli_on_disconnect (hio_dev_sck_t* sck)
@@ -74,6 +81,7 @@ static void cli_on_disconnect (hio_dev_sck_t* sck)
/* reached when the device is killed - which is what the defect did */ /* reached when the device is killed - which is what the defect did */
g_disconnected = 1; g_disconnected = 1;
g_done = 1; g_done = 1;
if (sck == g_qx) g_qx = HIO_NULL;
} }
/* a plain listener, so nothing about the peer depends on the code under test */ /* a plain listener, so nothing about the peer depends on the code under test */
@@ -176,6 +184,165 @@ static void test_connect_with_data_already_waiting (void)
close (lfd); close (lfd);
} }
/* the read buffer is shared by every device on the loop, so a device that can
* only work with a whole message at a time has to say so - what will not fit
* in one read is discarded by the kernel with nothing reported. a stream
* device has no such need and asks for nothing. */
static void test_device_read_buffer_requirement (void)
{
hio_dev_sck_make_t mi;
hio_dev_sck_t* d;
hio_oow_t small = HIO_MIN_READ_BUFFER_SIZE, dfl = HIO_DFL_READ_BUFFER_SIZE;
HIO_MEMSET (&mi, 0, HIO_SIZEOF(mi));
mi.on_read = cli_on_read;
mi.on_write = cli_on_write;
mi.on_connect = cli_on_connect;
mi.on_disconnect = cli_on_disconnect;
mi.type = HIO_DEV_SCK_TCP4;
d = hio_dev_sck_make(g_hio, 0, &mi);
OK (d && d->dev_rdmin == 0, "a stream socket asks for no particular read buffer size");
if (d) hio_dev_sck_kill (d);
mi.type = HIO_DEV_SCK_UDP4;
d = hio_dev_sck_make(g_hio, 0, &mi);
OK (d && d->dev_rdmin == HIO_DGRAM_READ_BUFFER_SIZE,
"a datagram socket asks for enough to hold the largest datagram");
if (d) hio_dev_sck_kill (d);
/* the qx channel is message-oriented too, but its message is a fixed
* struct - it must not be made to demand a datagram's worth */
mi.type = HIO_DEV_SCK_QX;
d = hio_dev_sck_make(g_hio, 0, &mi);
OK (d && d->dev_rdmin > 0 && d->dev_rdmin < HIO_MIN_READ_BUFFER_SIZE,
"the qx channel asks only for its own message size");
if (d) hio_dev_sck_kill (d);
if (hio_setoption(g_hio, HIO_READ_BUFFER_SIZE, &small) <= -1) { skip ("cannot shrink the read buffer", 3); return; }
mi.type = HIO_DEV_SCK_UDP4;
d = hio_dev_sck_make(g_hio, 0, &mi);
OK (!d && hio_geterrnum(g_hio) == HIO_ENOCAPA,
"a datagram socket refuses to start when the read buffer is too small");
if (d) hio_dev_sck_kill (d);
/* ...while the types that do not need it are unaffected, which is what
* makes the option usable at all - an http service uses a qx channel */
mi.type = HIO_DEV_SCK_TCP4;
d = hio_dev_sck_make(g_hio, 0, &mi);
OK (d != HIO_NULL, "a stream socket still starts on the smaller buffer");
if (d) hio_dev_sck_kill (d);
mi.type = HIO_DEV_SCK_QX;
d = hio_dev_sck_make(g_hio, 0, &mi);
OK (d != HIO_NULL, "and so does the qx channel");
if (d) hio_dev_sck_kill (d);
hio_setoption (g_hio, HIO_READ_BUFFER_SIZE, &dfl);
/* and a live device's requirement cannot be pulled out from under it */
mi.type = HIO_DEV_SCK_UDP4;
d = hio_dev_sck_make(g_hio, 0, &mi);
if (d)
{
OK (hio_setoption(g_hio, HIO_READ_BUFFER_SIZE, &small) <= -1 && hio_geterrnum(g_hio) == HIO_EPERM,
"shrinking below a running device's requirement is refused");
hio_dev_sck_kill (d);
}
else skip ("cannot make a udp device", 1);
hio_setoption (g_hio, HIO_READ_BUFFER_SIZE, &dfl);
}
/* the accepted-client path takes its socket type from elsewhere - for the qx
* channel, from a message another thread sent - so it cannot rely on a local
* listener of that type having already vouched for the read buffer. this hands
* the qx channel a datagram socket while the buffer is too small for one.
*
* it is the only way to reach that path today, since every listenable type in
* sck_type_map happens to be a stream type. that is what makes the check worth
* having rather than obvious. */
static int handover_via_qx (hio_dev_sck_t* qx, hio_dev_sck_type_t type)
{
hio_dev_sck_qxmsg_t msg;
int fd;
fd = socket(AF_INET, SOCK_DGRAM, 0);
if (fd <= -1) return -1;
HIO_MEMSET (&msg, 0, HIO_SIZEOF(msg));
msg.cmd = HIO_DEV_SCK_QXMSG_NEWCONN;
msg.scktype = type;
msg.syshnd = fd;
if (hio_bcstrtoskad(g_hio, "127.0.0.1:1", &msg.remoteaddr) <= -1) { close(fd); return -1; }
/* the qx device reads from its own handle; the side channel is the end a
* producer writes to */
if (write(qx->side_chan, &msg, HIO_SIZEOF(msg)) != (ssize_t)HIO_SIZEOF(msg)) { close(fd); return -1; }
return 0;
}
/* returns 1 if a device was accepted, 0 if not, -1 if the setup failed */
static int try_qx_handover (hio_oow_t bufsz, hio_dev_sck_type_t type)
{
hio_dev_sck_make_t mi;
hio_dev_sck_t* qx;
int i, r;
g_qx = HIO_NULL;
if (hio_setoption(g_hio, HIO_READ_BUFFER_SIZE, &bufsz) <= -1) return -1;
HIO_MEMSET (&mi, 0, HIO_SIZEOF(mi));
mi.type = HIO_DEV_SCK_QX;
mi.on_read = cli_on_read;
mi.on_write = cli_on_write;
mi.on_connect = cli_on_connect;
mi.on_disconnect = cli_on_disconnect;
/* a fresh channel each time - a refused handover halts the one that
* carried it, so it cannot be reused for the next attempt */
qx = hio_dev_sck_make(g_hio, 0, &mi);
if (!qx) return -1;
g_qx = qx;
g_accepted = 0;
if (handover_via_qx(qx, type) <= -1) r = -1;
else
{
for (i = 0; i < 20 && !g_accepted; i++) hio_exec(g_hio);
r = g_accepted;
}
/* a refused handover halts this channel, and the loop above will already
* have reaped it - so the pointer cannot be dereferenced to find out.
* on_disconnect clears g_qx, which is the only safe way to ask. */
if (g_qx) { hio_dev_sck_kill (g_qx); g_qx = HIO_NULL; }
hio_exec (g_hio);
return r;
}
static void test_accepted_device_read_buffer_requirement (void)
{
hio_oow_t dfl = HIO_DFL_READ_BUFFER_SIZE;
int refused, allowed;
refused = try_qx_handover(HIO_MIN_READ_BUFFER_SIZE, HIO_DEV_SCK_UDP4);
allowed = try_qx_handover(HIO_DFL_READ_BUFFER_SIZE, HIO_DEV_SCK_UDP4);
if (refused <= -1 || allowed <= -1) skip ("qx handover setup failed", 2);
else
{
OK (refused == 0, "a datagram socket handed over the qx channel is refused when the buffer is too small");
/* the control: the same handover with room for it must succeed, or the
* case above proves nothing about the buffer check */
OK (allowed == 1, "and the same handover succeeds once the buffer is large enough");
}
hio_setoption (g_hio, HIO_READ_BUFFER_SIZE, &dfl);
}
int main (void) int main (void)
{ {
hio_errinf_t errinf; hio_errinf_t errinf;
@@ -191,6 +358,8 @@ int main (void)
quiet_logging (g_hio); quiet_logging (g_hio);
test_connect_with_data_already_waiting (); test_connect_with_data_already_waiting ();
test_device_read_buffer_requirement ();
test_accepted_device_read_buffer_requirement ();
hio_close (g_hio); hio_close (g_hio);
return exit_status(); return exit_status();
+473
View File
@@ -0,0 +1,473 @@
/*
* sctp transport tests.
*
* these cover the one-to-one types, HIO_DEV_SCK_SCTP4 and its v6 twin. they
* are stream devices like tcp, but their reads and writes go through
* sendmsg()/recvmsg() so that the per-message ancillary data is reachable -
* which is the only reason to prefer sctp here in the first place.
*
* what is checked: an association forms and carries data; a stream number and
* a ppid set on the destination arrive at the far end; notifications are
* delivered to on_notification() and never spliced into the data stream; and a
* zero-length write still means "close the writing end" rather than "send an
* empty message".
*
* the second group covers the one-to-many (SOCK_SEQPACKET) types. those are
* not accept-based: one device carries every association, and a message is
* attributed by the source address and the association id it arrives with.
*
* the whole file skips where the system has no sctp - the library reports that
* as HIO_ENOIMPL from hio_dev_sck_make(), which is also worth asserting, since
* it is how a caller is meant to find out.
*/
#include <hio-sck.h>
#include <hio-prv.h>
#include "tap.h"
#include <string.h>
/* deliberately beyond what a system gives an association by default - linux
* negotiates ten outbound streams unless asked otherwise. writing on stream
* 40 therefore only works if SCTP_INITMSG really was set, which is the point:
* with the old call, which handed a struct sctp_initmsg to SCTP_EVENTS, the
* request silently did nothing and a stream this high is rejected. */
#define OSTREAMS 64
#define TEST_STREAM 40
#define TEST_PPID 0xC0FFEEu
#define PAYLOAD "sctp-payload"
static hio_t* g_hio = HIO_NULL;
static hio_dev_sck_t* g_srv = HIO_NULL;
static hio_dev_sck_t* g_acc = HIO_NULL;
static hio_dev_sck_t* g_cli = HIO_NULL;
static int g_connected, g_accepted, g_timeout;
static int g_got_data; /* payload arrived */
static int g_notifications; /* on_notification calls */
static int g_eof; /* the accepted side saw a zero-length read */
static int g_reads;
static hio_bch_t g_rbuf[256];
static int g_rlen;
static hio_uint16_t g_rchan; /* stream number the receiver saw */
static hio_uint32_t g_rppid;
static hio_tmridx_t g_deadline = HIO_TMRIDX_INVALID;
static void quiet_logging (hio_t* hio)
{
hio_bitmask_t mask = HIO_LOG_ERROR | HIO_LOG_FATAL | HIO_LOG_ALL_TYPES;
hio_setoption (hio, HIO_LOG_MASK, &mask);
}
static void on_deadline (hio_t* hio, const hio_ntime_t* now, hio_tmrjob_t* job)
{
g_timeout = 1;
}
/* ------------------------------------------------------------------ */
static int srv_on_read (hio_dev_sck_t* sck, const void* data, hio_iolen_t dlen, const hio_skad_t* srcaddr)
{
if (dlen <= 0)
{
/* the client shut its writing end - EOF, as for any stream device */
g_eof = 1;
return 0;
}
g_reads++;
g_got_data = 1;
if (dlen > (hio_iolen_t)HIO_SIZEOF(g_rbuf) - 1) dlen = HIO_SIZEOF(g_rbuf) - 1;
HIO_MEMCPY (g_rbuf, data, dlen);
g_rbuf[dlen] = '\0';
g_rlen = (int)dlen;
/* the ancillary data rides on the source address */
g_rchan = hio_skad_get_chan(srcaddr? srcaddr: &sck->remoteaddr);
g_rppid = hio_skad_get_ppid(srcaddr? srcaddr: &sck->remoteaddr);
return 0;
}
static int on_write (hio_dev_sck_t* sck, hio_iolen_t wrlen, void* wrctx, const hio_skad_t* dstaddr)
{
return 0;
}
static void on_notification (hio_dev_sck_t* sck, const void* data, hio_iolen_t dlen)
{
/* an association or path event. it must never reach on_read(). */
g_notifications++;
}
static void srv_on_connect (hio_dev_sck_t* sck)
{
if (sck->state & HIO_DEV_SCK_ACCEPTED)
{
g_accepted = 1;
g_acc = sck;
}
}
static void srv_on_disconnect (hio_dev_sck_t* sck)
{
if (sck == g_acc) g_acc = HIO_NULL;
else if (sck == g_srv) g_srv = HIO_NULL;
}
static int cli_on_read (hio_dev_sck_t* sck, const void* data, hio_iolen_t dlen, const hio_skad_t* srcaddr)
{
return 0;
}
static void cli_on_connect (hio_dev_sck_t* sck)
{
if (sck->state & HIO_DEV_SCK_CONNECTED) g_connected = 1;
}
static void cli_on_disconnect (hio_dev_sck_t* sck)
{
if (sck == g_cli) g_cli = HIO_NULL;
}
/* ------------------------------------------------------------------ */
static void fill_make (hio_dev_sck_make_t* mi, int server)
{
HIO_MEMSET (mi, 0, HIO_SIZEOF(*mi));
mi->type = HIO_DEV_SCK_SCTP4;
mi->on_write = on_write;
mi->on_read = server? srv_on_read: cli_on_read;
mi->on_connect = server? srv_on_connect: cli_on_connect;
mi->on_disconnect = server? srv_on_disconnect: cli_on_disconnect;
mi->on_notification = on_notification;
/* ask for more streams than the system default. the negotiated count is
* min(our sinit_num_ostreams, the peer's sinit_max_instreams), so both
* ends have to ask. */
mi->sctp_ostreams = OSTREAMS;
mi->sctp_instreams = OSTREAMS;
}
/* run the loop until the flag is set, or five seconds pass. the timeout is
* cleared on entry: a phase that legitimately waits must not be starved by an
* earlier phase having already used the budget up. */
static void run_until (int* flag)
{
hio_tmrjob_t j;
g_timeout = 0;
HIO_MEMSET (&j, 0, HIO_SIZEOF(j));
hio_gettime (g_hio, &j.when);
j.when.sec += 5;
j.handler = on_deadline;
j.idxptr = &g_deadline;
g_deadline = hio_instmrjob(g_hio, &j);
while (!*flag && !g_timeout)
{
if (hio_exec(g_hio) <= -1) break;
}
if (g_deadline != HIO_TMRIDX_INVALID) { hio_deltmrjob (g_hio, g_deadline); g_deadline = HIO_TMRIDX_INVALID; }
}
static void teardown (void)
{
if (g_cli) hio_dev_sck_halt (g_cli);
if (g_acc) hio_dev_sck_halt (g_acc);
if (g_srv) hio_dev_sck_halt (g_srv);
hio_exec (g_hio);
hio_exec (g_hio);
g_cli = g_acc = g_srv = HIO_NULL;
}
/* ------------------------------------------------------------------ */
static void test_association_and_ancillary (void)
{
hio_dev_sck_make_t mi;
hio_dev_sck_bind_t bi;
hio_dev_sck_listen_t li;
hio_dev_sck_connect_t ci;
hio_skad_t peer;
g_connected = g_accepted = g_timeout = g_got_data = 0;
g_notifications = g_eof = g_reads = g_rlen = 0;
g_rchan = 0; g_rppid = 0;
fill_make (&mi, 1);
g_srv = hio_dev_sck_make(g_hio, 0, &mi);
if (!g_srv) { skip ("cannot make an sctp server device", 8); return; }
HIO_MEMSET (&bi, 0, HIO_SIZEOF(bi));
if (hio_bcstrtoskad(g_hio, "127.0.0.1:0", &bi.localaddr) <= -1) { skip ("bad address", 8); teardown(); return; }
bi.options = HIO_DEV_SCK_BIND_REUSEADDR;
OK (hio_dev_sck_bind(g_srv, &bi) == 0, "an sctp socket binds");
HIO_MEMSET (&li, 0, HIO_SIZEOF(li));
li.backlogs = 4;
HIO_INIT_NTIME (&li.accept_tmout, -1, 0);
OK (hio_dev_sck_listen(g_srv, &li) == 0, "and listens");
if (hio_dev_sck_getsockaddr(g_srv, &peer) <= -1) { skip ("cannot read the bound address", 6); teardown(); return; }
fill_make (&mi, 0);
g_cli = hio_dev_sck_make(g_hio, 0, &mi);
if (!g_cli) { skip ("cannot make an sctp client device", 6); teardown(); return; }
HIO_MEMSET (&ci, 0, HIO_SIZEOF(ci));
ci.remoteaddr = peer;
HIO_INIT_NTIME (&ci.connect_tmout, 5, 0);
if (hio_dev_sck_connect(g_cli, &ci) <= -1) { skip ("connect failed to start", 6); teardown(); return; }
run_until (&g_accepted);
OK (g_connected, "the client reaches the CONNECTED state");
OK (g_accepted, "the server is handed an ACCEPTED association");
if (!g_accepted) { skip ("no association", 4); teardown(); return; }
/* write on a specific stream with a specific ppid. both ride on the
* destination address rather than on a separate argument, which is why
* hio_skad_t carries an extra area at all. */
{
hio_skad_t dst = peer;
hio_skad_set_chan (&dst, TEST_STREAM);
hio_skad_set_ppid (&dst, TEST_PPID);
/* [NOTE] a failure here is a failure, not a missing prerequisite -
* skipping would hide exactly what this case exists to catch. writing
* on a stream above the system default is rejected outright unless
* SCTP_INITMSG asked for the streams. */
if (hio_dev_sck_write(g_cli, PAYLOAD, HIO_SIZEOF(PAYLOAD) - 1, HIO_NULL, &dst) <= -1)
{
FAIL ("a write on a requested stream is accepted");
FAIL ("the stream number set on the destination arrives at the far end");
FAIL ("and so does the ppid");
FAIL ("a zero-length write closes the writing end rather than sending an empty message");
teardown ();
return;
}
}
run_until (&g_got_data);
OK (g_got_data && g_rlen == (int)HIO_SIZEOF(PAYLOAD) - 1 &&
HIO_MEMCMP(g_rbuf, PAYLOAD, HIO_SIZEOF(PAYLOAD) - 1) == 0,
"the association carries data intact");
OK (g_rchan == TEST_STREAM, "the stream number set on the destination arrives at the far end");
OK (g_rppid == TEST_PPID, "and so does the ppid");
/* a zero-length write is the shutdown indicator for a stream device. the
* seqpacket write method would have put an empty message on the wire
* instead, which the peer would see as data rather than as EOF. */
if (hio_dev_sck_write(g_cli, HIO_NULL, 0, HIO_NULL, HIO_NULL) <= -1) FAIL ("the zero-length write is accepted");
else
{
run_until (&g_eof);
OK (g_eof, "a zero-length write closes the writing end rather than sending an empty message");
}
teardown ();
}
static void test_notifications_are_not_data (void)
{
/* the events subscribed at socket setup mean an association coming up
* produces a notification. it must be reported through on_notification
* and must not appear as payload - the reads seen must be exactly the
* ones the test sent. */
OK (g_notifications > 0, "association setup produces at least one notification");
OK (g_reads == 1, "and no notification is delivered to on_read as data");
}
/* ------------------------------------------------------------------ */
/* one-to-many */
static hio_dev_sck_t* g_sp_srv = HIO_NULL;
static hio_dev_sck_t* g_sp_cli = HIO_NULL;
static int g_sp_got, g_sp_reads;
static hio_int32_t g_sp_assoc;
static hio_uint16_t g_sp_chan;
static int g_sp_len;
static hio_bch_t g_sp_buf[512];
static int sp_srv_on_read (hio_dev_sck_t* sck, const void* data, hio_iolen_t dlen, const hio_skad_t* srcaddr)
{
if (dlen <= 0) return 0;
g_sp_reads++;
if (dlen > (hio_iolen_t)HIO_SIZEOF(g_sp_buf) - 1) dlen = HIO_SIZEOF(g_sp_buf) - 1;
HIO_MEMCPY (g_sp_buf, data, dlen);
g_sp_buf[dlen] = '\0';
g_sp_len = (int)dlen;
g_sp_chan = hio_skad_get_chan(srcaddr);
g_sp_assoc = hio_skad_get_assoc(srcaddr);
g_sp_got = 1;
return 0;
}
static int sp_cli_on_read (hio_dev_sck_t* sck, const void* data, hio_iolen_t dlen, const hio_skad_t* srcaddr)
{
return 0;
}
static void sp_on_connect (hio_dev_sck_t* sck) { }
static void sp_on_disconnect (hio_dev_sck_t* sck)
{
if (sck == g_sp_srv) g_sp_srv = HIO_NULL;
else if (sck == g_sp_cli) g_sp_cli = HIO_NULL;
}
static void sp_fill_make (hio_dev_sck_make_t* mi, int server)
{
HIO_MEMSET (mi, 0, HIO_SIZEOF(*mi));
mi->type = HIO_DEV_SCK_SCTP4_SP;
mi->on_write = on_write;
mi->on_read = server? sp_srv_on_read: sp_cli_on_read;
mi->on_connect = sp_on_connect;
mi->on_disconnect = sp_on_disconnect;
mi->on_notification = on_notification;
mi->sctp_ostreams = OSTREAMS;
mi->sctp_instreams = OSTREAMS;
}
static void sp_teardown (void)
{
if (g_sp_cli) hio_dev_sck_halt (g_sp_cli);
if (g_sp_srv) hio_dev_sck_halt (g_sp_srv);
hio_exec (g_hio);
hio_exec (g_hio);
g_sp_cli = g_sp_srv = HIO_NULL;
}
static void test_one_to_many (void)
{
hio_dev_sck_make_t mi;
hio_dev_sck_bind_t bi;
hio_dev_sck_listen_t li;
hio_skad_t peer, dst;
hio_oow_t bigsz;
hio_uint8_t* big;
g_sp_got = g_sp_reads = g_sp_len = 0;
g_sp_assoc = 0; g_sp_chan = 0;
g_notifications = 0;
sp_fill_make (&mi, 1);
g_sp_srv = hio_dev_sck_make(g_hio, 0, &mi);
if (!g_sp_srv) { skip ("cannot make a one-to-many device", 6); return; }
/* a message-oriented device asks for a read buffer wide enough for the
* largest datagram, since a short read would truncate */
OK (g_sp_srv->dev_rdmin == HIO_DGRAM_READ_BUFFER_SIZE,
"a one-to-many socket requires a whole-message read buffer");
HIO_MEMSET (&bi, 0, HIO_SIZEOF(bi));
if (hio_bcstrtoskad(g_hio, "127.0.0.1:0", &bi.localaddr) <= -1) { skip ("bad address", 5); sp_teardown(); return; }
bi.options = HIO_DEV_SCK_BIND_REUSEADDR;
if (hio_dev_sck_bind(g_sp_srv, &bi) <= -1) { skip ("bind failed", 5); sp_teardown(); return; }
/* listen() is called, but nothing is ever accepted - associations are not
* devices in this model */
HIO_MEMSET (&li, 0, HIO_SIZEOF(li));
li.backlogs = 4;
HIO_INIT_NTIME (&li.accept_tmout, -1, 0);
OK (hio_dev_sck_listen(g_sp_srv, &li) == 0, "and it listens without accepting");
if (hio_dev_sck_getsockaddr(g_sp_srv, &peer) <= -1) { skip ("cannot read the bound address", 4); sp_teardown(); return; }
sp_fill_make (&mi, 0);
g_sp_cli = hio_dev_sck_make(g_hio, 0, &mi);
if (!g_sp_cli) { skip ("cannot make a one-to-many client", 4); sp_teardown(); return; }
/* no connect: sending to an address forms the association implicitly */
dst = peer;
hio_skad_set_chan (&dst, TEST_STREAM);
if (hio_dev_sck_write(g_sp_cli, PAYLOAD, HIO_SIZEOF(PAYLOAD) - 1, HIO_NULL, &dst) <= -1)
{
FAIL ("a write forms the association implicitly");
FAIL ("the stream number arrives with the message");
FAIL ("the association id arrives with the message");
FAIL ("an oversized message is dropped whole rather than split");
sp_teardown ();
return;
}
run_until (&g_sp_got);
OK (g_sp_got && g_sp_len == (int)HIO_SIZEOF(PAYLOAD) - 1 &&
HIO_MEMCMP(g_sp_buf, PAYLOAD, HIO_SIZEOF(PAYLOAD) - 1) == 0,
"a write forms the association implicitly and the message arrives");
OK (g_sp_chan == TEST_STREAM, "the stream number arrives with the message");
OK (g_sp_assoc != 0, "the association id arrives with the message");
/* now a message larger than the read buffer. without the MSG_EOR check
* the receiver would see it as two separate messages; with it, the whole
* message is dropped and nothing bogus is delivered. */
bigsz = HIO_DGRAM_READ_BUFFER_SIZE * 2;
big = (hio_uint8_t*)hio_allocmem(g_hio, bigsz);
if (!big) skip ("out of memory", 1);
else
{
int reads_before = g_sp_reads;
HIO_MEMSET (big, 'Z', bigsz);
g_sp_got = 0;
if (hio_dev_sck_write(g_sp_cli, big, bigsz, HIO_NULL, &dst) <= -1)
{
/* the system may refuse a message this large outright, which is
* also a correct outcome - nothing bogus reaches on_read */
OK (g_sp_reads == reads_before, "an oversized message is dropped whole rather than split");
}
else
{
run_until (&g_sp_got);
OK (g_sp_reads == reads_before,
"an oversized message is dropped whole rather than split");
}
hio_freemem (g_hio, big);
}
sp_teardown ();
}
/* ------------------------------------------------------------------ */
int main (void)
{
hio_errinf_t errinf;
hio_dev_sck_make_t mi;
hio_dev_sck_t* probe;
g_hio = hio_open(HIO_NULL, 0, HIO_NULL, HIO_FEATURE_ALL, 16, &errinf);
if (!g_hio)
{
no_plan ();
bail_out ("unable to open hio");
return -1;
}
quiet_logging (g_hio);
/* a library built without sctp reports HIO_ENOIMPL here, which is how a
* caller is meant to discover it. skip_all has to come before no_plan. */
fill_make (&mi, 0);
probe = hio_dev_sck_make(g_hio, 0, &mi);
if (!probe)
{
if (hio_geterrnum(g_hio) == HIO_ENOIMPL)
{
skip_all ("this build of hio has no sctp support");
hio_close (g_hio);
return exit_status();
}
no_plan ();
bail_out ("sctp device creation failed for an unexpected reason");
hio_close (g_hio);
return -1;
}
hio_dev_sck_kill (probe);
hio_exec (g_hio);
no_plan ();
test_association_and_ancillary ();
test_notifications_are_not_data ();
test_one_to_many ();
hio_close (g_hio);
return exit_status();
}