root/sys/netinet/libalias/alias_db.c
/*-
 * SPDX-License-Identifier: BSD-2-Clause
 *
 * Copyright (c) 2001 Charles Mott <cm@linktel.net>
 * 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/cdefs.h>
#ifdef _KERNEL
#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/systm.h>
#include <sys/lock.h>
#include <sys/module.h>
#include <sys/rwlock.h>
#include <sys/stdarg.h>
#include <sys/syslog.h>
#else
#include <stdarg.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/errno.h>
#include <sys/time.h>
#include <unistd.h>
#endif

#include <sys/socket.h>
#include <netinet/tcp.h>

#ifdef _KERNEL
#include <netinet/libalias/alias.h>
#include <netinet/libalias/alias_local.h>
#include <netinet/libalias/alias_mod.h>
#include <net/if.h>
#else
#include "alias.h"
#include "alias_local.h"
#include "alias_mod.h"
#endif

#include "alias_db.h"

static LIST_HEAD(, libalias) instancehead = LIST_HEAD_INITIALIZER(instancehead);
int LibAliasTime;

/* Kernel module definition. */
#ifdef _KERNEL
MALLOC_DEFINE(M_ALIAS, "libalias", "packet aliasing");

MODULE_VERSION(libalias, 1);

static int
alias_mod_handler(module_t mod, int type, void *data)
{
        switch (type) {
        case MOD_QUIESCE:
        case MOD_UNLOAD:
                finishoff();
        case MOD_LOAD:
                return (0);
        default:
                return (EINVAL);
        }
}

static moduledata_t alias_mod = {
       "alias", alias_mod_handler, NULL
};

DECLARE_MODULE(alias, alias_mod, SI_SUB_DRIVERS, SI_ORDER_SECOND);
#endif

SPLAY_GENERATE(splay_out, alias_link, all.out, cmp_out);
SPLAY_GENERATE(splay_in, group_in, in, cmp_in);
SPLAY_GENERATE(splay_internal_endpoint, alias_link, all.internal_endpoint,
    cmp_internal_endpoint);

static struct group_in *
StartPointIn(struct libalias *la,
    struct in_addr alias_addr, u_short alias_port, int link_type,
    int create)
{
        struct group_in *grp;
        struct group_in needle = {
                .alias_addr = alias_addr,
                .alias_port = alias_port,
                .link_type = link_type
        };

        grp = SPLAY_FIND(splay_in, &la->linkSplayIn, &needle);
        if (grp != NULL || !create || (grp = malloc(sizeof(*grp))) == NULL)
                return (grp);
        grp->alias_addr = alias_addr;
        grp->alias_port = alias_port;
        grp->link_type = link_type;
        LIST_INIT(&grp->full);
        LIST_INIT(&grp->partial);
        SPLAY_INSERT(splay_in, &la->linkSplayIn, grp);
        return (grp);
}

static int
SeqDiff(u_long x, u_long y)
{
/* Return the difference between two TCP sequence numbers
 * This function is encapsulated in case there are any unusual
 * arithmetic conditions that need to be considered.
 */
        return (ntohl(y) - ntohl(x));
}

#ifdef _KERNEL
static void
AliasLog(char *str, const char *format, ...)
{
        va_list ap;

        va_start(ap, format);
        vsnprintf(str, LIBALIAS_BUF_SIZE, format, ap);
        va_end(ap);
}
#else
static void
AliasLog(FILE *stream, const char *format, ...)
{
        va_list ap;

        va_start(ap, format);
        vfprintf(stream, format, ap);
        va_end(ap);
        fflush(stream);
}
#endif

static void
ShowAliasStats(struct libalias *la)
{
        LIBALIAS_LOCK_ASSERT(la);
        /* Used for debugging */
        if (la->logDesc) {
                int tot  = la->icmpLinkCount + la->udpLinkCount +
                    (la->sctpLinkCount>>1) + /* sctp counts half associations */
                    la->tcpLinkCount + la->pptpLinkCount +
                    la->protoLinkCount + la->fragmentIdLinkCount +
                    la->fragmentPtrLinkCount;

                AliasLog(la->logDesc,
                    "icmp=%u, udp=%u, tcp=%u, sctp=%u, pptp=%u, proto=%u, frag_id=%u frag_ptr=%u / tot=%u",
                    la->icmpLinkCount,
                    la->udpLinkCount,
                    la->tcpLinkCount,
                    la->sctpLinkCount>>1, /* sctp counts half associations */
                    la->pptpLinkCount,
                    la->protoLinkCount,
                    la->fragmentIdLinkCount,
                    la->fragmentPtrLinkCount,
                    tot);
#ifndef _KERNEL
                AliasLog(la->logDesc, " (sock=%u)\n", la->sockCount);
#endif
        }
}

void SctpShowAliasStats(struct libalias *la)
{
        ShowAliasStats(la);
}

/* get random port in network byte order */
static u_short
_RandomPort(struct libalias *la) {
        u_short port;

        port = la->aliasPortLower +
            arc4random_uniform(la->aliasPortLength);

        return ntohs(port);
}

/* GetNewPort() allocates port numbers.  Note that if a port number
   is already in use, that does not mean that it cannot be used by
   another link concurrently.  This is because GetNewPort() looks for
   unused triplets: (dest addr, dest port, alias port). */

static int
GetNewPort(struct libalias *la, struct alias_link *lnk, int alias_port_param)
{
        int i;
        int max_trials;
        u_short port;

        LIBALIAS_LOCK_ASSERT(la);
        /*
         * Description of alias_port_param for GetNewPort().  When
         * this parameter is zero or positive, it precisely specifies
         * the port number.  GetNewPort() will return this number
         * without check that it is in use.
        *
         * The aliasing port is automatically selected by one of
         * two methods below:
         *
         * When this parameter is GET_ALIAS_PORT, it indicates to get
         * a randomly selected port number.
         */
        if (alias_port_param >= 0 && alias_port_param < 0x10000) {
                lnk->alias_port = (u_short) alias_port_param;
                return (0);
        }
        if (alias_port_param != GET_ALIAS_PORT) {
#ifdef LIBALIAS_DEBUG
                fprintf(stderr, "PacketAlias/GetNewPort(): ");
                fprintf(stderr, "input parameter error\n");
#endif
                return (-1);
        }

        max_trials = GET_NEW_PORT_MAX_ATTEMPTS;

        if ((la->packetAliasMode & PKT_ALIAS_UDP_EIM) &&
            lnk->link_type == LINK_UDP) {
                /* Try reuse the same alias address:port for all destinations
                 * from the same internal address:port, as per RFC 4787.
                 */
                struct alias_link *search_result = FindLinkByInternalEndpoint(
                    la, lnk->src_addr, lnk->src_port, lnk->link_type);
                if (search_result != NULL) {
                        lnk->alias_port = search_result->alias_port;
                        return (0);
                }
        }

        /*
         * When the PKT_ALIAS_SAME_PORTS option is chosen,
         * the first try will be the actual source port. If
         * this is already in use, the remainder of the
         * trials will be random.
         */
        port = (la->packetAliasMode & PKT_ALIAS_SAME_PORTS)
            ? lnk->src_port
            : _RandomPort(la);

        /* Port number search */
        for (i = 0; i < max_trials; i++, port = _RandomPort(la)) {
                struct group_in *grp;
                struct alias_link *search_result;

                grp = StartPointIn(la, lnk->alias_addr, port, lnk->link_type, 0);
                if (grp == NULL)
                        break;

                /* As per RFC 4787, UDP cannot share the same alias port among
                 * multiple internal endpoints
                 */
                if ((la->packetAliasMode & PKT_ALIAS_UDP_EIM) &&
                    lnk->link_type == LINK_UDP)
                        continue;

                LIST_FOREACH(search_result, &grp->full, all.in) {
                        if (lnk->dst_addr.s_addr ==
                            search_result->dst_addr.s_addr &&
                            lnk->dst_port == search_result->dst_port)
                                break;     /* found match */
                }
                if (search_result == NULL)
                        break;
        }

        if (i >= max_trials) {
#ifdef LIBALIAS_DEBUG
                fprintf(stderr, "PacketAlias/GetNewPort(): ");
                fprintf(stderr, "could not find free port\n");
#endif
                return (-1);
        }

#ifndef NO_USE_SOCKETS
        if ((la->packetAliasMode & PKT_ALIAS_USE_SOCKETS) &&
            (lnk->flags & LINK_PARTIALLY_SPECIFIED) &&
            ((lnk->link_type == LINK_TCP) ||
             (lnk->link_type == LINK_UDP))) {
                if (!GetSocket(la, port, &lnk->sockfd, lnk->link_type)) {
                        return (-1);
                }
        }
#endif
        lnk->alias_port = port;

        return (0);
}

#ifndef NO_USE_SOCKETS
static          u_short
GetSocket(struct libalias *la, u_short port_net, int *sockfd, int link_type)
{
        int err;
        int sock;
        struct sockaddr_in sock_addr;

        LIBALIAS_LOCK_ASSERT(la);
        if (link_type == LINK_TCP)
                sock = socket(AF_INET, SOCK_STREAM, 0);
        else if (link_type == LINK_UDP)
                sock = socket(AF_INET, SOCK_DGRAM, 0);
        else {
#ifdef LIBALIAS_DEBUG
                fprintf(stderr, "PacketAlias/GetSocket(): ");
                fprintf(stderr, "incorrect link type\n");
#endif
                return (0);
        }

        if (sock < 0) {
#ifdef LIBALIAS_DEBUG
                fprintf(stderr, "PacketAlias/GetSocket(): ");
                fprintf(stderr, "socket() error %d\n", *sockfd);
#endif
                return (0);
        }
        sock_addr.sin_family = AF_INET;
        sock_addr.sin_addr.s_addr = htonl(INADDR_ANY);
        sock_addr.sin_port = port_net;

        err = bind(sock,
            (struct sockaddr *)&sock_addr,
            sizeof(sock_addr));
        if (err == 0) {
                la->sockCount++;
                *sockfd = sock;
                return (1);
        } else {
                close(sock);
                return (0);
        }
}
#endif

/* FindNewPortGroup() returns a base port number for an available
   range of contiguous port numbers. Note that if a port number
   is already in use, that does not mean that it cannot be used by
   another link concurrently.  This is because FindNewPortGroup()
   looks for unused triplets: (dest addr, dest port, alias port). */

int
FindNewPortGroup(struct libalias *la,
    struct in_addr dst_addr,
    struct in_addr alias_addr,
    u_short src_port,
    u_short dst_port,
    u_short port_count,
    u_char proto,
    u_char align)
{
        int i, j;
        int max_trials;
        u_short port;
        int link_type;

        LIBALIAS_LOCK_ASSERT(la);
        /*
         * Get link_type from protocol
         */

        switch (proto) {
        case IPPROTO_UDP:
                link_type = LINK_UDP;
                break;
        case IPPROTO_TCP:
                link_type = LINK_TCP;
                break;
        default:
                return (0);
                break;
        }

        /*
         * The aliasing port is automatically selected by one of two
         * methods below:
         */
        max_trials = GET_NEW_PORT_MAX_ATTEMPTS;

        if (la->packetAliasMode & PKT_ALIAS_SAME_PORTS) {
                /*
                 * When the ALIAS_SAME_PORTS option is chosen, the first
                 * try will be the actual source port. If this is already
                 * in use, the remainder of the trials will be random.
                 */
                port = src_port;

        } else {
                port = _RandomPort(la);
        }

        /* Port number search */
        for (i = 0; i < max_trials; i++, port = _RandomPort(la)) {
                struct alias_link *search_result;

                if (align)
                        port &= htons(0xfffe);

                for (j = 0; j < port_count; j++) {
                        u_short port_j = ntohs(port) + j;

                        if ((search_result = FindLinkIn(la, dst_addr,
                            alias_addr, dst_port, htons(port_j),
                            link_type, 0)) != NULL)
                                break;
                }

                /* Found a good range, return base */
                if (j == port_count)
                        return (port);
        }

#ifdef LIBALIAS_DEBUG
        fprintf(stderr, "PacketAlias/FindNewPortGroup(): ");
        fprintf(stderr, "could not find free port(s)\n");
#endif

        return (0);
}

static void
CleanupAliasData(struct libalias *la, int deletePermanent)
{
        struct alias_link *lnk, *lnk_tmp;

        LIBALIAS_LOCK_ASSERT(la);

        /* permanent entries may stay */
        TAILQ_FOREACH_SAFE(lnk, &la->checkExpire, expire.list, lnk_tmp)
                DeleteLink(&lnk, deletePermanent);
}
static void
CleanupLink(struct libalias *la, struct alias_link **lnk, int deletePermanent)
{
        LIBALIAS_LOCK_ASSERT(la);

        if (lnk == NULL || *lnk == NULL)
                return;

        if (LibAliasTime - (*lnk)->timestamp > (*lnk)->expire.time) {
                DeleteLink(lnk, deletePermanent);
                if ((*lnk) == NULL)
                        return;
        }

        /* move to end, swap may fail on a single entry list */
        TAILQ_REMOVE(&la->checkExpire, (*lnk), expire.list);
        TAILQ_INSERT_TAIL(&la->checkExpire, (*lnk), expire.list);
}

static struct alias_link *
UseLink(struct libalias *la, struct alias_link *lnk)
{
        CleanupLink(la, &lnk, 0);
        if (lnk != NULL)
                lnk->timestamp = LibAliasTime;
        return (lnk);
}

static void
DeleteLink(struct alias_link **plnk, int deletePermanent)
{
        struct alias_link *lnk = *plnk;
        struct libalias *la = lnk->la;

        LIBALIAS_LOCK_ASSERT(la);
        /* Don't do anything if the link is marked permanent */
        if (!deletePermanent && (lnk->flags & LINK_PERMANENT))
                return;

#ifndef NO_FW_PUNCH
        /* Delete associated firewall hole, if any */
        ClearFWHole(lnk);
#endif

        switch (lnk->link_type) {
        case LINK_PPTP:
                LIST_REMOVE(lnk, pptp.list);
                break;
        default: {
                struct group_in *grp;

                /* Free memory allocated for LSNAT server pool */
                if (lnk->server != NULL) {
                        struct server *head, *curr, *next;

                        head = curr = lnk->server;
                        do {
                                next = curr->next;
                                free(curr);
                        } while ((curr = next) != head);
                } else {
                        /* Adjust output table pointers */
                        SPLAY_REMOVE(splay_out, &la->linkSplayOut, lnk);
                }

                /* Adjust input table pointers */
                LIST_REMOVE(lnk, all.in);

                /* Adjust "internal endpoint" table pointer */
                SPLAY_REMOVE(splay_internal_endpoint,
                    &la->linkSplayInternalEndpoint, lnk);

                /* Remove intermediate node, if empty */
                grp = StartPointIn(la, lnk->alias_addr, lnk->alias_port, lnk->link_type, 0);
                if (grp != NULL &&
                    LIST_EMPTY(&grp->full) &&
                    LIST_EMPTY(&grp->partial)) {
                        SPLAY_REMOVE(splay_in, &la->linkSplayIn, grp);
                        free(grp);
                }
        }
                break;
        }

        /* remove from housekeeping */
        TAILQ_REMOVE(&la->checkExpire, lnk, expire.list);

#ifndef NO_USE_SOCKETS
        /* Close socket, if one has been allocated */
        if (lnk->sockfd != -1) {
                la->sockCount--;
                close(lnk->sockfd);
        }
#endif
        /* Link-type dependent cleanup */
        switch (lnk->link_type) {
        case LINK_ICMP:
                la->icmpLinkCount--;
                break;
        case LINK_UDP:
                la->udpLinkCount--;
                break;
        case LINK_TCP:
                la->tcpLinkCount--;
                free(lnk->data.tcp);
                break;
        case LINK_PPTP:
                la->pptpLinkCount--;
                break;
        case LINK_FRAGMENT_ID:
                la->fragmentIdLinkCount--;
                break;
        case LINK_FRAGMENT_PTR:
                la->fragmentPtrLinkCount--;
                if (lnk->data.frag_ptr != NULL)
                        free(lnk->data.frag_ptr);
                break;
        case LINK_ADDR:
                break;
        default:
                la->protoLinkCount--;
                break;
        }

        /* Free memory */
        free(lnk);
        *plnk = NULL;

        /* Write statistics, if logging enabled */
        if (la->packetAliasMode & PKT_ALIAS_LOG) {
                ShowAliasStats(la);
        }
}

struct alias_link *
AddLink(struct libalias *la, struct in_addr src_addr, struct in_addr dst_addr,
    struct in_addr alias_addr, u_short src_port, u_short dst_port,
    int alias_port_param, int link_type)
{
        struct alias_link *lnk;

        LIBALIAS_LOCK_ASSERT(la);

        lnk = malloc(sizeof(struct alias_link));
        if (lnk == NULL) {
#ifdef LIBALIAS_DEBUG
                fprintf(stderr, "PacketAlias/AddLink(): ");
                fprintf(stderr, "malloc() call failed.\n");
#endif
                return (NULL);
        }
        /* Basic initialization */
        lnk->la = la;
        lnk->src_addr = src_addr;
        lnk->dst_addr = dst_addr;
        lnk->alias_addr = alias_addr;
        lnk->proxy_addr.s_addr = INADDR_ANY;
        lnk->src_port = src_port;
        lnk->dst_port = dst_port;
        lnk->proxy_port = 0;
        lnk->server = NULL;
        lnk->link_type = link_type;
#ifndef NO_USE_SOCKETS
        lnk->sockfd = -1;
#endif
        lnk->flags = 0;
        lnk->pflags = 0;
        lnk->timestamp = LibAliasTime;

        /* Expiration time */
        switch (link_type) {
        case LINK_ICMP:
                lnk->expire.time = ICMP_EXPIRE_TIME;
                break;
        case LINK_UDP:
                lnk->expire.time = UDP_EXPIRE_TIME;
                break;
        case LINK_TCP:
                lnk->expire.time = TCP_EXPIRE_INITIAL;
                break;
        case LINK_FRAGMENT_ID:
                lnk->expire.time = FRAGMENT_ID_EXPIRE_TIME;
                break;
        case LINK_FRAGMENT_PTR:
                lnk->expire.time = FRAGMENT_PTR_EXPIRE_TIME;
                break;
        default:
                lnk->expire.time = PROTO_EXPIRE_TIME;
                break;
        }

        /* Determine alias flags */
        if (dst_addr.s_addr == INADDR_ANY)
                lnk->flags |= LINK_UNKNOWN_DEST_ADDR;
        if (dst_port == 0)
                lnk->flags |= LINK_UNKNOWN_DEST_PORT;

        /* Determine alias port */
        if (GetNewPort(la, lnk, alias_port_param) != 0) {
                free(lnk);
                return (NULL);
        }
        /* Link-type dependent initialization */
        switch (link_type) {
        case LINK_ICMP:
                la->icmpLinkCount++;
                break;
        case LINK_UDP:
                la->udpLinkCount++;
                break;
        case LINK_TCP: {
                struct tcp_dat *aux_tcp;
                int i;

                aux_tcp = malloc(sizeof(struct tcp_dat));
                if (aux_tcp == NULL) {
#ifdef LIBALIAS_DEBUG
                        fprintf(stderr, "PacketAlias/AddLink: ");
                        fprintf(stderr, " cannot allocate auxiliary TCP data\n");
#endif
                        free(lnk);
                        return (NULL);
                }

                la->tcpLinkCount++;
                aux_tcp->state.in = ALIAS_TCP_STATE_NOT_CONNECTED;
                aux_tcp->state.out = ALIAS_TCP_STATE_NOT_CONNECTED;
                aux_tcp->state.index = 0;
                aux_tcp->state.ack_modified = 0;
                for (i = 0; i < N_LINK_TCP_DATA; i++)
                        aux_tcp->ack[i].active = 0;
                aux_tcp->fwhole = -1;
                lnk->data.tcp = aux_tcp;
        }
                break;
        case LINK_PPTP:
                la->pptpLinkCount++;
                break;
        case LINK_FRAGMENT_ID:
                la->fragmentIdLinkCount++;
                break;
        case LINK_FRAGMENT_PTR:
                la->fragmentPtrLinkCount++;
                break;
        case LINK_ADDR:
                break;
        default:
                la->protoLinkCount++;
                break;
        }

        switch (link_type) {
        case LINK_PPTP:
                LIST_INSERT_HEAD(&la->pptpList, lnk, pptp.list);
                break;
        default: {
                struct group_in *grp;

                grp = StartPointIn(la, alias_addr, lnk->alias_port, link_type, 1);
                if (grp == NULL) {
                        free(lnk);
                        return (NULL);
                }

                /* Set up pointers for output lookup table */
                SPLAY_INSERT(splay_out, &la->linkSplayOut, lnk);

                /* Set up pointers for input lookup table */
                if (lnk->flags & LINK_PARTIALLY_SPECIFIED)
                        LIST_INSERT_HEAD(&grp->partial, lnk, all.in);
                else
                        LIST_INSERT_HEAD(&grp->full, lnk, all.in);

                /* Set up pointers for "internal endpoint" lookup table */
                SPLAY_INSERT(splay_internal_endpoint,
                    &la->linkSplayInternalEndpoint, lnk);
        }
                break;
        }

        /* Include the element into the housekeeping list */
        TAILQ_INSERT_TAIL(&la->checkExpire, lnk, expire.list);

        if (la->packetAliasMode & PKT_ALIAS_LOG)
                ShowAliasStats(la);

        return (lnk);
}

/*
 * If alias_port_param is less than zero, alias port will be automatically
 * chosen. If greater than zero, equal to alias port
 */
static struct alias_link *
ReLink(struct alias_link *old_lnk,
    struct in_addr src_addr,
    struct in_addr dst_addr,
    struct in_addr alias_addr,
    u_short src_port,
    u_short dst_port,
    int alias_port_param,
    int link_type,
    int deletePermanent)
{
        struct alias_link *new_lnk;
        struct libalias *la = old_lnk->la;

        LIBALIAS_LOCK_ASSERT(la);
        new_lnk = AddLink(la, src_addr, dst_addr, alias_addr,
            src_port, dst_port, alias_port_param,
            link_type);
#ifndef NO_FW_PUNCH
        if (new_lnk != NULL &&
            old_lnk->link_type == LINK_TCP &&
            old_lnk->data.tcp->fwhole > 0) {
                PunchFWHole(new_lnk);
        }
#endif
        DeleteLink(&old_lnk, deletePermanent);
        return (new_lnk);
}

static struct alias_link *
_SearchLinkOut(struct libalias *la, struct in_addr src_addr,
    struct in_addr dst_addr,
    u_short src_port,
    u_short dst_port,
    int link_type) {
        struct alias_link *lnk;
        struct alias_link needle = {
                .src_addr = src_addr,
                .dst_addr = dst_addr,
                .src_port = src_port,
                .dst_port = dst_port,
                .link_type = link_type
        };

        lnk = SPLAY_FIND(splay_out, &la->linkSplayOut, &needle);
        return (UseLink(la, lnk));
}

static struct alias_link *
_FindLinkOut(struct libalias *la, struct in_addr src_addr,
    struct in_addr dst_addr,
    u_short src_port,
    u_short dst_port,
    int link_type,
    int replace_partial_links)
{
        struct alias_link *lnk;

        LIBALIAS_LOCK_ASSERT(la);
        lnk = _SearchLinkOut(la, src_addr, dst_addr, src_port, dst_port, link_type);
        if (lnk != NULL || !replace_partial_links)
                return (lnk);

        /* Search for partially specified links. */
        if (dst_port != 0 && dst_addr.s_addr != INADDR_ANY) {
                lnk = _SearchLinkOut(la, src_addr, dst_addr, src_port, 0,
                    link_type);
                if (lnk == NULL)
                        lnk = _SearchLinkOut(la, src_addr, ANY_ADDR, src_port,
                            dst_port, link_type);
        }
        if (lnk == NULL &&
            (dst_port != 0 || dst_addr.s_addr != INADDR_ANY)) {
                lnk = _SearchLinkOut(la, src_addr, ANY_ADDR, src_port, 0,
                    link_type);
        }
        if (lnk != NULL) {
                lnk = ReLink(lnk,
                    src_addr, dst_addr, lnk->alias_addr,
                    src_port, dst_port, lnk->alias_port,
                    link_type, 0);
        }
        return (lnk);
}

static struct alias_link *
FindLinkOut(struct libalias *la, struct in_addr src_addr,
    struct in_addr dst_addr,
    u_short src_port,
    u_short dst_port,
    int link_type,
    int replace_partial_links)
{
        struct alias_link *lnk;

        LIBALIAS_LOCK_ASSERT(la);
        lnk = _FindLinkOut(la, src_addr, dst_addr, src_port, dst_port,
            link_type, replace_partial_links);

        if (lnk == NULL) {
                /*
                 * The following allows permanent links to be specified as
                 * using the default source address (i.e. device interface
                 * address) without knowing in advance what that address
                 * is.
                 */
                if (la->aliasAddress.s_addr != INADDR_ANY &&
                    src_addr.s_addr == la->aliasAddress.s_addr) {
                        lnk = _FindLinkOut(la, ANY_ADDR, dst_addr, src_port, dst_port,
                            link_type, replace_partial_links);
                }
        }
        return (lnk);
}

static struct alias_link *
_FindLinkIn(struct libalias *la, struct in_addr dst_addr,
    struct in_addr alias_addr,
    u_short dst_port,
    u_short alias_port,
    int link_type,
    int replace_partial_links)
{
        int flags_in;
        struct group_in *grp;
        struct alias_link *lnk;
        struct alias_link *lnk_unknown_all;
        struct alias_link *lnk_unknown_dst_addr;
        struct alias_link *lnk_unknown_dst_port;
        struct in_addr src_addr;
        u_short src_port;

        LIBALIAS_LOCK_ASSERT(la);
        /* Initialize pointers */
        lnk_unknown_all = NULL;
        lnk_unknown_dst_addr = NULL;
        lnk_unknown_dst_port = NULL;

        /* If either the dest addr or port is unknown, the search
         * loop will have to know about this. */
        flags_in = 0;
        if (dst_addr.s_addr == INADDR_ANY)
                flags_in |= LINK_UNKNOWN_DEST_ADDR;
        if (dst_port == 0)
                flags_in |= LINK_UNKNOWN_DEST_PORT;

        /* Search loop */
        grp = StartPointIn(la, alias_addr, alias_port, link_type, 0);
        if (grp == NULL)
                return (NULL);

        switch (flags_in) {
        case 0:
                LIST_FOREACH(lnk, &grp->full, all.in) {
                        if (lnk->dst_addr.s_addr == dst_addr.s_addr &&
                            lnk->dst_port == dst_port) {
                                struct alias_link *found;

                                found = UseLink(la, lnk);
                                if (found != NULL)
                                        return (found);
                                /* link expired */
                                grp = StartPointIn(la, alias_addr, alias_port, link_type, 0);
                                if (grp == NULL)
                                        return (NULL);
                                break;
                        }
                }
                break;
        case LINK_UNKNOWN_DEST_PORT:
                LIST_FOREACH(lnk, &grp->full, all.in) {
                        if(lnk->dst_addr.s_addr == dst_addr.s_addr) {
                                lnk_unknown_dst_port = lnk;
                                break;
                        }
                }
                break;
        case LINK_UNKNOWN_DEST_ADDR:
                LIST_FOREACH(lnk, &grp->full, all.in) {
                        if(lnk->dst_port == dst_port) {
                                lnk_unknown_dst_addr = lnk;
                                break;
                        }
                }
                break;
        case LINK_PARTIALLY_SPECIFIED:
                lnk_unknown_all = LIST_FIRST(&grp->full);
                break;
        }

        if (lnk_unknown_dst_port == NULL) {
                LIST_FOREACH(lnk, &grp->partial, all.in) {
                        int flags = (flags_in | lnk->flags) & LINK_PARTIALLY_SPECIFIED;

                        if (flags == LINK_PARTIALLY_SPECIFIED &&
                            lnk_unknown_all == NULL)
                                lnk_unknown_all = lnk;
                        if (flags == LINK_UNKNOWN_DEST_ADDR &&
                            lnk->dst_port == dst_port &&
                            lnk_unknown_dst_addr == NULL)
                                lnk_unknown_dst_addr = lnk;
                        if (flags == LINK_UNKNOWN_DEST_PORT &&
                            lnk->dst_addr.s_addr == dst_addr.s_addr) {
                                lnk_unknown_dst_port = lnk;
                                break;
                        }
                }
        }

        lnk = (lnk_unknown_dst_port != NULL) ? lnk_unknown_dst_port
            : (lnk_unknown_dst_addr != NULL) ? lnk_unknown_dst_addr
            : lnk_unknown_all;

        if (lnk == NULL || !replace_partial_links)
                return (lnk);

        if (lnk->server != NULL) {      /* LSNAT link */
                src_addr = lnk->server->addr;
                src_port = lnk->server->port;
                lnk->server = lnk->server->next;
        } else {
                src_addr = lnk->src_addr;
                src_port = lnk->src_port;
        }

        if (link_type == LINK_SCTP) {
                lnk->src_addr = src_addr;
                lnk->src_port = src_port;
        } else {
                lnk = ReLink(lnk,
                    src_addr, dst_addr, alias_addr,
                    src_port, dst_port, alias_port,
                    link_type, 0);
        }
        return (lnk);
}

static struct alias_link *
FindLinkIn(struct libalias *la, struct in_addr dst_addr,
    struct in_addr alias_addr,
    u_short dst_port,
    u_short alias_port,
    int link_type,
    int replace_partial_links)
{
        struct alias_link *lnk;

        LIBALIAS_LOCK_ASSERT(la);
        lnk = _FindLinkIn(la, dst_addr, alias_addr, dst_port, alias_port,
            link_type, replace_partial_links);

        if (lnk == NULL &&
            (la->packetAliasMode & PKT_ALIAS_UDP_EIM) &&
            link_type == LINK_UDP &&
            !(la->packetAliasMode & PKT_ALIAS_DENY_INCOMING)) {
                lnk = _FindLinkIn(la, ANY_ADDR, alias_addr, 0, alias_port,
                    link_type, replace_partial_links);
        }

        if (lnk == NULL) {
                /*
                 * The following allows permanent links to be specified as
                 * using the default aliasing address (i.e. device
                 * interface address) without knowing in advance what that
                 * address is.
                 */
                if (la->aliasAddress.s_addr != INADDR_ANY &&
                    alias_addr.s_addr == la->aliasAddress.s_addr) {
                        lnk = _FindLinkIn(la, dst_addr, ANY_ADDR, dst_port, alias_port,
                            link_type, replace_partial_links);
                }
        }
        return (lnk);
}

static struct alias_link *
FindLinkByInternalEndpoint(struct libalias *la, struct in_addr src_addr,
    u_short src_port,
    int link_type)
{
        struct alias_link needle = {
                .src_addr = src_addr,
                .src_port = src_port,
                .link_type = link_type
        };
        LIBALIAS_LOCK_ASSERT(la);
        return SPLAY_FIND(splay_internal_endpoint, &la->linkSplayInternalEndpoint, &needle);
}

/* External routines for finding/adding links

-- "external" means outside alias_db.c, but within alias*.c --

    FindIcmpIn(), FindIcmpOut()
    FindFragmentIn1(), FindFragmentIn2()
    AddFragmentPtrLink(), FindFragmentPtr()
    FindProtoIn(), FindProtoOut()
    FindUdpTcpIn(), FindUdpTcpOut()
    AddPptp(), FindPptpOutByCallId(), FindPptpInByCallId(),
    FindPptpOutByPeerCallId(), FindPptpInByPeerCallId()
    FindOriginalAddress(), FindAliasAddress()

(prototypes in alias_local.h)
*/

int
FindIcmpIn(struct libalias *la, struct in_addr dst_addr,
    struct in_addr alias_addr,
    u_short id_alias,
    int create,
    struct alias_link **lnkp)
{
        struct alias_link *lnk;

        LIBALIAS_LOCK_ASSERT(la);

        *lnkp = NULL;

        lnk = FindLinkIn(la, dst_addr, alias_addr,
            NO_DEST_PORT, id_alias,
            LINK_ICMP, 0);
        if (lnk == NULL && create && !(la->packetAliasMode & PKT_ALIAS_DENY_INCOMING)) {
                struct in_addr target_addr;

                target_addr = FindOriginalAddress(la, alias_addr);
                lnk = AddLink(la, target_addr, dst_addr, alias_addr,
                    id_alias, NO_DEST_PORT, id_alias,
                    LINK_ICMP);
                if (lnk == NULL)
                        return (PKT_ALIAS_ERROR);
        }
        *lnkp = lnk;
        return (lnk != NULL ? PKT_ALIAS_OK : PKT_ALIAS_IGNORED);
}

int
FindIcmpOut(struct libalias *la, struct in_addr src_addr,
    struct in_addr dst_addr,
    u_short id,
    int create,
    struct alias_link **lnkp)
{
        struct alias_link *lnk;

        LIBALIAS_LOCK_ASSERT(la);

        *lnkp = NULL;

        lnk = FindLinkOut(la, src_addr, dst_addr,
            id, NO_DEST_PORT,
            LINK_ICMP, 0);
        if (lnk == NULL && create) {
                struct in_addr alias_addr;

                alias_addr = FindAliasAddress(la, src_addr);
                lnk = AddLink(la, src_addr, dst_addr, alias_addr,
                    id, NO_DEST_PORT, GET_ALIAS_ID,
                    LINK_ICMP);
                if (lnk == NULL)
                        return (PKT_ALIAS_ERROR);
        }
        *lnkp = lnk;
        return (lnk != NULL ? PKT_ALIAS_OK : PKT_ALIAS_IGNORED);
}

struct alias_link *
FindFragmentIn1(struct libalias *la, struct in_addr dst_addr,
    struct in_addr alias_addr,
    u_short ip_id)
{
        struct alias_link *lnk;

        LIBALIAS_LOCK_ASSERT(la);
        lnk = FindLinkIn(la, dst_addr, alias_addr,
            NO_DEST_PORT, ip_id,
            LINK_FRAGMENT_ID, 0);

        if (lnk == NULL) {
                lnk = AddLink(la, ANY_ADDR, dst_addr, alias_addr,
                    NO_SRC_PORT, NO_DEST_PORT, ip_id,
                    LINK_FRAGMENT_ID);
        }
        return (lnk);
}

/* Doesn't add a link if one is not found. */
struct alias_link *
FindFragmentIn2(struct libalias *la, struct in_addr dst_addr,
    struct in_addr alias_addr, u_short ip_id)
{
        LIBALIAS_LOCK_ASSERT(la);
        return FindLinkIn(la, dst_addr, alias_addr,
            NO_DEST_PORT, ip_id,
            LINK_FRAGMENT_ID, 0);
}

struct alias_link *
AddFragmentPtrLink(struct libalias *la, struct in_addr dst_addr,
    u_short ip_id)
{
        LIBALIAS_LOCK_ASSERT(la);
        return AddLink(la, ANY_ADDR, dst_addr, ANY_ADDR,
            NO_SRC_PORT, NO_DEST_PORT, ip_id,
            LINK_FRAGMENT_PTR);
}

struct alias_link *
FindFragmentPtr(struct libalias *la, struct in_addr dst_addr,
    u_short ip_id)
{
        LIBALIAS_LOCK_ASSERT(la);
        return FindLinkIn(la, dst_addr, ANY_ADDR,
            NO_DEST_PORT, ip_id,
            LINK_FRAGMENT_PTR, 0);
}

int
FindProtoIn(struct libalias *la, struct in_addr dst_addr,
    struct in_addr alias_addr,
    u_char proto,
    struct alias_link **lnkp)
{
        struct alias_link *lnk;

        LIBALIAS_LOCK_ASSERT(la);

        *lnkp = NULL;

        lnk = FindLinkIn(la, dst_addr, alias_addr,
            NO_DEST_PORT, 0,
            proto, 1);
        if (lnk == NULL && !(la->packetAliasMode & PKT_ALIAS_DENY_INCOMING)) {
                struct in_addr target_addr;

                target_addr = FindOriginalAddress(la, alias_addr);
                lnk = AddLink(la, target_addr, dst_addr, alias_addr,
                    NO_SRC_PORT, NO_DEST_PORT, 0,
                    proto);
                if (lnk == NULL)
                        return (PKT_ALIAS_ERROR);
        }
        *lnkp = lnk;
        return (lnk != NULL ? PKT_ALIAS_OK : PKT_ALIAS_IGNORED);
}

int
FindProtoOut(struct libalias *la, struct in_addr src_addr,
    struct in_addr dst_addr,
    u_char proto,
    struct alias_link **lnkp)
{
        struct alias_link *lnk;

        LIBALIAS_LOCK_ASSERT(la);

        *lnkp = NULL;

        lnk = FindLinkOut(la, src_addr, dst_addr,
            NO_SRC_PORT, NO_DEST_PORT,
            proto, 1);
        if (lnk == NULL) {
                struct in_addr alias_addr;

                alias_addr = FindAliasAddress(la, src_addr);
                lnk = AddLink(la, src_addr, dst_addr, alias_addr,
                    NO_SRC_PORT, NO_DEST_PORT, 0,
                    proto);
                if (lnk == NULL)
                        return (PKT_ALIAS_ERROR);
        }
        *lnkp = lnk;
        return (lnk != NULL ? PKT_ALIAS_OK : PKT_ALIAS_IGNORED);
}

int
FindUdpTcpIn(struct libalias *la, struct in_addr dst_addr,
    struct in_addr alias_addr,
    u_short dst_port,
    u_short alias_port,
    u_char proto,
    int create,
    struct alias_link **lnkp)
{
        int link_type;
        struct alias_link *lnk;

        LIBALIAS_LOCK_ASSERT(la);

        *lnkp = NULL;

        switch (proto) {
        case IPPROTO_UDP:
                link_type = LINK_UDP;
                break;
        case IPPROTO_TCP:
                link_type = LINK_TCP;
                break;
        default:
                return (PKT_ALIAS_IGNORED);
        }

        lnk = FindLinkIn(la, dst_addr, alias_addr,
            dst_port, alias_port,
            link_type, create);

        if (lnk == NULL && create && !(la->packetAliasMode & PKT_ALIAS_DENY_INCOMING)) {
                struct in_addr target_addr;

                target_addr = FindOriginalAddress(la, alias_addr);
                lnk = AddLink(la, target_addr, dst_addr, alias_addr,
                    alias_port, dst_port, alias_port,
                    link_type);
                if (lnk == NULL)
                        return (PKT_ALIAS_ERROR);

        }
        *lnkp = lnk;
        return (lnk != NULL ? PKT_ALIAS_OK : PKT_ALIAS_IGNORED);
}

int
FindUdpTcpOut(struct libalias *la, struct in_addr src_addr,
    struct in_addr dst_addr,
    u_short src_port,
    u_short dst_port,
    u_char proto,
    int create,
    struct alias_link **lnkp)
{
        int link_type;
        struct alias_link *lnk;

        LIBALIAS_LOCK_ASSERT(la);

        *lnkp = NULL;

        switch (proto) {
        case IPPROTO_UDP:
                link_type = LINK_UDP;
                break;
        case IPPROTO_TCP:
                link_type = LINK_TCP;
                break;
        default:
                return (PKT_ALIAS_IGNORED);
        }

        lnk = FindLinkOut(la, src_addr, dst_addr, src_port, dst_port, link_type, create);
        if (lnk == NULL && create) {
                struct in_addr alias_addr;

                alias_addr = FindAliasAddress(la, src_addr);
                lnk = AddLink(la, src_addr, dst_addr, alias_addr,
                    src_port, dst_port, GET_ALIAS_PORT,
                    link_type);
                if (lnk == NULL)
                        return (PKT_ALIAS_ERROR);
        }
        *lnkp = lnk;
        return (lnk != NULL ? PKT_ALIAS_OK : PKT_ALIAS_IGNORED);
}

struct alias_link *
AddPptp(struct libalias *la, struct in_addr src_addr,
    struct in_addr dst_addr,
    struct in_addr alias_addr,
    u_int16_t src_call_id)
{
        struct alias_link *lnk;

        LIBALIAS_LOCK_ASSERT(la);
        lnk = AddLink(la, src_addr, dst_addr, alias_addr,
            src_call_id, 0, GET_ALIAS_PORT,
            LINK_PPTP);

        return (lnk);
}

struct alias_link *
FindPptpOutByCallId(struct libalias *la, struct in_addr src_addr,
    struct in_addr dst_addr,
    u_int16_t src_call_id)
{
        struct alias_link *lnk;

        LIBALIAS_LOCK_ASSERT(la);
        LIST_FOREACH(lnk, &la->pptpList, pptp.list)
                if (lnk->src_addr.s_addr == src_addr.s_addr &&
                    lnk->dst_addr.s_addr == dst_addr.s_addr &&
                    lnk->src_port == src_call_id)
                        break;

        return (UseLink(la, lnk));
}

struct alias_link *
FindPptpOutByPeerCallId(struct libalias *la, struct in_addr src_addr,
    struct in_addr dst_addr,
    u_int16_t dst_call_id)
{
        struct alias_link *lnk;

        LIBALIAS_LOCK_ASSERT(la);
        LIST_FOREACH(lnk, &la->pptpList, pptp.list)
                if (lnk->src_addr.s_addr == src_addr.s_addr &&
                    lnk->dst_addr.s_addr == dst_addr.s_addr &&
                    lnk->dst_port == dst_call_id)
                        break;

        return (UseLink(la, lnk));
}

struct alias_link *
FindPptpInByCallId(struct libalias *la, struct in_addr dst_addr,
    struct in_addr alias_addr,
    u_int16_t dst_call_id)
{
        struct alias_link *lnk;

        LIBALIAS_LOCK_ASSERT(la);

        LIST_FOREACH(lnk, &la->pptpList, pptp.list)
                if (lnk->dst_port == dst_call_id &&
                    lnk->dst_addr.s_addr == dst_addr.s_addr &&
                    lnk->alias_addr.s_addr == alias_addr.s_addr)
                        break;

        return (UseLink(la, lnk));
}

struct alias_link *
FindPptpInByPeerCallId(struct libalias *la, struct in_addr dst_addr,
    struct in_addr alias_addr,
    u_int16_t alias_call_id)
{
        struct alias_link *lnk;

        LIBALIAS_LOCK_ASSERT(la);
        LIST_FOREACH(lnk, &la->pptpList, pptp.list)
                if (lnk->alias_port == alias_call_id &&
                    lnk->dst_addr.s_addr == dst_addr.s_addr &&
                    lnk->alias_addr.s_addr == alias_addr.s_addr)
                        break;

        return (lnk);
}

struct alias_link *
FindRtspOut(struct libalias *la, struct in_addr src_addr,
    struct in_addr dst_addr,
    u_short src_port,
    u_short alias_port,
    u_char proto)
{
        int link_type;
        struct alias_link *lnk;

        LIBALIAS_LOCK_ASSERT(la);
        switch (proto) {
        case IPPROTO_UDP:
                link_type = LINK_UDP;
                break;
        case IPPROTO_TCP:
                link_type = LINK_TCP;
                break;
        default:
                return (NULL);
                break;
        }

        lnk = FindLinkOut(la, src_addr, dst_addr, src_port, 0, link_type, 1);

        if (lnk == NULL) {
                struct in_addr alias_addr;

                alias_addr = FindAliasAddress(la, src_addr);
                lnk = AddLink(la, src_addr, dst_addr, alias_addr,
                    src_port, 0, alias_port,
                    link_type);
        }
        return (lnk);
}

struct in_addr
FindOriginalAddress(struct libalias *la, struct in_addr alias_addr)
{
        struct alias_link *lnk;

        LIBALIAS_LOCK_ASSERT(la);
        lnk = FindLinkIn(la, ANY_ADDR, alias_addr,
            0, 0, LINK_ADDR, 0);
        if (lnk == NULL) {
                if (la->targetAddress.s_addr == INADDR_ANY)
                        return (alias_addr);
                else if (la->targetAddress.s_addr == INADDR_NONE)
                        return (la->aliasAddress.s_addr != INADDR_ANY) ?
                            la->aliasAddress : alias_addr;
                else
                        return (la->targetAddress);
        } else {
                if (lnk->server != NULL) {      /* LSNAT link */
                        struct in_addr src_addr;

                        src_addr = lnk->server->addr;
                        lnk->server = lnk->server->next;
                        return (src_addr);
                } else if (lnk->src_addr.s_addr == INADDR_ANY)
                        return (la->aliasAddress.s_addr != INADDR_ANY) ?
                            la->aliasAddress : alias_addr;
                else
                        return (lnk->src_addr);
        }
}

struct in_addr
FindAliasAddress(struct libalias *la, struct in_addr original_addr)
{
        struct alias_link *lnk;

        LIBALIAS_LOCK_ASSERT(la);
        lnk = FindLinkOut(la, original_addr, ANY_ADDR,
            0, 0, LINK_ADDR, 0);
        if (lnk == NULL) {
                return (la->aliasAddress.s_addr != INADDR_ANY) ?
                    la->aliasAddress : original_addr;
        } else {
                if (lnk->alias_addr.s_addr == INADDR_ANY)
                        return (la->aliasAddress.s_addr != INADDR_ANY) ?
                            la->aliasAddress : original_addr;
                else
                        return (lnk->alias_addr);
        }
}

/* External routines for getting or changing link data
   (external to alias_db.c, but internal to alias*.c)

    SetFragmentData(), GetFragmentData()
    SetFragmentPtr(), GetFragmentPtr()
    SetStateIn(), SetStateOut(), GetStateIn(), GetStateOut()
    GetOriginalAddress(), GetDestAddress(), GetAliasAddress()
    GetOriginalPort(), GetAliasPort()
    SetAckModified(), GetAckModified()
    GetDeltaAckIn(), GetDeltaSeqOut(), AddSeq()
    SetProtocolFlags(), GetProtocolFlags()
    SetDestCallId()
*/

void
SetFragmentAddr(struct alias_link *lnk, struct in_addr src_addr)
{
        lnk->data.frag_addr = src_addr;
}

void
GetFragmentAddr(struct alias_link *lnk, struct in_addr *src_addr)
{
        *src_addr = lnk->data.frag_addr;
}

void
SetFragmentPtr(struct alias_link *lnk, void *fptr)
{
        lnk->data.frag_ptr = fptr;
}

void
GetFragmentPtr(struct alias_link *lnk, void **fptr)
{
        *fptr = lnk->data.frag_ptr;
}

void
SetStateIn(struct alias_link *lnk, int state)
{
        /* TCP input state */
        switch (state) {
                case ALIAS_TCP_STATE_DISCONNECTED:
                if (lnk->data.tcp->state.out != ALIAS_TCP_STATE_CONNECTED)
                        lnk->expire.time = TCP_EXPIRE_DEAD;
                else
                        lnk->expire.time = TCP_EXPIRE_SINGLEDEAD;
                break;
        case ALIAS_TCP_STATE_CONNECTED:
                if (lnk->data.tcp->state.out == ALIAS_TCP_STATE_CONNECTED)
                        lnk->expire.time = TCP_EXPIRE_CONNECTED;
                break;
        default:
#ifdef _KERNEL
                panic("libalias:SetStateIn() unknown state");
#else
                abort();
#endif
        }
        lnk->data.tcp->state.in = state;
}

void
SetStateOut(struct alias_link *lnk, int state)
{
        /* TCP output state */
        switch (state) {
                case ALIAS_TCP_STATE_DISCONNECTED:
                if (lnk->data.tcp->state.in != ALIAS_TCP_STATE_CONNECTED)
                        lnk->expire.time = TCP_EXPIRE_DEAD;
                else
                        lnk->expire.time = TCP_EXPIRE_SINGLEDEAD;
                break;
        case ALIAS_TCP_STATE_CONNECTED:
                if (lnk->data.tcp->state.in == ALIAS_TCP_STATE_CONNECTED)
                        lnk->expire.time = TCP_EXPIRE_CONNECTED;
                break;
        default:
#ifdef _KERNEL
                panic("libalias:SetStateOut() unknown state");
#else
                abort();
#endif
        }
        lnk->data.tcp->state.out = state;
}

int
GetStateIn(struct alias_link *lnk)
{
        /* TCP input state */
        return (lnk->data.tcp->state.in);
}

int
GetStateOut(struct alias_link *lnk)
{
        /* TCP output state */
        return (lnk->data.tcp->state.out);
}

struct in_addr
GetOriginalAddress(struct alias_link *lnk)
{
        if (lnk->src_addr.s_addr == INADDR_ANY)
                return (lnk->la->aliasAddress);
        else
                return (lnk->src_addr);
}

struct in_addr
GetDestAddress(struct alias_link *lnk)
{
        return (lnk->dst_addr);
}

struct in_addr
GetAliasAddress(struct alias_link *lnk)
{
        if (lnk->alias_addr.s_addr == INADDR_ANY)
                return (lnk->la->aliasAddress);
        else
                return (lnk->alias_addr);
}

struct in_addr
GetDefaultAliasAddress(struct libalias *la)
{
        LIBALIAS_LOCK_ASSERT(la);
        return (la->aliasAddress);
}

void
SetDefaultAliasAddress(struct libalias *la, struct in_addr alias_addr)
{
        LIBALIAS_LOCK_ASSERT(la);
        la->aliasAddress = alias_addr;
}

u_short
GetOriginalPort(struct alias_link *lnk)
{
        return (lnk->src_port);
}

u_short
GetAliasPort(struct alias_link *lnk)
{
        return (lnk->alias_port);
}

#ifndef NO_FW_PUNCH
static u_short
GetDestPort(struct alias_link *lnk)
{
        return (lnk->dst_port);
}

#endif

/* Indicate that ACK numbers have been modified in a TCP connection */
void
SetAckModified(struct alias_link *lnk)
{
        lnk->data.tcp->state.ack_modified = 1;
}

struct in_addr
GetProxyAddress(struct alias_link *lnk)
{
        return (lnk->proxy_addr);
}

void
SetProxyAddress(struct alias_link *lnk, struct in_addr addr)
{
        lnk->proxy_addr = addr;
}

u_short
GetProxyPort(struct alias_link *lnk)
{
        return (lnk->proxy_port);
}

void
SetProxyPort(struct alias_link *lnk, u_short port)
{
        lnk->proxy_port = port;
}

/* See if ACK numbers have been modified */
int
GetAckModified(struct alias_link *lnk)
{
        return (lnk->data.tcp->state.ack_modified);
}

/*
 * Find out how much the ACK number has been altered for an
 * incoming TCP packet.  To do this, a circular list of ACK
 * numbers where the TCP packet size was altered is searched.
 */
// XXX ip free
int
GetDeltaAckIn(u_long ack, struct alias_link *lnk)
{
        int i, j;
        int delta, ack_diff_min;

        delta = 0;
        ack_diff_min = -1;
        i = lnk->data.tcp->state.index;
        for (j = 0; j < N_LINK_TCP_DATA; j++) {
                struct ack_data_record x;

                if (i == 0)
                        i = N_LINK_TCP_DATA;
                i--;
                x = lnk->data.tcp->ack[i];
                if (x.active == 1) {
                        int ack_diff;

                        ack_diff = SeqDiff(x.ack_new, ack);
                        if (ack_diff >= 0) {
                                if (ack_diff_min >= 0) {
                                        if (ack_diff < ack_diff_min) {
                                                delta = x.delta;
                                                ack_diff_min = ack_diff;
                                        }
                                } else {
                                        delta = x.delta;
                                        ack_diff_min = ack_diff;
                                }
                        }
                }
        }
        return (delta);
}

/*
 * Find out how much the sequence number has been altered for an
 * outgoing TCP packet.  To do this, a circular list of ACK numbers
 * where the TCP packet size was altered is searched.
 */
// XXX ip free
int
GetDeltaSeqOut(u_long seq, struct alias_link *lnk)
{
        int i, j;
        int delta, seq_diff_min;

        delta = 0;
        seq_diff_min = -1;
        i = lnk->data.tcp->state.index;
        for (j = 0; j < N_LINK_TCP_DATA; j++) {
                struct ack_data_record x;

                if (i == 0)
                        i = N_LINK_TCP_DATA;
                i--;
                x = lnk->data.tcp->ack[i];
                if (x.active == 1) {
                        int seq_diff;

                        seq_diff = SeqDiff(x.ack_old, seq);
                        if (seq_diff >= 0) {
                                if (seq_diff_min >= 0) {
                                        if (seq_diff < seq_diff_min) {
                                                delta = x.delta;
                                                seq_diff_min = seq_diff;
                                        }
                                } else {
                                        delta = x.delta;
                                        seq_diff_min = seq_diff;
                                }
                        }
                }
        }
        return (delta);
}

/*
 * When a TCP packet has been altered in length, save this
 * information in a circular list.  If enough packets have been
 * altered, then this list will begin to overwrite itself.
 */
// XXX ip free
void
AddSeq(struct alias_link *lnk, int delta, u_int ip_hl, u_short ip_len,
    u_long th_seq, u_int th_off)
{
        struct ack_data_record x;
        int hlen, tlen, dlen;
        int i;

        hlen = (ip_hl + th_off) << 2;
        tlen = ntohs(ip_len);
        dlen = tlen - hlen;

        x.ack_old = htonl(ntohl(th_seq) + dlen);
        x.ack_new = htonl(ntohl(th_seq) + dlen + delta);
        x.delta = delta;
        x.active = 1;

        i = lnk->data.tcp->state.index;
        lnk->data.tcp->ack[i] = x;

        i++;
        if (i == N_LINK_TCP_DATA)
                lnk->data.tcp->state.index = 0;
        else
                lnk->data.tcp->state.index = i;
}

void
SetExpire(struct alias_link *lnk, int expire)
{
        if (expire == 0) {
                lnk->flags &= ~LINK_PERMANENT;
                DeleteLink(&lnk, 0);
        } else if (expire == -1) {
                lnk->flags |= LINK_PERMANENT;
        } else if (expire > 0) {
                lnk->expire.time = expire;
        } else {
#ifdef LIBALIAS_DEBUG
                fprintf(stderr, "PacketAlias/SetExpire(): ");
                fprintf(stderr, "error in expire parameter\n");
#endif
        }
}

void
SetProtocolFlags(struct alias_link *lnk, int pflags)
{
        lnk->pflags = pflags;
}

int
GetProtocolFlags(struct alias_link *lnk)
{
        return (lnk->pflags);
}

void
SetDestCallId(struct alias_link *lnk, u_int16_t cid)
{
        LIBALIAS_LOCK_ASSERT(lnk->la);
        ReLink(lnk, lnk->src_addr, lnk->dst_addr, lnk->alias_addr,
            lnk->src_port, cid, lnk->alias_port, lnk->link_type, 1);
}

/* Miscellaneous Functions

    HouseKeeping()
    InitPacketAliasLog()
    UninitPacketAliasLog()
*/

/*
    Whenever an outgoing or incoming packet is handled, HouseKeeping()
    is called to find and remove timed-out aliasing links.  Logic exists
    to sweep through the entire table and linked list structure
    every 60 seconds.

    (prototype in alias_local.h)
*/

void
HouseKeeping(struct libalias *la)
{
        static unsigned int packets = 0;
        static unsigned int packet_limit = 1000;

        LIBALIAS_LOCK_ASSERT(la);
        packets++;

        /*
         * User space time/gettimeofday/... is very expensive.
         * Kernel space cache trashing is unnecessary.
         *
         * Save system time (seconds) in global variable LibAliasTime
         * for use by other functions. This is done so as not to
         * unnecessarily waste timeline by making system calls.
         *
         * Reduce the amount of house keeping work substantially by
         * sampling over the packets.
         */
        if (packet_limit <= 1 || packets % packet_limit == 0) {
                time_t now;

#ifdef _KERNEL
                now = time_uptime;
#else
                now = time(NULL);
#endif
                if (now != LibAliasTime) {
                        /* retry three times a second */
                        packet_limit = packets / 3;
                        packets = 0;
                        LibAliasTime = now;
                }

        }
        /* Do a cleanup for the first packets of the new second only */
        if (packets < (la->udpLinkCount + la->tcpLinkCount)) {
                struct alias_link * lnk = TAILQ_FIRST(&la->checkExpire);

                CleanupLink(la, &lnk, 0);
        }
}

/* Init the log file and enable logging */
static int
InitPacketAliasLog(struct libalias *la)
{
        LIBALIAS_LOCK_ASSERT(la);
        if (~la->packetAliasMode & PKT_ALIAS_LOG) {
#ifdef _KERNEL
                if ((la->logDesc = malloc(LIBALIAS_BUF_SIZE)))
                        ;
#else
                if ((la->logDesc = fopen("/var/log/alias.log", "w")))
                        fprintf(la->logDesc, "PacketAlias/InitPacketAliasLog: Packet alias logging enabled.\n");
#endif
                else
                        return (ENOMEM); /* log initialization failed */
                la->packetAliasMode |= PKT_ALIAS_LOG;
        }

        return (1);
}

/* Close the log-file and disable logging. */
static void
UninitPacketAliasLog(struct libalias *la)
{
        LIBALIAS_LOCK_ASSERT(la);
        if (la->logDesc) {
#ifdef _KERNEL
                free(la->logDesc);
#else
                fclose(la->logDesc);
#endif
                la->logDesc = NULL;
        }
        la->packetAliasMode &= ~PKT_ALIAS_LOG;
}

/* Outside world interfaces

-- "outside world" means other than alias*.c routines --

    PacketAliasRedirectPort()
    PacketAliasAddServer()
    PacketAliasRedirectProto()
    PacketAliasRedirectAddr()
    PacketAliasRedirectDynamic()
    PacketAliasRedirectDelete()
    PacketAliasSetAddress()
    PacketAliasInit()
    PacketAliasUninit()
    PacketAliasSetMode()

(prototypes in alias.h)
*/

/* Redirection from a specific public addr:port to a
   private addr:port */
struct alias_link *
LibAliasRedirectPort(struct libalias *la, struct in_addr src_addr, u_short src_port,
    struct in_addr dst_addr, u_short dst_port,
    struct in_addr alias_addr, u_short alias_port,
    u_char proto)
{
        int link_type;
        struct alias_link *lnk;

        LIBALIAS_LOCK(la);
        switch (proto) {
        case IPPROTO_UDP:
                link_type = LINK_UDP;
                break;
        case IPPROTO_TCP:
                link_type = LINK_TCP;
                break;
        case IPPROTO_SCTP:
                link_type = LINK_SCTP;
                break;
        default:
#ifdef LIBALIAS_DEBUG
                fprintf(stderr, "PacketAliasRedirectPort(): ");
                fprintf(stderr, "only SCTP, TCP and UDP protocols allowed\n");
#endif
                lnk = NULL;
                goto getout;
        }

        lnk = AddLink(la, src_addr, dst_addr, alias_addr,
            src_port, dst_port, alias_port,
            link_type);

        if (lnk != NULL) {
                lnk->flags |= LINK_PERMANENT;
        }
#ifdef LIBALIAS_DEBUG
        else {
                fprintf(stderr, "PacketAliasRedirectPort(): "
                    "call to AddLink() failed\n");
        }
#endif

getout:
        LIBALIAS_UNLOCK(la);
        return (lnk);
}

/* Add server to the pool of servers */
int
LibAliasAddServer(struct libalias *la, struct alias_link *lnk, struct in_addr addr, u_short port)
{
        struct server *server;
        int res;

        LIBALIAS_LOCK(la);
        (void)la;

        switch (lnk->link_type) {
        case LINK_PPTP:
                server = NULL;
                break;
        default:
                server = malloc(sizeof(struct server));
                break;
        }

        if (server != NULL) {
                struct server *head;

                server->addr = addr;
                server->port = port;

                head = lnk->server;
                if (head == NULL) {
                        server->next = server;
                        /* not usable for outgoing connections */
                        SPLAY_REMOVE(splay_out, &la->linkSplayOut, lnk);
                } else {
                        struct server *s;

                        for (s = head; s->next != head; s = s->next)
                                ;
                        s->next = server;
                        server->next = head;
                }
                lnk->server = server;
                res = 0;
        } else
                res = -1;

        LIBALIAS_UNLOCK(la);
        return (res);
}

/* Redirect packets of a given IP protocol from a specific
   public address to a private address */
struct alias_link *
LibAliasRedirectProto(struct libalias *la, struct in_addr src_addr,
    struct in_addr dst_addr,
    struct in_addr alias_addr,
    u_char proto)
{
        struct alias_link *lnk;

        LIBALIAS_LOCK(la);
        lnk = AddLink(la, src_addr, dst_addr, alias_addr,
            NO_SRC_PORT, NO_DEST_PORT, 0,
            proto);

        if (lnk != NULL) {
                lnk->flags |= LINK_PERMANENT;
        }
#ifdef LIBALIAS_DEBUG
        else {
                fprintf(stderr, "PacketAliasRedirectProto(): "
                    "call to AddLink() failed\n");
        }
#endif

        LIBALIAS_UNLOCK(la);
        return (lnk);
}

/* Static address translation */
struct alias_link *
LibAliasRedirectAddr(struct libalias *la, struct in_addr src_addr,
    struct in_addr alias_addr)
{
        struct alias_link *lnk;

        LIBALIAS_LOCK(la);
        lnk = AddLink(la, src_addr, ANY_ADDR, alias_addr,
            0, 0, 0,
            LINK_ADDR);

        if (lnk != NULL) {
                lnk->flags |= LINK_PERMANENT;
        }
#ifdef LIBALIAS_DEBUG
        else {
                fprintf(stderr, "PacketAliasRedirectAddr(): "
                    "call to AddLink() failed\n");
        }
#endif

        LIBALIAS_UNLOCK(la);
        return (lnk);
}

/* Mark the aliasing link dynamic */
int
LibAliasRedirectDynamic(struct libalias *la, struct alias_link *lnk)
{
        int res;

        LIBALIAS_LOCK(la);
        (void)la;

        if (lnk->flags & LINK_PARTIALLY_SPECIFIED)
                res = -1;
        else {
                lnk->flags &= ~LINK_PERMANENT;
                res = 0;
        }
        LIBALIAS_UNLOCK(la);
        return (res);
}

/* This is a dangerous function to put in the API,
   because an invalid pointer can crash the program. */
void
LibAliasRedirectDelete(struct libalias *la, struct alias_link *lnk)
{
        LIBALIAS_LOCK(la);
        (void)la;
        DeleteLink(&lnk, 1);
        LIBALIAS_UNLOCK(la);
}

void
LibAliasSetAddress(struct libalias *la, struct in_addr addr)
{
        LIBALIAS_LOCK(la);
        if (la->packetAliasMode & PKT_ALIAS_RESET_ON_ADDR_CHANGE
            && la->aliasAddress.s_addr != addr.s_addr)
                CleanupAliasData(la, 0);

        la->aliasAddress = addr;
        LIBALIAS_UNLOCK(la);
}

void
LibAliasSetAliasPortRange(struct libalias *la, u_short port_low,
    u_short port_high)
{
        LIBALIAS_LOCK(la);
        if (port_low) {
                la->aliasPortLower = port_low;
                /* Add 1 to the aliasPortLength as modulo has range of 1 to n-1 */
                la->aliasPortLength = port_high - port_low + 1;
        } else {
                /* Set default values */
                la->aliasPortLower = 0x8000;
                la->aliasPortLength = 0x8000;
        }
        LIBALIAS_UNLOCK(la);
}

void
LibAliasSetTarget(struct libalias *la, struct in_addr target_addr)
{
        LIBALIAS_LOCK(la);
        la->targetAddress = target_addr;
        LIBALIAS_UNLOCK(la);
}

static void
finishoff(void)
{
        while (!LIST_EMPTY(&instancehead))
                LibAliasUninit(LIST_FIRST(&instancehead));
}

struct libalias *
LibAliasInit(struct libalias *la)
{
        if (la == NULL) {
#ifdef _KERNEL
#undef malloc   /* XXX: ugly */
                la = malloc(sizeof *la, M_ALIAS, M_WAITOK | M_ZERO);
#else
                la = calloc(1, sizeof *la);
                if (la == NULL)
                        return (la);
#endif

#ifndef _KERNEL
                /* kernel cleans up on module unload */
                if (LIST_EMPTY(&instancehead))
                        atexit(finishoff);
#endif
                LIST_INSERT_HEAD(&instancehead, la, instancelist);

#ifdef _KERNEL
                LibAliasTime = time_uptime;
#else
                LibAliasTime = time(NULL);
#endif

                SPLAY_INIT(&la->linkSplayIn);
                SPLAY_INIT(&la->linkSplayOut);
                SPLAY_INIT(&la->linkSplayInternalEndpoint);
                LIST_INIT(&la->pptpList);
                TAILQ_INIT(&la->checkExpire);
#ifdef _KERNEL
                AliasSctpInit(la);
#endif
                LIBALIAS_LOCK_INIT(la);
                LIBALIAS_LOCK(la);
        } else {
                LIBALIAS_LOCK(la);
                CleanupAliasData(la, 1);
#ifdef _KERNEL
                AliasSctpTerm(la);
                AliasSctpInit(la);
#endif
        }

        la->aliasAddress.s_addr = INADDR_ANY;
        la->targetAddress.s_addr = INADDR_ANY;
        la->aliasPortLower = 0x8000;
        la->aliasPortLength = 0x8000;

        la->icmpLinkCount = 0;
        la->udpLinkCount = 0;
        la->tcpLinkCount = 0;
        la->sctpLinkCount = 0;
        la->pptpLinkCount = 0;
        la->protoLinkCount = 0;
        la->fragmentIdLinkCount = 0;
        la->fragmentPtrLinkCount = 0;
        la->sockCount = 0;

        la->packetAliasMode = PKT_ALIAS_SAME_PORTS
#ifndef NO_USE_SOCKETS
            | PKT_ALIAS_USE_SOCKETS
#endif
            | PKT_ALIAS_RESET_ON_ADDR_CHANGE;
#ifndef NO_FW_PUNCH
        la->fireWallFD = -1;
#endif
#ifndef _KERNEL
        LibAliasRefreshModules();
#endif
        LIBALIAS_UNLOCK(la);
        return (la);
}

void
LibAliasUninit(struct libalias *la)
{
        LIBALIAS_LOCK(la);
#ifdef _KERNEL
        AliasSctpTerm(la);
#endif
        CleanupAliasData(la, 1);
        UninitPacketAliasLog(la);
#ifndef NO_FW_PUNCH
        UninitPunchFW(la);
#endif
        LIST_REMOVE(la, instancelist);
        LIBALIAS_UNLOCK(la);
        LIBALIAS_LOCK_DESTROY(la);
        free(la);
}

/* Change mode for some operations */
unsigned int
LibAliasSetMode(
    struct libalias *la,
    unsigned int flags,         /* Which state to bring flags to */
    unsigned int mask           /* Mask of which flags to affect (use 0 to
                                 * do a probe for flag values) */
)
{
        int res = -1;

        LIBALIAS_LOCK(la);
        if (flags & mask & PKT_ALIAS_LOG) {
                /* Enable logging */
                if (InitPacketAliasLog(la) == ENOMEM)
                        goto getout;
        } else if (~flags & mask & PKT_ALIAS_LOG)
                /* _Disable_ logging */
                UninitPacketAliasLog(la);

#ifndef NO_FW_PUNCH
        if (flags & mask & PKT_ALIAS_PUNCH_FW)
                /* Start punching holes in the firewall? */
                InitPunchFW(la);
        else if (~flags & mask & PKT_ALIAS_PUNCH_FW)
                /* Stop punching holes in the firewall? */
                UninitPunchFW(la);
#endif

        /* Other flags can be set/cleared without special action */
        la->packetAliasMode = (flags & mask) | (la->packetAliasMode & ~mask);
        res = la->packetAliasMode;
getout:
        LIBALIAS_UNLOCK(la);
        return (res);
}

#ifndef NO_FW_PUNCH

/*****************
  Code to support firewall punching.  This shouldn't really be in this
  file, but making variables global is evil too.
  ****************/

/* Firewall include files */
#include <net/if.h>
#include <netinet/ip_fw.h>
#include <string.h>
#include <err.h>

/*
 * helper function, updates the pointer to cmd with the length
 * of the current command, and also cleans up the first word of
 * the new command in case it has been clobbered before.
 */
static ipfw_insn *
next_cmd(ipfw_insn * cmd)
{
        cmd += F_LEN(cmd);
        bzero(cmd, sizeof(*cmd));
        return (cmd);
}

/*
 * A function to fill simple commands of size 1.
 * Existing flags are preserved.
 */
static ipfw_insn *
fill_cmd(ipfw_insn * cmd, enum ipfw_opcodes opcode, int size,
    int flags, u_int16_t arg)
{
        cmd->opcode = opcode;
        cmd->len = ((cmd->len | flags) & (F_NOT | F_OR)) | (size & F_LEN_MASK);
        cmd->arg1 = arg;
        return next_cmd(cmd);
}

static ipfw_insn *
fill_ip(ipfw_insn * cmd1, enum ipfw_opcodes opcode, u_int32_t addr)
{
        ipfw_insn_ip *cmd = (ipfw_insn_ip *)cmd1;

        cmd->addr.s_addr = addr;
        return fill_cmd(cmd1, opcode, F_INSN_SIZE(ipfw_insn_u32), 0, 0);
}

static ipfw_insn *
fill_one_port(ipfw_insn * cmd1, enum ipfw_opcodes opcode, u_int16_t port)
{
        ipfw_insn_u16 *cmd = (ipfw_insn_u16 *)cmd1;

        cmd->ports[0] = cmd->ports[1] = port;
        return fill_cmd(cmd1, opcode, F_INSN_SIZE(ipfw_insn_u16), 0, 0);
}

static int
fill_rule(void *buf, int bufsize, int rulenum,
    enum ipfw_opcodes action, int proto,
    struct in_addr sa, u_int16_t sp, struct in_addr da, u_int16_t dp)
{
        struct ip_fw *rule = (struct ip_fw *)buf;
        ipfw_insn *cmd = (ipfw_insn *)rule->cmd;

        bzero(buf, bufsize);
        rule->rulenum = rulenum;

        cmd = fill_cmd(cmd, O_PROTO, F_INSN_SIZE(ipfw_insn), 0, proto);
        cmd = fill_ip(cmd, O_IP_SRC, sa.s_addr);
        cmd = fill_one_port(cmd, O_IP_SRCPORT, sp);
        cmd = fill_ip(cmd, O_IP_DST, da.s_addr);
        cmd = fill_one_port(cmd, O_IP_DSTPORT, dp);

        rule->act_ofs = (u_int32_t *)cmd - (u_int32_t *)rule->cmd;
        cmd = fill_cmd(cmd, action, F_INSN_SIZE(ipfw_insn), 0, 0);

        rule->cmd_len = (u_int32_t *)cmd - (u_int32_t *)rule->cmd;

        return ((char *)cmd - (char *)buf);
}

static void
InitPunchFW(struct libalias *la)
{
        la->fireWallField = malloc(la->fireWallNumNums);
        if (la->fireWallField) {
                memset(la->fireWallField, 0, la->fireWallNumNums);
                if (la->fireWallFD < 0) {
                        la->fireWallFD = socket(AF_INET, SOCK_RAW, IPPROTO_RAW);
                }
                ClearAllFWHoles(la);
                la->fireWallActiveNum = la->fireWallBaseNum;
        }
}

static void
UninitPunchFW(struct libalias *la)
{
        ClearAllFWHoles(la);
        if (la->fireWallFD >= 0)
                close(la->fireWallFD);
        la->fireWallFD = -1;
        if (la->fireWallField)
                free(la->fireWallField);
        la->fireWallField = NULL;
        la->packetAliasMode &= ~PKT_ALIAS_PUNCH_FW;
}

/* Make a certain link go through the firewall */
void
PunchFWHole(struct alias_link *lnk)
{
        struct libalias *la;
        int r;                  /* Result code */
        struct ip_fw rule;      /* On-the-fly built rule */
        int fwhole;             /* Where to punch hole */

        la = lnk->la;

        /* Don't do anything unless we are asked to */
        if (!(la->packetAliasMode & PKT_ALIAS_PUNCH_FW) ||
            la->fireWallFD < 0 ||
            lnk->link_type != LINK_TCP)
                return;

        memset(&rule, 0, sizeof rule);

        /** Build rule **/

        /* Find empty slot */
        for (fwhole = la->fireWallActiveNum;
            fwhole < la->fireWallBaseNum + la->fireWallNumNums &&
            fw_tstfield(la, la->fireWallField, fwhole);
            fwhole++);
        if (fwhole == la->fireWallBaseNum + la->fireWallNumNums) {
                for (fwhole = la->fireWallBaseNum;
                    fwhole < la->fireWallActiveNum &&
                    fw_tstfield(la, la->fireWallField, fwhole);
                    fwhole++);
                if (fwhole == la->fireWallActiveNum) {
                        /* No rule point empty - we can't punch more holes. */
                        la->fireWallActiveNum = la->fireWallBaseNum;
#ifdef LIBALIAS_DEBUG
                        fprintf(stderr, "libalias: Unable to create firewall hole!\n");
#endif
                        return;
                }
        }
        /* Start next search at next position */
        la->fireWallActiveNum = fwhole + 1;

        /*
         * generate two rules of the form
         *
         * add fwhole accept tcp from OAddr OPort to DAddr DPort add fwhole
         * accept tcp from DAddr DPort to OAddr OPort
         */
        if (GetOriginalPort(lnk) != 0 && GetDestPort(lnk) != 0) {
                u_int32_t rulebuf[255];
                int i;

                i = fill_rule(rulebuf, sizeof(rulebuf), fwhole,
                    O_ACCEPT, IPPROTO_TCP,
                    GetOriginalAddress(lnk), ntohs(GetOriginalPort(lnk)),
                    GetDestAddress(lnk), ntohs(GetDestPort(lnk)));
                r = setsockopt(la->fireWallFD, IPPROTO_IP, IP_FW_ADD, rulebuf, i);
                if (r)
                        err(1, "alias punch inbound(1) setsockopt(IP_FW_ADD)");

                i = fill_rule(rulebuf, sizeof(rulebuf), fwhole,
                    O_ACCEPT, IPPROTO_TCP,
                    GetDestAddress(lnk), ntohs(GetDestPort(lnk)),
                    GetOriginalAddress(lnk), ntohs(GetOriginalPort(lnk)));
                r = setsockopt(la->fireWallFD, IPPROTO_IP, IP_FW_ADD, rulebuf, i);
                if (r)
                        err(1, "alias punch inbound(2) setsockopt(IP_FW_ADD)");
        }

        /* Indicate hole applied */
        lnk->data.tcp->fwhole = fwhole;
        fw_setfield(la, la->fireWallField, fwhole);
}

/* Remove a hole in a firewall associated with a particular alias
   lnk.  Calling this too often is harmless. */
static void
ClearFWHole(struct alias_link *lnk)
{
        struct libalias *la;

        la = lnk->la;
        if (lnk->link_type == LINK_TCP) {
                int fwhole = lnk->data.tcp->fwhole;  /* Where is the firewall hole? */
                struct ip_fw rule;

                if (fwhole < 0)
                        return;

                memset(&rule, 0, sizeof rule);  /* useless for ipfw2 */
                while (!setsockopt(la->fireWallFD, IPPROTO_IP, IP_FW_DEL,
                    &fwhole, sizeof fwhole));
                fw_clrfield(la, la->fireWallField, fwhole);
                lnk->data.tcp->fwhole = -1;
        }
}

/* Clear out the entire range dedicated to firewall holes. */
static void
ClearAllFWHoles(struct libalias *la)
{
        struct ip_fw rule;      /* On-the-fly built rule */
        int i;

        if (la->fireWallFD < 0)
                return;

        memset(&rule, 0, sizeof rule);
        for (i = la->fireWallBaseNum; i < la->fireWallBaseNum + la->fireWallNumNums; i++) {
                int r = i;

                while (!setsockopt(la->fireWallFD, IPPROTO_IP, IP_FW_DEL, &r, sizeof r));
        }
        /* XXX: third arg correct here ? /phk */
        memset(la->fireWallField, 0, la->fireWallNumNums);
}

#endif /* !NO_FW_PUNCH */

void
LibAliasSetFWBase(struct libalias *la, unsigned int base, unsigned int num)
{
        LIBALIAS_LOCK(la);
#ifndef NO_FW_PUNCH
        la->fireWallBaseNum = base;
        la->fireWallNumNums = num;
#endif
        LIBALIAS_UNLOCK(la);
}

void
LibAliasSetSkinnyPort(struct libalias *la, unsigned int port)
{
        LIBALIAS_LOCK(la);
        la->skinnyPort = port;
        LIBALIAS_UNLOCK(la);
}

/*
 * Find the address to redirect incoming packets
 */
struct in_addr
FindSctpRedirectAddress(struct libalias *la,  struct sctp_nat_msg *sm)
{
        struct alias_link *lnk;
        struct in_addr redir;

        LIBALIAS_LOCK_ASSERT(la);
        lnk = FindLinkIn(la, sm->ip_hdr->ip_src, sm->ip_hdr->ip_dst,
            sm->sctp_hdr->dest_port,sm->sctp_hdr->dest_port, LINK_SCTP, 1);
        if (lnk != NULL) {
                /* port redirect */
                return (lnk->src_addr);
        } else {
                redir = FindOriginalAddress(la,sm->ip_hdr->ip_dst);
                if (redir.s_addr == la->aliasAddress.s_addr ||
                    redir.s_addr == la->targetAddress.s_addr) {
                        /* No address found */
                        lnk = FindLinkIn(la, sm->ip_hdr->ip_src, sm->ip_hdr->ip_dst,
                            NO_DEST_PORT, 0, LINK_SCTP, 1);
                        if (lnk != NULL)
                                /* redirect proto */
                                return (lnk->src_addr);
                }
                return (redir); /* address redirect */
        }
}