#include <err.h>
#include <port.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <sys/sysmacros.h>
#include <stdbool.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <sys/debug.h>
static hrtime_t sock_to = MSEC2NSEC(100);
static const uint32_t msgdata = 0x7777;
#define PORT_NOSA 24134
#define PORT_BIDIR 24135
#define PORT_MISMATCH 24136
#define PORT_OBSA 24137
#define PORT_IBSA 24138
typedef enum {
TCPSIG_SENDRECV,
TCPSIG_NOCONNECT,
TCPSIG_CONNREFUSED,
TCPSIG_NODATA
} tcpsig_pass_t;
typedef struct {
const char *tt_desc;
const int tt_domain;
const uint16_t tt_port;
const bool tt_enable_src;
const bool tt_enable_dst;
const tcpsig_pass_t tt_pass;
} tcpsig_test_t;
static const tcpsig_test_t tcpsig_tests[] = {
{
.tt_desc = "IPv4 NOSA with MD5 enabled on both sides",
.tt_domain = PF_INET,
.tt_port = PORT_NOSA,
.tt_enable_src = true,
.tt_enable_dst = true,
.tt_pass = TCPSIG_CONNREFUSED
}, {
.tt_desc = "IPv4 NOSA with MD5 disabled on both sides",
.tt_domain = PF_INET,
.tt_port = PORT_NOSA,
.tt_enable_src = false,
.tt_enable_dst = false,
.tt_pass = TCPSIG_SENDRECV
}, {
.tt_desc = "IPv4 NOSA with MD5 enabled on src only",
.tt_domain = PF_INET,
.tt_port = PORT_NOSA,
.tt_enable_src = true,
.tt_enable_dst = false,
.tt_pass = TCPSIG_CONNREFUSED
}, {
.tt_desc = "IPv4 NOSA with MD5 enabled on dst only",
.tt_domain = PF_INET,
.tt_port = PORT_NOSA,
.tt_enable_src = false,
.tt_enable_dst = true,
.tt_pass = TCPSIG_SENDRECV
},
{
.tt_desc = "IPv6 NOSA with MD5 enabled on both sides",
.tt_domain = PF_INET6,
.tt_port = PORT_NOSA,
.tt_enable_src = true,
.tt_enable_dst = true,
.tt_pass = TCPSIG_CONNREFUSED
}, {
.tt_desc = "IPv6 NOSA with MD5 disabled on both sides",
.tt_domain = PF_INET6,
.tt_port = PORT_NOSA,
.tt_enable_src = false,
.tt_enable_dst = false,
.tt_pass = TCPSIG_SENDRECV
}, {
.tt_desc = "IPv6 NOSA with MD5 enabled on src only",
.tt_domain = PF_INET6,
.tt_port = PORT_NOSA,
.tt_enable_src = true,
.tt_enable_dst = false,
.tt_pass = TCPSIG_CONNREFUSED
}, {
.tt_desc = "IPv6 NOSA with MD5 enabled on dst only",
.tt_domain = PF_INET6,
.tt_port = PORT_NOSA,
.tt_enable_src = false,
.tt_enable_dst = true,
.tt_pass = TCPSIG_SENDRECV
},
{
.tt_desc = "IPv4 BIDIR with MD5 enabled on both sides",
.tt_domain = PF_INET,
.tt_port = PORT_BIDIR,
.tt_enable_src = true,
.tt_enable_dst = true,
.tt_pass = TCPSIG_SENDRECV
}, {
.tt_desc = "IPv4 BIDIR with MD5 disabled on both sides",
.tt_domain = PF_INET,
.tt_port = PORT_BIDIR,
.tt_enable_src = false,
.tt_enable_dst = false,
.tt_pass = TCPSIG_SENDRECV
}, {
.tt_desc = "IPv4 BIDIR with MD5 enabled on src only",
.tt_domain = PF_INET,
.tt_port = PORT_BIDIR,
.tt_enable_src = true,
.tt_enable_dst = false,
.tt_pass = TCPSIG_NOCONNECT
}, {
.tt_desc = "IPv4 BIDIR with MD5 enabled on dst only",
.tt_domain = PF_INET,
.tt_port = PORT_BIDIR,
.tt_enable_src = false,
.tt_enable_dst = true,
.tt_pass = TCPSIG_NOCONNECT
}, {
.tt_desc = "IPv6 BIDIR with MD5 enabled on both sides",
.tt_domain = PF_INET6,
.tt_port = PORT_BIDIR,
.tt_enable_src = true,
.tt_enable_dst = true,
.tt_pass = TCPSIG_SENDRECV
}, {
.tt_desc = "IPv6 BIDIR with MD5 disabled on both sides",
.tt_domain = PF_INET6,
.tt_port = PORT_BIDIR,
.tt_enable_src = false,
.tt_enable_dst = false,
.tt_pass = TCPSIG_SENDRECV
}, {
.tt_desc = "IPv6 BIDIR with MD5 enabled on src only",
.tt_domain = PF_INET6,
.tt_port = PORT_BIDIR,
.tt_enable_src = true,
.tt_enable_dst = false,
.tt_pass = TCPSIG_NOCONNECT
}, {
.tt_desc = "IPv6 BIDIR with MD5 enabled on dst only",
.tt_domain = PF_INET6,
.tt_port = PORT_BIDIR,
.tt_enable_src = false,
.tt_enable_dst = true,
.tt_pass = TCPSIG_NOCONNECT
},
{
.tt_desc = "IPv4 MISMATCH with MD5 enabled on both sides",
.tt_domain = PF_INET,
.tt_port = PORT_MISMATCH,
.tt_enable_src = true,
.tt_enable_dst = true,
.tt_pass = TCPSIG_SENDRECV
}, {
.tt_desc = "IPv4 MISMATCH with MD5 disabled on both sides",
.tt_domain = PF_INET,
.tt_port = PORT_MISMATCH,
.tt_enable_src = false,
.tt_enable_dst = false,
.tt_pass = TCPSIG_SENDRECV
}, {
.tt_desc = "IPv4 MISMATCH with MD5 enabled on src only",
.tt_domain = PF_INET,
.tt_port = PORT_MISMATCH,
.tt_enable_src = true,
.tt_enable_dst = false,
.tt_pass = TCPSIG_NOCONNECT
}, {
.tt_desc = "IPv4 MISMATCH with MD5 enabled on dst only",
.tt_domain = PF_INET,
.tt_port = PORT_MISMATCH,
.tt_enable_src = false,
.tt_enable_dst = true,
.tt_pass = TCPSIG_NOCONNECT
}, {
.tt_desc = "IPv6 MISMATCH with MD5 enabled on both sides",
.tt_domain = PF_INET6,
.tt_port = PORT_MISMATCH,
.tt_enable_src = true,
.tt_enable_dst = true,
.tt_pass = TCPSIG_SENDRECV
}, {
.tt_desc = "IPv6 MISMATCH with MD5 disabled on both sides",
.tt_domain = PF_INET6,
.tt_port = PORT_MISMATCH,
.tt_enable_src = false,
.tt_enable_dst = false,
.tt_pass = TCPSIG_SENDRECV
}, {
.tt_desc = "IPv6 MISMATCH with MD5 enabled on src only",
.tt_domain = PF_INET6,
.tt_port = PORT_MISMATCH,
.tt_enable_src = true,
.tt_enable_dst = false,
.tt_pass = TCPSIG_NOCONNECT
}, {
.tt_desc = "IPv6 MISMATCH with MD5 enabled on dst only",
.tt_domain = PF_INET6,
.tt_port = PORT_MISMATCH,
.tt_enable_src = false,
.tt_enable_dst = true,
.tt_pass = TCPSIG_NOCONNECT
},
{
.tt_desc = "IPv4 OBSA with MD5 enabled on both sides",
.tt_domain = PF_INET,
.tt_port = PORT_OBSA,
.tt_enable_src = true,
.tt_enable_dst = true,
.tt_pass = TCPSIG_NOCONNECT
}, {
.tt_desc = "IPv4 OBSA with MD5 disabled on both sides",
.tt_domain = PF_INET,
.tt_port = PORT_OBSA,
.tt_enable_src = false,
.tt_enable_dst = false,
.tt_pass = TCPSIG_SENDRECV
}, {
.tt_desc = "IPv4 OBSA with MD5 enabled on src only",
.tt_domain = PF_INET,
.tt_port = PORT_OBSA,
.tt_enable_src = true,
.tt_enable_dst = false,
.tt_pass = TCPSIG_NOCONNECT
}, {
.tt_desc = "IPv4 OBSA with MD5 enabled on dst only",
.tt_domain = PF_INET,
.tt_port = PORT_OBSA,
.tt_enable_src = false,
.tt_enable_dst = true,
.tt_pass = TCPSIG_NOCONNECT
}, {
.tt_desc = "IPv6 OBSA with MD5 enabled on both sides",
.tt_domain = PF_INET6,
.tt_port = PORT_OBSA,
.tt_enable_src = true,
.tt_enable_dst = true,
.tt_pass = TCPSIG_NOCONNECT
}, {
.tt_desc = "IPv6 OBSA with MD5 disabled on both sides",
.tt_domain = PF_INET6,
.tt_port = PORT_OBSA,
.tt_enable_src = false,
.tt_enable_dst = false,
.tt_pass = TCPSIG_SENDRECV
}, {
.tt_desc = "IPv6 OBSA with MD5 enabled on src only",
.tt_domain = PF_INET6,
.tt_port = PORT_OBSA,
.tt_enable_src = true,
.tt_enable_dst = false,
.tt_pass = TCPSIG_NOCONNECT
}, {
.tt_desc = "IPv6 OBSA with MD5 enabled on dst only",
.tt_domain = PF_INET6,
.tt_port = PORT_OBSA,
.tt_enable_src = false,
.tt_enable_dst = true,
.tt_pass = TCPSIG_NOCONNECT
},
{
.tt_desc = "IPv4 IBSA with MD5 enabled on both sides",
.tt_domain = PF_INET,
.tt_port = PORT_IBSA,
.tt_enable_src = true,
.tt_enable_dst = true,
.tt_pass = TCPSIG_CONNREFUSED
}, {
.tt_desc = "IPv4 IBSA with MD5 disabled on both sides",
.tt_domain = PF_INET,
.tt_port = PORT_IBSA,
.tt_enable_src = false,
.tt_enable_dst = false,
.tt_pass = TCPSIG_SENDRECV
}, {
.tt_desc = "IPv4 IBSA with MD5 enabled on src only",
.tt_domain = PF_INET,
.tt_port = PORT_IBSA,
.tt_enable_src = true,
.tt_enable_dst = false,
.tt_pass = TCPSIG_CONNREFUSED
}, {
.tt_desc = "IPv4 IBSA with MD5 enabled on dst only",
.tt_domain = PF_INET,
.tt_port = PORT_IBSA,
.tt_enable_src = false,
.tt_enable_dst = true,
.tt_pass = TCPSIG_SENDRECV
}, {
.tt_desc = "IPv6 IBSA with MD5 enabled on both sides",
.tt_domain = PF_INET6,
.tt_port = PORT_IBSA,
.tt_enable_src = true,
.tt_enable_dst = true,
.tt_pass = TCPSIG_CONNREFUSED
}, {
.tt_desc = "IPv6 IBSA with MD5 disabled on both sides",
.tt_domain = PF_INET6,
.tt_port = PORT_IBSA,
.tt_enable_src = false,
.tt_enable_dst = false,
.tt_pass = TCPSIG_SENDRECV
}, {
.tt_desc = "IPv6 IBSA with MD5 enabled on src only",
.tt_domain = PF_INET6,
.tt_port = PORT_IBSA,
.tt_enable_src = true,
.tt_enable_dst = false,
.tt_pass = TCPSIG_CONNREFUSED
}, {
.tt_desc = "IPv6 IBSA with MD5 enabled on dst only",
.tt_domain = PF_INET6,
.tt_port = PORT_IBSA,
.tt_enable_src = false,
.tt_enable_dst = true,
.tt_pass = TCPSIG_SENDRECV
}
};
static bool
tcpsig_bind_dest(const tcpsig_test_t *test, int sock,
struct sockaddr_storage *dst)
{
socklen_t len;
struct sockaddr_storage addr;
(void) memset(&addr, 0, sizeof (struct sockaddr_storage));
if (test->tt_domain == PF_INET) {
struct sockaddr_in *in = (struct sockaddr_in *)&addr;
in->sin_family = AF_INET;
in->sin_port = htons(test->tt_port);
if (inet_pton(AF_INET, "127.0.0.1", &in->sin_addr) != 1) {
warnx("TEST FAILED: %s: failed to convert 127.0.0.1 "
"to an IPv4 address", test->tt_desc);
return (false);
}
len = sizeof (struct sockaddr_in);
} else {
struct sockaddr_in6 *in6 = (struct sockaddr_in6 *)&addr;
in6->sin6_family = AF_INET6;
in6->sin6_port = htons(test->tt_port);
if (inet_pton(AF_INET6, "::1", &in6->sin6_addr) != 1) {
warnx("TEST FAILED: %s: failed to convert ::1 "
"to an IPv6 address", test->tt_desc);
return (false);
}
len = sizeof (struct sockaddr_in6);
}
if (bind(sock, (struct sockaddr *)&addr, len) != 0) {
warn("TEST FAILED: %s: failed to bind listen socket",
test->tt_desc);
return (false);
}
len = sizeof (struct sockaddr_storage);
if (getsockname(sock, (struct sockaddr *)dst, &len) != 0) {
warn("TEST FAILED: %s: failed to retrieve socket address ",
test->tt_desc);
return (false);
}
return (true);
}
static bool
tcpsig_connect(const tcpsig_test_t *test, int port, int src, int dst, int *cfd,
const struct sockaddr *addr)
{
struct timespec to = { .tv_nsec = sock_to };
int namelen = test->tt_domain == PF_INET ? sizeof (struct sockaddr_in) :
sizeof (struct sockaddr_in6);
int conn, opt;
unsigned int optlen;
port_event_t pe;
if (listen(dst, 5) != 0) {
warn("TEST FAILED: %s: failed to listen", test->tt_desc);
return (false);
}
if (connect(src, addr, namelen) != 0 && errno != EINPROGRESS) {
if (errno == ECONNREFUSED &&
test->tt_pass == TCPSIG_CONNREFUSED) {
(void) printf("TEST PASSED: %s: connection refused\n",
test->tt_desc);
return (true);
}
warn("TEST FAILED: %s: failed to connect", test->tt_desc);
return (false);
}
if (port_associate(port, PORT_SOURCE_FD, src, POLLOUT, NULL) != 0) {
err(EXIT_FAILURE, "INTERNAL TEST FAILURE: %s: could not port "
"associate to watch connect", test->tt_desc);
}
if (port_get(port, &pe, &to) != 0) {
if (test->tt_pass == TCPSIG_NOCONNECT) {
(void) printf(
"TEST PASSED: %s: correctly failed to connect\n",
test->tt_desc);
return (true);
} else {
warn("TEST FAILED: %s: timed out waiting to connect",
test->tt_desc);
return (false);
}
}
if ((pe.portev_events & POLLOUT) == 0) {
warnx("TEST FAILED: %s: connect port event doesn't contain "
"POLLOUT, found 0x%x", test->tt_desc, pe.portev_events);
return (false);
}
if (port_associate(port, PORT_SOURCE_FD, dst, POLLIN, NULL) != 0) {
err(EXIT_FAILURE, "INTERNAL TEST FAILURE: %s: could not port "
"associate to watch accept", test->tt_desc);
}
if (port_get(port, &pe, &to) != 0) {
warn("TEST FAILED: %s: timed out waiting to accept",
test->tt_desc);
return (false);
}
if ((pe.portev_events & POLLIN) == 0) {
warnx("TEST FAILED: %s: accept port event doesn't contain "
"POLLIN, found 0x%x", test->tt_desc, pe.portev_events);
return (false);
}
conn = accept4(dst, NULL, NULL, SOCK_NONBLOCK);
if (conn < 0) {
warn("TEST FAILED: %s: failed to get client connection",
test->tt_desc);
return (false);
}
optlen = sizeof (opt);
if (getsockopt(conn, IPPROTO_TCP, TCP_MD5SIG, &opt, &optlen) != 0) {
warn("TEST FAILED: %s: failed to retrieve accepted socket "
"TCP_MD5SIG option", test->tt_desc);
return (false);
}
if (optlen != sizeof (opt)) {
warn("TEST FAILED: %s: TCP_MD5SIG option has wrong length %d "
"(expected %ld).", test->tt_desc, optlen, sizeof (opt));
return (false);
}
if (test->tt_enable_dst && !test->tt_enable_src &&
test->tt_pass == TCPSIG_SENDRECV && opt != 0) {
warnx("TEST FAILED: %s: TCP_MD5SIG is set and should not be",
test->tt_desc);
return (false);
} else if (test->tt_enable_src && opt == 0) {
warnx("TEST FAILED: %s: TCP_MD5SIG is not set and should be",
test->tt_desc);
return (false);
}
if (test->tt_pass != TCPSIG_SENDRECV &&
test->tt_pass != TCPSIG_NODATA) {
warnx("TEST FAILED: %s: expected connect to fail, but passed",
test->tt_desc);
return (false);
}
*cfd = conn;
return (true);
}
static bool
tcpsig_sendrecv(const tcpsig_test_t *test, int port, int src, int dst)
{
struct timespec to = { .tv_nsec = sock_to };
port_event_t pe;
uint32_t data;
ssize_t sret;
if (send(src, &msgdata, sizeof (msgdata), MSG_NOSIGNAL) !=
sizeof (msgdata)) {
warn("TEST FAILED: %s: failed to write message to socket",
test->tt_desc);
}
if (port_associate(port, PORT_SOURCE_FD, dst, POLLIN, NULL) != 0) {
err(EXIT_FAILURE, "INTERNAL TEST FAILURE: %s: could not port "
"associate to watch recv", test->tt_desc);
}
if (port_get(port, &pe, &to) != 0) {
if (test->tt_pass == TCPSIG_NODATA) {
(void) printf("TEST PASSED: %s: timed out waiting "
"for data\n", test->tt_desc);
return (true);
} else {
warn("TEST FAILED: %s: timed out waiting to recv",
test->tt_desc);
return (false);
}
}
if ((pe.portev_events & POLLIN) == 0) {
warnx("TEST FAILED: %s: receive port event doesn't contain "
"POLLIN, found 0x%x", test->tt_desc, pe.portev_events);
return (false);
}
sret = recv(dst, &data, sizeof (data), MSG_DONTWAIT);
if (sret != (ssize_t)sizeof (data)) {
warnx("TEST FAILED: %s: failed to receive data: %zx",
test->tt_desc, sret);
return (false);
}
if (test->tt_pass != TCPSIG_SENDRECV) {
warnx("TEST FAILED: %s: found data, despite expecting not to",
test->tt_desc);
return (false);
}
if (data != msgdata) {
warnx("TEST FAILED: %s: data mismatch: expected 0x%x, found "
"0x%x", test->tt_desc, msgdata, data);
return (false);
}
(void) printf("TEST PASSED: %s: successfully received data\n",
test->tt_desc);
return (true);
}
static bool
tcpsig_test_one(const tcpsig_test_t *test)
{
int src = -1, dst = -1, cfd = -1, port = -1, tdst;
int x;
bool ret = true;
struct sockaddr_storage dst_addr;
if ((port = port_create()) < 0)
err(EXIT_FAILURE, "TEST FAILED: failed to create event port");
src = socket(test->tt_domain, SOCK_STREAM | SOCK_NONBLOCK, 0);
if (src < 0) {
warn("TEST FAILED: %s: failed to create source socket",
test->tt_desc);
ret = false;
goto cleanup;
}
x = test->tt_enable_src ? 1 : 0;
if (setsockopt(src, IPPROTO_TCP, TCP_MD5SIG, &x, sizeof (x)) != 0) {
warn("TEST FAILED: %s: failed to configure src MD5SIG option",
test->tt_desc);
ret = false;
goto cleanup;
}
dst = socket(test->tt_domain, SOCK_STREAM | SOCK_NONBLOCK, 0);
if (dst < 0) {
warn("TEST FAILED: %s: failed to create destination socket",
test->tt_desc);
ret = false;
goto cleanup;
}
x = test->tt_enable_dst ? 1 : 0;
if (setsockopt(dst, IPPROTO_TCP, TCP_MD5SIG, &x, sizeof (x)) != 0) {
warn("TEST FAILED: %s: failed to configure dst MD5SIG option",
test->tt_desc);
ret = false;
goto cleanup;
}
if (!tcpsig_bind_dest(test, dst, &dst_addr)) {
ret = false;
goto cleanup;
}
if (!tcpsig_connect(test, port, src, dst, &cfd,
(struct sockaddr *)&dst_addr)) {
ret = false;
goto cleanup;
}
if (test->tt_pass != TCPSIG_SENDRECV && test->tt_pass != TCPSIG_NODATA)
goto cleanup;
tdst = cfd;
if (!tcpsig_sendrecv(test, port, src, tdst)) {
ret = false;
goto cleanup;
}
cleanup:
if (port > -1)
(void) close(port);
if (src > -1) {
(void) shutdown(src, SHUT_RDWR);
(void) close(src);
}
if (dst > -1)
(void) close(dst);
if (cfd > -1)
(void) close(cfd);
return (ret);
}
int
main(int argc, char **argv)
{
size_t max = ARRAY_SIZE(tcpsig_tests) - 1;
int ret = EXIT_SUCCESS;
if (argc == 2) {
const char *errstr;
size_t idx;
idx = (size_t)strtonumx(argv[1], 0, max, &errstr, 0);
if (errstr != NULL) {
(void) fprintf(stderr, "Syntax: %s [test number]\n",
getprogname());
(void) fprintf(stderr,
"Test number is in the range [0-%u]\n", max);
(void) fprintf(stderr, "\nAvailable tests:\n");
for (size_t i = 0; i <= max; i++) {
(void) fprintf(stderr, " %5d - %s\n", i,
tcpsig_tests[i].tt_desc);
}
return (EXIT_FAILURE);
}
if (!tcpsig_test_one(&tcpsig_tests[idx]))
ret = EXIT_FAILURE;
} else {
for (size_t i = 0; i <= max; i++) {
if (!tcpsig_test_one(&tcpsig_tests[i]))
ret = EXIT_FAILURE;
}
if (ret == EXIT_SUCCESS)
(void) printf("All tests passed successfully\n");
}
return (ret);
}