Scapy - sending IPv6 Router Advertisement failing with lifetime > 0
<p>I am attempting to create a tool using Scapy to discover link-local IPv6 hosts by sending a fake router advertisement to the FF02::1 multicast address.</p>

<pre><code>*SNIP*
router_advertisement = scapy.IPv6(src=ra_src_addr, dst='FF02::1')/scapy.ICMPv6ND_RA(routerlifetime=0, reachabletime=0)/scapy.ICMPv6NDOptSrcLLAddr(lladdr=hw_addr)/scapy.ICMPv6NDOptPrefixInfo(prefixlen=64, validlifetime=0x6, preferredlifetime=0x6, prefix='dead::')
answer, unanswer = scapy.sr(router_advertisement, timeout=10, multi=True)

for reply in answer:
    print(reply[1][scapy.Ether].src + ' : ' + reply[1]scapy.IPv6].src)
</code></pre>

<p>Everything above the snip is mostly setting up the parameters of the router advertisement (ra_prefix, hw_addr, etc). I've put the full script on pastebin to avoid cluttering the question: <a href="http://pastebin.com/4Q3JheXh" rel="nofollow">http://pastebin.com/4Q3JheXh</a></p>

<p>The problem with the above is that while Scapy is successfully sending the router advertisement packet, and I'm seeing neighbor solicitation responses, Scapy is exiting in error before I can view answers with sr().</p>

<p>Full output:</p>

<pre><code>    WARNING: No route found for IPv6 destination :: (no default route?)                                                                                                    
    Begin emission:                                                                                                                                                        
Finished to send 1 packets.
...Traceback (most recent call last):
  File "find_ipv6_local.py", line 40, in &lt;module&gt;
    answer, unanswer = scapy.sr(router_advertisement, timeout=10, multi=True)
  File "/usr/lib/python2.7/site-packages/scapy/sendrecv.py", line 317, in sr
    a,b=sndrcv(s,x,*args,**kargs)
  File "/usr/lib/python2.7/site-packages/scapy/sendrecv.py", line 141, in sndrcv
    h = r.hashret()
  File "/usr/lib/python2.7/site-packages/scapy/layers/inet6.py", line 423, in hashret
    return struct.pack("B", nh)+self.payload.hashret()
  File "/usr/lib/python2.7/site-packages/scapy/packet.py", line 711, in hashret
    return self.payload.hashret()
  File "/usr/lib/python2.7/site-packages/scapy/layers/inet6.py", line 1317, in hashret
    return struct.pack("HH",self.mladdr)+self.payload.hashret()
struct.error: pack expected 2 items for packing (got 1)
</code></pre>

<p>Interestingly, when I set <strong>validlifetime</strong> and <strong>preferredlifetime</strong> to 0, Scapy does not crash and burn. However, this is less than helpful, as a lifetime of 0 does not get me any responses.</p>

<p>Have I screwed up somewhere in the script, or is Scapy a little <em>off</em> when it comes to IPv6?</p>

<p>I would argue that <code>scapy</code> is a little dodgy in this respect.  Your trackeback here:</p>

<pre><code>return struct.pack("HH",self.mladdr)+self.payload.hashret()
</code></pre>

<p>Should contain the following:</p>

<pre><code>return struct.pack("HH", 0, 0)+""
</code></pre>

<p>That <code>struct.pack</code> has no chance of working, it at least should be:</p>

<pre><code>return struct.pack("HH", *self.mladdr)+self.payload.hashnet()
</code></pre>

<p>So that <code>struct.pack</code> has no chance of receiving the two arguments needed for "HH".  That <strong>is</strong> a bug.</p>

<p>Since you do not care about the payload you can change the scapy code for <code>ICMPv6MLQuery</code> from:</p>

<pre><code>class ICMPv6MLQuery(_ICMPv6ML): # RFC 2710
    name = "MLD - Multicast Listener Query"
    type   = 130
    mrd    = 10000
    mladdr = "::" # 10s for mrd
    overload_fields = {IPv6: { "dst": "ff02::1", "hlim": 1, "nh": 58 }} 
    def hashret(self):
        if self.mladdr != "::":
            return struct.pack("HH",self.mladdr)+self.payload.hashret()
        else:
            return self.payload.hashret()
</code></pre>

<p>To</p>

<pre><code>class ICMPv6MLQuery(_ICMPv6ML): # RFC 2710
    name = "MLD - Multicast Listener Query"
    type   = 130
    mrd    = 10000
    mladdr = "::" # 10s for mrd
    overload_fields = {IPv6: { "dst": "ff02::1", "hlim": 1, "nh": 58 }} 
    def hashret(self):
        return self.payload.hashret()
</code></pre>

<p>i.e. kill the <code>struct.pack</code> altogether.</p>

<hr>

<p>On another note: this:</p>

<pre><code>WARNING: No route found for IPv6 destination :: (no default route?)
</code></pre>

<p>May prevent you from receiving an answer (the NIC may decide that nothing could have arrived and ignore it). It is wise to configure at least some trivial route, e.g.</p>

<pre><code>$ route -6
Kernel IPv6 routing table
Destination               Next Hop                   Flag Met Ref Use If
::/0                      [::]                       U    256 0     0 eth0
</code></pre>

python networking ipv6 scapy