% 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
if conf.use_pypy:
    # See https://pypy.readthedocs.io/en/latest/cpython_differences.html
    tap0.close()
    tap1.close()
else:
    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
if conf.use_pypy:
    # See https://pypy.readthedocs.io/en/latest/cpython_differences.html
    tun0.close()
    tun1.close()
else:
    del tun0, tun1


############
############
+ Test bridge_and_sniff() using veth pairs
~ linux needs_root veth

= Ensure bridge_and_sniff does not close sockets if data is send within xfrm on ingress interface

with VEthPair('a_0', 'a_1') as veth_0:
    with VEthPair('b_0', 'b_1') as veth_1:
        xfrm_count = {
            'a_0':0,
            'b_0': 0
        }
        def xfrm_x(pkt):
            pkt_tx = pkt.copy()
            ether_lyr = pkt_tx[Ether]
            ether_lyr.type = 0x1234  # we send to peer interface - avoid loop
            # send on receiving interface - triggers return None on recv() in L2Socket
            sendp(pkt_tx, iface=pkt.sniffed_on)
            global xfrm_count
            xfrm_count[pkt.sniffed_on] = xfrm_count[pkt.sniffed_on] + 1
            return True
        t_bridge = Thread(target=bridge_and_sniff,
                          args=('a_0', 'b_0'),
                          kwargs={
                              'xfrm12': xfrm_x,
                              'xfrm21': xfrm_x,
                              'store': False,
                              'count': 4,
                              'lfilter': lambda p: Ether in p and p[Ether].type == 0xbeef})
        t_bridge.start()
        time.sleep(1)
        # send frames in both directions
        for if_name in ['a_1', 'b_1', 'a_1', 'b_1']:
            sendp([Ether(type=0xbeef) /
                   Raw(b'On a scale from one to ten what is your favourite colour of the alphabet?')],
                  iface=if_name)
        t_bridge.join(1)
        # now test of the socket used in bridge_and_sniff() was alive all the time
        assert (xfrm_count['a_0'] == 2)
        assert (xfrm_count['b_0'] == 2)


############
############
+ Test arpleak() using a tap socket

~ tap linux

= Create two tap interfaces

import mock
import struct
import subprocess
from threading import Thread
import time

tap0 = TunTapInterface("tap0")

if LINUX:
    assert subprocess.check_call(["ip", "link", "set", "tap0", "up"]) == 0
else:
    assert subprocess.check_call(["ifconfig", "tap0", "up"]) == 0


def answer_arp_leak(pkt):
    mymac = b"\x00\x01\x02\x03\x04\x06"
    myip = b"\x01\x02\x03\x02"
    if not ARP in pkt:
        return
    e_src = pkt.src
    pkt = raw(pkt[ARP])
    if pkt[:4] != b'\x00\x01\x08\x00':
        print("Invalid ARP")
        return
    hwlen, plen, op = struct.unpack('>BBH', pkt[4:8])
    if op != 1:
        print("Invalid ARP op")
        return
    fmt = ('%ds%ds' % (hwlen, plen)) * 2
    hwsrc, psrc, hwdst, pdst = struct.unpack(fmt,
                                             pkt[8:8 + (plen + hwlen) * 2])
    if pdst[:4] != myip[:plen]:
        print("Invalid ARP pdst %r" % pdst)
        return
    ans = Ether(dst=e_src, src=mymac, type=0x0806)
    ans /= (b'\x00\x01\x08\x00' +
            struct.pack('>BBH' + fmt,
                        hwlen, plen, 2, mymac, myip, hwsrc, psrc))
    tap0.send(ans)
    print('Answered!')

t_answer = Thread(
    target=sniff,
    kwargs={"prn": answer_arp_leak, "timeout": 10, "store": False,
            "opened_socket": tap0}
)

t_answer.start()

@mock.patch("scapy.layers.l2.get_if_addr")
@mock.patch("scapy.layers.l2.get_if_hwaddr")
def test_arpleak(mock_get_if_hwaddr, mock_get_if_addr, hwlen=255, plen=255):
    conf.route.ifadd("tap0", "1.2.3.0/24")
    mock_get_if_addr.side_effect = lambda _: "1.2.3.1"
    mock_get_if_hwaddr.side_effect = lambda _: "00:01:02:03:04:05"
    return arpleak("1.2.3.2/31", timeout=2, hwlen=hwlen, plen=plen)

time.sleep(2)

ans, unans = test_arpleak()
assert len(ans) == 1
assert len(unans) == 1
ans, unans = test_arpleak(hwlen=6)
assert len(ans) == 1
assert len(unans) == 1
ans, unans = test_arpleak(plen=4)
assert len(ans) == 1
assert len(unans) == 1

t_answer.join(15)

if t_answer.isAlive():
    raise Exception("Test timed out")

if conf.use_pypy:
    # See https://pypy.readthedocs.io/en/latest/cpython_differences.html
    tap0.close()
else:
    del tap0
