root/sbin/natd/natd.c
/*
 * natd - Network Address Translation Daemon for FreeBSD.
 *
 * This software is provided free of charge, with no 
 * warranty of any kind, either expressed or implied.
 * Use at your own risk.
 * 
 * You may copy, modify and distribute this software (natd.c) freely.
 *
 * Ari Suutari <suutari@iki.fi>
 */

#include <sys/cdefs.h>
#define SYSLOG_NAMES

#include <sys/types.h>
#include <sys/socket.h>
#include <sys/sysctl.h>
#include <sys/time.h>
#include <sys/queue.h>

#include <netinet/in.h>
#include <netinet/in_systm.h>
#include <netinet/ip.h>
#include <netinet/tcp.h>
#include <netinet/udp.h>
#include <netinet/ip_icmp.h>
#include <net/if.h>
#include <net/if_dl.h>
#include <net/route.h>
#include <arpa/inet.h>

#include <alias.h>
#include <ctype.h>
#include <err.h>
#include <errno.h>
#include <netdb.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <syslog.h>
#include <unistd.h>

#include "natd.h"

struct instance {
        const char              *name;
        struct libalias         *la;
        LIST_ENTRY(instance)    list;

        int                     ifIndex;
        int                     assignAliasAddr;
        char*                   ifName;
        int                     logDropped;
        u_short                 inPort;
        u_short                 outPort;
        u_short                 inOutPort;
        struct in_addr          aliasAddr;
        int                     ifMTU;
        int                     aliasOverhead;
        int                     dropIgnoredIncoming;
        int                     divertIn;
        int                     divertOut;
        int                     divertInOut;
};

static LIST_HEAD(, instance) root = LIST_HEAD_INITIALIZER(root);

struct libalias *mla;
static struct instance *mip;
static int ninstance = 1;

/* 
 * Default values for input and output
 * divert socket ports.
 */

#define DEFAULT_SERVICE "natd"

/*
 * Definition of a port range, and macros to deal with values.
 * FORMAT:  HI 16-bits == first port in range, 0 == all ports.
 *          LO 16-bits == number of ports in range
 * NOTES:   - Port values are not stored in network byte order.
 */

typedef u_long port_range;

#define GETLOPORT(x)     ((x) >> 0x10)
#define GETNUMPORTS(x)   ((x) & 0x0000ffff)
#define GETHIPORT(x)     (GETLOPORT((x)) + GETNUMPORTS((x)))

/* Set y to be the low-port value in port_range variable x. */
#define SETLOPORT(x,y)   ((x) = ((x) & 0x0000ffff) | ((y) << 0x10))

/* Set y to be the number of ports in port_range variable x. */
#define SETNUMPORTS(x,y) ((x) = ((x) & 0xffff0000) | (y))

/*
 * Function prototypes.
 */

static void     DoAliasing (int fd, int direction);
static void     DaemonMode (void);
static void     HandleRoutingInfo (int fd);
static void     Usage (void);
static char*    FormatPacket (struct ip*);
static void     PrintPacket (struct ip*);
static void     SyslogPacket (struct ip*, int priority, const char *label);
static int      SetAliasAddressFromIfName (const char *ifName);
static void     InitiateShutdown (int);
static void     Shutdown (int);
static void     RefreshAddr (int);
static void     ParseOption (const char* option, const char* parms);
static void     ReadConfigFile (const char* fileName);
static void     SetupPortRedirect (const char* parms);
static void     SetupProtoRedirect(const char* parms);
static void     SetupAddressRedirect (const char* parms);
static void     StrToAddr (const char* str, struct in_addr* addr);
static u_short  StrToPort (const char* str, const char* proto);
static int      StrToPortRange (const char* str, const char* proto, port_range *portRange);
static int      StrToProto (const char* str);
static int      StrToAddrAndPortRange (char* str, struct in_addr* addr, char* proto, port_range *portRange);
static void     ParseArgs (int argc, char** argv);
static void     SetupPunchFW(const char *strValue);
static void     SetupSkinnyPort(const char *strValue);
static void     NewInstance(const char *name);
static void     DoGlobal (int fd);
static int      CheckIpfwRulenum(unsigned int rnum);

/*
 * Globals.
 */

static  int                     verbose;
static  int                     background;
static  int                     running;
static  int                     logFacility;

static  int                     dynamicMode;
static  int                     icmpSock;
static  int                     logIpfwDenied;
static  const char*             pidName;
static  int                     routeSock;
static  int                     globalPort;
static  int                     divertGlobal;
static  int                     exitDelay;


int main (int argc, char** argv)
{
        struct sockaddr_in      addr;
        fd_set                  readMask;
        int                     fdMax;
        int                     rval;
/* 
 * Initialize packet aliasing software.
 * Done already here to be able to alter option bits
 * during command line and configuration file processing.
 */
        NewInstance("default");

/*
 * Parse options.
 */
        verbose                 = 0;
        background              = 0;
        running                 = 1;
        dynamicMode             = 0;
        logFacility             = LOG_DAEMON;
        logIpfwDenied           = -1;
        pidName                 = PIDFILE;
        routeSock               = -1;
        icmpSock                = -1;
        fdMax                   = -1;
        divertGlobal            = -1;
        exitDelay               = EXIT_DELAY;

        ParseArgs (argc, argv);
/*
 * Log ipfw(8) denied packets by default in verbose mode.
 */
        if (logIpfwDenied == -1)
                logIpfwDenied = verbose;
/*
 * Open syslog channel.
 */
        openlog ("natd", LOG_CONS | LOG_PID | (verbose ? LOG_PERROR : 0),
                 logFacility);

        LIST_FOREACH(mip, &root, list) {
                mla = mip->la;
/*
 * If not doing the transparent proxying only,
 * check that valid aliasing address has been given.
 */
                if (mip->aliasAddr.s_addr == INADDR_NONE && mip->ifName == NULL &&
                    !(LibAliasSetMode(mla, 0,0) & PKT_ALIAS_PROXY_ONLY))
                        errx (1, "instance %s: aliasing address not given", mip->name);

                if (mip->aliasAddr.s_addr != INADDR_NONE && mip->ifName != NULL)
                        errx (1, "both alias address and interface "
                                 "name are not allowed");
/*
 * Check that valid port number is known.
 */
                if (mip->inPort != 0 || mip->outPort != 0)
                        if (mip->inPort == 0 || mip->outPort == 0)
                                errx (1, "both input and output ports are required");

                if (mip->inPort == 0 && mip->outPort == 0 && mip->inOutPort == 0)
                        ParseOption ("port", DEFAULT_SERVICE);

/*
 * Check if ignored packets should be dropped.
 */
                mip->dropIgnoredIncoming = LibAliasSetMode (mla, 0, 0);
                mip->dropIgnoredIncoming &= PKT_ALIAS_DENY_INCOMING;
/*
 * Create divert sockets. Use only one socket if -p was specified
 * on command line. Otherwise, create separate sockets for
 * outgoing and incoming connections.
 */
                if (mip->inOutPort) {

                        mip->divertInOut = socket(PF_DIVERT, SOCK_RAW, 0);
                        if (mip->divertInOut == -1)
                                Quit ("Unable to create divert socket.");
                        if (mip->divertInOut > fdMax)
                                fdMax = mip->divertInOut;

                        mip->divertIn  = -1;
                        mip->divertOut = -1;
/*
 * Bind socket.
 */

                        addr.sin_family         = AF_INET;
                        addr.sin_addr.s_addr    = INADDR_ANY;
                        addr.sin_port           = mip->inOutPort;

                        if (bind (mip->divertInOut,
                                  (struct sockaddr*) &addr,
                                  sizeof addr) == -1)
                                Quit ("Unable to bind divert socket.");
                }
                else {

                        mip->divertIn = socket(PF_DIVERT, SOCK_RAW, 0);
                        if (mip->divertIn == -1)
                                Quit ("Unable to create incoming divert socket.");
                        if (mip->divertIn > fdMax)
                                fdMax = mip->divertIn;


                        mip->divertOut = socket(PF_DIVERT, SOCK_RAW, 0);
                        if (mip->divertOut == -1)
                                Quit ("Unable to create outgoing divert socket.");
                        if (mip->divertOut > fdMax)
                                fdMax = mip->divertOut;

                        mip->divertInOut = -1;

/*
 * Bind divert sockets.
 */

                        addr.sin_family         = AF_INET;
                        addr.sin_addr.s_addr    = INADDR_ANY;
                        addr.sin_port           = mip->inPort;

                        if (bind (mip->divertIn,
                                  (struct sockaddr*) &addr,
                                  sizeof addr) == -1)
                                Quit ("Unable to bind incoming divert socket.");

                        addr.sin_family         = AF_INET;
                        addr.sin_addr.s_addr    = INADDR_ANY;
                        addr.sin_port           = mip->outPort;

                        if (bind (mip->divertOut,
                                  (struct sockaddr*) &addr,
                                  sizeof addr) == -1)
                                Quit ("Unable to bind outgoing divert socket.");
                }
/*
 * Create routing socket if interface name specified and in dynamic mode.
 */
                if (mip->ifName) {
                        if (dynamicMode) {

                                if (routeSock == -1)
                                        routeSock = socket (PF_ROUTE, SOCK_RAW, 0);
                                if (routeSock == -1)
                                        Quit ("Unable to create routing info socket.");
                                if (routeSock > fdMax)
                                        fdMax = routeSock;

                                mip->assignAliasAddr = 1;
                        }
                        else {
                                do {
                                        rval = SetAliasAddressFromIfName (mip->ifName);
                                        if (background == 0 || dynamicMode == 0)
                                                break;
                                        if (rval == EAGAIN)
                                                sleep(1);
                                } while (rval == EAGAIN);
                                if (rval != 0)
                                        exit(1);
                        }
                }

        }
        if (globalPort) {

                divertGlobal = socket(PF_DIVERT, SOCK_RAW, 0);
                if (divertGlobal == -1)
                        Quit ("Unable to create divert socket.");
                if (divertGlobal > fdMax)
                        fdMax = divertGlobal;

/*
* Bind socket.
*/

                addr.sin_family         = AF_INET;
                addr.sin_addr.s_addr    = INADDR_ANY;
                addr.sin_port           = globalPort;

                if (bind (divertGlobal,
                          (struct sockaddr*) &addr,
                          sizeof addr) == -1)
                        Quit ("Unable to bind global divert socket.");
        }
/*
 * Create socket for sending ICMP messages.
 */
        icmpSock = socket (AF_INET, SOCK_RAW, IPPROTO_ICMP);
        if (icmpSock == -1)
                Quit ("Unable to create ICMP socket.");

/*
 * And disable reads for the socket, otherwise it slowly fills
 * up with received icmps which we do not use.
 */
        shutdown(icmpSock, SHUT_RD);

/*
 * Become a daemon unless verbose mode was requested.
 */
        if (!verbose)
                DaemonMode ();
/*
 * Catch signals to manage shutdown and
 * refresh of interface address.
 */
        siginterrupt(SIGTERM, 1);
        siginterrupt(SIGHUP, 1);
        if (exitDelay)
                signal(SIGTERM, InitiateShutdown);
        else
                signal(SIGTERM, Shutdown);
        signal (SIGHUP, RefreshAddr);
/*
 * Set alias address if it has been given.
 */
        mip = LIST_FIRST(&root);        /* XXX: simon */
        LIST_FOREACH(mip, &root, list) {
                mla = mip->la;
                if (mip->aliasAddr.s_addr != INADDR_NONE)
                        LibAliasSetAddress (mla, mip->aliasAddr);
        }

        while (running) {
                mip = LIST_FIRST(&root);        /* XXX: simon */

                if (mip->divertInOut != -1 && !mip->ifName && ninstance == 1) {
/*
 * When using only one socket, just call 
 * DoAliasing repeatedly to process packets.
 */
                        DoAliasing (mip->divertInOut, DONT_KNOW);
                        continue;
                }
/* 
 * Build read mask from socket descriptors to select.
 */
                FD_ZERO (&readMask);
/*
 * Check if new packets are available.
 */
                LIST_FOREACH(mip, &root, list) {
                        if (mip->divertIn != -1)
                                FD_SET (mip->divertIn, &readMask);

                        if (mip->divertOut != -1)
                                FD_SET (mip->divertOut, &readMask);

                        if (mip->divertInOut != -1)
                                FD_SET (mip->divertInOut, &readMask);
                }
/*
 * Routing info is processed always.
 */
                if (routeSock != -1)
                        FD_SET (routeSock, &readMask);

                if (divertGlobal != -1)
                        FD_SET (divertGlobal, &readMask);

                if (select (fdMax + 1,
                            &readMask,
                            NULL,
                            NULL,
                            NULL) == -1) {

                        if (errno == EINTR)
                                continue;

                        Quit ("Select failed.");
                }

                if (divertGlobal != -1)
                        if (FD_ISSET (divertGlobal, &readMask))
                                DoGlobal (divertGlobal);
                LIST_FOREACH(mip, &root, list) {
                        mla = mip->la;
                        if (mip->divertIn != -1)
                                if (FD_ISSET (mip->divertIn, &readMask))
                                        DoAliasing (mip->divertIn, INPUT);

                        if (mip->divertOut != -1)
                                if (FD_ISSET (mip->divertOut, &readMask))
                                        DoAliasing (mip->divertOut, OUTPUT);

                        if (mip->divertInOut != -1) 
                                if (FD_ISSET (mip->divertInOut, &readMask))
                                        DoAliasing (mip->divertInOut, DONT_KNOW);

                }
                if (routeSock != -1)
                        if (FD_ISSET (routeSock, &readMask))
                                HandleRoutingInfo (routeSock);
        }

        if (background)
                unlink (pidName);

        return 0;
}

static void DaemonMode(void)
{
        FILE*   pidFile;

        daemon (0, 0);
        background = 1;

        pidFile = fopen (pidName, "w");
        if (pidFile) {

                fprintf (pidFile, "%d\n", getpid ());
                fclose (pidFile);
        }
}

static void ParseArgs (int argc, char** argv)
{
        int             arg;
        char*           opt;
        char            parmBuf[256];
        int             len; /* bounds checking */

        for (arg = 1; arg < argc; arg++) {

                opt  = argv[arg];
                if (*opt != '-') {

                        warnx ("invalid option %s", opt);
                        Usage ();
                }

                parmBuf[0] = '\0';
                len = 0;

                while (arg < argc - 1) {

                        if (argv[arg + 1][0] == '-')
                                break;

                        if (len) {
                                strncat (parmBuf, " ", sizeof(parmBuf) - (len + 1));
                                len += strlen(parmBuf + len);
                        }

                        ++arg;
                        strncat (parmBuf, argv[arg], sizeof(parmBuf) - (len + 1));
                        len += strlen(parmBuf + len);

                }

                ParseOption (opt + 1, (len ? parmBuf : NULL));

        }
}

static void DoGlobal (int fd)
{
        int                     bytes;
        int                     origBytes;
        char                    buf[IP_MAXPACKET];
        struct sockaddr_in      addr;
        int                     wrote;
        socklen_t               addrSize;
        struct ip*              ip;
        char                    msgBuf[80];

/*
 * Get packet from socket.
 */
        addrSize  = sizeof addr;
        origBytes = recvfrom (fd,
                              buf,
                              sizeof buf,
                              0,
                              (struct sockaddr*) &addr,
                              &addrSize);

        if (origBytes == -1) {

                if (errno != EINTR)
                        Warn ("read from divert socket failed");

                return;
        }

#if 0
        if (mip->assignAliasAddr) {
                if (SetAliasAddressFromIfName (mip->ifName) != 0)
                        exit(1);
                mip->assignAliasAddr = 0;
        }
#endif
/*
 * This is an IP packet.
 */
        ip = (struct ip*) buf;

        if (verbose) {
/*
 * Print packet direction and protocol type.
 */
                printf ("Glb ");

                switch (ip->ip_p) {
                case IPPROTO_TCP:
                        printf ("[TCP]  ");
                        break;

                case IPPROTO_UDP:
                        printf ("[UDP]  ");
                        break;

                case IPPROTO_ICMP:
                        printf ("[ICMP] ");
                        break;

                default:
                        printf ("[%d]    ", ip->ip_p);
                        break;
                }
/*
 * Print addresses.
 */
                PrintPacket (ip);
        }

        LIST_FOREACH(mip, &root, list) {
                mla = mip->la;
                if (LibAliasOutTry (mla, buf, IP_MAXPACKET, 0) != PKT_ALIAS_IGNORED)
                        break;
        }
/*
 * Length might have changed during aliasing.
 */
        bytes = ntohs (ip->ip_len);
/*
 * Update alias overhead size for outgoing packets.
 */
        if (mip != NULL && bytes - origBytes > mip->aliasOverhead)
                mip->aliasOverhead = bytes - origBytes;

        if (verbose) {
                
/*
 * Print addresses after aliasing.
 */
                printf (" aliased to\n");
                printf ("           ");
                PrintPacket (ip);
                printf ("\n");
        }

/*
 * Put packet back for processing.
 */
        wrote = sendto (fd, 
                        buf,
                        bytes,
                        0,
                        (struct sockaddr*) &addr,
                        sizeof addr);
        
        if (wrote != bytes) {

                if (errno == EMSGSIZE && mip != NULL) {

                        if (mip->ifMTU != -1)
                                SendNeedFragIcmp (icmpSock,
                                                  (struct ip*) buf,
                                                  mip->ifMTU - mip->aliasOverhead);
                }
                else if (errno == EACCES && logIpfwDenied) {

                        sprintf (msgBuf, "failed to write packet back");
                        Warn (msgBuf);
                }
        }
}


static void DoAliasing (int fd, int direction)
{
        int                     bytes;
        int                     origBytes;
        char                    buf[IP_MAXPACKET];
        struct sockaddr_in      addr;
        int                     wrote;
        int                     status;
        socklen_t               addrSize;
        struct ip*              ip;
        char                    msgBuf[80];
        int                     rval;

        if (mip->assignAliasAddr) {
                do {
                        rval = SetAliasAddressFromIfName (mip->ifName);
                        if (background == 0 || dynamicMode == 0)
                                break;
                        if (rval == EAGAIN)
                                sleep(1);
                } while (rval == EAGAIN);
                if (rval != 0)
                        exit(1);
                mip->assignAliasAddr = 0;
        }
/*
 * Get packet from socket.
 */
        addrSize  = sizeof addr;
        origBytes = recvfrom (fd,
                              buf,
                              sizeof buf,
                              0,
                              (struct sockaddr*) &addr,
                              &addrSize);

        if (origBytes == -1) {

                if (errno != EINTR)
                        Warn ("read from divert socket failed");

                return;
        }
/*
 * This is an IP packet.
 */
        ip = (struct ip*) buf;
        if (direction == DONT_KNOW) {
                if (addr.sin_addr.s_addr == INADDR_ANY)
                        direction = OUTPUT;
                else
                        direction = INPUT;
        }

        if (verbose) {
/*
 * Print packet direction and protocol type.
 */
                printf (direction == OUTPUT ? "Out " : "In  ");
                if (ninstance > 1)
                        printf ("{%s}", mip->name);

                switch (ip->ip_p) {
                case IPPROTO_TCP:
                        printf ("[TCP]  ");
                        break;

                case IPPROTO_UDP:
                        printf ("[UDP]  ");
                        break;

                case IPPROTO_ICMP:
                        printf ("[ICMP] ");
                        break;

                default:
                        printf ("[%d]    ", ip->ip_p);
                        break;
                }
/*
 * Print addresses.
 */
                PrintPacket (ip);
        }

        if (direction == OUTPUT) {
/*
 * Outgoing packets. Do aliasing.
 */
                LibAliasOut (mla, buf, IP_MAXPACKET);
        }
        else {

/*
 * Do aliasing.
 */     
                status = LibAliasIn (mla, buf, IP_MAXPACKET);
                if (status == PKT_ALIAS_IGNORED &&
                    mip->dropIgnoredIncoming) {

                        if (verbose)
                                printf (" dropped.\n");

                        if (mip->logDropped)
                                SyslogPacket (ip, LOG_WARNING, "denied");

                        return;
                }
        }
/*
 * Length might have changed during aliasing.
 */
        bytes = ntohs (ip->ip_len);
/*
 * Update alias overhead size for outgoing packets.
 */
        if (direction == OUTPUT &&
            bytes - origBytes > mip->aliasOverhead)
                mip->aliasOverhead = bytes - origBytes;

        if (verbose) {
                
/*
 * Print addresses after aliasing.
 */
                printf (" aliased to\n");
                printf ("           ");
                PrintPacket (ip);
                printf ("\n");
        }

/*
 * Put packet back for processing.
 */
        wrote = sendto (fd, 
                        buf,
                        bytes,
                        0,
                        (struct sockaddr*) &addr,
                        sizeof addr);
        
        if (wrote != bytes) {

                if (errno == EMSGSIZE) {

                        if (direction == OUTPUT &&
                            mip->ifMTU != -1)
                                SendNeedFragIcmp (icmpSock,
                                                  (struct ip*) buf,
                                                  mip->ifMTU - mip->aliasOverhead);
                }
                else if (errno == EACCES && logIpfwDenied) {

                        sprintf (msgBuf, "failed to write packet back");
                        Warn (msgBuf);
                }
        }
}

static void HandleRoutingInfo (int fd)
{
        int                     bytes;
        struct if_msghdr        ifMsg;
/*
 * Get packet from socket.
 */
        bytes = read (fd, &ifMsg, sizeof ifMsg);
        if (bytes == -1) {

                Warn ("read from routing socket failed");
                return;
        }

        if (ifMsg.ifm_version != RTM_VERSION) {

                Warn ("unexpected packet read from routing socket");
                return;
        }

        if (verbose)
                printf ("Routing message %#x received.\n", ifMsg.ifm_type);

        if ((ifMsg.ifm_type == RTM_NEWADDR || ifMsg.ifm_type == RTM_IFINFO)) {
                LIST_FOREACH(mip, &root, list) {
                        mla = mip->la;
                        if (ifMsg.ifm_index == mip->ifIndex) {
                                if (verbose)
                                        printf("Interface address/MTU has probably changed.\n");
                                mip->assignAliasAddr = 1;
                        }
                }
        }
}

static void PrintPacket (struct ip* ip)
{
        printf ("%s", FormatPacket (ip));
}

static void SyslogPacket (struct ip* ip, int priority, const char *label)
{
        syslog (priority, "%s %s", label, FormatPacket (ip));
}

static char* FormatPacket (struct ip* ip)
{
        static char     buf[256];
        struct tcphdr*  tcphdr;
        struct udphdr*  udphdr;
        struct icmp*    icmphdr;
        char            src[20];
        char            dst[20];

        strcpy (src, inet_ntoa (ip->ip_src));
        strcpy (dst, inet_ntoa (ip->ip_dst));

        switch (ip->ip_p) {
        case IPPROTO_TCP:
                tcphdr = (struct tcphdr*) ((char*) ip + (ip->ip_hl << 2));
                sprintf (buf, "[TCP] %s:%d -> %s:%d",
                              src,
                              ntohs (tcphdr->th_sport),
                              dst,
                              ntohs (tcphdr->th_dport));
                break;

        case IPPROTO_UDP:
                udphdr = (struct udphdr*) ((char*) ip + (ip->ip_hl << 2));
                sprintf (buf, "[UDP] %s:%d -> %s:%d",
                              src,
                              ntohs (udphdr->uh_sport),
                              dst,
                              ntohs (udphdr->uh_dport));
                break;

        case IPPROTO_ICMP:
                icmphdr = (struct icmp*) ((char*) ip + (ip->ip_hl << 2));
                sprintf (buf, "[ICMP] %s -> %s %u(%u)",
                              src,
                              dst,
                              icmphdr->icmp_type,
                              icmphdr->icmp_code);
                break;

        default:
                sprintf (buf, "[%d] %s -> %s ", ip->ip_p, src, dst);
                break;
        }

        return buf;
}

static int
SetAliasAddressFromIfName(const char *ifn)
{
        size_t needed;
        int mib[6];
        char *buf, *lim, *next;
        struct if_msghdr *ifm;
        struct ifa_msghdr *ifam;
        struct sockaddr_dl *sdl;
        struct sockaddr_in *sin;

        mib[0] = CTL_NET;
        mib[1] = PF_ROUTE;
        mib[2] = 0;
        mib[3] = AF_INET;       /* Only IP addresses please */
        mib[4] = NET_RT_IFLIST;
        mib[5] = 0;             /* ifIndex??? */
/*
 * Get interface data.
 */
        if (sysctl(mib, 6, NULL, &needed, NULL, 0) == -1)
                err(1, "iflist-sysctl-estimate");
        if ((buf = malloc(needed)) == NULL)
                errx(1, "malloc failed");
        if (sysctl(mib, 6, buf, &needed, NULL, 0) == -1 && errno != ENOMEM)
                err(1, "iflist-sysctl-get");
        lim = buf + needed;
/*
 * Loop through interfaces until one with
 * given name is found. This is done to
 * find correct interface index for routing
 * message processing.
 */
        mip->ifIndex    = 0;
        next = buf;
        while (next < lim) {
                ifm = (struct if_msghdr *)next;
                next += ifm->ifm_msglen;
                if (ifm->ifm_version != RTM_VERSION) {
                        if (verbose)
                                warnx("routing message version %d "
                                      "not understood", ifm->ifm_version);
                        continue;
                }
                if (ifm->ifm_type == RTM_IFINFO) {
                        sdl = (struct sockaddr_dl *)(ifm + 1);
                        if (strlen(ifn) == sdl->sdl_nlen &&
                            strncmp(ifn, sdl->sdl_data, sdl->sdl_nlen) == 0) {
                                mip->ifIndex = ifm->ifm_index;
                                mip->ifMTU = ifm->ifm_data.ifi_mtu;
                                break;
                        }
                }
        }
        if (!mip->ifIndex)
                errx(1, "unknown interface name %s", ifn);
/*
 * Get interface address.
 */
        sin = NULL;
        while (next < lim) {
                ifam = (struct ifa_msghdr *)next;
                next += ifam->ifam_msglen;
                if (ifam->ifam_version != RTM_VERSION) {
                        if (verbose)
                                warnx("routing message version %d "
                                      "not understood", ifam->ifam_version);
                        continue;
                }
                if (ifam->ifam_type != RTM_NEWADDR)
                        break;
                if (ifam->ifam_addrs & RTA_IFA) {
                        int i;
                        char *cp = (char *)(ifam + 1);

                        for (i = 1; i < RTA_IFA; i <<= 1)
                                if (ifam->ifam_addrs & i)
                                        cp += SA_SIZE((struct sockaddr *)cp);
                        if (((struct sockaddr *)cp)->sa_family == AF_INET) {
                                sin = (struct sockaddr_in *)cp;
                                break;
                        }
                }
        }
        if (sin == NULL) {
                warnx("%s: cannot get interface address", ifn);
                free(buf);
                return EAGAIN;
        }

        LibAliasSetAddress(mla, sin->sin_addr);
        syslog(LOG_INFO, "Aliasing to %s, mtu %d bytes",
               inet_ntoa(sin->sin_addr), mip->ifMTU);

        free(buf);

        return 0;
}

void Quit (const char* msg)
{
        Warn (msg);
        exit (1);
}

void Warn (const char* msg)
{
        if (background)
                syslog (LOG_ALERT, "%s (%m)", msg);
        else
                warn ("%s", msg);
}

static void RefreshAddr (int sig __unused)
{
        LibAliasRefreshModules();
        if (mip != NULL && mip->ifName != NULL)
                mip->assignAliasAddr = 1;
}

static void InitiateShutdown (int sig __unused)
{
/*
 * Start timer to allow kernel gracefully
 * shutdown existing connections when system
 * is shut down.
 */
        siginterrupt(SIGALRM, 1);
        signal (SIGALRM, Shutdown);
        ualarm(exitDelay*1000, 1000);
}

static void Shutdown (int sig __unused)
{
        running = 0;
}

/* 
 * Different options recognized by this program.
 */

enum Option {

        LibAliasOption,
        Instance,
        Verbose,
        InPort,
        OutPort,
        Port,
        GlobalPort,
        AliasAddress,
        TargetAddress,
        InterfaceName,
        RedirectPort,
        RedirectProto,
        RedirectAddress,
        ConfigFile,
        DynamicMode,
        ProxyRule,
        LogDenied,
        LogFacility,
        PunchFW,
        SkinnyPort,
        LogIpfwDenied,
        PidFile,
        ExitDelay
};

enum Param {
        
        YesNo,
        Numeric,
        String,
        None,
        Address,
        Service
};

/*
 * Option information structure (used by ParseOption).
 */

struct OptionInfo {
        
        enum Option             type;
        int                     packetAliasOpt;
        enum Param              parm;
        const char*             parmDescription;
        const char*             description;
        const char*             name; 
        const char*             shortName;
};

/*
 * Table of known options.
 */

static struct OptionInfo optionTable[] = {

        { LibAliasOption,
                PKT_ALIAS_UNREGISTERED_ONLY,
                YesNo,
                "[yes|no]",
                "alias only unregistered addresses",
                "unregistered_only",
                "u" },

        { LibAliasOption,
                PKT_ALIAS_LOG,
                YesNo,
                "[yes|no]",
                "enable logging",
                "log",
                "l" },

        { LibAliasOption,
                PKT_ALIAS_PROXY_ONLY,
                YesNo,
                "[yes|no]",
                "proxy only",
                "proxy_only",
                NULL },

        { LibAliasOption,
                PKT_ALIAS_REVERSE,
                YesNo,
                "[yes|no]",
                "operate in reverse mode",
                "reverse",
                NULL },

        { LibAliasOption,
                PKT_ALIAS_DENY_INCOMING,
                YesNo,
                "[yes|no]",
                "allow incoming connections",
                "deny_incoming",
                "d" },

        { LibAliasOption,
                PKT_ALIAS_USE_SOCKETS,
                YesNo,
                "[yes|no]",
                "use sockets to inhibit port conflict",
                "use_sockets",
                "s" },

        { LibAliasOption,
                PKT_ALIAS_SAME_PORTS,
                YesNo,
                "[yes|no]",
                "try to keep original port numbers for connections",
                "same_ports",
                "m" },

        { LibAliasOption,
                PKT_ALIAS_UDP_EIM,
                YesNo,
                "[yes|no]",
                "UDP traffic uses endpoint-independent mapping (\"full cone\" NAT)",
                "udp_eim",
                NULL },

        { Verbose,
                0,
                YesNo,
                "[yes|no]",
                "verbose mode, dump packet information",
                "verbose",
                "v" },
        
        { DynamicMode,
                0,
                YesNo,
                "[yes|no]",
                "dynamic mode, automatically detect interface address changes",
                "dynamic",
                NULL },
        
        { InPort,
                0,
                Service,
                "number|service_name",
                "set port for incoming packets",
                "in_port",
                "i" },
        
        { OutPort,
                0,
                Service,
                "number|service_name",
                "set port for outgoing packets",
                "out_port",
                "o" },
        
        { Port,
                0,
                Service,
                "number|service_name",
                "set port (defaults to natd/divert)",
                "port",
                "p" },
        
        { GlobalPort,
                0,
                Service,
                "number|service_name",
                "set globalport",
                "globalport",
                NULL },
        
        { AliasAddress,
                0,
                Address,
                "x.x.x.x",
                "address to use for aliasing",
                "alias_address",
                "a" },
        
        { TargetAddress,
                0,
                Address,
                "x.x.x.x",
                "address to use for incoming sessions",
                "target_address",
                "t" },
        
        { InterfaceName,
                0,
                String,
                "network_if_name",
                "take aliasing address from interface",
                "interface",
                "n" },

        { ProxyRule,
                0,
                String,
                "[type encode_ip_hdr|encode_tcp_stream] port xxxx server "
                "a.b.c.d:yyyy",
                "add transparent proxying / destination NAT",
                "proxy_rule",
                NULL },

        { RedirectPort,
                0,
                String,
                "tcp|udp local_addr:local_port_range[,...] [public_addr:]public_port_range"
                " [remote_addr[:remote_port_range]]",
                "redirect a port (or ports) for incoming traffic",
                "redirect_port",
                NULL },

        { RedirectProto,
                0,
                String,
                "proto local_addr [public_addr] [remote_addr]",
                "redirect packets of a given proto",
                "redirect_proto",
                NULL },

        { RedirectAddress,
                0,
                String,
                "local_addr[,...] public_addr",
                "define mapping between local and public addresses",
                "redirect_address",
                NULL },

        { ConfigFile,
                0,
                String,
                "file_name",
                "read options from configuration file",
                "config",
                "f" },

        { LogDenied,
                0,
                YesNo,
                "[yes|no]",
                "enable logging of denied incoming packets",
                "log_denied",
                NULL },

        { LogFacility,
                0,
                String,
                "facility",
                "name of syslog facility to use for logging",
                "log_facility",
                NULL },

        { PunchFW,
                0,
                String,
                "basenumber:count",
                "punch holes in the firewall for incoming FTP/IRC DCC connections",
                "punch_fw",
                NULL },

        { SkinnyPort,
                0,
                String,
                "port",
                "set the TCP port for use with the Skinny Station protocol",
                "skinny_port",
                NULL },

        { LogIpfwDenied,
                0,
                YesNo,
                "[yes|no]",
                "log packets converted by natd, but denied by ipfw",
                "log_ipfw_denied",
                NULL },

        { PidFile,
                0,
                String,
                "file_name",
                "store PID in an alternate file",
                "pid_file",
                "P" },
        { Instance,
                0,
                String,
                "instance name",
                "name of aliasing engine instance",
                "instance",
                NULL },
        { ExitDelay,
                0,
                Numeric,
                "ms",
                "delay in ms before daemon exit after signal",
                "exit_delay",
                NULL },
};
        
static void ParseOption (const char* option, const char* parms)
{
        int                     i;
        struct OptionInfo*      info;
        int                     yesNoValue;
        int                     aliasValue;
        int                     numValue;
        u_short                 uNumValue;
        const char*             strValue;
        struct in_addr          addrValue;
        int                     max;
        char*                   end;
        const CODE*             fac_record = NULL;
/*
 * Find option from table.
 */
        max = sizeof (optionTable) / sizeof (struct OptionInfo);
        for (i = 0, info = optionTable; i < max; i++, info++) {

                if (!strcmp (info->name, option))
                        break;

                if (info->shortName)
                        if (!strcmp (info->shortName, option))
                                break;
        }

        if (i >= max) {

                warnx ("unknown option %s", option);
                Usage ();
        }

        uNumValue       = 0;
        yesNoValue      = 0;
        numValue        = 0;
        strValue        = NULL;
/*
 * Check parameters.
 */
        switch (info->parm) {
        case YesNo:
                if (!parms)
                        parms = "yes";

                if (!strcmp (parms, "yes"))
                        yesNoValue = 1;
                else
                        if (!strcmp (parms, "no"))
                                yesNoValue = 0;
                        else
                                errx (1, "%s needs yes/no parameter", option);
                break;

        case Service:
                if (!parms)
                        errx (1, "%s needs service name or "
                                 "port number parameter",
                                 option);

                uNumValue = StrToPort (parms, "divert");
                break;

        case Numeric:
                if (parms)
                        numValue = strtol (parms, &end, 10);
                else
                        end = NULL;

                if (end == parms)
                        errx (1, "%s needs numeric parameter", option);
                break;

        case String:
                strValue = parms;
                if (!strValue)
                        errx (1, "%s needs parameter", option);
                break;

        case None:
                if (parms)
                        errx (1, "%s does not take parameters", option);
                break;

        case Address:
                if (!parms)
                        errx (1, "%s needs address/host parameter", option);

                StrToAddr (parms, &addrValue);
                break;
        }

        switch (info->type) {
        case LibAliasOption:
        
                aliasValue = yesNoValue ? info->packetAliasOpt : 0;
                LibAliasSetMode (mla, aliasValue, info->packetAliasOpt);
                break;

        case Verbose:
                verbose = yesNoValue;
                break;

        case DynamicMode:
                dynamicMode = yesNoValue;
                break;

        case InPort:
                mip->inPort = uNumValue;
                break;

        case OutPort:
                mip->outPort = uNumValue;
                break;

        case Port:
                mip->inOutPort = uNumValue;
                break;

        case GlobalPort:
                globalPort = uNumValue;
                break;

        case AliasAddress:
                memcpy (&mip->aliasAddr, &addrValue, sizeof (struct in_addr));
                break;

        case TargetAddress:
                LibAliasSetTarget(mla, addrValue);
                break;

        case RedirectPort:
                SetupPortRedirect (strValue);
                break;

        case RedirectProto:
                SetupProtoRedirect(strValue);
                break;

        case RedirectAddress:
                SetupAddressRedirect (strValue);
                break;

        case ProxyRule:
                LibAliasProxyRule (mla, strValue);
                break;

        case InterfaceName:
                if (mip->ifName)
                        free (mip->ifName);

                mip->ifName = strdup (strValue);
                break;

        case ConfigFile:
                ReadConfigFile (strValue);
                break;

        case LogDenied:
                mip->logDropped = yesNoValue;
                break;

        case LogFacility:

                fac_record = facilitynames;
                while (fac_record->c_name != NULL) {

                        if (!strcmp (fac_record->c_name, strValue)) {

                                logFacility = fac_record->c_val;
                                break;

                        }
                        else
                                fac_record++;
                }

                if(fac_record->c_name == NULL)
                        errx(1, "Unknown log facility name: %s", strValue);     

                break;

        case PunchFW:
                SetupPunchFW(strValue);
                break;

        case SkinnyPort:
                SetupSkinnyPort(strValue);
                break;

        case LogIpfwDenied:
                logIpfwDenied = yesNoValue;
                break;

        case PidFile:
                pidName = strdup (strValue);
                break;
        case Instance:
                NewInstance(strValue);
                break;
        case ExitDelay:
                if (numValue < 0 || numValue > MAX_EXIT_DELAY)
                        errx(1, "Incorrect exit delay: %d", numValue);  
                exitDelay = numValue;
                break;
        }
}

void ReadConfigFile (const char* fileName)
{
        FILE*   file;
        char    *buf;
        size_t  len;
        char    *ptr, *p;
        char*   option;

        file = fopen (fileName, "r");
        if (!file)
                err(1, "cannot open config file %s", fileName);

        while ((buf = fgetln(file, &len)) != NULL) {
                if (buf[len - 1] == '\n')
                        buf[len - 1] = '\0';
                else
                        errx(1, "config file format error: "
                                "last line should end with newline");

/*
 * Check for comments, strip off trailing spaces.
 */
                if ((ptr = strchr(buf, '#')))
                        *ptr = '\0';
                for (ptr = buf; isspace(*ptr); ++ptr)
                        continue;
                if (*ptr == '\0')
                        continue;
                for (p = strchr(buf, '\0'); isspace(*--p);)
                        continue;
                *++p = '\0';

/*
 * Extract option name.
 */
                option = ptr;
                while (*ptr && !isspace (*ptr))
                        ++ptr;

                if (*ptr != '\0') {

                        *ptr = '\0';
                        ++ptr;
                }
/*
 * Skip white space between name and parms.
 */
                while (*ptr && isspace (*ptr))
                        ++ptr;

                ParseOption (option, *ptr ? ptr : NULL);
        }

        fclose (file);
}

static void Usage(void)
{
        int                     i;
        int                     max;
        struct OptionInfo*      info;

        fprintf (stderr, "Recognized options:\n\n");

        max = sizeof (optionTable) / sizeof (struct OptionInfo);
        for (i = 0, info = optionTable; i < max; i++, info++) {

                fprintf (stderr, "-%-20s %s\n", info->name,
                                                info->parmDescription);

                if (info->shortName)
                        fprintf (stderr, "-%-20s %s\n", info->shortName,
                                                        info->parmDescription);

                fprintf (stderr, "      %s\n\n", info->description);
        }

        exit (1);
}

void SetupPortRedirect (const char* parms)
{
        char            *buf;
        char*           ptr;
        char*           serverPool;
        struct in_addr  localAddr;
        struct in_addr  publicAddr;
        struct in_addr  remoteAddr;
        port_range      portRange;
        u_short         localPort      = 0;
        u_short         publicPort     = 0;
        u_short         remotePort     = 0;
        u_short         numLocalPorts  = 0;
        u_short         numPublicPorts = 0;
        u_short         numRemotePorts = 0;
        int             proto;
        char*           protoName;
        char*           separator;
        int             i;
        struct alias_link *aliaslink = NULL;

        buf = strdup (parms);
        if (!buf)
                errx (1, "redirect_port: strdup() failed");
/*
 * Extract protocol.
 */
        protoName = strtok (buf, " \t");
        if (!protoName)
                errx (1, "redirect_port: missing protocol");

        proto = StrToProto (protoName);
/*
 * Extract local address.
 */
        ptr = strtok (NULL, " \t");
        if (!ptr)
                errx (1, "redirect_port: missing local address");

        separator = strchr(ptr, ',');
        if (separator) {                /* LSNAT redirection syntax. */
                localAddr.s_addr = INADDR_NONE;
                localPort = ~0;
                numLocalPorts = 1;
                serverPool = ptr;
        } else {
                if ( StrToAddrAndPortRange (ptr, &localAddr, protoName, &portRange) != 0 )
                        errx (1, "redirect_port: invalid local port range");

                localPort     = GETLOPORT(portRange);
                numLocalPorts = GETNUMPORTS(portRange);
                serverPool = NULL;
        }

/*
 * Extract public port and optionally address.
 */
        ptr = strtok (NULL, " \t");
        if (!ptr)
                errx (1, "redirect_port: missing public port");

        separator = strchr (ptr, ':');
        if (separator) {
                if (StrToAddrAndPortRange (ptr, &publicAddr, protoName, &portRange) != 0 )
                        errx (1, "redirect_port: invalid public port range");
        }
        else {
                publicAddr.s_addr = INADDR_ANY;
                if (StrToPortRange (ptr, protoName, &portRange) != 0)
                        errx (1, "redirect_port: invalid public port range");
        }

        publicPort     = GETLOPORT(portRange);
        numPublicPorts = GETNUMPORTS(portRange);

/*
 * Extract remote address and optionally port.
 */
        ptr = strtok (NULL, " \t");
        if (ptr) {
                separator = strchr (ptr, ':');
                if (separator) {
                        if (StrToAddrAndPortRange (ptr, &remoteAddr, protoName, &portRange) != 0)
                                errx (1, "redirect_port: invalid remote port range");
                } else {
                        SETLOPORT(portRange, 0);
                        SETNUMPORTS(portRange, 1);
                        StrToAddr (ptr, &remoteAddr);
                }
        }
        else {
                SETLOPORT(portRange, 0);
                SETNUMPORTS(portRange, 1);
                remoteAddr.s_addr = INADDR_ANY;
        }

        remotePort     = GETLOPORT(portRange);
        numRemotePorts = GETNUMPORTS(portRange);

/*
 * Make sure port ranges match up, then add the redirect ports.
 */
        if (numLocalPorts != numPublicPorts)
                errx (1, "redirect_port: port ranges must be equal in size");

        /* Remote port range is allowed to be '0' which means all ports. */
        if (numRemotePorts != numLocalPorts && (numRemotePorts != 1 || remotePort != 0))
                errx (1, "redirect_port: remote port must be 0 or equal to local port range in size");

        for (i = 0 ; i < numPublicPorts ; ++i) {
                /* If remotePort is all ports, set it to 0. */
                u_short remotePortCopy = remotePort + i;
                if (numRemotePorts == 1 && remotePort == 0)
                        remotePortCopy = 0;

                aliaslink = LibAliasRedirectPort (mla, localAddr,
                                                htons(localPort + i),
                                                remoteAddr,
                                                htons(remotePortCopy),
                                                publicAddr,
                                                htons(publicPort + i),
                                                proto);
        }

/*
 * Setup LSNAT server pool.
 */
        if (serverPool != NULL && aliaslink != NULL) {
                ptr = strtok(serverPool, ",");
                while (ptr != NULL) {
                        if (StrToAddrAndPortRange(ptr, &localAddr, protoName, &portRange) != 0)
                                errx(1, "redirect_port: invalid local port range");

                        localPort = GETLOPORT(portRange);
                        if (GETNUMPORTS(portRange) != 1)
                                errx(1, "redirect_port: local port must be single in this context");
                        LibAliasAddServer(mla, aliaslink, localAddr, htons(localPort));
                        ptr = strtok(NULL, ",");
                }
        }
        
        free (buf);
}

void
SetupProtoRedirect(const char* parms)
{
        char            *buf;
        char*           ptr;
        struct in_addr  localAddr;
        struct in_addr  publicAddr;
        struct in_addr  remoteAddr;
        int             proto;
        char*           protoName;
        struct protoent *protoent;

        buf = strdup (parms);
        if (!buf)
                errx (1, "redirect_port: strdup() failed");
/*
 * Extract protocol.
 */
        protoName = strtok(buf, " \t");
        if (!protoName)
                errx(1, "redirect_proto: missing protocol");

        protoent = getprotobyname(protoName);
        if (protoent == NULL)
                errx(1, "redirect_proto: unknown protocol %s", protoName);
        else
                proto = protoent->p_proto;
/*
 * Extract local address.
 */
        ptr = strtok(NULL, " \t");
        if (!ptr)
                errx(1, "redirect_proto: missing local address");
        else
                StrToAddr(ptr, &localAddr);
/*
 * Extract optional public address.
 */
        ptr = strtok(NULL, " \t");
        if (ptr)
                StrToAddr(ptr, &publicAddr);
        else
                publicAddr.s_addr = INADDR_ANY;
/*
 * Extract optional remote address.
 */
        ptr = strtok(NULL, " \t");
        if (ptr)
                StrToAddr(ptr, &remoteAddr);
        else
                remoteAddr.s_addr = INADDR_ANY;
/*
 * Create aliasing link.
 */
        (void)LibAliasRedirectProto(mla, localAddr, remoteAddr, publicAddr,
                                       proto);

        free (buf);
}

void SetupAddressRedirect (const char* parms)
{
        char            *buf;
        char*           ptr;
        char*           separator;
        struct in_addr  localAddr;
        struct in_addr  publicAddr;
        char*           serverPool;
        struct alias_link *aliaslink;

        buf = strdup (parms);
        if (!buf)
                errx (1, "redirect_port: strdup() failed");
/*
 * Extract local address.
 */
        ptr = strtok (buf, " \t");
        if (!ptr)
                errx (1, "redirect_address: missing local address");

        separator = strchr(ptr, ',');
        if (separator) {                /* LSNAT redirection syntax. */
                localAddr.s_addr = INADDR_NONE;
                serverPool = ptr;
        } else {
                StrToAddr (ptr, &localAddr);
                serverPool = NULL;
        }
/*
 * Extract public address.
 */
        ptr = strtok (NULL, " \t");
        if (!ptr)
                errx (1, "redirect_address: missing public address");

        StrToAddr (ptr, &publicAddr);
        aliaslink = LibAliasRedirectAddr(mla, localAddr, publicAddr);

/*
 * Setup LSNAT server pool.
 */
        if (serverPool != NULL && aliaslink != NULL) {
                ptr = strtok(serverPool, ",");
                while (ptr != NULL) {
                        StrToAddr(ptr, &localAddr);
                        LibAliasAddServer(mla, aliaslink, localAddr, htons(~0));
                        ptr = strtok(NULL, ",");
                }
        }

        free (buf);
}

void StrToAddr (const char* str, struct in_addr* addr)
{
        struct hostent* hp;

        if (inet_aton (str, addr))
                return;

        hp = gethostbyname (str);
        if (!hp)
                errx (1, "unknown host %s", str);

        memcpy (addr, hp->h_addr, sizeof (struct in_addr));
}

u_short StrToPort (const char* str, const char* proto)
{
        u_short         port;
        struct servent* sp;
        char*           end;

        port = strtol (str, &end, 10);
        if (end != str)
                return htons (port);

        sp = getservbyname (str, proto);
        if (!sp)
                errx (1, "%s/%s: unknown service", str, proto);

        return sp->s_port;
}

int StrToPortRange (const char* str, const char* proto, port_range *portRange)
{
        const char*     sep;
        struct servent* sp;
        char*           end;
        u_short         loPort;
        u_short         hiPort;
        
        /* First see if this is a service, return corresponding port if so. */
        sp = getservbyname (str,proto);
        if (sp) {
                SETLOPORT(*portRange, ntohs(sp->s_port));
                SETNUMPORTS(*portRange, 1);
                return 0;
        }
                
        /* Not a service, see if it's a single port or port range. */
        sep = strchr (str, '-');
        if (sep == NULL) {
                SETLOPORT(*portRange, strtol(str, &end, 10));
                if (end != str) {
                        /* Single port. */
                        SETNUMPORTS(*portRange, 1);
                        return 0;
                }

                /* Error in port range field. */
                errx (1, "%s/%s: unknown service", str, proto);
        }

        /* Port range, get the values and sanity check. */
        sscanf (str, "%hu-%hu", &loPort, &hiPort);
        SETLOPORT(*portRange, loPort);
        SETNUMPORTS(*portRange, 0);     /* Error by default */
        if (loPort <= hiPort)
                SETNUMPORTS(*portRange, hiPort - loPort + 1);

        if (GETNUMPORTS(*portRange) == 0)
                errx (1, "invalid port range %s", str);

        return 0;
}


static int
StrToProto (const char* str)
{
        if (!strcmp (str, "tcp"))
                return IPPROTO_TCP;

        if (!strcmp (str, "udp"))
                return IPPROTO_UDP;

        errx (1, "unknown protocol %s. Expected tcp or udp", str);
}

static int
StrToAddrAndPortRange (char* str, struct in_addr* addr, char* proto, port_range *portRange)
{
        char*   ptr;

        ptr = strchr (str, ':');
        if (!ptr)
                errx (1, "%s is missing port number", str);

        *ptr = '\0';
        ++ptr;

        StrToAddr (str, addr);
        return StrToPortRange (ptr, proto, portRange);
}

static void
SetupPunchFW(const char *strValue)
{
        unsigned int base, num;

        if (sscanf(strValue, "%u:%u", &base, &num) != 2)
                errx(1, "punch_fw: basenumber:count parameter required");

        if (CheckIpfwRulenum(base + num - 1) == -1)
                errx(1, "punch_fw: basenumber:count parameter should fit "
                        "the maximum allowed rule numbers");

        LibAliasSetFWBase(mla, base, num);
        (void)LibAliasSetMode(mla, PKT_ALIAS_PUNCH_FW, PKT_ALIAS_PUNCH_FW);
}

static void
SetupSkinnyPort(const char *strValue)
{
        unsigned int port;

        if (sscanf(strValue, "%u", &port) != 1)
                errx(1, "skinny_port: port parameter required");

        LibAliasSetSkinnyPort(mla, port);
}

static void
NewInstance(const char *name)
{
        struct instance *ip;

        LIST_FOREACH(ip, &root, list) {
                if (!strcmp(ip->name, name)) {
                        mla = ip->la;
                        mip = ip;
                        return;
                }
        }
        ninstance++;
        ip = calloc(1, sizeof(*ip));
        ip->name = strdup(name);
        ip->la = LibAliasInit (ip->la);
        ip->assignAliasAddr     = 0;
        ip->ifName              = NULL;
        ip->logDropped          = 0;
        ip->inPort              = 0;
        ip->outPort             = 0;
        ip->inOutPort           = 0;
        ip->aliasAddr.s_addr    = INADDR_NONE;
        ip->ifMTU               = -1;
        ip->aliasOverhead       = 12;
        LIST_INSERT_HEAD(&root, ip, list);
        mla = ip->la;
        mip = ip;
}

static int
CheckIpfwRulenum(unsigned int rnum)
{
        unsigned int default_rule;
        size_t len = sizeof(default_rule);

        if (sysctlbyname("net.inet.ip.fw.default_rule", &default_rule, &len,
                NULL, 0) == -1) {
                warn("Failed to get the default ipfw rule number, using "
                     "default historical value 65535.  The reason was");
                default_rule = 65535;
        }
        if (rnum >= default_rule) {
                return -1;
        }

        return 0;
}