/*
 * SPDX-License-Identifier: GPL-2.0-or-later
 *
 * bittwiste - pcap capture file editor
 * Copyright (C) 2006 - 2024 Addy Yeow <ayeowch@gmail.com>
 *
 * This program 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 2
 * of the License, or (at your option) any later version.
 *
 * This program 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 this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */

#include "bittwiste.h"
#include "template_pcap.h"
#include "tinymt64.h"

const char *program_name;

/* general options */
int header_opt = -1; /* specifies which header to edit, -1 -> no header selected */
int layer_opt = 0;   /* copy up to the specified layer only */
int start_oset_opt = 0, end_oset_opt = 0;  /* delete the specified byte offset */
int start_opt = 0, end_opt = 0;            /* copy the specified range of packets only */
time_t start_sec_opt = 0, end_sec_opt = 0; /* copy packets within the specified timeframe only */
int64_t gap_start_opt = -1,
        gap_end_opt = -1;              /* inter-packet gap range in microseconds (inclusive) */
struct pcap_timeval gap_last_ts = {0}; /* track last timestamp when applying custom gap */
int repeat_opt = 0;                    /* duplicate packets for the specified times */
int csum_opt = 1;                      /* set to 0 to disable checksum correction */
int linktype_opt = -1;                 /* pcap preamble link type field, -1 -> no override */

/* payload options */
struct payload_opt *payload_opt = NULL;

bool nsec = false; /* set to true if we have timestamps in nanosecond resolution */

uint16_t l2_len = ETH_HDR_LEN; /* Ethernet header length (including VLAN header if exist) */

/* TinyMT as random number generator (RNG) */
tinymt64_t tinymt;

/* header specific options */
struct ethopt *ethopt = NULL;            /* Ethernet options */
struct vlanopt vlanopts[VLAN_MAX_DEPTH]; /* VLAN options */
struct arpopt *arpopt = NULL;            /* ARP options */
struct ipopt *ipopt = NULL;              /* IP options */
struct ip6opt *ip6opt = NULL;            /* IPv6 options */
struct icmpopt *icmpopt = NULL;          /* ICMP options */
struct icmp6opt *icmp6opt = NULL;        /* ICMPv6 options */
struct tcpopt *tcpopt = NULL;            /* TCP options */
struct udpopt *udpopt = NULL;            /* UDP options */

/* stats */
static uint64_t pkts = 0;
static uint64_t bytes = 0;

int main(int argc, char **argv)
{
    char *cp;
    int c;
    char *str = NULL;
    char *infile = NULL;
    char *outfile = NULL;
    struct tm *tm = NULL;

    /* use current time as default seed for RNG, can be optionally set using -P flag */
    uint64_t seed = time(NULL);

    if ((cp = strrchr(argv[0], '/')) != NULL)
        program_name = cp + 1;
    else
        program_name = argv[0];

    /* process general options */
    while ((c = getopt(argc, argv, "I:O:L:X:CM:D:R:S:N:G:P:T:h")) != -1)
    {
        switch (c)
        {
        case 'I':
            infile = optarg; /* file path or template name */
            break;
        case 'O':
            outfile = optarg;
            break;
        case 'L':
            layer_opt = strtol(optarg, NULL, 0);
            /*
             * 2 - Ethernet, VLAN
             * 3 - ARP, IP
             * 4 - ICMP, TCP, UDP
             */
            if (layer_opt < 2 || layer_opt > 4)
                error("layer is out of range");
            break;
        case 'X': /* ignored if option -L and -T are not specified */
            payload_opt = (struct payload_opt *)malloc(sizeof(struct payload_opt));
            if (payload_opt == NULL)
                error("malloc(): cannot allocate memory for payload_opt");
            memset(payload_opt, 0, sizeof(struct payload_opt));
            set_payload_options(optarg, payload_opt);
            break;
        case 'C':
            csum_opt = 0; /* DISABLE checksum correction */
            break;
        case 'M':
            linktype_opt = strtol(optarg, NULL, 0);
            /*
             * 1 - Ethernet
             * 9 - PPP
             * 12 - Raw IP
             * 51 - PPPoE
             * 105 - IEEE 802.11 wireless
             * 117 - OpenBSD pflog
             * 118 - Cisco IOS
             * 119 - 802.11 with Prism hdr
             */
            if (linktype_opt < 0 || linktype_opt > UCHAR_MAX)
                error("linktype is out of range");
            break;
        case 'D':
            /*
             * -D 15-18, delete from byte 15th through byte 18th (inclusive),
             * starting from link-layer hdr
             */
            str = strdup(optarg);
            if (str == NULL)
                error("strdup(): cannot allocate memory for str");
            if ((cp = (char *)strtok(str, "-")) == NULL)
                error("invalid offset specification");
            start_oset_opt = strtol(cp, NULL, 0);
            if ((cp = (char *)strtok(NULL, "-")) == NULL)
                end_oset_opt = start_oset_opt; /* delete a single byte, e.g. -D 15 */
            else
                end_oset_opt = strtol(cp, NULL, 0);
            free(str);
            if (start_oset_opt <= 0 || end_oset_opt <= 0 || (start_oset_opt > end_oset_opt))
                error("invalid offset specification");
            break;
        case 'R': /* e.g. -R 5-21 or -R 9 */
            str = strdup(optarg);
            if (str == NULL)
                error("strdup(): cannot allocate memory for str");
            if ((cp = (char *)strtok(str, "-")) == NULL)
                error("invalid range specification");
            start_opt = strtol(cp, NULL, 0);
            if ((cp = (char *)strtok(NULL, "-")) == NULL)
                end_opt = start_opt; /* only one packet */
            else
                end_opt = strtol(cp, NULL, 0);
            free(str);
            if (start_opt <= 0 || end_opt <= 0 || (start_opt > end_opt))
                error("invalid range specification");
            break;
        case 'S':
            /*
             * time frame with one-second resolution: -S 22/10/2006,21:47:35-24/10/2006,13:16:05
             * format: -S DD/MM/YYYY,HH:MM:SS-DD/MM/YYYY,HH:MM:SS
             * note that -S 22/10/2006-24/10/2006 is equivalent to -S
             * 22/10/2006,00:00:00-24/10/2006,00:00:00
             */
            str = strdup(optarg);
            if (str == NULL)
                error("strdup(): cannot allocate memory for str");
            if ((cp = (char *)strtok(str, "-")) == NULL)
                error("invalid timeframe specification");
            tm = (struct tm *)malloc(sizeof(struct tm));
            if (tm == NULL)
                error("malloc(): cannot allocate memory for tm");
            if (!strptime(cp, "%d/%m/%Y,%T", tm))
                error("invalid timeframe specification");
            start_sec_opt = mktime(tm);
            if ((cp = (char *)strtok(NULL, "-")) == NULL)
                end_sec_opt = start_sec_opt; /* only the packets within the one-second resolution */
            else
            {
                if (!strptime(cp, "%d/%m/%Y,%T", tm))
                    error("invalid timeframe specification");
            }
            end_sec_opt = mktime(tm);
            free(tm);
            free(str);
            if (start_sec_opt > end_sec_opt)
                error("invalid timeframe specification");
            break;
        case 'N': /* e.g. -N 10, duplicate packet for 10 times */
            repeat_opt = strtol(optarg, NULL, 0);
            if (repeat_opt < 0)
                error("invalid repeat specification");
            break;
        case 'G': /* inter-packet gap in microseconds, e.g. -G 1000-10000 or -G 1000 */
            str = strdup(optarg);
            if (str == NULL)
                error("strdup(): cannot allocate memory for str");
            if ((cp = (char *)strtok(str, "-")) == NULL)
                error("invalid gap range specification");
            gap_start_opt = strtol(cp, NULL, 0);
            if ((cp = (char *)strtok(NULL, "-")) == NULL)
                gap_end_opt = gap_start_opt; /* fixed gap */
            else
                gap_end_opt = strtol(cp, NULL, 0); /* ranged random gap */
            free(str);
            if (gap_start_opt < 0 || gap_end_opt < 0 || gap_start_opt > INT32_MAX ||
                gap_end_opt > INT32_MAX || (gap_start_opt > gap_end_opt))
                error("invalid gap range specification");
            gap_last_ts.tv_sec = GAP_START;
            break;
        case 'P': /* optional positive integer to seed RNG */
            seed = strtol(optarg, NULL, 0);
            if (seed < 0 || seed > UINT32_MAX)
                error("invalid seed specification");
            break;
        case 'T':
            if (strcasecmp(optarg, "eth") == 0)
                header_opt = ETH;
            else if (strcasecmp(optarg, "vlan") == 0)
                header_opt = VLAN;
            else if (strcasecmp(optarg, "arp") == 0)
                header_opt = ARP;
            else if (strcasecmp(optarg, "ip") == 0)
                header_opt = IP;
            else if (strcasecmp(optarg, "ip6") == 0)
                header_opt = IP6;
            else if (strcasecmp(optarg, "icmp") == 0)
                header_opt = ICMP;
            else if (strcasecmp(optarg, "icmp6") == 0)
                header_opt = ICMP6;
            else if (strcasecmp(optarg, "tcp") == 0)
                header_opt = TCP;
            else if (strcasecmp(optarg, "udp") == 0)
                header_opt = UDP;
            else
                error("invalid header specification");
            /* process hdr specific options */
            parse_header_options(argc, argv);
            break;
        case 'h':
        default:
            usage();
        }
    }

    if (infile == NULL)
        error("input file not specified");

    if (outfile == NULL)
        error("output file not specified");

    if (strcmp(infile, outfile) == 0)
        error("invalid outfile specification");

    /* initialize RNG */
    tinymt64_init(&tinymt, seed);

    /* set signal handler for SIGINT (Control-C) */
    (void)signal(SIGINT, cleanup);

    parse_trace(infile, outfile);

    cleanup(0);

    /* NOTREACHED */
    exit(EXIT_SUCCESS);
}

void set_payload_options(char *optarg, struct payload_opt *opt)
{
    /*
     * optarg:
     * - 0302aad1 (fixed 4 bytes), flag = FIELD_SET
     * - 0-1000 (random bytes of length 0 to 1000 bytes), flag = FIELD_SET_RAND_RANGE
     * - 20-20 (random bytes of length 20 bytes), flag = FIELD_SET_RAND_RANGE
     * - 0-1000:00 (fixed 0x00 of length 0 to 1000 bytes), flag = FIELD_SET_RANGE
     * - 0-1000:ff (fixed 0xff of length 0 to 1000 bytes), flag = FIELD_SET_RANGE
     * - 0-1000:0302aad1 (fixed 0x0302aad1 of length 0 to 1000 bytes), flag = FIELD_SET_RANGE
     */
    char *str = strdup(optarg);
    if (str == NULL)
        error("strdup(): cannot allocate memory for str");

    /* parse <min_len>-<max_len>:<optional_bytes> */
    char *input_min_len = strtok(str, "-");
    char *input_max_len_and_bytes = strtok(NULL, "-");
    if (input_min_len && input_max_len_and_bytes)
    {
        opt->min_len = atoi(input_min_len);

        char *input_max_len = strtok(input_max_len_and_bytes, ":");
        if (input_max_len == NULL)
            error("invalid payload specification");
        opt->max_len = atoi(input_max_len);

        if (opt->min_len < 0 || opt->max_len > PKT_MAX_LEN || opt->min_len > opt->max_len)
            error("invalid payload specification");

        char *input_bytes = strtok(NULL, ":");
        if (input_bytes)
        {
            set_payload(input_bytes, opt);
            opt->flag = FIELD_SET_RANGE;
        }
        else
        {
            set_payload(NULL, opt);
            opt->flag = FIELD_SET_RAND_RANGE;
        }
    }
    else
    {
        set_payload(optarg, opt);
        opt->flag = FIELD_SET;
    }

    opt->update_payload = update_payload;

    free(str);
}

void set_payload(char *input_bytes, struct payload_opt *opt)
{
    int c, i, index;

    if (input_bytes)
    {
        c = strlen(input_bytes);
        if (c > (PKT_MAX_LEN * 2) || (c % 2) != 0)
            error("invalid payload specification");
        opt->len = c / 2; /* length of input bytes */
        if (opt->max_len == 0)
            opt->max_len = opt->len;
    }

    opt->payload = (uint8_t *)malloc(sizeof(uint8_t) * opt->max_len);
    if (opt->payload == NULL)
        error("malloc(): cannot allocate memory for payload");
    memset(opt->payload, 0, sizeof(uint8_t) * opt->max_len);

    if (input_bytes)
    {
        /* make a byte of data from every 2 characters of input_bytes, repeat as needed */
        for (i = 0; i < opt->max_len; i++)
        {
            index = (i % opt->len) * 2;
            uint8_t hex_byte[3] = {input_bytes[index], input_bytes[index + 1], '\0'};
            if (!isxdigit(hex_byte[0]) || !isxdigit(hex_byte[1]))
                error("invalid payload specification");
            sscanf((char *)hex_byte, "%hhx", &opt->payload[i]);
        }
    }
}

void update_payload(struct payload_opt *opt, uint16_t cur_len)
{
    if (opt->flag == FIELD_SET_RANGE || opt->flag == FIELD_SET_RAND_RANGE)
        opt->len = get_random_number_in_range(opt->min_len, opt->max_len);

    /* truncate payload if it is too large */
    if ((opt->len + cur_len) > PKT_MAX_LEN)
        opt->len -= (opt->len + cur_len) - PKT_MAX_LEN;

    /* populate payload with random bytes */
    if (opt->flag == FIELD_SET_RAND_RANGE)
    {
        for (int i = 0; i < opt->len; i++)
            opt->payload[i] = get_random_number(UINT8_MAX);
    }
}

void set_eth_addr_options(char *optarg, struct eth_addr_opt *opt)
{
    /*
     * optarg:
     * - 11:11:11:11:11:11 (overwrite MAC), flag = FIELD_SET
     * - 11:11:11:11:11:11,22:22:22:22:22:22 (overwrite matching MAC), flag = FIELD_REPLACE
     * - rand (overwrite MAC with random MAC), flag = FIELD_SET_RAND
     * - 11:11:11:11:11:11,rand (overwrite matching MAC with random MAC), flag = FIELD_REPLACE_RAND
     */
    char *str = strdup(optarg);
    if (str == NULL)
        error("strdup(): cannot allocate memory for str");

    char *cp = strtok(str, ",");
    if (cp == NULL)
        error("invalid MAC address");

    if (strcasecmp(cp, "rand") == 0)
        opt->flag = FIELD_SET_RAND; /* overwrite MAC with random MAC */
    else
    {
        if (eth_aton(cp, opt->old) != 1)
            error("invalid MAC address");

        cp = strtok(NULL, ",");
        if (cp == NULL)
            opt->flag = FIELD_SET; /* overwrite MAC */
        else if (strcasecmp(cp, "rand") == 0)
            opt->flag = FIELD_REPLACE_RAND; /* overwrite matching MAC with random MAC */
        else
        {
            opt->flag = FIELD_REPLACE; /* overwrite matching MAC */
            if (eth_aton(cp, opt->new) != 1)
                error("invalid MAC address");
        }
    }

    free(str);
}

void set_rand_in_addr_options(char *cp, struct in_addr *netnum, struct in_addr *netmask,
                              uint8_t *rand_bits)
{
    uint8_t netlen;

    /*
     * parse CIDR notation in the form of <network number>/<prefix length>, e.g. 1.0.0.0/8
     * 0.0.0.0/0 will result in random IPv4 selected from the entire range
     */
    char *input_netnum = strtok(cp, "/");
    char *input_netlen = strtok(NULL, "/");

    if (input_netnum == NULL || input_netlen == NULL)
        error("invalid CIDR notation");

    if (inet_pton(AF_INET, input_netnum, netnum) != 1)
        error("invalid CIDR notation");

    /* extract prefix length to calculate netmask */
    netlen = atoi(input_netlen);
    if (netlen < 0 || netlen > 32)
        error("invalid CIDR notation");

    /* number of bits available to the right that can be randomized */
    *rand_bits = 32 - netlen;

    /* calculate network mask and update the network number */
    if (netlen == 0)
        netmask->s_addr = 0; /* special handling for /0 */
    else
    {
        netmask->s_addr = htonl((uint32_t)(0xffffffffu << (32 - netlen)));
        if (netnum->s_addr != (netnum->s_addr & netmask->s_addr))
            netnum->s_addr &= netmask->s_addr;
    }
}

void set_in_addr_options(char *optarg, struct in_addr_opt *opt)
{
    /*
     * optarg:
     * - 1.1.1.1 (overwrite IP), flag = FIELD_SET
     * - 1.1.1.1,2.2.2.2 (overwrite matching IP), flag = FIELD_REPLACE
     * - 1.0.0.0/8 (overwrite IP with IP from CIDR), flag = FIELD_SET_RAND
     * - 1.1.1.1,2.0.0.0/8 (overwrite matching IP with IP from CIDR), flag = FIELD_REPLACE_RAND
     */
    char *str = strdup(optarg);
    if (str == NULL)
        error("strdup(): cannot allocate memory for str");

    char *cp = strtok(str, ",");
    if (cp == NULL)
        error("invalid IPv4 address");

    if (strstr(cp, "/") != NULL && strchr(cp, ',') == NULL)
    {
        opt->flag = FIELD_SET_RAND; /* overwrite IP with IP from CIDR */
        set_rand_in_addr_options(cp, &opt->new, &opt->netmask, &opt->rand_bits);
    }
    else
    {
        if (inet_pton(AF_INET, cp, &opt->old) != 1)
            error("invalid IPv4 address");

        cp = strtok(NULL, ",");
        if (cp == NULL)
            opt->flag = FIELD_SET; /* overwrite IP */
        else if (strstr(cp, "/") != NULL && strchr(cp, ',') == NULL)
        {
            opt->flag = FIELD_REPLACE_RAND; /* overwrite matching IP with IP from CIDR */
            set_rand_in_addr_options(cp, &opt->new, &opt->netmask, &opt->rand_bits);
        }
        else
        {
            opt->flag = FIELD_REPLACE; /* overwrite matching IP */
            if (inet_pton(AF_INET, cp, &opt->new) != 1)
                error("invalid IPv4 address");
        }
    }

    free(str);
}

void set_rand_in6_addr_options(char *cp, struct in6_addr *netnum, struct in6_addr *netmask,
                               uint8_t *rand_bits)
{
    uint8_t netlen, shift, i, s;

    /*
     * parse CIDR notation in the form of <network number>/<prefix length>, e.g. 2001:db8::/48
     * ::/0 will result in random IPv6 selected from the entire range
     */
    char *input_netnum = strtok(cp, "/");
    char *input_netlen = strtok(NULL, "/");

    if (input_netnum == NULL || input_netlen == NULL)
        error("invalid CIDR notation");

    if (inet_pton(AF_INET6, input_netnum, netnum) != 1)
        error("invalid CIDR notation");

    /* extract prefix length to calculate netmask */
    netlen = atoi(input_netlen);
    if (netlen < 0 || netlen > 128)
        error("invalid CIDR notation");

    /* number of bits available to the right that can be randomized */
    *rand_bits = 128 - netlen;

    /* calculate network mask and update the network number */
    shift = netlen;
    for (i = 0; i < 16; i++) /* 16 octets in IPv6 */
    {
        s = (shift > 8) ? 8 : shift;
        shift -= s;
        netmask->s6_addr[i] = (uint8_t)(0xffu << (8 - s));
        if (netnum->s6_addr[i] != (netnum->s6_addr[i] & netmask->s6_addr[i]))
            netnum->s6_addr[i] &= netmask->s6_addr[i];
    }
}

void set_in6_addr_options(char *optarg, struct in6_addr_opt *opt)
{
    /*
     * optarg:
     * - ::1 (overwrite IP), flag = FIELD_SET
     * - ::1,::2 (overwrite matching IP), flag = FIELD_REPLACE
     * - ::2/64 (overwrite IP with IP from CIDR), flag = FIELD_SET_RAND
     * - ::1,::2/64 (overwrite matching IP with IP from CIDR), flag = FIELD_REPLACE_RAND
     */
    char *str = strdup(optarg);
    if (str == NULL)
        error("strdup(): cannot allocate memory for str");

    char *cp = strtok(str, ",");
    if (cp == NULL)
        error("invalid IPv6 address");

    if (strstr(cp, "/") != NULL && strchr(cp, ',') == NULL)
    {
        opt->flag = FIELD_SET_RAND; /* overwrite IP with IP from CIDR */
        set_rand_in6_addr_options(cp, &opt->new, &opt->netmask, &opt->rand_bits);
    }
    else
    {
        if (inet_pton(AF_INET6, cp, &opt->old) != 1)
            error("invalid IPv6 address");

        cp = strtok(NULL, ",");
        if (cp == NULL)
            opt->flag = FIELD_SET; /* overwrite IP */
        else if (strstr(cp, "/") != NULL && strchr(cp, ',') == NULL)
        {
            opt->flag = FIELD_REPLACE_RAND; /* overwrite matching IP with IP from CIDR */
            set_rand_in6_addr_options(cp, &opt->new, &opt->netmask, &opt->rand_bits);
        }
        else
        {
            opt->flag = FIELD_REPLACE; /* overwrite matching IP */
            if (inet_pton(AF_INET6, cp, &opt->new) != 1)
                error("invalid IPv6 address");
        }
    }

    free(str);
}

uint64_t parse_number_alias(const char *cp)
{
    if (strcasecmp(cp, "vlan") == 0)
        return ETH_TYPE_VLAN_CTAG;
    else if (strcasecmp(cp, "ctag") == 0)
        return ETH_TYPE_VLAN_CTAG;
    else if (strcasecmp(cp, "stag") == 0)
        return ETH_TYPE_VLAN_STAG;
    else if (strcasecmp(cp, "ip") == 0)
        return ETH_TYPE_IP;
    else if (strcasecmp(cp, "ip6") == 0)
        return ETH_TYPE_IPV6;
    else if (strcasecmp(cp, "arp") == 0)
        return ETH_TYPE_ARP;

    return strtoul(cp, NULL, 0);
}

void set_number_options(char *optarg, void *val_a, void *val_b, uint8_t *flag, size_t max_val)
{
    /*
     * optarg:
     * - 1 (overwrite value), flag = FIELD_SET
     * - 1,2 (overwrite matching value), flag = FIELD_REPLACE
     * - rand (overwrite value with random value), flag = FIELD_SET_RAND
     * - 1,rand (overwrite matching value with random value), flag = FIELD_REPLACE_RAND
     */
    char *str = strdup(optarg);
    if (str == NULL)
        error("strdup(): cannot allocate memory for str");

    char *cp = strtok(str, ",");
    if (cp == NULL)
        error("invalid number specification");

    if (strcasecmp(cp, "rand") == 0)
        *flag = FIELD_SET_RAND; /* overwrite value with random value */
    else
    {
        /* input value can be known alias (e.g. "ip"), integer, hexadecimal, or octal */
        uint64_t v = parse_number_alias(cp);
        if (v < 0 || v > max_val)
            error("number is out of range: %lu", v);

        /*
         * accept UINT8_MAX for e.g. protocol number
         * accept UINT16_MAX for e.g. tcp port number
         * accept UINT32_MAX for e.g. tcp sequence number
         */
        if (max_val <= UINT8_MAX)
            *((uint8_t *)val_a) = (uint8_t)v;
        else if (max_val <= UINT16_MAX)
            *((uint16_t *)val_a) = (uint16_t)v;
        else
            *((uint32_t *)val_a) = (uint32_t)v;

        cp = strtok(NULL, ",");
        if (cp == NULL)
            *flag = FIELD_SET; /* overwrite value */
        else if (strcasecmp(cp, "rand") == 0)
            *flag = FIELD_REPLACE_RAND; /* overwrite matching value with random value */
        else
        {
            v = parse_number_alias(cp);
            if (v < 0 || v > max_val)
                error("number is out of range: %lu", v);

            if (max_val <= UINT8_MAX)
                *((uint8_t *)val_b) = (uint8_t)v;
            else if (max_val <= UINT16_MAX)
                *((uint16_t *)val_b) = (uint16_t)v;
            else
                *((uint32_t *)val_b) = (uint32_t)v;

            *flag = FIELD_REPLACE; /* overwrite matching value */
        }
    }

    free(str);
}

void parse_header_options(int argc, char **argv)
{
    char *cp;
    int c;
    char *str = NULL;
    uint32_t v; /* input value (can be integer, hexadecimal, or octal) returned by strtol */

    if (header_opt == ETH)
    {
        ethopt = (struct ethopt *)malloc(sizeof(struct ethopt));
        if (ethopt == NULL)
            error("malloc(): cannot allocate memory for ethopt");
        memset(ethopt, 0, sizeof(struct ethopt));
        while ((c = getopt(argc, argv, "d:s:t:")) != -1)
        {
            switch (c)
            {
            case 'd': /* destination MAC */
                set_eth_addr_options(optarg, &ethopt->dhost);
                break;
            case 's': /* source MAC */
                set_eth_addr_options(optarg, &ethopt->shost);
                break;
            case 't': /* 16-bit type: 0x0000 to 0xffff */
                set_number_options(optarg, &ethopt->old_eth_type, &ethopt->new_eth_type,
                                   &ethopt->eth_type_flag, UINT16_MAX);
                break;
            default:
                usage();
            }
        }
    }
    else if (header_opt == VLAN)
    {
        int i = 0, d = 0, p = 0, t = 0;
        memset(vlanopts, 0, sizeof(vlanopts));
        while ((c = getopt(argc, argv, "i:d:p:t:")) != -1)
        {
            switch (c)
            {
            case 'i': /* VLAN ID: 0 to 4095 */
                if (i >= VLAN_MAX_DEPTH)
                    error("vlan depth exceeded");
                set_number_options(optarg, &vlanopts[i].vlan_old_id, &vlanopts[i].vlan_new_id,
                                   &vlanopts[i].vlan_id_flag, VLAN_ID_MAX);
                i++;
                break;
            case 'd': /* DEI: 0 or 1 */
                if (d >= VLAN_MAX_DEPTH)
                    error("vlan depth exceeded");
                set_number_options(optarg, &vlanopts[d].vlan_old_dei, &vlanopts[d].vlan_new_dei,
                                   &vlanopts[d].vlan_dei_flag, 1);
                d++;
                break;
            case 'p': /* PCP: 0 to 7 */
                if (p >= VLAN_MAX_DEPTH)
                    error("vlan depth exceeded");
                set_number_options(optarg, &vlanopts[p].vlan_old_pcp, &vlanopts[p].vlan_new_pcp,
                                   &vlanopts[p].vlan_pcp_flag, VLAN_PCP_MAX);
                p++;
                break;
            case 't': /* 16-bit encapsulated protocol: 0x0000 to 0xffff */
                if (t >= VLAN_MAX_DEPTH)
                    error("vlan depth exceeded");
                set_number_options(optarg, &vlanopts[t].vlan_old_eth_type,
                                   &vlanopts[t].vlan_new_eth_type, &vlanopts[t].vlan_eth_type_flag,
                                   UINT16_MAX);
                t++;
                break;
            default:
                usage();
            }
        }
    }
    else if (header_opt == ARP)
    {
        arpopt = (struct arpopt *)malloc(sizeof(struct arpopt));
        if (arpopt == NULL)
            error("malloc(): cannot allocate memory for arpopt");
        memset(arpopt, 0, sizeof(struct arpopt));
        while ((c = getopt(argc, argv, "o:s:p:t:q:")) != -1)
        {
            switch (c)
            {
            case 'o': /* opcode */
                v = strtol(optarg, NULL, 0);
                if (v < 0 || v > USHRT_MAX)
                    error("ARP opcode is out of range");
                arpopt->ar_op = (uint16_t)v;
                arpopt->ar_op_flag = 1;
                break;
            case 's': /* sender MAC */
                set_eth_addr_options(optarg, &arpopt->sha);
                break;
            case 'p': /* sender IP */
                str = strdup(optarg);
                if (str == NULL)
                    error("strdup(): cannot allocate memory for str");
                if ((cp = (char *)strtok(str, ",")) == NULL)
                    error("invalid sender IP address");
                if (inet_pton(AF_INET, cp, &(arpopt->ar_old_spa)) != 1)
                    error("invalid sender IP address");
                if ((cp = (char *)strtok(NULL, ",")) == NULL) /* overwrite all sender IP address */
                    arpopt->ar_spa_flag = 1;
                else
                { /* overwrite matching IP address only */
                    arpopt->ar_spa_flag = 2;
                    if (inet_pton(AF_INET, cp, &(arpopt->ar_new_spa)) != 1)
                        error("invalid sender IP address");
                }
                free(str);
                break;
            case 't': /* target MAC */
                set_eth_addr_options(optarg, &arpopt->tha);
                break;
            case 'q': /* target IP */
                str = strdup(optarg);
                if (str == NULL)
                    error("strdup(): cannot allocate memory for str");
                if ((cp = (char *)strtok(str, ",")) == NULL)
                    error("invalid target IP address");
                if (inet_pton(AF_INET, cp, &(arpopt->ar_old_tpa)) != 1)
                    error("invalid target IP address");
                if ((cp = (char *)strtok(NULL, ",")) == NULL) /* overwrite all target IP address */
                    arpopt->ar_tpa_flag = 1;
                else
                { /* overwrite matching IP address only */
                    arpopt->ar_tpa_flag = 2;
                    if (inet_pton(AF_INET, cp, &(arpopt->ar_new_tpa)) != 1)
                        error("invalid target IP address");
                }
                free(str);
                break;
            default:
                usage();
            }
        }
    }
    else if (header_opt == IP)
    {
        ipopt = (struct ipopt *)malloc(sizeof(struct ipopt));
        if (ipopt == NULL)
            error("malloc(): cannot allocate memory for ipopt");
        memset(ipopt, 0, sizeof(struct ipopt));
        while ((c = getopt(argc, argv, "c:e:i:f:o:t:p:s:d:")) != -1)
        {
            switch (c)
            {
            case 'c': /* 6-bit DS field ('c' for codepoints; 'd' taken by destination IP) */
                v = strtol(optarg, NULL, 0);
                if (v < 0 || v > DS_FIELD_MAX)
                    error("DS field is out of range");
                ipopt->ip_ds_field = (uint8_t)v;
                ipopt->ip_ds_field_flag = 1;
                break;
            case 'e': /* 2-bit ECN field */
                v = strtol(optarg, NULL, 0);
                if (v < 0 || v > ECN_FIELD_MAX)
                    error("ECN field is out of range");
                ipopt->ip_ecn_field = (uint8_t)v;
                ipopt->ip_ecn_field_flag = 1;
                break;
            case 'i': /* identification */
                set_number_options(optarg, &ipopt->ip_old_id, &ipopt->ip_new_id, &ipopt->ip_id_flag,
                                   UINT16_MAX);
                break;
            case 'f': /* flags */
                for (c = 0; optarg[c]; c++)
                    optarg[c] = tolower(optarg[c]);
                if (strchr(optarg, 'r') != NULL) /* reserved bit */
                    ipopt->ip_flag_r = 1;
                if (strchr(optarg, 'd') != NULL) /* don't fragment bit */
                    ipopt->ip_flag_d = 1;
                if (strchr(optarg, 'm') != NULL) /* more fragment bit */
                    ipopt->ip_flag_m = 1;
                if (strchr(optarg, '-') != NULL)
                { /* remove flags */
                    ipopt->ip_flag_r = 0;
                    ipopt->ip_flag_d = 0;
                    ipopt->ip_flag_m = 0;
                }
                ipopt->ip_flags_flag = 1;
                break;
            case 'o': /* fragment offset */
                v = strtol(optarg, NULL, 0);
                if (v < 0 || v > IP_FO_MAX)
                    error("IP fragment offset is out of range");
                ipopt->ip_fo = (uint16_t)v;
                ipopt->ip_fo_flag = 1;
                break;
            case 't': /* time to live */
                set_number_options(optarg, &ipopt->ip_old_ttl, &ipopt->ip_new_ttl,
                                   &ipopt->ip_ttl_flag, UINT8_MAX);
                break;
            case 'p': /* protocol */
                set_number_options(optarg, &ipopt->ip_old_p, &ipopt->ip_new_p, &ipopt->ip_p_flag,
                                   UINT8_MAX);
                break;
            case 's': /* source IP */
                set_in_addr_options(optarg, &ipopt->ip_src);
                break;
            case 'd': /* destination IP */
                set_in_addr_options(optarg, &ipopt->ip_dst);
                break;
            default:
                usage();
            }
        }
    }
    else if (header_opt == IP6)
    {
        ip6opt = (struct ip6opt *)malloc(sizeof(struct ip6opt));
        if (ip6opt == NULL)
            error("malloc(): cannot allocate memory for ip6opt");
        memset(ip6opt, 0, sizeof(struct ip6opt));
        while ((c = getopt(argc, argv, "c:e:f:n:h:s:d:")) != -1)
        {
            switch (c)
            {
            case 'c': /* 6-bit DS field ('c' for codepoints; 'd' taken by destination IP) */
                v = strtol(optarg, NULL, 0);
                if (v < 0 || v > DS_FIELD_MAX)
                    error("DS field is out of range");
                ip6opt->ip6_ds_field = (uint8_t)v;
                ip6opt->ip6_ds_field_flag = 1;
                break;
            case 'e': /* 2-bit ECN field */
                v = strtol(optarg, NULL, 0);
                if (v < 0 || v > ECN_FIELD_MAX)
                    error("ECN field is out of range");
                ip6opt->ip6_ecn_field = (uint8_t)v;
                ip6opt->ip6_ecn_field_flag = 1;
                break;
            case 'f': /* 20-bit flow label: 0x00000 to 0xfffff (1048575) */
                v = strtol(optarg, NULL, 0);
                if (v < 0 || v > IP6_FLOW_LABEL_MAX)
                    error("IPv6 flow label is out of range");
                ip6opt->ip6_flow_label = (uint32_t)v;
                ip6opt->ip6_flow_label_flag = 1;
                break;
            case 'n': /* 8-bit next header */
                set_number_options(optarg, &ip6opt->ip6_old_next_header,
                                   &ip6opt->ip6_new_next_header, &ip6opt->ip6_next_header_flag,
                                   UINT8_MAX);
                break;
            case 'h': /* 8-bit hop limit */
                set_number_options(optarg, &ip6opt->ip6_old_hop_limit, &ip6opt->ip6_new_hop_limit,
                                   &ip6opt->ip6_hop_limit_flag, UINT8_MAX);
                break;
            case 's': /* source IP */
                set_in6_addr_options(optarg, &ip6opt->ip6_src);
                break;
            case 'd': /* destination IP */
                set_in6_addr_options(optarg, &ip6opt->ip6_dst);
                break;
            default:
                usage();
            }
        }
    }
    else if (header_opt == ICMP)
    {
        icmpopt = (struct icmpopt *)malloc(sizeof(struct icmpopt));
        if (icmpopt == NULL)
            error("malloc(): cannot allocate memory for icmpopt");
        memset(icmpopt, 0, sizeof(struct icmpopt));
        while ((c = getopt(argc, argv, "t:c:")) != -1)
        {
            switch (c)
            {
            case 't': /* type, e.g. 8 for echo request, 0 for echo reply */
                v = strtol(optarg, NULL, 0);
                if (v < 0 || v > UCHAR_MAX)
                    error("ICMP type is out of range");
                icmpopt->icmp_type = (uint8_t)v;
                icmpopt->icmp_type_flag = 1;
                break;
            case 'c': /* code */
                v = strtol(optarg, NULL, 0);
                if (v < 0 || v > UCHAR_MAX)
                    error("ICMP code is out of range");
                icmpopt->icmp_code = (uint8_t)v;
                icmpopt->icmp_code_flag = 1;
                break;
            default:
                usage();
            }
        }
    }
    else if (header_opt == ICMP6)
    {
        icmp6opt = (struct icmp6opt *)malloc(sizeof(struct icmp6opt));
        if (icmp6opt == NULL)
            error("malloc(): cannot allocate memory for icmp6opt");
        memset(icmp6opt, 0, sizeof(struct icmp6opt));
        while ((c = getopt(argc, argv, "t:c:")) != -1)
        {
            switch (c)
            {
            case 't': /* type, e.g. 128 for echo request, 129 for echo reply */
                v = strtol(optarg, NULL, 0);
                if (v < 0 || v > UCHAR_MAX)
                    error("ICMPv6 type is out of range");
                icmp6opt->icmp6_type = (uint8_t)v;
                icmp6opt->icmp6_type_flag = 1;
                break;
            case 'c': /* code */
                v = strtol(optarg, NULL, 0);
                if (v < 0 || v > UCHAR_MAX)
                    error("ICMPv6 code is out of range");
                icmp6opt->icmp6_code = (uint8_t)v;
                icmp6opt->icmp6_code_flag = 1;
                break;
            default:
                usage();
            }
        }
    }
    else if (header_opt == TCP)
    {
        tcpopt = (struct tcpopt *)malloc(sizeof(struct tcpopt));
        if (tcpopt == NULL)
            error("malloc(): cannot allocate memory for tcpopt");
        memset(tcpopt, 0, sizeof(struct tcpopt));
        while ((c = getopt(argc, argv, "s:d:q:a:f:w:u:")) != -1)
        {
            switch (c)
            {
            case 's': /* source port */
                set_number_options(optarg, &tcpopt->th_old_sport, &tcpopt->th_new_sport,
                                   &tcpopt->th_sport_flag, UINT16_MAX);
                break;
            case 'd': /* destination port */
                set_number_options(optarg, &tcpopt->th_old_dport, &tcpopt->th_new_dport,
                                   &tcpopt->th_dport_flag, UINT16_MAX);
                break;
            case 'q': /* sequence number */
                set_number_options(optarg, &tcpopt->th_old_seq, &tcpopt->th_new_seq,
                                   &tcpopt->th_seq_flag, UINT32_MAX);
                break;
            case 'a': /* acknowledgment number */
                set_number_options(optarg, &tcpopt->th_old_ack, &tcpopt->th_new_ack,
                                   &tcpopt->th_ack_flag, UINT32_MAX);
                break;
            case 'f': /* flags */
                for (c = 0; optarg[c]; c++)
                    optarg[c] = tolower(optarg[c]);
                if (strchr(optarg, 'c') != NULL) /* CWR */
                    tcpopt->th_flag_c = 1;
                if (strchr(optarg, 'e') != NULL) /* ECE */
                    tcpopt->th_flag_e = 1;
                if (strchr(optarg, 'u') != NULL) /* URG */
                    tcpopt->th_flag_u = 1;
                if (strchr(optarg, 'a') != NULL) /* ACK */
                    tcpopt->th_flag_a = 1;
                if (strchr(optarg, 'p') != NULL) /* PSH */
                    tcpopt->th_flag_p = 1;
                if (strchr(optarg, 'r') != NULL) /* RST */
                    tcpopt->th_flag_r = 1;
                if (strchr(optarg, 's') != NULL) /* SYN */
                    tcpopt->th_flag_s = 1;
                if (strchr(optarg, 'f') != NULL) /* FIN */
                    tcpopt->th_flag_f = 1;
                if (strchr(optarg, '-') != NULL)
                { /* remove flags */
                    tcpopt->th_flag_c = 0;
                    tcpopt->th_flag_e = 0;
                    tcpopt->th_flag_u = 0;
                    tcpopt->th_flag_a = 0;
                    tcpopt->th_flag_p = 0;
                    tcpopt->th_flag_r = 0;
                    tcpopt->th_flag_s = 0;
                    tcpopt->th_flag_f = 0;
                }
                tcpopt->th_flags_flag = 1;
                break;
            case 'w': /* window size */
                v = strtol(optarg, NULL, 0);
                if (v < 0 || v > USHRT_MAX)
                    error("TCP window size is out of range");
                tcpopt->th_win = (uint16_t)v;
                tcpopt->th_win_flag = 1;
                break;
            case 'u': /* urgent pointer */
                v = strtol(optarg, NULL, 0);
                if (v < 0 || v > USHRT_MAX)
                    error("TCP urgent pointer is out of range");
                tcpopt->th_urp = (uint16_t)v;
                tcpopt->th_urp_flag = 1;
                break;
            default:
                usage();
            }
        }
    }
    else if (header_opt == UDP)
    {
        udpopt = (struct udpopt *)malloc(sizeof(struct udpopt));
        if (udpopt == NULL)
            error("malloc(): cannot allocate memory for udpopt");
        memset(udpopt, 0, sizeof(struct udpopt));
        while ((c = getopt(argc, argv, "s:d:")) != -1)
        {
            switch (c)
            {
            case 's': /* source port */
                set_number_options(optarg, &udpopt->uh_old_sport, &udpopt->uh_new_sport,
                                   &udpopt->uh_sport_flag, UINT16_MAX);
                break;
            case 'd': /* destination port */
                set_number_options(optarg, &udpopt->uh_old_dport, &udpopt->uh_new_dport,
                                   &udpopt->uh_dport_flag, UINT16_MAX);
                break;
            default:
                usage();
            }
        }
    }
    /* NOTREACHED */
}

void parse_trace(char *infile, char *outfile)
{
    FILE *fp;         /* file pointer to input file */
    FILE *fp_outfile; /* file pointer to output file */
    struct pcap_file_header preamble;
    struct pcap_sf_pkthdr *header;
    uint8_t *pkt_data;    /* original packet data starting from link-layer hdr */
    int repeat_index = 0; /* to track number of times we have read input file */
    int pkt_index; /* to check if we are within start_opt and end_opt for range specification */

    load_input_file(infile, &fp);

    notice("output file: %s", outfile);
    if ((fp_outfile = fopen(outfile, "wb")) == NULL)
        error("fopen(): error creating %s", outfile);

    /* preamble occupies the first 24 bytes of a trace file */
    if (fread(&preamble, sizeof(preamble), 1, fp) == 0)
        error("fread(): error reading %s", infile);
    if (preamble.magic != PCAP_MAGIC && preamble.magic != NSEC_PCAP_MAGIC)
        error("%s is not a valid pcap based trace file", infile);

    /* we have timestamps in nanosecond resolution */
    if (preamble.magic == NSEC_PCAP_MAGIC)
        nsec = true;

    /* override pcap preamble link type with user specified link type */
    if (linktype_opt >= 0)
        preamble.linktype = linktype_opt;

    /* write preamble to output file */
    if (fwrite(&preamble, sizeof(preamble), 1, fp_outfile) != 1)
        error("fwrite(): error writing %s", outfile);

    /* pcap hdr */
    header = (struct pcap_sf_pkthdr *)calloc(1, PCAP_HDR_LEN);
    if (header == NULL)
        error("calloc(): cannot allocate memory for header");

    /* check -N to duplicate packets */
    while (repeat_index <= repeat_opt)
    {
        /*
         * loop through the remaining data by reading the pcap hdr first.
         * pcap hdr (16 bytes) = secs. + usecs./nsecs. + caplen + len
         */
        pkt_index = 1;
        while (fread(header, PCAP_HDR_LEN, 1, fp))
        {
            /* original packet data starting from link-layer hdr */
            pkt_data = (uint8_t *)malloc(sizeof(uint8_t) * header->caplen);
            if (pkt_data == NULL)
                error("malloc(): cannot allocate memory for pkt_data");

            /* copy captured packet data starting from link-layer hdr into pkt_data */
            if (fread(pkt_data, header->caplen, 1, fp) == 0)
                error("fread(): error reading %s", infile);

            /* check -R to select range of packets */
            if ((pkt_index >= start_opt && pkt_index <= end_opt) ||
                (start_opt == 0 && end_opt == 0))
            {
                /* check -S to select packets within a timeframe */
                if ((header->ts.tv_sec >= start_sec_opt && header->ts.tv_sec <= end_sec_opt) ||
                    (start_sec_opt == 0 && end_sec_opt == 0))
                {
                    /* check -D to truncate packet */
                    if (start_oset_opt != 0 && end_oset_opt != 0 &&
                        start_oset_opt <= header->caplen)
                        truncate_packet(pkt_data, header, outfile, &fp_outfile);
                    else
                        modify_packet(pkt_data, header, outfile, &fp_outfile);
                    ++pkts; /* packets written */
                }
            }

            free(pkt_data);
            ++pkt_index;
        }
        /* reset to start of input file and skip preamble */
        if (fseek(fp, sizeof(preamble), SEEK_SET) != 0)
            error("fseek(): error reading %s", infile);
        ++repeat_index;
    }

    /* get bytes written */
    if (fseek(fp_outfile, 0, SEEK_END) != 0)
        error("fseek(): error writing %s", outfile);
    bytes = ftell(fp_outfile);
    if (bytes == -1)
        error("ftell(): error writing %s", outfile);

    free(header);
    (void)fclose(fp);
    (void)fclose(fp_outfile);
}

void truncate_packet(const uint8_t *pkt_data, struct pcap_sf_pkthdr *header, char *outfile,
                     FILE **fp_outfile)
{
    int i;
    int len;   /* original header->caplen */
    int end_o; /* aligned end_oset_opt */

    /* align end_oset_opt so that it does not go beyond header->caplen */
    if (end_oset_opt > header->caplen)
        end_o = header->caplen;
    else
        end_o = end_oset_opt;

    len = header->caplen; /* original capture length (before byte deletion) */
    header->caplen = header->len = len - ((end_o - start_oset_opt) + 1);

    /* write pcap header */
    if (fwrite(header, PCAP_HDR_LEN, 1, *fp_outfile) != 1)
        error("fwrite(): error writing %s", outfile);

    for (i = 0; i < start_oset_opt - 1; i++)
    {
        if (fputc(pkt_data[i], *fp_outfile) == EOF)
            error("fputc(): error writing %s", outfile);
    }

    for (i = end_o; i < len; i++)
    {
        if (fputc(pkt_data[i], *fp_outfile) == EOF)
            error("fputc(): error writing %s", outfile);
    }
}

void modify_packet(const uint8_t *pkt_data, struct pcap_sf_pkthdr *header, char *outfile,
                   FILE **fp_outfile)
{
    uint8_t *new_pkt_data; /* modified pkt_data inclusive of pcap hdr is written here */
    int len;               /* new_pkt_data length */
    int ret;
    int rem_bytes;

    if (payload_opt)
        len = PKT_MAX_LEN;
    else
        len = header->caplen;

    /* modified pkt_data inclusive of pcap hdr */
    new_pkt_data = (uint8_t *)malloc(sizeof(uint8_t) * (PCAP_HDR_LEN + len));
    if (new_pkt_data == NULL)
        error("malloc(): cannot allocate memory for new_pkt_data");
    memset(new_pkt_data, 0, PCAP_HDR_LEN + len);

    /*
     * encapsulated editing function starting from link-layer hdr.
     * parse_eth() returns bytes written in new_pkt_data starting from link-layer hdr
     */
    ret = parse_eth(pkt_data, new_pkt_data, header) + PCAP_HDR_LEN;

    /* we are editing pcap hdr to apply custom inter-packet gap */
    if (gap_start_opt >= 0)
        update_pcap_hdr(header);

    /* copy pcap hdr into new_pkt_data */
    memcpy(new_pkt_data, header, PCAP_HDR_LEN);

    /* no changes */
    if (ret == PCAP_HDR_LEN)
    { /* parse_eth() returns 0 */
        /* write pcap hdr */
        if (fwrite(header, PCAP_HDR_LEN, 1, *fp_outfile) != 1)
            error("fwrite(): error writing %s", outfile);

        if (fwrite(pkt_data, header->caplen, 1, *fp_outfile) != 1)
            error("fwrite(): error writing %s", outfile);
    }
    /* overwrite the entire pkt_data with new_pkt_data */
    else if (ret == header->caplen + PCAP_HDR_LEN)
    {
        if (fwrite(new_pkt_data, ret, 1, *fp_outfile) != 1)
            error("fwrite(): error writing %s", outfile);
    }
    else
    {
        if (fwrite(new_pkt_data, ret, 1, *fp_outfile) != 1)
            error("fwrite(): error writing %s", outfile);

        if (payload_opt)
        {
            /* write remaining bytes from new_pkt_data */
            rem_bytes = header->caplen - (ret - PCAP_HDR_LEN);
            if (rem_bytes > 0)
            {
                if (fwrite(new_pkt_data + ret, rem_bytes, 1, *fp_outfile) != 1)
                    error("fwrite(): error writing %s", outfile);
            }
        }
        else
        {
            /* write remaining bytes from pkt_data */
            rem_bytes = header->caplen - (ret - PCAP_HDR_LEN);
            if (rem_bytes > 0)
            {
                if (fwrite(pkt_data + (ret - PCAP_HDR_LEN), rem_bytes, 1, *fp_outfile) != 1)
                    error("fwrite(): error writing %s", outfile);
            }
        }
    }

    free(new_pkt_data);
}

void load_input_file(char *infile, FILE **fp)
{
    /* attempt to load from built-in template first, i.e. without actual input file */
    if (strcasecmp(infile, "eth") == 0)
    {
        notice("input file: %s (Ethernet header template)", infile);
        *fp = fmemopen((void *)TEMPLATE_PCAP_ETH, TEMPLATE_PCAP_ETH_LEN, "r");
    }
    else if (strcasecmp(infile, "vlan") == 0)
    {
        notice("input file: %s (VLAN header template)", infile);
        *fp = fmemopen((void *)TEMPLATE_PCAP_VLAN, TEMPLATE_PCAP_VLAN_LEN, "r");
    }
    else if (strcasecmp(infile, "arp") == 0)
    {
        notice("input file: %s (ARP header template)", infile);
        *fp = fmemopen((void *)TEMPLATE_PCAP_ARP, TEMPLATE_PCAP_ARP_LEN, "r");
    }
    else if (strcasecmp(infile, "ip") == 0)
    {
        notice("input file: %s (IPv4 header template)", infile);
        *fp = fmemopen((void *)TEMPLATE_PCAP_IP, TEMPLATE_PCAP_IP_LEN, "r");
    }
    else if (strcasecmp(infile, "ip6") == 0)
    {
        notice("input file: %s (IPv6 header template)", infile);
        *fp = fmemopen((void *)TEMPLATE_PCAP_IP6, TEMPLATE_PCAP_IP6_LEN, "r");
    }
    else if (strcasecmp(infile, "icmp") == 0)
    {
        notice("input file: %s (ICMPv4 header template)", infile);
        *fp = fmemopen((void *)TEMPLATE_PCAP_ICMP, TEMPLATE_PCAP_ICMP_LEN, "r");
    }
    else if (strcasecmp(infile, "icmp6") == 0)
    {
        notice("input file: %s (ICMPv6 header template)", infile);
        *fp = fmemopen((void *)TEMPLATE_PCAP_ICMP6, TEMPLATE_PCAP_ICMP6_LEN, "r");
    }
    else if (strcasecmp(infile, "tcp") == 0)
    {
        notice("input file: %s (IPv4 TCP header template)", infile);
        *fp = fmemopen((void *)TEMPLATE_PCAP_TCP, TEMPLATE_PCAP_TCP_LEN, "r");
    }
    else if (strcasecmp(infile, "ip6tcp") == 0)
    {
        notice("input file: %s (IPv6 TCP header template)", infile);
        *fp = fmemopen((void *)TEMPLATE_PCAP_IP6_TCP, TEMPLATE_PCAP_IP6_TCP_LEN, "r");
    }
    else if (strcasecmp(infile, "udp") == 0)
    {
        notice("input file: %s (IPv4 UDP header template)", infile);
        *fp = fmemopen((void *)TEMPLATE_PCAP_UDP, TEMPLATE_PCAP_UDP_LEN, "r");
    }
    else if (strcasecmp(infile, "ip6udp") == 0)
    {
        notice("input file: %s (IPv6 UDP header template)", infile);
        *fp = fmemopen((void *)TEMPLATE_PCAP_IP6_UDP, TEMPLATE_PCAP_IP6_UDP_LEN, "r");
    }
    else
    {
        /* load actual input file */
        notice("input file: %s", infile);
        if ((*fp = fopen(infile, "rb")) == NULL)
        {
            error("fopen(): error reading %s", infile);
            return;
        }
    }

    if (*fp == NULL)
    {
        error("fmemopen(): error reading %s", infile);
        return;
    }
}

void update_pcap_hdr(struct pcap_sf_pkthdr *header)
{
    uint64_t us;

    if (gap_start_opt == gap_end_opt)
        us = gap_start_opt;
    else
        us = gap_start_opt + get_random_number(gap_end_opt - gap_start_opt);

    if (nsec)
        pcap_timeval_nsadd(&gap_last_ts, us * 1000);
    else
        pcap_timeval_usadd(&gap_last_ts, us);

    header->ts = gap_last_ts;
}

static inline void copy_to_hdr(const uint8_t *pkt, size_t offset, void *hdr, size_t len)
{
    /* go past offset in pkt and copy len of pkt into hdr */
    memcpy(hdr, pkt + offset, len);
}

static inline void copy_to_pkt(uint8_t *pkt, size_t offset, const void *cp, size_t len)
{
    /* go past offset in pkt and copy len of cp into pkt */
    memcpy(pkt + offset, cp, len);
}

uint16_t parse_eth(const uint8_t *pkt_data, uint8_t *new_pkt_data, struct pcap_sf_pkthdr *header)
{
    /*
     * Ethernet header (14 bytes)
     * 1. destination MAC (6 bytes)
     * 2. source MAC (6 bytes)
     * 3. type (2 bytes)
     */
    struct ethhdr *eth_hdr;
    struct vlanhdr *vlan_hdr; /* VLAN header (4 bytes) */
    uint16_t eth_type;
    int i;

    /* do nothing if Ethernet hdr is truncated */
    if (header->caplen < ETH_HDR_LEN)
        return (0);

    eth_hdr = (struct ethhdr *)malloc(ETH_HDR_LEN);
    if (eth_hdr == NULL)
        error("malloc(): cannot allocate memory for eth_hdr");

    copy_to_hdr(pkt_data, 0, eth_hdr, ETH_HDR_LEN);

    eth_type = ntohs(eth_hdr->eth_type);

    /* we are editing Ethernet hdr */
    if (header_opt == ETH)
        update_eth_hdr(eth_hdr);

    copy_to_pkt(new_pkt_data, PCAP_HDR_LEN, eth_hdr, ETH_HDR_LEN);

    free(eth_hdr);

    l2_len = ETH_HDR_LEN;

    i = 0;
    while ((eth_type == ETH_TYPE_VLAN_CTAG || eth_type == ETH_TYPE_VLAN_STAG) && i < VLAN_MAX_DEPTH)
    {
        /* do nothing if VLAN header is truncated */
        if (header->caplen < l2_len + VLAN_HDR_LEN)
            return (0);

        vlan_hdr = (struct vlanhdr *)malloc(VLAN_HDR_LEN);
        if (vlan_hdr == NULL)
            error("malloc(): cannot allocate memory for vlan_hdr");

        copy_to_hdr(pkt_data, l2_len, vlan_hdr, VLAN_HDR_LEN);

        /* preceding eth_type is TPID, get encapsulated protocol */
        eth_type = ntohs(vlan_hdr->eth_type);

        /* we are editing VLAN hdr */
        if (header_opt == VLAN)
            update_vlan_hdr(vlan_hdr, &vlanopts[i]);

        copy_to_pkt(new_pkt_data, PCAP_HDR_LEN + l2_len, vlan_hdr, VLAN_HDR_LEN);

        free(vlan_hdr);

        l2_len += VLAN_HDR_LEN;

        i++;
    }

    /* copy up to layer 2 only, discard remaining data */
    if (layer_opt == 2)
    {
        /* we are editing Ethernet hdr and we have payload */
        if ((header_opt == ETH || header_opt == VLAN) && payload_opt)
        {
            payload_opt->update_payload(payload_opt, l2_len);

            copy_to_pkt(new_pkt_data, PCAP_HDR_LEN + l2_len, payload_opt->payload,
                        payload_opt->len);

            header->caplen = header->len = l2_len + payload_opt->len;
        }
        else
            header->caplen = header->len = l2_len;

        return (header->caplen);
    }

    /* parse ARP datagram */
    if (eth_type == ETH_TYPE_ARP)
        return (parse_arp(pkt_data, new_pkt_data, header));
    /* parse IP datagram */
    else if (eth_type == ETH_TYPE_IP)
        return (parse_ip(pkt_data, new_pkt_data, header, NULL, 0));
    /* parse IPv6 datagram */
    else if (eth_type == ETH_TYPE_IPV6)
        return (parse_ip6(pkt_data, new_pkt_data, header));
    /* no further editing support for other datagram */
    else
        return (l2_len);
}

void update_eth_hdr(struct ethhdr *eth_hdr)
{
    /* overwrite destination MAC */
    if (ethopt->dhost.flag == FIELD_SET)
        memcpy(eth_hdr->eth_dhost, ethopt->dhost.old, ETH_ADDR_LEN);
    else if (ethopt->dhost.flag == FIELD_REPLACE &&
             memcmp(eth_hdr->eth_dhost, ethopt->dhost.old, ETH_ADDR_LEN) == 0)
        memcpy(eth_hdr->eth_dhost, ethopt->dhost.new, ETH_ADDR_LEN);
    else if (ethopt->dhost.flag == FIELD_SET_RAND ||
             (ethopt->dhost.flag == FIELD_REPLACE_RAND &&
              memcmp(eth_hdr->eth_dhost, ethopt->dhost.old, ETH_ADDR_LEN) == 0))
        set_random_eth_addr(eth_hdr->eth_dhost);

    /* overwrite source MAC */
    if (ethopt->shost.flag == FIELD_SET)
        memcpy(eth_hdr->eth_shost, ethopt->shost.old, ETH_ADDR_LEN);
    else if (ethopt->shost.flag == FIELD_REPLACE &&
             memcmp(eth_hdr->eth_shost, ethopt->shost.old, ETH_ADDR_LEN) == 0)
        memcpy(eth_hdr->eth_shost, ethopt->shost.new, ETH_ADDR_LEN);
    else if (ethopt->shost.flag == FIELD_SET_RAND ||
             (ethopt->shost.flag == FIELD_REPLACE_RAND &&
              memcmp(eth_hdr->eth_shost, ethopt->shost.old, ETH_ADDR_LEN) == 0))
        set_random_eth_addr(eth_hdr->eth_shost);

    /* overwrite Ethernet type */
    if (ethopt->eth_type_flag == FIELD_SET)
        eth_hdr->eth_type = htons(ethopt->old_eth_type);
    else if (ethopt->eth_type_flag == FIELD_REPLACE &&
             eth_hdr->eth_type == htons(ethopt->old_eth_type))
        eth_hdr->eth_type = htons(ethopt->new_eth_type);
    else if (ethopt->eth_type_flag == FIELD_SET_RAND ||
             (ethopt->eth_type_flag == FIELD_REPLACE_RAND &&
              eth_hdr->eth_type == htons(ethopt->old_eth_type)))
        eth_hdr->eth_type = htons(get_random_number(UINT16_MAX));
}

void update_vlan_hdr(struct vlanhdr *vlan_hdr, struct vlanopt *vlanopt)
{
    uint16_t tci = vlan_hdr->vlan_tci;

    /* overwrite VLAN ID */
    if (vlanopt->vlan_id_flag == FIELD_SET)
        tci = SET_VLAN_ID(tci, vlanopt->vlan_old_id);
    else if (vlanopt->vlan_id_flag == FIELD_REPLACE && GET_VLAN_ID(tci) == vlanopt->vlan_old_id)
        tci = SET_VLAN_ID(tci, vlanopt->vlan_new_id);
    else if (vlanopt->vlan_id_flag == FIELD_SET_RAND ||
             (vlanopt->vlan_id_flag == FIELD_REPLACE_RAND &&
              GET_VLAN_ID(tci) == vlanopt->vlan_old_id))
        tci = SET_VLAN_ID(tci, get_random_number(VLAN_ID_MAX));

    /* overwrite VLAN DEI */
    if (vlanopt->vlan_dei_flag == FIELD_SET)
        tci = SET_VLAN_DEI(tci, vlanopt->vlan_old_dei);
    else if (vlanopt->vlan_dei_flag == FIELD_REPLACE && GET_VLAN_DEI(tci) == vlanopt->vlan_old_dei)
        tci = SET_VLAN_DEI(tci, vlanopt->vlan_new_dei);
    else if (vlanopt->vlan_dei_flag == FIELD_SET_RAND ||
             (vlanopt->vlan_dei_flag == FIELD_REPLACE_RAND &&
              GET_VLAN_DEI(tci) == vlanopt->vlan_old_dei))
        tci = SET_VLAN_DEI(tci, get_random_number(1)); /* 0 or 1 */

    /* overwrite VLAN PCP */
    if (vlanopt->vlan_pcp_flag == FIELD_SET)
        tci = SET_VLAN_PCP(tci, vlanopt->vlan_old_pcp);
    else if (vlanopt->vlan_pcp_flag == FIELD_REPLACE && GET_VLAN_PCP(tci) == vlanopt->vlan_old_pcp)
        tci = SET_VLAN_PCP(tci, vlanopt->vlan_new_pcp);
    else if (vlanopt->vlan_pcp_flag == FIELD_SET_RAND ||
             (vlanopt->vlan_pcp_flag == FIELD_REPLACE_RAND &&
              GET_VLAN_PCP(tci) == vlanopt->vlan_old_pcp))
        tci = SET_VLAN_PCP(tci, get_random_number(VLAN_PCP_MAX));

    vlan_hdr->vlan_tci = tci;

    /* overwrite encapsulated protocol */
    if (vlanopt->vlan_eth_type_flag == FIELD_SET)
        vlan_hdr->eth_type = htons(vlanopt->vlan_old_eth_type);
    else if (vlanopt->vlan_eth_type_flag == FIELD_REPLACE &&
             vlan_hdr->eth_type == htons(vlanopt->vlan_old_eth_type))
        vlan_hdr->eth_type = htons(vlanopt->vlan_new_eth_type);
    else if (vlanopt->vlan_eth_type_flag == FIELD_SET_RAND ||
             (vlanopt->vlan_eth_type_flag == FIELD_REPLACE_RAND &&
              vlan_hdr->eth_type == htons(vlanopt->vlan_old_eth_type)))
        vlan_hdr->eth_type = htons(get_random_number(UINT16_MAX));
}

uint16_t parse_arp(const uint8_t *pkt_data, uint8_t *new_pkt_data, struct pcap_sf_pkthdr *header)
{
    /*
     * Ethernet ARP header (28 bytes)
     *  1. hardware type (2 bytes)
     *  2. protocol type (2 bytes)
     *  3. hardware address length (1 byte)
     *  4. protocol address length (1 byte)
     *  5. opcode (2 bytes)
     *  6. sender hardware address (6 bytes)
     *  7. sender protocol address (4 bytes)
     *  8. target hardware address (6 bytes)
     *  9. target protocol address (4 bytes)
     */
    struct arphdr *arp_hdr;

    /* do nothing if ARP hdr is truncated */
    if (header->caplen < l2_len + ARP_HDR_LEN)
        return (l2_len);

    arp_hdr = (struct arphdr *)malloc(ARP_HDR_LEN);
    if (arp_hdr == NULL)
        error("malloc(): cannot allocate memory for arp_hdr");

    copy_to_hdr(pkt_data, l2_len, arp_hdr, ARP_HDR_LEN);

    /* do nothing if this is an unsupported ARP hdr */
    if (arp_hdr->ar_hln != ETH_ADDR_LEN || arp_hdr->ar_pln != IP_ADDR_LEN)
    {
        free(arp_hdr);
        return (l2_len);
    }

    /* we are editing ARP hdr */
    if (header_opt == ARP)
        update_arp_hdr(arp_hdr);

    copy_to_pkt(new_pkt_data, PCAP_HDR_LEN + l2_len, arp_hdr, ARP_HDR_LEN);

    free(arp_hdr);

    /* copy up to layer 3 only, discard remaining data */
    if (layer_opt == 3)
    {
        /* we are editing ARP hdr and we have payload */
        if (header_opt == ARP && payload_opt)
        {
            payload_opt->update_payload(payload_opt, l2_len + ARP_HDR_LEN);

            copy_to_pkt(new_pkt_data, PCAP_HDR_LEN + l2_len + ARP_HDR_LEN, payload_opt->payload,
                        payload_opt->len);

            header->caplen = header->len = l2_len + ARP_HDR_LEN + payload_opt->len;
        }
        else
            header->caplen = header->len = l2_len + ARP_HDR_LEN;

        return (header->caplen);
    }

    /* no further editing support after ARP hdr */
    return (l2_len + ARP_HDR_LEN);
}

void update_arp_hdr(struct arphdr *arp_hdr)
{
    /* overwrite opcode */
    if (arpopt->ar_op_flag)
        arp_hdr->ar_op = htons(arpopt->ar_op);

    /* overwrite sender MAC */
    if (arpopt->sha.flag == FIELD_SET)
        memcpy(arp_hdr->ar_sha, arpopt->sha.old, ETH_ADDR_LEN);
    else if (arpopt->sha.flag == FIELD_REPLACE &&
             memcmp(arp_hdr->ar_sha, arpopt->sha.old, ETH_ADDR_LEN) == 0)
        memcpy(arp_hdr->ar_sha, arpopt->sha.new, ETH_ADDR_LEN);
    else if (arpopt->sha.flag == FIELD_SET_RAND ||
             (arpopt->sha.flag == FIELD_REPLACE_RAND &&
              memcmp(arp_hdr->ar_sha, arpopt->sha.old, ETH_ADDR_LEN) == 0))
        set_random_eth_addr(arp_hdr->ar_sha);

    /* overwrite sender IP */
    if (arpopt->ar_spa_flag == 1) /* overwrite all sender IP */
        memcpy(arp_hdr->ar_spa, arpopt->ar_old_spa, IP_ADDR_LEN);
    else if (arpopt->ar_spa_flag == 2 && /* overwrite matching IP only */
             memcmp(arp_hdr->ar_spa, arpopt->ar_old_spa, IP_ADDR_LEN) == 0)
        memcpy(arp_hdr->ar_spa, arpopt->ar_new_spa, IP_ADDR_LEN);

    /* overwrite target MAC */
    if (arpopt->tha.flag == FIELD_SET)
        memcpy(arp_hdr->ar_tha, arpopt->tha.old, ETH_ADDR_LEN);
    else if (arpopt->tha.flag == FIELD_REPLACE &&
             memcmp(arp_hdr->ar_tha, arpopt->tha.old, ETH_ADDR_LEN) == 0)
        memcpy(arp_hdr->ar_tha, arpopt->tha.new, ETH_ADDR_LEN);
    else if (arpopt->tha.flag == FIELD_SET_RAND ||
             (arpopt->tha.flag == FIELD_REPLACE_RAND &&
              memcmp(arp_hdr->ar_tha, arpopt->tha.old, ETH_ADDR_LEN) == 0))
        set_random_eth_addr(arp_hdr->ar_tha);

    /* overwrite target IP */
    if (arpopt->ar_tpa_flag == 1) /* overwrite all target IP */
        memcpy(arp_hdr->ar_tpa, arpopt->ar_old_tpa, IP_ADDR_LEN);
    else if (arpopt->ar_tpa_flag == 2 && /* overwrite matching IP only */
             memcmp(arp_hdr->ar_tpa, arpopt->ar_old_tpa, IP_ADDR_LEN) == 0)
        memcpy(arp_hdr->ar_tpa, arpopt->ar_new_tpa, IP_ADDR_LEN);
}

uint16_t parse_ip(const uint8_t *pkt_data, uint8_t *new_pkt_data, struct pcap_sf_pkthdr *header,
                  struct ip *ip_hdr, int flag)
{
    /*
     * IP header (20 bytes + optional X bytes for options)
     *  1. version (4 bits)
     *  2. header length (4 bits)
     *  3. service type (1 byte)
     *  4. total length (2 bytes)
     *  5. id (2 bytes)
     *  6. flag (3 bits)
     *  7. fragment offset (13 bits)
     *  8. ttl (1 byte)
     *  9. protocol (1 byte)
     * 10. header checksum (2 bytes)
     * 11. source IP (4 bytes)
     * 12. destination IP (4 bytes)
     * 13. options (X bytes)
     */
    uint16_t ip_hlb;  /* hdr length in bytes */
    uint8_t r = '\0'; /* flags */
    uint8_t d = '\0';
    uint8_t m = '\0';
    uint8_t ip_p = '\0';  /* protocol */
    uint8_t *ip_o = NULL; /* options (X bytes) */
    int i, j;

    /*
     * flag is 0; entry from Ethernet hdr to edit IP hdr.
     * flag is 1; entry from ICMP, TCP or UDP hdr to update IP total length and recalculate
     *            checksum for IP hdr.
     */
    if (flag == 0 && ip_hdr == NULL)
    {
        /* do nothing if IP hdr is truncated */
        if (header->caplen < l2_len + IP_HDR_LEN)
            return (l2_len);

        ip_hdr = (struct ip *)malloc(IP_HDR_LEN);
        if (ip_hdr == NULL)
            error("malloc(): cannot allocate memory for ip_hdr");

        copy_to_hdr(pkt_data, l2_len, ip_hdr, IP_HDR_LEN);
    }

    ip_hlb = ip_hdr->ip_hl * 4; /* convert to bytes */

    /* have IP options */
    if (ip_hlb > IP_HDR_LEN)
    {
        /* do nothing if IP hdr with options is truncated */
        if (header->caplen < l2_len + ip_hlb)
        {
            free(ip_hdr);
            return (l2_len);
        }

        ip_o = (uint8_t *)malloc(sizeof(uint8_t) * (ip_hlb - IP_HDR_LEN));
        if (ip_o == NULL)
            error("malloc(): cannot allocate memory for ip_o");

        /* copy IP options into ip_o */
        for (i = 0, j = l2_len + IP_HDR_LEN; i < (ip_hlb - IP_HDR_LEN); i++, j++)
            ip_o[i] = pkt_data[j];
    }

    if (flag == 0)
    {
        /* we are editing IP hdr */
        if (header_opt == IP)
        {
            /* original flags */
            r = (ntohs(ip_hdr->ip_off) & IP_RF) > 0 ? 1 : 0;
            d = (ntohs(ip_hdr->ip_off) & IP_DF) > 0 ? 1 : 0;
            m = (ntohs(ip_hdr->ip_off) & IP_MF) > 0 ? 1 : 0;

            update_ip_hdr(ip_hdr, &r, &d, &m);
        }

        /*
         * if more fragment flag is set, we should not parse the protocol hdr
         * (ICMP, TCP, or UDP) just yet since this is a fragmented packet
         */
        m = (ntohs(ip_hdr->ip_off) & IP_MF) > 0 ? 1 : 0;
        ip_p = ip_hdr->ip_p;

        /* we are going to copy up to layer 3 only, change total length */
        if (layer_opt == 3)
        {
            /* we are editing IP hdr and we have payload, include its length in total length */
            if (header_opt == IP && payload_opt)
            {
                payload_opt->update_payload(payload_opt, l2_len + ip_hlb);
                ip_hdr->ip_len = htons(ip_hlb + payload_opt->len);
            }
            else
                ip_hdr->ip_len = htons(ip_hlb);
        }
    }

    /* recalculate checksum (cover IP hdr only) */
    if (csum_opt)
        update_ip_cksum(ip_hdr, ip_o, &ip_hlb);

    copy_to_pkt(new_pkt_data, PCAP_HDR_LEN + l2_len, ip_hdr, IP_HDR_LEN);

    /* have IP options */
    if (ip_hlb > IP_HDR_LEN)
    {
        copy_to_pkt(new_pkt_data, PCAP_HDR_LEN + l2_len + IP_HDR_LEN, ip_o, ip_hlb - IP_HDR_LEN);
        free(ip_o);
    }

    if (flag == 0)
    {
        /* copy up to layer 3 only, discard remaining data */
        if (layer_opt == 3)
        {
            /* we are editing IP hdr and we have payload */
            if (header_opt == IP && payload_opt)
            {
                copy_to_pkt(new_pkt_data, PCAP_HDR_LEN + l2_len + ip_hlb, payload_opt->payload,
                            payload_opt->len);

                header->caplen = header->len = l2_len + ip_hlb + payload_opt->len;

                /*
                 * if payload is specified and it applies to ICMP, TCP, or UDP hdr + data,
                 * and checksum correction on this payload is needed,
                 * and more fragment flag is not set -> not a fragmented packet
                 */
                if (csum_opt && !m)
                {
                    /* parse ICMP datagram */
                    if (ip_p == IPPROTO_ICMP)
                        return (parse_icmp(pkt_data, new_pkt_data, header, ip_hdr));
                    /* parse TCP datagram */
                    else if (ip_p == IPPROTO_TCP)
                        return (parse_tcp(pkt_data, new_pkt_data, header, ip_hdr));
                    /* parse UDP datagram */
                    else if (ip_p == IPPROTO_UDP)
                        return (parse_udp(pkt_data, new_pkt_data, header, ip_hdr));
                }
            }
            else
                header->caplen = header->len = l2_len + ip_hlb;

            free(ip_hdr);
            return (header->caplen);
        }

        /* !m means more fragment flag is not set -> not a fragmented packet */
        if (!m)
        {
            /* parse ICMP datagram */
            if (ip_p == IPPROTO_ICMP)
                return (parse_icmp(pkt_data, new_pkt_data, header, ip_hdr));
            /* parse TCP datagram */
            else if (ip_p == IPPROTO_TCP)
                return (parse_tcp(pkt_data, new_pkt_data, header, ip_hdr));
            /* parse UDP datagram */
            else if (ip_p == IPPROTO_UDP)
                return (parse_udp(pkt_data, new_pkt_data, header, ip_hdr));
        }

        /* no further editing support for other datagram or fragmented packet */
        free(ip_hdr);
        return (l2_len + ip_hlb);
    }
    return (0); /* flag is 1 */
}

void update_ip_cksum(struct ip *ip_hdr, uint8_t *ip_o, uint16_t *ip_hlb)
{
    uint8_t *ip_hdr_o; /* IP hdr with options (for hdr checksum calculation) */

    ip_hdr->ip_sum = 0x0000; /* clear checksum field */

    /* have IP options */
    if (*ip_hlb > IP_HDR_LEN)
    {
        ip_hdr_o = (uint8_t *)malloc(sizeof(uint8_t) * (*ip_hlb));
        if (ip_hdr_o == NULL)
            error("malloc(): cannot allocate memory for ip_hdr_o");

        /* copy ip_hdr and ip_o into ip_hdr_o */
        memcpy(ip_hdr_o, ip_hdr, IP_HDR_LEN);
        memcpy(ip_hdr_o + IP_HDR_LEN, ip_o, *ip_hlb - IP_HDR_LEN);

        /* calculate checksum of ip_hdr_o */
        ip_hdr->ip_sum = cksum(ip_hdr_o, *ip_hlb);

        free(ip_hdr_o);
    }
    else
        ip_hdr->ip_sum = cksum((uint8_t *)ip_hdr, *ip_hlb);
}

void update_ip_hdr(struct ip *ip_hdr, uint8_t *r, uint8_t *d, uint8_t *m)
{
    uint16_t ip_fo; /* fragment offset (number of 64-bit segments) */

    /* overwrite first 6-bit (DS field) of 8-bit type of service field */
    if (ipopt->ip_ds_field_flag)
        /* left shifted DS field value by 2-bit ECN field */
        ip_hdr->ip_tos |= ipopt->ip_ds_field << 2;

    /* overwrite last 2-bit (ECN field) of 8-bit type of service field */
    if (ipopt->ip_ecn_field_flag)
        ip_hdr->ip_tos |= ipopt->ip_ecn_field;

    /* overwrite identification */
    if (ipopt->ip_id_flag == FIELD_SET)
        ip_hdr->ip_id = htons(ipopt->ip_old_id);
    else if (ipopt->ip_id_flag == FIELD_REPLACE && ip_hdr->ip_id == htons(ipopt->ip_old_id))
        ip_hdr->ip_id = htons(ipopt->ip_new_id);
    else if (ipopt->ip_id_flag == FIELD_SET_RAND ||
             (ipopt->ip_id_flag == FIELD_REPLACE_RAND && ip_hdr->ip_id == htons(ipopt->ip_old_id)))
        ip_hdr->ip_id = htons(get_random_number(UINT16_MAX));

    /* original fragment offset */
    ip_fo = ntohs(ip_hdr->ip_off) & IP_OFFMASK;

    /* overwrite fragment offset only */
    if (ipopt->ip_fo_flag && !ipopt->ip_flags_flag)
    {
        ip_hdr->ip_off = htons((ipopt->ip_fo & IP_OFFMASK) | (*r ? IP_RF : 0) | (*d ? IP_DF : 0) |
                               (*m ? IP_MF : 0));
    }
    /* overwrite flags only */
    else if (!ipopt->ip_fo_flag && ipopt->ip_flags_flag)
    {
        ip_hdr->ip_off = htons((ip_fo & IP_OFFMASK) | ((ipopt->ip_flag_r) ? IP_RF : 0) |
                               ((ipopt->ip_flag_d) ? IP_DF : 0) | ((ipopt->ip_flag_m) ? IP_MF : 0));
    }
    /* overwrite fragment offset and flags */
    else if (ipopt->ip_fo_flag && ipopt->ip_flags_flag)
    {
        ip_hdr->ip_off = htons((ipopt->ip_fo & IP_OFFMASK) | ((ipopt->ip_flag_r) ? IP_RF : 0) |
                               ((ipopt->ip_flag_d) ? IP_DF : 0) | ((ipopt->ip_flag_m) ? IP_MF : 0));
    }

    /* overwrite time to live */
    if (ipopt->ip_ttl_flag == FIELD_SET)
        ip_hdr->ip_ttl = ipopt->ip_old_ttl;
    else if (ipopt->ip_ttl_flag == FIELD_REPLACE && ip_hdr->ip_ttl == ipopt->ip_old_ttl)
        ip_hdr->ip_ttl = ipopt->ip_new_ttl;
    else if (ipopt->ip_ttl_flag == FIELD_SET_RAND ||
             (ipopt->ip_ttl_flag == FIELD_REPLACE_RAND && ip_hdr->ip_ttl == ipopt->ip_old_ttl))
        ip_hdr->ip_ttl = get_random_number(UINT8_MAX);

    /* overwrite protocol */
    if (ipopt->ip_p_flag == FIELD_SET)
        ip_hdr->ip_p = ipopt->ip_old_p;
    else if (ipopt->ip_p_flag == FIELD_REPLACE && ip_hdr->ip_p == htons(ipopt->ip_old_p))
        ip_hdr->ip_p = ipopt->ip_new_p;
    else if (ipopt->ip_p_flag == FIELD_SET_RAND ||
             (ipopt->ip_p_flag == FIELD_REPLACE_RAND && ip_hdr->ip_p == ipopt->ip_old_p))
        ip_hdr->ip_p = get_random_number(UINT8_MAX);

    /* overwrite source IP */
    if (ipopt->ip_src.flag == FIELD_SET)
        memcpy(&ip_hdr->ip_src, &ipopt->ip_src.old, sizeof(struct in_addr));
    else if (ipopt->ip_src.flag == FIELD_REPLACE &&
             memcmp(&ip_hdr->ip_src, &ipopt->ip_src.old, sizeof(struct in_addr)) == 0)
        memcpy(&ip_hdr->ip_src, &ipopt->ip_src.new, sizeof(struct in_addr));
    else if (ipopt->ip_src.flag == FIELD_SET_RAND ||
             (ipopt->ip_src.flag == FIELD_REPLACE_RAND &&
              memcmp(&ip_hdr->ip_src, &ipopt->ip_src.old, sizeof(struct in_addr)) == 0))
        set_random_in_addr(&ip_hdr->ip_src, &ipopt->ip_src);

    /* overwrite destination IP */
    if (ipopt->ip_dst.flag == FIELD_SET)
        memcpy(&ip_hdr->ip_dst, &ipopt->ip_dst.old, sizeof(struct in_addr));
    else if (ipopt->ip_dst.flag == FIELD_REPLACE &&
             memcmp(&ip_hdr->ip_dst, &ipopt->ip_dst.old, sizeof(struct in_addr)) == 0)
        memcpy(&ip_hdr->ip_dst, &ipopt->ip_dst.new, sizeof(struct in_addr));
    else if (ipopt->ip_dst.flag == FIELD_SET_RAND ||
             (ipopt->ip_dst.flag == FIELD_REPLACE_RAND &&
              memcmp(&ip_hdr->ip_dst, &ipopt->ip_dst.old, sizeof(struct in_addr)) == 0))
        set_random_in_addr(&ip_hdr->ip_dst, &ipopt->ip_dst);
}

uint16_t parse_ip6(const uint8_t *pkt_data, uint8_t *new_pkt_data, struct pcap_sf_pkthdr *header)
{
    /*
     * IPv6 header (40 bytes + optional X bytes for extension headers)
     * 1. version (4 bits)
     * 2. traffic class (8 bits)
     * 3. flow label (20 bits)
     * 4. payload length (16 bits) - rest of packet after 40 bytes headers
     * 5. next header (8 bits) - same values as IPv4 protocol field
     * 6. hop limit (8 bits)
     * 7. source address (128 bits)
     * 8. destination address (128 bits)
     * 9. extension headers (X bytes)
     */
    struct ip6 *ip6_hdr;

    /* do nothing if IPv6 hdr is truncated */
    if (header->caplen < l2_len + IP6_HDR_LEN)
        return (l2_len);

    ip6_hdr = (struct ip6 *)malloc(IP6_HDR_LEN);
    if (ip6_hdr == NULL)
        error("malloc(): cannot allocate memory for ip6_hdr");

    copy_to_hdr(pkt_data, l2_len, ip6_hdr, IP6_HDR_LEN);

    /* do nothing if next hdr is unsupported */
    if (ip6_hdr->ip6_nxt != IPPROTO_TCP && ip6_hdr->ip6_nxt != IPPROTO_UDP &&
        ip6_hdr->ip6_nxt != IPPROTO_ICMPV6)
    {
        free(ip6_hdr);
        return (l2_len);
    }

    /* we are editing IPv6 hdr */
    if (header_opt == IP6)
        update_ip6_hdr(ip6_hdr);

    /* we are going to copy up to layer 3 only, change payload length */
    if (layer_opt == 3)
    {
        /* we are editing IPv6 hdr and we have payload, use its length as payload length */
        if (header_opt == IP6 && payload_opt)
        {
            payload_opt->update_payload(payload_opt, l2_len + IP6_HDR_LEN);
            ip6_hdr->ip6_plen = htons(payload_opt->len);
        }
        else
            ip6_hdr->ip6_plen = 0;
    }

    copy_to_pkt(new_pkt_data, PCAP_HDR_LEN + l2_len, ip6_hdr, IP6_HDR_LEN);

    /* copy up to layer 3 only, discard remaining data */
    if (layer_opt == 3)
    {
        /* we are editing IPv6 hdr and we have payload */
        if (header_opt == IP6 && payload_opt)
        {
            copy_to_pkt(new_pkt_data, PCAP_HDR_LEN + l2_len + IP6_HDR_LEN, payload_opt->payload,
                        payload_opt->len);

            header->caplen = header->len = l2_len + IP6_HDR_LEN + payload_opt->len;

            /*
             * if payload is specified and it applies to ICMPv6, TCP, or UDP hdr + data,
             * and checksum correction on this payload is needed
             */
            if (csum_opt)
            {
                /* parse ICMPv6 datagram */
                if (ip6_hdr->ip6_nxt == IPPROTO_ICMPV6)
                    return (parse_icmp6(pkt_data, new_pkt_data, header, ip6_hdr));
                /* parse TCP datagram */
                else if (ip6_hdr->ip6_nxt == IPPROTO_TCP)
                    return (parse_tcp6(pkt_data, new_pkt_data, header, ip6_hdr));
                /* parse UDP datagram */
                else if (ip6_hdr->ip6_nxt == IPPROTO_UDP)
                    return (parse_udp6(pkt_data, new_pkt_data, header, ip6_hdr));
            }
        }
        else
            header->caplen = header->len = l2_len + IP6_HDR_LEN;

        free(ip6_hdr);
        return (header->caplen);
    }

    /* parse ICMPv6 datagram */
    if (ip6_hdr->ip6_nxt == IPPROTO_ICMPV6)
        return (parse_icmp6(pkt_data, new_pkt_data, header, ip6_hdr));
    /* parse TCP datagram */
    else if (ip6_hdr->ip6_nxt == IPPROTO_TCP)
        return (parse_tcp6(pkt_data, new_pkt_data, header, ip6_hdr));
    /* parse UDP datagram */
    else if (ip6_hdr->ip6_nxt == IPPROTO_UDP)
        return (parse_udp6(pkt_data, new_pkt_data, header, ip6_hdr));

    /* no further editing support for other datagram */
    free(ip6_hdr);
    return (l2_len + IP6_HDR_LEN);
}

void update_ip6_hdr(struct ip6 *ip6_hdr)
{
    /* overwrite first 6-bit (DS field) of 8-bit traffic class field */
    if (ip6opt->ip6_ds_field_flag)
        /* left shifted DS field value by 2-bit ECN field + 20-bit flow label */
        ip6_hdr->ip6_flow |= htonl(ip6opt->ip6_ds_field << 22);

    /* overwrite last 2-bit (ECN field) of 8-bit traffic class field */
    if (ip6opt->ip6_ecn_field_flag)
        /* left shifted ECN field value by 20-bit flow label */
        ip6_hdr->ip6_flow |= htonl(ip6opt->ip6_ecn_field << 20);

    /* overwrite flow label */
    if (ip6opt->ip6_flow_label_flag)
    {
        /* keep 4-bit version and 8-bit traffic class but overwrite 20-bit flow label */
        ip6_hdr->ip6_flow = (ip6_hdr->ip6_flow & ~IP6_FLOWLABEL_MASK) |
                            (htonl(ip6opt->ip6_flow_label) & IP6_FLOWLABEL_MASK);
    }

    /* overwrite next header */
    if (ip6opt->ip6_next_header_flag == FIELD_SET)
        ip6_hdr->ip6_nxt = ip6opt->ip6_old_next_header;
    else if (ip6opt->ip6_next_header_flag == FIELD_REPLACE &&
             ip6_hdr->ip6_nxt == ip6opt->ip6_old_next_header)
        ip6_hdr->ip6_nxt = ip6opt->ip6_new_next_header;
    else if (ip6opt->ip6_next_header_flag == FIELD_SET_RAND ||
             (ip6opt->ip6_next_header_flag == FIELD_REPLACE_RAND &&
              ip6_hdr->ip6_nxt == ip6opt->ip6_old_next_header))
        ip6_hdr->ip6_nxt = get_random_number(UINT8_MAX);

    /* overwrite hop limit */
    if (ip6opt->ip6_hop_limit_flag == FIELD_SET)
        ip6_hdr->ip6_hlim = ip6opt->ip6_old_hop_limit;
    else if (ip6opt->ip6_hop_limit_flag == FIELD_REPLACE &&
             ip6_hdr->ip6_hlim == ip6opt->ip6_old_hop_limit)
        ip6_hdr->ip6_hlim = ip6opt->ip6_new_hop_limit;
    else if (ip6opt->ip6_hop_limit_flag == FIELD_SET_RAND ||
             (ip6opt->ip6_hop_limit_flag == FIELD_REPLACE_RAND &&
              ip6_hdr->ip6_hlim == ip6opt->ip6_old_hop_limit))
        ip6_hdr->ip6_hlim = get_random_number(UINT8_MAX);

    /* overwrite source IP */
    if (ip6opt->ip6_src.flag == FIELD_SET)
        memcpy(&ip6_hdr->ip6_src, &ip6opt->ip6_src.old, sizeof(struct in6_addr));
    else if (ip6opt->ip6_src.flag == FIELD_REPLACE &&
             memcmp(&ip6_hdr->ip6_src, &ip6opt->ip6_src.old, sizeof(struct in6_addr)) == 0)
        memcpy(&ip6_hdr->ip6_src, &ip6opt->ip6_src.new, sizeof(struct in6_addr));
    else if (ip6opt->ip6_src.flag == FIELD_SET_RAND ||
             (ip6opt->ip6_src.flag == FIELD_REPLACE_RAND &&
              memcmp(&ip6_hdr->ip6_src, &ip6opt->ip6_src.old, sizeof(struct in6_addr)) == 0))
        set_random_in6_addr(&ip6_hdr->ip6_src, &ip6opt->ip6_src);

    /* overwrite destination IP */
    if (ip6opt->ip6_dst.flag == FIELD_SET)
        memcpy(&ip6_hdr->ip6_dst, &ip6opt->ip6_dst.old, sizeof(struct in6_addr));
    else if (ip6opt->ip6_dst.flag == FIELD_REPLACE &&
             memcmp(&ip6_hdr->ip6_dst, &ip6opt->ip6_dst.old, sizeof(struct in6_addr)) == 0)
        memcpy(&ip6_hdr->ip6_dst, &ip6opt->ip6_dst.new, sizeof(struct in6_addr));
    else if (ip6opt->ip6_dst.flag == FIELD_SET_RAND ||
             (ip6opt->ip6_dst.flag == FIELD_REPLACE_RAND &&
              memcmp(&ip6_hdr->ip6_dst, &ip6opt->ip6_dst.old, sizeof(struct in6_addr)) == 0))
        set_random_in6_addr(&ip6_hdr->ip6_dst, &ip6opt->ip6_dst);
}

uint16_t parse_icmp(const uint8_t *pkt_data, uint8_t *new_pkt_data, struct pcap_sf_pkthdr *header,
                    struct ip *ip_hdr)
{
    /*
     * ICMP header (4 bytes)
     *  1. type (1 byte)
     *  2. code (1 byte)
     *  3. checksum (2 bytes)
     */
    struct icmphdr *icmp_hdr;
    uint16_t ip_hlb; /* IP hdr length in bytes */
    uint16_t ip_fo;  /* IP fragment offset (number of 64-bit segments) */

    ip_hlb = ip_hdr->ip_hl * 4; /* convert to bytes */

    /* do nothing if ICMP hdr is truncated */
    if (header->caplen < l2_len + ip_hlb + ICMP_HDR_LEN)
    {
        free(ip_hdr);
        return (l2_len + ip_hlb);
    }

    icmp_hdr = (struct icmphdr *)malloc(ICMP_HDR_LEN);
    if (icmp_hdr == NULL)
        error("malloc(): cannot allocate memory for icmp_hdr");

    /*
     * we have payload which covers ICMP hdr + data,
     * use that payload instead of pkt_data
     */
    if (layer_opt == 3 && header_opt == IP && payload_opt)
        copy_to_hdr(new_pkt_data, PCAP_HDR_LEN + l2_len + ip_hlb, icmp_hdr, ICMP_HDR_LEN);
    else
    {
        copy_to_hdr(pkt_data, l2_len + ip_hlb, icmp_hdr, ICMP_HDR_LEN);

        /* we are editing ICMP hdr */
        if (header_opt == ICMP)
        {
            /* overwrite type */
            if (icmpopt->icmp_type_flag)
                icmp_hdr->icmp_type = icmpopt->icmp_type;

            /* overwrite code */
            if (icmpopt->icmp_code_flag)
                icmp_hdr->icmp_code = icmpopt->icmp_code;
        }

        /* we are going to copy up to layer 4 only */
        if (layer_opt == 4)
        {
            /*
             * we are editing ICMP hdr and we have payload,
             * attach the payload first before checksum calculation
             */
            if (header_opt == ICMP && payload_opt)
            {
                payload_opt->update_payload(payload_opt, l2_len + ip_hlb + ICMP_HDR_LEN);

                copy_to_pkt(new_pkt_data, PCAP_HDR_LEN + l2_len + ip_hlb + ICMP_HDR_LEN,
                            payload_opt->payload, payload_opt->len);

                header->caplen = header->len = l2_len + ip_hlb + ICMP_HDR_LEN + payload_opt->len;
            }
            else
                header->caplen = header->len = l2_len + ip_hlb + ICMP_HDR_LEN;

            /* update IP total length */
            ip_hdr->ip_len = htons(header->caplen - l2_len);

            /*
             * reuse parsing function for IP hdr
             * to update IP total length in new_pkt_data
             * and recalculate checksum for IP hdr if required
             */
            (void)parse_ip(pkt_data, new_pkt_data, header, ip_hdr, 1);
        }
    }

    /* we have no support for checksum calculation for fragmented packet */
    ip_fo = ntohs(ip_hdr->ip_off) & IP_OFFMASK;

    /*
     * recalculate checksum for ICMP hdr (cover ICMP hdr + trailing data)
     * if we have enough data
     */
    if (csum_opt && ip_fo == 0 && header->caplen >= (l2_len + ntohs(ip_hdr->ip_len)))
        update_icmp_cksum(pkt_data, ip_hdr, icmp_hdr, &ip_hlb);

    free(ip_hdr);

    copy_to_pkt(new_pkt_data, PCAP_HDR_LEN + l2_len + ip_hlb, icmp_hdr, ICMP_HDR_LEN);

    free(icmp_hdr);

    /* no further editing support after ICMP hdr */
    if (layer_opt == 4)
        return (header->caplen);
    /*
     * we have written payload_opt (payload after IP hdr) which covers ICMP hdr + data,
     * checksum for ICMP hdr corrected above,
     * while ICMP data is written to new_pkt_data in parse_ip()
     */
    else if (layer_opt == 3)
        return (header->caplen);
    else
        return (l2_len + ip_hlb + ICMP_HDR_LEN);
}

void update_icmp_cksum(const uint8_t *pkt_data, struct ip *ip_hdr, struct icmphdr *icmp_hdr,
                       uint16_t *ip_hlb)
{
    uint8_t *icmpp; /* ICMP hdr + trailing data */
    uint16_t icmpp_len;
    int i;

    icmpp_len = ntohs(ip_hdr->ip_len) - *ip_hlb;

    icmpp = (uint8_t *)malloc(sizeof(uint8_t) * icmpp_len);
    if (icmpp == NULL)
        error("malloc(): cannot allocate memory for icmpp");
    memset(icmpp, 0, icmpp_len);

    /* clear checksum field */
    icmp_hdr->icmp_cksum = 0x0000;

    /* copy ICMP hdr from icmp_hdr into icmpp */
    memcpy(icmpp, icmp_hdr, ICMP_HDR_LEN);

    /* copy trailing data from payload_opt into icmpp */
    if (layer_opt == 4 && header_opt == ICMP && payload_opt)
    {
        for (i = ICMP_HDR_LEN; i < (ICMP_HDR_LEN + payload_opt->len); i++)
            icmpp[i] = payload_opt->payload[i - ICMP_HDR_LEN];
    }
    /* copy trailing data from payload_opt (payload after IP hdr) into icmpp */
    else if (layer_opt == 3 && header_opt == IP && payload_opt)
    {
        for (i = ICMP_HDR_LEN; i < payload_opt->len; i++)
            icmpp[i] = payload_opt->payload[i];
    }
    /* copy trailing data from pkt_data into icmpp */
    else
    {
        for (i = ICMP_HDR_LEN; i < icmpp_len; i++)
            icmpp[i] = pkt_data[l2_len + *ip_hlb + i];
    }

    /* recalculate checksum */
    icmp_hdr->icmp_cksum = cksum(icmpp, icmpp_len);

    free(icmpp);
}

uint16_t parse_icmp6(const uint8_t *pkt_data, uint8_t *new_pkt_data, struct pcap_sf_pkthdr *header,
                     struct ip6 *ip6_hdr)
{
    /*
     * ICMPv6 header (4 bytes)
     *  1. type (1 byte)
     *  2. code (1 byte)
     *  3. checksum (2 bytes)
     */
    struct icmp6hdr *icmp6_hdr;

    /* do nothing if ICMPv6 hdr is truncated */
    if (header->caplen < l2_len + IP6_HDR_LEN + ICMP6_HDR_LEN)
    {
        free(ip6_hdr);
        return (l2_len + IP6_HDR_LEN);
    }

    icmp6_hdr = (struct icmp6hdr *)malloc(ICMP6_HDR_LEN);
    if (icmp6_hdr == NULL)
        error("malloc(): cannot allocate memory for icmp6_hdr");

    /*
     * we have payload which covers ICMPv6 hdr + data,
     * use that payload instead of pkt_data
     */
    if (layer_opt == 3 && header_opt == IP6 && payload_opt)
        copy_to_hdr(new_pkt_data, PCAP_HDR_LEN + l2_len + IP6_HDR_LEN, icmp6_hdr, ICMP6_HDR_LEN);
    else
    {
        copy_to_hdr(pkt_data, l2_len + IP6_HDR_LEN, icmp6_hdr, ICMP6_HDR_LEN);

        /* we are editing ICMPv6 hdr */
        if (header_opt == ICMP6)
        {
            /* overwrite type */
            if (icmp6opt->icmp6_type_flag)
                icmp6_hdr->icmp6_type = icmp6opt->icmp6_type;

            /* overwrite code */
            if (icmp6opt->icmp6_code_flag)
                icmp6_hdr->icmp6_code = icmp6opt->icmp6_code;
        }

        /* we are going to copy up to layer 4 only */
        if (layer_opt == 4)
        {
            /*
             * we are editing ICMPv6 hdr and we have payload,
             * attach the payload first before checksum calculation
             */
            if (header_opt == ICMP6 && payload_opt)
            {
                payload_opt->update_payload(payload_opt, l2_len + IP6_HDR_LEN + ICMP6_HDR_LEN);

                copy_to_pkt(new_pkt_data, PCAP_HDR_LEN + l2_len + IP6_HDR_LEN + ICMP6_HDR_LEN,
                            payload_opt->payload, payload_opt->len);

                header->caplen = header->len =
                    l2_len + IP6_HDR_LEN + ICMP6_HDR_LEN + payload_opt->len;
            }
            else
                header->caplen = header->len = l2_len + IP6_HDR_LEN + ICMP6_HDR_LEN;

            /* update IPv6 payload length */
            ip6_hdr->ip6_plen = htons(header->caplen - (l2_len + IP6_HDR_LEN));

            copy_to_pkt(new_pkt_data, PCAP_HDR_LEN + l2_len, ip6_hdr, IP6_HDR_LEN);
        }
    }

    /*
     * recalculate checksum for ICMPv6 hdr (cover IPv6 pseudo hdr + ICMPv6 hdr + trailing data)
     * if we have enough data
     */
    if (csum_opt && header->caplen >= (l2_len + IP6_HDR_LEN + ntohs(ip6_hdr->ip6_plen)))
        update_icmp6_cksum(pkt_data, ip6_hdr, icmp6_hdr);

    free(ip6_hdr);

    copy_to_pkt(new_pkt_data, PCAP_HDR_LEN + l2_len + IP6_HDR_LEN, icmp6_hdr, ICMP6_HDR_LEN);

    free(icmp6_hdr);

    /* no further editing support after ICMPv6 hdr */
    if (layer_opt == 4)
        return (header->caplen);
    /*
     * we have written payload_opt (payload after IPv6 hdr) which covers ICMPv6 hdr + data,
     * checksum for ICMPv6 hdr corrected above,
     * while ICMPv6 data is written to new_pkt_data in parse_ip6()
     */
    else if (layer_opt == 3)
        return (header->caplen);
    else
        return (l2_len + IP6_HDR_LEN + ICMP6_HDR_LEN);
}

void update_icmp6_cksum(const uint8_t *pkt_data, struct ip6 *ip6_hdr, struct icmp6hdr *icmp6_hdr)
{
    struct ip6pseudo *ip6p; /* IPv6 pseudo hdr */
    uint8_t *icmp6p;        /* IPv6 pseudo hdr + ICMPv6 hdr + trailing data */
    uint16_t icmp6p_len;
    int i;

    /* create IP pseudo hdr */
    ip6p = create_ip6pseudo(ip6_hdr);

    icmp6p_len = sizeof(struct ip6pseudo) + ntohs(ip6p->ip6pseudo_len);

    icmp6p = (uint8_t *)malloc(sizeof(uint8_t) * icmp6p_len);
    if (icmp6p == NULL)
        error("malloc(): cannot allocate memory for icmp6p");
    memset(icmp6p, 0, icmp6p_len);

    /* copy IPv6 pseudo hdr from ip6p into icmp6p */
    memcpy(icmp6p, ip6p, sizeof(struct ip6pseudo));
    free(ip6p);

    /* clear checksum field */
    icmp6_hdr->icmp6_cksum = 0x0000;

    /* copy icmp6_hdr into icmp6p */
    memcpy(icmp6p + sizeof(struct ip6pseudo), icmp6_hdr, IP_HDR_LEN);

    /* copy trailing data from payload_opt into icmp6p */
    if (layer_opt == 4 && header_opt == ICMP6 && payload_opt)
    {
        for (i = ICMP6_HDR_LEN; i < (icmp6p_len - sizeof(struct ip6pseudo)); i++)
            icmp6p[i + sizeof(struct ip6pseudo)] = payload_opt->payload[i - ICMP6_HDR_LEN];
    }
    /* copy trailing data from payload_opt (payload after IPv6 hdr) into icmp6p */
    else if (layer_opt == 3 && header_opt == IP6 && payload_opt)
    {
        for (i = ICMP6_HDR_LEN; i < payload_opt->len; i++)
            icmp6p[i + sizeof(struct ip6pseudo)] = payload_opt->payload[i];
    }
    /* copy trailing data from pkt_data into icmp6p */
    else
    {
        for (i = ICMP6_HDR_LEN; i < (icmp6p_len - sizeof(struct ip6pseudo)); i++)
            icmp6p[i + sizeof(struct ip6pseudo)] = pkt_data[l2_len + IP6_HDR_LEN + i];
    }

    /* recalculate checksum */
    icmp6_hdr->icmp6_cksum = cksum(icmp6p, icmp6p_len);

    free(icmp6p);
}

uint16_t parse_tcp(const uint8_t *pkt_data, uint8_t *new_pkt_data, struct pcap_sf_pkthdr *header,
                   struct ip *ip_hdr)
{
    /*
     * TCP header (20 bytes + optional X bytes for options)
     *  1. source port (2 bytes)
     *  2. destination port (2 bytes)
     *  3. sequence number (4 bytes)
     *  4. acknowledgment number (4 bytes)
     *  5. data offset (4 bits) - number of 32-bit segments in TCP header
     *  6. reserved (6 bits)
     *  7. flags (6 bits)
     *  8. window (2 bytes)
     *  9. checksum (2 bytes)
     * 10. urgent pointer (2 bytes)
     * 11. options (X bytes)
     */
    struct tcphdr *tcp_hdr;
    uint8_t *tcp_o = NULL; /* options (X bytes) */
    uint16_t tcp_hlb;      /* TCP hdr length in bytes */
    uint16_t ip_hlb;       /* IP hdr length in bytes */
    uint16_t ip_fo;        /* IP fragment offset (number of 64-bit segments) */
    int i, j;

    ip_hlb = ip_hdr->ip_hl * 4; /* convert to bytes */

    /* do nothing if TCP hdr is truncated */
    if (header->caplen < l2_len + ip_hlb + TCP_HDR_LEN)
    {
        free(ip_hdr);
        return (l2_len + ip_hlb);
    }

    tcp_hdr = (struct tcphdr *)malloc(TCP_HDR_LEN);
    if (tcp_hdr == NULL)
        error("malloc(): cannot allocate memory for tcp_hdr");

    /*
     * we have payload which covers TCP hdr + data,
     * use that payload instead of pkt_data
     */
    if (layer_opt == 3 && header_opt == IP && payload_opt)
        copy_to_hdr(new_pkt_data, PCAP_HDR_LEN + l2_len + ip_hlb, tcp_hdr, TCP_HDR_LEN);
    else
        copy_to_hdr(pkt_data, l2_len + ip_hlb, tcp_hdr, TCP_HDR_LEN);

    tcp_hlb = tcp_hdr->th_off * 4; /* convert to bytes */

    /* have TCP options */
    if (tcp_hlb > TCP_HDR_LEN)
    {
        /* do nothing if TCP hdr with options is truncated */
        if (header->caplen < (l2_len + ip_hlb + tcp_hlb))
        {
            free(ip_hdr);
            free(tcp_hdr);
            return (l2_len + ip_hlb);
        }

        tcp_o = (uint8_t *)malloc(sizeof(uint8_t) * (tcp_hlb - TCP_HDR_LEN));
        if (tcp_o == NULL)
            error("malloc(): cannot allocate memory for tcp_o");

        if (layer_opt == 3 && header_opt == IP && payload_opt)
        {
            /* copy TCP options from new_pkt_data into tcp_o */
            for (i = 0, j = TCP_HDR_LEN; i < (tcp_hlb - TCP_HDR_LEN); i++, j++)
                tcp_o[i] = new_pkt_data[PCAP_HDR_LEN + l2_len + ip_hlb + j];
        }
        else
        {
            /* copy TCP options from pkt_data into tcp_o */
            for (i = 0, j = TCP_HDR_LEN; i < (tcp_hlb - TCP_HDR_LEN); i++, j++)
                tcp_o[i] = pkt_data[l2_len + ip_hlb + j];
        }
    }

    /* we are editing TCP hdr */
    if (header_opt == TCP)
        update_tcp_hdr(tcp_hdr);

    /* we are going to copy up to layer 4 only */
    if (layer_opt == 4)
    {
        /*
         * we are editing TCP hdr and we have payload,
         * attach the payload first before checksum calculation
         */
        if (header_opt == TCP && payload_opt)
        {
            payload_opt->update_payload(payload_opt, l2_len + ip_hlb + tcp_hlb);

            copy_to_pkt(new_pkt_data, PCAP_HDR_LEN + l2_len + ip_hlb + tcp_hlb,
                        payload_opt->payload, payload_opt->len);

            header->caplen = header->len = l2_len + ip_hlb + tcp_hlb + payload_opt->len;
        }
        else
            header->caplen = header->len = l2_len + ip_hlb + tcp_hlb;

        /* update IP total length */
        ip_hdr->ip_len = htons(header->caplen - l2_len);

        /*
         * reuse parsing function for IP hdr
         * to update IP total length in new_pkt_data
         * and recalculate checksum for IP hdr if required
         */
        (void)parse_ip(pkt_data, new_pkt_data, header, ip_hdr, 1);
    }

    /* we have no support for checksum calculation for fragmented packet */
    ip_fo = ntohs(ip_hdr->ip_off) & IP_OFFMASK;

    /*
     * recalculate checksum for TCP hdr (cover IP pseudo hdr + TCP hdr + trailing data)
     * if we have enough data
     */
    if (csum_opt && ip_fo == 0 && header->caplen >= (l2_len + ntohs(ip_hdr->ip_len)))
        update_tcp_cksum(pkt_data, ip_hdr, tcp_hdr, &ip_hlb, &tcp_hlb, tcp_o);

    free(ip_hdr);

    copy_to_pkt(new_pkt_data, PCAP_HDR_LEN + l2_len + ip_hlb, tcp_hdr, TCP_HDR_LEN);

    free(tcp_hdr);

    /* have TCP options */
    if (tcp_hlb > TCP_HDR_LEN)
    {
        copy_to_pkt(new_pkt_data, PCAP_HDR_LEN + l2_len + ip_hlb + TCP_HDR_LEN, tcp_o,
                    tcp_hlb - TCP_HDR_LEN);
        free(tcp_o);
    }

    /* no further editing support after TCP hdr */
    if (layer_opt == 4)
        return (header->caplen);
    /*
     * we have written payload_opt (payload after IP hdr) which covers TCP hdr + data,
     * checksum for TCP hdr corrected above,
     * while TCP data is written to new_pkt_data in parse_ip()
     */
    else if (layer_opt == 3)
        return (header->caplen);
    else
        return (l2_len + ip_hlb + tcp_hlb);
}

void update_tcp_cksum(const uint8_t *pkt_data, struct ip *ip_hdr, struct tcphdr *tcp_hdr,
                      uint16_t *ip_hlb, uint16_t *tcp_hlb, uint8_t *tcp_o)
{
    struct ippseudo *ipp; /* IP pseudo hdr */
    uint8_t *tcpp;        /* IP pseudo hdr + TCP hdr (with options if exist) + trailing data */
    uint16_t tcpp_len;
    int i;

    /* create IP pseudo hdr */
    ipp = create_ippseudo(ip_hdr, ip_hlb);

    tcpp_len = sizeof(struct ippseudo) + ntohs(ipp->ippseudo_len);

    tcpp = (uint8_t *)malloc(sizeof(uint8_t) * tcpp_len);
    if (tcpp == NULL)
        error("malloc(): cannot allocate memory for tcpp");
    memset(tcpp, 0, tcpp_len);

    /* copy IP pseudo hdr from ipp into tcpp */
    memcpy(tcpp, ipp, sizeof(struct ippseudo));
    free(ipp);

    /* clear checksum field */
    tcp_hdr->th_sum = 0x0000;

    /* copy TCP hdr from tcp_hdr into tcpp */
    memcpy(tcpp + sizeof(struct ippseudo), tcp_hdr, TCP_HDR_LEN);

    /* have TCP options, copy tcp_o into tcpp */
    if (*tcp_hlb > TCP_HDR_LEN)
        memcpy(tcpp + sizeof(struct ippseudo) + TCP_HDR_LEN, tcp_o, *tcp_hlb - TCP_HDR_LEN);

    /* copy trailing data from payload_opt into tcpp */
    if (layer_opt == 4 && header_opt == TCP && payload_opt)
    {
        for (i = *tcp_hlb; i < (tcpp_len - sizeof(struct ippseudo)); i++)
            tcpp[i + sizeof(struct ippseudo)] = payload_opt->payload[i - *tcp_hlb];
    }
    /* copy trailing data from payload_opt (payload after IP hdr) into tcpp */
    else if (layer_opt == 3 && header_opt == IP && payload_opt)
    {
        for (i = *tcp_hlb; i < payload_opt->len; i++)
            tcpp[i + sizeof(struct ippseudo)] = payload_opt->payload[i];
    }
    /* copy trailing data from pkt_data into tcpp */
    else
    {
        for (i = *tcp_hlb; i < (tcpp_len - sizeof(struct ippseudo)); i++)
            tcpp[i + sizeof(struct ippseudo)] = pkt_data[l2_len + *ip_hlb + i];
    }

    /* recalculate checksum */
    tcp_hdr->th_sum = cksum(tcpp, tcpp_len);

    free(tcpp);
}

uint16_t parse_tcp6(const uint8_t *pkt_data, uint8_t *new_pkt_data, struct pcap_sf_pkthdr *header,
                    struct ip6 *ip6_hdr)
{
    /*
     * TCP header (20 bytes + optional X bytes for options)
     *  1. source port (2 bytes)
     *  2. destination port (2 bytes)
     *  3. sequence number (4 bytes)
     *  4. acknowledgment number (4 bytes)
     *  5. data offset (4 bits) - number of 32-bit segments in TCP header
     *  6. reserved (6 bits)
     *  7. flags (6 bits)
     *  8. window (2 bytes)
     *  9. checksum (2 bytes)
     * 10. urgent pointer (2 bytes)
     * 11. options (X bytes)
     */
    struct tcphdr *tcp_hdr;
    uint8_t *tcp_o = NULL; /* options (X bytes) */
    uint16_t tcp_hlb;      /* TCP hdr length in bytes */
    int i, j;

    /* do nothing if TCP hdr is truncated */
    if (header->caplen < l2_len + IP6_HDR_LEN + TCP_HDR_LEN)
    {
        free(ip6_hdr);
        return (l2_len + IP6_HDR_LEN);
    }

    tcp_hdr = (struct tcphdr *)malloc(TCP_HDR_LEN);
    if (tcp_hdr == NULL)
        error("malloc(): cannot allocate memory for tcp_hdr");

    /*
     * we have payload which covers TCP hdr + data,
     * use that payload instead of pkt_data
     */
    if (layer_opt == 3 && header_opt == IP6 && payload_opt)
        copy_to_hdr(new_pkt_data, PCAP_HDR_LEN + l2_len + IP6_HDR_LEN, tcp_hdr, TCP_HDR_LEN);
    else
        copy_to_hdr(pkt_data, l2_len + IP6_HDR_LEN, tcp_hdr, TCP_HDR_LEN);

    tcp_hlb = tcp_hdr->th_off * 4; /* convert to bytes */

    /* have TCP options */
    if (tcp_hlb > TCP_HDR_LEN)
    {
        /* do nothing if TCP hdr with options is truncated */
        if (header->caplen < (l2_len + IP6_HDR_LEN + tcp_hlb))
        {
            free(ip6_hdr);
            free(tcp_hdr);
            return (l2_len + IP6_HDR_LEN);
        }

        tcp_o = (uint8_t *)malloc(sizeof(uint8_t) * (tcp_hlb - TCP_HDR_LEN));
        if (tcp_o == NULL)
            error("malloc(): cannot allocate memory for tcp_o");

        if (layer_opt == 3 && header_opt == IP6 && payload_opt)
        {
            /* copy TCP options from new_pkt_data into tcp_o */
            for (i = 0, j = TCP_HDR_LEN; i < (tcp_hlb - TCP_HDR_LEN); i++, j++)
                tcp_o[i] = new_pkt_data[PCAP_HDR_LEN + l2_len + IP6_HDR_LEN + j];
        }
        else
        {
            /* copy TCP options from pkt_data into tcp_o */
            for (i = 0, j = TCP_HDR_LEN; i < (tcp_hlb - TCP_HDR_LEN); i++, j++)
                tcp_o[i] = pkt_data[l2_len + IP6_HDR_LEN + j];
        }
    }

    /* we are editing TCP hdr */
    if (header_opt == TCP)
        update_tcp_hdr(tcp_hdr);

    /* we are going to copy up to layer 4 only */
    if (layer_opt == 4)
    {
        /*
         * we are editing TCP hdr and we have payload,
         * attach the payload first before checksum calculation
         */
        if (header_opt == TCP && payload_opt)
        {
            payload_opt->update_payload(payload_opt, l2_len + IP6_HDR_LEN + tcp_hlb);

            copy_to_pkt(new_pkt_data, PCAP_HDR_LEN + l2_len + IP6_HDR_LEN + tcp_hlb,
                        payload_opt->payload, payload_opt->len);

            header->caplen = header->len = l2_len + IP6_HDR_LEN + tcp_hlb + payload_opt->len;
        }
        else
            header->caplen = header->len = l2_len + IP6_HDR_LEN + tcp_hlb;

        /* update IPv6 payload length */
        ip6_hdr->ip6_plen = htons(header->caplen - (l2_len + IP6_HDR_LEN));

        copy_to_pkt(new_pkt_data, PCAP_HDR_LEN + l2_len, ip6_hdr, IP6_HDR_LEN);
    }

    /*
     * recalculate checksum for TCP hdr (cover IPv6 pseudo hdr + TCP hdr + trailing data)
     * if we have enough data
     */
    if (csum_opt && header->caplen >= (l2_len + IP6_HDR_LEN + ntohs(ip6_hdr->ip6_plen)))
        update_tcp6_cksum(pkt_data, ip6_hdr, tcp_hdr, &tcp_hlb, tcp_o);

    free(ip6_hdr);

    copy_to_pkt(new_pkt_data, PCAP_HDR_LEN + l2_len + IP6_HDR_LEN, tcp_hdr, TCP_HDR_LEN);

    free(tcp_hdr);

    /* have TCP options */
    if (tcp_hlb > TCP_HDR_LEN)
    {
        copy_to_pkt(new_pkt_data, PCAP_HDR_LEN + l2_len + IP6_HDR_LEN + TCP_HDR_LEN, tcp_o,
                    tcp_hlb - TCP_HDR_LEN);
        free(tcp_o);
    }

    /* no further editing support after TCP hdr */
    if (layer_opt == 4)
        return (header->caplen);
    /*
     * we have written payload_opt (payload after IPv6 hdr) which covers TCP hdr + data,
     * checksum for TCP hdr corrected above,
     * while TCP data is written to new_pkt_data in parse_ip6()
     */
    else if (layer_opt == 3)
        return (header->caplen);
    else
        return (l2_len + IP6_HDR_LEN + tcp_hlb);
}

void update_tcp6_cksum(const uint8_t *pkt_data, struct ip6 *ip6_hdr, struct tcphdr *tcp_hdr,
                       uint16_t *tcp_hlb, uint8_t *tcp_o)
{
    struct ip6pseudo *ip6p; /* IPv6 pseudo hdr */
    uint8_t *tcpp;          /* IPv6 pseudo hdr + TCP hdr (with options if exist) + trailing data */
    uint16_t tcpp_len;
    int i;

    /* create IP pseudo hdr */
    ip6p = create_ip6pseudo(ip6_hdr);

    tcpp_len = sizeof(struct ip6pseudo) + ntohs(ip6p->ip6pseudo_len);

    tcpp = (uint8_t *)malloc(sizeof(uint8_t) * tcpp_len);
    if (tcpp == NULL)
        error("malloc(): cannot allocate memory for tcpp");
    memset(tcpp, 0, tcpp_len);

    /* copy IPv6 pseudo hdr from ipp into tcpp */
    memcpy(tcpp, ip6p, sizeof(struct ip6pseudo));
    free(ip6p);

    /* clear checksum field */
    tcp_hdr->th_sum = 0x0000;

    /* copy TCP hdr from tcp_hdr into tcpp */
    memcpy(tcpp + sizeof(struct ip6pseudo), tcp_hdr, TCP_HDR_LEN);

    /* have TCP options, copy tcp_o into tcpp */
    if (*tcp_hlb > TCP_HDR_LEN)
        memcpy(tcpp + sizeof(struct ip6pseudo) + TCP_HDR_LEN, tcp_o, *tcp_hlb - TCP_HDR_LEN);

    /* copy trailing data from payload_opt into tcpp */
    if (layer_opt == 4 && header_opt == TCP && payload_opt)
    {
        for (i = *tcp_hlb; i < (tcpp_len - sizeof(struct ip6pseudo)); i++)
            tcpp[i + sizeof(struct ip6pseudo)] = payload_opt->payload[i - *tcp_hlb];
    }
    /* copy trailing data from payload_opt (payload after IPv6 hdr) into tcpp */
    else if (layer_opt == 3 && header_opt == IP6 && payload_opt)
    {
        for (i = *tcp_hlb; i < payload_opt->len; i++)
            tcpp[i + sizeof(struct ip6pseudo)] = payload_opt->payload[i];
    }
    /* copy trailing data from pkt_data into tcpp */
    else
    {
        for (i = *tcp_hlb; i < (tcpp_len - sizeof(struct ip6pseudo)); i++)
            tcpp[i + sizeof(struct ip6pseudo)] = pkt_data[l2_len + IP6_HDR_LEN + i];
    }

    /* recalculate checksum */
    tcp_hdr->th_sum = cksum(tcpp, tcpp_len);

    free(tcpp);
}

void update_tcp_hdr(struct tcphdr *tcp_hdr)
{
    /* overwrite source port */
    if (tcpopt->th_sport_flag == FIELD_SET)
        tcp_hdr->th_sport = htons(tcpopt->th_old_sport);
    else if (tcpopt->th_sport_flag == FIELD_REPLACE &&
             tcp_hdr->th_sport == htons(tcpopt->th_old_sport))
        tcp_hdr->th_sport = htons(tcpopt->th_new_sport);
    else if (tcpopt->th_sport_flag == FIELD_SET_RAND ||
             (tcpopt->th_sport_flag == FIELD_REPLACE_RAND &&
              tcp_hdr->th_sport == htons(tcpopt->th_old_sport)))
        tcp_hdr->th_sport = htons(get_random_number(UINT16_MAX));

    /* overwrite destination port */
    if (tcpopt->th_dport_flag == FIELD_SET)
        tcp_hdr->th_dport = htons(tcpopt->th_old_dport);
    else if (tcpopt->th_dport_flag == FIELD_REPLACE &&
             tcp_hdr->th_dport == htons(tcpopt->th_old_dport))
        tcp_hdr->th_dport = htons(tcpopt->th_new_dport);
    else if (tcpopt->th_dport_flag == FIELD_SET_RAND ||
             (tcpopt->th_dport_flag == FIELD_REPLACE_RAND &&
              tcp_hdr->th_dport == htons(tcpopt->th_old_dport)))
        tcp_hdr->th_dport = htons(get_random_number(UINT16_MAX));

    /* overwrite sequence number */
    if (tcpopt->th_seq_flag == FIELD_SET)
        tcp_hdr->th_seq = htonl(tcpopt->th_old_seq);
    else if (tcpopt->th_seq_flag == FIELD_REPLACE && tcp_hdr->th_seq == htonl(tcpopt->th_old_seq))
        tcp_hdr->th_seq = htonl(tcpopt->th_new_seq);
    else if (tcpopt->th_seq_flag == FIELD_SET_RAND ||
             (tcpopt->th_seq_flag == FIELD_REPLACE_RAND &&
              tcp_hdr->th_seq == htonl(tcpopt->th_old_seq)))
        tcp_hdr->th_seq = htonl(get_random_number(UINT32_MAX));

    /* overwrite acknowledgment number */
    if (tcpopt->th_ack_flag == FIELD_SET)
        tcp_hdr->th_ack = htonl(tcpopt->th_old_ack);
    else if (tcpopt->th_ack_flag == FIELD_REPLACE && tcp_hdr->th_ack == htonl(tcpopt->th_old_ack))
        tcp_hdr->th_ack = htonl(tcpopt->th_new_ack);
    else if (tcpopt->th_ack_flag == FIELD_SET_RAND ||
             (tcpopt->th_ack_flag == FIELD_REPLACE_RAND &&
              tcp_hdr->th_ack == htonl(tcpopt->th_old_ack)))
        tcp_hdr->th_ack = htonl(get_random_number(UINT32_MAX));

    /* overwrite flags */
    if (tcpopt->th_flags_flag)
        tcp_hdr->th_flags = ((tcpopt->th_flag_c ? TH_CWR : 0) | (tcpopt->th_flag_e ? TH_ECE : 0) |
                             (tcpopt->th_flag_u ? TH_URG : 0) | (tcpopt->th_flag_a ? TH_ACK : 0) |
                             (tcpopt->th_flag_p ? TH_PUSH : 0) | (tcpopt->th_flag_r ? TH_RST : 0) |
                             (tcpopt->th_flag_s ? TH_SYN : 0) | (tcpopt->th_flag_f ? TH_FIN : 0));

    /* overwrite window size */
    if (tcpopt->th_win_flag)
        tcp_hdr->th_win = htons(tcpopt->th_win);

    /* overwrite urgent pointer */
    if (tcpopt->th_urp_flag)
        tcp_hdr->th_urp = htons(tcpopt->th_urp);
}

uint16_t parse_udp(const uint8_t *pkt_data, uint8_t *new_pkt_data, struct pcap_sf_pkthdr *header,
                   struct ip *ip_hdr)
{
    /*
     * UDP header (8 bytes)
     *  1. source port (2 bytes)
     *  2. destination port (2 bytes)
     *  3. length (2 bytes)
     *  4. checksum (2 bytes)
     */
    struct udphdr *udp_hdr;
    uint16_t ip_hlb; /* IP hdr length in bytes */
    uint16_t ip_fo;  /* IP fragment offset (number of 64-bit segments) */

    ip_hlb = ip_hdr->ip_hl * 4; /* convert to bytes */

    /* do nothing if UDP hdr is truncated */
    if (header->caplen < l2_len + ip_hlb + UDP_HDR_LEN)
    {
        free(ip_hdr);
        return (l2_len + ip_hlb);
    }

    udp_hdr = (struct udphdr *)malloc(UDP_HDR_LEN);
    if (udp_hdr == NULL)
        error("malloc(): cannot allocate memory for udp_hdr");

    /*
     * we have payload which covers UDP hdr + data,
     * use that payload instead of pkt_data
     */
    if (layer_opt == 3 && header_opt == IP && payload_opt)
        copy_to_hdr(new_pkt_data, PCAP_HDR_LEN + l2_len + ip_hlb, udp_hdr, UDP_HDR_LEN);
    else
        copy_to_hdr(pkt_data, l2_len + ip_hlb, udp_hdr, UDP_HDR_LEN);

    /* we are editing UDP hdr */
    if (header_opt == UDP)
        update_udp_hdr(udp_hdr);

    /* we are going to copy up to layer 4 only */
    if (layer_opt == 4)
    {
        /*
         * we are editing UDP hdr and we have payload,
         * attach the payload first before checksum calculation
         */
        if (header_opt == UDP && payload_opt)
        {
            payload_opt->update_payload(payload_opt, l2_len + ip_hlb + UDP_HDR_LEN);

            copy_to_pkt(new_pkt_data, PCAP_HDR_LEN + l2_len + ip_hlb + UDP_HDR_LEN,
                        payload_opt->payload, payload_opt->len);

            header->caplen = header->len = l2_len + ip_hlb + UDP_HDR_LEN + payload_opt->len;
        }
        else
            header->caplen = header->len = l2_len + ip_hlb + UDP_HDR_LEN;

        /* update UDP length */
        udp_hdr->uh_ulen = htons(header->caplen - (l2_len + ip_hlb));

        /* update IP total length */
        ip_hdr->ip_len = htons(header->caplen - l2_len);

        /*
         * reuse parsing function for IP hdr
         * to update IP total length in new_pkt_data
         * and recalculate checksum for IP hdr if required
         */
        (void)parse_ip(pkt_data, new_pkt_data, header, ip_hdr, 1);
    }

    /* we have no support for checksum calculation for fragmented packet */
    ip_fo = ntohs(ip_hdr->ip_off) & IP_OFFMASK;

    /*
     * recalculate checksum for UDP hdr (cover IP pseudo hdr + UDP hdr + trailing data)
     * if we have enough data
     */
    if (csum_opt && ip_fo == 0 && header->caplen >= (l2_len + ntohs(ip_hdr->ip_len)))
        update_udp_cksum(pkt_data, ip_hdr, udp_hdr, &ip_hlb);

    free(ip_hdr);

    copy_to_pkt(new_pkt_data, PCAP_HDR_LEN + l2_len + ip_hlb, udp_hdr, UDP_HDR_LEN);

    free(udp_hdr);

    /* no further editing support after UDP hdr */
    if (layer_opt == 4)
        return (header->caplen);
    /*
     * we have written payload_opt (payload after IP hdr) which covers UDP hdr + data,
     * checksum for UDP hdr corrected above,
     * while UDP data is written to new_pkt_data in parse_ip()
     */
    else if (layer_opt == 3)
        return (header->caplen);
    else
        return (l2_len + ip_hlb + UDP_HDR_LEN);
}

void update_udp_cksum(const uint8_t *pkt_data, struct ip *ip_hdr, struct udphdr *udp_hdr,
                      uint16_t *ip_hlb)
{
    struct ippseudo *ipp; /* IP pseudo hdr */
    uint8_t *udpp;        /* IP pseudo hdr + UDP hdr + trailing data */
    uint16_t udpp_len;
    int i;

    /* create IP pseudo hdr */
    ipp = create_ippseudo(ip_hdr, ip_hlb);

    udpp_len = sizeof(struct ippseudo) + ntohs(ipp->ippseudo_len);

    udpp = (uint8_t *)malloc(sizeof(uint8_t) * udpp_len);
    if (udpp == NULL)
        error("malloc(): cannot allocate memory for udpp");
    memset(udpp, 0, udpp_len);

    /* copy IP pseudo hdr from ipp into udpp */
    memcpy(udpp, ipp, sizeof(struct ippseudo));
    free(ipp);

    /* clear checksum field */
    udp_hdr->uh_sum = 0x0000;

    /* copy UDP hdr from udp_hdr into udpp */
    memcpy(udpp + sizeof(struct ippseudo), udp_hdr, UDP_HDR_LEN);

    /* copy trailing data from payload_opt into udpp */
    if (layer_opt == 4 && header_opt == UDP && payload_opt)
    {
        for (i = UDP_HDR_LEN; i < (udpp_len - sizeof(struct ippseudo)); i++)
            udpp[i + sizeof(struct ippseudo)] = payload_opt->payload[i - UDP_HDR_LEN];
    }
    /* copy trailing data from payload_opt (payload after IP hdr) into udpp */
    else if (layer_opt == 3 && header_opt == IP && payload_opt)
    {
        for (i = UDP_HDR_LEN; i < payload_opt->len; i++)
            udpp[i + sizeof(struct ippseudo)] = payload_opt->payload[i];
    }
    /* copy trailing data from pkt_data into udpp */
    else
    {
        for (i = UDP_HDR_LEN; i < (udpp_len - sizeof(struct ippseudo)); i++)
            udpp[i + sizeof(struct ippseudo)] = pkt_data[l2_len + *ip_hlb + i];
    }

    /* recalculate checksum */
    udp_hdr->uh_sum = cksum(udpp, udpp_len);

    free(udpp);
}

uint16_t parse_udp6(const uint8_t *pkt_data, uint8_t *new_pkt_data, struct pcap_sf_pkthdr *header,
                    struct ip6 *ip6_hdr)
{
    /*
     * UDP header (8 bytes)
     *  1. source port (2 bytes)
     *  2. destination port (2 bytes)
     *  3. length (2 bytes)
     *  4. checksum (2 bytes)
     */
    struct udphdr *udp_hdr;

    /* do nothing if UDP hdr is truncated */
    if (header->caplen < l2_len + IP6_HDR_LEN + UDP_HDR_LEN)
    {
        free(ip6_hdr);
        return (l2_len + IP6_HDR_LEN);
    }

    udp_hdr = (struct udphdr *)malloc(UDP_HDR_LEN);
    if (udp_hdr == NULL)
        error("malloc(): cannot allocate memory for udp_hdr");

    /*
     * we have payload which covers UDP hdr + data,
     * use that payload instead of pkt_data
     */
    if (layer_opt == 3 && header_opt == IP6 && payload_opt)
        copy_to_hdr(new_pkt_data, PCAP_HDR_LEN + l2_len + IP6_HDR_LEN, udp_hdr, UDP_HDR_LEN);
    else
        copy_to_hdr(pkt_data, l2_len + IP6_HDR_LEN, udp_hdr, UDP_HDR_LEN);

    /* we are editing UDP hdr */
    if (header_opt == UDP)
        update_udp_hdr(udp_hdr);

    /* we are going to copy up to layer 4 only */
    if (layer_opt == 4)
    {
        /*
         * we are editing UDP hdr and we have payload,
         * attach the payload first before checksum calculation
         */
        if (header_opt == UDP && payload_opt)
        {
            payload_opt->update_payload(payload_opt, l2_len + IP6_HDR_LEN + UDP_HDR_LEN);

            copy_to_pkt(new_pkt_data, PCAP_HDR_LEN + l2_len + IP6_HDR_LEN + UDP_HDR_LEN,
                        payload_opt->payload, payload_opt->len);

            header->caplen = header->len = l2_len + IP6_HDR_LEN + UDP_HDR_LEN + payload_opt->len;
        }
        else
            header->caplen = header->len = l2_len + IP6_HDR_LEN + UDP_HDR_LEN;

        /* update UDP length and IPv6 payload length */
        udp_hdr->uh_ulen = ip6_hdr->ip6_plen = htons(header->caplen - (l2_len + IP6_HDR_LEN));

        copy_to_pkt(new_pkt_data, PCAP_HDR_LEN + l2_len, ip6_hdr, IP6_HDR_LEN);
    }

    /*
     * recalculate checksum for UDP hdr (cover IPv6 pseudo hdr + UDP hdr + trailing data)
     * if we have enough data
     */
    if (csum_opt && header->caplen >= (l2_len + IP6_HDR_LEN + ntohs(ip6_hdr->ip6_plen)))
        update_udp6_cksum(pkt_data, ip6_hdr, udp_hdr);

    free(ip6_hdr);

    copy_to_pkt(new_pkt_data, PCAP_HDR_LEN + l2_len + IP6_HDR_LEN, udp_hdr, UDP_HDR_LEN);

    free(udp_hdr);

    /* no further editing support after UDP hdr */
    if (layer_opt == 4)
        return (header->caplen);
    /*
     * we have written payload_opt (payload after IPv6 hdr) which covers UDP hdr + data,
     * checksum for UDP hdr corrected above,
     * while UDP data is written to new_pkt_data in parse_ip6()
     */
    else if (layer_opt == 3)
        return (header->caplen);
    else
        return (l2_len + IP6_HDR_LEN + UDP_HDR_LEN);
}

void update_udp6_cksum(const uint8_t *pkt_data, struct ip6 *ip6_hdr, struct udphdr *udp_hdr)
{
    struct ip6pseudo *ip6p; /* IPv6 pseudo hdr */
    uint8_t *udpp;          /* IPv6 pseudo hdr + UDP hdr + trailing data */
    uint16_t udpp_len;
    int i;

    /* create IPv6 pseudo hdr */
    ip6p = create_ip6pseudo(ip6_hdr);

    udpp_len = sizeof(struct ip6pseudo) + ntohs(ip6p->ip6pseudo_len);

    udpp = (uint8_t *)malloc(sizeof(uint8_t) * udpp_len);
    if (udpp == NULL)
        error("malloc(): cannot allocate memory for udpp");
    memset(udpp, 0, udpp_len);

    /* copy IPv6 pseudo hdr from ipp into udpp */
    memcpy(udpp, ip6p, sizeof(struct ip6pseudo));
    free(ip6p);

    /* clear checksum field */
    udp_hdr->uh_sum = 0x0000;

    /* copy UDP hdr from udp_hdr into udpp */
    memcpy(udpp + sizeof(struct ip6pseudo), udp_hdr, UDP_HDR_LEN);

    /* copy trailing data from payload_opt into udpp */
    if (layer_opt == 4 && header_opt == UDP && payload_opt)
    {
        for (i = UDP_HDR_LEN; i < (udpp_len - sizeof(struct ip6pseudo)); i++)
            udpp[i + sizeof(struct ip6pseudo)] = payload_opt->payload[i - UDP_HDR_LEN];
    }
    /* copy trailing data from payload_opt (payload after IPv6 hdr) into udpp */
    else if (layer_opt == 3 && header_opt == IP6 && payload_opt)
    {
        for (i = UDP_HDR_LEN; i < payload_opt->len; i++)
            udpp[i + sizeof(struct ip6pseudo)] = payload_opt->payload[i];
    }
    /* copy trailing data from pkt_data into udpp */
    else
    {
        for (i = UDP_HDR_LEN; i < (udpp_len - sizeof(struct ip6pseudo)); i++)
            udpp[i + sizeof(struct ip6pseudo)] = pkt_data[l2_len + IP6_HDR_LEN + i];
    }

    /* recalculate checksum */
    udp_hdr->uh_sum = cksum(udpp, udpp_len);

    free(udpp);
}

void update_udp_hdr(struct udphdr *udp_hdr)
{
    /* overwrite source port */
    if (udpopt->uh_sport_flag == FIELD_SET)
        udp_hdr->uh_sport = htons(udpopt->uh_old_sport);
    else if (udpopt->uh_sport_flag == FIELD_REPLACE &&
             udp_hdr->uh_sport == htons(udpopt->uh_old_sport))
        udp_hdr->uh_sport = htons(udpopt->uh_new_sport);
    else if (udpopt->uh_sport_flag == FIELD_SET_RAND ||
             (udpopt->uh_sport_flag == FIELD_REPLACE_RAND &&
              udp_hdr->uh_sport == htons(udpopt->uh_old_sport)))
        udp_hdr->uh_sport = htons(get_random_number(UINT16_MAX));

    /* overwrite destination port */
    if (udpopt->uh_dport_flag == FIELD_SET)
        udp_hdr->uh_dport = htons(udpopt->uh_old_dport);
    else if (udpopt->uh_dport_flag == FIELD_REPLACE &&
             udp_hdr->uh_dport == htons(udpopt->uh_old_dport))
        udp_hdr->uh_dport = htons(udpopt->uh_new_dport);
    else if (udpopt->uh_dport_flag == FIELD_SET_RAND ||
             (udpopt->uh_dport_flag == FIELD_REPLACE_RAND &&
              udp_hdr->uh_dport == htons(udpopt->uh_old_dport)))
        udp_hdr->uh_dport = htons(get_random_number(UINT16_MAX));
}

void set_random_eth_addr(uint8_t *eth_addr)
{
    uint64_t r = tinymt64_generate_uint64(&tinymt); /* 8 segments of random 8 bits */

    for (uint8_t i = 0; i < ETH_ADDR_LEN; i++)
    {
        eth_addr[i] = (uint8_t)(r & 0xff);
        r >>= 8; /* use next segment of random 8 bits */
    }
}

void set_random_in_addr(struct in_addr *addr, struct in_addr_opt *opt)
{
    uint8_t rem_bits = opt->rand_bits;              /* remaining last/right bits to randomize */
    uint64_t r = tinymt64_generate_uint64(&tinymt); /* 8 segments of random 8 bits */

    for (uint8_t i = 0; i < 4; i++) /* loop 4 octets */
    {
        rem_bits -= (rem_bits > 8) ? 8 : rem_bits;

        opt->new.s_addr = opt->new.s_addr ^ ((opt->new.s_addr ^ r) & ~opt->netmask.s_addr);

        if (rem_bits == 0)
            break;

        r >>= 8; /* use next segment of random 8 bits */
    }
    memcpy(addr, &opt->new, sizeof(struct in_addr));
}

void set_random_in6_addr(struct in6_addr *addr, struct in6_addr_opt *opt)
{
    uint8_t rem_bits = opt->rand_bits;              /* remaining last/right bits to randomize */
    uint64_t r = tinymt64_generate_uint64(&tinymt); /* 8 segments of random 8 bits */

    for (uint8_t i = 15; i >= 0; i--) /* loop 16 octets starting from last octet */
    {
        rem_bits -= (rem_bits > 8) ? 8 : rem_bits;

        opt->new.s6_addr[i] =
            opt->new.s6_addr[i] ^ ((opt->new.s6_addr[i] ^ r) & ~opt->netmask.s6_addr[i]);

        if (rem_bits == 0)
            break;

        r >>= 8; /* use next segment of random 8 bits */

        /* exhausted all 8 segments, regenerate new segments of random 8 bits */
        if (i % 8 == 0)
            r = tinymt64_generate_uint64(&tinymt);
    }
    memcpy(addr, &opt->new, sizeof(struct in6_addr));
}

uint64_t get_random_number(uint64_t max_val)
{
    /* return uniformly distributed random number between 0 and max_val inclusive */
    return tinymt64_generate_double(&tinymt) * (max_val + 1);
}

uint64_t get_random_number_in_range(uint64_t min_val, uint64_t max_val)
{
    /* return uniformly distributed random number between min_val and max_val inclusive */
    uint64_t range_size = max_val - min_val + 1;
    return (uint64_t)(tinymt64_generate_double(&tinymt) * range_size) + min_val;
}

struct ippseudo *create_ippseudo(struct ip *ip_hdr, uint16_t *ip_hlb)
{
    struct ippseudo *ipp = (struct ippseudo *)malloc(sizeof(struct ippseudo));
    if (ipp == NULL)
        error("malloc(): cannot allocate memory for ipp");

    memcpy(&ipp->ippseudo_src, &ip_hdr->ip_src, sizeof(struct in_addr));
    memcpy(&ipp->ippseudo_dst, &ip_hdr->ip_dst, sizeof(struct in_addr));
    ipp->ippseudo_pad = 0x00;
    ipp->ippseudo_p = ip_hdr->ip_p;
    ipp->ippseudo_len = htons(ntohs(ip_hdr->ip_len) - *ip_hlb);

    return ipp;
}

struct ip6pseudo *create_ip6pseudo(struct ip6 *ip6_hdr)
{
    struct ip6pseudo *ip6p = (struct ip6pseudo *)malloc(sizeof(struct ip6pseudo));
    if (ip6p == NULL)
        error("malloc(): cannot allocate memory for ip6p");
    memset(ip6p, 0, sizeof(struct ip6pseudo));

    memcpy(&ip6p->ip6pseudo_src, &ip6_hdr->ip6_src, sizeof(struct in6_addr));
    memcpy(&ip6p->ip6pseudo_dst, &ip6_hdr->ip6_dst, sizeof(struct in6_addr));
    ip6p->ip6pseudo_len = ip6_hdr->ip6_plen;
    ip6p->ip6pseudo_nxt = ip6_hdr->ip6_nxt;

    return ip6p;
}

/* Reference: rfc1071.txt */
uint16_t cksum(const void *cp, uint16_t len)
{
    const uint16_t *word_16 = cp; /* 16-bit word at a time */
    uint16_t rem = len;
    unsigned int sum = 0;

    /* add all 16-bit words */
    while (rem > 1)
    {
        sum += *word_16;
        word_16++;
        rem -= 2;
    }

    /* add last byte if len is odd */
    if (rem)
        sum += *(uint8_t *)word_16;

    /* fold 32-bit sum into 16 bits in network byte order */
    while (sum > 0xffff)
        sum = (sum >> 16) + (sum & 0xffff);

    /* one's complement the sum */
    return (uint16_t)(~sum);
}

int eth_aton(const char *cp, uint8_t *eth_addr)
{
    int i;
    unsigned int o0, o1, o2, o3, o4, o5;

    i = sscanf(cp, "%x:%x:%x:%x:%x:%x", &o0, &o1, &o2, &o3, &o4, &o5);

    if (i != 6)
    {
        eth_addr = NULL;
        return 0;
    }

    eth_addr[0] = o0;
    eth_addr[1] = o1;
    eth_addr[2] = o2;
    eth_addr[3] = o3;
    eth_addr[4] = o4;
    eth_addr[5] = o5;
    return 1;
}

void cleanup(int signum)
{
    /* starting cleanup, ignore further SIGINT signals */
    signal(SIGINT, SIG_IGN);

    void *opts[] = {payload_opt, ethopt, arpopt, ipopt, ip6opt, icmpopt, icmp6opt, tcpopt, udpopt};
    for (size_t i = 0; i < sizeof(opts) / sizeof(opts[0]); i++)
    {
        if (opts[i])
            free(opts[i]);
    }

    if (signum == -1)
        exit(EXIT_FAILURE);
    else
        info();

    exit(EXIT_SUCCESS);
}

void info(void)
{
    (void)putchar('\n');
    notice("%llu packets (%llu bytes) written", pkts, bytes);
}

void notice(const char *fmt, ...)
{
    va_list ap;
    va_start(ap, fmt);
    (void)vprintf(fmt, ap);
    va_end(ap);

    if (*fmt && fmt[strlen(fmt) - 1] != '\n')
        (void)puts("");
}

void error(const char *fmt, ...)
{
    va_list ap;
    (void)fprintf(stderr, "%s: ", program_name);
    va_start(ap, fmt);
    (void)vfprintf(stderr, fmt, ap);
    va_end(ap);

    if (*fmt && fmt[strlen(fmt) - 1] != '\n')
        fputc('\n', stderr);

    cleanup(-1);
}

void usage(void)
{
    (void)fprintf(stderr,
                  "%s version %s, Copyright (C) 2006 - 2024 Addy Yeow <ayeowch@gmail.com>\n"
                  "Usage: %s [-I input] [-O output] [-L layer] [-X payload] [-C]\n"
                  "                 [-M linktype] [-D offset] [-R range] [-S timeframe]\n"
                  "                 [-N repeat] [-G gaprange] [-P seed] [-T header]\n"
                  "                 [header-specific-options] [-h]\n"
                  "\nOptions:\n"
                  " -I input        Input pcap based trace file. Typically, input should be a\n"
                  "                 file path to a pcap based trace file. However, for\n"
                  "                 convenience, the following template names are also\n"
                  "                 accepted to load trace file from one of the built-in\n"
                  "                 templates:\n"
                  "                 eth    : Ethernet header\n"
                  "                 vlan   : VLAN header (IEEE 802.1Q, 802.1ad)\n"
                  "                 arp    : ARP header\n"
                  "                 ip     : IPv4 header\n"
                  "                 ip6    : IPv6 header\n"
                  "                 icmp   : ICMPv4 header\n"
                  "                 icmp6  : ICMPv6 header\n"
                  "                 tcp    : IPv4 TCP header\n"
                  "                 ip6tcp : IPv6 TCP header\n"
                  "                 udp    : IPv4 UDP header\n"
                  "                 ip6udp : IPv6 UDP header\n"
                  "                 Example: -I icmp\n"
                  " -O output       Output trace file.\n"
                  " -L layer        Copy up to the specified 'layer' and discard the remaining\n"
                  "                 data. Value for 'layer' must be either 2, 3, or 4 where\n"
                  "                 2 for Ethernet and VLAN, 3 for ARP, IPv4, or IPv6, and 4 for\n"
                  "                 ICMPv4, ICMPv6, TCP, or UDP.\n"
                  " -X payload      Append 'payload' in hex digits to the end of each packet.\n"
                  "                 Example: -X 0302aad1\n"
                  "                 -X flag is ignored if -L and -T flag are not specified.\n"
                  " -C              Specify this flag to disable checksum correction.\n"
                  "                 Checksum correction is applicable for non-fragmented\n"
                  "                 supported packets only.\n"
                  " -M linktype     Replace the 'linktype' stored in the pcap file header.\n"
                  "                 Typically, value for 'linktype' is 1 for Ethernet.\n"
                  "                 Example: -M 12 (for raw IP), -M 51 (for PPPoE)\n"
                  " -D offset       Delete the specified byte 'offset' from each packet.\n"
                  "                 First byte (starting from link layer header) starts from 1.\n"
                  "                 -L, -X, -C and -T flag are ignored if -D flag is specified.\n"
                  "                 Example: -D 15-40, -D 10, or -D 18-9999\n"
                  " -R range        Save only the specified 'range' of packets.\n"
                  "                 Example: -R 5-21 or -R 9\n"
                  " -S timeframe    Save only the packets within the specified 'timeframe' with\n"
                  "                 up to one-second resolution using DD/MM/YYYY,HH:MM:SS as the\n"
                  "                 format for start and end time in 'timeframe'.\n"
                  "                 Example: -S 22/10/2006,21:47:35-24/10/2006,13:16:05\n"
                  "                 -S flag is evaluated after -R flag.\n"
                  " -N repeat       Duplicate packets from the 'input' trace file 'repeat'\n"
                  "                 times. Use this flag to create a stream of packets,\n"
                  "                 each with, for example, a random tcp sequence number, from\n"
                  "                 a 1-packet trace file.\n"
                  "                 Example: -N 100000\n"
                  "                 -N flag is evaluated after -R and -S flag.\n"
                  " -G gaprange     Apply inter-packet gap between packets in microseconds from\n"
                  "                 0 to (2^31 - 1). Values in 'gaprange' are inclusive and\n"
                  "                 selected randomly. A single value implies a fixed gap.\n"
                  "                 Example: -G 1000-10000 or -G 1000\n"
                  "                 -G flag is evaluated after -R, -S, and -N flag.\n"
                  " -P seed         Positive integer to seed the random number generator (RNG)\n"
                  "                 used, for example, to  generate random  port number.\n"
                  "                 If unset, current timestamp will be used as the RNG seed.\n"
                  "                 bittwiste uses Mersenne Twister for high-speed uniformly\n"
                  "                 distributed random number generation.\n"
                  " -T header       Edit only the specified 'header'. Possible keywords for\n"
                  "                 'header' are, eth, vlan, arp, ip, ip6, icmp, icmp6, tcp, or\n"
                  "                 udp.\n"
                  "                 -T flag must appear last among the general options.\n"
                  " -h              Print version information and usage.\n"
                  " header-specific-options\n"
                  "                 See bittwiste manual page for header specific options:\n"
                  "                 https://bittwist.sourceforge.io/doc/bittwiste.1.html\n",
                  program_name, BITTWISTE_VERSION, program_name);
    exit(EXIT_SUCCESS);
}
