File Coverage

blib/lib/Net/PcapWriter/TCP.pm
Criterion Covered Total %
statement 73 86 84.8
branch 42 72 58.3
condition 11 20 55.0
subroutine 11 13 84.6
pod 0 6 0.0
total 137 197 69.5


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