Skip to content

Commit 05dfc47

Browse files
committed
fixup
1 parent 8777dc1 commit 05dfc47

File tree

8 files changed

+125
-31
lines changed

8 files changed

+125
-31
lines changed

doc/scapy/usage.rst

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,31 @@ Now that we know how to manipulate packets. Let's see how to send them. The send
252252
Sent 1 packets.
253253
<PacketList: TCP:0 UDP:0 ICMP:0 Other:1>
254254

255+
.. _multicast:
256+
257+
Multicast on layer 3: Scope Indentifiers
258+
----------------------------------------
259+
260+
.. index::
261+
single: Multicast
262+
263+
.. note:: This feature is only available since Scapy 2.6.0.
264+
265+
If you try to use multicast addresses (IPv4) or link-local addresses (IPv6), you'll notice that Scapy follows the routing table and takes the first entry. In order to specify which interface to use when looking through the routing table, Scapy supports scope identifiers (similar to RFC6874 but for both IPv6 and IPv4).
266+
267+
.. code:: python
268+
269+
>>> conf.checkIPaddr = False # answer IP will be != from the one we requested
270+
# send on interface 'eth0'
271+
>>> sr(IP(dst="224.0.0.1%eth0")/ICMP(), multi=True)
272+
>>> sr(IPv6(dst="ff02::2%eth0")/ICMPv6EchoRequest(), multi=True)
273+
274+
You can use both ``%eth0`` format or ``%15`` (the interface id) format. You can query those using ``conf.ifaces``.
275+
276+
.. note::
277+
278+
Behind the scene, calling ``IP(dst="224.0.0.1%eth0")`` creates a ``ScopedIP`` object that contains ``224.0.0.1`` on the scope of the interface ``eth0``. If you are using an interface object (for instance ``conf.iface``), you can also craft that object. For instance::
279+
>>> pkt = IP(dst=ScopedIP("224.0.0.1", scope=conf.iface))/ICMP()
255280

256281
Fuzzing
257282
-------

scapy/base_classes.py

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ def __repr__(self):
109109
return "<SetGen %r>" % self.values
110110

111111

112-
class _NetIP(str):
112+
class _ScopedIP(str):
113113
"""
114114
An str that also holds extra attributes.
115115
"""
@@ -119,21 +119,21 @@ def __init__(self, _: str) -> None:
119119
self.scope = None
120120

121121
def __repr__(self) -> str:
122-
val = super(_NetIP, self).__repr__()
122+
val = super(_ScopedIP, self).__repr__()
123123
if self.scope is not None:
124-
return "NetIP(%s, scope=%s)" % (val, repr(self.scope))
124+
return "ScopedIP(%s, scope=%s)" % (val, repr(self.scope))
125125
return val
126126

127127

128-
def NetIP(net: str, scope: Optional[Any] = None) -> _NetIP:
128+
def ScopedIP(net: str, scope: Optional[Any] = None) -> _ScopedIP:
129129
"""
130130
An str that also holds extra attributes.
131131
132132
Examples::
133133
134-
>>> NetIP("224.0.0.1%eth0") # interface 'eth0'
135-
>>> NetIP("224.0.0.1%1") # interface index 1
136-
>>> NetIP("224.0.0.1", scope=conf.iface)
134+
>>> ScopedIP("224.0.0.1%eth0") # interface 'eth0'
135+
>>> ScopedIP("224.0.0.1%1") # interface index 1
136+
>>> ScopedIP("224.0.0.1", scope=conf.iface)
137137
"""
138138
if "%" in net:
139139
try:
@@ -152,7 +152,7 @@ def NetIP(net: str, scope: Optional[Any] = None) -> _NetIP:
152152
"valid interface !" % scope
153153
)
154154
scope = network_name(iface)
155-
x = _NetIP(net)
155+
x = _ScopedIP(net)
156156
x.scope = scope
157157
return x
158158

@@ -217,7 +217,8 @@ def __init__(self, net, stop=None, scope=None):
217217
self.__class__.__name__)
218218
self.scope = None
219219
if "%" in net:
220-
net = NetIP(net)
220+
net = ScopedIP(net)
221+
if isinstance(net, _ScopedIP):
221222
self.scope = net.scope
222223
if stop is None:
223224
try:
@@ -245,7 +246,7 @@ def __iter__(self):
245246
# type: () -> Iterator[str]
246247
# Python 2 won't handle huge (> sys.maxint) values in range()
247248
for i in range(self.count):
248-
yield NetIP(
249+
yield ScopedIP(
249250
self.int2ip(self.start + i),
250251
scope=self.scope,
251252
)
@@ -261,7 +262,7 @@ def __iterlen__(self):
261262

262263
def choice(self):
263264
# type: () -> str
264-
return NetIP(
265+
return ScopedIP(
265266
self.int2ip(random.randint(self.start, self.stop)),
266267
scope=self.scope,
267268
)

scapy/fields.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -38,11 +38,11 @@
3838
from scapy.utils6 import in6_6to4ExtractAddr, in6_isaddr6to4, \
3939
in6_isaddrTeredo, in6_ptop, Net6, teredoAddrExtractInfo
4040
from scapy.base_classes import (
41-
_NetIP,
41+
_ScopedIP,
4242
BasePacket,
4343
Field_metaclass,
4444
Net,
45-
NetIP,
45+
ScopedIP,
4646
)
4747

4848
# Typing imports
@@ -854,7 +854,7 @@ def h2i(self, pkt, x):
854854
if isinstance(x, bytes):
855855
x = plain_str(x) # type: ignore
856856
if isinstance(x, str):
857-
x = NetIP(x)
857+
x = ScopedIP(x)
858858
try:
859859
inet_aton(x)
860860
except socket.error:
@@ -899,7 +899,7 @@ def any2i(self, pkt, x):
899899

900900
def i2repr(self, pkt, x):
901901
# type: (Optional[Packet], Union[str, Net]) -> str
902-
if isinstance(x, _NetIP) and x.scope:
902+
if isinstance(x, _ScopedIP) and x.scope:
903903
return repr(x)
904904
r = self.resolve(self.i2h(pkt, x))
905905
return r if isinstance(r, str) else repr(r)
@@ -944,8 +944,9 @@ def h2i(self, pkt, x):
944944
if isinstance(x, bytes):
945945
x = plain_str(x)
946946
if isinstance(x, str):
947+
x = ScopedIP(x)
947948
try:
948-
x = NetIP(in6_ptop(x))
949+
x = ScopedIP(in6_ptop(x), scope=x.scope)
949950
except socket.error:
950951
return Net6(x) # type: ignore
951952
elif isinstance(x, tuple):
@@ -985,7 +986,7 @@ def i2repr(self, pkt, x):
985986
elif in6_isaddr6to4(x): # print encapsulated address
986987
vaddr = in6_6to4ExtractAddr(x)
987988
return "%s [6to4 GW: %s]" % (self.i2h(pkt, x), vaddr)
988-
elif isinstance(x, _NetIP) and x.scope:
989+
elif isinstance(x, _ScopedIP) and x.scope:
989990
return repr(x)
990991
r = self.i2h(pkt, x) # No specific information to return
991992
return r if isinstance(r, str) else repr(r)

scapy/layers/inet.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
from scapy.utils import checksum, do_graph, incremental_label, \
1919
linehexdump, strxor, whois, colgen
2020
from scapy.ansmachine import AnsweringMachine
21-
from scapy.base_classes import Gen, Net, _NetIP
21+
from scapy.base_classes import Gen, Net, _ScopedIP
2222
from scapy.data import ETH_P_IP, ETH_P_ALL, DLT_RAW, DLT_RAW_ALT, DLT_IPV4, \
2323
IP_PROTOS, TCP_SERVICES, UDP_SERVICES
2424
from scapy.layers.l2 import (
@@ -570,7 +570,7 @@ def extract_padding(self, s):
570570
def route(self):
571571
dst = self.dst
572572
scope = None
573-
if isinstance(dst, (Net, _NetIP)):
573+
if isinstance(dst, (Net, _ScopedIP)):
574574
scope = dst.scope
575575
if isinstance(dst, (Gen, list)):
576576
dst = next(iter(dst))

scapy/layers/inet6.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020

2121
from scapy.arch import get_if_hwaddr
2222
from scapy.as_resolvers import AS_resolver_riswhois
23-
from scapy.base_classes import Gen, _NetIP
23+
from scapy.base_classes import Gen, _ScopedIP
2424
from scapy.compat import chb, orb, raw, plain_str, bytes_encode
2525
from scapy.consts import WINDOWS
2626
from scapy.config import conf
@@ -319,7 +319,7 @@ def route(self):
319319
"""Used to select the L2 address"""
320320
dst = self.dst
321321
scope = None
322-
if isinstance(dst, (Net6, _NetIP)):
322+
if isinstance(dst, (Net6, _ScopedIP)):
323323
scope = dst.scope
324324
if isinstance(dst, (Gen, list)):
325325
dst = next(iter(dst))

scapy/layers/l2.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414

1515
from scapy.ansmachine import AnsweringMachine
1616
from scapy.arch import get_if_addr, get_if_hwaddr
17-
from scapy.base_classes import Gen, Net, _NetIP
17+
from scapy.base_classes import Gen, Net, _ScopedIP
1818
from scapy.compat import chb
1919
from scapy.config import conf
2020
from scapy import consts
@@ -564,7 +564,7 @@ def route(self):
564564
self.getfield_and_val("pdst"))
565565
fld_inner, dst = fld._find_fld_pkt_val(self, dst)
566566
scope = None
567-
if isinstance(dst, (Net, _NetIP)):
567+
if isinstance(dst, (Net, _ScopedIP)):
568568
scope = dst.scope
569569
if isinstance(dst, Gen):
570570
dst = next(iter(dst))

scapy/sendrecv.py

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -475,7 +475,11 @@ def send(x, # type: _PacketIterable
475475
"""
476476
if "iface" in kargs:
477477
# Warn that it isn't used.
478-
warnings.warn("'iface' has no effect on L3 I/O send().", SyntaxWarning)
478+
warnings.warn(
479+
"'iface' has no effect on L3 I/O send(). For multicast/link-local "
480+
"see https://scapy.readthedocs.io/en/latest/usage.html#multicast",
481+
SyntaxWarning,
482+
)
479483
del kargs["iface"]
480484
iface, ipv6 = _interface_selection(x)
481485
return _send(
@@ -691,7 +695,11 @@ def sr(x, # type: _PacketIterable
691695
"""
692696
if "iface" in kargs:
693697
# Warn that it isn't used.
694-
warnings.warn("'iface' has no effect on L3 I/O sr().", SyntaxWarning)
698+
warnings.warn(
699+
"'iface' has no effect on L3 I/O sr(). For multicast/link-local "
700+
"see https://scapy.readthedocs.io/en/latest/usage.html#multicast",
701+
SyntaxWarning,
702+
)
695703
del kargs["iface"]
696704
iface, ipv6 = _interface_selection(x)
697705
s = iface.l3socket(ipv6)(
@@ -714,7 +722,11 @@ def sr1(*args, **kargs):
714722
"""
715723
if "iface" in kargs:
716724
# Warn that it isn't used.
717-
warnings.warn("'iface' has no effect on L3 I/O sr1().", SyntaxWarning)
725+
warnings.warn(
726+
"'iface' has no effect on L3 I/O sr1(). For multicast/link-local "
727+
"see https://scapy.readthedocs.io/en/latest/usage.html#multicast",
728+
SyntaxWarning,
729+
)
718730
del kargs["iface"]
719731
ans, _ = sr(*args, **kargs)
720732
if ans:
@@ -949,7 +961,11 @@ def srflood(x, # type: _PacketIterable
949961
"""
950962
if "iface" in kargs:
951963
# Warn that it isn't used.
952-
warnings.warn("'iface' has no effect on L3 I/O srflood().", SyntaxWarning)
964+
warnings.warn(
965+
"'iface' has no effect on L3 I/O srflood(). For multicast/link-local "
966+
"see https://scapy.readthedocs.io/en/latest/usage.html#multicast",
967+
SyntaxWarning,
968+
)
953969
del kargs["iface"]
954970
iface, ipv6 = _interface_selection(x)
955971
s = iface.l3socket(ipv6)(
@@ -983,7 +999,11 @@ def sr1flood(x, # type: _PacketIterable
983999
"""
9841000
if "iface" in kargs:
9851001
# Warn that it isn't used.
986-
warnings.warn("'iface' has no effect on L3 I/O sr1flood().", SyntaxWarning)
1002+
warnings.warn(
1003+
"'iface' has no effect on L3 I/O sr1flood(). For multicast/link-local "
1004+
"see https://scapy.readthedocs.io/en/latest/usage.html#multicast",
1005+
SyntaxWarning,
1006+
)
9871007
del kargs["iface"]
9881008
iface, ipv6 = _interface_selection(x)
9891009
s = iface.l3socket(ipv6)(

test/linux.uts

Lines changed: 50 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,53 @@ if exit_status == 0:
6363
else:
6464
assert True
6565

66+
67+
= Test scoped interface addresses
68+
~ linux needs_root
69+
70+
import os
71+
exit_status = os.system("ip link add name scapy0 type dummy")
72+
exit_status = os.system("ip link add name scapy1 type dummy")
73+
exit_status |= os.system("ip addr add 192.0.2.1/24 dev scapy0")
74+
exit_status |= os.system("ip addr add 192.0.3.1/24 dev scapy1")
75+
exit_status |= os.system("ip link set scapy0 address 00:01:02:03:04:05 multicast on up")
76+
exit_status |= os.system("ip link set scapy1 address 06:07:08:09:10:11 multicast on up")
77+
assert exit_status == 0
78+
79+
conf.ifaces.reload()
80+
conf.route.resync()
81+
conf.route6.resync()
82+
83+
conf.route6
84+
85+
try:
86+
# IPv4
87+
a = Ether()/IP(dst="224.0.0.1%scapy0")
88+
assert a[Ether].src == "00:01:02:03:04:05"
89+
assert a[IP].src == "192.0.2.1"
90+
b = Ether()/IP(dst="224.0.0.1%scapy1")
91+
assert b[Ether].src == "06:07:08:09:10:11"
92+
assert b[IP].src == "192.0.3.1"
93+
c = Ether()/IP(dst="224.0.0.1/24%scapy1")
94+
assert c[Ether].src == "06:07:08:09:10:11"
95+
assert c[IP].src == "192.0.3.1"
96+
# IPv6
97+
a = Ether()/IPv6(dst="ff02::fb%scapy0")
98+
assert a[Ether].src == "00:01:02:03:04:05"
99+
assert a[IPv6].src == "fe80::201:2ff:fe03:405"
100+
b = Ether()/IPv6(dst="ff02::fb%scapy1")
101+
assert b[Ether].src == "06:07:08:09:10:11"
102+
assert b[IPv6].src == "fe80::407:8ff:fe09:1011"
103+
c = Ether()/IPv6(dst="ff02::fb/30%scapy1")
104+
assert c[Ether].src == "06:07:08:09:10:11"
105+
assert c[IPv6].src == "fe80::407:8ff:fe09:1011"
106+
finally:
107+
exit_status = os.system("ip link del scapy0")
108+
exit_status = os.system("ip link del scapy1")
109+
conf.ifaces.reload()
110+
conf.route.resync()
111+
conf.route6.resync()
112+
66113
= catch loopback device missing
67114
~ linux needs_root
68115

@@ -309,16 +356,16 @@ assert test_L3PacketSocket_sendto_python3()
309356

310357
import os
311358
from scapy.sendrecv import _interface_selection
312-
assert _interface_selection(None, IP(dst="8.8.8.8")/UDP()) == (conf.iface, False)
359+
assert _interface_selection(IP(dst="8.8.8.8")/UDP()) == (conf.iface, False)
313360
exit_status = os.system("ip link add name scapy0 type dummy")
314361
exit_status = os.system("ip addr add 192.0.2.1/24 dev scapy0")
315362
exit_status = os.system("ip addr add fc00::/24 dev scapy0")
316363
exit_status = os.system("ip link set scapy0 up")
317364
conf.ifaces.reload()
318365
conf.route.resync()
319366
conf.route6.resync()
320-
assert _interface_selection(None, IP(dst="192.0.2.42")/UDP()) == ("scapy0", False)
321-
assert _interface_selection(None, IPv6(dst="fc00::ae0d")/UDP()) == ("scapy0", True)
367+
assert _interface_selection(IP(dst="192.0.2.42")/UDP()) == ("scapy0", False)
368+
assert _interface_selection(IPv6(dst="fc00::ae0d")/UDP()) == ("scapy0", True)
322369
exit_status = os.system("ip link del name dev scapy0")
323370
conf.ifaces.reload()
324371
conf.route.resync()

0 commit comments

Comments
 (0)