diff --git a/scapy/arch/bpf/supersocket.py b/scapy/arch/bpf/supersocket.py index c4085669cf3..3789c4583b3 100644 --- a/scapy/arch/bpf/supersocket.py +++ b/scapy/arch/bpf/supersocket.py @@ -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: @@ -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: @@ -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) @@ -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 # @@ -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 diff --git a/scapy/arch/libpcap.py b/scapy/arch/libpcap.py index 4d51709cb07..21587452413 100644 --- a/scapy/arch/libpcap.py +++ b/scapy/arch/libpcap.py @@ -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, @@ -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): @@ -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] @@ -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: @@ -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) @@ -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 @@ -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() diff --git a/scapy/arch/linux/__init__.py b/scapy/arch/linux/__init__.py index 7bdb17e1265..86c8e92f826 100644 --- a/scapy/arch/linux/__init__.py +++ b/scapy/arch/linux/__init__.py @@ -57,8 +57,8 @@ # Typing imports from typing import ( Any, - Callable, Dict, + List, NoReturn, Optional, Tuple, @@ -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) @@ -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): """ diff --git a/scapy/layers/inet6.py b/scapy/layers/inet6.py index 694f9345817..18d5f2f2fda 100644 --- a/scapy/layers/inet6.py +++ b/scapy/layers/inet6.py @@ -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: diff --git a/scapy/sendrecv.py b/scapy/sendrecv.py index 437241c4d9b..4e020d70dba 100644 --- a/scapy/sendrecv.py +++ b/scapy/sendrecv.py @@ -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: diff --git a/test/sendsniff.uts b/test/sendsniff.uts index 3c5dcc30a60..45d8b6aef0e 100644 --- a/test/sendsniff.uts +++ b/test/sendsniff.uts @@ -359,3 +359,39 @@ if conf.use_pypy: tap0.close() else: del tap0 + +##### +##### ++ Test sr() on multiple interfaces + += Setup multiple linux interfaces and ranges +~ linux needs_root dbg + +import os +exit_status = os.system("ip netns add blob0") +exit_status |= os.system("ip netns add blob1") +exit_status |= os.system("ip link add name scapy0.0 type veth peer name scapy0.1") +exit_status |= os.system("ip link add name scapy1.0 type veth peer name scapy1.1") +exit_status |= os.system("ip link set scapy0.1 netns blob0 up") +exit_status |= os.system("ip link set scapy1.1 netns blob1 up") +exit_status |= os.system("ip addr add 100.64.2.1/24 dev scapy0.0") +exit_status |= os.system("ip addr add 100.64.3.1/24 dev scapy1.0") +exit_status |= os.system("ip --netns blob0 addr add 100.64.2.2/24 dev scapy0.1") +exit_status |= os.system("ip --netns blob1 addr add 100.64.3.2/24 dev scapy1.1") +exit_status |= os.system("ip link set scapy0.0 up") +exit_status |= os.system("ip link set scapy1.0 up") +assert exit_status == 0 + +conf.ifaces.reload() +conf.route.resync() + +try: + pkts = sr(IP(dst=["100.64.2.2", "100.64.3.2"])/ICMP(), timeout=1)[0] + assert len(pkts) == 2 + assert pkts[0].answer.src in ["100.64.2.2", "100.64.3.2"] + assert pkts[1].answer.src in ["100.64.2.2", "100.64.3.2"] +finally: + e = os.system("ip netns del blob0") + e = os.system("ip netns del blob1") + conf.ifaces.reload() + conf.route.resync()