#
# $Id: ICMPv6.pm,v bc01789674fd 2019/05/23 05:51:45 gomor $
#
package Net::Frame::Layer::ICMPv6;
use strict; use warnings;

our $VERSION = '1.11';

use Net::Frame::Layer qw(:consts :subs);
use Exporter;
our @ISA = qw(Net::Frame::Layer Exporter);

our %EXPORT_TAGS = (
   consts => [qw(
      NF_ICMPv6_CODE_ZERO
      NF_ICMPv6_TYPE_DESTUNREACH
      NF_ICMPv6_CODE_NOROUTE
      NF_ICMPv6_CODE_ADMINPROHIBITED
      NF_ICMPv6_CODE_NOTASSIGNED
      NF_ICMPv6_CODE_ADDRESSUNREACH
      NF_ICMPv6_CODE_PORTUNREACH
      NF_ICMPv6_CODE_FAILPOLICY
      NF_ICMPv6_CODE_REJECTROUTE
      NF_ICMPv6_TYPE_TOOBIG
      NF_ICMPv6_TYPE_TIMEEXCEED
      NF_ICMPv6_CODE_HOPLIMITEXCEED
      NF_ICMPv6_CODE_FRAGREASSEMBLYEXCEEDED
      NF_ICMPv6_TYPE_PARAMETERPROBLEM
      NF_ICMPv6_CODE_ERRONEOUSHERDERFIELD
      NF_ICMPv6_CODE_UNKNOWNNEXTHEADER
      NF_ICMPv6_CODE_UNKNOWNOPTION
      NF_ICMPv6_TYPE_ECHO_REQUEST
      NF_ICMPv6_TYPE_ECHO_REPLY
      NF_ICMPv6_TYPE_MLDQUERY
      NF_ICMPv6_TYPE_MLDREPORTv1
      NF_ICMPv6_TYPE_MLDDONE
      NF_ICMPv6_TYPE_ROUTERSOLICITATION
      NF_ICMPv6_TYPE_ROUTERADVERTISEMENT
      NF_ICMPv6_TYPE_NEIGHBORSOLICITATION
      NF_ICMPv6_TYPE_NEIGHBORADVERTISEMENT
      NF_ICMPv6_TYPE_MLDREPORTv2
      NF_ICMPv6_OPTION_SOURCELINKLAYERADDRESS
      NF_ICMPv6_OPTION_TARGETLINKLAYERADDRESS
      NF_ICMPv6_OPTION_PREFIXINFORMATION
      NF_ICMPv6_OPTION_REDIRECTEDHEADER
      NF_ICMPv6_OPTION_MTU
      NF_ICMPv6_FLAG_ROUTER
      NF_ICMPv6_FLAG_SOLICITED
      NF_ICMPv6_FLAG_OVERRIDE
      NF_ICMPv6_FLAG_MANAGEDADDRESSCONFIGURATION
      NF_ICMPv6_FLAG_OTHERCONFIGURATION
   )],
);
our @EXPORT_OK = (
   @{$EXPORT_TAGS{consts}},
);

use constant NF_ICMPv6_CODE_ZERO                    => 0;
use constant NF_ICMPv6_TYPE_DESTUNREACH             => 1;
use constant NF_ICMPv6_CODE_NOROUTE                 => 0;
use constant NF_ICMPv6_CODE_ADMINPROHIBITED         => 1;
use constant NF_ICMPv6_CODE_NOTASSIGNED             => 2;
use constant NF_ICMPv6_CODE_ADDRESSUNREACH          => 3;
use constant NF_ICMPv6_CODE_PORTUNREACH             => 4;
use constant NF_ICMPv6_CODE_FAILPOLICY              => 5;
use constant NF_ICMPv6_CODE_REJECTROUTE             => 6;
use constant NF_ICMPv6_TYPE_TOOBIG                  => 2;
use constant NF_ICMPv6_TYPE_TIMEEXCEED              => 3;
use constant NF_ICMPv6_CODE_HOPLIMITEXCEED          => 0;
use constant NF_ICMPv6_CODE_FRAGREASSEMBLYEXCEEDED  => 1;
use constant NF_ICMPv6_TYPE_PARAMETERPROBLEM        => 4;
use constant NF_ICMPv6_CODE_ERRONEOUSHERDERFIELD    => 0;
use constant NF_ICMPv6_CODE_UNKNOWNNEXTHEADER       => 1;
use constant NF_ICMPv6_CODE_UNKNOWNOPTION           => 2;
use constant NF_ICMPv6_TYPE_ECHO_REQUEST            => 128;
use constant NF_ICMPv6_TYPE_ECHO_REPLY              => 129;
use constant NF_ICMPv6_TYPE_MLDQUERY                => 130;
use constant NF_ICMPv6_TYPE_MLDREPORTv1             => 131;
use constant NF_ICMPv6_TYPE_MLDDONE                 => 132;
use constant NF_ICMPv6_TYPE_ROUTERSOLICITATION      => 133;
use constant NF_ICMPv6_TYPE_ROUTERADVERTISEMENT     => 134;
use constant NF_ICMPv6_TYPE_NEIGHBORSOLICITATION    => 135;
use constant NF_ICMPv6_TYPE_NEIGHBORADVERTISEMENT   => 136;
use constant NF_ICMPv6_TYPE_MLDREPORTv2             => 143;

use constant NF_ICMPv6_OPTION_SOURCELINKLAYERADDRESS => 0x01;
use constant NF_ICMPv6_OPTION_TARGETLINKLAYERADDRESS => 0x02;
use constant NF_ICMPv6_OPTION_PREFIXINFORMATION      => 0x03;
use constant NF_ICMPv6_OPTION_REDIRECTEDHEADER       => 0x04;
use constant NF_ICMPv6_OPTION_MTU                    => 0x05;

use constant NF_ICMPv6_FLAG_ROUTER    => 0x04;
use constant NF_ICMPv6_FLAG_SOLICITED => 0x02;
use constant NF_ICMPv6_FLAG_OVERRIDE  => 0x01;

use constant NF_ICMPv6_FLAG_MANAGEDADDRESSCONFIGURATION => 1 << 5;
use constant NF_ICMPv6_FLAG_OTHERCONFIGURATION          => 1 << 4;
use constant NF_ICMPv6_FLAG_MOBILEIPv6HOMEAGENT         => 1 << 3;
use constant NF_ICMPv6_FLAG_ROUTERSELECTIONPREFHIGH     => 1 << 1; # 01b
use constant NF_ICMPv6_FLAG_ROUTERSELECTIONPREFMEDIUM   => 0;      # 00b
use constant NF_ICMPv6_FLAG_ROUTERSELECTIONPREFLOW      => 3 << 1; # 11b
use constant NF_ICMPv6_FLAG_ROUTERSELECTIONPREFRESERVED => 2 << 1; # 10b
use constant NF_ICMPv6_FLAG_NEIGHBORDISCOVERYPROXY      => 1;

our @AS = qw(
   type
   code
   checksum
);
__PACKAGE__->cgBuildIndices;
__PACKAGE__->cgBuildAccessorsScalar(\@AS);

use Bit::Vector;

sub new {
   shift->SUPER::new(
      type     => NF_ICMPv6_TYPE_ECHO_REQUEST,
      code     => NF_ICMPv6_CODE_ZERO,
      checksum => 0,
      @_,
   );
}

# XXX: may be better, by keying on type also
sub getKey        { shift->layer }
sub getKeyReverse { shift->layer }

sub match {
   my $self = shift;
   my ($with) = @_;
   my $sType = $self->type;
   my $wType = $with->type;
   if ($sType eq NF_ICMPv6_TYPE_ECHO_REQUEST
   &&  $wType eq NF_ICMPv6_TYPE_ECHO_REPLY) {
      return 1;
   }
   elsif ($sType eq NF_ICMPv6_TYPE_NEIGHBORSOLICITATION
      &&  $wType eq NF_ICMPv6_TYPE_NEIGHBORADVERTISEMENT) {
      return 1;
   }
   elsif ($sType eq NF_ICMPv6_TYPE_ROUTERSOLICITATION
      &&  $wType eq NF_ICMPv6_TYPE_ROUTERADVERTISEMENT) {
      return 1;
   }
   elsif  ($sType eq NF_ICMPv6_TYPE_MLDQUERY
      && (($wType eq NF_ICMPv6_TYPE_MLDREPORTv1)
      ||  ($wType eq NF_ICMPv6_TYPE_MLDREPORTv2)) ) {
      return 1;
   }
   return 0;
}

sub getLength { 4 }

sub pack {
   my $self = shift;

   my $raw = $self->SUPER::pack('CCn',
      $self->type, $self->code, $self->checksum,
   ) or return;

   return $self->raw($raw);
}

sub unpack {
   my $self = shift;

   my ($type, $code, $checksum, $payload) =
      $self->SUPER::unpack('CCn a*', $self->raw)
         or return;

   $self->type($type);
   $self->code($code);
   $self->checksum($checksum);
   $self->payload($payload);

   return $self;
}

sub computeChecksums {
   my $self = shift;
   my ($layers) = @_;

   my $ip;
   my $rh0;
   my $hbh; # Hop-by-hop Ext Hdr
   my $dst; # Destination Ext Hdr
   my $mob; # Mobility Ext Hdr
   my $lastNextHeader;
   my $fragmentFlag = 0;
   my $start   = 0;
   my $last    = $self;
   my $payload = '';
   for my $l (@$layers) {
      if (! $ip       && $l->layer eq 'IPv6')              { $ip       = $l; }
      if (! $rh0      && $l->layer eq 'IPv6::Routing')     { $rh0      = $l; }
      if (! $hbh      && $l->layer eq 'IPv6::HopByHop')    { $hbh      = $l; }
      if (! $dst      && $l->layer eq 'IPv6::Destination') { $dst      = $l; }
      if (! $mob      && $l->layer eq 'IPv6::Mobility')    { $mob      = $l; }

      if ($l->can('nextHeader')) { $lastNextHeader = $l->nextHeader; }

      if ($l->layer eq 'IPv6::Fragment') { $fragmentFlag = 1; }

      $last = $l;
      if (! $start) {
         $start++ if $l->layer eq 'ICMPv6';
         next;
      }
      $payload .= $l->pack;
   }

   my $lastIpDst       = $ip->dst;
   my $ipPayloadLength = $ip->payloadLength;
   # If RH0, need to set $ip->dst to last $rh0->addresses
   # unless segmentsLeft == 0 (RFC 2460 sec 8.1)
   if ($rh0 && $rh0->segmentsLeft != 0) {
      for ($rh0->addresses) {
         $lastIpDst = $_;
      }
      # Pseudo header length is upper layer minus any EH (RFC 2460 sec 8.1)
      $ipPayloadLength -= $rh0->getLength;
   }
   # Pseudo header length is upper layer minus any EH (RFC 2460 sec 8.1)
   if ($fragmentFlag) {
      $ipPayloadLength -= 8; # 8 = length of fragment EH
   }
   if ($hbh) {
      $ipPayloadLength -= $hbh->getLength;
   }
   if ($dst) {
      $ipPayloadLength -= $dst->getLength;
   }
   if ($mob) {
      $ipPayloadLength -= $mob->getLength;
   }

   # Build pseudo-header and pack ICMPv6 packet
   my $zero       = Bit::Vector->new_Dec(24, 0);
   my $nextHeader = Bit::Vector->new_Dec( 8, $lastNextHeader);
   my $v32        = $zero->Concat_List($nextHeader);

   my $packed = $self->SUPER::pack('a*a*NNCCn',
      inet6Aton($ip->src), inet6Aton($lastIpDst), $ipPayloadLength,
      $v32->to_Dec, $self->type, $self->code, 0
   ) or return;

   if (defined($last->payload) && length($last->payload)) {
      $payload .= $last->payload;
   }
   if (length($payload)) {
      $packed .= $self->SUPER::pack('a*', $payload)
         or return;
   }

   $self->checksum(inetChecksum($packed));

   return 1;
}

sub encapsulate {
   my $self = shift;

   return $self->nextLayer if $self->nextLayer;

   if ($self->payload) {
      my $type = $self->type;
#     if ($type eq NF_ICMPv6_TYPE_REDIRECT) {
#        return 'IPv6';
#     }
      if ($type eq NF_ICMPv6_TYPE_ECHO_REQUEST
      ||  $type eq NF_ICMPv6_TYPE_ECHO_REPLY) {
         return 'ICMPv6::Echo';
      }
      elsif ($type eq NF_ICMPv6_TYPE_NEIGHBORSOLICITATION) {
         return 'ICMPv6::NeighborSolicitation';
      }
      elsif ($type eq NF_ICMPv6_TYPE_NEIGHBORADVERTISEMENT) {
         return 'ICMPv6::NeighborAdvertisement';
      }
      elsif ($type eq NF_ICMPv6_TYPE_ROUTERSOLICITATION) {
         return 'ICMPv6::RouterSolicitation';
      }
      elsif ($type eq NF_ICMPv6_TYPE_ROUTERADVERTISEMENT) {
         return 'ICMPv6::RouterAdvertisement';
      }
      elsif ($type eq NF_ICMPv6_TYPE_DESTUNREACH) {
         return 'ICMPv6::DestUnreach';
      }
      elsif ($type eq NF_ICMPv6_TYPE_TIMEEXCEED) {
         return 'ICMPv6::TimeExceed';
      }
      elsif ($type eq NF_ICMPv6_TYPE_TOOBIG) {
         return 'ICMPv6::TooBig';
      }
      elsif ($type eq NF_ICMPv6_TYPE_PARAMETERPROBLEM) {
         return 'ICMPv6::ParameterProblem';
      }
      elsif ($type eq NF_ICMPv6_TYPE_MLDQUERY
         ||  $type eq NF_ICMPv6_TYPE_MLDREPORTv1
         ||  $type eq NF_ICMPv6_TYPE_MLDDONE) {
         return 'ICMPv6::MLD';
      }
      elsif ($type eq NF_ICMPv6_TYPE_MLDREPORTv2) {
         return 'ICMPv6::MLD::Report';
      }
   }

   return NF_LAYER_NONE;
}

sub print {
   my $self = shift;

   my $l = $self->layer;
   my $buf = sprintf "$l: type:%d  code:%d  checksum:0x%04x",
      $self->type, $self->code, $self->checksum;

   return $buf;
}

1;

__END__

=head1 NAME

Net::Frame::Layer::ICMPv6 - Internet Control Message Protocol v6 layer object

=head1 SYNOPSIS

   use Net::Frame::Simple;
   use Net::Frame::Layer::ICMPv6 qw(:consts);

   my $icmp = Net::Frame::Layer::ICMPv6->new(
      type     => NF_ICMPv6_TYPE_ECHO_REQUEST,
      code     => NF_ICMPv6_CODE_ZERO,
      checksum => 0,
   );

   # Build an ICMPv6 echo-request
   use Net::Frame::Layer::ICMPv6::Echo;
   my $echo = Net::Frame::Layer::ICMPv6::Echo->new(payload => 'echo');

   my $echoReq = Net::Frame::Simple->new(layers => [ $icmp, $echo ]);
   print $echoReq->print."\n";

   # Build an ICMPv6 neighbor-solicitation
   use Net::Frame::Layer::ICMPv6::NeighborSolicitation;
   my $solicit = Net::Frame::Layer::ICMPv6::NeighborSolicitation->new(
      targetAddress => $targetIpv6Address,
   );
   $icmp->type(NF_ICMPv6_TYPE_NEIGHBORSOLICITATION);

   my $nsReq = Net::Frame::Simple->new(layers => [ $icmp, $solicit ]);
   print $nsReq->print."\n";

   #
   # Read a raw layer
   #

   my $layer = Net::Frame::Layer::ICMPv6->new(raw => $raw);

   print $layer->print."\n";
   print 'PAYLOAD: '.unpack('H*', $layer->payload)."\n"
      if $layer->payload;

=head1 DESCRIPTION

This modules implements the encoding and decoding of the ICMPv6 layer.

RFC: http://www.rfc-editor.org/rfc/rfc4861.txt

RFC: http://www.rfc-editor.org/rfc/rfc4389.txt

RFC: http://www.rfc-editor.org/rfc/rfc4191.txt

RFC: http://www.rfc-editor.org/rfc/rfc3775.txt

RFC: http://www.rfc-editor.org/rfc/rfc2463.txt

RFC: http://www.rfc-editor.org/rfc/rfc2461.txt

RFC: http://www.rfc-editor.org/rfc/rfc2460.txt

See also B<Net::Frame::Layer> for other attributes and methods.

=head1 ATTRIBUTES

=over 4

=item B<type>

=item B<code>

Type and code fields. See B<CONSTANTS>.

=item B<checksum>

The checksum of ICMPv6 header.

=back

The following are inherited attributes. See B<Net::Frame::Layer> for more information.

=over 4

=item B<raw>

=item B<payload>

=item B<nextLayer>

=back

=head1 METHODS

=over 4

=item B<new>

=item B<new> (hash)

Object constructor. You can pass attributes that will overwrite default ones. See B<SYNOPSIS> for default values.

=item B<computeChecksums>

Computes the ICMPv6 checksum.

=item B<getKey>

=item B<getKeyReverse>

These two methods are basically used to increase the speed when using B<recv> method from B<Net::Frame::Simple>. Usually, you write them when you need to write B<match> method.

=item B<match> (Net::Frame::Layer::ICMPv6 object)

This method is mostly used internally. You pass a B<Net::Frame::Layer::ICMPv6> layer as a parameter, and it returns true if this is a response corresponding for the request, or returns false if not.

=back

The following are inherited methods. Some of them may be overridden in this layer, and some others may not be meaningful in this layer. See B<Net::Frame::Layer> for more information.

=over 4

=item B<layer>

=item B<computeLengths>

=item B<computeChecksums>

=item B<pack>

=item B<unpack>

=item B<encapsulate>

=item B<getLength>

=item B<getPayloadLength>

=item B<print>

=item B<dump>

=back

=head1 CONSTANTS

Load them: use Net::Frame::Layer::ICMPv6 qw(:consts);

Various types and codes for ICMPv6 header.

=over 4

=item B<NF_ICMPv6_CODE_ZERO>

=item B<NF_ICMPv6_TYPE_DESTUNREACH>

=item B<NF_ICMPv6_CODE_NOROUTE>

=item B<NF_ICMPv6_CODE_ADMINPROHIBITED>

=item B<NF_ICMPv6_CODE_NOTASSIGNED>

=item B<NF_ICMPv6_CODE_ADDRESSUNREACH>

=item B<NF_ICMPv6_CODE_PORTUNREACH>

=item B<NF_ICMPv6_CODE_FAILPOLICY>

=item B<NF_ICMPv6_CODE_REJECTROUTE>

=item B<NF_ICMPv6_TYPE_TOOBIG>

=item B<NF_ICMPv6_TYPE_TIMEEXCEED>

=item B<NF_ICMPv6_CODE_HOPLIMITEXCEED>

=item B<NF_ICMPv6_CODE_FRAGREASSEMBLYEXCEEDED>

=item B<NF_ICMPv6_TYPE_PARAMETERPROBLEM>

=item B<NF_ICMPv6_CODE_ERRONEOUSHERDERFIELD>

=item B<NF_ICMPv6_CODE_UNKNOWNNEXTHEADER>

=item B<NF_ICMPv6_CODE_UNKNOWNOPTION>

=item B<NF_ICMPv6_TYPE_ECHO_REQUEST>

=item B<NF_ICMPv6_TYPE_ECHO_REPLY>

=item B<NF_ICMPv6_TYPE_MLDQUERY>

=item B<NF_ICMPv6_TYPE_MLDREPORTv1>

=item B<NF_ICMPv6_TYPE_MLDDONE>

=item B<NF_ICMPv6_TYPE_ROUTERSOLICITATION>

=item B<NF_ICMPv6_TYPE_ROUTERADVERTISEMENT>

=item B<NF_ICMPv6_TYPE_NEIGHBORSOLICITATION>

=item B<NF_ICMPv6_TYPE_NEIGHBORADVERTISEMENT>

=item B<NF_ICMPv6_TYPE_MLDREPORTv2>

=item B<NF_ICMPv6_OPTION_SOURCELINKLAYERADDRESS>

=item B<NF_ICMPv6_OPTION_TARGETLINKLAYERADDRESS>

=item B<NF_ICMPv6_OPTION_PREFIXINFORMATION>

=item B<NF_ICMPv6_OPTION_REDIRECTEDHEADER>

=item B<NF_ICMPv6_OPTION_MTU>

=back

Various flags for some ICMPv6 messages.

=over 4

=item B<NF_ICMPv6_FLAG_ROUTER>

=item B<NF_ICMPv6_FLAG_SOLICITED>

=item B<NF_ICMPv6_FLAG_OVERRIDE>

=item B<NF_ICMPv6_FLAG_MANAGEDADDRESSCONFIGURATION>

=item B<NF_ICMPv6_FLAG_OTHERCONFIGURATION>

=item B<NF_ICMPv6_FLAG_MOBILEIPv6HOMEAGENT>

=item B<NF_ICMPv6_FLAG_ROUTERSELECTIONPREFHIGH>

=item B<NF_ICMPv6_FLAG_ROUTERSELECTIONPREFMEDIUM>

=item B<NF_ICMPv6_FLAG_ROUTERSELECTIONPREFLOW>

=item B<NF_ICMPv6_FLAG_ROUTERSELECTIONPREFRESERVED>

=item B<NF_ICMPv6_FLAG_NEIGHBORDISCOVERYPROXY>

=back

=head1 SEE ALSO

L<Net::Frame::Layer>

=head1 AUTHOR

Patrice E<lt>GomoRE<gt> Auffret

=head1 COPYRIGHT AND LICENSE

Copyright (c) 2006-2019, Patrice E<lt>GomoRE<gt> Auffret

You may distribute this module under the terms of the Artistic license.
See LICENSE.Artistic file in the source distribution archive.

=cut
