#!/usr/bin/perl
#
# $Id: ipq_aodv_display,v 1.8 2006/07/18 11:54:19 mstock Exp $
#
# Get packets from IPQueue and display information on (AODV-)content, 
# then pass packets back to kernel with NF_ACCEPT verdict. Based on passer.pl
# from libiptables-ipv4-ipqueue-perl.
# 
# Copyright (c) 2000-2002 James Morris <jmorris@intercode.com.au>
# Copyright (c) 2006 (AODV-extension) Manfred Stock <mstock@student.ethz.ch>
# 
#
# This code is GPL.
#
use strict;
use Term::ANSIColor qw(:constants);
$Term::ANSIColor::AUTORESET = 1;

use Getopt::Long;

use Socket;
$^W = 1;
my @NFHOOKS = ("NF_IP_PRE_ROUTING","NF_IP_LOCAL_IN","NF_IP_FORWARD","NF_IP_LOCAL_OUT","NF_IP_POST_ROUTING");
my @AODVTYPES = ("","RREQ","RREP","RERR","RREP-ACK");

use IPTables::IPv4::IPQueue qw(:constants);

my $printcontent = 0;
my $help = 0;

GetOptions(
	"printcontent" => \$printcontent,
	"help" => \$help
) or die ("Error parsing options: $!");

if($help){
	print "Usage: ipq_aodv_display [--printcontent]\n";
	exit 0;
}


sub msgInfo
{
	my $msg = shift;
	print CYAN "Received a new packet with id ".$msg->packet_id()." from ".$NFHOOKS[$msg->hook()]."-hook:\n";
	print "\tDatalength:  " . $msg->data_len() . "\n";
	# Check that length is larger or equal to the minimal length of a
	# rreq/rrep (20 byte IP, 8 byte UDP, 20 Byte rrep (which is 4 byte
	# shorter than rreq))
	if($msg->data_len() >= 20+8+20){
		print "\tTrying to parse IP-header:\n";
		my $ipheadlen = ord(substr $msg->payload,0,1) & 0xf;
		my $proto = ord(substr $msg->payload,9,1);
		my $ttl = ord(substr $msg->payload,8,1);
		print "\t\tHeader length: $ipheadlen words\n";
		printf("\t\tTTL: $ttl; Protocol: %x (%s)\n",$proto,getprotobynumber($proto));
		print "\t\tSource address: ",inet_ntoa(substr $msg->payload,12,4),"\n";
		print "\t\tDestination address: ",inet_ntoa(substr $msg->payload,16,4),"\n";
		my $offset = $ipheadlen*4;
		# Check that header-length is valid and if protocol is udp
		if($ipheadlen >= 5 && $proto eq 0x11){
			udpinfo($msg,$offset);		
		}else{
			print BOLD RED "\tHeader too short or specified transport layer protocol is not udp!\n";
		}
	}else{
		print BOLD RED "\tPacket too short for an AODV rreq/rrep...\n";
	}
	print "\n";
}

sub udpinfo
{
	my $msg = shift;
	my $offset = shift;
	# Make sure that we are still inside the packet...
	if ($offset+8 <= $msg->data_len()){
		print "\tTrying to parse UDP-header:\n";
		my $sport = unpack "n",substr($msg->payload,$offset,2);
		my $dport = unpack "n",substr($msg->payload,$offset+2,2);
		print "\t\tSouceport: $sport Destinationport: $dport\n";
		my $udplen = unpack "n",substr($msg->payload,$offset+4,2);
		print "\t\tLength of UDP-Packet: $udplen\n";
		# Check if the offsets and lengths we found so far are correct
		if($offset + $udplen == $msg->data_len()){
			# There was a UDP-header
			$offset += 8;
			aodvinfo($msg,$offset);
		}else{
			print BOLD RED "The given lengths ",($offset+$udplen)," do not match data-length ",$msg->data_len()," of packet!\n";
		}
	}
}

sub aodvinfo
{
	my $msg = shift;
	my $offset = shift;
	# Make sure the given length allows that it contains an AODV-message (8 byte UDP + 20 byte rrep)
	if($offset + 20 <= $msg->data_len()){
		print YELLOW "\tTrying to parse AODV-packet:\n";
		my $type = ord(substr $msg->payload,$offset,1);
		print "\t\tType: $type (",BOLD,GREEN,$AODVTYPES[$type],RESET,")\n";
		my $hopcount = ord(substr $msg->payload,$offset+3,1);
		print "\t\tHopcount: $hopcount\n";
		if($type eq 1 || $type eq 2){
			if($type eq 1){
				rreqinfo($msg,$offset);
				# Route request - check that length is ok
				if($offset + 24 <= $msg->data_len()){
					$offset += 24;
				}else{
					print BOLD RED "Invalid rreq-message!\n\n";
					return;
				}
			}elsif($type eq 2){
				rrepinfo($msg,$offset);
				# Route-reply - we always used it's length, so it's save to increment
				$offset += 20;
			}
			if($offset < $msg->data_len()){
				aodvextinfo($msg,$offset);
			}else{
				print RED "\t\tPacket can not contain an AODV-extension!\n";
			}
		}else{
			print BOLD RED "Given type may not contain extensions!";
		}
	}else{
		print BOLD RED "Packet too small, it cannot contain an AODV rrep/rreq!\n";
	}
}

sub rreqinfo
{
	my $msg = shift;
	my $offset = shift;
	print "\t\tRREQ-ID: ",unpack("N",substr($msg->payload,$offset+4,4)),"\n";
	print "\t\tDestination address: ",inet_ntoa(substr $msg->payload,$offset+8,4),", ";
	print "sequence number: ",unpack("N",substr($msg->payload,$offset+12,4)),"\n";
	print "\t\tOriginator  address: ",inet_ntoa(substr $msg->payload,$offset+16,4),", ";
	print "sequence number: ",unpack("N",substr($msg->payload,$offset+20,4)),"\n";
}

sub rrepinfo
{
	my $msg = shift;
	my $offset = shift;
	print "\t\tDestination address: ",inet_ntoa(substr $msg->payload,$offset+4,4),", ";
	print "sequence number: ",unpack("N",substr($msg->payload,$offset+8,4)),"\n";
	print "\t\tOriginator  address: ",inet_ntoa(substr $msg->payload,$offset+12,4),"\n";
	print "\t\tLifetime: ",unpack("N",substr($msg->payload,$offset+16,4)),"\n";
}

sub aodvextinfo
{
	my $msg = shift;
	my $offset = shift;
	my $ext = 0;
	print GREEN "\t\tPacket seems to contain (an) AODV-extension(s)! Try to parse...\n";
	while($offset+2 <= $msg->data_len()){
		my $etype = ord(substr $msg->payload,$offset,1);
		my $elen = ord(substr $msg->payload,$offset+1,1);
		print BLUE "\t\t\tExtension $ext is of type $etype and has length $elen\n";
		printExtension(substr $msg->payload,$offset+2,$elen);
		$ext++;
		$offset += $elen+2;
		if($offset > $msg->data_len()){
			print BOLD RED "\t\t\tGiven length seems to be invalid (exceeds packet length)!\n"
		}
	}
}

sub printExtension(){
	my $content = shift;
	if($printcontent){
		my @array_data = split(//, $content);
		my $lpos = 0;
		my $cpos = 0;
		map {$_ = join("",unpack("H*", $_)); } @array_data;
		for(my $i = 0; $i < $#array_data; $i+=16){
			print "\t\t\t";
			print join(" ", @array_data[$i .. ($i+15 < $#array_data ? $i+15 : $#array_data)]);
			my $line = substr $content, $i,16;
			$line =~ s/\W/./g;
			print "   " for 1 .. ($i+15 < $#array_data ? 0 : (16-length($line)));
			print "  $line\n";
		}
	}
}

sub main
{
	my ($queue, $msg);
	
	$queue = new IPTables::IPv4::IPQueue(copy_mode => IPQ_COPY_PACKET, copy_range => 1500)
		or die IPTables::IPv4::IPQueue->errstr;

	while (1) {
	
		$msg = $queue->get_message()
			or die IPTables::IPv4::IPQueue->errstr;
		
		msgInfo($msg);
		
		$queue->set_verdict($msg->packet_id(), NF_ACCEPT) > 0
			or die IPTables::IPv4::IPQueue->errstr;
	}
}

main();

