#!/usr/bin/perl
#
# Module: vyatta-nat-translate.pl
# 
# **** License ****
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 as
# published by the Free Software Foundation.
# 
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# General Public License for more details.
# 
# This code was originally developed by Vyatta, Inc.
# Portions created by Vyatta are Copyright (C) 2007 Vyatta, Inc.
# All Rights Reserved.
# 
# Author: Stig Thormodsrud
# Date: July 2008
# Description: Script to display nat translations
# 
# **** End License ****
#

use Getopt::Long;
use XML::Simple;
use Data::Dumper;
use POSIX;

use warnings;
use strict;

my $dump = 0;
my ($xml_file, $verbose, $proto, $stats, $ipaddr, $pipe);
my $mode;
my $verbose_format = "%-20s %-18s %-20s %-18s\n";
my $format         = "%-20s %-20s %-4s  %-4s  %-8s";


sub add_xml_root {
    my $xml = shift;

    $xml = '<data>' . $xml . '</data>';
    return $xml;
}

sub read_xml_file {
    my $file = shift;

    local($/, *FD);  # slurp mode
    open FD, "<", $file or die "Couldn't open $file\n";
    my $xml = <FD>;
    close FD;
    $xml = add_xml_root($xml);
    return $xml;
}

sub print_xml {
    my $data = shift;
    print Dumper($data); 
}

sub guess_snat_dnat {
    my ($src, $dst) = @_;

    if ($src->{original} eq $dst->{reply}) {
	return "dnat";
    }
    if ($dst->{original} eq $src->{reply}) {
	return "snat";
    }
    return "unkn";
}

sub nat_print_xml {
    my ($data, $mode) = @_;

    my $flow = 0;

    my %flowh;
    while (1) {
	my $meta = 0;
	last if ! defined $data->{flow}[$flow];
	my $flow_ref = $data->{flow}[$flow];
	my $flow_type = $flow_ref->{type};
	my (%src, %dst, %sport, %dport, %proto);
	my (%packets, %bytes);
	my $timeout = undef;
	my $uses = undef;
	while (1) {
	    my $meta_ref = $flow_ref->{meta}[$meta];
	    last if ! defined $meta_ref;
	    my $dir = $meta_ref->{direction};
	    if ($dir eq 'original' or $dir eq 'reply') {
		my $l3_ref    = $meta_ref->{layer3}[0];
		my $l4_ref    = $meta_ref->{layer4}[0];
		my $count_ref = $meta_ref->{counters}[0];
		if (defined $l3_ref) {
		    $src{$dir} = $l3_ref->{src}[0];
		    $dst{$dir} = $l3_ref->{dst}[0];
		    if (defined $l4_ref) {
			$sport{$dir} = $l4_ref->{sport}[0];
			$dport{$dir} = $l4_ref->{dport}[0];
			$proto{$dir} = $l4_ref->{protoname};
		    }
		}
		if (defined $stats and defined $count_ref) {
		    $packets{$dir} = $count_ref->{packets}[0];
		    $bytes{$dir}   = $count_ref->{bytes}[0];
		}
	    } elsif ($dir eq 'independent') {
		$timeout = $meta_ref->{timeout}[0];
		$uses    = $meta_ref->{'use'}[0];
	    }
	    $meta++;
	}
	my ($proto, $in_src, $in_dst, $out_src, $out_dst);
	$proto    = $proto{original};
	$in_src   = "$src{original}";
	$in_src  .= ":$sport{original}" if defined $sport{original};
	$in_dst   = "$dst{original}";
	$in_dst  .= ":$dport{original}" if defined $dport{original};
	$out_src  = "$dst{reply}";
	$out_src .= ":$dport{reply}" if defined $dport{reply};
	$out_dst  = "$src{reply}";
	$out_dst .= ":$sport{reply}" if defined $sport{reply};
	if (defined $verbose) {
	    printf($verbose_format, $in_src, $in_dst, $out_src, $out_dst);
	}
	if (! defined $mode) {
	    $mode = guess_snat_dnat(\%src, \%dst);
	}
	if (defined $mode) {
	    my ($from, $to);
	    if ($mode eq 'snat') {
		$from = "$src{original}";
		$to   = "$dst{reply}";
		if (defined $sport{original} and defined $dport{reply}) {
		    if ($sport{original} ne $dport{reply}) {
			$from .= ":$sport{original}";
			$to   .= ":$dport{reply}";
		    }
		}
	    } else {
		$from = "$dst{original}";
		$to   = "$src{reply}";
		if (defined $dport{original} and defined $sport{reply}) {
		    if ($dport{original} ne $sport{reply}) {
			$from .= ":$dport{original}";
			$to   .= ":$sport{reply}";
		    }
		}
	    }
	    if (defined $verbose) {
		print "  $proto: $mode: $from ==> $to";
	    } else {
		my $timeout2 = "";
		if (defined $timeout) {
		    $timeout2 = $timeout;
		}
		printf($format, $from, $to, $mode, $proto, $timeout2);
		print " $flow_type" if defined $flow_type;
		print "\n";
	    }
	}
	if (defined $verbose) {
	    print "  timeout: $timeout" if defined $timeout;
	    print " use: $uses " if defined $uses;
	    print " type: $flow_type" if defined $flow_type;
	    print "\n";
	}
	if (defined $stats) {
	    foreach my $dir ('original', 'reply') {
		if (defined $packets{$dir}) {
                    printf("  %-8s: packets %s, bytes %s\n",
		           $dir, $packets{$dir}, $bytes{$dir});
		}
	    }
	}
	$flow++;
    }
    return $flow;
}


#
# main
#
GetOptions("verbose"  => \$verbose,
	   "proto=s"  => \$proto,
	   "file=s"   => \$xml_file,
	   "stats"    => \$stats,
	   "mode=s"   => \$mode,
	   "ipaddr=s" => \$ipaddr,
	   "pipe"     => \$pipe,
);

my $conntrack = '/usr/sbin/conntrack';
if  (! -f $conntrack) {
    die "Package [conntrack] not installed";
}

my $xs = XML::Simple->new(ForceArray => 1, KeepRoot => 0);
my ($xml, $data);

# flush stdout after every write for pipe mode
$| = 1 if defined $pipe; 

if (defined $verbose) {
    printf($verbose_format, 'Pre-NAT src', 'Pre-NAT dst', 
	   'Post-NAT src', 'Post-NAT dst');
} else {
    printf($format, 'Pre-NAT', 'Post-NAT', 'Type', 'Prot', 'Timeout');
    print " Type" if defined $pipe;
    print "\n";
}

if (defined $xml_file) {
    $xml = read_xml_file($xml_file);
    $data = $xs->XMLin($xml);
    if ($dump) {
	print_xml($data);
	exit;
    }
    nat_print_xml($data, 'snat');

} elsif (defined $pipe) {
    while ($xml = <STDIN>) {
	$xml = add_xml_root($xml);
	$data = $xs->XMLin($xml);
	nat_print_xml($data, $mode);
    }
} else {
    if (defined $proto) {
	$proto = "-p $proto" 
    } else {
	$proto = "";
    }
    $mode = 'both' if ! defined $mode;
    if ($mode eq 'both' or $mode eq 'snat') {
	my $ipopt = "";
	if (defined $ipaddr) {
	    $ipopt = "--orig-src $ipaddr";
	} 
	$xml = `sudo $conntrack -L -n $ipopt -o xml $proto`;
	my $snat_xml = add_xml_root($xml);
	$data = $xs->XMLin($snat_xml);
	nat_print_xml($data, 'snat');	
    }
    if ($mode eq 'both' or $mode eq 'dnat') {
	my $ipopt = "";
	if (defined $ipaddr) {
	    $ipopt = "--orig-dst $ipaddr";
	} 
	$xml = `sudo $conntrack -L -g $ipopt -o xml $proto`;
	my $dnat_xml = add_xml_root($xml);
	$data = $xs->XMLin($dnat_xml);
	nat_print_xml($data, 'dnat');	
    }
}

# end of file