diff --git a/ConfigureChecks.cmake b/ConfigureChecks.cmake index 4d5adc4219fb5155027737206e45dec59c341813..4a2f55e95258f67ad310f0c9671a28ce0333fa61 100644 --- a/ConfigureChecks.cmake +++ b/ConfigureChecks.cmake @@ -43,6 +43,7 @@ int main(void){ return 0; } endif(CMAKE_COMPILER_IS_GNUCC AND NOT MINGW AND NOT OS2) # HEADERS +check_include_file(netinet/tcp_fsm.h HAVE_NETINET_TCP_FSM_H) check_include_file(sys/filio.h HAVE_SYS_FILIO_H) check_include_file(sys/signalfd.h HAVE_SYS_SIGNALFD_H) check_include_file(sys/eventfd.h HAVE_SYS_EVENTFD_H) diff --git a/config.h.cmake b/config.h.cmake index 36050b560b0b7149276733f0208fb113946e44b2..d3ceb238f0274e8905d1e2535f6dea30dbd52b13 100644 --- a/config.h.cmake +++ b/config.h.cmake @@ -9,6 +9,7 @@ /************************** HEADER FILES *************************/ +#cmakedefine HAVE_NETINET_TCP_FSM_H 1 #cmakedefine HAVE_SYS_FILIO_H 1 #cmakedefine HAVE_SYS_SIGNALFD_H 1 #cmakedefine HAVE_SYS_EVENTFD_H 1 diff --git a/src/socket_wrapper.c b/src/socket_wrapper.c index 5b7c9eaddc526625bdaf034baf5cf6beafe56eb9..333bdd40df72deea2b0e6e0cbadd6e5998e0a93f 100644 --- a/src/socket_wrapper.c +++ b/src/socket_wrapper.c @@ -66,6 +66,9 @@ #include #include #include +#ifdef HAVE_NETINET_TCP_FSM_H +#include +#endif #include #include #include @@ -264,6 +267,7 @@ struct socket_info int defer_connect; int pktinfo; int tcp_nodelay; + int listening; /* The unix path so we can unlink it on close() */ struct sockaddr_un un_addr; @@ -4097,6 +4101,9 @@ static int swrap_listen(int s, int backlog) } ret = libc_listen(s, backlog); + if (ret == 0) { + si->listening = 1; + } out: SWRAP_UNLOCK_SI(si); @@ -4446,6 +4453,56 @@ static int swrap_getsockopt(int s, int level, int optname, ret = 0; goto done; #endif /* TCP_NODELAY */ +#ifdef TCP_INFO + case TCP_INFO: { + struct tcp_info info; + socklen_t ilen = sizeof(info); + +#ifdef HAVE_NETINET_TCP_FSM_H +/* This is FreeBSD */ +# define __TCP_LISTEN TCPS_LISTEN +# define __TCP_ESTABLISHED TCPS_ESTABLISHED +# define __TCP_CLOSE TCPS_CLOSED +#else +/* This is Linux */ +# define __TCP_LISTEN TCP_LISTEN +# define __TCP_ESTABLISHED TCP_ESTABLISHED +# define __TCP_CLOSE TCP_CLOSE +#endif + + ZERO_STRUCT(info); + if (si->listening) { + info.tcpi_state = __TCP_LISTEN; + } else if (si->connected) { + /* + * For now we just fake a few values + * supported both by FreeBSD and Linux + */ + info.tcpi_state = __TCP_ESTABLISHED; + info.tcpi_rto = 200000; /* 200 msec */ + info.tcpi_rtt = 5000; /* 5 msec */ + info.tcpi_rttvar = 5000; /* 5 msec */ + } else { + info.tcpi_state = __TCP_CLOSE; + info.tcpi_rto = 1000000; /* 1 sec */ + info.tcpi_rtt = 0; + info.tcpi_rttvar = 250000; /* 250 msec */ + } + + if (optval == NULL || optlen == NULL || + *optlen < (socklen_t)ilen) { + errno = EINVAL; + ret = -1; + goto done; + } + + *optlen = ilen; + memcpy(optval, &info, ilen); + + ret = 0; + goto done; + } +#endif /* TCP_INFO */ default: break; } @@ -4578,7 +4635,7 @@ static int swrap_vioctl(int s, unsigned long int r, va_list va) { struct socket_info *si = find_socket_info(s); va_list ap; - int value; + int *value_ptr = NULL; int rc; if (!si) { @@ -4593,14 +4650,34 @@ static int swrap_vioctl(int s, unsigned long int r, va_list va) switch (r) { case FIONREAD: - value = *((int *)va_arg(ap, int *)); + if (rc == 0) { + value_ptr = ((int *)va_arg(ap, int *)); + } if (rc == -1 && errno != EAGAIN && errno != ENOBUFS) { swrap_pcap_dump_packet(si, NULL, SWRAP_PENDING_RST, NULL, 0); - } else if (value == 0) { /* END OF FILE */ + } else if (value_ptr != NULL && *value_ptr == 0) { /* END OF FILE */ swrap_pcap_dump_packet(si, NULL, SWRAP_PENDING_RST, NULL, 0); } break; +#ifdef FIONWRITE + case FIONWRITE: + /* this is FreeBSD */ + FALL_THROUGH; /* to TIOCOUTQ */ +#endif /* FIONWRITE */ + case TIOCOUTQ: /* same as SIOCOUTQ on Linux */ + /* + * This may return more bytes then the application + * sent into the socket, for tcp it should + * return the number of unacked bytes. + * + * On AF_UNIX, all bytes are immediately acked! + */ + if (rc == 0) { + value_ptr = ((int *)va_arg(ap, int *)); + *value_ptr = 0; + } + break; } va_end(ap); diff --git a/tests/echo_srv.c b/tests/echo_srv.c index 93778f21f66ffd3e450df773eb093676787f4ca9..87c85f74b22a96ca54878de502b98c3d5e123141 100644 --- a/tests/echo_srv.c +++ b/tests/echo_srv.c @@ -9,6 +9,10 @@ #include #include +#include +#ifdef HAVE_NETINET_TCP_FSM_H +#include +#endif #include #include @@ -315,6 +319,34 @@ static int setup_srv(struct echo_srv_opts *opts, int *_sock) perror("listen"); return ret; } +#ifdef TCP_INFO + { + struct tcp_info info; + socklen_t optlen = sizeof(info); + + ZERO_STRUCT(info); + ret = getsockopt(sock, IPPROTO_TCP, TCP_INFO, &info, &optlen); + if (ret == -1) { + ret = errno; + perror("TCP_INFO failed"); + close(sock); + return ret; + } +#ifdef HAVE_NETINET_TCP_FSM_H +/* This is FreeBSD */ +# define __TCP_LISTEN TCPS_LISTEN +#else +/* This is Linux */ +# define __TCP_LISTEN TCP_LISTEN +#endif + if (info.tcpi_state != __TCP_LISTEN) { + errno = ret = ERANGE; + perror("not __TCP_LISTEN => ERANGE..."); + close(sock); + return ret; + } + } +#endif /* TCP_INFO */ } *_sock = sock; diff --git a/tests/test_echo_tcp_socket_options.c b/tests/test_echo_tcp_socket_options.c index dfa46fe46439271ffba0bb9974b3fed714effc8c..5e5661d04b9f730ab9ac2fd6e48e7d990476e1d4 100644 --- a/tests/test_echo_tcp_socket_options.c +++ b/tests/test_echo_tcp_socket_options.c @@ -11,12 +11,25 @@ #include #include #include +#ifdef HAVE_NETINET_TCP_FSM_H +#include +#endif #include #include #include #include #include +#ifdef HAVE_NETINET_TCP_FSM_H +/* This is FreeBSD */ +# define __TCP_ESTABLISHED TCPS_ESTABLISHED +# define __TCP_CLOSE TCPS_CLOSED +#else +/* This is Linux */ +# define __TCP_ESTABLISHED TCP_ESTABLISHED +# define __TCP_CLOSE TCP_CLOSE +#endif + #ifndef ZERO_STRUCT #define ZERO_STRUCT(x) memset((char *)&(x), 0, sizeof(x)) #endif @@ -300,6 +313,9 @@ static void test_sockopt_tcp(void **state) .sa_socklen = sizeof(struct sockaddr_in), }; int opt = -1; +#ifdef TCP_INFO + struct tcp_info info; +#endif socklen_t optlen = sizeof(int); int rc; @@ -318,9 +334,27 @@ static void test_sockopt_tcp(void **state) &addr.sa.in.sin_addr); assert_int_equal(rc, 1); +#ifdef TCP_INFO + ZERO_STRUCT(info); + optlen = sizeof(info); + rc = getsockopt(s, IPPROTO_TCP, TCP_INFO, &info, &optlen); + assert_return_code(rc, errno); + assert_int_equal(optlen, sizeof(info)); + printf("info.tcpi_state=0x%x\n", info.tcpi_state); + printf("info.tcpi_rto=%u\n", info.tcpi_rto); + printf("info.tcpi_rtt=%u\n", info.tcpi_rtt); + printf("info.tcpi_rttvar=%u\n", info.tcpi_rttvar); + assert_int_equal(info.tcpi_state, __TCP_CLOSE); + assert_int_not_equal(info.tcpi_rto, 0); + assert_int_equal(info.tcpi_rtt, 0); + assert_int_not_equal(info.tcpi_rttvar, 0); +#endif /* TCP_INFO */ + rc = connect(s, &addr.sa.s, addr.sa_socklen); assert_int_equal(rc, 0); + opt = -1; + optlen = sizeof(int); rc = getsockopt(s, IPPROTO_TCP, TCP_NODELAY, &opt, &optlen); assert_return_code(rc, errno); assert_int_equal(opt, 0); @@ -336,6 +370,22 @@ static void test_sockopt_tcp(void **state) assert_return_code(rc, errno); assert_int_equal(opt, 1); +#ifdef TCP_INFO + ZERO_STRUCT(info); + optlen = sizeof(info); + rc = getsockopt(s, IPPROTO_TCP, TCP_INFO, &info, &optlen); + assert_return_code(rc, errno); + assert_int_equal(optlen, sizeof(info)); + printf("info.tcpi_state=0x%x\n", info.tcpi_state); + printf("info.tcpi_rto=%u\n", info.tcpi_rto); + printf("info.tcpi_rtt=%u\n", info.tcpi_rtt); + printf("info.tcpi_rttvar=%u\n", info.tcpi_rttvar); + assert_int_equal(info.tcpi_state, __TCP_ESTABLISHED); + assert_int_not_equal(info.tcpi_rto, 0); + assert_int_not_equal(info.tcpi_rtt, 0); + assert_int_not_equal(info.tcpi_rttvar, 0); +#endif /* TCP_INFO */ + close(s); }