/* $Id$ */ /* * Copyright (c) 2001-2010 Aaron Turner * Copyright (c) 2013-2024 Fred Klassen - 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 . */ #include "bridge.h" #include "config.h" #include "common.h" #include "tcpbridge.h" #include #include #include #include #include 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; }