Skip to content
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
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):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we use a generic select()method instead of duplicating it in all sockets?

Copy link
Member Author

@gpotter2 gpotter2 Jul 28, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure if that's clearer, considering the types change in each function.
We would also require a dependency to another L3Socket or something, and I think sockets are already abstracted enough

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A classmethod could do the trick. What do you think?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sounds like a LOT of refactoring work that I honnestly don't really want to do :(

# 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