File Coverage

blib/lib/Net/PcapWriter/TCP.pm
Criterion Covered Total %
statement 76 95 80.0
branch 45 82 54.8
condition 11 20 55.0
subroutine 11 15 73.3
pod 0 8 0.0
total 143 220 65.0


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