#!/usr/bin/perl

##
## Copyright (c) 2010-2017 Intel Corporation
##
## Licensed under the Apache License, Version 2.0 (the "License");
## you may not use this file except in compliance with the License.
## You may obtain a copy of the License at
##
##     http://www.apache.org/licenses/LICENSE-2.0
##
## Unless required by applicable law or agreed to in writing, software
## distributed under the License is distributed on an "AS IS" BASIS,
## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
## See the License for the specific language governing permissions and
## limitations under the License.
##

use strict vars;
use Getopt::Long;
use Pod::Usage;
use Net::Pcap;
use Net::Frame::Layer;
use Net::Frame::Layer::ETH qw(:consts);
use Net::Frame::Layer::IPv6 qw(:consts);
use Net::Frame::Layer::IPv4 qw(:consts);
use Net::Frame::Layer::UDP;
use Socket qw(AF_INET AF_INET6 inet_ntop inet_pton);

use constant NUM_PACKETS => 30000;

use constant ETHER_ADDR_LEN => 6;
use constant ETHER_TYPE_LEN => 2;
use constant ETHER_HDR_LEN => ( 2 * ETHER_ADDR_LEN ) + ETHER_TYPE_LEN;
use constant ETHER_STATIC_MAC => "78acdddddddd";

use constant UDP_HDR_LEN => 8;
use constant UDP_STATIC_PORT => 0x6666;

use constant IPv6_HOP_LIMIT => 4;
use constant IPv6_STATIC_IP => "2222:2222:2222:2222:2222:2222:2222:2222";

use constant IPv4_TIME_TO_LIVE => 32;
use constant IPv4_STATIC_IP => "68.68.68.68";

srand;

my $type = 'tun';
my $pkt_count = NUM_PACKETS;

GetOptions(
	'inet' => sub { $type = 'inet'},
	'tun' => sub { $type = 'tun'},
	'count=i'      => \$pkt_count,
	'in=s' => \(my $in = 'ip6_tun_bind.lua'),
	'out=s' => \(my $out = 'output.pcap'),
	'size=s' => \(my $size = 0)
) or exit;

my $pcap = pcap_open_dead( DLT_EN10MB, 65535 );
my $dumper = pcap_dump_open($pcap, $out ) or die 'Could not create output file: ' . $out;

if( $type eq 'inet' ) {
	gen_inet_pcap( $in, $pkt_count );
}
if( $type eq 'tun' ) {
	gen_tun_pcap( $in, $pkt_count );
}

pcap_close( $pcap );

# Trim string
sub trim {
	my ( $str ) = @_;

	$str =~ s/^\s+|\s+$//g;

	return $str;
}

# Generate random port based on $port and $port_mask
sub rand_port {
	my ( $port, $port_mask ) = @_;

	return ( $port | int( rand( 0xFFFF ) & $port_mask ) );
}

# Generate packet originating from CPE
sub gen_tun_packet {
	my ( $sz, $ether, $ipv6, $ipv4, $udp ) = @_;

	my $hdr_ether = Net::Frame::Layer::ETH->new(
		src => $ether->{'src'},
		dst => $ether->{'dst'},
		type => NF_ETH_TYPE_IPv6
	)->pack;

	my $hdr_ipv6 = Net::Frame::Layer::IPv6->new(
		nextHeader => NF_IPv6_PROTOCOL_IPIP,
		hopLimit => IPv6_HOP_LIMIT,
		src => $ipv6->{'src'},
		dst => $ipv6->{'dst'},
		payloadLength => $sz + NF_IPv4_HDR_LEN + UDP_HDR_LEN
	)->pack;

	my $hdr_ipv4 = Net::Frame::Layer::IPv4->new(
		length => $sz + UDP_HDR_LEN + NF_IPv4_HDR_LEN,
		ttl => IPv4_TIME_TO_LIVE,
		protocol => NF_IPv4_PROTOCOL_UDP,
		src => $ipv4->{'src'},
		dst => $ipv4->{'dst'}
	)->pack;

	my $hdr_udp = Net::Frame::Layer::UDP->new(
		src => $udp->{'src'},
		dst => $udp->{'dst'},
		length => $sz + UDP_HDR_LEN
	)->pack;
	
	my $pkt = pack( "H*", "de" x $sz );
	$pkt = $hdr_ether . $hdr_ipv6 . $hdr_ipv4 . $hdr_udp . $pkt;

	my $pkt_size = length( $pkt );

	my $hdr = {
		tv_sec => 0,
		tv_usec => 0,
		len => $pkt_size,
		caplen => $pkt_size
	};

	return ( $hdr, $pkt );
}

# Generate packet originating from the internet
sub gen_inet_packet {
	my ( $sz, $ether, $ipv4, $udp ) = @_;

	my $hdr_ether = Net::Frame::Layer::ETH->new(
		src => $ether->{'src'},
		dst => $ether->{'dst'},
		type => NF_ETH_TYPE_IPv4
	)->pack;

	my $hdr_ipv4 = Net::Frame::Layer::IPv4->new(
		length => $sz + UDP_HDR_LEN + NF_IPv4_HDR_LEN,
		ttl => IPv4_TIME_TO_LIVE,
		protocol => NF_IPv4_PROTOCOL_UDP,
		src => $ipv4->{'src'},
		dst => $ipv4->{'dst'}
	)->pack;

	my $hdr_udp = Net::Frame::Layer::UDP->new(
		src => $udp->{'src'},
		dst => $udp->{'dst'},
		length => $sz + UDP_HDR_LEN
	)->pack;
	
	my $pkt = pack( "H*", "de" x $sz );
	$pkt = $hdr_ether . $hdr_ipv4 . $hdr_udp . $pkt;

	my $pkt_size = length( $pkt );

	my $hdr = {
		tv_sec => 0,
		tv_usec => 0,
		len => $pkt_size,
		caplen => $pkt_size
	};

	return ( $hdr, $pkt );
}

# Read bindings file
sub read_bindings {
	my ( $file ) = @_;

	print "Reading bindings file...\n";

	my @rows;

	open my $fh, "<:encoding(utf8)", $file or die $file . ": $!";
LINE:	while ( my $line = <$fh> ) {
		next if ($line =~ /^--.*/);  # Skip comments
		
		my ($ip6, $mac, $ip4, $port);
		if ($line =~ /\s*\{.*\},\s*$/) {  # Weak check for a data line...

			$line =~ /ip6\s*=\s*ip6\("([^\)]*)"\)/ && do { $ip6 = trim($1); };
			unless ( inet_pton( AF_INET6, $ip6 ) ) { print "ERROR - Invalid ipv6: $ip6\n"; next LINE; }

			$line =~ /ip\s*=\s*ip\("([^\)]*)"\)/ && do { $ip4 = trim($1); };
			unless ( inet_pton( AF_INET, $ip4 ) ) { print "ERROR - Invalid ipv4: $ip4\n"; next LINE; }

			$line =~ /mac\s*=\s*mac\("([^\)]*)"\)/ && do { $mac = trim($1); };
			unless ( $mac =~ /^([0-9a-f]{2}([:-]|$)){6}$/i ) { print "ERROR - Invalid mac: $mac\n"; next LINE; }

			$line =~ /port\s*=\s*([0-9]*)/ && do { $port = trim($1); };
			unless ( int($port) ) { print "ERROR - Invalid port number: $port\n"; next LINE; }

			push @rows, {
				ipv6 => $ip6,
				mac => $mac,
				ipv4 => $ip4,
				port => $port
			}
		}
	}
	close $fh;

	return @rows;
}

# Generate packets originating from CPE
sub gen_tun_pcap {
	my ( $binding_file, $pkt_count ) = @_;
	my @bind = read_bindings($binding_file);
	my $idx = 0;
	my $row;
	my $public_port = 0;

	print "Generating $pkt_count Tunnel packets...\n";

	my $max = @bind;
	for( my $i=0; $i<$pkt_count; $i++ ) {

		$idx = rand $max;
		$row = @bind[$idx];

		$public_port = rand_port( $row->{port}, 0x3f );

		my ( $hdr, $pkt ) = gen_tun_packet(
			$size,
			{ src => $row->{mac}, dst => ETHER_STATIC_MAC },
			{ src => $row->{ipv6}, dst => IPv6_STATIC_IP },
			{ src => $row->{ipv4}, dst => IPv4_STATIC_IP },
			{ src => $public_port, dst => UDP_STATIC_PORT }
		);

		pcap_dump( $dumper, $hdr, $pkt );
	}
}

# Generate packets originating from the internet
sub gen_inet_pcap {
	my ( $binding_file, $pkt_count ) = @_;
	my @bind = read_bindings($binding_file);
	my $idx = 0;
	my $row;
	my $public_port = 0;

	print "Generating $pkt_count Internet packets...\n";

	my $max = @bind;
	for( my $i=0; $i<$pkt_count; $i++ ) {

		$idx = rand $max;
		$row = @bind[$idx];

		$public_port = rand_port( $row->{port}, 0x3f );

		my ( $hdr, $pkt ) = gen_inet_packet(
			$size,
			{ src => ETHER_STATIC_MAC, dst => $row->{mac} },
			{ src => IPv4_STATIC_IP, dst => $row->{ipv4} },
			{ src => UDP_STATIC_PORT, dst => $public_port }
		);

		pcap_dump( $dumper, $hdr, $pkt );
	}
}