Last active
August 29, 2015 13:56
-
-
Save hirose31/9182442 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env perl | |
use strict; | |
use warnings; | |
use Data::Dumper; | |
BEGIN { | |
sub p($) { | |
local $Data::Dumper::Indent = 1; | |
local $Data::Dumper::Deepcopy = 1; | |
local $Data::Dumper::Sortkeys = 1; | |
local $Data::Dumper::Terse = 1; | |
local $Data::Dumper::Useqq = 1; | |
local $Data::Dumper::Quotekeys = 0; | |
print STDERR Dumper($_[0]); | |
} | |
} | |
use Net::Pcap; | |
use Net::Pcap::Reassemble; | |
use NetPacket::Ethernet; | |
use NetPacket::IP; | |
use NetPacket::TCP; | |
use POSIX qw(strftime); | |
sub debug { | |
return unless $ENV{DEBUG}; | |
my @m = @_; | |
chomp @m; | |
print STDERR "@_\n"; | |
} | |
MAIN: { | |
my $pcapfile = shift @ARGV; | |
if (! -r $pcapfile) { | |
die "Cannot read: $pcapfile"; | |
} | |
my $err; | |
my $pcap = Net::Pcap::open_offline($pcapfile, \$err) | |
or die "open_offline: $err"; | |
### We can apply extra filters | |
# my $filter; | |
# pcap_compile($pcap, \$filter, "tcp port 6379", 1, 0); | |
# pcap_setfilter($pcap, $filter); | |
my $stream = {}; # need to initialize to pass reference of THIS variable | |
Net::Pcap::Reassemble::loop($pcap, -1, \&process_packet, { | |
stream => $stream, | |
}); | |
# p $stream; | |
for my $tcp (sort { | |
$a->{start}[0]+$a->{start}[1]/1000000 | |
<=> | |
$b->{start}[0]+$b->{start}[1]/1000000 | |
} values %$stream) { | |
printf("%s.%06d\n", | |
strftime("%Y-%m-%d %H:%M:%S", localtime( $tcp->{start}[0] )), | |
$tcp->{start}[1], | |
); | |
print "req:\n".join(' ', @{ $tcp->{data}{req} })."\n"; | |
print "res:\n".join(' ', @{ $tcp->{data}{res} })."\n"; | |
} | |
exit; | |
} | |
sub process_packet { | |
my($opt, $header, $packet) = @_; | |
my $stream = $opt->{stream}; | |
my $eth = NetPacket::Ethernet->decode($packet); | |
if ($eth->{type} != NetPacket::Ethernet::ETH_TYPE_IP) { | |
warn "Not a IP ethernet frame: $eth->{type}"; | |
return; | |
} | |
my $ip = NetPacket::IP->decode($eth->{data}); | |
if ($ip->{proto} != NetPacket::IP::IP_PROTO_TCP) { | |
warn "Not a TCP packet: $ip->{proto}"; | |
return; | |
} | |
my $tcp = NetPacket::TCP->decode($ip->{data}); | |
### rebuild TCP stream from TCP packets | |
if ($tcp->{flags} & SYN && !($tcp->{flags} & ACK)) { | |
my $next_seqnum = $tcp->{seqnum} + 1; | |
$stream->{ $next_seqnum } = { | |
start => [ $header->{tv_sec}, $header->{tv_usec} ], | |
end => [ 0, 0 ], | |
state => 'syn', | |
data => { req => [], res => [] }, | |
}; | |
} elsif ($tcp->{flags} & SYN && $tcp->{flags} & ACK) { | |
if (exists $stream->{ $tcp->{acknum} }) { | |
$stream->{ $tcp->{acknum} }{state} = 'syn+ack'; | |
} else { | |
debug("Got syn+ack but no syn packet"); | |
} | |
} elsif ($tcp->{flags} & ACK && length($tcp->{data}) > 0) { | |
if (exists $stream->{ $tcp->{seqnum} }) { | |
# client -> server | |
my $next_seqnum = $tcp->{seqnum} + length($tcp->{data}); | |
$stream->{ $next_seqnum } = delete $stream->{ $tcp->{seqnum} }; | |
$stream->{ $next_seqnum }{state} = 'ack'; | |
push @{ $stream->{ $next_seqnum }{data}{req} }, $tcp->{data}; | |
} elsif (exists $stream->{ $tcp->{acknum} }) { | |
# server -> client | |
my $next_seqnum = $tcp->{acknum}; | |
$stream->{ $next_seqnum } = delete $stream->{ $tcp->{acknum} }; | |
$stream->{ $next_seqnum }{state} = 'ack'; | |
push @{ $stream->{ $next_seqnum }{data}{res} }, $tcp->{data}; | |
} else { | |
debug("Got ack but no syn+ack packet"); | |
} | |
} elsif ($tcp->{flags} & ACK && $tcp->{flags} & FIN) { | |
if (exists $stream->{ $tcp->{seqnum} }) { | |
my $next_seqnum = $tcp->{seqnum} + 1; | |
$stream->{ $next_seqnum } = delete $stream->{ $tcp->{seqnum} }; | |
$stream->{ $next_seqnum }{state} = 'fin1'; | |
} elsif (exists $stream->{ $tcp->{acknum} }) { | |
my $next_seqnum = $tcp->{acknum}; | |
$stream->{ $next_seqnum }{state} = 'fin2'; | |
} else { | |
debug("Got unknown FIN packet"); | |
} | |
} elsif ($tcp->{flags} & ACK && length($tcp->{data}) == 0) { | |
if (exists $stream->{ $tcp->{seqnum} }) { | |
my $next_seqnum = $tcp->{seqnum}; | |
$stream->{ $next_seqnum }{state} = 'closed'; | |
$stream->{ $next_seqnum }{end} = [ $header->{tv_sec}, $header->{tv_usec} ]; | |
} else { | |
; # no problem, multiple ack | |
} | |
} | |
} | |
__END__ | |
### Cannot parse a pcap file generated by "-i any", so must specify correct interface name. | |
# tcpdump -i lo -s65535 tcp port 11211 -w memcached.pcap | |
(access to 127.0.0.1:11211) | |
$ parse-pcap.pl memcached.pcap | |
2014-02-24 14:11:58.451993 | |
req: | |
set foo 0 0 3 | |
FOO | |
res: | |
STORED | |
2014-02-24 14:11:59.732226 | |
req: | |
get foo | |
res: | |
VALUE foo 0 3 | |
FOO | |
END | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment