/* $Id$ */

/*
 *   Copyright (c) 2001-2010 Aaron Turner <aturner at synfin dot net>
 *   Copyright (c) 2013-2026 Fred Klassen <tcpreplay at appneta dot com> - AppNeta
 *
 *   The Tcpreplay Suite of tools is free software: you can redistribute it
 *   and/or modify it under the terms of the GNU General Public License as
 *   published by the Free Software Foundation, either version 3 of the
 *   License, or with the authors permission any later version.
 *
 *   The Tcpreplay Suite is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with the Tcpreplay Suite.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "defines.h"
#include "config.h"
#include "common.h"
#include <lib/sll.h>
#include <arpa/inet.h>
#include <ctype.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>

#if defined HAVE_PCAP_VERSION && !defined HAVE_WIN32
extern const char pcap_version[];
#endif

#define JUNIPER_FLAG_NO_L2 0x02 /* L2 header */
#define JUNIPER_FLAG_EXT 0x80   /* Juniper extensions present */
#define JUNIPER_PCAP_MAGIC "MGC"

static void *get_ipv6_next(struct tcpr_ipv6_ext_hdr_base *exthdr, const u_char *end_ptr);

/**
 * Depending on what version of libpcap there are different ways to get
 * the version of the libpcap library.  This presents a unified way to
 * get that information.
 */
const char *
get_pcap_version(void)
{
#if defined HAVE_WINPCAP
    static char ourver[255];
    char *last, *version;
    /* WinPcap returns a string like:
     * WinPcap version 4.0 (packet.dll version 4.0.0.755), based on libpcap version 0.9.5
     */
    version = safe_strdup(pcap_lib_version());

    strtok_r(version, " ", &last);
    strtok_r(NULL, " ", &last);
    strlcpy(ourver, strtok_r(NULL, " ", &last), 255);
    safe_free(version);
    return ourver;
#elif defined HAVE_PCAP_VERSION
    return pcap_version;
#else
    return pcap_lib_version();
#endif
}

/*
 * Advance L2 protocol and L2 length past any MPLS labels.
 * e.g. https://www.cloudshark.org/captures/20f210391b21
 *
 * If EoMPLS is detected, also advance L2 offset to point to the
 * encapsulated L2.
 * e.g. https://www.cloudshark.org/captures/b15412060b3d
 *
 * pktdata:       pointer to the raw packet
 * datalen:       number of bytes captured in the packet
 * next_protocol: reference to the next L2 protocol to be examined and possibly updated
 * l2len:         reference to the length of the L2 header discovered so far
 * l2offset:      reference to the offset to the start of the L2 header - typically 0
 *
 * return 0 on success, -1 on failure
 */
int
parse_mpls(const u_char *pktdata, uint32_t datalen, uint16_t *next_protocol, uint32_t *l2len, uint32_t *l2offset)
{
    struct tcpr_mpls_label *mpls_label;
    const u_char *end_ptr = pktdata + datalen;
    u_char first_nibble;
    eth_hdr_t *eth_hdr;
    bool bos = false;
    uint32_t label;
    int len;

    assert(next_protocol);
    assert(l2len);
    assert(l2offset);

    len = (int)*l2len;

    /* move over MPLS labels until we get to the last one */
    while (!bos) {
        if (pktdata + len + sizeof(*mpls_label) > end_ptr) {
            warnx("parse_mpls: Need at least %zu bytes for MPLS header but only %u available",
                  sizeof(*mpls_label) + len,
                  datalen);
            return -1;
        }

        mpls_label = (struct tcpr_mpls_label *)(pktdata + len);
        len += sizeof(*mpls_label);
        bos = (ntohl(mpls_label->entry) & MPLS_LS_S_MASK) != 0;
        label = ntohl(mpls_label->entry) >> MPLS_LS_LABEL_SHIFT;
        if (label == MPLS_LABEL_GACH) {
            /* Generic Associated Channel Header */
            warn("GACH MPLS label not supported at this time");
            return -1;
        }
    }

    if ((u_char *)(mpls_label + 1) + 1 > end_ptr) {
        warnx("parse_mpls: Need at least %zu bytes for MPLS label but only %u available",
              sizeof(*mpls_label) + 1,
              datalen);
        return -1;
    }

    first_nibble = *((u_char *)(mpls_label + 1)) >> 4;
    switch (first_nibble) {
    case 4:
        *next_protocol = ETHERTYPE_IP;
        break;
    case 6:
        *next_protocol = ETHERTYPE_IP6;
        break;
    case 0:
        /* EoMPLS - jump over PW Ethernet Control Word and handle
         * inner Ethernet header
         */
        if (pktdata + len + 4 + sizeof(*eth_hdr) > end_ptr) {
            warnx("parse_mpls: Need at least %zu bytes for EoMPLS header but only %u available",
                  sizeof(*eth_hdr) + len + 4,
                  datalen);
            return -1;
        }

        len += 4;
        *l2offset = len;
        eth_hdr = (eth_hdr_t *)(pktdata + len);
        len += sizeof(*eth_hdr);
        *next_protocol = ntohs(eth_hdr->ether_type);
        break;
    default:
        warn("parse_mpls:suspect Generic Associated Channel Header");
        return -1;
    }

    *l2len = (uint32_t)len;
    return 0;
}

/*
 * Advance L2 protocol and L2 length past any VLAN tags.
 * e.g. https://www.cloudshark.org/captures/e4fa464563d2
 *
 * pktdata:       pointer to the raw packet
 * datalen:       number of bytes captured in the packet
 * next_protocol: reference to the next L2 protocol to be examined and possibly updated
 * l2len:         reference to the length of the L2 header discovered so far
 *
 * return 0 on success, -1 on failure
 */
int
parse_vlan(const u_char *pktdata, uint32_t datalen, uint16_t *next_protocol, uint32_t *l2len)
{
    vlan_hdr_t *vlan_hdr;
    if ((size_t)datalen < *l2len + sizeof(*vlan_hdr)) {
        warnx("parse_vlan: Need at least %zu bytes for VLAN header but only %u available", sizeof(*vlan_hdr), datalen);
        return -1;
    }
    vlan_hdr = (vlan_hdr_t *)(pktdata + *l2len);
    *next_protocol = ntohs(vlan_hdr->vlan_tpid);
    *l2len += sizeof(vlan_hdr_t);

    return 0;
}

/*
 * Loop through all non-protocol L2 headers while updating key variables
 *
 * pktdata:       pointer to the raw packet
 * datalen:       number of bytes captured in the packet
 * next_protocol: reference to the next L2 protocol to be examined and possibly updated
 * l2len:         reference to the length of the L2 header discovered so far
 * l2offset:      reference to the offset to the start of the L2 header - typically 0
 * vlan_offset: reference to the offset to the start of the VLAN headers, if any
 *
 * return 0 on success, -1 on failure
 */
static int
parse_metadata(const u_char *pktdata,
               uint32_t datalen,
               uint16_t *next_protocol,
               uint32_t *l2len,
               uint32_t *l2offset,
               uint32_t *vlan_offset)
{
    bool done = false;
    assert(next_protocol);
    assert(l2len);
    assert(l2offset);
    assert(vlan_offset);

    if (!pktdata || !datalen)
        errx(-1, "parse_metadata: invalid L2 parameters: pktdata=0x%p len=%d", pktdata, datalen);

    while (!done) {
        switch (*next_protocol) {
        case ETHERTYPE_VLAN:
        case ETHERTYPE_Q_IN_Q:
        case ETHERTYPE_8021QINQ:
            if (*vlan_offset == 0)
                *vlan_offset = *l2len;

            if (parse_vlan(pktdata, datalen, next_protocol, l2len))
                return -1;

            break;
        case ETHERTYPE_MPLS:
        case ETHERTYPE_MPLS_MULTI:
            if (parse_mpls(pktdata, datalen, next_protocol, l2len, l2offset))
                return -1;

            break;
        default:
            done = true;
        }
    }

    return 0;
}

/*
 * Parse raw packet and get the L3 protocol and L2 length. In cases where the
 * L2 header is not at the beginning of the packet
 * (e.g. DLT_JUNIPER_ETHER or EoMPLS), report the offset to the start of the
 * L2 header
 *
 * pktdata:     pointer to the raw packet
 * datalen:     number of bytes captured in the packet
 * datalink:    data link type of the packet
 * protocol:    reference to the L3 protocol as discovered in the L2 header
 * l2len:       reference to the total length of the L2 header
 * l2offset:    reference to the offset to the start of the L2 header (typically 0)
 * vlan_offset: reference to the offset to the start of the VLAN headers, if any
 *
 * return 0 on success, -1 on failure
 */
int
get_l2len_protocol(const u_char *pktdata,
                   uint32_t datalen,
                   int datalink,
                   uint16_t *protocol,
                   uint32_t *l2len,
                   uint32_t *l2offset,
                   uint32_t *vlan_offset)
{
    assert(protocol);
    assert(l2len);
    assert(l2offset);
    assert(vlan_offset);

    if (!pktdata || !datalen) {
        err_no_exitx("get_l2len_protocol: invalid L2 parameters: pktdata=0x%p len=%d", pktdata, datalen);
        return -1;
    }

    *protocol = 0;
    *l2len = 0;
    *l2offset = 0;
    *vlan_offset = 0;

    switch (datalink) {
    case DLT_NULL:
    case DLT_RAW:
        if ((pktdata[0] >> 4) == 4)
            *protocol = ETHERTYPE_IP;
        else if ((pktdata[0] >> 4) == 6)
            *protocol = ETHERTYPE_IP6;
        break;
    case DLT_JUNIPER_ETHER:
        if (datalen < 4) {
            warnx("%s (0x%x): Need at least 4 bytes for DLT_JUNIPER_ETHER but only %u available",
                  pcap_datalink_val_to_description(datalink),
                  datalink,
                  datalen);
            return -1;
        }

        if (memcmp(pktdata, JUNIPER_PCAP_MAGIC, 3) != 0) {
            warnx("%s (0x%x): No JUNIPER_PCAP_MAGIC Magic Number found during protocol lookup",
                  pcap_datalink_val_to_description(datalink),
                  datalink);
            return -1;
        }

        if ((pktdata[3] & JUNIPER_FLAG_EXT) == JUNIPER_FLAG_EXT) {
            if (datalen < 6) {
                warnx("%s (0x%x): Need at least 6 bytes for JUNIPER_FLAG_EXT but only %u available",
                      pcap_datalink_val_to_description(datalink),
                      datalink,
                      datalen);
                return -1;
            }

            *l2offset = ntohs(*((uint16_t *)&pktdata[4]));
            *l2offset += 6; /* MGC + flags + ext_total_len */
        } else {
            *l2offset = 4; /* MGC + flags (no header extensions) */
        }

        if ((pktdata[3] & JUNIPER_FLAG_NO_L2) == JUNIPER_FLAG_NO_L2) {
            /* no L2 header present - *l2offset is actually IP offset */
            uint32_t ip_hdr_offset = *l2offset;
            uint32_t hdrSpaceNeeded = ip_hdr_offset + 1;
            if (datalen < hdrSpaceNeeded) {
                warnx("%s (0x%x): Need at least %u bytes for JUNIPER_FLAG_NO_L2 but only %u available",
                      pcap_datalink_val_to_description(datalink),
                      hdrSpaceNeeded,
                      datalink,
                      datalen);
                return -1;
            }

            if ((pktdata[ip_hdr_offset] >> 4) == 4)
                *protocol = ETHERTYPE_IP;
            else if ((pktdata[ip_hdr_offset] >> 4) == 6)
                *protocol = ETHERTYPE_IP6;

            return 0;
        }

        /* fall through */
    case DLT_EN10MB: {
        eth_hdr_t *eth_hdr;
        uint16_t ether_type;
        uint32_t l2_net_off = sizeof(*eth_hdr) + *l2offset;

        if (datalen <= l2_net_off + 4) {
            warnx("%s (0x%x): Need at least %u bytes for DLT_EN10MB but only %u available",
                  pcap_datalink_val_to_description(datalink),
                  datalink,
                  l2_net_off + 4,
                  datalen);
            return -1;
        }

        eth_hdr = (eth_hdr_t *)(pktdata + *l2offset);
        ether_type = ntohs(eth_hdr->ether_type);
        if (parse_metadata(pktdata, datalen, &ether_type, &l2_net_off, l2offset, vlan_offset))
            return -1;

        *l2len = l2_net_off;
        if (ether_type >= 1536) {
            /* Ethernet II frame - return in host order */
            *protocol = ether_type;
        } else if (ether_type > 1500) {
            warnx("%s (0x%x): unsupported 802.3 length %u",
                  pcap_datalink_val_to_description(datalink),
                  datalink,
                  ether_type);
            return -1;
        } else {
            /* 803.3 frame */
            /* we don't modify 802.3 protocols */
            return -1;
        }
        break;
    }
    case DLT_PPP_SERIAL:
        if ((size_t)datalen < sizeof(struct tcpr_pppserial_hdr)) {
            warnx("%s (0x%x): Need at least %zu bytes for DLT_PPP_SERIAL but only %u available",
                  pcap_datalink_val_to_description(datalink),
                  datalink,
                  sizeof(struct tcpr_pppserial_hdr),
                  datalen);
            return -1;
        }

        struct tcpr_pppserial_hdr *ppp = (struct tcpr_pppserial_hdr *)pktdata;
        *l2len = sizeof(*ppp);
        if (ntohs(ppp->protocol) == 0x0021)
            *protocol = ETHERTYPE_IP;
        else
            *protocol = ntohs(ppp->protocol);

        break;
    case DLT_C_HDLC:
        if (datalen < CISCO_HDLC_LEN) {
            warnx("%s (0x%x): Need at least %u bytes for DLT_C_HDLC but only %u available",
                  pcap_datalink_val_to_description(datalink),
                  datalink,
                  CISCO_HDLC_LEN,
                  datalen);
            return -1;
        }

        hdlc_hdr_t *hdlc_hdr = (hdlc_hdr_t *)pktdata;
        *l2len = sizeof(*hdlc_hdr);
        *protocol = ntohs(hdlc_hdr->protocol);
        break;
    case DLT_LINUX_SLL:
        if (datalen < SLL_HDR_LEN) {
            warnx("%s (0x%x): Need at least %u bytes for DLT_LINUX_SLL but only %u available",
                  pcap_datalink_val_to_description(datalink),
                  datalink,
                  SLL_HDR_LEN,
                  datalen);
            return -1;
        }

        *l2len = SLL_HDR_LEN;
        sll_hdr_t *sll_hdr = (sll_hdr_t *)pktdata;
        *protocol = ntohs(sll_hdr->sll_protocol);
        break;
    case DLT_LINUX_SLL2:
        if (datalen < SLL2_HDR_LEN) {
            warnx("%s (0x%x): Need at least %u bytes for DLT_LINUX_SLL2 but only %u available",
                  pcap_datalink_val_to_description(datalink),
                  datalink,
                  SLL2_HDR_LEN,
                  datalen);
            return -1;
        }

        *l2len = SLL2_HDR_LEN;
        sll2_hdr_t *sll2_hdr = (sll2_hdr_t *)pktdata;
        *protocol = ntohs(sll2_hdr->sll2_protocol);
        break;
    default:
        errx(-1,
             "Unable to process unsupported DLT type: %s (0x%x)",
             pcap_datalink_val_to_description(datalink),
             datalink);
    }

    return 0;
}

/**
 * returns the length in number of bytes of the L2 header, or -1 on error
 */
int
get_l2len(const u_char *pktdata, int datalen, int datalink)
{
    uint16_t _U_ protocol;
    uint32_t _U_ l2offset;
    uint32_t _U_ vlan_offset;
    uint32_t l2len = 0;

    int res = get_l2len_protocol(pktdata, datalen, datalink, &protocol, &l2len, &l2offset, &vlan_offset);

    if (res == -1)
        return 0;

    return (int)l2len;
}

/**
 * \brief returns a ptr to the ipv4 header + data or NULL if it's not IP
 *
 * we may use an extra buffer for the IP header (and above)
 * on strictly aligned systems where the layer 2 header doesn't
 * fall on a 4 byte boundary (like a standard Ethernet header)
 *
 * Note: you can cast the result as an ip_hdr_t, but you'll be able
 * to access data above the header minus any stripped L2 data
 */
const u_char *
get_ipv4(const u_char *pktdata, int datalen, int datalink, u_char **newbuff)
{
    const u_char *packet = pktdata;
    const u_char *ip_hdr = NULL;
    ssize_t pkt_len = datalen;
    uint32_t _U_ vlan_offset;
    uint32_t l2offset;
    uint16_t proto;
    uint32_t l2len;
    int res;

    assert(packet);
    assert(pkt_len);
    assert(*newbuff);

    res = get_l2len_protocol(packet, pkt_len, datalink, &proto, &l2len, &l2offset, &vlan_offset);

    /* sanity... pkt_len must be > l2len + IP header len*/
    if (res == -1 || l2len + TCPR_IPV4_H > pkt_len) {
        dbg(1, "get_ipv4(): Layer 2 len > total packet len, hence no IP header");
        return NULL;
    }

    if (proto != ETHERTYPE_IP)
        return NULL;

    packet += l2offset;
    l2len -= l2offset;
#ifdef FORCE_ALIGN
    pkt_len -= l2offset;

    /*
     * copy layer 3 and up to our temp packet buffer
     * for now on, we have to edit the packetbuff because
     * just before we send the packet, we copy the packetbuff
     * back onto the pkt.data + l2len buffer
     * we do all this work to prevent byte alignment issues
     */
    if (l2len % sizeof(long)) {
        memcpy(*newbuff, (packet + l2len), (pkt_len - l2len));
        ip_hdr = *newbuff;
    } else {
        /* we don't have to do a memcpy if l2len lands on a boundary */
        ip_hdr = (packet + l2len);
    }
#else
    /*
     * on non-strict byte align systems, don't need to memcpy(),
     * just point to l2len bytes into the existing buffer
     */
    ip_hdr = (packet + l2len);
#endif

    return ip_hdr;
}

/**
 * \brief returns a ptr to the ipv6 header + data or NULL if it's not IP
 *
 * we may use an extra buffer for the IP header (and above)
 * on strictly aligned systems where the layer 2 header doesn't
 * fall on a 4 byte boundary (like a standard Ethernet header)
 *
 * Note: you can cast the result as an ip_hdr_t, but you'll be able
 * to access data above the header minus any stripped L2 data
 */
const u_char *
get_ipv6(const u_char *pktdata, int datalen, int datalink, u_char **newbuff)
{
    const u_char *packet = pktdata;
    const u_char *ip6_hdr = NULL;
    ssize_t pkt_len = datalen;
    uint32_t _U_ vlan_offset;
    uint32_t l2offset;
    uint16_t proto;
    uint32_t l2len;
    int res;

    assert(packet);
    assert(pkt_len);
    assert(*newbuff);

    res = get_l2len_protocol(packet, pkt_len, datalink, &proto, &l2len, &l2offset, &vlan_offset);

    /* sanity... pkt_len must be > l2len + IP header len*/
    if (res == -1 || l2len + TCPR_IPV6_H > pkt_len) {
        dbg(1, "get_ipv6(): Layer 2 len > total packet len, hence no IPv6 header");
        return NULL;
    }

    if (proto != ETHERTYPE_IP6)
        return NULL;

    packet += l2offset;
    l2len -= l2offset;
#ifdef FORCE_ALIGN
    pkt_len -= l2offset;

    /*
     * copy layer 3 and up to our temp packet buffer
     * for now on, we have to edit the packetbuff because
     * just before we send the packet, we copy the packetbuff
     * back onto the pkt.data + l2len buffer
     * we do all this work to prevent byte alignment issues
     */
    if (l2len % sizeof(long)) {
        memcpy(*newbuff, (packet + l2len), (pkt_len - l2len));
        ip6_hdr = *newbuff;
    } else {
        /* we don't have to do a memcpy if l2len lands on a boundary */
        ip6_hdr = (packet + l2len);
    }
#else
    /*
     * on non-strict byte align systems, don't need to memcpy(),
     * just point to l2len bytes into the existing buffer
     */
    ip6_hdr = (packet + l2len);
#endif

    return ip6_hdr;
}

/**
 * \brief returns a pointer to the layer 4 header which is just beyond the IPv4 header
 *
 * If the packet is to short, returns NULL
 */
void *
get_layer4_v4(const ipv4_hdr_t *ip_hdr, const u_char *end_ptr)
{
    void *ptr;

    assert(ip_hdr);
    assert(end_ptr);

    ptr = (u_char *)ip_hdr + (ip_hdr->ip_hl << 2);
    /* make sure we don't jump over the end of the buffer */
    if ((u_char *)ptr > end_ptr)
        return NULL;

    return ((void *)ptr);
}

/**
 * returns a pointer to the layer 4 header which is just beyond the IPv6 header
 * and any extension headers or NULL when there is none as in the case of
 * v6 Frag or ESP header.  Function is recursive.
 */
void *
get_layer4_v6(const ipv6_hdr_t *ip6_hdr, const u_char *end_ptr)
{
    struct tcpr_ipv6_ext_hdr_base *next, *exthdr;
    bool done = false;
    uint8_t proto;

    assert(ip6_hdr);
    assert(end_ptr);

    /* jump to the end of the IPv6 header */
    next = (struct tcpr_ipv6_ext_hdr_base *)((u_char *)ip6_hdr + TCPR_IPV6_H);
    if ((u_char *)next > end_ptr)
        return NULL;

    proto = ip6_hdr->ip_nh;
    while (!done) {
        dbgx(3, "Processing proto: 0x%hx", (uint16_t)proto);

        switch (proto) {
        /* recurse due to v6-in-v6, need to recast next as an IPv6 Header */
        case TCPR_IPV6_NH_IPV6:
            dbg(3, "recursing due to v6-in-v6");
            next = get_layer4_v6((ipv6_hdr_t *)next, end_ptr);
            break;

        /* loop again */
        case TCPR_IPV6_NH_AH:
        case TCPR_IPV6_NH_ROUTING:
        case TCPR_IPV6_NH_DESTOPTS:
        case TCPR_IPV6_NH_HBH:
            dbgx(3, "Going deeper due to extension header 0x%02X", proto);
            exthdr = get_ipv6_next(next, end_ptr);
            if (exthdr == NULL) {
                next = NULL;
                done = true;
                break;
            }
            proto = exthdr->ip_nh;
            next = exthdr;
            break;

        /*
         * handle (unparsable) IPv6 fragment data
         */
        case TCPR_IPV6_NH_FRAGMENT:
            // next points to l4 data
            dbgx(3, "Go deeper due to fragment extension header 0x%02X", proto);
            exthdr = get_ipv6_next(next, end_ptr);
            if ((exthdr == NULL) || ((u_char *)exthdr > end_ptr)) {
                next = NULL;
                done = true;
                break;
            }
            proto = exthdr->ip_nh;
            next = exthdr;
            // done = true;
            break;

        /*
         * Can't handle.  Unparsable IPv6 encrypted data
         */
        case TCPR_IPV6_NH_ESP:
            next = NULL;
            done = true;
            break;

        /*
         * no further processing, either TCP, UDP, ICMP, etc...
         */
        default:
            if (proto != ip6_hdr->ip_nh && next) {
                dbgx(3, "Returning byte offset of this ext header: %u", IPV6_EXTLEN_TO_BYTES(next->ip_len));
                next = (void *)((u_char *)next + IPV6_EXTLEN_TO_BYTES(next->ip_len));
                if ((u_char*)next > end_ptr)
                    return NULL;
            } else {
                dbgx(3, "%s", "Returning end of IPv6 Header");
            }

            done = true;
        } /* switch */

        if (next == NULL)
            done = true;
    } /* while */

    return next;
}

/**
 * returns the next payload or header of the current extension header
 * returns NULL for none/ESP.
 */
static void *
get_ipv6_next(struct tcpr_ipv6_ext_hdr_base *exthdr, const u_char *end_ptr)
{
    uint8_t extlen;
    u_char *ptr;
    assert(exthdr);

    if ((u_char *)exthdr + sizeof(*exthdr) > end_ptr)
        return NULL;

    dbgx(3, "Jumping to next IPv6 header.  Processing 0x%02x", exthdr->ip_nh);
    switch (exthdr->ip_nh) {
    /* no further processing */
    case TCPR_IPV6_NH_NO_NEXT:
    case TCPR_IPV6_NH_ESP:
        dbg(3, "No-Next or ESP... can't go any further...");
        return NULL;

    /*
     * fragment header is fixed size
     * FIXME: Frag header has further ext headers (has a ip_nh field)
     * but I don't support it because there's never a full L4 + payload beyond.
     */
    case TCPR_IPV6_NH_FRAGMENT:
        dbg(3, "Looks like were a fragment header. Returning some frag'd data.");
        ptr = (void *)((u_char *)exthdr + sizeof(struct tcpr_ipv6_frag_hdr));
        if (ptr > end_ptr)
            return NULL;
        return (void *)ptr;

    /* all the rest require us to go deeper using the ip_len field */
    case TCPR_IPV6_NH_IPV6:
    case TCPR_IPV6_NH_ROUTING:
    case TCPR_IPV6_NH_DESTOPTS:
    case TCPR_IPV6_NH_HBH:
    case TCPR_IPV6_NH_AH:
        extlen = IPV6_EXTLEN_TO_BYTES(exthdr->ip_len);
        if (extlen == 0) {
            dbg(3, "Malformed IPv6 extension header...");
            return NULL;
        }
        dbgx(3,
             "Looks like we're an ext header (0x%hhx).  Jumping %u bytes"
             " to the next",
             exthdr->ip_nh,
             extlen);
        ptr = (u_char *)exthdr + extlen;
        if (ptr > end_ptr)
            return NULL;
        return (void *)ptr;

    default:
        dbg(3, "Must not be a v6 extension header... returning self");
        return (void *)exthdr;
    }
}

/**
 * returns the protocol of the actual layer4 header by processing through
 * the extension headers
 */
uint8_t
get_ipv6_l4proto(const ipv6_hdr_t *ip6_hdr, const u_char *end_ptr)
{
    u_char *ptr = (u_char *)ip6_hdr + TCPR_IPV6_H; /* jump to the end of the IPv6 header */
    uint8_t proto;
    struct tcpr_ipv6_ext_hdr_base *exthdr = NULL;

    assert(ip6_hdr);

    if (ptr > end_ptr)
        return TCPR_IPV6_NH_NO_NEXT;

    proto = ip6_hdr->ip_nh;
    while (TRUE) {
        dbgx(3, "Processing next proto 0x%02X", proto);
        switch (proto) {
        /* no further processing for IPV6 types with nothing beyond them */
        case TCPR_IPV6_NH_NO_NEXT:
        case TCPR_IPV6_NH_FRAGMENT:
        case TCPR_IPV6_NH_ESP:
            dbg(3, "No-Next or ESP... can't go any further...");
            return proto;

        /* recurse */
        case TCPR_IPV6_NH_IPV6:
            dbg(3, "Recursing due to v6 in v6");
            return get_ipv6_l4proto((ipv6_hdr_t *)ptr, end_ptr);

        /* loop again */
        case TCPR_IPV6_NH_AH:
        case TCPR_IPV6_NH_ROUTING:
        case TCPR_IPV6_NH_DESTOPTS:
        case TCPR_IPV6_NH_HBH:
            dbgx(3, "Jumping to next extension header (0x%hhx)", proto);
            exthdr = get_ipv6_next((struct tcpr_ipv6_ext_hdr_base *)ptr, end_ptr);
            if (exthdr == NULL || (u_char *)exthdr + sizeof(*exthdr) > end_ptr)
                return TCPR_IPV6_NH_NO_NEXT;
            proto = exthdr->ip_nh;
            ptr = (u_char *)exthdr;
            break;

        /* should be TCP, UDP or the like */
        default:
            dbgx(3, "Selecting next L4 Proto as: 0x%02x", proto);
            return proto;
        } /* switch */
    }     /* while */
}

/**
 * \brief Converts a human readable IPv4 address to a binary one
 *
 * stolen from LIBNET since I didn't want to have to deal with
 * passing a libnet_t around.  Returns 0xFFFFFFFF (255.255.255.255)
 * on error
 */
uint32_t
get_name2addr4(const char *hostname, bool dnslookup)
{
    struct in_addr addr;
#if !defined HAVE_INET_ATON && defined HAVE_INET_ADDR
    struct hostent *host_ent;
#endif

    if (dnslookup) {
#ifdef HAVE_INET_ATON
        if (inet_aton(hostname, &addr) != 1) {
            return (0xffffffff);
        }
#elif defined HAVE_INET_ADDR

        if ((addr.s_addr = inet_addr(hostname)) == INADDR_NONE) {
            if (!(host_ent = gethostbyname(hostname))) {
                warnx("unable to resolve %s: %s", hostname, strerror(errno));
                /* this is actually 255.255.255.255 */
                return (0xffffffff);
            }

            /* was: host_ent->h_length); */
            memcpy(&addr.s_addr, host_ent->h_addr, sizeof(addr.s_addr));
        }
#else
        warn("Unable to support get_name2addr4 w/ resolve");
        /* call ourselves recursively once w/o resolving the hostname */
        return get_name2addr4(hostname, DNS_DONT_RESOLVE);
#endif
        /* return in network byte order */
        return (addr.s_addr);
    } else {
        /*
         *  We only want dots 'n decimals.
         */
        int i;
        uint32_t m;

        if (!isdigit(hostname[0])) {
            warnx("Expected dotted-quad notation (%s) when DNS lookups are disabled", hostname);
            /* XXX - this is actually 255.255.255.255 */
            return (-1);
        }

        m = 0;
        for (i = 0; i < 4; i++) {
            u_int val;

            m <<= 8;
            if (*hostname) {
                val = 0;
                while (*hostname && *hostname != '.') {
                    val *= 10;
                    val += *hostname - '0';
                    if (val > 255) {
                        dbgx(4, "value %d > 255 for dotted quad", val);
                        /* this is actually 255.255.255.255 */
                        return (-1);
                    }
                    hostname++;
                }
                m |= val;
                if (*hostname) {
                    hostname++;
                }
            }
        }
        /* host byte order */
        return (ntohl(m));
    }
}

/**
 *  \brief Converts human readable IPv6 address to binary value
 *
 * Wrapper around inet_pton
 * Returns 1 for valid, 0 for not parsable and -1 for system error.
 * Does not support DNS.
 */
int
get_name2addr6(const char *hostname, bool dnslookup, struct tcpr_in6_addr *addr)
{
    (void)dnslookup; /* prevent warning about unused arg */

#ifdef HAVE_INET_PTON
    return inet_pton(AF_INET6, hostname, addr);
#else
#error "Unable to support get_name2addr6: Missing inet_pton() support."
#endif
}

/**
 * \brief Converts binary IPv4 address to a string.
 *
 * Generic wrapper around inet_ntop() and inet_ntoa() depending on whichever
 * is available on your system. Does not support DNS.
 */
const char *
get_addr2name4(uint32_t ip, bool _U_ dnslookup)
{
    struct in_addr addr;
    static char *new_string = NULL;

    if (new_string == NULL)
        new_string = (char *)safe_malloc(255);

    new_string[0] = '\0';
    addr.s_addr = ip;

#ifdef HAVE_INET_NTOP
    if (inet_ntop(AF_INET, &addr, new_string, 255) == NULL) {
        warnx("Unable to convert 0x%x to a string", ip);
        new_string[0] = 0;
    }
    return new_string;
#elif defined HAVE_INET_NTOA
    return inet_ntoa(&addr);
#else
#error "Unable to support get_addr2name4."
#endif
}

/**
 * \brief Converts a IPv6 binary address to a string.a
 *
 * Does not support DNS.
 */
const char *
get_addr2name6(const struct tcpr_in6_addr *addr, _U_ bool dnslookup)
{
    static char *new_string = NULL;

    if (new_string == NULL)
        new_string = (char *)safe_malloc(255);

    new_string[0] = '\0';

#ifdef HAVE_INET_NTOP
    if (inet_ntop(AF_INET6, addr, new_string, 255) == NULL) {
        warn("Unable to convert addr to a string");
        new_string[0] = 0;
    }
    return new_string;
#else
#error "Unable to support get_addr2name6."
#endif
}

/**
 * \brief Converts the binary network address of a tcpr_cidr_t to a string
 */
const char *
get_cidr2name(const tcpr_cidr_t *cidr_ptr, bool dnslookup)
{
    if (cidr_ptr->family == AF_INET) {
        return get_addr2name4(cidr_ptr->u.network, dnslookup);
    } else if (cidr_ptr->family == AF_INET6) {
        return get_addr2name6(&cidr_ptr->u.network6, dnslookup);
    } else {
        return NULL;
    }
}
