diff --git a/.appveyor.yml b/.appveyor.yml
index 3a744d8528cfbd3e6b1fda65d0ade1e18539b1fe..ffa3c7a8a54c21c928a6ac034ed7be06f8ed32ab 100644
--- a/.appveyor.yml
+++ b/.appveyor.yml
@@ -29,7 +29,7 @@ test_script:
   - 'del test\regression.uts'
 
   # Secondary and contrib unit tests
-  - 'del test\bpf.uts test\linux.uts' # Don't bother with OS dependent regression tests
+  - 'del test\bpf.uts test\linux.uts test\sendsniff.uts' # Don't bother with OS dependent regression tests
   - "%PYTHON%\\python -m coverage run --parallel-mode bin\\UTscapy -c test\\configs\\windows.utsc || exit /b 42"
   
   # TLS unit tests
diff --git a/.travis/test.sh b/.travis/test.sh
index 2c27c3e8dbc914761c7b67c4b1cc3e258f95a662..abd6f0cfb6a309e11787bd4e77f1840579718a7b 100644
--- a/.travis/test.sh
+++ b/.travis/test.sh
@@ -12,6 +12,11 @@ then
   SCAPY_SUDO=""
 fi
 
+if [ "$SCAPY_USE_PCAPDNET" = "yes" ]
+then
+  UT_FLAGS+=" -K not_pcapdnet"
+fi
+
 # AES-CCM, ChaCha20Poly1305 and X25519 were added to Cryptography v2.0
 # but the minimal version mandated by scapy is v1.7
 UT_FLAGS+=" -K crypto_advanced"
diff --git a/scapy/sendrecv.py b/scapy/sendrecv.py
index 4503abcac26c5526c0cbd2841ba4c59885f78022..12cb665493466e039ddfd97a7fe703ee246a712c 100644
--- a/scapy/sendrecv.py
+++ b/scapy/sendrecv.py
@@ -31,6 +31,7 @@ from scapy.modules.six import iteritems
 if conf.route is None:
     # unused import, only to initialize conf.route
     import scapy.route
+from scapy.supersocket import SuperSocket
 
 #################
 ## Debug class ##
@@ -734,16 +735,25 @@ Examples:
 
 
 @conf.commands.register
-def bridge_and_sniff(if1, if2, prn=None, L2socket=None, *args, **kargs):
-    """Forward traffic between interfaces if1 and if2, sniff and return the
-exchanged packets.
+def bridge_and_sniff(if1, if2, xfrm12=None, xfrm21=None, prn=None, L2socket=None,
+                     *args, **kargs):
+    """Forward traffic between interfaces if1 and if2, sniff and return
+the exchanged packets.
 
 Arguments:
 
-  if1, if2: the interfaces to use
+  if1, if2: the interfaces to use (interface names or opened sockets).
+
+  xfrm12: a function to call when forwarding a packet from if1 to
+      if2. If it returns True, the packet is forwarded as it. If it
+      returns False or None, the packet is discarded. If it returns a
+      packet, this packet is forwarded instead of the original packet
+      one.
+
+  xfrm21: same as xfrm12 for packets forwarded from if2 to if1.
 
   The other arguments are the same than for the function sniff(),
-      except for opened_socket, offline and iface that are ignored.
+      except for offline, opened_socket and iface that are ignored.
       See help(sniff) for more.
 
     """
@@ -752,20 +762,45 @@ Arguments:
             log_runtime.warning("Argument %s cannot be used in "
                                 "bridge_and_sniff() -- ignoring it.", arg)
             del kargs[arg]
-    if L2socket is None:
-        L2socket = conf.L2socket
-    s1 = L2socket(iface=if1)
-    s2 = L2socket(iface=if2)
-    peers = {if1: s2, if2: s1}
+    def _init_socket(iface, count):
+        if isinstance(iface, SuperSocket):
+            return iface, "iface%d" % count
+        else:
+            return (L2socket or conf.L2socket)(iface=iface), iface
+    sckt1, if1 = _init_socket(if1, 1)
+    sckt2, if2 = _init_socket(if2, 2)
+    peers = {if1: sckt2, if2: sckt1}
+    xfrms = {}
+    if xfrm12 is not None:
+        xfrms[if1] = xfrm12
+    if xfrm21 is not None:
+        xfrms[if2] = xfrm21
     def prn_send(pkt):
         try:
             sendsock = peers[pkt.sniffed_on]
         except KeyError:
             return
+        if pkt.sniffed_on in xfrms:
+            try:
+                newpkt = xfrms[pkt.sniffed_on](pkt)
+            except:
+                log_runtime.warning(
+                    'Exception in transformation function for packet [%s] '
+                    'received on %s -- dropping',
+                    pkt.summary(), pkt.sniffed_on, exc_info=True
+                )
+                return
+            else:
+                if newpkt is True:
+                    newpkt = pkt.original
+                elif not newpkt:
+                    return
+        else:
+            newpkt = pkt.original
         try:
-            sendsock.send(pkt.original)
+            sendsock.send(newpkt)
         except:
-            log_runtime.warning('Cannot forward packet [%s] received from %s',
+            log_runtime.warning('Cannot forward packet [%s] received on %s',
                                 pkt.summary(), pkt.sniffed_on, exc_info=True)
     if prn is None:
         prn = prn_send
@@ -775,7 +810,8 @@ Arguments:
             prn_send(pkt)
             return prn_orig(pkt)
 
-    return sniff(opened_socket={s1: if1, s2: if2}, prn=prn, *args, **kargs)
+    return sniff(opened_socket={sckt1: if1, sckt2: if2}, prn=prn,
+                 *args, **kargs)
 
 
 @conf.commands.register
diff --git a/scapy/supersocket.py b/scapy/supersocket.py
index 81feec212db0d9e752a6614270630dc5e579d974..9ea7373f9d02235b16a176a12189524ac0d77d30 100644
--- a/scapy/supersocket.py
+++ b/scapy/supersocket.py
@@ -8,14 +8,18 @@ SuperSocket.
 """
 
 from __future__ import absolute_import
-import socket,time
+import socket
+import subprocess
+import struct
+import time
 
 from scapy.config import conf
+from scapy.consts import LINUX, OPENBSD, BSD
 from scapy.data import *
 from scapy.error import warning, log_runtime
+import scapy.modules.six as six
 import scapy.packet
 from scapy.utils import PcapReader, tcpdump
-import scapy.modules.six as six
 
 class _SuperSocket_metaclass(type):
     def __repr__(self):
@@ -44,12 +48,14 @@ class SuperSocket(six.with_metaclass(_SuperSocket_metaclass)):
     def close(self):
         if self.closed:
             return
-        self.closed=1
-        if self.ins != self.outs:
-            if self.outs and self.outs.fileno() != -1:
-                self.outs.close()
-        if self.ins and self.ins.fileno() != -1:
-            self.ins.close()
+        self.closed = True
+        if hasattr(self, "outs"):
+            if not hasattr(self, "ins") or self.ins != self.outs:
+                if self.outs and self.outs.fileno() != -1:
+                    self.outs.close()
+        if hasattr(self, "ins"):
+            if self.ins and self.ins.fileno() != -1:
+                self.ins.close()
     def sr(self, *args, **kargs):
         from scapy import sendrecv
         return sendrecv.sndrcv(self, *args, **kargs)
@@ -201,5 +207,81 @@ class L2ListenTcpdump(SuperSocket):
         return self.ins.recv(x)
 
 
+class TunTapInterface(SuperSocket):
+    """A socket to act as the host's peer of a tun / tap interface.
+
+    """
+    desc = "Act as the host's peer of a tun / tap interface"
+
+    def __init__(self, iface=None, mode_tun=None, *arg, **karg):
+        self.iface = conf.iface if iface is None else iface
+        self.mode_tun = ("tun" in iface) if mode_tun is None else mode_tun
+        self.closed = True
+        self.open()
+
+    def __enter__(self):
+        return self
+
+    def __del__(self):
+        self.close()
+
+    def __exit__(self, *_):
+        self.close()
+
+    def open(self):
+        """Open the TUN or TAP device."""
+        if not self.closed:
+            return
+        self.outs = self.ins = open(
+            "/dev/net/tun" if LINUX else ("/dev/%s" % self.iface), "r+b",
+        )
+        if LINUX:
+            from fcntl import ioctl
+            # TUNSETIFF = 0x400454ca
+            # IFF_TUN = 0x0001
+            # IFF_TAP = 0x0002
+            # IFF_NO_PI = 0x1000
+            ioctl(self.ins, 0x400454ca, struct.pack(
+                "16sH", self.iface, 0x0001 if self.mode_tun else 0x1002,
+            ))
+        self.closed = False
+
+    def __call__(self, *arg, **karg):
+        """Needed when using an instantiated TunTapInterface object for
+conf.L2listen, conf.L2socket or conf.L3socket.
+
+        """
+        return self
+
+    def recv(self, x=MTU):
+        if self.mode_tun:
+            data = os.read(self.ins.fileno(), x + 4)
+            proto = struct.unpack('!H', data[2:4])[0]
+            return conf.l3types.get(proto, conf.raw_layer)(data[4:])
+        return conf.l2types.get(1, conf.raw_layer)(
+            os.read(self.ins.fileno(), x)
+        )
+
+    def send(self, x):
+        sx = str(x)
+        if hasattr(x, "sent_time"):
+            x.sent_time = time.time()
+        if self.mode_tun:
+            try:
+                proto = conf.l3types[type(x)]
+            except KeyError:
+                log_runtime.warning(
+                    "Cannot find layer 3 protocol value to send %s in "
+                    "conf.l3types, using 0",
+                    x.name if hasattr(x, "name") else type(x).__name__
+                )
+                proto = 0
+            sx = struct.pack('!HH', 0, proto) + sx
+        try:
+            os.write(self.outs.fileno(), sx)
+        except socket.error:
+            log_runtime.error("%s send", self.__class__.__name__, exc_info=True)
+
+
 if conf.L3socket is None:
     conf.L3socket = L3RawSocket
diff --git a/test/sendsniff.uts b/test/sendsniff.uts
new file mode 100644
index 0000000000000000000000000000000000000000..ba810c48c854245d94cd24bf361b40bf513a7bb3
--- /dev/null
+++ b/test/sendsniff.uts
@@ -0,0 +1,169 @@
+% send, sniff, sr* tests for Scapy
+
+~ netaccess
+
+############
+############
++ Test bridge_and_sniff() using tap sockets
+
+~ tap linux
+
+= Create two tap interfaces
+
+import subprocess
+from threading import Thread
+
+tap0, tap1 = [TunTapInterface("tap%d" % i) for i in range(2)]
+
+if LINUX:
+    for i in range(2):
+        assert subprocess.check_call(["ip", "link", "set", "tap%d" % i, "up"]) == 0
+else:
+    for i in range(2):
+        assert subprocess.check_call(["ifconfig", "tap%d" % i, "up"]) == 0
+
+= Run a sniff thread on the tap1 **interface**
+* It will terminate when 5 IP packets from 1.2.3.4 have been sniffed
+t_sniff = Thread(
+    target=sniff,
+    kwargs={"iface": "tap1", "count": 5, "prn": Packet.summary,
+            "lfilter": lambda p: IP in p and p[IP].src == "1.2.3.4"}
+)
+t_sniff.start()
+
+= Run a bridge_and_sniff thread between the taps **sockets**
+* It will terminate when 5 IP packets from 1.2.3.4 have been forwarded
+t_bridge = Thread(target=bridge_and_sniff, args=(tap0, tap1),
+                  kwargs={"store": False, "count": 5, 'prn': Packet.summary,
+                          "lfilter": lambda p: IP in p and p[IP].src == "1.2.3.4"})
+t_bridge.start()
+
+= Send five IP packets from 1.2.3.4 to the tap0 **interface**
+time.sleep(1)
+sendp([Ether(dst=ETHER_BROADCAST) / IP(src="1.2.3.4") / ICMP()], iface="tap0",
+      count=5)
+
+= Wait for the threads
+t_bridge.join()
+t_sniff.join()
+
+= Run a sniff thread on the tap1 **interface**
+* It will terminate when 5 IP packets from 2.3.4.5 have been sniffed
+t_sniff = Thread(
+    target=sniff,
+    kwargs={"iface": "tap1", "count": 5, "prn": Packet.summary,
+            "lfilter": lambda p: IP in p and p[IP].src == "2.3.4.5"}
+)
+t_sniff.start()
+
+= Run a bridge_and_sniff thread between the taps **sockets**
+* It will "NAT" packets from 1.2.3.4 to 2.3.4.5 and will terminate when 5 IP packets have been forwarded
+def nat_1_2(pkt):
+    if IP in pkt and pkt[IP].src == "1.2.3.4":
+        pkt[IP].src = "2.3.4.5"
+        del pkt[IP].chksum
+        return pkt
+    return False
+
+t_bridge = Thread(target=bridge_and_sniff, args=(tap0, tap1),
+                  kwargs={"store": False, "count": 5, 'prn': Packet.summary,
+                          "xfrm12": nat_1_2,
+                          "lfilter": lambda p: IP in p and p[IP].src == "1.2.3.4"})
+t_bridge.start()
+
+= Send five IP packets from 1.2.3.4 to the tap0 **interface**
+time.sleep(1)
+sendp([Ether(dst=ETHER_BROADCAST) / IP(src="1.2.3.4") / ICMP()], iface="tap0",
+      count=5)
+
+= Wait for the threads
+t_bridge.join()
+t_sniff.join()
+
+= Delete the tap interfaces
+del tap0, tap1
+
+
+############
+############
++ Test bridge_and_sniff() using tun sockets
+
+~ tun linux not_pcapdnet
+
+= Create two tun interfaces
+
+import subprocess
+from threading import Thread
+
+tun0, tun1 = [TunTapInterface("tun%d" % i) for i in range(2)]
+
+if LINUX:
+    for i in range(2):
+        assert subprocess.check_call(["ip", "link", "set", "tun%d" % i, "up"]) == 0
+else:
+    for i in range(2):
+        assert subprocess.check_call(["ifconfig", "tun%d" % i, "up"]) == 0
+
+= Run a sniff thread on the tun1 **interface**
+* It will terminate when 5 IP packets from 1.2.3.4 have been sniffed
+t_sniff = Thread(
+    target=sniff,
+    kwargs={"iface": "tun1", "count": 5, "prn": Packet.summary,
+            "lfilter": lambda p: IP in p and p[IP].src == "1.2.3.4"}
+)
+t_sniff.start()
+
+= Run a bridge_and_sniff thread between the tuns **sockets**
+* It will terminate when 5 IP packets from 1.2.3.4 have been forwarded.
+t_bridge = Thread(target=bridge_and_sniff, args=(tun0, tun1),
+                  kwargs={"store": False, "count": 5, 'prn': Packet.summary,
+                          "xfrm12": lambda pkt: pkt,
+                          "lfilter": lambda p: IP in p and p[IP].src == "1.2.3.4"})
+t_bridge.start()
+
+= Send five IP packets from 1.2.3.4 to the tun0 **interface**
+time.sleep(1)
+conf.route.add(net="1.2.3.4/32", dev="tun0")
+send(IP(src="1.2.3.4", dst="1.2.3.4") / ICMP(), count=5)
+conf.route.delt(net="1.2.3.4/32", dev="tun0")
+
+= Wait for the threads
+t_bridge.join()
+t_sniff.join()
+
+= Run a sniff thread on the tun1 **interface**
+* It will terminate when 5 IP packets from 2.3.4.5 have been sniffed
+t_sniff = Thread(
+    target=sniff,
+    kwargs={"iface": "tun1", "count": 5, "prn": Packet.summary,
+            "lfilter": lambda p: IP in p and p[IP].src == "2.3.4.5"}
+)
+t_sniff.start()
+
+= Run a bridge_and_sniff thread between the tuns **sockets**
+* It will "NAT" packets from 1.2.3.4 to 2.3.4.5 and will terminate when 5 IP packets have been forwarded
+def nat_1_2(pkt):
+    if IP in pkt and pkt[IP].src == "1.2.3.4":
+        pkt[IP].src = "2.3.4.5"
+        del pkt[IP].chksum
+        return pkt
+    return False
+
+t_bridge = Thread(target=bridge_and_sniff, args=(tun0, tun1),
+                  kwargs={"store": False, "count": 5, 'prn': Packet.summary,
+                          "xfrm12": nat_1_2,
+                          "lfilter": lambda p: IP in p and p[IP].src == "1.2.3.4"})
+t_bridge.start()
+
+= Send five IP packets from 1.2.3.4 to the tun0 **interface**
+time.sleep(1)
+conf.route.add(net="1.2.3.4/32", dev="tun0")
+send(IP(src="1.2.3.4", dst="1.2.3.4") / ICMP(), count=5)
+conf.route.delt(net="1.2.3.4/32", dev="tun0")
+
+= Wait for the threads
+t_bridge.join()
+t_sniff.join()
+
+= Delete the tun interfaces
+del tun0, tun1