Skip to content

Commit

Permalink
Fix sr() on multiple interfaces (#4474)
Browse files Browse the repository at this point in the history
  • Loading branch information
gpotter2 authored Jul 30, 2024
1 parent 7233cb6 commit 891e44d
Show file tree
Hide file tree
Showing 6 changed files with 201 additions and 47 deletions.
51 changes: 41 additions & 10 deletions scapy/arch/bpf/supersocket.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ def __init__(self,
)

self.fd_flags = None # type: Optional[int]
self.assigned_interface = None
self.type = type

# SuperSocket mandatory variables
if promisc is None:
Expand Down Expand Up @@ -155,7 +155,6 @@ def __init__(self,
)
except IOError:
raise Scapy_Exception("BIOCSETIF failed on %s" % self.iface)
self.assigned_interface = self.iface

# Set the interface into promiscuous
if self.promisc:
Expand Down Expand Up @@ -466,6 +465,25 @@ def nonblock_recv(self):

class L3bpfSocket(L2bpfSocket):

def __init__(self,
iface=None, # type: Optional[_GlobInterfaceType]
type=ETH_P_ALL, # type: int
promisc=None, # type: Optional[bool]
filter=None, # type: Optional[str]
nofilter=0, # type: int
monitor=False, # type: bool
):
super(L3bpfSocket, self).__init__(
iface=iface,
type=type,
promisc=promisc,
filter=filter,
nofilter=nofilter,
monitor=monitor,
)
self.filter = filter
self.send_socks = {network_name(self.iface): self}

def recv(self, x: int = BPF_BUFFER_LENGTH, **kwargs: Any) -> Optional['Packet']:
"""Receive on layer 3"""
r = SuperSocket.recv(self, x, **kwargs)
Expand All @@ -485,12 +503,14 @@ def send(self, pkt):
iff = network_name(conf.iface)

# Assign the network interface to the BPF handle
if self.assigned_interface != iff:
try:
fcntl.ioctl(self.bpf_fd, BIOCSETIF, struct.pack("16s16x", iff.encode())) # noqa: E501
except IOError:
raise Scapy_Exception("BIOCSETIF failed on %s" % iff)
self.assigned_interface = iff
if iff not in self.send_socks:
self.send_socks[iff] = L3bpfSocket(
iface=iff,
type=self.type,
filter=self.filter,
promisc=self.promisc,
)
fd = self.send_socks[iff]

# Build the frame
#
Expand Down Expand Up @@ -529,12 +549,23 @@ def send(self, pkt):
warning("Cannot write to %s according to the documentation!", iff)
return
else:
frame = self.guessed_cls() / pkt
frame = fd.guessed_cls() / pkt

pkt.sent_time = time.time()

# Send the frame
return L2bpfSocket.send(self, frame)
return L2bpfSocket.send(fd, frame)

@staticmethod
def select(sockets, remain=None):
# type: (List[SuperSocket], Optional[float]) -> List[SuperSocket]
socks = [] # type: List[SuperSocket]
for sock in sockets:
if isinstance(sock, L3bpfSocket):
socks += sock.send_socks.values()
else:
socks.append(sock)
return L2bpfSocket.select(socks, remain=remain)


# Sockets manipulation functions
Expand Down
71 changes: 53 additions & 18 deletions scapy/arch/libpcap.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,12 @@
from scapy.compat import raw, plain_str
from scapy.config import conf
from scapy.consts import WINDOWS
from scapy.data import MTU, ETH_P_ALL
from scapy.data import (
DLT_RAW_ALT,
DLT_RAW,
ETH_P_ALL,
MTU,
)
from scapy.error import (
Scapy_Exception,
log_loading,
Expand Down Expand Up @@ -78,20 +83,27 @@


class _L2libpcapSocket(SuperSocket):
__slots__ = ["pcap_fd"]
__slots__ = ["pcap_fd", "lvl"]

def __init__(self, fd):
# type: (_PcapWrapper_libpcap) -> None
self.pcap_fd = fd
ll = self.pcap_fd.datalink()
if ll in conf.l2types:
self.cls = conf.l2types[ll]
self.LL = conf.l2types[ll]
if ll in [
DLT_RAW,
DLT_RAW_ALT,
]:
self.lvl = 3
else:
self.lvl = 2
else:
self.cls = conf.default_l2
self.LL = conf.default_l2
warning(
"Unable to guess datalink type "
"(interface=%s linktype=%i). Using %s",
self.iface, ll, self.cls.name
self.iface, ll, self.LL.name
)

def recv_raw(self, x=MTU):
Expand All @@ -103,7 +115,7 @@ def recv_raw(self, x=MTU):
ts, pkt = self.pcap_fd.next()
if pkt is None:
return None, None, None
return self.cls, pkt, ts
return self.LL, pkt, ts

def nonblock_recv(self, x=MTU):
# type: (int) -> Optional[Packet]
Expand Down Expand Up @@ -540,6 +552,7 @@ def __init__(self,
if iface is None:
iface = conf.iface
self.iface = iface
self.type = type
if promisc is not None:
self.promisc = promisc
else:
Expand Down Expand Up @@ -580,6 +593,7 @@ def __init__(self,
filter = "(ether proto %i) and (%s)" % (type, filter)
else:
filter = "ether proto %i" % type
self.filter = filter
if filter:
self.pcap_fd.setfilter(filter)

Expand All @@ -598,12 +612,12 @@ class L3pcapSocket(L2pcapSocket):
def __init__(self, *args, **kwargs):
# type: (*Any, **Any) -> None
super(L3pcapSocket, self).__init__(*args, **kwargs)
self.send_pcap_fds = {network_name(self.iface): self.pcap_fd}
self.send_socks = {network_name(self.iface): self}

def recv(self, x=MTU, **kwargs):
# type: (int, **Any) -> Optional[Packet]
r = L2pcapSocket.recv(self, x, **kwargs)
if r:
if r and self.lvl == 2:
r.payload.time = r.time
return r.payload
return r
Expand All @@ -614,28 +628,49 @@ def send(self, x):
iff = x.route()[0]
if iff is None:
iff = network_name(conf.iface)
if iff not in self.send_pcap_fds:
self.send_pcap_fds[iff] = fd = open_pcap(
device=iff,
snaplen=0,
promisc=False,
to_ms=0,
type_x = type(x)
if iff not in self.send_socks:
self.send_socks[iff] = L3pcapSocket(
iface=iff,
type=self.type,
filter=self.filter,
promisc=self.promisc,
monitor=self.monitor,
)
sock = self.send_socks[iff]
fd = sock.pcap_fd
if sock.lvl == 3:
if not issubclass(sock.LL, type_x):
warning("Incompatible L3 types detected using %s instead of %s !",
type_x, sock.LL)
sock.LL = type_x
if sock.lvl == 2:
sx = bytes(sock.LL() / x)
else:
fd = self.send_pcap_fds[iff]
sx = bytes(x)
# Now send.
sx = raw(self.cls() / x)
try:
x.sent_time = time.time()
except AttributeError:
pass
return fd.send(sx)

@staticmethod
def select(sockets, remain=None):
# type: (List[SuperSocket], Optional[float]) -> List[SuperSocket]
socks = [] # type: List[SuperSocket]
for sock in sockets:
if isinstance(sock, L3pcapSocket):
socks += sock.send_socks.values()
else:
socks.append(sock)
return L2pcapSocket.select(socks, remain=remain)

def close(self):
# type: () -> None
if self.closed:
return
super(L3pcapSocket, self).close()
for fd in self.send_pcap_fds.values():
if fd is not self.pcap_fd:
for fd in self.send_socks.values():
if fd is not self:
fd.close()
86 changes: 68 additions & 18 deletions scapy/arch/linux/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,8 @@
# Typing imports
from typing import (
Any,
Callable,
Dict,
List,
NoReturn,
Optional,
Tuple,
Expand Down Expand Up @@ -322,6 +322,26 @@ def send(self, x):
class L3PacketSocket(L2Socket):
desc = "read/write packets at layer 3 using Linux PF_PACKET sockets"

def __init__(self,
iface=None, # type: Optional[Union[str, NetworkInterface]]
type=ETH_P_ALL, # type: int
promisc=None, # type: Optional[Any]
filter=None, # type: Optional[Any]
nofilter=0, # type: int
monitor=None, # type: Optional[Any]
):
self.send_socks = {}
super(L3PacketSocket, self).__init__(
iface=iface,
type=type,
promisc=promisc,
filter=filter,
nofilter=nofilter,
monitor=monitor,
)
self.filter = filter
self.send_socks = {network_name(self.iface): self}

def recv(self, x=MTU, **kwargs):
# type: (int, **Any) -> Optional[Packet]
pkt = SuperSocket.recv(self, x, **kwargs)
Expand All @@ -332,39 +352,69 @@ def recv(self, x=MTU, **kwargs):

def send(self, x):
# type: (Packet) -> int
# Select the file descriptor to send the packet on.
iff = x.route()[0]
if iff is None:
iff = network_name(conf.iface)
sdto = (iff, self.type)
self.outs.bind(sdto)
sn = self.outs.getsockname()
ll = lambda x: x # type: Callable[[Packet], Packet]
type_x = type(x)
if type_x in conf.l3types:
sdto = (iff, conf.l3types.layer2num[type_x])
if sn[3] in conf.l2types:
ll = lambda x: conf.l2types.num2layer[sn[3]]() / x
if self.lvl == 3 and not issubclass(self.LL, type_x):
warning("Incompatible L3 types detected using %s instead of %s !",
type_x, self.LL)
self.LL = type_x
sx = raw(ll(x))
x.sent_time = time.time()
if iff not in self.send_socks:
self.send_socks[iff] = L3PacketSocket(
iface=iff,
type=conf.l3types.layer2num.get(type_x, self.type),
filter=self.filter,
promisc=self.promisc,
)
sock = self.send_socks[iff]
fd = sock.outs
if sock.lvl == 3:
if not issubclass(sock.LL, type_x):
warning("Incompatible L3 types detected using %s instead of %s !",
type_x, sock.LL)
sock.LL = type_x
if sock.lvl == 2:
sx = bytes(sock.LL() / x)
else:
sx = bytes(x)
# Now send.
try:
x.sent_time = time.time()
except AttributeError:
pass
try:
return self.outs.sendto(sx, sdto)
return fd.send(sx)
except socket.error as msg:
if msg.errno == 22 and len(sx) < conf.min_pkt_size:
return self.outs.send(
return fd.send(
sx + b"\x00" * (conf.min_pkt_size - len(sx))
)
elif conf.auto_fragment and msg.errno == 90:
i = 0
for p in x.fragment():
i += self.outs.sendto(raw(ll(p)), sdto)
i += fd.send(bytes(self.LL() / p))
return i
else:
raise

@staticmethod
def select(sockets, remain=None):
# type: (List[SuperSocket], Optional[float]) -> List[SuperSocket]
socks = [] # type: List[SuperSocket]
for sock in sockets:
if isinstance(sock, L3PacketSocket):
socks += sock.send_socks.values()
else:
socks.append(sock)
return L2Socket.select(socks, remain=remain)

def close(self):
# type: () -> None
if self.closed:
return
super(L3PacketSocket, self).close()
for fd in self.send_socks.values():
if fd is not self:
fd.close()


class VEthPair(object):
"""
Expand Down
2 changes: 2 additions & 0 deletions scapy/layers/inet6.py
Original file line number Diff line number Diff line change
Expand Up @@ -492,6 +492,8 @@ class IPv46(IP, IPv6):
This class implements a dispatcher that is used to detect the IP version
while parsing Raw IP pcap files.
"""
name = "IPv4/6"

@classmethod
def dispatch_hook(cls, _pkt=None, *_, **kargs):
if _pkt:
Expand Down
2 changes: 1 addition & 1 deletion scapy/sendrecv.py
Original file line number Diff line number Diff line change
Expand Up @@ -1285,7 +1285,7 @@ def stop_cb():
for p in packets:
if lfilter and not lfilter(p):
continue
p.sniffed_on = sniff_sockets[s]
p.sniffed_on = sniff_sockets.get(s, None)
# post-processing
self.count += 1
if store:
Expand Down
Loading

0 comments on commit 891e44d

Please sign in to comment.