So after a lot of research and trying out other implementations, I finally assembled a version that works for me.
It keeps lists of MAC addresses of both interfaces and hence can prevent loops.
Also it's faster than other implementations by keeping an L2Socket and reusing it.
There is still a lot one could tune, but here it is:
#!/usr/bin/python2
import signal
from threading import Thread,Lock
from scapy.all import *
def usage():
print 'Usage: scapy_bridge.py host1_interface host2_interfcae'
print ''
print 'Example: sudo python scapy_bridge.py eth1 eth2'
print ' Sets up a bridge between the hosts connected on eth1 and eth2'
class Sniffer():
def __init__(self, input_interface, output_interface, sniffer_name):
self.input_interface = input_interface
self.output_interface = output_interface
self.sniffer_name = sniffer_name
self.output_socket = L2Socket(output_interface)
self.output_mac = get_if_hwaddr(self.output_interface)
def add_mac(self, list, mac):
if mac not in list:
list.append(mac)
def process_packet(self, pkt):
global macs_if1, macs_if2
handle_packet = True
if Ether in pkt:
src_mac = pkt[Ether].src
dst_mac = pkt[Ether].dst
if self.sniffer_name == '1to2':
if src_mac in macs_if2:
handle_packet = False
else:
self.add_mac(macs_if1, src_mac)
else:
if src_mac in macs_if1:
handle_packet = False
else:
self.add_mac(macs_if2, src_mac)
print 'MAC table 1: ' + str(macs_if1)
print 'MAC table 2: ' + str(macs_if2)
if handle_packet:
p = pkt.copy()
print 'MSGLEN=%d' % len(p)
if len(p) > 1400:
p.show()
frags = fragment(p)
for frag in frags:
self.output_socket.send(frag)
else:
self.output_socket.send(p)
def stopper_check(self, pkt):
return not still_running_lock.locked()
def sniffloop(self):
sniff(iface=self.input_interface, prn=self.process_packet, stop_filter=self.stopper_check)
# ==================================== MAIN
# global list of running threads
threads = []
# MAC table
macs_if1 = []
macs_if2 = []
# global lock to signal that we're still running
still_running_lock = Lock()
# catch Ctl-c and clean up threads
def signal_handler(signal, frame):
print 'Cleaning up sniff threads...'
still_running_lock.release()
try:
for t in threads: t.join()
except:
pass
print 'exiting.'
sys.exit(0)
if __name__ == '__main__':
if '-h' in sys.argv or '--help' in sys.argv or len(sys.argv) != 3:
usage()
sys.exit(-1)
(host1_interface, host2_interface) = sys.argv[1:]
sniffer1 = Sniffer(host1_interface, host2_interface, '1to2')
sniffer2 = Sniffer(host2_interface, host1_interface, '2to1')
threads.append( Thread(target=sniffer1.sniffloop) )
threads.append( Thread(target=sniffer2.sniffloop) )
# set our "state" to running by acquiring the lock
still_running_lock.acquire()
for t in threads: t.start()
signal.signal(signal.SIGINT, signal_handler)
signal.pause()
EDIT:
I would like to add, that if you want to mess with the packets, mitmproxy in transparent mode together with iptables seems to be a perfect solution which also does not require any client side configuration. (i.e. it's transparent)