Skip to content

Linux 802.1Q support #2091

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Jul 10, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 54 additions & 1 deletion scapy/arch/linux.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@


import array
import ctypes
from fcntl import ioctl
import os
from select import select
Expand Down Expand Up @@ -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))
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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)
Expand Down
18 changes: 15 additions & 3 deletions scapy/sendrecv.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 ''))
47 changes: 47 additions & 0 deletions test/linux.uts
Original file line number Diff line number Diff line change
Expand Up @@ -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()