#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>
#ifdef WARN_TCP
#define WARN_SUCCESS 0x00000001
#else
#define WARN_SUCCESS 0x00000000
#endif
#define TEST_MADDR "224.100.100.100"
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;
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);
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);
}
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;
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));
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");
modifiedv.s_addr = inet_addr("127.0.0.1");
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);
}
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));
}
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));
}
}
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));
}
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));
}
}
static void
test_addr(int raw_sock, int tcp_sock, int udp_sock)
{
struct ip_mreq imr;
imr.imr_multiaddr.s_addr = inet_addr(TEST_MADDR);
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);
}
#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);
imr.imr_multiaddr.s_addr = inet_addr(TEST_MADDR);
imr.imr_interface.s_addr = inet_addr("127.0.0.1");
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);
}