diff --git a/configure.ac b/configure.ac index ad51ef45..4dd13e8c 100644 --- a/configure.ac +++ b/configure.ac @@ -1,400 +1,401 @@ # # Copyright (C) 2010-2015 Red Hat, Inc. All rights reserved. # # Authors: Fabio M. Di Nitto # Federico Simoncelli # # This software licensed under GPL-2.0+, LGPL-2.0+ # # -*- Autoconf -*- # Process this file with autoconf to produce a configure script. # AC_PREREQ([2.63]) AC_INIT([kronosnet], m4_esyscmd([build-aux/git-version-gen .tarball-version]), [devel@lists.kronosnet.org]) AC_USE_SYSTEM_EXTENSIONS AM_INIT_AUTOMAKE([1.11.1 dist-bzip2 dist-xz color-tests -Wno-portability subdir-objects]) LT_PREREQ([2.2.6]) LT_INIT AC_CONFIG_MACRO_DIR([m4]) AC_CONFIG_SRCDIR([kronosnetd/main.c]) AC_CONFIG_HEADERS([config.h]) AC_CANONICAL_HOST AC_PROG_LIBTOOL AC_LANG([C]) systemddir=${prefix}/lib/systemd/system if test "$prefix" = "NONE"; then prefix="/usr" if test "$localstatedir" = "\${prefix}/var"; then localstatedir="/var" fi if test "$sysconfdir" = "\${prefix}/etc"; then sysconfdir="/etc" fi if test "$systemddir" = "NONE/lib/systemd/system"; then systemddir=/lib/systemd/system fi if test "$libdir" = "\${exec_prefix}/lib"; then if test -e /usr/lib64; then libdir="/usr/lib64" else libdir="/usr/lib" fi fi fi # Checks for programs. if ! ${MAKE-make} --version /cannot/make/this >/dev/null 2>&1; then AC_MSG_ERROR(["you don't seem to have GNU make; it is required"]) fi AC_PROG_AWK AC_PROG_GREP AC_PROG_SED AC_PROG_CPP AC_PROG_CC AM_PROG_CC_C_O AC_PROG_LN_S AC_PROG_INSTALL AC_PROG_MAKE_SET AC_PROG_CXX AC_PROG_RANLIB AC_CHECK_PROGS([PUBLICAN], [publican], [:]) AC_CHECK_PROGS([PKGCONFIG], [pkg-config]) AC_ARG_ENABLE([kronosnetd], [ --enable-kronosnetd : Kronosnetd support ],, [ enable_kronosnetd="no" ]) AM_CONDITIONAL([BUILD_KRONOSNETD], test x$enable_kronosnetd = xyes) AC_ARG_ENABLE([libtap], [ --enable-libtap : libtap support ],, [ enable_libtap="no" ]) if test "x$enable_kronosnetd" = xyes; then enable_libtap=yes fi AM_CONDITIONAL([BUILD_LIBTAP], test x$enable_libtap = xyes) AC_ARG_ENABLE([libknet-sctp], [ --enable-libknet-sctp : libknet SCTP support ],, [ enable_libknet_sctp="yes" ]) ## local helper functions # this function checks if CC support options passed as # args. Global CFLAGS are ignored during this test. cc_supports_flag() { saveCPPFLAGS="$CPPFLAGS" CPPFLAGS="$@" if echo $CC | grep -q clang; then CPPFLAGS="-Werror $CPPFLAGS" fi AC_MSG_CHECKING([whether $CC supports "$@"]) AC_PREPROC_IFELSE([AC_LANG_PROGRAM([])], [RC=0; AC_MSG_RESULT([yes])], [RC=1; AC_MSG_RESULT([no])]) CPPFLAGS="$saveCPPFLAGS" return $RC } # helper macro to check libs without adding them to LIBS check_lib_no_libs() { lib_no_libs_arg1=$1 shift lib_no_libs_arg2=$1 shift lib_no_libs_args=$@ AC_CHECK_LIB([$lib_no_libs_arg1], [$lib_no_libs_arg2],,, [$lib_no_libs_args]) LIBS=$ac_check_lib_save_LIBS } # Checks for C features AC_C_INLINE # Checks for libraries. AC_CHECK_LIB([pthread], [pthread_create]) AC_CHECK_LIB([m], [ceil]) AC_CHECK_LIB([rt], [clock_gettime]) PKG_CHECK_MODULES([nss],[nss]) # Checks for header files. AC_CHECK_HEADERS([fcntl.h]) AC_CHECK_HEADERS([stdlib.h]) AC_CHECK_HEADERS([string.h]) AC_CHECK_HEADERS([strings.h]) AC_CHECK_HEADERS([sys/ioctl.h]) AC_CHECK_HEADERS([syslog.h]) AC_CHECK_HEADERS([unistd.h]) AC_CHECK_HEADERS([netinet/in.h]) AC_CHECK_HEADERS([sys/socket.h]) AC_CHECK_HEADERS([arpa/inet.h]) AC_CHECK_HEADERS([netdb.h]) AC_CHECK_HEADERS([limits.h]) AC_CHECK_HEADERS([stdint.h]) AC_CHECK_HEADERS([sys/epoll.h]) if test "x$enable_libknet_sctp" = xyes; then AC_CHECK_HEADERS([netinet/sctp.h],, AC_MSG_ERROR(["missing required SCTP headers"])) fi # Checks for typedefs, structures, and compiler characteristics. AC_C_INLINE AC_TYPE_SIZE_T AC_TYPE_PID_T AC_TYPE_SSIZE_T AC_TYPE_UINT8_T AC_TYPE_UINT16_T AC_TYPE_UINT32_T AC_TYPE_UINT64_T AC_TYPE_INT32_T # Checks for library functions. AC_FUNC_ALLOCA AC_FUNC_FORK AC_FUNC_MALLOC AC_FUNC_REALLOC AC_CHECK_FUNCS([memset]) AC_CHECK_FUNCS([strdup]) AC_CHECK_FUNCS([strerror]) AC_CHECK_FUNCS([dup2]) AC_CHECK_FUNCS([select]) AC_CHECK_FUNCS([socket]) AC_CHECK_FUNCS([inet_ntoa]) AC_CHECK_FUNCS([memmove]) AC_CHECK_FUNCS([strchr]) AC_CHECK_FUNCS([atexit]) AC_CHECK_FUNCS([ftruncate]) AC_CHECK_FUNCS([strrchr]) AC_CHECK_FUNCS([strstr]) AC_CHECK_FUNCS([clock_gettime]) AC_CHECK_FUNCS([strcasecmp]) AC_CHECK_FUNCS([sendmmsg]) AC_CHECK_FUNCS([recvmmsg]) AC_CHECK_FUNCS([kevent]) # if neither sys/epoll.h nor kevent are present, we should fail. if test "x$ac_cv_header_sys_epoll_h" = xno && test "x$ac_cv_func_kevent" = xno; then AC_MSG_ERROR([Both epoll and kevent unavailable on this OS]) fi if test "x$ac_cv_header_sys_epoll_h" = xyes && test "x$ac_cv_func_kevent" = xyes; then AC_MSG_ERROR([Both epoll and kevent available on this OS, please contact the maintainers to fix the code]) fi # Check entries in specific structs AC_CHECK_MEMBER([struct mmsghdr.msg_hdr], [AC_DEFINE_UNQUOTED([HAVE_MMSGHDR], [1], [struct mmsghdr exists])], [], [[#include ]]) # checks (for kronosnetd) if test "x$enable_kronosnetd" = xyes; then AC_CHECK_HEADERS([security/pam_appl.h], [AC_CHECK_LIB([pam], [pam_start])], [AC_MSG_ERROR([Unable to find LinuxPAM devel files])]) AC_CHECK_HEADERS([security/pam_misc.h], [AC_CHECK_LIB([pam_misc], [misc_conv])], [AC_MSG_ERROR([Unable to find LinuxPAM MISC devel files])]) PKG_CHECK_MODULES([libqb], [libqb]) AC_CHECK_LIB([qb], [qb_log_thread_priority_set], [have_qb_log_thread_priority_set="yes"], [have_qb_log_thread_priority_set="no"]) if test "x${have_qb_log_thread_priority_set}" = xyes; then AC_DEFINE_UNQUOTED([HAVE_QB_LOG_THREAD_PRIORITY_SET], 1, [have qb_log_thread_priority_set]) fi fi # local options AC_ARG_ENABLE([debug], [ --enable-debug enable debug build. ], [ default="no" ]) AC_ARG_ENABLE([publicandocs], [ --enable-publicandocs enable docs build. ], [ default="no" ]) AC_ARG_WITH([initdefaultdir], [ --with-initdefaultdir : path to /etc/sysconfig/.. or /etc/default dir. ], [ INITDEFAULTDIR="$withval" ], [ INITDEFAULTDIR="$sysconfdir/default" ]) AC_ARG_WITH([initddir], [ --with-initddir=DIR : path to init script directory. ], [ INITDDIR="$withval" ], [ INITDDIR="$sysconfdir/init.d" ]) AC_ARG_WITH([systemddir], [ --with-systemddir=DIR : path to systemd unit files directory. ], [ SYSTEMDDIR="$withval" ], [ SYSTEMDDIR="$systemddir" ]) AC_ARG_WITH([syslogfacility], [ --with-syslogfacility=FACILITY default syslog facility. ], [ SYSLOGFACILITY="$withval" ], [ SYSLOGFACILITY="LOG_DAEMON" ]) AC_ARG_WITH([sysloglevel], [ --with-sysloglevel=LEVEL default syslog level. ], [ SYSLOGLEVEL="$withval" ], [ SYSLOGLEVEL="LOG_INFO" ]) AC_ARG_WITH([defaultadmgroup], [ --with-defaultadmgroup=GROUP define PAM group. Users part of this group will be allowed to configure kronosnet. Others will only receive read-only rights. ], [ DEFAULTADMGROUP="$withval" ], [ DEFAULTADMGROUP="kronosnetadm" ]) ## random vars LOGDIR=${localstatedir}/log/ RUNDIR=${localstatedir}/run/ DEFAULT_CONFIG_DIR=${sysconfdir}/kronosnet ## do subst AM_CONDITIONAL([BUILD_DOCS], [test "x${enable_publicandocs}" = xyes]) AM_CONDITIONAL([DEBUG], [test "x${enable_debug}" = xyes]) AC_SUBST([DEFAULT_CONFIG_DIR]) AC_SUBST([INITDEFAULTDIR]) AC_SUBST([INITDDIR]) AC_SUBST([SYSTEMDDIR]) AC_SUBST([LOGDIR]) AC_SUBST([DEFAULTADMGROUP]) AC_DEFINE_UNQUOTED([DEFAULT_CONFIG_DIR], ["$(eval echo ${DEFAULT_CONFIG_DIR})"], [Default config directory]) AC_DEFINE_UNQUOTED([DEFAULT_CONFIG_FILE], ["$(eval echo ${DEFAULT_CONFIG_DIR}/kronosnetd.conf)"], [Default config file]) AC_DEFINE_UNQUOTED([LOGDIR], ["$(eval echo ${LOGDIR})"], [Default logging directory]) AC_DEFINE_UNQUOTED([DEFAULT_LOG_FILE], ["$(eval echo ${LOGDIR}/kronosnetd.log)"], [Default log file]) AC_DEFINE_UNQUOTED([RUNDIR], ["$(eval echo ${RUNDIR})"], [Default run directory]) AC_DEFINE_UNQUOTED([SYSLOGFACILITY], [$(eval echo ${SYSLOGFACILITY})], [Default syslog facility]) AC_DEFINE_UNQUOTED([SYSLOGLEVEL], [$(eval echo ${SYSLOGLEVEL})], [Default syslog level]) AC_DEFINE_UNQUOTED([DEFAULTADMGROUP], ["$(eval echo ${DEFAULTADMGROUP})"], [Default admin group]) ## *FLAGS handling ENV_CFLAGS="$CFLAGS" ENV_CPPFLAGS="$CPPFLAGS" ENV_LDFLAGS="$LDFLAGS" # debug build stuff if test "x${enable_debug}" = xyes; then AC_DEFINE_UNQUOTED([DEBUG], [1], [Compiling Debugging code]) OPT_CFLAGS="-O0" else OPT_CFLAGS="-O3" fi # gdb flags if test "x${GCC}" = xyes; then GDB_FLAGS="-ggdb3" else GDB_FLAGS="-g" fi # extra warnings EXTRA_WARNINGS="" WARNLIST=" all shadow missing-prototypes missing-declarations strict-prototypes declaration-after-statement pointer-arith write-strings cast-align bad-function-cast missing-format-attribute format=2 format-security format-nonliteral no-long-long unsigned-char gnu89-inline no-strict-aliasing error address cpp overflow parentheses sequence-point switch uninitialized unused-but-set-variable unused-function unused-result unused-value unused-variable " for j in $WARNLIST; do if cc_supports_flag -W$j; then EXTRA_WARNINGS="$EXTRA_WARNINGS -W$j"; fi done CFLAGS="$ENV_CFLAGS $lt_prog_compiler_pic $OPT_CFLAGS $GDB_FLAGS \ $EXTRA_WARNINGS $WERROR_CFLAGS" CPPFLAGS="$ENV_CPPFLAGS" LDFLAGS="$ENV_LDFLAGS $lt_prog_compiler_pic -Wl,--as-needed" AC_CONFIG_FILES([ Makefile init/Makefile libtap/Makefile libtap/libtap.pc kronosnetd/Makefile kronosnetd/kronosnetd.logrotate libknet/Makefile libknet/libknet.pc libknet/tests/Makefile docs/Makefile poc-code/Makefile poc-code/iov-hash/Makefile poc-code/access-list/Makefile + poc-code/sctp-defrag-bug/Makefile ]) AC_OUTPUT diff --git a/poc-code/Makefile.am b/poc-code/Makefile.am index 4b12510e..e0986523 100644 --- a/poc-code/Makefile.am +++ b/poc-code/Makefile.am @@ -1,13 +1,13 @@ # # Copyright (C) 2016 Red Hat, Inc. All rights reserved. # # Author: Fabio M. Di Nitto # # This software licensed under GPL-2.0+, LGPL-2.0+ # MAINTAINERCLEANFILES = Makefile.in include $(top_srcdir)/build-aux/check.mk -SUBDIRS = access-list iov-hash +SUBDIRS = access-list iov-hash sctp-defrag-bug diff --git a/poc-code/sctp-defrag-bug/Makefile.am b/poc-code/sctp-defrag-bug/Makefile.am new file mode 100644 index 00000000..72617ee3 --- /dev/null +++ b/poc-code/sctp-defrag-bug/Makefile.am @@ -0,0 +1,26 @@ +# +# Copyright (C) 2017 Red Hat, Inc. All rights reserved. +# +# Author: Fabio M. Di Nitto +# +# This software licensed under GPL-2.0+, LGPL-2.0+ +# + +MAINTAINERCLEANFILES = Makefile.in + +include $(top_srcdir)/build-aux/check.mk + +AM_CPPFLAGS = -I$(top_srcdir)/libknet + +# override global LIBS that pulls in lots of craft we don't need here +LIBS = + +noinst_PROGRAMS = client server + +noinst_HEADERS = common.h + +server_SOURCES = server.c \ + common.c + +client_SOURCES = client.c \ + common.c diff --git a/poc-code/sctp-defrag-bug/client.c b/poc-code/sctp-defrag-bug/client.c new file mode 100644 index 00000000..919084e8 --- /dev/null +++ b/poc-code/sctp-defrag-bug/client.c @@ -0,0 +1,198 @@ +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_NETINET_SCTP_H +#include +#include "common.h" + +int main(int argc, char **argv) +{ + int err = 0; + int rv; + char defport[8] = "50000"; + char *address = NULL, *port = NULL; + + struct sockaddr_storage ss; + int sock; + + int rx_epoll; + struct epoll_event ev; + struct epoll_event events[32]; + int i, nev; + + struct mmsghdr msg_in[256]; + + struct mmsghdr msg_out[256]; + struct iovec iov_out[256]; + + int sent_msgs; + + while ((rv = getopt(argc, argv, "a:p:")) != EOF) { + switch(rv) { + case 'a': + address = optarg; + break; + case 'p': + port = optarg; + break; + default: + fprintf(stderr, "Unknown option\n"); + return -1; + break; + } + } + + /* + * Setup RX buffers + */ + memset(&msg_in, 0, sizeof(struct mmsghdr)); + if (setup_rx_buffers(msg_in) < 0) { + return -1; + } + + /* + * setup TX buffers + */ + for (i = 0; i < 256; i++) { + iov_out[i].iov_base = (void *)malloc(65536); + if (!iov_out[i].iov_base) { + fprintf(stderr, "Unable to malloc RX buffers(%d): %s\n", + errno, strerror(errno)); + return -1; + } + memset(iov_out[i].iov_base, 0, 65536); + iov_out[i].iov_len = 65536; + } + + rx_epoll = epoll_create(32); + if (rx_epoll < 0) { + fprintf(stderr, "Unable to create rx_epoll (%d): %s\n", + errno, strerror(errno)); + return -1; + } + + /* + * setup SCTP client socket + */ + + if (!address) { + fprintf(stderr, "No server address specified (use -a ).\n"); + fprintf(stderr, "Scanning the internet for SCTP servers (this might take a while).\n"); + while (1) { + fprintf(stderr, "..."); + sleep(1); + } + } + + if (!port) { + port = defport; + } + + if (strtoaddr(address, port, &ss, sizeof(struct sockaddr_storage)) < 0) { + return -1; + } + + sock = socket(ss.ss_family, SOCK_STREAM, IPPROTO_SCTP); + if (sock < 0) { + fprintf(stderr, "unable to create socket (%d): %s\n", + errno, strerror(errno)); + return -1; + } + + if (setup_sctp_common_sock_opts(sock, &ss) < 0) { + fprintf(stderr, "Unable to set socket options\n"); + goto out; + } + + if (connect(sock, (struct sockaddr *)&ss, sizeof(struct sockaddr_storage)) < 0) { + if ((errno != EALREADY) && (errno != EINPROGRESS) && (errno != EISCONN)) { + fprintf(stderr, "Unable to connect to server: (%d): %s\n", + errno, strerror(errno)); + return -1; + } + } + + /* + * i am supposed to check SO_ERROR to see if it's connected, but this is a PoC + * and I am lazy + */ + + sleep(1); + + memset(&ev, 0, sizeof(struct epoll_event)); + ev.events = EPOLLIN; + ev.data.fd = sock; + if (epoll_ctl(rx_epoll, EPOLL_CTL_ADD, sock, &ev) < 0) { + fprintf(stderr, "Unable to add listen socket to epoll (%d): %s\n", + errno, strerror(errno)); + goto out; + } + + /* + * main loop + */ + + while(1) { + nev = epoll_wait(rx_epoll, events, 32, 0); + if (nev < 0) { + fprintf(stderr, "SCTP listen handler EPOLL ERROR (%d): %s\n", + errno, strerror(errno)); + } else { + for (i = 0; i < nev; i++) { + if (events[i].data.fd == sock) { + get_incoming_data(events[i].data.fd, msg_in); + } + } + } + + sleep(1); + + memset(&msg_out, 0, sizeof(msg_out)); + for (i = 0; i < 256; i++) { + memset(&msg_out[i].msg_hdr, 0, sizeof(struct msghdr)); + msg_out[i].msg_hdr.msg_name = &ss; + msg_out[i].msg_hdr.msg_namelen = sizeof(struct sockaddr_storage); + msg_out[i].msg_hdr.msg_iov = &iov_out[i]; + msg_out[i].msg_hdr.msg_iovlen = 1; + } + + sent_msgs = sendmmsg(sock, msg_out, 256, MSG_DONTWAIT | MSG_NOSIGNAL); + if (sent_msgs <= 0) { + fprintf(stderr, "Error sending msgs (%d): %s\n", + errno, strerror(errno)); + } + if (sent_msgs != 256) { + fprintf(stderr, "Unable to send all 256 messages at once (sent: %d)\n", + sent_msgs); + } + fprintf(stderr, "sent %d messages\n", sent_msgs); + } + +out: + if (sock >= 0) { + close(sock); + } + if (rx_epoll >= 0) { + close(rx_epoll); + } + + return err; +} +#else +int main(void) +{ + printf("SCTP unsupported in this build\n"); + errno = EINVAL; + return -1; +} +#endif diff --git a/poc-code/sctp-defrag-bug/common.c b/poc-code/sctp-defrag-bug/common.c new file mode 100644 index 00000000..905f351a --- /dev/null +++ b/poc-code/sctp-defrag-bug/common.c @@ -0,0 +1,312 @@ +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_NETINET_SCTP_H +#include +#include "common.h" + +int strtoaddr(const char *host, const char *port, struct sockaddr_storage *ss, socklen_t sslen) +{ + int err; + struct addrinfo hints; + struct addrinfo *result = NULL; + + if (!host) { + errno = EINVAL; + return -1; + } + + if (!port) { + errno = EINVAL; + return -1; + } + + if (!ss) { + errno = EINVAL; + return -1; + } + + if (!sslen) { + errno = EINVAL; + return -1; + } + + memset(&hints, 0, sizeof(struct addrinfo)); + + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_DGRAM; + hints.ai_flags = AI_NUMERICHOST | AI_NUMERICSERV; + + err = getaddrinfo(host, port, &hints, &result); + + if (!err) { + memmove(ss, result->ai_addr, + (sslen < result->ai_addrlen) ? sslen : result->ai_addrlen); + + freeaddrinfo(result); + } + + return err; +} + +int _fdset_cloexec(int fd) +{ + int fdflags; + + fdflags = fcntl(fd, F_GETFD, 0); + if (fdflags < 0) + return -1; + + fdflags |= FD_CLOEXEC; + + if (fcntl(fd, F_SETFD, fdflags) < 0) + return -1; + + return 0; +} + +int _fdset_nonblock(int fd) +{ + int fdflags; + + fdflags = fcntl(fd, F_GETFL, 0); + if (fdflags < 0) + return -1; + + fdflags |= O_NONBLOCK; + + if (fcntl(fd, F_SETFL, fdflags) < 0) + return -1; + + return 0; +} + +int setup_sctp_common_sock_opts(int sock, struct sockaddr_storage *ss) +{ + struct sctp_event_subscribe events; + int value; + + if (_fdset_cloexec(sock)) { + fprintf(stderr, "unable to set CLOEXEC socket opts (%d): %s\n", + errno, strerror(errno)); + return -1; + } + + if (_fdset_nonblock(sock)) { + fprintf(stderr, "unable to set NONBLOCK socket opts (%d): %s\n", + errno, strerror(errno)); + return -1; + } + + value = 8388608; + if (setsockopt(sock, SOL_SOCKET, SO_RCVBUFFORCE, &value, sizeof(value)) < 0) { + fprintf(stderr, "Unable to set receive buffer (%d): %s\n", + errno, strerror(errno)); + return -1; + } + + value = 8388608; + if (setsockopt(sock, SOL_SOCKET, SO_SNDBUFFORCE, &value, sizeof(value)) < 0) { + fprintf(stderr, "Unable to set send buffer (%d): %s\n", + errno, strerror(errno)); + return -1; + } + + if (ss->ss_family == AF_INET6) { + value = IPV6_PMTUDISC_PROBE; + if (setsockopt(sock, SOL_IPV6, IPV6_MTU_DISCOVER, &value, sizeof(value)) <0) { + fprintf(stderr, "Unable to set PMTUDISC (%d): %s\n", + errno, strerror(errno)); + return -1; + } + } else { + value = IP_PMTUDISC_PROBE; + if (setsockopt(sock, SOL_IP, IP_MTU_DISCOVER, &value, sizeof(value)) <0) { + fprintf(stderr, "Unable to set PMTUDISC (%d): %s\n", + errno, strerror(errno)); + return -1; + } + } + + value = 1; + if (setsockopt(sock, SOL_SCTP, SCTP_NODELAY, &value, sizeof(value)) < 0) { + fprintf(stderr, "Unable to set SCTP_NODELAY (%d): %s\n", + errno, strerror(errno)); + return -1; + } + +#if 0 /* workaround */ + value = 1; + if (setsockopt(sock, SOL_SCTP, SCTP_DISABLE_FRAGMENTS, &value, sizeof(value)) < 0) { + fprintf(stderr, "Unable to set SCTP_DISABLE_FRAGMENTS (%d): %s\n", + errno, strerror(errno)); + return -1; + } +#endif + + memset(&events, 0, sizeof (events)); + events.sctp_data_io_event = 1; + events.sctp_association_event = 1; + events.sctp_send_failure_event = 1; + events.sctp_address_event = 1; + events.sctp_peer_error_event = 1; + events.sctp_shutdown_event = 1; + if (setsockopt(sock, IPPROTO_SCTP, SCTP_EVENTS, &events, sizeof (events)) < 0) { + fprintf(stderr, "Unable to configure SCTP notifications (%d): %s\n", + errno, strerror(errno)); + return -1; + } + + return 0; +} + +int setup_sctp_server_sock_opts(int sock, struct sockaddr_storage *ss) +{ + int value; + + if (setup_sctp_common_sock_opts(sock, ss) < 0) { + return -1; + } + + value = 1; + if (setsockopt(sock, SOL_IP, IP_FREEBIND, &value, sizeof(value)) <0) { + fprintf(stderr, "Unable to set FREEBIND (%d): %s\n", + errno, strerror(errno)); + return -1; + } + + if (ss->ss_family == AF_INET6) { + value = 1; + if (setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, + &value, sizeof(value)) < 0) { + fprintf(stderr, "Unable to set IPv6 only (%d): %s\n", + errno, strerror(errno)); + return -1; + } + } + + value = 1; + if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &value, sizeof(value)) < 0) { + fprintf(stderr, "Unable to set REUSEADDR (%d): %s\n", + errno, strerror(errno)); + return -1; + } + + return 0; +} + +static void parse_incoming_data(struct mmsghdr msg) +{ + int i; + struct iovec *iov = msg.msg_hdr.msg_iov; + size_t iovlen = msg.msg_hdr.msg_iovlen; + union sctp_notification *snp; + + if (!(msg.msg_hdr.msg_flags & MSG_NOTIFICATION)) { + if (msg.msg_len == 0) { + fprintf(stderr, "Received 0bytes packet\n"); + exit(-1); + } + /* check pckt len here */ + if (msg.msg_len != 65536) { + fprintf(stderr, "KABOOM: %d\n", msg.msg_len); + exit(-1); + } + return; + } + + if (!(msg.msg_hdr.msg_flags & MSG_EOR)) { + fprintf(stderr, "[event] end of notifications\n"); + return; + } + + /* got a notification */ + for (i=0; i< iovlen; i++) { + snp = iov[i].iov_base; + + switch (snp->sn_header.sn_type) { + case SCTP_ASSOC_CHANGE: + fprintf(stderr, "[event] sctp assoc change\n"); + break; + case SCTP_SHUTDOWN_EVENT: + fprintf(stderr, "[event] sctp shutdown event\n"); + break; + case SCTP_SEND_FAILED: + fprintf(stderr, "[event] sctp send failed\n"); + break; + case SCTP_PEER_ADDR_CHANGE: + fprintf(stderr, "[event] sctp peer addr change\n"); + break; + case SCTP_REMOTE_ERROR: + fprintf(stderr, "[event] sctp remote error\n"); + break; + default: + fprintf(stderr, "[event] unknown sctp event type: %hu\n", snp->sn_header.sn_type); + exit(-1); + break; + } + } + return; +} + +void get_incoming_data(int sock, struct mmsghdr *msg) +{ + int i, msg_recv; + + msg_recv = recvmmsg(sock, msg, 256, MSG_DONTWAIT | MSG_NOSIGNAL, NULL); + + if (msg_recv <= 0) { + fprintf(stderr, "Error message received from recvmmsg (%d): %s\n", + errno, strerror(errno)); + exit(-1); + } + + fprintf(stderr, "Received: %d messages\n", msg_recv); + + for (i = 0; i < msg_recv; i++) { + parse_incoming_data(msg[i]); + } +} + + +int setup_rx_buffers(struct mmsghdr *msg) +{ + int i; + struct sockaddr_storage addr[256]; + struct iovec iov_in[256]; + + if (!msg) { + return -1; + } + + /* + * Setup buffers + */ + for (i = 0; i < 256; i++) { + iov_in[i].iov_base = (void *)malloc(65536); + if (!iov_in[i].iov_base) { + fprintf(stderr, "Unable to malloc RX buffers(%d): %s\n", + errno, strerror(errno)); + return -1; + } + memset(iov_in[i].iov_base, 0, 65536); + iov_in[i].iov_len = 65536; + memset(&msg[i].msg_hdr, 0, sizeof(struct msghdr)); + msg[i].msg_hdr.msg_name = &addr[i]; + msg[i].msg_hdr.msg_namelen = sizeof(struct sockaddr_storage); + msg[i].msg_hdr.msg_iov = &iov_in[i]; + msg[i].msg_hdr.msg_iovlen = 1; + } + return 0; +} +#endif diff --git a/poc-code/sctp-defrag-bug/common.h b/poc-code/sctp-defrag-bug/common.h new file mode 100644 index 00000000..fc82d8cf --- /dev/null +++ b/poc-code/sctp-defrag-bug/common.h @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2017 Red Hat, Inc. All rights reserved. + * + * Authors: Fabio M. Di Nitto + * + * This software licensed under GPL-2.0+, LGPL-2.0+ + */ + +#ifndef __COMMON_H__ +#define __COMMON_H__ + +int strtoaddr(const char *host, const char *port, struct sockaddr_storage *ss, socklen_t sslen); +int _fdset_cloexec(int fd); +int _fdset_nonblock(int fd); +int setup_sctp_common_sock_opts(int sock, struct sockaddr_storage *ss); +int setup_sctp_server_sock_opts(int sock, struct sockaddr_storage *ss); +void get_incoming_data(int sock, struct mmsghdr *msg); +int setup_rx_buffers(struct mmsghdr *msg); + +#endif diff --git a/poc-code/sctp-defrag-bug/server.c b/poc-code/sctp-defrag-bug/server.c new file mode 100644 index 00000000..ac79f3d6 --- /dev/null +++ b/poc-code/sctp-defrag-bug/server.c @@ -0,0 +1,168 @@ +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_NETINET_SCTP_H +#include +#include "common.h" + +int main(int argc, char **argv) +{ + int err = 0; + int rv; + char defaddr[8] = "0.0.0.0"; + char defport[8] = "50000"; + char *address = NULL, *port = NULL; + + struct sockaddr_storage ss, newss; + int sock, newsock; + socklen_t sock_len = sizeof(ss); + + int rx_epoll; + struct epoll_event ev; + struct epoll_event events[32]; + int i, nev; + + struct mmsghdr msg[256]; + + while ((rv = getopt(argc, argv, "a:p:")) != EOF) { + switch(rv) { + case 'a': + address = optarg; + break; + case 'p': + port = optarg; + break; + default: + fprintf(stderr, "Unknown option\n"); + return -1; + break; + } + } + + memset(&msg, 0, sizeof(msg)); + if (setup_rx_buffers(msg) < 0) { + return -1; + } + + rx_epoll = epoll_create(32); + if (rx_epoll < 0) { + fprintf(stderr, "Unable to create rx_epoll (%d): %s\n", + errno, strerror(errno)); + return -1; + } + + /* + * setup SCTP server socket + */ + + if (!address) { + address = defaddr; + } + if (!port) { + port = defport; + } + + if (strtoaddr(address, port, &ss, sizeof(struct sockaddr_storage)) < 0) { + return -1; + } + + sock = socket(ss.ss_family, SOCK_STREAM, IPPROTO_SCTP); + if (sock < 0) { + fprintf(stderr, "unable to create socket (%d): %s\n", + errno, strerror(errno)); + return -1; + } + + if (setup_sctp_server_sock_opts(sock, &ss) < 0) { + fprintf(stderr, "Unable to set socket options\n"); + goto out; + } + + if (bind(sock, (struct sockaddr *)&ss, sizeof(struct sockaddr_storage)) < 0) { + fprintf(stderr, "Unable to bind socket (%d): %s\n", + errno, strerror(errno)); + goto out; + } + + if (listen(sock, 5) < 0) { + fprintf(stderr, "Unable to listen socket (%d): %s\n", + errno, strerror(errno)); + goto out; + } + + memset(&ev, 0, sizeof(struct epoll_event)); + ev.events = EPOLLIN; + ev.data.fd = sock; + if (epoll_ctl(rx_epoll, EPOLL_CTL_ADD, sock, &ev) < 0) { + fprintf(stderr, "Unable to add listen socket to epoll (%d): %s\n", + errno, strerror(errno)); + goto out; + } + + /* + * main loop + */ + + while(1) { + nev = epoll_wait(rx_epoll, events, 32, -1); + if (nev < 0) { + fprintf(stderr, "SCTP listen handler EPOLL ERROR (%d): %s\n", + errno, strerror(errno)); + } + + for (i = 0; i < nev; i++) { + if (events[i].data.fd == sock) { + newsock = accept(sock, (struct sockaddr *)&newss, &sock_len); + if (newsock < 0) { + fprintf(stderr, "Error accepting connection (%d): %s\n", + errno, strerror(errno)); + continue; + } + if (setup_sctp_common_sock_opts(newsock, &newss) < 0) { + fprintf(stderr, "Error setting sockopts\n"); + close(newsock); + continue; + } + memset(&ev, 0, sizeof(struct epoll_event)); + ev.events = EPOLLIN; + ev.data.fd = newsock; + if (epoll_ctl(rx_epoll, EPOLL_CTL_ADD, newsock, &ev) < 0) { + fprintf(stderr, "Unable to add accept new connection (%d): %s\n", + errno, strerror(errno)); + close(newsock); + } + fprintf(stderr, "Accepted socket: %d\n", newsock); + } else { + get_incoming_data(events[i].data.fd, msg); + } + } + } + +out: + if (sock >= 0) { + close(sock); + } + if (rx_epoll >= 0) { + close(rx_epoll); + } + + return err; +} +#else +int main(void) +{ + printf("SCTP unsupported in this build\n"); + errno = EINVAL; + return -1; +} +#endif