File Coverage

lib/Linux/PacketFilter.pm
Criterion Covered Total %
statement 48 48 100.0
branch 4 4 100.0
condition 5 7 71.4
subroutine 8 8 100.0
pod 2 2 100.0
total 67 69 97.1


line stmt bran cond sub pod time code
1             package Linux::PacketFilter;
2              
3 2     2   115307 use strict;
  2         5  
  2         46  
4 2     2   8 use warnings;
  2         4  
  2         424  
5              
6             our $VERSION = '0.01_2';
7              
8             =encoding utf-8
9              
10             =head1 NAME
11              
12             Linux::PacketFilter - Simple interface to Linux packet filtering
13              
14             =head1 SYNOPSIS
15              
16             # Reject any packet that starts with a period:
17             my $filter = Linux::PacketFilter->new(
18              
19             # Load the accumulator with the 1st byte in the packet:
20             [ 'ld b abs', 0 ],
21              
22             # If the accumulator value is an ASCII period, continue;
23             # otherwise, skip a line.
24             [ 'jmp jeq k', ord('.'), 0, 1 ],
25              
26             # If we continued, we’ll get here and thus reject the packet.
27             [ ret => 0 ],
28              
29             # If we get here, we skipped a line above. That means
30             # the packet’s first byte wasn’t an ASCII period,
31             # so we'll return the full packet.
32             [ ret => 0xffffffff ],
33             );
34              
35             $filter->apply( $socket );
36              
37             =head1 DESCRIPTION
38              
39             This module is a simple, small, pure-Perl compiler for Linux’s
40             “classic” Berkeley Packet Filter (BPF) implementation.
41              
42             =head1 HOW TO USE THIS MODULE
43              
44             If you’re familiar with BPF already, the SYNOPSIS above should mostly make
45             sense “out-of-the-box”. If you’re new to BPF, though, take heart; it’s
46             fairly straightforward.
47              
48             The best source I have found for learning about BPF itself is
49             L;
50             see the section entitled B.
51              
52             Linux-specific implementation notes are available in the kernel
53             source tree at L. This contains a lot of detail
54             about uses for BPF that don't pertain to packet filtering, though.
55              
56             L Take
57             especial note of the need to convert between network and host byte order.
58             (See below for a convenience that this module provides for this conversion.)
59              
60             You might also take interest in L.
61              
62             B This module works with Linux’s I<“classic”> BPF, not the
63             much more powerful (and complex) “extended” BPF.
64              
65             =cut
66              
67             my %BPF;
68              
69             sub _populate_BPF {
70 2     2   12 %BPF = (
71             w => 0x00, # 32-bit word
72             h => 0x08, # 16-bit half-word
73             b => 0x10, # 8-bit byte
74             # dw => 0x18, # 64-bit double word
75              
76             k => 0x00, # given constant
77             x => 0x08, # index register
78              
79             # Conveniences:
80             k_n => 0x00,
81             k_N => 0x00,
82             );
83              
84             # ld = to accumulator
85             # ldx = to index
86             # st = accumulator to scratch[k]
87             # stx = index to scratch[k]
88 2         7 my @inst = qw( ld ldx st stx alu jmp ret misc );
89 2         7 for my $i ( 0 .. $#inst ) {
90 16         31 $BPF{ $inst[$i] } = $i;
91             }
92              
93             # Load accumulator:
94             # imm = k
95             # abs = offset into packet
96             # ind = index + k
97             # mem = scratch[k]
98             # len = packet length
99             # msh = IP header length (hack ..)
100 2         6 my @code = qw( imm abs ind mem len msh );
101 2         14 for my $i ( 0 .. $#code ) {
102 12         26 $BPF{ $code[$i] } = ( $i << 5 );
103             }
104              
105 2         8 my @alu = qw( add sub mul div or and lsh rsh neg mod xor );
106 2         5 for my $i ( 0 .. $#alu ) {
107 22         36 $BPF{ $alu[$i] } = ( $i << 4 );
108             }
109              
110             # ja = move forward k
111             # jeq = move (A == k) ? jt : jf
112             # jset = (A & k)
113 2         17 my @j = qw( ja jeq jgt jge jset );
114 2         6 for my $i ( 0 .. $#j ) {
115 10         17 $BPF{ $j[$i] } = ( $i << 4 );
116             }
117              
118 2         6 return;
119             }
120              
121             =head1 METHODS
122              
123             =head2 $obj = I->new( @filters )
124              
125             Creates an object that represents an array of instructions for
126             the BPF filter machine. Each @filters member is an array reference
127             that represents a single instruction and has either 2 or 4 members,
128             which correspond with the BPF_STMT and BPF_JUMP macros, respectively.
129              
130             The first member of each array reference is, rather than a number,
131             a space-separated string of options, lower-cased and without the
132             leading C. So where in C you would write:
133              
134             BPF_LD | BPF_W | BPF_ABS
135              
136             … in this module you write:
137              
138             'ld w abs'
139              
140             The full list of options for a single instruction is:
141              
142             =over
143              
144             =item * C, C, C
145              
146             =item * C, C, C, C (See below for
147             an explanation of the last two.)
148              
149             =item * C, C, C, C, C, C, C, C
150              
151             =item * C, C, C, C, C, C
152              
153             =item * C, C, C, C
, C, C, C, C,
154             C, C, C
155              
156             =item * C, C, C, C, C
157              
158             =back
159              
160             =head3 Byte order conversion
161              
162             Since it’s common to need to do byte order conversions with
163             packet filtering, Linux::PacketFilter adds a convenience for this:
164             the codes C and C indicate to encode the given constant value
165             in 16-bit or 32-bit network byte order, respectively. These have the same
166             effect as calling C and C in C.
167              
168             B Linux’s exact behavior regarding byte order in BPF isn’t
169             always clear, and this module is only tested thus far on little-endian
170             systems. It seems that only certain operations, like C, require the
171             conversion.
172              
173             =cut
174              
175 2     2   14 use constant _is_big_endian => pack('n', 1) eq pack('S', 1);
  2         4  
  2         176  
176              
177             use constant {
178 2         171 _INSTR_PACK => 'S CC L',
179              
180             _NETWORK_INSTR_PACK => {
181             'k_n' => _is_big_endian ? 'S CC N' : 'S CC n x2',
182             'k_N' => 'S CC N',
183             },
184              
185             _ARRAY_PACK => 'S x![P] P',
186 2     2   13 };
  2         2  
187              
188 2     2   12 use constant _INSTR_LEN => length( pack _INSTR_PACK() );
  2         10  
  2         577  
189              
190             sub new {
191 7     7 1 16857 my $class = shift;
192              
193 7 100       28 _populate_BPF() if !%BPF;
194              
195 7         23 my $buf = ("\0" x (_INSTR_LEN() * @_));
196              
197 7         9 my $f = 0;
198              
199 7         15 for my $filter (@_) {
200 26         47 my $code = 0;
201              
202 26         29 my $tmpl;
203              
204 26         116 for my $piece ( split m<\s+>, $filter->[0] ) {
205 64   50     121 $code |= ($BPF{$piece} // die "Unknown BPF option: “$piece”");
206              
207 64   66     145 $tmpl ||= _NETWORK_INSTR_PACK()->{$piece};
208             }
209              
210             substr(
211             $buf, $f, _INSTR_LEN(),
212             pack(
213             ( $tmpl || _INSTR_PACK() ),
214             $code,
215 26 100 100     116 (@$filter == 2) ? (0, 0, $filter->[1]) : @{$filter}[2, 3, 1],
  6         17  
216             ),
217             );
218              
219 26         43 $f += _INSTR_LEN();
220             }
221              
222 7         41 return bless [ pack(_ARRAY_PACK(), 0 + @_, $buf), $buf ], $class;
223             }
224              
225             =head2 $ok = I->attach( $SOCKET )
226              
227             Attaches the filter instructions to the given $SOCKET.
228              
229             Note that this class purposely omits public access to the value that
230             is given to the underlying L system call. This is because
231             that value contains a pointer to a Perl string. That pointer is only valid
232             during this object’s lifetime, and bad stuff (e.g., segmentation faults)
233             can happen when you give the kernel pointers to strings that Perl has
234             already garbage-collected.
235              
236             The return is the same as the underlying call to Perl’s
237             L built-in. C<$!> is set as that function leaves it.
238              
239             =cut
240              
241             sub attach {
242 6     6 1 49 my ($self, $socket) = @_;
243              
244             # For no good reason, Perl require() clobbers $@ and $!.
245 6         11 do {
246 6         36 local ($@, $!);
247 6         45 require Socket;
248             };
249              
250 6         6052 return setsockopt $socket, Socket::SOL_SOCKET(), Socket::SO_ATTACH_FILTER(), $self->[0];
251             }
252              
253             #----------------------------------------------------------------------
254              
255             1;
256              
257             =head1 AUTHOR
258              
259             Copyright 2019 Gasper Software Consulting (L)
260              
261             =head1 SEE ALSO
262              
263             L suits a similar purpose to this
264             module’s but appears to be geared solely toward PF_PACKET sockets.
265             It also defines its own language for specifying the filters, which I find
266             less helpful than this module’s approach of “porting” the C macros
267             to Perl, thus better capitalizing on existing documention.