#! /usr/bin/perl
#
# tsh2pcap.pl
#
# Converts time sequenced headers (.tsh) to libpcap format.
#
# Adapted from tsh2tcpdump.pl by
#    Rob Beverly <rbeverly \at\ csail \d0t\ mit \d0t\ edu>, November, 2003.
# 
# 2006-Mar-22: Fixed a bug in the TCP header length field found by
#    Alessandro de Peppo
# 2006-Mar-22: Added proper TCP checksums 

#
# IMPORTANT NOTE:
#
# For whatever reason, Net::Pcap does not include an open_dead
# method.  I've sent the maintainers a request to include with
# the following diff to Pcap.xs in the Net-Pcap-0.05 package:
#
# 214a215,219
# > pcap_t *
# > pcap_open_dead(linktype, snaplen)
# >       int linktype
# >       int snaplen
#
# This script only works with this modification to Net::Pcap
#

#   tsh format:
#
#    0                   1                   2                   3
#    0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
#   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
#0  |                    timestamp (seconds)                        | Time
#   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
#1  |     i/f#      |  timestamp (microseconds)                     |
#   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
#2  |Version|  IHL  |Type of Service|          Total Length         | IP
#   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
#3  |         Identification        |Flags|      Fragment Offset    |
#   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
#4  |  Time to Live |    Protocol   |         Header Checksum       |
#   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
#5  |                       Source Address                          |
#   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
#6  |                    Destination Address                        |
#   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
#7  |          Source Port          |       Destination Port        | TCP
#   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
#8  |                        Sequence Number                        |
#   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
#9  |                    Acknowledgment Number                      |
#   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
#   |  Data |           |U|A|P|R|S|F|                               |
#10 | Offset| Reserved  |R|C|S|S|Y|I|            Window             |
#   |       |           |G|K|H|T|N|N|                               |
#   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
#

use Net::Pcap;
use NetPacket;

open( infile, $ARGV[0] ) || die("USAGE: $0 input-tsh [output]\n");

$output = $ARGV[1];
$output = $ARGV[0] . ".tcp" unless $output;

# use DLT_RAW = 12 (no link-layer goo!), assume 40 Bytes capture
$caplen      = 40;
$DLT_RAW     = 12;
$pcap        = Net::Pcap::open_dead( $DLT_RAW, $caplen );
$pcap_dumper = Net::Pcap::dump_open( $pcap, $output );

while ( read( infile, $record, 44 ) ) {

    $ihl = ( vec( $record, 8, 8 ) & 0x0f ) << 2;
    $vhl = vec( $record, 8, 8 );
    $tos = vec( $record, 9, 8 );
    $len = ( vec( $record, 10, 8 ) << 8 ) + vec( $record, 11, 8 );
    $id  = ( vec( $record, 12, 8 ) << 8 ) + vec( $record, 13, 8 );
    $off = ( vec( $record, 14, 8 ) << 8 ) + vec( $record, 15, 8 );
    $ttl = vec( $record, 16, 8 );
    $p   = vec( $record, 17, 8 );
    $sum = ( vec( $record, 18, 8 ) << 8 ) + vec( $record, 19, 8 );

    # Can't use the checksum from the tsh; we'll calculate a new one
    $sum = 0;

    $hdr->{tv_sec}  = vec( $record, 0, 32 );
    $hdr->{tv_usec} = vec( $record, 1, 32 ) & 0xffffff;
    $hdr->{caplen}  = $caplen;
    $hdr->{len}     = $len;

    $src =
      ( vec( $record, 20, 8 ) << 24 ) + ( vec( $record, 21, 8 ) << 16 ) +
      ( vec( $record, 22, 8 ) << 8 ) + vec( $record, 23, 8 );
    $dst =
      ( vec( $record, 24, 8 ) << 24 ) + ( vec( $record, 25, 8 ) << 16 ) +
      ( vec( $record, 26, 8 ) << 8 ) + vec( $record, 27, 8 );

    # Calculate a pseudo-header
    $ip = pack( 'CCnnnCCnNN',
        $vhl, $tos, $len, $id, $off, $ttl, $p, $sum, $src, $dst );

    # Calculate an IP checksum
    $sum = NetPacket::htons( NetPacket::in_cksum($ip) );

    # Repack
    $ip = pack( 'CCnnnCCnNN',
        $vhl, $tos, $len, $id, $off, $ttl, $p, $sum, $src, $dst );

    # TCP
    if ( $p == 6 ) {
        $sport =
          ( vec( $record, $ihl + 8, 8 ) << 8 ) +
          ( vec( $record, $ihl + 9, 8 ) );
        $dport =
          ( vec( $record, $ihl + 10, 8 ) << 8 ) +
          ( vec( $record, $ihl + 11, 8 ) );

        # Look at sequence numbers
        $seq =
          ( vec( $record, $ihl + 12, 8 ) << 24 ) +
          ( vec( $record, $ihl + 13, 8 ) << 16 ) +
          ( vec( $record, $ihl + 14, 8 ) << 8 ) + 
          vec( $record, $ihl + 15, 8 );

        $ack =
          ( vec( $record, $ihl + 16, 8 ) << 24 ) +
          ( vec( $record, $ihl + 17, 8 ) << 16 ) +
          ( vec( $record, $ihl + 18, 8 ) << 8 ) + 
          vec( $record, $ihl + 19, 8 );
        $off = ( vec( $record, $ihl + 20, 8 ) & 0xf0);

        $flags = vec( $record, $ihl + 21, 8 );
        $rwin =
          ( vec( $record, $ihl + 22, 8 ) << 8 ) + vec( $record, $ihl + 23, 8 );

        # Find tcp payload length and left and right edge of window
        $thl     = vec( $record, $ihl + 20, 8 ) >> 2;
        $datalen = $plen - $ihl - $thl;

        # Fudge TCP cksum 
        $sum = 0;
        $tcp = pack( 'nnNNccnnn',
            $sport, $dport, $seq, $ack, $off, $flags, $rwin, $sum, 0 );

        # Create Pseudo-header in order to compute chksum.  
        $pseudo = pack( 'NNccna20', $src, $dst, 0x00, $p, $len - $ihl, $tcp);
#        $offbytes = (($off & 0xf0) >> 4)*4;
#        $pad = $len - $ihl - $offbytes;
#        $pseudotcpip = $pseudo.0 x $pad;

        $sum = NetPacket::htons( NetPacket::in_cksum($pseudo) );
        $tcp = pack( 'nnNNccnnn',
            $sport, $dport, $seq, $ack, $off, $flags, $rwin, $sum, 0 );
        $tcpip = pack( 'a20a20', $ip, $tcp );
        Net::Pcap::dump( $pcap_dumper, $hdr, $tcpip );
    }

    # UDP
    elsif ( $p == 17 ) {
        $sport =
          ( vec( $record, $ihl + 8, 8 ) << 8 ) +
          ( vec( $record, $ihl + 9, 8 ) );
        $dport =
          ( vec( $record, $ihl + 10, 8 ) << 8 ) +
          ( vec( $record, $ihl + 11, 8 ) );

        $len =
          ( vec( $record, $ihl + 12, 8 ) << 8 ) +
          ( vec( $record, $ihl + 13, 8 ) );

        # Fudge UDP cksum for now
        $sum   = 0;
        $udp   = pack( 'nnnn', $sport, $dport, $len, $sum );
        $udpip = pack( 'a20a8', $ip, $udp );
        Net::Pcap::dump( $pcap_dumper, $hdr, $udpip );
    }

    # Non-UDP/TCP
    else {
        Net::Pcap::dump( $pcap_dumper, $hdr, $ip );
    }
}

Net::Pcap::dump_close($pcap_dumper);
Net::Pcap::close($pcap);

