diff --git a/scapy/arch/linux.py b/scapy/arch/linux.py index fec9ab2ce09..50eebc7e981 100644 --- a/scapy/arch/linux.py +++ b/scapy/arch/linux.py @@ -11,6 +11,7 @@ import array +import ctypes from fcntl import ioctl import os from select import select @@ -88,9 +89,28 @@ PACKET_LOOPBACK = 5 # MC/BRD frame looped back PACKET_USER = 6 # To user space PACKET_KERNEL = 7 # To kernel space +PACKET_AUXDATA = 8 PACKET_FASTROUTE = 6 # Fastrouted frame # Unused, PACKET_FASTROUTE and PACKET_LOOPBACK are invisible to user space +# Used to get VLAN data +ETH_P_8021Q = 0x8100 +TP_STATUS_VLAN_VALID = 1 << 4 + + +class tpacket_auxdata(ctypes.Structure): + _fields_ = [ + ("tp_status", ctypes.c_uint), + ("tp_len", ctypes.c_uint), + ("tp_snaplen", ctypes.c_uint), + ("tp_mac", ctypes.c_ushort), + ("tp_net", ctypes.c_ushort), + ("tp_vlan_tci", ctypes.c_ushort), + ("tp_padding", ctypes.c_ushort), + ] + + +# Utils def get_if_raw_hwaddr(iff): return struct.unpack("16xh6s8x", get_if(iff, SIOCGIFHWADDR)) @@ -465,6 +485,9 @@ def __init__(self, iface=None, type=ETH_P_ALL, promisc=None, filter=None, socket.SO_RCVBUF, conf.bufsize ) + if not six.PY2: + # Receive Auxiliary Data (VLAN tags) + self.ins.setsockopt(SOL_PACKET, PACKET_AUXDATA, 1) if isinstance(self, L2ListenSocket): self.outs = None else: @@ -493,9 +516,39 @@ def close(self): set_promisc(self.ins, self.iface, 0) SuperSocket.close(self) + if six.PY2: + def _recv_raw(self, sock, x): + """Internal function to receive a Packet""" + pkt, sa_ll = sock.recvfrom(x) + return pkt, sa_ll + else: + def _recv_raw(self, sock, x): + """Internal function to receive a Packet, + and process ancillary data. + """ + flags_len = socket.CMSG_LEN(4096) + pkt, ancdata, flags, sa_ll = sock.recvmsg(x, flags_len) + if not pkt: + return pkt, sa_ll + for cmsg_lvl, cmsg_type, cmsg_data in ancdata: + # Check available ancillary data + if (cmsg_lvl == SOL_PACKET and cmsg_type == PACKET_AUXDATA): + # Parse AUXDATA + auxdata = tpacket_auxdata.from_buffer_copy(cmsg_data) + if auxdata.tp_vlan_tci != 0 or \ + auxdata.tp_status & TP_STATUS_VLAN_VALID: + # Insert VLAN tag + tag = struct.pack( + "!HH", + ETH_P_8021Q, + auxdata.tp_vlan_tci + ) + pkt = pkt[:12] + tag + pkt[12:] + return pkt, sa_ll + def recv_raw(self, x=MTU): """Receives a packet, then returns a tuple containing (cls, pkt_data, time)""" # noqa: E501 - pkt, sa_ll = self.ins.recvfrom(x) + pkt, sa_ll = self._recv_raw(self.ins, x) if self.outs and sa_ll[2] == socket.PACKET_OUTGOING: return None, None, None ts = get_last_packet_timestamp(self.ins) diff --git a/scapy/sendrecv.py b/scapy/sendrecv.py index 2d357ab53b1..c1dd6c95aba 100644 --- a/scapy/sendrecv.py +++ b/scapy/sendrecv.py @@ -1060,12 +1060,24 @@ def prn(pkt): @conf.commands.register def tshark(*args, **kargs): - """Sniff packets and print them calling pkt.summary(), a bit like text wireshark""" # noqa: E501 - print("Capturing on '" + str(kargs.get('iface') if 'iface' in kargs else conf.iface) + "'") # noqa: E501 - i = [0] # This should be a nonlocal variable, using a mutable object for Python 2 compatibility # noqa: E501 + """Sniff packets and print them calling pkt.summary(). + This tries to replicate what text-wireshark (tshark) would look like""" + + if 'iface' in kargs: + iface = kargs.get('iface') + elif 'opened_socket' in kargs: + iface = kargs.get('opened_socket').iface + else: + iface = conf.iface + print("Capturing on '%s'" % iface) + + # This should be a nonlocal variable, using a mutable object + # for Python 2 compatibility + i = [0] def _cb(pkt): print("%5d\t%s" % (i[0], pkt.summary())) i[0] += 1 + sniff(prn=_cb, store=False, *args, **kargs) print("\n%d packet%s captured" % (i[0], 's' if i[0] > 1 else '')) diff --git a/test/linux.uts b/test/linux.uts index f8d1a14f16f..bd671059c24 100644 --- a/test/linux.uts +++ b/test/linux.uts @@ -329,3 +329,50 @@ exit_status = os.system("ip addr add 192.0.2.1/24 dev scapy0") exit_status = os.system("ip link set scapy0 up") assert _interface_selection(None, IP(dst="192.0.2.42")/UDP()) == "scapy0" exit_status = os.system("ip link del name dev scapy0") + += Test 802.Q sniffing +~ linux needs_root python3_only + +from threading import Thread, Condition + +veth = VEthPair("left0", "right0") +veth.setup() +veth.up() +exit_status = os.system("ip link add link right0 name vlanright0 type vlan id 42") +exit_status = os.system("ip link add link left0 name vlanleft0 type vlan id 42") +exit_status = os.system("ip link set vlanright0 up") +exit_status = os.system("ip link set vlanleft0 up") +exit_status = os.system("ip addr add 198.51.100.1/24 dev vlanleft0") +exit_status = os.system("ip addr add 198.51.100.2/24 dev vlanright0") + +cond_started = Condition() + +def _sniffer_started(): + + global cond_started + cond_started.acquire() + cond_started.notify() + cond_started.release() + +cond_started.acquire() + +dot1q_count = 0 + +def _sniffer(): + sniffed = sniff(iface="right0", + lfilter=lambda p: Dot1Q in p, + count=2, + timeout=5, + started_callback=_sniffer_started) + global dot1q_count + dot1q_count = len(sniffed) + +t_sniffer = Thread(target=_sniffer) +t_sniffer.start() +cond_started.wait() +sendp(Ether()/IP(dst="198.51.100.2")/ICMP(), iface='vlanleft0', count=2) + +t_sniffer.join(1) +assert(dot1q_count == 2) + +veth.destroy()