root/sys/netgraph/ng_nat.c
/*-
 * SPDX-License-Identifier: BSD-2-Clause
 *
 * Copyright 2005, Gleb Smirnoff <glebius@FreeBSD.org>
 * 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/param.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/mbuf.h>
#include <sys/malloc.h>
#include <sys/ctype.h>
#include <sys/errno.h>
#include <sys/syslog.h>

#include <netinet/in_systm.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <netinet/ip_var.h>
#include <netinet/tcp.h>
#include <machine/in_cksum.h>

#include <net/dlt.h>
#include <net/ethernet.h>

#include <netinet/libalias/alias.h>
#include <netinet/libalias/alias_local.h>

#include <netgraph/ng_message.h>
#include <netgraph/ng_parse.h>
#include <netgraph/ng_nat.h>
#include <netgraph/netgraph.h>

#ifdef NG_SEPARATE_MALLOC
static MALLOC_DEFINE(M_NETGRAPH_NAT, "netgraph_nat", "netgraph nat node");
#else
#define M_NETGRAPH_NAT M_NETGRAPH
#endif

static ng_constructor_t ng_nat_constructor;
static ng_rcvmsg_t      ng_nat_rcvmsg;
static ng_shutdown_t    ng_nat_shutdown;
static ng_newhook_t     ng_nat_newhook;
static ng_rcvdata_t     ng_nat_rcvdata;
static ng_disconnect_t  ng_nat_disconnect;

static unsigned int     ng_nat_translate_flags(unsigned int x);

/* Parse type for struct ng_nat_mode. */
static const struct ng_parse_struct_field ng_nat_mode_fields[]
        = NG_NAT_MODE_INFO;
static const struct ng_parse_type ng_nat_mode_type = {
        &ng_parse_struct_type,
        &ng_nat_mode_fields
};

/* Parse type for 'description' field in structs. */
static const struct ng_parse_fixedstring_info ng_nat_description_info
        = { NG_NAT_DESC_LENGTH };
static const struct ng_parse_type ng_nat_description_type = {
        &ng_parse_fixedstring_type,
        &ng_nat_description_info
};

/* Parse type for struct ng_nat_redirect_port. */
static const struct ng_parse_struct_field ng_nat_redirect_port_fields[]
        = NG_NAT_REDIRECT_PORT_TYPE_INFO(&ng_nat_description_type);
static const struct ng_parse_type ng_nat_redirect_port_type = {
        &ng_parse_struct_type,
        &ng_nat_redirect_port_fields
};

/* Parse type for struct ng_nat_redirect_addr. */
static const struct ng_parse_struct_field ng_nat_redirect_addr_fields[]
        = NG_NAT_REDIRECT_ADDR_TYPE_INFO(&ng_nat_description_type);
static const struct ng_parse_type ng_nat_redirect_addr_type = {
        &ng_parse_struct_type,
        &ng_nat_redirect_addr_fields
};

/* Parse type for struct ng_nat_redirect_proto. */
static const struct ng_parse_struct_field ng_nat_redirect_proto_fields[]
        = NG_NAT_REDIRECT_PROTO_TYPE_INFO(&ng_nat_description_type);
static const struct ng_parse_type ng_nat_redirect_proto_type = {
        &ng_parse_struct_type,
        &ng_nat_redirect_proto_fields
};

/* Parse type for struct ng_nat_add_server. */
static const struct ng_parse_struct_field ng_nat_add_server_fields[]
        = NG_NAT_ADD_SERVER_TYPE_INFO;
static const struct ng_parse_type ng_nat_add_server_type = {
        &ng_parse_struct_type,
        &ng_nat_add_server_fields
};

/* Parse type for one struct ng_nat_listrdrs_entry. */
static const struct ng_parse_struct_field ng_nat_listrdrs_entry_fields[]
        = NG_NAT_LISTRDRS_ENTRY_TYPE_INFO(&ng_nat_description_type);
static const struct ng_parse_type ng_nat_listrdrs_entry_type = {
        &ng_parse_struct_type,
        &ng_nat_listrdrs_entry_fields
};

/* Parse type for 'redirects' array in struct ng_nat_list_redirects. */
static int
ng_nat_listrdrs_ary_getLength(const struct ng_parse_type *type,
        const u_char *start, const u_char *buf)
{
        const struct ng_nat_list_redirects *lr;

        lr = (const struct ng_nat_list_redirects *)
            (buf - offsetof(struct ng_nat_list_redirects, redirects));
        return lr->total_count;
}

static const struct ng_parse_array_info ng_nat_listrdrs_ary_info = {
        &ng_nat_listrdrs_entry_type,
        &ng_nat_listrdrs_ary_getLength,
        NULL
};
static const struct ng_parse_type ng_nat_listrdrs_ary_type = {
        &ng_parse_array_type,
        &ng_nat_listrdrs_ary_info
};

/* Parse type for struct ng_nat_list_redirects. */
static const struct ng_parse_struct_field ng_nat_list_redirects_fields[]
        = NG_NAT_LIST_REDIRECTS_TYPE_INFO(&ng_nat_listrdrs_ary_type);
static const struct ng_parse_type ng_nat_list_redirects_type = {
        &ng_parse_struct_type,
        &ng_nat_list_redirects_fields
};

/* Parse type for struct ng_nat_libalias_info. */
static const struct ng_parse_struct_field ng_nat_libalias_info_fields[]
        = NG_NAT_LIBALIAS_INFO;
static const struct ng_parse_type ng_nat_libalias_info_type = {
        &ng_parse_struct_type,
        &ng_nat_libalias_info_fields
};

/* List of commands and how to convert arguments to/from ASCII. */
static const struct ng_cmdlist ng_nat_cmdlist[] = {
        {
          NGM_NAT_COOKIE,
          NGM_NAT_SET_IPADDR,
          "setaliasaddr",
          &ng_parse_ipaddr_type,
          NULL
        },
        {
          NGM_NAT_COOKIE,
          NGM_NAT_SET_MODE,
          "setmode",
          &ng_nat_mode_type,
          NULL
        },
        {
          NGM_NAT_COOKIE,
          NGM_NAT_SET_TARGET,
          "settarget",
          &ng_parse_ipaddr_type,
          NULL
        },
        {
          NGM_NAT_COOKIE,
          NGM_NAT_REDIRECT_PORT,
          "redirectport",
          &ng_nat_redirect_port_type,
          &ng_parse_uint32_type
        },
        {
          NGM_NAT_COOKIE,
          NGM_NAT_REDIRECT_ADDR,
          "redirectaddr",
          &ng_nat_redirect_addr_type,
          &ng_parse_uint32_type
        },
        {
          NGM_NAT_COOKIE,
          NGM_NAT_REDIRECT_PROTO,
          "redirectproto",
          &ng_nat_redirect_proto_type,
          &ng_parse_uint32_type
        },
        {
          NGM_NAT_COOKIE,
          NGM_NAT_REDIRECT_DYNAMIC,
          "redirectdynamic",
          &ng_parse_uint32_type,
          NULL
        },
        {
          NGM_NAT_COOKIE,
          NGM_NAT_REDIRECT_DELETE,
          "redirectdelete",
          &ng_parse_uint32_type,
          NULL
        },
        {
          NGM_NAT_COOKIE,
          NGM_NAT_ADD_SERVER,
          "addserver",
          &ng_nat_add_server_type,
          NULL
        },
        {
          NGM_NAT_COOKIE,
          NGM_NAT_LIST_REDIRECTS,
          "listredirects",
          NULL,
          &ng_nat_list_redirects_type
        },
        {
          NGM_NAT_COOKIE,
          NGM_NAT_PROXY_RULE,
          "proxyrule",
          &ng_parse_string_type,
          NULL
        },
        {
          NGM_NAT_COOKIE,
          NGM_NAT_LIBALIAS_INFO,
          "libaliasinfo",
          NULL,
          &ng_nat_libalias_info_type
        },
        {
          NGM_NAT_COOKIE,
          NGM_NAT_SET_DLT,
          "setdlt",
          &ng_parse_uint8_type,
          NULL
        },
        {
          NGM_NAT_COOKIE,
          NGM_NAT_GET_DLT,
          "getdlt",
          NULL,
          &ng_parse_uint8_type
        },
        { 0 }
};

/* Netgraph node type descriptor. */
static struct ng_type typestruct = {
        .version =      NG_ABI_VERSION,
        .name =         NG_NAT_NODE_TYPE,
        .constructor =  ng_nat_constructor,
        .rcvmsg =       ng_nat_rcvmsg,
        .shutdown =     ng_nat_shutdown,
        .newhook =      ng_nat_newhook,
        .rcvdata =      ng_nat_rcvdata,
        .disconnect =   ng_nat_disconnect,
        .cmdlist =      ng_nat_cmdlist,
};
NETGRAPH_INIT(nat, &typestruct);
MODULE_DEPEND(ng_nat, libalias, 1, 1, 1);

/* Element for list of redirects. */
struct ng_nat_rdr_lst {
        STAILQ_ENTRY(ng_nat_rdr_lst) entries;
        struct alias_link       *lnk;
        struct ng_nat_listrdrs_entry rdr;
};
STAILQ_HEAD(rdrhead, ng_nat_rdr_lst);

/* Information we store for each node. */
struct ng_nat_priv {
        node_p          node;           /* back pointer to node */
        hook_p          in;             /* hook for demasquerading */
        hook_p          out;            /* hook for masquerading */
        struct libalias *lib;           /* libalias handler */
        uint32_t        flags;          /* status flags */
        uint32_t        rdrcount;       /* number or redirects in list */
        uint32_t        nextid;         /* for next in turn in list */
        struct rdrhead  redirhead;      /* redirect list header */
        uint8_t         dlt;            /* DLT_XXX from bpf.h */
};
typedef struct ng_nat_priv *priv_p;

/* Values of flags */
#define NGNAT_CONNECTED         0x1     /* We have both hooks connected */
#define NGNAT_ADDR_DEFINED      0x2     /* NGM_NAT_SET_IPADDR happened */

static int
ng_nat_constructor(node_p node)
{
        priv_p priv;

        /* Initialize private descriptor. */
        priv = malloc(sizeof(*priv), M_NETGRAPH_NAT, M_WAITOK | M_ZERO);

        /* Init aliasing engine. */
        priv->lib = LibAliasInit(NULL);

        /* Set same ports on. */
        (void )LibAliasSetMode(priv->lib, PKT_ALIAS_SAME_PORTS,
            PKT_ALIAS_SAME_PORTS);

        /* Init redirects housekeeping. */
        priv->rdrcount = 0;
        priv->nextid = 1;
        priv->dlt = DLT_RAW;
        STAILQ_INIT(&priv->redirhead);

        /* Link structs together. */
        NG_NODE_SET_PRIVATE(node, priv);
        priv->node = node;

        /*
         * libalias is not thread safe, so our node
         * must be single threaded.
         */
        NG_NODE_FORCE_WRITER(node);

        return (0);
}

static int
ng_nat_newhook(node_p node, hook_p hook, const char *name)
{
        const priv_p priv = NG_NODE_PRIVATE(node);

        if (strcmp(name, NG_NAT_HOOK_IN) == 0) {
                priv->in = hook;
        } else if (strcmp(name, NG_NAT_HOOK_OUT) == 0) {
                priv->out = hook;
        } else
                return (EINVAL);

        if (priv->out != NULL &&
            priv->in != NULL)
                priv->flags |= NGNAT_CONNECTED;

        return(0);
}

static int
ng_nat_rcvmsg(node_p node, item_p item, hook_p lasthook)
{
        const priv_p priv = NG_NODE_PRIVATE(node);
        struct ng_mesg *resp = NULL;
        struct ng_mesg *msg;
        int error = 0;

        NGI_GET_MSG(item, msg);

        switch (msg->header.typecookie) {
        case NGM_NAT_COOKIE:
                switch (msg->header.cmd) {
                case NGM_NAT_SET_IPADDR:
                    {
                        struct in_addr *const ia = (struct in_addr *)msg->data;

                        if (msg->header.arglen < sizeof(*ia)) {
                                error = EINVAL;
                                break;
                        }

                        LibAliasSetAddress(priv->lib, *ia);

                        priv->flags |= NGNAT_ADDR_DEFINED;
                    }
                        break;
                case NGM_NAT_SET_MODE:
                    {
                        struct ng_nat_mode *const mode = 
                            (struct ng_nat_mode *)msg->data;

                        if (msg->header.arglen < sizeof(*mode)) {
                                error = EINVAL;
                                break;
                        }
                        
                        if (LibAliasSetMode(priv->lib, 
                            ng_nat_translate_flags(mode->flags),
                            ng_nat_translate_flags(mode->mask)) < 0) {
                                error = ENOMEM;
                                break;
                        }
                    }
                        break;
                case NGM_NAT_SET_TARGET:
                    {
                        struct in_addr *const ia = (struct in_addr *)msg->data;

                        if (msg->header.arglen < sizeof(*ia)) {
                                error = EINVAL;
                                break;
                        }

                        LibAliasSetTarget(priv->lib, *ia);
                    }
                        break;
                case NGM_NAT_REDIRECT_PORT:
                    {
                        struct ng_nat_rdr_lst *entry;
                        struct ng_nat_redirect_port *const rp =
                            (struct ng_nat_redirect_port *)msg->data;

                        if (msg->header.arglen < sizeof(*rp)) {
                                error = EINVAL;
                                break;
                        }

                        if ((entry = malloc(sizeof(struct ng_nat_rdr_lst),
                            M_NETGRAPH_NAT, M_NOWAIT | M_ZERO)) == NULL) {
                                error = ENOMEM;
                                break;
                        }

                        /* Try actual redirect. */
                        entry->lnk = LibAliasRedirectPort(priv->lib,
                                rp->local_addr, htons(rp->local_port),
                                rp->remote_addr, htons(rp->remote_port),
                                rp->alias_addr, htons(rp->alias_port),
                                rp->proto);

                        if (entry->lnk == NULL) {
                                error = ENOMEM;
                                free(entry, M_NETGRAPH_NAT);
                                break;
                        }

                        /* Successful, save info in our internal list. */
                        entry->rdr.local_addr = rp->local_addr;
                        entry->rdr.alias_addr = rp->alias_addr;
                        entry->rdr.remote_addr = rp->remote_addr;
                        entry->rdr.local_port = rp->local_port;
                        entry->rdr.alias_port = rp->alias_port;
                        entry->rdr.remote_port = rp->remote_port;
                        entry->rdr.proto = rp->proto;
                        bcopy(rp->description, entry->rdr.description,
                            NG_NAT_DESC_LENGTH);

                        /* Safety precaution. */
                        entry->rdr.description[NG_NAT_DESC_LENGTH-1] = '\0';

                        entry->rdr.id = priv->nextid++;
                        priv->rdrcount++;

                        /* Link to list of redirects. */
                        STAILQ_INSERT_TAIL(&priv->redirhead, entry, entries);

                        /* Response with id of newly added entry. */
                        NG_MKRESPONSE(resp, msg, sizeof(entry->rdr.id), M_NOWAIT);
                        if (resp == NULL) {
                                error = ENOMEM;
                                break;
                        }
                        bcopy(&entry->rdr.id, resp->data, sizeof(entry->rdr.id));
                    }
                        break;
                case NGM_NAT_REDIRECT_ADDR:
                    {
                        struct ng_nat_rdr_lst *entry;
                        struct ng_nat_redirect_addr *const ra =
                            (struct ng_nat_redirect_addr *)msg->data;

                        if (msg->header.arglen < sizeof(*ra)) {
                                error = EINVAL;
                                break;
                        }

                        if ((entry = malloc(sizeof(struct ng_nat_rdr_lst),
                            M_NETGRAPH_NAT, M_NOWAIT | M_ZERO)) == NULL) {
                                error = ENOMEM;
                                break;
                        }

                        /* Try actual redirect. */
                        entry->lnk = LibAliasRedirectAddr(priv->lib,
                                ra->local_addr, ra->alias_addr);

                        if (entry->lnk == NULL) {
                                error = ENOMEM;
                                free(entry, M_NETGRAPH_NAT);
                                break;
                        }

                        /* Successful, save info in our internal list. */
                        entry->rdr.local_addr = ra->local_addr;
                        entry->rdr.alias_addr = ra->alias_addr;
                        entry->rdr.proto = NG_NAT_REDIRPROTO_ADDR;
                        bcopy(ra->description, entry->rdr.description,
                            NG_NAT_DESC_LENGTH);

                        /* Safety precaution. */
                        entry->rdr.description[NG_NAT_DESC_LENGTH-1] = '\0';

                        entry->rdr.id = priv->nextid++;
                        priv->rdrcount++;

                        /* Link to list of redirects. */
                        STAILQ_INSERT_TAIL(&priv->redirhead, entry, entries);

                        /* Response with id of newly added entry. */
                        NG_MKRESPONSE(resp, msg, sizeof(entry->rdr.id), M_NOWAIT);
                        if (resp == NULL) {
                                error = ENOMEM;
                                break;
                        }
                        bcopy(&entry->rdr.id, resp->data, sizeof(entry->rdr.id));
                    }
                        break;
                case NGM_NAT_REDIRECT_PROTO:
                    {
                        struct ng_nat_rdr_lst *entry;
                        struct ng_nat_redirect_proto *const rp =
                            (struct ng_nat_redirect_proto *)msg->data;

                        if (msg->header.arglen < sizeof(*rp)) {
                                error = EINVAL;
                                break;
                        }

                        if ((entry = malloc(sizeof(struct ng_nat_rdr_lst),
                            M_NETGRAPH_NAT, M_NOWAIT | M_ZERO)) == NULL) {
                                error = ENOMEM;
                                break;
                        }

                        /* Try actual redirect. */
                        entry->lnk = LibAliasRedirectProto(priv->lib,
                                rp->local_addr, rp->remote_addr,
                                rp->alias_addr, rp->proto);

                        if (entry->lnk == NULL) {
                                error = ENOMEM;
                                free(entry, M_NETGRAPH_NAT);
                                break;
                        }

                        /* Successful, save info in our internal list. */
                        entry->rdr.local_addr = rp->local_addr;
                        entry->rdr.alias_addr = rp->alias_addr;
                        entry->rdr.remote_addr = rp->remote_addr;
                        entry->rdr.proto = rp->proto;
                        bcopy(rp->description, entry->rdr.description,
                            NG_NAT_DESC_LENGTH);

                        /* Safety precaution. */
                        entry->rdr.description[NG_NAT_DESC_LENGTH-1] = '\0';

                        entry->rdr.id = priv->nextid++;
                        priv->rdrcount++;

                        /* Link to list of redirects. */
                        STAILQ_INSERT_TAIL(&priv->redirhead, entry, entries);

                        /* Response with id of newly added entry. */
                        NG_MKRESPONSE(resp, msg, sizeof(entry->rdr.id), M_NOWAIT);
                        if (resp == NULL) {
                                error = ENOMEM;
                                break;
                        }
                        bcopy(&entry->rdr.id, resp->data, sizeof(entry->rdr.id));
                    }
                        break;
                case NGM_NAT_REDIRECT_DYNAMIC:
                case NGM_NAT_REDIRECT_DELETE:
                    {
                        struct ng_nat_rdr_lst *entry;
                        uint32_t *const id = (uint32_t *)msg->data;

                        if (msg->header.arglen < sizeof(*id)) {
                                error = EINVAL;
                                break;
                        }

                        /* Find entry with supplied id. */
                        STAILQ_FOREACH(entry, &priv->redirhead, entries) {
                                if (entry->rdr.id == *id)
                                        break;
                        }

                        /* Not found. */
                        if (entry == NULL) {
                                error = ENOENT;
                                break;
                        }

                        if (msg->header.cmd == NGM_NAT_REDIRECT_DYNAMIC) {
                                if (LibAliasRedirectDynamic(priv->lib,
                                    entry->lnk) == -1) {
                                        error = ENOTTY; /* XXX Something better? */
                                        break;
                                }
                        } else {        /* NGM_NAT_REDIRECT_DELETE */
                                LibAliasRedirectDelete(priv->lib, entry->lnk);
                        }

                        /* Delete entry from our internal list. */
                        priv->rdrcount--;
                        STAILQ_REMOVE(&priv->redirhead, entry, ng_nat_rdr_lst, entries);
                        free(entry, M_NETGRAPH_NAT);
                    }
                        break;
                case NGM_NAT_ADD_SERVER:
                    {
                        struct ng_nat_rdr_lst *entry;
                        struct ng_nat_add_server *const as =
                            (struct ng_nat_add_server *)msg->data;

                        if (msg->header.arglen < sizeof(*as)) {
                                error = EINVAL;
                                break;
                        }

                        /* Find entry with supplied id. */
                        STAILQ_FOREACH(entry, &priv->redirhead, entries) {
                                if (entry->rdr.id == as->id)
                                        break;
                        }

                        /* Not found. */
                        if (entry == NULL) {
                                error = ENOENT;
                                break;
                        }

                        if (LibAliasAddServer(priv->lib, entry->lnk,
                            as->addr, htons(as->port)) == -1) {
                                error = ENOMEM;
                                break;
                        }

                        entry->rdr.lsnat++;
                    }
                        break;
                case NGM_NAT_LIST_REDIRECTS:
                    {
                        struct ng_nat_rdr_lst *entry;
                        struct ng_nat_list_redirects *ary; 
                        int i = 0;

                        NG_MKRESPONSE(resp, msg, sizeof(*ary) +
                            (priv->rdrcount) * sizeof(*entry), M_NOWAIT);
                        if (resp == NULL) {
                                error = ENOMEM;
                                break;
                        }

                        ary = (struct ng_nat_list_redirects *)resp->data;
                        ary->total_count = priv->rdrcount;

                        STAILQ_FOREACH(entry, &priv->redirhead, entries) {
                                bcopy(&entry->rdr, &ary->redirects[i++],
                                    sizeof(struct ng_nat_listrdrs_entry));
                        }
                    }
                        break;
                case NGM_NAT_PROXY_RULE:
                    {
                        char *cmd = (char *)msg->data;

                        if (msg->header.arglen < 6) {
                                error = EINVAL;
                                break;
                        }

                        if (LibAliasProxyRule(priv->lib, cmd) != 0)
                                error = ENOMEM;
                    }
                        break;
                case NGM_NAT_LIBALIAS_INFO:
                    {
                        struct ng_nat_libalias_info *i;

                        NG_MKRESPONSE(resp, msg,
                            sizeof(struct ng_nat_libalias_info), M_NOWAIT);
                        if (resp == NULL) {
                                error = ENOMEM;
                                break;
                        }
                        i = (struct ng_nat_libalias_info *)resp->data;
#define COPY(F) do {                                            \
        if (priv->lib->F >= 0 && priv->lib->F < UINT32_MAX)     \
                i->F = priv->lib->F;                            \
        else                                                    \
                i->F = UINT32_MAX;                              \
} while (0)
                
                        COPY(icmpLinkCount);
                        COPY(udpLinkCount);
                        COPY(tcpLinkCount);
                        COPY(pptpLinkCount);
                        COPY(sctpLinkCount);
                        COPY(protoLinkCount);
                        COPY(fragmentIdLinkCount);
                        COPY(fragmentPtrLinkCount);
                        COPY(sockCount);
#undef COPY
                    }
                        break;
                case NGM_NAT_SET_DLT:
                        if (msg->header.arglen != sizeof(uint8_t)) {
                                error = EINVAL;
                                break;
                        }
                        switch (*(uint8_t *) msg->data) {
                        case DLT_EN10MB:
                        case DLT_RAW:
                                priv->dlt = *(uint8_t *) msg->data;
                                break;
                        default:
                                error = EINVAL;
                                break;
                        }
                        break;
                default:
                        error = EINVAL;         /* unknown command */
                        break;
                }
                break;
                case NGM_NAT_GET_DLT:
                        NG_MKRESPONSE(resp, msg, sizeof(uint8_t), M_WAITOK);
                        if (resp == NULL) {
                                error = ENOMEM;
                                break;
                        }
                        *((uint8_t *) resp->data) = priv->dlt;
                        break;
        default:
                error = EINVAL;                 /* unknown cookie type */
                break;
        }

        NG_RESPOND_MSG(error, node, item, resp);
        NG_FREE_MSG(msg);
        return (error);
}

static int
ng_nat_rcvdata(hook_p hook, item_p item )
{
        const priv_p priv = NG_NODE_PRIVATE(NG_HOOK_NODE(hook));
        struct mbuf     *m;
        struct ip       *ip;
        int rval, ipofs, error = 0;
        char *c;

        /* We have no required hooks. */
        if (!(priv->flags & NGNAT_CONNECTED)) {
                NG_FREE_ITEM(item);
                return (ENXIO);
        }

        /* We have no alias address yet to do anything. */
        if (!(priv->flags & NGNAT_ADDR_DEFINED))
                goto send;

        m = NGI_M(item);

        if ((m = m_megapullup(m, m->m_pkthdr.len)) == NULL) {
                NGI_M(item) = NULL;     /* avoid double free */
                NG_FREE_ITEM(item);
                return (ENOBUFS);
        }

        NGI_M(item) = m;

        switch (priv->dlt) {
        case DLT_RAW:
                ipofs = 0;
                break;
        case DLT_EN10MB:
            {
                struct ether_header *eh;

                if (m->m_pkthdr.len < sizeof(struct ether_header)) {
                        NG_FREE_ITEM(item);
                        return (ENXIO);
                }
                eh = mtod(m, struct ether_header *);
                switch (ntohs(eh->ether_type)) {
                case ETHERTYPE_IP:
                        ipofs = sizeof(struct ether_header);
                        break;
                default:
                        goto send;
                }
                break;
            }
        default:
                panic("Corrupted priv->dlt: %u", priv->dlt);
        }

        if (m->m_pkthdr.len < ipofs + sizeof(struct ip))
                goto send;              /* packet too short to hold IP */

        c = (char *)mtodo(m, ipofs);
        ip = (struct ip *)mtodo(m, ipofs);

        if (ip->ip_v != IPVERSION)
                goto send;              /* other IP version, let it pass */
        uint16_t ip_len = ntohs(ip->ip_len);
        if (m->m_pkthdr.len < (ipofs + ip_len))
                goto send;              /* packet too short (i.e. fragmented or broken) */

        /*
         * We drop packet when:
         * 1. libalias returns PKT_ALIAS_ERROR;
         * 2. For incoming packets:
         *      a) for unresolved fragments;
         *      b) libalias returns PKT_ALIAS_IGNORED and
         *              PKT_ALIAS_DENY_INCOMING flag is set.
         */
        if (hook == priv->in) {
                rval = LibAliasIn(priv->lib, c, m->m_len - ipofs +
                    M_TRAILINGSPACE(m));
                if (rval == PKT_ALIAS_ERROR ||
                    rval == PKT_ALIAS_UNRESOLVED_FRAGMENT ||
                    (rval == PKT_ALIAS_IGNORED &&
                     (priv->lib->packetAliasMode &
                      PKT_ALIAS_DENY_INCOMING) != 0)) {
                        NG_FREE_ITEM(item);
                        return (EINVAL);
                }
        } else if (hook == priv->out) {
                rval = LibAliasOut(priv->lib, c, m->m_len - ipofs +
                    M_TRAILINGSPACE(m));
                if (rval == PKT_ALIAS_ERROR) {
                        NG_FREE_ITEM(item);
                        return (EINVAL);
                }
        } else
                panic("ng_nat: unknown hook!\n");

        if (rval == PKT_ALIAS_RESPOND)
                m->m_flags |= M_SKIP_FIREWALL;

        /* Re-read just in case it has been updated */
        ip_len = ntohs(ip->ip_len);
        int new_m_len = ip_len + ipofs;

        if (new_m_len > (m->m_len +  M_TRAILINGSPACE(m))) {
                /*
                 * This is just a safety railguard to make sure LibAlias has not
                 * screwed the IP packet up somehow, should probably be KASSERT()
                 * at some point. Calling in_delayed_cksum() will parse IP packet
                 * again and reliably panic if there is less data than the IP
                 * header declares, there might be some other places too.
                 */
                log(LOG_ERR, "ng_nat_rcvdata: outgoing packet corrupted, "
                    "not enough data: expected %d, available (%d - %d)\n",
                    ip_len, m->m_len + (int)M_TRAILINGSPACE(m), ipofs);
                NG_FREE_ITEM(item);
                return (ENXIO);
        }

        m->m_pkthdr.len = m->m_len = new_m_len;

        if ((ip->ip_off & htons(IP_OFFMASK)) != 0 || ip->ip_p != IPPROTO_TCP)
                goto send;

        uint16_t pl_offset = ip->ip_hl << 2;
        struct tcphdr *th = (struct tcphdr *)((caddr_t)ip + pl_offset);

        /*
         * Here is our terrible HACK.
         *
         * Sometimes LibAlias edits contents of TCP packet.
         * In this case it needs to recompute full TCP
         * checksum. However, the problem is that LibAlias
         * doesn't have any idea about checksum offloading
         * in kernel. To workaround this, we do not do
         * checksumming in LibAlias, but only mark the
         * packets with TH_RES1 in the th_x2 field. If we
         * receive a marked packet, we calculate correct
         * checksum for it aware of offloading.
         *
         * Why do I do such a terrible hack instead of
         * recalculating checksum for each packet?
         * Because the previous checksum was not checked!
         * Recalculating checksums for EVERY packet will
         * hide ALL transmission errors. Yes, marked packets
         * still suffer from this problem. But, sigh, natd(8)
         * has this problem, too.
         */

        if (!(tcp_get_flags(th) & TH_RES1))
                goto send;

        tcp_set_flags(th, tcp_get_flags(th) & ~TH_RES1);
        th->th_sum = in_pseudo(ip->ip_src.s_addr, ip->ip_dst.s_addr,
            htons(IPPROTO_TCP + ip_len - pl_offset));

        if ((m->m_pkthdr.csum_flags & CSUM_TCP) != 0)
                goto send;

        m->m_pkthdr.csum_data = offsetof(struct tcphdr, th_sum);
        in_delayed_cksum_o(m, ipofs);

send:
        if (hook == priv->in)
                NG_FWD_ITEM_HOOK(error, item, priv->out);
        else
                NG_FWD_ITEM_HOOK(error, item, priv->in);

        return (error);
}

static int
ng_nat_shutdown(node_p node)
{
        const priv_p priv = NG_NODE_PRIVATE(node);

        NG_NODE_SET_PRIVATE(node, NULL);
        NG_NODE_UNREF(node);

        /* Free redirects list. */
        while (!STAILQ_EMPTY(&priv->redirhead)) {
                struct ng_nat_rdr_lst *entry = STAILQ_FIRST(&priv->redirhead);
                STAILQ_REMOVE_HEAD(&priv->redirhead, entries);
                free(entry, M_NETGRAPH_NAT);
        }

        /* Final free. */
        LibAliasUninit(priv->lib);
        free(priv, M_NETGRAPH_NAT);

        return (0);
}

static int
ng_nat_disconnect(hook_p hook)
{
        const priv_p priv = NG_NODE_PRIVATE(NG_HOOK_NODE(hook));

        priv->flags &= ~NGNAT_CONNECTED;

        if (hook == priv->out)
                priv->out = NULL;
        if (hook == priv->in)
                priv->in = NULL;

        if (priv->out == NULL && priv->in == NULL)
                ng_rmnode_self(NG_HOOK_NODE(hook));

        return (0);
}

static unsigned int
ng_nat_translate_flags(unsigned int x)
{
        unsigned int    res = 0;

        if (x & NG_NAT_LOG)
                res |= PKT_ALIAS_LOG;
        if (x & NG_NAT_DENY_INCOMING)
                res |= PKT_ALIAS_DENY_INCOMING;
        if (x & NG_NAT_SAME_PORTS)
                res |= PKT_ALIAS_SAME_PORTS;
        if (x & NG_NAT_UNREGISTERED_ONLY)
                res |= PKT_ALIAS_UNREGISTERED_ONLY;
        if (x & NG_NAT_RESET_ON_ADDR_CHANGE)
                res |= PKT_ALIAS_RESET_ON_ADDR_CHANGE;
        if (x & NG_NAT_PROXY_ONLY)
                res |= PKT_ALIAS_PROXY_ONLY;
        if (x & NG_NAT_REVERSE)
                res |= PKT_ALIAS_REVERSE;
        if (x & NG_NAT_UNREGISTERED_CGN)
                res |= PKT_ALIAS_UNREGISTERED_CGN;
        if (x & NG_NAT_UDP_EIM)
                res |= PKT_ALIAS_UDP_EIM;

        return (res);
}