なんだか、数年毎に再アップしてる気がしますが。まあいいや。

このscriptをtcpfilterとか適当な名前で保存しておいて、たとえば

# tcpdump -xnls 0 port 80 | ./tcpfilter

とかすると、tcpdumpのhexなパケットを整形してくれます。

SIPとかで使うと超便利です。

CPANも何も使ってないので、ただperlさえ入ってれば動くのがいいとこかな。

このscriptさえコピっていけばいいので。

remove_control_codeの中の正規表現をコメントアウトすると、制御コードも目視できますわ。

#!/usr/bin/perl
#
# tcpfilter - analyze and format an output of tcpdump
#
# - zaki - <zaki@redsip.com>
# $Id: tcpfilter,v 1.5 2004/06/02 06:14:51 zaki Exp zaki $
#

use strict;
use warnings;
use English;

$OUTPUT_AUTOFLUSH = 1;

sub remove_sp {
    my($data) = @_;
    $data =~ s|\s||g;
    return $data;
}

sub get_total_len {
    my($data) = @_;
    my @data = split(m||, $data);
    my $total_len = hex("$data[5 - 1]$data[6 - 1]$data[7 - 1]$data[8 - 1]") * 2;
    return $total_len;
}

sub output {
    my($data, $time, $count) = @_;

    ip_sanity_check($data) or return;
    my @data = split(m||, $data);
    my $ip_len = ip_header_len(@data);
    my $src_ip = ip_src(@data);
    my $dst_ip = ip_dst(@data);
    my $proto = protocol_type(@data);

    my($header_len, $src_port, $dst_port);
    if($proto eq '00000110') {
        check_syn($ip_len, @data) and return;
        my $tcp_len = tcp_header_len($ip_len, @data);
        $src_port = tcp_src_port($ip_len, @data);
        $dst_port = tcp_dst_port($ip_len, @data);
        $header_len = $ip_len + $tcp_len;
    } elsif($proto eq '00010001') {
        my $udp_len = udp_header_len();
        $src_port = udp_src_port($ip_len, @data);
        $dst_port = udp_dst_port($ip_len, @data);
        $header_len = $ip_len + $udp_len;
    } else {
        return;
    }

    $data = truncate_header($header_len, $data);
    $data = hex_to_ascii($data);
    $data = remove_control_code($data);

    print_packet($data, $time, $count, $src_ip, $dst_ip, $src_port, $dst_port);
}

sub ip_sanity_check {
    my($data) = @_;
    return $data =~ m|^4[0-9A-Fa-f]{3}| ? 1 : 0;
}

sub ip_header_len {
    my @data = @_;
    my $ip_len = hex($data[2 - 1]) * 2 * 4;
    return $ip_len;
}

sub ip_src {
    my @data = @_;
    my $src_ip = hex("$data[25 - 1]$data[26 - 1]") . '.' .
                 hex("$data[27 - 1]$data[28 - 1]") . '.' .
                 hex("$data[29 - 1]$data[30 - 1]") . '.' .
                 hex("$data[31 - 1]$data[32 - 1]");
    return $src_ip;
}

sub ip_dst {
    my @data = @_;
    my $dst_ip = hex("$data[33 - 1]$data[34 - 1]") . '.' .
                 hex("$data[35 - 1]$data[36 - 1]") . '.' .
                 hex("$data[37 - 1]$data[38 - 1]") . '.' .
                 hex("$data[39 - 1]$data[40 - 1]");
    return $dst_ip;
}

sub protocol_type {
    my @data = @_;
    my $proto = unpack("B8", pack("H2", "$data[19 - 1]$data[20 - 1]"));
    return $proto;
}

sub check_syn {
    my($ip_len, @data) = @_;
    my $tcp_flag = unpack("B4", pack("H1", $data[$ip_len + 28 - 1]));
    my @tcp_flag = split(m||, $tcp_flag);
    return $tcp_flag[2] eq '1' ? 1 : 0;
}

sub tcp_header_len {
    my($ip_len, @data) = @_;
    my $tcp_offset = $ip_len + 25;
    my $tcp_len = hex($data[$tcp_offset - 1]) * 2 * 4;
    return $tcp_len;
}

sub tcp_src_port {
    my($ip_len, @data) = @_;
    my $port_hex = $data[$ip_len + 1 - 1] . $data[$ip_len + 2 - 1] .
                   $data[$ip_len + 3 - 1] . $data[$ip_len + 4 - 1];
    my $src_port = hex($port_hex);
    return $src_port;
}

sub tcp_dst_port {
    my($ip_len, @data) = @_;
    my $port_hex = $data[$ip_len + 5 - 1] . $data[$ip_len + 6 - 1] .
                   $data[$ip_len + 7 - 1] . $data[$ip_len + 8 - 1];
    my $dst_port = hex($port_hex);
    return $dst_port;
}

sub udp_header_len {
    return 16;
}

sub udp_src_port {
    my($ip_len, @data) = @_;
    my $port_hex = $data[$ip_len + 1 - 1] . $data[$ip_len + 2 - 1] .
                   $data[$ip_len + 3 - 1] . $data[$ip_len + 4 - 1];
    my $src_port = hex($port_hex);
    return $src_port;
}

sub udp_dst_port {
    my($ip_len, @data) = @_;
    my $port_hex = $data[$ip_len + 5 - 1] . $data[$ip_len + 6 - 1] .
                   $data[$ip_len + 7 - 1] . $data[$ip_len + 8 - 1];
    my $dst_port = hex($port_hex);
    return $dst_port;
}

sub truncate_header {
    my($header_len, $data) = @_;
    $data =~ s|^.{$header_len}||;
    return $data;
}

sub hex_to_ascii {
    my($data) = @_;
    $data =~ s|[0-9A-Fa-f][0-9A-Fa-f]|pack("C1", hex($MATCH))|ge;
    return $data;
}

sub remove_control_code {
    my($data) = @_;
    $data =~ s|[\x00-\x07\x0e-\x1f\x80-\xff]||g;
    return $data;
}

sub print_packet {
    my($data, $time, $count, $src_ip, $dst_ip, $src_port, $dst_port) = @_;
    length $data > 0 or return;
    print "===== $count $time $src_ip:$src_port $dst_ip:$dst_port =====\r\n";
    print $data, "\r\n";
}

my $time;
my $buf = '';
my $len = 0;
my $padding = 0;
my $count = 0;
while(my $line = <STDIN>) {
    if($line =~ m|^\s+(?:0x[0-9a-f]{4}:\s+)?((?:[0-9a-f]{1,4} ?)+)(\s{2})?|) {
        $padding and next;
        $buf .= remove_sp($1);
        if($len > 0) {
            if(length $buf >= $len) {
                output($buf, $time, $count);
                $padding++;
            }
        } else {
            $len = get_total_len($buf);
        }
    } elsif($line =~ m|^(\d{2}:\d{2}:\d{2}\.\d+)|) {
        $count++;
        $time = $1;
        $buf = '';
        $len = 0;
        $padding = 0;
    }
}