diff options
author | rwatson <rwatson@FreeBSD.org> | 2005-07-22 19:36:29 +0000 |
---|---|---|
committer | rwatson <rwatson@FreeBSD.org> | 2005-07-22 19:36:29 +0000 |
commit | c45e23d0396e341c2ea30fff66b565186107df40 (patch) | |
tree | cc93e8861bf92564350afc99a15e6f358ec83482 /tools/regression | |
parent | feb635227b5d85a9fac6b1231c58366d8fcbb601 (diff) | |
download | FreeBSD-src-c45e23d0396e341c2ea30fff66b565186107df40.zip FreeBSD-src-c45e23d0396e341c2ea30fff66b565186107df40.tar.gz |
Add a simple multicast socket regression test set:
- Test that the basic socket options have the right defaults, that we can
change them, read them back, etc.
- Add and remove some multicast addresses.
- Send a loopback multicast address and make sure it arrives intact.
There's more that could be done here, but it's a start.
MFC after: 3 days
Diffstat (limited to 'tools/regression')
-rw-r--r-- | tools/regression/netinet/msocket/Makefile | 7 | ||||
-rw-r--r-- | tools/regression/netinet/msocket/msocket.c | 464 |
2 files changed, 471 insertions, 0 deletions
diff --git a/tools/regression/netinet/msocket/Makefile b/tools/regression/netinet/msocket/Makefile new file mode 100644 index 0000000..885afcf --- /dev/null +++ b/tools/regression/netinet/msocket/Makefile @@ -0,0 +1,7 @@ +# $FreeBSD$ + +PROG= msocket +NO_MAN= +CFLAGS+= -Wall + +.include <bsd.prog.mk> diff --git a/tools/regression/netinet/msocket/msocket.c b/tools/regression/netinet/msocket/msocket.c new file mode 100644 index 0000000..534e9f4 --- /dev/null +++ b/tools/regression/netinet/msocket/msocket.c @@ -0,0 +1,464 @@ +/*- + * Copyright (c) 2005 Robert N. M. Watson + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#include <sys/types.h> +#include <sys/socket.h> + +#include <netinet/in.h> + +#include <arpa/inet.h> + +#include <err.h> +#include <errno.h> +#include <fcntl.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> + +/* + * Regression test for multicast sockets and options: + * + * - Check the defaults for ttl, if, and loopback. Make sure they can be set + * and then read. + * + * - Check that adding and removing multicast addresses seems to work. + * + * - Send a test message over loop back multicast and make sure it arrives. + * + * NB: + * + * Would be nice to use BPF or if_tap to actually check packet contents and + * layout, make sure that the ttl is set right, etc. + * + * Would be nice if attempts to use multicast options on TCP sockets returned + * an error, as the docs suggest it might. + */ + +#ifdef WARN_TCP +#define WARN_SUCCESS 0x00000001 /* Set for TCP to warn on success. */ +#else +#define WARN_SUCCESS 0x00000000 +#endif + +/* + * Multicast test address, picked arbitrarily. Will be used with the + * loopback interface. + */ +#define TEST_MADDR "224.100.100.100" + +/* + * Test that a given IP socket option (optname) has a default value of + * 'defaultv', that we can set it to 'modifiedv', and use 'fakev' as a dummy + * value that shouldn't be returned at any point during the tests. Perform + * the tests on the raw socket, tcp socket, and upd socket passed. + * 'optstring' is used in printing warnings and errors as needed. + */ +static void +test_u_char(int optname, const char *optstring, u_char defaultv, + u_char modifiedv, u_char fakev, const char *socktype, int sock, + int flags) +{ + socklen_t socklen; + u_char uc; + int ret; + + /* + * Check that we read back the expected default. + */ + uc = fakev; + socklen = sizeof(uc); + + ret = getsockopt(sock, IPPROTO_IP, optname, &uc, &socklen); + if (ret < 0) + err(-1, "FAIL: getsockopt(%s, IPPROTO_IP, %s)", + socktype, optstring); + if (ret == 0 && (flags & WARN_SUCCESS)) + warnx("WARN: getsockopt(%s, IPPROTO_IP, %s) returned 0", + socktype, optstring); + if (uc != defaultv) + errx(-1, "FAIL: getsockopt(%s, IPPROTO_IP, %s) default is " + "%d not %d", socktype, optstring, uc, defaultv); + + /* + * Set to a modifiedv value, read it back and make sure it got there. + */ + uc = modifiedv; + ret = setsockopt(sock, IPPROTO_IP, optname, &uc, sizeof(uc)); + if (ret == -1) + err(-1, "FAIL: setsockopt(%s, IPPROTO_IP, %s)", + socktype, optstring); + if (ret == 0 && (flags & WARN_SUCCESS)) + warnx("WARN: setsockopt(%s, IPPROTO_IP, %s) returned 0", + socktype, optstring); + + uc = fakev; + socklen = sizeof(uc); + ret = getsockopt(sock, IPPROTO_IP, optname, &uc, &socklen); + if (ret < 0) + err(-1, "FAIL: getsockopt(%s, IPPROTO_IP, %s)", + socktype, optstring); + if (ret == 0 && (flags & WARN_SUCCESS)) + warnx("WARN: getsockopt(%s, IPPROTO_IP, %s) returned 0", + socktype, optstring); + if (uc != modifiedv) + errx(-1, "FAIL: getsockopt(%s, IPPROTO_IP, %s) set value is " + "%d not %d", socktype, optstring, uc, modifiedv); +} + +/* + * test_in_addr() is like test_u_char(), only it runs on a struct in_addr + * (surprise). + */ +static void +test_in_addr(int optname, const char *optstring, struct in_addr defaultv, + struct in_addr modifiedv, struct in_addr fakev, const char *socktype, + int sock, int flags) +{ + socklen_t socklen; + struct in_addr ia; + int ret; + + /* + * Check that we read back the expected default. + */ + ia = fakev; + socklen = sizeof(ia); + + ret = getsockopt(sock, IPPROTO_IP, optname, &ia, &socklen); + if (ret < 0) + err(-1, "FAIL: getsockopt(%s, IPPROTO_IP, %s)", + socktype, optstring); + if (ret == 0 && (flags & WARN_SUCCESS)) + warnx("WARN: getsockopt(%s, IPPROTO_IP, %s) returned 0", + socktype, optstring); + if (memcmp(&ia, &defaultv, sizeof(struct in_addr))) + errx(-1, "FAIL: getsockopt(%s, IPPROTO_IP, %s) default is " + "%s not %s", socktype, optstring, inet_ntoa(ia), + inet_ntoa(defaultv)); + + /* + * Set to a modifiedv value, read it back and make sure it got there. + */ + ia = modifiedv; + ret = setsockopt(sock, IPPROTO_IP, optname, &ia, sizeof(ia)); + if (ret == -1) + err(-1, "FAIL: setsockopt(%s, IPPROTO_IP, %s)", + socktype, optstring); + if (ret == 0 && (flags & WARN_SUCCESS)) + warnx("WARN: setsockopt(%s, IPPROTO_IP, %s) returned 0", + socktype, optstring); + + ia = fakev; + socklen = sizeof(ia); + ret = getsockopt(sock, IPPROTO_IP, optname, &ia, &socklen); + if (ret < 0) + err(-1, "FAIL: getsockopt(%s, IPPROTO_IP, %s)", + socktype, optstring); + if (ret == 0 && (flags & WARN_SUCCESS)) + warnx("WARN: getsockopt(%s, IPPROTO_IP, %s) returned 0", + socktype, optstring); + if (memcmp(&ia, &modifiedv, sizeof(struct in_addr))) + errx(-1, "FAIL: getsockopt(%s, IPPROTO_IP, %s) set value is " + "%s not %s", socktype, optstring, inet_ntoa(ia), + inet_ntoa(modifiedv)); +} + +static void +test_ttl(int raw_sock, int tcp_sock, int udp_sock) +{ + + test_u_char(IP_MULTICAST_TTL, "IP_MULTICAST_TTL", 1, 2, 243, + "raw_sock", raw_sock, 0); + test_u_char(IP_MULTICAST_TTL, "IP_MULTICAST_TTL", 1, 2, 243, + "tcp_sock", tcp_sock, WARN_SUCCESS); + test_u_char(IP_MULTICAST_TTL, "IP_MULTICAST_TTL", 1, 2, 243, + "udp_sock", udp_sock, 0); +} + +static void +test_loop(int raw_sock, int tcp_sock, int udp_sock) +{ + + test_u_char(IP_MULTICAST_LOOP, "IP_MULTICAST_LOOP", 1, 0, 243, + "raw_sock", raw_sock, 0); + test_u_char(IP_MULTICAST_LOOP, "IP_MULTICAST_LOOP", 1, 0, 243, + "tcp_sock", tcp_sock, WARN_SUCCESS); + test_u_char(IP_MULTICAST_LOOP, "IP_MULTICAST_LOOP", 1, 0, 243, + "udp_sock", udp_sock, 0); +} + +static void +test_if(int raw_sock, int tcp_sock, int udp_sock) +{ + struct in_addr defaultv, modifiedv, fakev; + + defaultv.s_addr = inet_addr("0.0.0.0"); + + /* Should be valid on all hosts. */ + modifiedv.s_addr = inet_addr("127.0.0.1"); + + /* Should not happen. */ + fakev.s_addr = inet_addr("255.255.255.255"); + + test_in_addr(IP_MULTICAST_IF, "IP_MULTICAST_IF", defaultv, modifiedv, + fakev, "raw_sock", raw_sock, 0); + test_in_addr(IP_MULTICAST_IF, "IP_MULTICAST_IF", defaultv, modifiedv, + fakev, "tcp_sock", tcp_sock, WARN_SUCCESS); + test_in_addr(IP_MULTICAST_IF, "IP_MULTICAST_IF", defaultv, modifiedv, + fakev, "udp_sock", udp_sock, 0); +} + +/* + * Add a multicast address to an interface. Warn if appropriate. No query + * interface so can't check if it's there directly; instead we have to try + * to add it a second time and make sure we get back EADDRINUSE. + */ +static void +test_add_multi(int sock, const char *socktype, struct ip_mreq imr, + int flags) +{ + char buf[128]; + int ret; + + ret = setsockopt(sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, &imr, + sizeof(imr)); + if (ret < 0) { + strlcpy(buf, inet_ntoa(imr.imr_multiaddr), 128); + err(-1, "FAIL: setsockopt(%s, IPPROTO_IP, IP_ADD_MEMBERSHIP " + "%s, %s)", socktype, buf, inet_ntoa(imr.imr_interface)); + } + if (ret == 0 && (flags & WARN_SUCCESS)) { + strlcpy(buf, inet_ntoa(imr.imr_multiaddr), 128); + warnx("WARN: setsockopt(%s, IPPROTO_IP, IP_ADD_MEMBERSHIP " + "%s, %s) returned 0", socktype, buf, + inet_ntoa(imr.imr_interface)); + } + + /* Try to add a second time to make sure it got there. */ + ret = setsockopt(sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, &imr, + sizeof(imr)); + if (ret == 0) { + strlcpy(buf, inet_ntoa(imr.imr_multiaddr), 128); + err(-1, "FAIL: setsockopt(%s, IPPROTO_IP, IP_ADD_MEMBERSHIP " + "%s, %s) dup returned 0", socktype, buf, + inet_ntoa(imr.imr_interface)); + } + if (ret < 0 && errno != EADDRINUSE) { + strlcpy(buf, inet_ntoa(imr.imr_multiaddr), 128); + err(-1, "FAIL: setsockopt(%s, IPPROTO_IP, IP_ADD_MEMBERSHIP " + "%s, %s)", socktype, buf, inet_ntoa(imr.imr_interface)); + } +} + +/* + * Drop a multicast address from an interface. Warn if appropriate. No + * query interface so can't check if it's gone directly; instead we have to + * try to drop it a second time and make sure we get back EADDRNOTAVAIL. + */ +static void +test_drop_multi(int sock, const char *socktype, struct ip_mreq imr, + int flags) +{ + char buf[128]; + int ret; + + ret = setsockopt(sock, IPPROTO_IP, IP_DROP_MEMBERSHIP, &imr, + sizeof(imr)); + if (ret < 0) { + strlcpy(buf, inet_ntoa(imr.imr_multiaddr), 128); + err(-1, "FAIL: setsockopt(%s, IPPROTO_IP, IP_DROP_MEMBERSHIP " + "%s, %s)", socktype, buf, inet_ntoa(imr.imr_interface)); + } + if (ret == 0 && (flags & WARN_SUCCESS)) { + strlcpy(buf, inet_ntoa(imr.imr_multiaddr), 128); + warnx("WARN: setsockopt(%s, IPPROTO_IP, IP_DROP_MEMBERSHIP " + "%s, %s) returned 0", socktype, buf, + inet_ntoa(imr.imr_interface)); + } + + /* Try a second time to make sure it's gone. */ + ret = setsockopt(sock, IPPROTO_IP, IP_DROP_MEMBERSHIP, &imr, + sizeof(imr)); + if (ret == 0) { + strlcpy(buf, inet_ntoa(imr.imr_multiaddr), 128); + err(-1, "FAIL: setsockopt(%s, IPPROTO_IP, IP_DROP_MEMBERSHIP " + "%s, %s) returned 0", socktype, buf, + inet_ntoa(imr.imr_interface)); + } + if (ret < 0 && errno != EADDRNOTAVAIL) { + strlcpy(buf, inet_ntoa(imr.imr_multiaddr), 128); + err(-1, "FAIL: setsockopt(%s, IPPROTO_IP, IP_DROP_MEMBERSHIP " + "%s, %s)", socktype, buf, inet_ntoa(imr.imr_interface)); + } +} + +/* + * Should really also test trying to add an invalid address, delete one + * that's not there, etc. + */ +static void +test_addr(int raw_sock, int tcp_sock, int udp_sock) +{ + struct ip_mreq imr; + + /* Arbitrary. */ + imr.imr_multiaddr.s_addr = inet_addr(TEST_MADDR); + + /* Localhost should be OK. */ + imr.imr_interface.s_addr = inet_addr("127.0.0.1"); + + test_add_multi(raw_sock, "raw_sock", imr, 0); + test_drop_multi(raw_sock, "raw_sock", imr, 0); + + test_add_multi(tcp_sock, "raw_sock", imr, WARN_SUCCESS); + test_drop_multi(tcp_sock, "raw_sock", imr, WARN_SUCCESS); + + test_add_multi(udp_sock, "raw_sock", imr, 0); + test_drop_multi(udp_sock, "raw_sock", imr, 0); +} + +/* + * Test an actual simple UDP message - send a single byte to an address we're + * subscribed to, and hope to get it back. We create a new UDP socket for + * this purpose because we will need to bind it. + */ +#define UDP_PORT 5012 +static void +test_udp(void) +{ + struct sockaddr_in sin; + struct ip_mreq imr; + struct in_addr if_addr; + char message; + ssize_t len; + int sock; + + sock = socket(PF_INET, SOCK_DGRAM, 0); + if (sock < 0) + err(-1, "FAIL: test_udp: socket(PF_INET, SOCK_DGRAM)"); + + if (fcntl(sock, F_SETFL, O_NONBLOCK) < 0) + err(-1, "FAIL: test_udp: fcntl(F_SETFL, O_NONBLOCK)"); + + bzero(&sin, sizeof(sin)); + sin.sin_len = sizeof(sin); + sin.sin_family = AF_INET; + sin.sin_port = htons(UDP_PORT); + sin.sin_addr.s_addr = inet_addr(TEST_MADDR); + + if (bind(sock, (struct sockaddr *)&sin, sizeof(sin)) < 0) + err(-1, "FAIL: test_udp: bind(udp_sock, 127.0.0.1:%d", + UDP_PORT); + + /* Arbitrary. */ + imr.imr_multiaddr.s_addr = inet_addr(TEST_MADDR); + + /* Localhost should be OK. */ + imr.imr_interface.s_addr = inet_addr("127.0.0.1"); + + /* + * Tell socket what interface to send on -- use localhost. + */ + if_addr.s_addr = inet_addr("127.0.0.1"); + if (setsockopt(sock, IPPROTO_IP, IP_MULTICAST_IF, &if_addr, + sizeof(if_addr)) < 0) + err(-1, "test_udp: setsockopt(IPPROTO_IP, IP_MULTICAST_IF)"); + + test_add_multi(sock, "udp_sock", imr, 0); + + bzero(&sin, sizeof(sin)); + sin.sin_len = sizeof(sin); + sin.sin_family = AF_INET; + sin.sin_port = htons(UDP_PORT); + sin.sin_addr.s_addr = inet_addr(TEST_MADDR); + + message = 'A'; + len = sizeof(message); + len = sendto(sock, &message, len, 0, (struct sockaddr *)&sin, + sizeof(sin)); + if (len < 0) + err(-1, "test_udp: sendto"); + + if (len != sizeof(message)) + errx(-1, "test_udp: sendto: expected to send %d, instead %d", + sizeof(message), len); + + message = 'B'; + len = sizeof(sin); + len = recvfrom(sock, &message, sizeof(message), 0, + (struct sockaddr *)&sin, &len); + if (len < 0) + err(-1, "test_udp: recvfrom"); + + if (len != sizeof(message)) + errx(-1, "test_udp: recvfrom: len %d != message len %d", + len, sizeof(message)); + + if (message != 'A') + errx(-1, "test_udp: recvfrom: expected 'A', got '%c'", + message); + + test_drop_multi(sock, "udp_sock", imr, 0); + + close(sock); +} +#undef UDP_PORT + +int +main(int argc, char *argv[]) +{ + int raw_sock, tcp_sock, udp_sock; + + if (geteuid() != 0) + errx(-1, "FAIL: root privilege required"); + + raw_sock = socket(PF_INET, SOCK_RAW, 0); + if (raw_sock == -1) + err(-1, "FAIL: socket(PF_INET, SOCK_RAW)"); + + tcp_sock = socket(PF_INET, SOCK_STREAM, 0); + if (raw_sock == -1) + err(-1, "FAIL: socket(PF_INET, SOCK_STREAM)"); + + udp_sock = socket(PF_INET, SOCK_DGRAM, 0); + if (raw_sock == -1) + err(-1, "FAIL: socket(PF_INET, SOCK_DGRAM)"); + + test_ttl(raw_sock, tcp_sock, udp_sock); + test_loop(raw_sock, tcp_sock, udp_sock); + test_if(raw_sock, tcp_sock, udp_sock); + test_addr(raw_sock, tcp_sock, udp_sock); + + close(udp_sock); + close(tcp_sock); + close(raw_sock); + + test_udp(); + + return (0); +} |