appneta/tcpreplay

View on GitHub
src/bridge.c

Summary

Maintainability
Test Coverage
/* $Id$ */

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

#include "bridge.h"
#include "config.h"
#include "common.h"
#include "tcpbridge.h"
#include <errno.h>
#include <signal.h>
#include <stdlib.h>
#include <string.h>
#include <sys/time.h>

extern tcpreplay_stats_t stats;
volatile bool didsig;

static void live_callback(u_char *, const struct pcap_pkthdr *, const u_char *);

static void signal_catcher(int signo);

/**
 * First, prep our RB Tree which tracks where each (source)
 * MAC really lives so we don't create really nasty network
 * storms.
 */
static struct macsrc_t *new_node(void);

RB_HEAD(macsrc_tree, macsrc_t) macsrc_root;

static int
rbmacsrc_comp(struct macsrc_t *a, struct macsrc_t *b)
{
    return (memcmp(a->key, b->key, ETHER_ADDR_LEN));
}

RB_PROTOTYPE(macsrc_tree, macsrc_t, node, rbmacsrc_comp)
RB_GENERATE(macsrc_tree, macsrc_t, node, rbmacsrc_comp)

/**
 * create a new node... Malloc's memory
 */
struct macsrc_t *
new_node(void)
{
    struct macsrc_t *node;

    node = (struct macsrc_t *)safe_malloc(sizeof(struct macsrc_t));

    memset(node, '\0', sizeof(struct macsrc_t));
    return (node);
}

/**
 * main loop for bridging in only one direction
 * optimized to not use poll(), but rather libpcap's builtin pcap_loop()
 */
static void
do_bridge_unidirectional(tcpbridge_opt_t *options, tcpedit_t *tcpedit)
{
    struct live_data_t livedata;
    int retcode;

    assert(options);
    assert(tcpedit);

    livedata.tcpedit = tcpedit;
    livedata.source = PCAP_INT1;
    livedata.pcap = options->pcap1;
    livedata.options = options;

    if ((retcode = pcap_loop(options->pcap1, (int)options->limit_send, live_callback, (u_char *)&livedata)) < 0) {
        warnx("Error in %d pcap_loop(): %s", retcode, pcap_geterr(options->pcap1));
    }
}

/**
 * main loop for bridging in both directions.  Since we dealing with two handles
 * we need to poll() on them which isn't the most efficient
 */
static void
do_bridge_bidirectional(tcpbridge_opt_t *options, tcpedit_t *tcpedit)
{
    struct pollfd polls[2]; /* one for left & right pcap */
    int pollresult, pollcount, timeout;
    struct live_data_t livedata;

    assert(options);
    assert(tcpedit);

    livedata.tcpedit = tcpedit;
    livedata.options = options;

    /*
     * loop until ctrl-C or we've sent enough packets
     * note that if -L wasn't specified, limit_send is
     * set to 0 so this will loop infinately
     */
    while ((options->limit_send == 0) || (options->limit_send > stats.pkts_sent)) {
        if (didsig)
            break;

        dbgx(3, "limit_send: " COUNTER_SPEC " \t pkts_sent: " COUNTER_SPEC, options->limit_send, stats.pkts_sent);

        /* reset the result codes */
        polls[PCAP_INT1].revents = 0;
        polls[PCAP_INT1].events = POLLIN;
        polls[PCAP_INT1].fd = pcap_fileno(options->pcap1);

        polls[PCAP_INT2].revents = 0;
        polls[PCAP_INT2].events = POLLIN;
        polls[PCAP_INT2].fd = pcap_fileno(options->pcap2);

        timeout = options->poll_timeout;
        pollcount = 2;

        /* poll for a packet on the two interfaces */
        pollresult = poll(polls, pollcount, timeout);

        /* poll has returned, process the result */
        if (pollresult > 0) {
            dbgx(3, "pollresult: %d", pollresult);

            /* success, got one or more packets */
            if (polls[PCAP_INT1].revents > 0) {
                dbg(5, "Processing first interface");
                livedata.source = PCAP_INT1;
                livedata.pcap = options->pcap1;
                pcap_dispatch(options->pcap1, -1, (pcap_handler)live_callback, (u_char *)&livedata);
            }

            /* check the other interface?? */
            if (polls[PCAP_INT2].revents > 0) {
                dbg(5, "Processing second interface");
                livedata.source = PCAP_INT2;
                livedata.pcap = options->pcap2;
                pcap_dispatch(options->pcap2, -1, (pcap_handler)live_callback, (u_char *)&livedata);
            }

        } else if (pollresult == 0) {
            dbg(3, "poll timeout exceeded...");
            /* do something here? */
        } else {
            /* poll error, probably a Ctrl-C */
            warnx("poll() error: %s", strerror(errno));
        }

        /* go back to the top of the loop */
    }

} /* do_bridge_bidirectional() */

/**
 * Main entry point to bridging.  Does some initial setup and then calls the
 * correct loop (unidirectional or bidirectional)
 */
void
do_bridge(tcpbridge_opt_t *options, tcpedit_t *tcpedit)
{
    /* do we apply a bpf filter? */
    if (options->bpf.filter != NULL) {
        /* compile filter */
        dbgx(2, "Try to compile pcap bpf filter: %s", options->bpf.filter);
        if (pcap_compile(options->pcap1, &options->bpf.program, options->bpf.filter, options->bpf.optimize, 0) != 0) {
            errx(-1, "Error compiling BPF filter: %s", pcap_geterr(options->pcap1));
        }

        /* apply filter */
        pcap_setfilter(options->pcap1, &options->bpf.program);
        pcap_freecode(&options->bpf.program);

        /* same for other interface if applicable */
        if (options->unidir == 0) {
            /* compile filter */
            dbgx(2, "Try to compile pcap bpf filter: %s", options->bpf.filter);
            if (pcap_compile(options->pcap2, &options->bpf.program, options->bpf.filter, options->bpf.optimize, 0) !=
                0) {
                errx(-1, "Error compiling BPF filter: %s", pcap_geterr(options->pcap2));
            }

            /* apply filter */
            pcap_setfilter(options->pcap2, &options->bpf.program);
            pcap_freecode(&options->bpf.program);
        }
    }

    /* register signals */
    didsig = 0;
    (void)signal(SIGINT, signal_catcher);

    if (options->unidir == 1) {
        do_bridge_unidirectional(options, tcpedit);
    } else {
        do_bridge_bidirectional(options, tcpedit);
    }

    if (get_current_time(&stats.end_time) < 0)
        errx(-1, "get_current_time() failed: %s", strerror(errno));
    packet_stats(&stats);
}

/**
 * This is the callback we use with pcap_dispatch to process
 * each packet received by libpcap on the two interfaces.
 * Need to return > 0 to denote success
 */
static void
live_callback(u_char *usr_data, const struct pcap_pkthdr *const_pkthdr, const u_char *nextpkt)
{
    struct live_data_t *livedata = (struct live_data_t *)usr_data;
    struct pcap_pkthdr pkthdr_buf = *const_pkthdr;
    struct pcap_pkthdr *pkthdr = &pkthdr_buf;
    ipv4_hdr_t *ip_hdr = NULL;
    ipv6_hdr_t *ip6_hdr = NULL;
    pcap_t *send = NULL;
    static u_char *pktdata = NULL; /* full packet buffer */
    int cache_mode;
    static unsigned long packetnum = 0;
    struct macsrc_t *node, finder; /* rb tree nodes */
#ifdef DEBUG
    u_char dstmac[ETHER_ADDR_LEN];
#endif
    u_int16_t l2proto;

    packetnum++;
    dbgx(2, "packet %lu caplen %d", packetnum, pkthdr->caplen);

    /* only malloc the first time */
    if (pktdata == NULL) {
        /* create packet buffers */
        pktdata = (u_char *)safe_malloc(MAXPACKET);
    } else {
        /* zero out the old packet info */
        memset(pktdata, '\0', MAXPACKET);
    }

    /* copy the packet to our buffer */
    memcpy(pktdata, nextpkt, pkthdr->caplen);

#ifdef ENABLE_VERBOSE
    /* decode packet? */
    if (livedata->options->verbose)
        tcpdump_print(livedata->options->tcpdump, pkthdr, nextpkt);
#endif

    /* lookup our source MAC in the tree */
    memcpy(&finder.key, &pktdata[ETHER_ADDR_LEN], ETHER_ADDR_LEN);
#ifdef DEBUG
    memcpy(&dstmac, pktdata, ETHER_ADDR_LEN);
    dbgx(1, "SRC MAC: " MAC_FORMAT "\tDST MAC: " MAC_FORMAT, MAC_STR(finder.key), MAC_STR(dstmac));
#endif

    /* first, is this a packet sent locally?  If so, ignore it */
    if ((memcmp(livedata->options->intf1_mac, &finder.key, ETHER_ADDR_LEN)) == 0) {
        dbgx(1, "Packet matches the MAC of %s, skipping.", livedata->options->intf1);
        return;
    } else if ((memcmp(livedata->options->intf2_mac, &finder.key, ETHER_ADDR_LEN)) == 0) {
        dbgx(1, "Packet matches the MAC of %s, skipping.", livedata->options->intf2);
        return;
    }

    node = RB_FIND(macsrc_tree, &macsrc_root, &finder);

    /* if we can't find the node, build a new one */
    if (node == NULL) {
        dbg(1, "Unable to find MAC in the tree");
        node = new_node();
        node->source = livedata->source;
        memcpy(&node->key, &finder.key, ETHER_ADDR_LEN);
        RB_INSERT(macsrc_tree, &macsrc_root, node);
    }

    /* otherwise compare sources */
    else if (node->source != livedata->source) {
        dbg(1, "Found the dest MAC in the tree and it doesn't match this source NIC... skipping packet");
        /*
         * IMPORTANT!!!
         * Never send a packet out the same interface we sourced it on!
         */
        return;
    }

    /* what is our cache mode? */
    cache_mode = livedata->source == PCAP_INT1 ? TCPR_DIR_C2S : TCPR_DIR_S2C;

    l2proto = tcpedit_l3proto(livedata->tcpedit, BEFORE_PROCESS, pktdata, (int)pkthdr->len);
    dbgx(2, "Packet protocol: %04hx", l2proto);

    /* should we skip this packet based on CIDR match? */
    if (l2proto == ETHERTYPE_IP) {
        dbg(3, "Packet is IPv4");
        ip_hdr = (ipv4_hdr_t *)tcpedit_l3data(livedata->tcpedit, BEFORE_PROCESS, pktdata, (int)pkthdr->len);

        /* look for include or exclude CIDR match */
        if (livedata->options->xX.cidr != NULL) {
            if (!ip_hdr || !process_xX_by_cidr_ipv4(livedata->options->xX.mode, livedata->options->xX.cidr, ip_hdr)) {
                dbg(2, "Skipping IPv4 packet due to CIDR match");
                return;
            }
        }

    } else if (l2proto == ETHERTYPE_IP6) {
        dbg(3, "Packet is IPv6");
        ip6_hdr = (ipv6_hdr_t *)tcpedit_l3data(livedata->tcpedit, BEFORE_PROCESS, pktdata, (int)pkthdr->len);

        /* look for include or exclude CIDR match */
        if (livedata->options->xX.cidr != NULL) {
            if (!process_xX_by_cidr_ipv6(livedata->options->xX.mode, livedata->options->xX.cidr, ip6_hdr)) {
                dbg(2, "Skipping IPv6 packet due to CIDR match");
                return;
            }
        }
    }

    if (tcpedit_packet(livedata->tcpedit, &pkthdr, &pktdata, cache_mode) < 0)
        return;

    /*
     * send packets out the OTHER interface
     * and update the dst mac if necessary
     */
    switch (node->source) {
    case PCAP_INT1:
        dbgx(2, "Packet source was %s... sending out on %s", livedata->options->intf1, livedata->options->intf2);
        send = livedata->options->pcap2;
        break;

    case PCAP_INT2:
        dbgx(2, "Packet source was %s... sending out on %s", livedata->options->intf2, livedata->options->intf1);
        send = livedata->options->pcap1;
        break;

    default:
        errx(-1, "wtf?  our node->source != PCAP_INT1 and != PCAP_INT2: %c", node->source);
    }

    /*
     * write packet out on the network
     */
    if (pcap_sendpacket(send, pktdata, (int)pkthdr->caplen) < 0)
        errx(-1,
             "Unable to send packet out %s: %s",
             send == livedata->options->pcap1 ? livedata->options->intf1 : livedata->options->intf2,
             pcap_geterr(send));

    stats.bytes_sent += pkthdr->caplen;
    stats.pkts_sent++;

    dbgx(1, "Sent packet " COUNTER_SPEC, stats.pkts_sent);
} /* live_callback() */

static void
signal_catcher(int signo)
{
    /* stdio in signal handlers causes a race condition, instead set a flag */
    if (signo == SIGINT)
        didsig = true;
}