root/tools/regression/netinet/ipsockopt/ipsockopt.c
/*-
 * Copyright (c) 2004 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.
 */

#include <sys/types.h>
#include <sys/socket.h>

#include <netinet/in.h>
#include <netinet/in_systm.h>
#include <netinet/ip.h>
#include <arpa/inet.h>

#include <err.h>
#include <errno.h>
#include <getopt.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

static int dorandom = 0;
static int nmcastgroups = IP_MAX_MEMBERSHIPS;
static int verbose = 0;

/*
 * The test tool exercises IP-level socket options by interrogating the
 * getsockopt()/setsockopt() APIs.  It does not currently test that the
 * intended semantics of each option are implemented (i.e., that setting IP
 * options on the socket results in packets with the desired IP options in
 * it).
 */

/*
 * get_socket() is a wrapper function that returns a socket of the specified
 * type, and created with or without restored root privilege (if running
 * with a real uid of root and an effective uid of some other user).  This
 * us to test whether the same rights are granted using a socket with a
 * privileged cached credential vs. a socket with a regular credential.
 */
#define PRIV_ASIS       0
#define PRIV_GETROOT    1
static int
get_socket_unpriv(int type)
{

        return (socket(PF_INET, type, 0));
}

static int
get_socket_priv(int type)
{
        uid_t olduid;
        int sock;

        if (getuid() != 0)
                errx(-1, "get_sock_priv: running without real uid 0");
        
        olduid = geteuid();
        if (seteuid(0) < 0)
                err(-1, "get_sock_priv: seteuid(0)");

        sock = socket(PF_INET, type, 0);

        if (seteuid(olduid) < 0)
                err(-1, "get_sock_priv: seteuid(%d)", olduid);

        return (sock);
}

static int
get_socket(int type, int priv)
{

        if (priv)
                return (get_socket_priv(type));
        else
                return (get_socket_unpriv(type));
}

/*
 * Exercise the IP_OPTIONS socket option.  Confirm the following properties:
 *
 * - That there is no initial set of options (length returned is 0).
 * - That if we set a specific set of options, we can read it back.
 * - That if we then reset the options, they go away.
 *
 * Use a UDP socket for this.
 */
static void
test_ip_options(int sock, const char *socktypename)
{
        u_int32_t new_options, test_options[2];
        socklen_t len;

        /*
         * Start off by confirming the default IP options on a socket are to
         * have no options set.
         */
        len = sizeof(test_options);
        if (getsockopt(sock, IPPROTO_IP, IP_OPTIONS, test_options, &len) < 0)
                err(-1, "test_ip_options(%s): initial getsockopt()",
                    socktypename);

        if (len != 0)
                errx(-1, "test_ip_options(%s): initial getsockopt() returned "
                    "%d bytes", socktypename, len);

#define TEST_MAGIC      0xc34e4212
#define NEW_OPTIONS     htonl(IPOPT_EOL | (IPOPT_NOP << 8) | (IPOPT_NOP << 16) \
                         | (IPOPT_NOP << 24))

        /*
         * Write some new options into the socket.
         */
        new_options = NEW_OPTIONS;
        if (setsockopt(sock, IPPROTO_IP, IP_OPTIONS, &new_options,
            sizeof(new_options)) < 0)
                err(-1, "test_ip_options(%s): setsockopt(NOP|NOP|NOP|EOL)",
                    socktypename);

        /*
         * Store some random cruft in a local variable and retrieve the
         * options to make sure they set.  Note that we pass in an array
         * of u_int32_t's so that if whatever ended up in the option was
         * larger than what we put in, we find out about it here.
         */
        test_options[0] = TEST_MAGIC;
        test_options[1] = TEST_MAGIC;
        len = sizeof(test_options);
        if (getsockopt(sock, IPPROTO_IP, IP_OPTIONS, test_options, &len) < 0)
                err(-1, "test_ip_options(%s): getsockopt() after set",
                    socktypename);

        /*
         * Getting the right amount back is important.
         */
        if (len != sizeof(new_options))
                errx(-1, "test_ip_options(%s): getsockopt() after set "
                    "returned %d bytes of data", socktypename, len);

        /*
         * One possible failure mode is that the call succeeds but neglects to
         * copy out the data.
         */
        if (test_options[0] == TEST_MAGIC)
                errx(-1, "test_ip_options(%s): getsockopt() after set didn't "
                    "return data", socktypename);

        /*
         * Make sure we get back what we wrote on.
         */
        if (new_options != test_options[0])
                errx(-1, "test_ip_options(%s): getsockopt() after set "
                    "returned wrong options (%08x, %08x)", socktypename,
                    new_options, test_options[0]);

        /*
         * Now we reset the value to make sure clearing works.
         */
        if (setsockopt(sock, IPPROTO_IP, IP_OPTIONS, NULL, 0) < 0)
                err(-1, "test_ip_options(%s): setsockopt() to reset",
                    socktypename);

        /*
         * Make sure it was really cleared.
         */
        test_options[0] = TEST_MAGIC;
        test_options[1] = TEST_MAGIC;
        len = sizeof(test_options);
        if (getsockopt(sock, IPPROTO_IP, IP_OPTIONS, test_options, &len) < 0)
                err(-1, "test_ip_options(%s): getsockopt() after reset",
                    socktypename);

        if (len != 0)
                errx(-1, "test_ip_options(%s): getsockopt() after reset "
                    "returned %d bytes", socktypename, len);
}

/*
 * This test checks the behavior of the IP_HDRINCL socket option, which
 * allows users with privilege to specify the full header on an IP raw
 * socket.  We test that the option can only be used with raw IP sockets, not
 * with UDP or TCP sockets.  We also confirm that the raw socket is only
 * available to a privileged user (subject to the UID when called).  We
 * confirm that it defaults to off
 *
 * Unlike other tests, doesn't use caller-provided socket.  Probably should
 * be fixed.
 */
static void
test_ip_hdrincl(void)
{
        int flag[2], sock;
        socklen_t len;

        /*
         * Try to receive or set the IP_HDRINCL flag on a TCP socket.
         */
        sock = socket(PF_INET, SOCK_STREAM, 0);
        if (sock == -1)
                err(-1, "test_ip_hdrincl(): socket(SOCK_STREAM)");

        flag[0] = -1;
        len = sizeof(flag[0]);
        if (getsockopt(sock, IPPROTO_IP, IP_HDRINCL, flag, &len) == 0)
                err(-1, "test_ip_hdrincl(): initial getsockopt(IP_HDRINCL)");

        if (errno != ENOPROTOOPT)
                errx(-1, "test_ip_hdrincl(): initial getsockopt(IP_HDRINC) "
                    "returned %d (%s) not ENOPROTOOPT", errno,
                    strerror(errno));

        flag[0] = 1;
        if (setsockopt(sock, IPPROTO_IP, IP_HDRINCL, flag, sizeof(flag[0]))
            == 0)
                err(-1,"test_ip_hdrincl(): setsockopt(IP_HDRINCL) on TCP "
                    "succeeded\n");

        if (errno != ENOPROTOOPT)
                errx(-1, "test_ip_hdrincl(): setsockopt(IP_HDRINCL) on TCP "
                    "returned %d (%s) not ENOPROTOOPT\n", errno,
                    strerror(errno));

        close(sock);

        /*
         * Try to receive or set the IP_HDRINCL flag on a UDP socket.
         */
        sock = socket(PF_INET, SOCK_DGRAM, 0);
        if (sock == -1)
                err(-1, "test_ip_hdrincl(): socket(SOCK_DGRAM");

        flag[0] = -1;
        len = sizeof(flag[0]);
        if (getsockopt(sock, IPPROTO_IP, IP_HDRINCL, flag, &len) == 0)
                err(-1, "test_ip_hdrincl(): getsockopt(IP_HDRINCL) on UDP "
                    "succeeded\n");

        if (errno != ENOPROTOOPT)
                errx(-1, "test_ip_hdrincl(): getsockopt(IP_HDRINCL) on UDP "
                    "returned %d (%s) not ENOPROTOOPT\n", errno,
                    strerror(errno));

        if (setsockopt(sock, IPPROTO_IP, IP_HDRINCL, flag, sizeof(flag[0]))
            == 0)
                err(-1, "test_ip_hdrincl(): setsockopt(IP_HDRINCL) on UDP "
                    "succeeded\n");

        if (errno != ENOPROTOOPT)
                errx(-1, "test_ip_hdrincl(): setsockopt(IP_HDRINCL) on UDP "
                    "returned %d (%s) not ENOPROTOOPT\n", errno,
                    strerror(errno));

        close(sock);

        /*
         * Now try on a raw socket.  Access ontrol should prevent non-root
         * users from creating the raw socket, so check that here based on
         * geteuid().  If we're non-root, we just return assuming the socket
         * create fails since the remainder of the tests apply only on a raw
         * socket.
         */
        sock = socket(PF_INET, SOCK_RAW, 0);
        if (geteuid() != 0) {
                if (sock != -1)
                        errx(-1, "test_ip_hdrincl: created raw socket as "
                            "uid %d", geteuid());
                return;
        }
        if (sock == -1)
                err(-1, "test_ip_hdrincl(): socket(PF_INET, SOCK_RAW)");

        /*
         * Make sure the initial value of the flag is 0 (disabled).
         */
        flag[0] = -1;
        flag[1] = -1;
        len = sizeof(flag);
        if (getsockopt(sock, IPPROTO_IP, IP_HDRINCL, flag, &len) < 0)
                err(-1, "test_ip_hdrincl(): getsockopt(IP_HDRINCL) on raw "
                    "socket");

        if (len != sizeof(flag[0]))
                errx(-1, "test_ip_hdrincl(): %d bytes returned on "
                    "initial get\n", len);

        if (flag[0] != 0)
                errx(-1, "test_ip_hdrincl(): initial flag value of %d\n",
                    flag[0]);

        /*
         * Enable the IP_HDRINCL flag.
         */
        flag[0] = 1;
        if (setsockopt(sock, IPPROTO_IP, IP_HDRINCL, flag, sizeof(flag[0]))
            < 0)
                err(-1, "test_ip_hdrincl(): setsockopt(IP_HDRINCL, 1)");

        /*
         * Check that the IP_HDRINCL flag was set.
         */
        flag[0] = -1;
        flag[1] = -1;
        len = sizeof(flag);
        if (getsockopt(sock, IPPROTO_IP, IP_HDRINCL, flag, &len) < 0)
                err(-1, "test_ip_hdrincl(): getsockopt(IP_HDRINCL) after "
                    "set");

        if (flag[0] == 0)
                errx(-1, "test_ip_hdrincl(): getsockopt(IP_HDRINCL) "
                    "after set had flag of %d\n", flag[0]);

#define HISTORICAL_INP_HDRINCL  8
        if (flag[0] != HISTORICAL_INP_HDRINCL)
                warnx("test_ip_hdrincl(): WARNING: getsockopt(IP_H"
                    "DRINCL) after set had non-historical value of %d\n",
                    flag[0]);

        /*
         * Reset the IP_HDRINCL flag to 0.
         */
        flag[0] = 0;
        if (setsockopt(sock, IPPROTO_IP, IP_HDRINCL, flag, sizeof(flag[0]))
            < 0)
                err(-1, "test_ip_hdrincl(): setsockopt(IP_HDRINCL, 0)");

        /*
         * Check that the IP_HDRINCL flag was reset to 0.
         */
        flag[0] = -1;
        flag[1] = -1;
        len = sizeof(flag);
        if (getsockopt(sock, IPPROTO_IP, IP_HDRINCL, flag, &len) < 0)
                err(-1, "test_ip_hdrincl(): getsockopt(IP_HDRINCL) after "
                    "reset");

        if (flag[0] != 0)
                errx(-1, "test_ip_hdrincl(): getsockopt(IP_HDRINCL) "
                    "after set had flag of %d\n", flag[0]);

        close(sock);
}

/*
 * As with other non-int or larger sized socket options, the IP_TOS and
 * IP_TTL fields in kernel is stored as an 8-bit value, reflecting the IP
 * header fields, but useful I/O to the field occurs using 32-bit integers.
 * The FreeBSD kernel will permit writes from variables at least an int in
 * size (and ignore additional bytes), and will permit a read to buffers 1
 * byte or larger (but depending on endianness, may truncate out useful
 * values if the caller provides less room).
 *
 * Given the limitations of the API, use a UDP socket to confirm that the
 * following are true:
 *
 * - We can read the IP_TOS/IP_TTL options.
 * - The initial value of the TOS option is 0, TTL is 64.
 * - That if we provide more than 32 bits of storage, we get back only 32
 *   bits of data.
 * - When we set it to a non-zero value expressible with a u_char, we can
 *   read that value back.
 * - When we reset it back to zero, we can read it as 0.
 * - When we set it to a value >255, the value is truncated to something less
 *   than 255.
 */
static void
test_ip_uchar(int sock, const char *socktypename, int option,
    const char *optionname, int initial)
{
        int val[2];
        socklen_t len;

        /*
         * Check that the initial value is 0, and that the size is one
         * u_char;
         */
        val[0] = -1;
        val[1] = -1;
        len = sizeof(val);
        if (getsockopt(sock, IPPROTO_IP, option, val, &len) < 0)
                err(-1, "test_ip_uchar(%s, %s): initial getsockopt()",
                    socktypename, optionname);

        if (len != sizeof(val[0]))
                errx(-1, "test_ip_uchar(%s, %s): initial getsockopt() "
                    "returned %d bytes", socktypename, optionname, len);

        if (val[0] == -1)
                errx(-1, "test_ip_uchar(%s, %s): initial getsockopt() didn't "
                    "return data", socktypename, optionname);

        if (val[0] != initial)
                errx(-1, "test_ip_uchar(%s, %s): initial getsockopt() "
                    "returned value of %d, not %d", socktypename, optionname,
                    val[0], initial);

        /*
         * Set the field to a valid value.
         */
        val[0] = 128;
        val[1] = -1;
        if (setsockopt(sock, IPPROTO_IP, option, val, sizeof(val[0])) < 0)
                err(-1, "test_ip_uchar(%s, %s): setsockopt(128)",
                    socktypename, optionname);

        /*
         * Check that when we read back the field, we get the same value.
         */
        val[0] = -1;
        val[1] = -1;
        len = sizeof(val);
        if (getsockopt(sock, IPPROTO_IP, option, val, &len) < 0)
                err(-1, "test_ip_uchar(%s, %s): getsockopt() after set to "
                    "128", socktypename, optionname);

        if (len != sizeof(val[0]))
                errx(-1, "test_ip_uchar(%s, %s): getsockopt() after set to "
                    "128 returned %d bytes", socktypename, optionname, len);

        if (val[0] == -1)
                errx(-1, "test_ip_uchar(%s, %s): getsockopt() after set to "
                    "128 didn't return data", socktypename, optionname);

        if (val[0] != 128)
                errx(-1, "test_ip_uchar(%s, %s): getsockopt() after set to "
                    "128 returned %d", socktypename, optionname, val[0]);

        /*
         * Reset the value to 0, check that it was reset.
         */
        val[0] = 0;
        val[1] = 0;
        if (setsockopt(sock, IPPROTO_IP, option, val, sizeof(val[0])) < 0)
                err(-1, "test_ip_uchar(%s, %s): setsockopt() to reset from "
                    "128", socktypename, optionname);

        if (len != sizeof(val[0]))
                errx(-1, "test_ip_uchar(%s, %s): getsockopt() after reset "
                   "from 128 returned %d bytes", socktypename, optionname,
                    len);

        if (val[0] == -1)
                errx(-1, "test_ip_uchar(%s, %s): getsockopt() after reset "
                    "from 128 didn't return data", socktypename, optionname);

        if (val[0] != 0)
                errx(-1, "test_ip_uchar(%s, %s): getsockopt() after reset "
                    "from 128 returned %d", socktypename, optionname,
                    val[0]);

        /*
         * Set the value to something out of range and check that it comes
         * back truncated, or that we get EINVAL back.  Traditional u_char
         * IP socket options truncate, but newer ones (such as multicast
         * socket options) will return EINVAL.
         */
        val[0] = 32000;
        val[1] = -1;
        if (setsockopt(sock, IPPROTO_IP, option, val, sizeof(val[0])) < 0) {
                /*
                 * EINVAL is a fine outcome, no need to run the truncation
                 * tests.
                 */
                if (errno == EINVAL)
                        return;
                err(-1, "test_ip_uchar(%s, %s): getsockopt(32000)",
                    socktypename, optionname);
        }

        val[0] = -1;
        val[1] = -1;
        len = sizeof(val);
        if (getsockopt(sock, IPPROTO_IP, option, val, &len) < 0)
                err(-1, "test_ip_uchar(%s, %s): getsockopt() after set to "
                    "32000", socktypename, optionname);

        if (len != sizeof(val[0]))
                errx(-1, "test_ip_uchar(%s, %s): getsockopt() after set to "
                    "32000 returned %d bytes", socktypename, optionname,
                    len);

        if (val[0] == -1)
                errx(-1, "test_ip_uchar(%s, %s): getsockopt() after set to "
                    "32000 didn't return data", socktypename, optionname);

        if (val[0] == 32000)
                errx(-1, "test_ip_uchar(%s, %s): getsockopt() after set to "
                    "32000 returned 32000: failed to truncate", socktypename,
                    optionname);
}

/*
 * Generic test for a boolean socket option.  Caller provides the option
 * number, string name, expected default (initial) value, and whether or not
 * the option is root-only.  For each option, test:
 *
 * - That we can read the option.
 * - That the initial value is as expected.
 * - That we can modify the value.
 * - That on modification, the new value can be read back.
 * - That we can reset the value.
 * - that on reset, the new value can be read back.
 */
#define BOOLEAN_ANYONE          1
#define BOOLEAN_ROOTONLY        1
static void
test_ip_boolean(int sock, const char *socktypename, int option,
    char *optionname, int initial, int rootonly)
{
        int newvalue, val[2];
        socklen_t len;

        /*
         * The default for a boolean might be true or false.  If it's false,
         * we will try setting it to true (but using a non-1 value of true).
         * If it's true, we'll set it to false.
         */
        if (initial == 0)
                newvalue = 0xff;
        else
                newvalue = 0;

        val[0] = -1;
        val[1] = -1;
        len = sizeof(val);
        if (getsockopt(sock, IPPROTO_IP, option, val, &len) < 0)
                err(-1, "test_ip_boolean: initial getsockopt()");

        if (len != sizeof(val[0]))
                errx(-1, "test_ip_boolean(%s, %s): initial getsockopt() "
                    "returned %d bytes", socktypename, optionname, len);

        if (val[0] == -1)
                errx(-1, "test_ip_boolean(%s, %s): initial getsockopt() "
                    "didn't return data", socktypename, optionname);

        if (val[0] != initial)
                errx(-1, "test_ip_boolean(%s, %s): initial getsockopt() "
                    "returned %d (expected %d)", socktypename, optionname,
                    val[0], initial);

        /*
         * Set the socket option to a new non-default value.
         */
        if (setsockopt(sock, IPPROTO_IP, option, &newvalue, sizeof(newvalue))
            < 0)
                err(-1, "test_ip_boolean(%s, %s): setsockopt() to %d",
                    socktypename, optionname, newvalue);

        /*
         * Read the value back and see if it is not the default (note: will
         * not be what we set it to, as we set it to 0xff above).
         */
        val[0] = -1;
        val[1] = -1;
        len = sizeof(val);
        if (getsockopt(sock, IPPROTO_IP, option, val, &len) < 0)
                err(-1, "test_ip_boolean(%s, %s): getsockopt() after set to "
                    "%d", socktypename, optionname, newvalue);

        if (len != sizeof(val[0]))
                errx(-1, "test_ip_boolean(%s, %s): getsockopt() after set "
                    "to %d returned %d bytes", socktypename, optionname,
                    newvalue, len);

        if (val[0] == -1)
                errx(-1, "test_ip_boolean(%s, %s): getsockopt() after set "
                    "to %d didn't return data", socktypename, optionname,
                    newvalue);

        /*
         * If we set it to true, check for '1', otherwise '0.
         */
        if (val[0] != (newvalue ? 1 : 0))
                errx(-1, "test_ip_boolean(%s, %s): getsockopt() after set "
                    "to %d returned %d", socktypename, optionname, newvalue,
                    val[0]);

        /*
         * Reset to initial value.
         */
        newvalue = initial;
        if (setsockopt(sock, IPPROTO_IP, option, &newvalue, sizeof(newvalue))
            < 0)
                err(-1, "test_ip_boolean(%s, %s): setsockopt() to reset",
                    socktypename, optionname);

        /*
         * Check reset version.
         */
        val[0] = -1;
        val[1] = -1;
        len = sizeof(val);
        if (getsockopt(sock, IPPROTO_IP, option, val, &len) < 0)
                err(-1, "test_ip_boolean(%s, %s): getsockopt() after reset",
                    socktypename, optionname);

        if (len != sizeof(val[0]))
                errx(-1, "test_ip_boolean(%s, %s): getsockopt() after reset "
                    "returned %d bytes", socktypename, optionname, len);

        if (val[0] == -1)
                errx(-1, "test_ip_boolean(%s, %s): getsockopt() after reset "
                    "didn't return data", socktypename, optionname);

        if (val[0] != newvalue)
                errx(-1, "test_ip_boolean(%s, %s): getsockopt() after reset "
                    "returned %d", socktypename, optionname, newvalue);
}

/*
 * Test the IP_ADD_MEMBERSHIP socket option, and the dynamic allocator
 * for the imo_membership vector which now hangs off struct ip_moptions.
 * We then call IP_DROP_MEMBERSHIP for each group so joined.
 */
static void
test_ip_multicast_membership(int sock, const char *socktypename)
{
    char addrbuf[16];
    struct ip_mreq mreq;
    uint32_t basegroup;
    uint16_t i;
    int sotype;
    socklen_t sotypelen;

    sotypelen = sizeof(sotype);
    if (getsockopt(sock, SOL_SOCKET, SO_TYPE, &sotype, &sotypelen) < 0)
        err(-1, "test_ip_multicast_membership(%s): so_type getsockopt()",
            socktypename);
    /*
     * Do not perform the test for SOCK_STREAM sockets, as this makes
     * no sense.
     */
    if (sotype == SOCK_STREAM)
        return;
    /*
     * The 224/8 range is administratively scoped and has special meaning,
     * therefore it is not used for this test.
     * If we were not told to be non-deterministic:
     * Join multicast groups from 238.1.1.0 up to nmcastgroups.
     * Otherwise, pick a multicast group ID in subnet 238/5 with 11 random
     * bits in the middle, and join groups in linear order up to nmcastgroups.
     */
    if (dorandom) {
        /* be non-deterministic (for interactive operation; a fuller test) */
        srandomdev();
        basegroup = 0xEE000000; /* 238.0.0.0 */
        basegroup |= ((random() % ((1 << 11) - 1)) << 16);      /* 11 bits */
    } else {
        /* be deterministic (for automated operation) */
        basegroup = 0xEE010100; /* 238.1.1.0 */
    }
    /*
     * Join the multicast group(s) on the default multicast interface;
     * this usually maps to the interface to which the default
     * route is pointing.
     */
    for (i = 1; i < nmcastgroups+1; i++) {
        mreq.imr_multiaddr.s_addr = htonl((basegroup + i));
        mreq.imr_interface.s_addr = INADDR_ANY;
        inet_ntop(AF_INET, &mreq.imr_multiaddr, addrbuf, sizeof(addrbuf));
        if (verbose)
                fprintf(stderr, "IP_ADD_MEMBERSHIP %s INADDR_ANY\n", addrbuf);
        if (setsockopt(sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq,
                       sizeof(mreq)) < 0) {
                err(-1,
"test_ip_multicast_membership(%d, %s): failed IP_ADD_MEMBERSHIP (%s, %s)",
                    sock, socktypename, addrbuf, "INADDR_ANY");
        }
    }
    for (i = 1; i < nmcastgroups+1; i++) {
        mreq.imr_multiaddr.s_addr = htonl((basegroup + i));
        mreq.imr_interface.s_addr = INADDR_ANY;
        inet_ntop(AF_INET, &mreq.imr_multiaddr, addrbuf, sizeof(addrbuf));
        if (verbose)
                fprintf(stderr, "IP_DROP_MEMBERSHIP %s INADDR_ANY\n", addrbuf);
        if (setsockopt(sock, IPPROTO_IP, IP_DROP_MEMBERSHIP, &mreq,
                       sizeof(mreq)) < 0) {
                err(-1,
"test_ip_multicast_membership(%d, %s): failed IP_DROP_MEMBERSHIP (%s, %s)",
                    sock, socktypename, addrbuf, "INADDR_ANY");
        }
    }
}

/*
 * XXX: For now, nothing here.
 */
static void
test_ip_multicast_if(int sock, const char *socktypename)
{

        /*
         * It's probably worth trying INADDR_ANY and INADDR_LOOPBACK here
         * to see what happens.
         */
}

/*
 * XXX: For now, nothing here.
 */
static void
test_ip_multicast_vif(int sock, const char *socktypename)
{

        /*
         * This requires some knowledge of the number of virtual interfaces,
         * and what is valid.
         */
}

static void
testsuite(int priv)
{
        const char *socktypenameset[] = {"SOCK_DGRAM", "SOCK_STREAM",
            "SOCK_RAW"};
        int socktypeset[] = {SOCK_DGRAM, SOCK_STREAM, SOCK_RAW};
        const char *socktypename;
        int i, sock, socktype;

        test_ip_hdrincl();

        for (i = 0; i < sizeof(socktypeset)/sizeof(int); i++) {
                socktype = socktypeset[i];
                socktypename = socktypenameset[i];

                /*
                 * If we can't acquire root privilege, we can't open raw
                 * sockets, so don't actually try.
                 */
                if (getuid() != 0 && socktype == SOCK_RAW)
                        continue;
                if (geteuid() != 0 && !priv && socktype == SOCK_RAW)
                        continue;

                /*
                 * XXXRW: On 5.3, this seems not to work for SOCK_RAW.
                 */
                sock = get_socket(socktype, priv);
                if (sock == -1)
                        err(-1, "get_socket(%s, %d) for test_ip_uchar(IP_TOS)",
                            socktypename, priv);
                test_ip_uchar(sock, socktypename, IP_TOS, "IP_TOS", 0);
                close(sock);

                sock = get_socket(socktype, priv);
                if (sock == -1)
                        err(-1, "get_socket(%s %d) for test_ip_uchar(IP_TTL)",
                            socktypename, priv);
                test_ip_uchar(sock, socktypename, IP_TTL, "IP_TTL", 64);
                close(sock);

                sock = get_socket(socktype, priv);
                if (sock == -1)
                        err(-1, "get_socket(%s, %d) for test_ip_boolean"
                            "(IP_RECVOPTS)", socktypename, priv);
                test_ip_boolean(sock, socktypename, IP_RECVOPTS,
                    "IP_RECVOPTS", 0, BOOLEAN_ANYONE);
                close(sock);

                sock = get_socket(socktype, priv);
                if (sock == -1)
                        err(-1, "get_socket(%s, %d) for test_ip_boolean"
                             "(IP_RECVRETOPTS)", socktypename, priv);
                test_ip_boolean(sock, socktypename, IP_RECVRETOPTS,
                    "IP_RECVRETOPTS", 0, BOOLEAN_ANYONE);
                close(sock);

                sock = get_socket(socktype, priv);
                if (sock == -1)
                        err(-1, "get_socket(%s, %d) for test_ip_boolean"
                            "(IP_RECVDSTADDR)", socktypename, priv);
                test_ip_boolean(sock, socktypename, IP_RECVDSTADDR,
                    "IP_RECVDSTADDR", 0, BOOLEAN_ANYONE);
                close(sock);

                sock = get_socket(socktype, priv);
                if (sock == -1)
                        err(-1, "get_socket(%s, %d) for test_ip_boolean"
                            "(IP_RECVTTL)", socktypename, priv);
                test_ip_boolean(sock, socktypename, IP_RECVTTL, "IP_RECVTTL",
                    0, BOOLEAN_ANYONE);
                close(sock);

                sock = get_socket(socktype, priv);
                if (sock == -1)
                        err(-1, "get_socket(%s, %d) for test_ip_boolean"
                            "(IP_RECVIF)", socktypename, priv);
                test_ip_boolean(sock, socktypename, IP_RECVIF, "IP_RECVIF",
                    0, BOOLEAN_ANYONE);
                close(sock);

                sock = get_socket(socktype, priv);
                if (sock == -1)
                        err(-1, "get_socket(%s, %d) for test_ip_boolean"
                            "(IP_FAITH)", socktypename, priv);
                test_ip_boolean(sock, socktypename, IP_FAITH, "IP_FAITH", 0,
                    BOOLEAN_ANYONE);
                close(sock);

                sock = get_socket(socktype, priv);
                if (sock == -1)
                        err(-1, "get_socket(%s, %d) for test_ip_boolean"
                            "(IP_ONESBCAST)", socktypename, priv);
                test_ip_boolean(sock, socktypename, IP_ONESBCAST,
                    "IP_ONESBCAST", 0, BOOLEAN_ANYONE);
                close(sock);

                /*
                 * Test the multicast TTL exactly as we would the regular
                 * TTL, only expect a different default.
                 */
                sock = get_socket(socktype, priv);
                if (sock == -1)
                        err(-1, "get_socket(%s, %d) for IP_MULTICAST_TTL",
                            socktypename, priv);
                test_ip_uchar(sock, socktypename, IP_MULTICAST_TTL,
                    "IP_MULTICAST_TTL", 1);
                close(sock);

                /*
                 * The multicast loopback flag can be tested using our
                 * boolean tester, but only because the FreeBSD API is a bit
                 * more flexible than earlir APIs and will accept an int as
                 * well as a u_char.  Loopback is enabled by default.
                 */
                sock = get_socket(socktype, priv);
                if (sock == -1)
                        err(-1, "get_socket(%s, %d) for IP_MULTICAST_LOOP",
                            socktypename, priv);
                test_ip_boolean(sock, socktypename, IP_MULTICAST_LOOP,
                    "IP_MULTICAST_LOOP", 1, BOOLEAN_ANYONE);
                close(sock);

                sock = get_socket(socktype, priv);
                if (sock == -1)
                        err(-1, "get_socket(%s, %d) for test_ip_options",
                            socktypename, priv);
                //test_ip_options(sock, socktypename);
                close(sock);

                sock = get_socket(socktype, priv);
                if (sock == -1)
                        err(-1, "get_socket(%s, %d) for test_ip_options",
                            socktypename, priv);
                test_ip_multicast_membership(sock, socktypename);
                close(sock);

                test_ip_multicast_if(0, NULL);
                test_ip_multicast_vif(0, NULL);
                /*
                 * XXX: Still need to test:
                 * IP_PORTRANGE
                 * IP_IPSEC_POLICY?
                 */
        }
}

static void
usage()
{

        fprintf(stderr, "usage: ipsockopt [-M ngroups] [-r] [-v]\n");
        exit(EXIT_FAILURE);
}

/*
 * Very simply exercise that we can get and set each option.  If we're running
 * as root, run it also as nobody.  If not as root, complain about that.
 */
int
main(int argc, char *argv[])
{
        int ch;

        while ((ch = getopt(argc, argv, "M:rv")) != -1) {
                switch (ch) {
                case 'M':
                        nmcastgroups = atoi(optarg);
                        break;
                case 'r':
                        dorandom = 1;   /* introduce non-determinism */
                        break;
                case 'v':
                        verbose = 1;
                        break;
                default:
                        usage();
                }
        }

        printf("1..1\n");

        if (geteuid() != 0) {
                warnx("Not running as root, can't run tests as root");
                fprintf(stderr, "\n");
                fprintf(stderr,
                   "Running tests with uid %d sock uid %d\n", geteuid(),
                    geteuid());
                testsuite(PRIV_ASIS);
        } else {
                fprintf(stderr,
                    "Running tests with ruid %d euid %d sock uid 0\n",
                    getuid(), geteuid());
                testsuite(PRIV_ASIS);
                if (seteuid(65534) != 0)
                        err(-1, "seteuid(65534)");
                fprintf(stderr,
                    "Running tests with ruid %d euid %d sock uid 65534\n",
                    getuid(), geteuid());
                testsuite(PRIV_ASIS);
                fprintf(stderr,
                    "Running tests with ruid %d euid %d sock uid 0\n",
                    getuid(), geteuid());
                testsuite(PRIV_GETROOT);
        }
        printf("ok 1 - ipsockopt\n");
        exit(0);
}