use strict; use warnings; package Net::PcapWriter::IP; use Socket qw(AF_INET AF_INET6); use base 'Exporter'; # re-export the usable inet_pton our @EXPORT = qw(ip_chksum ip4_packet ip6_packet ip_packet inet_pton); my $do_chksum = 1; sub calculate_checksums { $do_chksum = $_[1] } BEGIN { # inet_pton is in Socket since 5.12 # but even if it is in Socket it can throw a non-implemented error eval { Socket->import('inet_pton'); inet_pton(AF_INET,'127.0.0.1'); inet_pton(AF_INET6,'::1'); 1 } or eval { require Socket6; Socket6->import('inet_pton'); inet_pton(AF_INET,'127.0.0.1'); inet_pton(AF_INET6,'::1'); 1 } or die "you need either a modern perl or Socket6" } # construct IPv4 packet or packet generating sub sub ip4_packet { my ($data,$src,$dst,$protocol,$chksum_offset,$no_pseudo_header) = @_; my $hdr = pack('CCnnnCCna4a4', 0x45, # version 4, len=5 (no options) 0, # type of service defined($data) ? length($data)+20 : 20, # total length 0,0, # id=0, not fragmented 128, # TTL $protocol, 0, # checksum - computed later scalar(inet_pton(AF_INET,$src) || die "no IPv4 $src"), scalar(inet_pton(AF_INET,$dst) || die "no IPv4 $dst"), ); if (defined $data) { return $hdr.$data if ! $do_chksum; if (defined $chksum_offset) { my $ckdata = $no_pseudo_header ? $data : substr($hdr,-8).pack('xCna*', $protocol,length($data), # proto + len $data ); substr($data,$chksum_offset, 2) = pack('n',ip_chksum($ckdata)); } substr($hdr,10,2) = pack('n',ip_chksum($hdr)); return $hdr.$data; } # data not defined, return sub which creates packet once data are known if (!$do_chksum) { return sub { substr(my $lhdr = $hdr,2,2) = pack('n',length($_[0])+20); return $lhdr.$_[0]; }; } if (! defined $chksum_offset) { return sub { substr(my $lhdr = $hdr,2,2) = pack('n',length($_[0])+20); substr($lhdr,10,2) = pack('n',ip_chksum($lhdr)); return $lhdr.$_[0]; }; } return sub { my $data = shift; my $ckdata = $no_pseudo_header ? $data : substr($hdr,-8).pack('xCna*', $protocol,length($data), # proto + len $data ); substr($data,$chksum_offset, 2) = pack('n',ip_chksum($ckdata)); substr(my $lhdr = $hdr,2,2) = pack('n',length($data)+20); substr($lhdr,10,2) = pack('n',ip_chksum($lhdr)); return $lhdr.$data; }; } # construct IPv6 packet sub ip6_packet { my ($data,$src,$dst,$protocol,$chksum_offset) = @_; my $hdr = pack('NnCCA16A16', 6 << 28 | 0 << 20 | 0, # version, traffic class, flow label defined($data) ? length($data) : 0, # length of payload $protocol, # next header = protocol 128, # hop limit scalar(inet_pton(AF_INET6,$src) || die "no IPv6 $src"), scalar(inet_pton(AF_INET6,$dst) || die "no IPv6 $dst"), ); if (defined $data) { # return packet if ($do_chksum && defined $chksum_offset) { my $ckdata = substr($hdr,-32).pack('NxxxCa*', length($data), $protocol, # len + proto $data ); substr($data,$chksum_offset, 2) = pack('n',ip_chksum($ckdata)); } return $hdr.$data; } # data not defined, return sub which creates packet once data are known if (! defined $chksum_offset) { return sub { substr($hdr,4,2) = pack('n',length($_[0])); return $hdr.$_[0] } } return sub { my $data = shift; substr($hdr,4,2) = pack('n',length($data)); if ($do_chksum) { my $ckdata = substr($hdr,-32).pack('NxxxCa*', length($data), $protocol, # len + proto $data ); substr($data,$chksum_offset, 2) = pack('n',ip_chksum($ckdata)); } return $hdr.$data; }; } sub ip_packet { goto &ip6_packet if $_[1] =~m{:}; goto &ip4_packet; } sub ip_chksum16 { my $data = pop; $data .= "\x00" if length($data) % 2; # padding my $sum = 0; $sum += $_ for (unpack('n*', $data)); $sum = ($sum >> 16) + ($sum & 0xffff); $sum = ~(($sum >> 16) + $sum) & 0xffff; return $sum; } sub ip_chksum32 { my $data = pop; $data .= "\x00" x (4 - length($data) % 4); # padding my $sum = 0; $sum += $_ for unpack('N*', $data); $sum = ($sum >> 16) + ($sum & 0xffff); $sum = ($sum >> 16) + ($sum & 0xffff); $sum = ($sum >> 16) + ($sum & 0xffff); return ~$sum; } require Config; *ip_chksum = $Config::Config{ivsize} == 8 ? \&ip_chksum32 : \&ip_chksum16; 1;