File Coverage

blib/lib/Net/PcapWriter/TCP.pm
Criterion Covered Total %
statement 75 90 83.3
branch 44 78 56.4
condition 11 20 55.0
subroutine 11 14 78.5
pod 0 7 0.0
total 141 209 67.4


line stmt bran cond sub pod time code
1 7     7   57 use strict;
  7         15  
  7         224  
2 7     7   39 use warnings;
  7         14  
  7         300  
3              
4             package Net::PcapWriter::TCP;
5 7     7   1938 use fields qw(flow writer l2prefix pktmpl last_timestamp connected);
  7         11096  
  7         32  
6              
7 7     7   3159 use Net::PcapWriter::IP;
  7         24  
  7         3073  
8 7     7   48 use Socket qw(AF_INET IPPROTO_TCP);
  7         1257  
  7         7755  
9              
10             sub new {
11 2     2 0 8 my ($class,$writer,$src,$sport,$dst,$dport) = @_;
12 2         10 my $self = fields::new($class);
13             $self->{flow} = [
14             # src, dst, sport, dport, state, sn
15             # state = 0bFfSs: acked [F]in|send [f]in|acked [S]yn|send [s]yn
16             # sn gets initialized on sending SYN
17 2         5039 [ $src,$dst,$sport,$dport,0, undef ],
18             [ $dst,$src,$dport,$sport,0, undef ],
19             ];
20 2         8 $self->{writer} = $writer;
21 2         6 $self->{last_timestamp} = undef;
22 2         11 $self->{l2prefix} = $self->{writer}->layer2prefix($src);
23             $self->{pktmpl} = [
24 2         15 ip_packet( undef, $src, $dst, IPPROTO_TCP, 16),
25             ip_packet( undef, $dst, $src, IPPROTO_TCP, 16),
26             ];
27 2         11 return $self;
28             }
29              
30             sub write_with_flags {
31 16     16 0 42 my ($self,$dir,$data,$flags,$timestamp) = @_;
32 16   100     50 $flags ||= {};
33 16         36 my $flow = $self->{flow}[$dir];
34              
35 16 100 66     51 if ($flags->{syn} and ($flow->[4] & 0b0001) == 0) {
36 4         10 $flow->[4] |= 0b0001;
37 4   33     68 $flow->[5] ||= rand(2**32);
38             }
39 16         30 my $sn = $flow->[5];
40              
41 16 50       36 if ($flags->{rst}) {
42             # consider closed
43 0         0 $flow->[4] |= 0b1100;
44 0 0       0 $self->{flow}[$dir?0:1][4] |= 0b1100;
45             }
46 16 100       36 if ($flags->{fin}) {
47 4 50       45 if (($flow->[4] & 0b0100) == 0) {
48 4         9 $flow->[4] |= 0b0100;
49 4         36 $flow->[5]++
50             }
51             }
52 16 100       37 if ($flags->{ack}) {
53 8 100       32 my $oflow = $self->{flow}[$dir?0:1];
54 8 50       24 $flow->[4] |= 0b0010 if $oflow->[4] & 0b0001; # ACK the SYN
55 8 100       28 $flow->[4] |= 0b1000 if $oflow->[4] & 0b0100; # ACK the FIN
56             }
57              
58 16 50       33 return if ! defined $data; # only update state
59              
60 16 100       43 my $ack = $self->{flow}[$dir?0:1][5];
61 16 100       41 $flags->{ack} = 1 if defined $ack;
62              
63 16         29 my $f = 0;
64 16 50       36 $f |= 0b000100 if $flags->{rst};
65 16 50       38 $f |= 0b001000 if $flags->{psh};
66 16 100       37 $f |= 0b010000 if $flags->{ack};
67 16 50       74 $f |= 0b100000 if $flags->{urg};
68 16 100       36 $f |= 0b000001 if $flags->{fin};
69 16 100       33 if ( $flags->{syn} ) {
70 4         13 $f |= 0b000010;
71 4         17 $sn = ($sn-1) % 2**32;
72             }
73              
74             my $tcp = pack("nnNNCCnnna*",
75             $flow->[2],$flow->[3], # sport,dport
76             $sn, # sn
77             $ack||0, # ack
78             0x50, # size of TCP header >> 4
79             $f, # flags
80             $flags->{window} || 2**15, # window
81             0, # checksum computed later
82 16   100     145 $flags->{urg}||0, # urg pointer
      50        
      50        
83             $data # payload
84             );
85              
86 16         48 $flow->[5] = (
87             $flow->[5]
88             + length($data)
89             ) % 2**32;
90 16         31 $self->{last_timestamp} = $timestamp;
91             $self->{writer}->packet(
92 16         55 $self->{l2prefix} . $self->{pktmpl}[$dir]($tcp),
93             $timestamp
94             );
95             }
96              
97             sub write {
98 4     4 0 32 my ($self,$dir,$data,$timestamp) = @_;
99 4 100       22 _connect($self,$timestamp) if ! $self->{connected};
100 4         10 write_with_flags($self,$dir,$data,undef,$timestamp);
101             }
102              
103             sub _connect {
104 2     2   7 my ($self,$timestamp) = @_;
105 2         6 my $flow = $self->{flow};
106 2 50 33     12 goto done if ($flow->[1][4] & 0b11) == 0b11
107             && ($flow->[0][4] & 0b11) == 0b11;
108              
109             # client: SYN
110 2 50       17 write_with_flags($self,0,'',{ syn => 1 },$timestamp)
111             if ($flow->[0][4] & 0b01) == 0;
112              
113             # server: SYN+ACK
114 2 50       38 write_with_flags($self,1,'',{
    50          
    50          
115             ($flow->[1][4] & 0b01) == 0 ? ( syn => 1 ):(),
116             ($flow->[1][4] & 0b10) == 0 ? ( ack => 1 ):(),
117             },$timestamp) if ($flow->[1][4] & 0b11) != 0b11;
118              
119             # client: ACK
120 2 50       20 write_with_flags($self,0,'',{ ack => 1 },$timestamp)
121             if ($flow->[0][4] & 0b10) == 0;
122              
123             done:
124 2         11 $self->{connected} = 1;
125             }
126              
127             sub connect {
128 0     0 0 0 my ($self,$timestamp) = @_;
129 0 0       0 _connect($self,$timestamp) if ! $self->{connected};
130             }
131              
132             sub shutdown {
133 0     0 0 0 my ($self,$dir,$timestamp) = @_;
134 0 0       0 if (($self->{flow}[$dir][4] & 0b0100) == 0) {
135 0 0       0 _connect($self,$timestamp) if ! $self->{connected};
136 0         0 write_with_flags($self,$dir,'',{ fin => 1 },$timestamp);
137 0 0       0 write_with_flags($self,$dir ? 0:1,'',{ ack => 1 },$timestamp);
138             }
139             }
140              
141             sub close {
142 2     2 0 7 my ($self,$dir,$type,$timestamp) = @_;
143 2         6 my $flow = $self->{flow};
144              
145 2 50 33     16 if (!defined $type or $type eq '') {
    50          
    0          
146             # simulate close only - don't write any packets
147 0         0 $flow->[0][4] |= 0b1100;
148 0         0 $flow->[1][4] |= 0b1100;
149              
150             } elsif ($type eq 'fin') {
151             # $dir: FIN
152 2 50       15 write_with_flags($self,$dir,'',{ fin => 1 },$timestamp)
153             if ($flow->[$dir][4] & 0b0100) == 0;
154              
155             # $odir: FIN+ACK
156 2 50       12 my $odir = $dir?0:1;
157 2 50       25 write_with_flags($self,$odir,'',{
    50          
    50          
158             ($flow->[$odir][4] & 0b0100) == 0 ? ( fin => 1 ):(),
159             ($flow->[$odir][4] & 0b1000) == 0 ? ( ack => 1 ):(),
160             },$timestamp) if ($flow->[$odir][4] & 0b1100) != 0b1100;
161              
162             # $dir: ACK
163 2 50       19 write_with_flags($self,$dir,'',{ ack => 1 },$timestamp)
164             if ($flow->[$dir][4] & 0b1000) == 0;
165              
166             } elsif ($type eq 'rst') {
167             # single RST and then connection is closed
168 0         0 write_with_flags($self,$dir,'',{ rst => 1 },$timestamp);
169              
170             } else {
171 0         0 die "only fin|rst|undef are allowed with close"
172             }
173             }
174              
175             sub ack {
176 0     0 0 0 my ($self,$dir,$timestamp) = @_;
177 0         0 write_with_flags($self,$dir,'',{ ack => 1 },$timestamp);
178             }
179              
180             sub DESTROY {
181 2     2   18 my $self = shift;
182 2 50       9 $self->{writer} or return; # happens in global destruction
183 2         10 &close($self,0,'fin',$self->{last_timestamp});
184             }
185              
186              
187             1;
188              
189