Skip to content

Commit 1faafa8

Browse files
committed
Fix sr() on multiple interfaces
1 parent 420173c commit 1faafa8

File tree

5 files changed

+169
-40
lines changed

5 files changed

+169
-40
lines changed

scapy/arch/bpf/supersocket.py

Lines changed: 41 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ def __init__(self,
115115
)
116116

117117
self.fd_flags = None # type: Optional[int]
118-
self.assigned_interface = None
118+
self.type = type
119119

120120
# SuperSocket mandatory variables
121121
if promisc is None:
@@ -155,7 +155,6 @@ def __init__(self,
155155
)
156156
except IOError:
157157
raise Scapy_Exception("BIOCSETIF failed on %s" % self.iface)
158-
self.assigned_interface = self.iface
159158

160159
# Set the interface into promiscuous
161160
if self.promisc:
@@ -466,6 +465,25 @@ def nonblock_recv(self):
466465

467466
class L3bpfSocket(L2bpfSocket):
468467

468+
def __init__(self,
469+
iface=None, # type: Optional[_GlobInterfaceType]
470+
type=ETH_P_ALL, # type: int
471+
promisc=None, # type: Optional[bool]
472+
filter=None, # type: Optional[str]
473+
nofilter=0, # type: int
474+
monitor=False, # type: bool
475+
):
476+
super(L3bpfSocket, self).__init__(
477+
iface=iface,
478+
type=type,
479+
promisc=promisc,
480+
filter=filter,
481+
nofilter=nofilter,
482+
monitor=monitor,
483+
)
484+
self.filter = filter
485+
self.send_socks = {network_name(self.iface): self}
486+
469487
def recv(self, x: int = BPF_BUFFER_LENGTH, **kwargs: Any) -> Optional['Packet']:
470488
"""Receive on layer 3"""
471489
r = SuperSocket.recv(self, x, **kwargs)
@@ -485,12 +503,14 @@ def send(self, pkt):
485503
iff = network_name(conf.iface)
486504

487505
# Assign the network interface to the BPF handle
488-
if self.assigned_interface != iff:
489-
try:
490-
fcntl.ioctl(self.bpf_fd, BIOCSETIF, struct.pack("16s16x", iff.encode())) # noqa: E501
491-
except IOError:
492-
raise Scapy_Exception("BIOCSETIF failed on %s" % iff)
493-
self.assigned_interface = iff
506+
if iff not in self.send_socks:
507+
self.send_socks[iff] = L3bpfSocket(
508+
iface=iff,
509+
type=self.type,
510+
filter=self.filter,
511+
promisc=self.promisc,
512+
)
513+
fd = self.send_socks[iff]
494514

495515
# Build the frame
496516
#
@@ -529,12 +549,23 @@ def send(self, pkt):
529549
warning("Cannot write to %s according to the documentation!", iff)
530550
return
531551
else:
532-
frame = self.guessed_cls() / pkt
552+
frame = fd.guessed_cls() / pkt
533553

534554
pkt.sent_time = time.time()
535555

536556
# Send the frame
537-
return L2bpfSocket.send(self, frame)
557+
return L2bpfSocket.send(fd, frame)
558+
559+
@staticmethod
560+
def select(sockets, remain=None):
561+
# type: (List[SuperSocket], Optional[float]) -> List[SuperSocket]
562+
socks = [] # type: List[SuperSocket]
563+
for sock in sockets:
564+
if isinstance(sock, L3bpfSocket):
565+
socks += sock.send_socks.values()
566+
else:
567+
socks.append(sock)
568+
return L2bpfSocket.select(socks, remain=remain)
538569

539570

540571
# Sockets manipulation functions

scapy/arch/libpcap.py

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -540,6 +540,7 @@ def __init__(self,
540540
if iface is None:
541541
iface = conf.iface
542542
self.iface = iface
543+
self.type = type
543544
if promisc is not None:
544545
self.promisc = promisc
545546
else:
@@ -580,6 +581,7 @@ def __init__(self,
580581
filter = "(ether proto %i) and (%s)" % (type, filter)
581582
else:
582583
filter = "ether proto %i" % type
584+
self.filter = filter
583585
if filter:
584586
self.pcap_fd.setfilter(filter)
585587

@@ -598,7 +600,7 @@ class L3pcapSocket(L2pcapSocket):
598600
def __init__(self, *args, **kwargs):
599601
# type: (*Any, **Any) -> None
600602
super(L3pcapSocket, self).__init__(*args, **kwargs)
601-
self.send_pcap_fds = {network_name(self.iface): self.pcap_fd}
603+
self.send_socks = {network_name(self.iface): self}
602604

603605
def recv(self, x=MTU, **kwargs):
604606
# type: (int, **Any) -> Optional[Packet]
@@ -614,15 +616,15 @@ def send(self, x):
614616
iff = x.route()[0]
615617
if iff is None:
616618
iff = network_name(conf.iface)
617-
if iff not in self.send_pcap_fds:
618-
self.send_pcap_fds[iff] = fd = open_pcap(
619-
device=iff,
620-
snaplen=0,
621-
promisc=False,
622-
to_ms=0,
619+
if iff not in self.send_socks:
620+
self.send_socks[iff] = L3pcapSocket(
621+
iface=iff,
622+
type=self.type,
623+
filter=self.filter,
624+
promisc=self.promisc,
625+
monitor=self.monitor,
623626
)
624-
else:
625-
fd = self.send_pcap_fds[iff]
627+
fd = self.send_socks[iff].pcap_fd
626628
# Now send.
627629
sx = raw(self.cls() / x)
628630
try:
@@ -631,11 +633,22 @@ def send(self, x):
631633
pass
632634
return fd.send(sx)
633635

636+
@staticmethod
637+
def select(sockets, remain=None):
638+
# type: (List[SuperSocket], Optional[float]) -> List[SuperSocket]
639+
socks = [] # type: List[SuperSocket]
640+
for sock in sockets:
641+
if isinstance(sock, L3pcapSocket):
642+
socks += sock.send_socks.values()
643+
else:
644+
socks.append(sock)
645+
return L2pcapSocket.select(socks, remain=remain)
646+
634647
def close(self):
635648
# type: () -> None
636649
if self.closed:
637650
return
638651
super(L3pcapSocket, self).close()
639-
for fd in self.send_pcap_fds.values():
640-
if fd is not self.pcap_fd:
652+
for fd in self.send_socks.values():
653+
if fd is not self:
641654
fd.close()

scapy/arch/linux/__init__.py

Lines changed: 67 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,8 @@
5757
# Typing imports
5858
from typing import (
5959
Any,
60-
Callable,
6160
Dict,
61+
List,
6262
NoReturn,
6363
Optional,
6464
Tuple,
@@ -322,6 +322,26 @@ def send(self, x):
322322
class L3PacketSocket(L2Socket):
323323
desc = "read/write packets at layer 3 using Linux PF_PACKET sockets"
324324

325+
def __init__(self,
326+
iface=None, # type: Optional[Union[str, NetworkInterface]]
327+
type=ETH_P_ALL, # type: int
328+
promisc=None, # type: Optional[Any]
329+
filter=None, # type: Optional[Any]
330+
nofilter=0, # type: int
331+
monitor=None, # type: Optional[Any]
332+
):
333+
self.send_socks = {}
334+
super(L3PacketSocket, self).__init__(
335+
iface=iface,
336+
type=type,
337+
promisc=promisc,
338+
filter=filter,
339+
nofilter=nofilter,
340+
monitor=monitor,
341+
)
342+
self.filter = filter
343+
self.send_socks = {network_name(self.iface): self}
344+
325345
def recv(self, x=MTU, **kwargs):
326346
# type: (int, **Any) -> Optional[Packet]
327347
pkt = SuperSocket.recv(self, x, **kwargs)
@@ -332,39 +352,68 @@ def recv(self, x=MTU, **kwargs):
332352

333353
def send(self, x):
334354
# type: (Packet) -> int
355+
# Select the file descriptor to send the packet on.
335356
iff = x.route()[0]
336357
if iff is None:
337358
iff = network_name(conf.iface)
338-
sdto = (iff, self.type)
339-
self.outs.bind(sdto)
340-
sn = self.outs.getsockname()
341-
ll = lambda x: x # type: Callable[[Packet], Packet]
342359
type_x = type(x)
343-
if type_x in conf.l3types:
344-
sdto = (iff, conf.l3types.layer2num[type_x])
345-
if sn[3] in conf.l2types:
346-
ll = lambda x: conf.l2types.num2layer[sn[3]]() / x
347-
if self.lvl == 3 and not issubclass(self.LL, type_x):
348-
warning("Incompatible L3 types detected using %s instead of %s !",
349-
type_x, self.LL)
350-
self.LL = type_x
351-
sx = raw(ll(x))
352-
x.sent_time = time.time()
360+
if iff not in self.send_socks:
361+
self.send_socks[iff] = L3PacketSocket(
362+
iface=iff,
363+
type=conf.l3types.layer2num.get(type_x, self.type),
364+
filter=self.filter,
365+
promisc=self.promisc,
366+
)
367+
fd = self.send_socks[iff].outs
368+
if self.lvl == 3:
369+
if not issubclass(self.LL, type_x):
370+
warning("Incompatible L3 types detected using %s instead of %s !",
371+
type_x, self.LL)
372+
self.LL = type_x
373+
if self.lvl == 2:
374+
sx = bytes(self.LL() / x)
375+
else:
376+
sx = bytes(x)
377+
# Now send.
378+
try:
379+
x.sent_time = time.time()
380+
except AttributeError:
381+
pass
353382
try:
354-
return self.outs.sendto(sx, sdto)
383+
return fd.send(sx)
355384
except socket.error as msg:
356385
if msg.errno == 22 and len(sx) < conf.min_pkt_size:
357-
return self.outs.send(
386+
return fd.send(
358387
sx + b"\x00" * (conf.min_pkt_size - len(sx))
359388
)
360389
elif conf.auto_fragment and msg.errno == 90:
361390
i = 0
362391
for p in x.fragment():
363-
i += self.outs.sendto(raw(ll(p)), sdto)
392+
i += fd.send(bytes(self.LL() / p))
364393
return i
365394
else:
366395
raise
367396

397+
@staticmethod
398+
def select(sockets, remain=None):
399+
# type: (List[SuperSocket], Optional[float]) -> List[SuperSocket]
400+
socks = [] # type: List[SuperSocket]
401+
for sock in sockets:
402+
if isinstance(sock, L3PacketSocket):
403+
socks += sock.send_socks.values()
404+
else:
405+
socks.append(sock)
406+
return L2Socket.select(socks, remain=remain)
407+
408+
def close(self):
409+
# type: () -> None
410+
if self.closed:
411+
return
412+
super(L3PacketSocket, self).close()
413+
for fd in self.send_socks.values():
414+
if fd is not self:
415+
fd.close()
416+
368417

369418
class VEthPair(object):
370419
"""

scapy/sendrecv.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1285,7 +1285,7 @@ def stop_cb():
12851285
for p in packets:
12861286
if lfilter and not lfilter(p):
12871287
continue
1288-
p.sniffed_on = sniff_sockets[s]
1288+
p.sniffed_on = sniff_sockets.get(s, None)
12891289
# post-processing
12901290
self.count += 1
12911291
if store:

test/sendsniff.uts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -359,3 +359,39 @@ if conf.use_pypy:
359359
tap0.close()
360360
else:
361361
del tap0
362+
363+
#####
364+
#####
365+
+ Test sr() on multiple interfaces
366+
367+
= Setup multiple linux interfaces and ranges
368+
~ linux needs_root dbg
369+
370+
import os
371+
exit_status = os.system("ip netns add blob0")
372+
exit_status |= os.system("ip netns add blob1")
373+
exit_status |= os.system("ip link add name scapy0.0 type veth peer name scapy0.1")
374+
exit_status |= os.system("ip link add name scapy1.0 type veth peer name scapy1.1")
375+
exit_status |= os.system("ip link set scapy0.1 netns blob0 up")
376+
exit_status |= os.system("ip link set scapy1.1 netns blob1 up")
377+
exit_status |= os.system("ip addr add 100.64.2.1/24 dev scapy0.0")
378+
exit_status |= os.system("ip addr add 100.64.3.1/24 dev scapy1.0")
379+
exit_status |= os.system("ip --netns blob0 addr add 100.64.2.2/24 dev scapy0.1")
380+
exit_status |= os.system("ip --netns blob1 addr add 100.64.3.2/24 dev scapy1.1")
381+
exit_status |= os.system("ip link set scapy0.0 up")
382+
exit_status |= os.system("ip link set scapy1.0 up")
383+
assert exit_status == 0
384+
385+
conf.ifaces.reload()
386+
conf.route.resync()
387+
388+
try:
389+
pkts = sr(IP(dst=["100.64.2.2", "100.64.3.2"])/ICMP(), timeout=1)[0]
390+
assert len(pkts) == 2
391+
assert pkts[0].answer.src in ["100.64.2.2", "100.64.3.2"]
392+
assert pkts[1].answer.src in ["100.64.2.2", "100.64.3.2"]
393+
finally:
394+
e = os.system("ip netns del blob0")
395+
e = os.system("ip netns del blob1")
396+
conf.ifaces.reload()
397+
conf.route.resync()

0 commit comments

Comments
 (0)