File Coverage

blib/lib/App/SilverSplash/IPTables.pm
Criterion Covered Total %
statement 32 156 20.5
branch 0 24 0.0
condition 7 17 41.1
subroutine 8 27 29.6
pod 0 16 0.0
total 47 240 19.5


line stmt bran cond sub pod time code
1             package App::SilverSplash::IPTables;
2              
3 1     1   7 use strict;
  1         3  
  1         130  
4 1     1   7 use warnings;
  1         3  
  1         39  
5              
6 1     1   5 use base 'App::SilverSplash';
  1         2  
  1         109  
7              
8 1     1   6 use Data::Dumper qw(Dumper);
  1         2  
  1         51  
9              
10 1     1   6 use Config::SL ();
  1         2  
  1         24  
11 1     1   3249 use URI::Escape ();
  1         2339  
  1         132  
12              
13 1   50 1   10 use constant DEBUG => $ENV{SL_DEBUG} || 0;
  1         2  
  1         486  
14              
15             our (
16             $Config, $Iptables, $Wan_if, %tables_chains,
17             $Lan_if, $Perlbal_port, $Mark_op, $Lease_file,
18             $Gateway_ip, $Wan_ip, $Wan_mac,
19             );
20              
21             BEGIN {
22 1     1   12 $Config = Config::SL->new;
23 1   50     12953 $Iptables = $Config->sl_iptables || die 'oops';
24 1   50     66 $Perlbal_port = $Config->sl_perlbal_port || die 'oops';
25              
26             # wan and lan interfaces
27 1   50     71 $Wan_if = $Config->sl_wan_if || die 'oops';
28 1   50     35 $Lan_if = $Config->sl_lan_if || die 'oops';
29              
30 1         6471 ($Gateway_ip) = `/sbin/ifconfig $Lan_if` =~ m/inet addr:(\S+)/;
31 1         6234 ($Wan_ip) = `/sbin/ifconfig $Wan_if` =~ m/inet addr:(\S+)/;
32 1         6437 ($Wan_mac) = `/sbin/ifconfig $Wan_if` =~ m/HWaddr\s(\S+)/;
33              
34 1   50     99 $Mark_op = $Config->sl_mark_op || die 'oops';
35 1   50     91 $Lease_file = $Config->sl_dhcp_lease_file || die 'oops';
36              
37 1         5348 %tables_chains = (
38             filter => [qw( slAUT slAUTads slNET slRTR )],
39             mangle => [qw( slBLK slINC slOUT slTRU )],
40             nat => [qw( slOUT )],
41             );
42              
43             }
44              
45             our $Blocked_mark = '0x100';
46             our $Trusted_mark = '0x200';
47             our $Paid_mark = '0x400';
48             our $Ads_mark = '0x500';
49              
50             sub load_allows {
51 0     0 0   my ( $class, $file ) = @_;
52              
53 0           my $fh;
54 0 0         open( $fh, '<', $Config->sl_root . "/conf/$file" ) or die $!;
55 0           my $ct = do { local $/; <$fh> };
  0            
  0            
56 0 0         close($fh) or die $!;
57              
58 0           my @lines = split( /\n/, $ct );
59 0           @lines = grep { $_ =~ m/\S/ }
  0            
60 0           grep { $_ !~ /#/ } # skip comments
61 0           grep { defined $_ } @lines; # skip undef
62              
63 0           return \@lines;
64             }
65              
66             sub init_firewall {
67 0     0 0   my $class = shift;
68              
69 0           `echo 1 > /proc/sys/net/ipv4/ip_forward`;
70              
71             # flush the existing firewall
72 0           $class->clear_firewall();
73              
74             # create the chains
75 0           foreach my $table ( sort keys %tables_chains ) {
76 0           foreach my $chain ( @{ $tables_chains{$table} } ) {
  0            
77              
78 0           iptables("-t $table -N $chain");
79             }
80             }
81              
82             # walled garden exceptions
83 0           my $hosts_allow = $class->load_allows('cp_hosts_allow.txt');
84 0           my $sslhosts_allow = $class->load_allows('cp_sslhosts_allow.txt');
85 0           my $accept = "slNET -d %s -p tcp -m tcp --dport %d -j ACCEPT";
86              
87 0           my $slout = "slOUT -d %s -p tcp -m tcp --dport %d -j ACCEPT";
88              
89 0           my $hosts_accept =
90 0           join( "\n", map { sprintf( $accept, $_, 80 ) } @{$hosts_allow} );
  0            
91              
92 0           my $sslhosts_accept =
93 0           join( "\n", map { sprintf( $accept, $_, 443 ) } @{$sslhosts_allow} );
  0            
94              
95 0           my $hosts_slout =
96 0           join( "\n", map { sprintf( $slout, $_, 80 ) } @{$hosts_allow} );
  0            
97              
98 0           my $sslhosts_slout =
99 0           join( "\n", map { sprintf( $slout, $_, 443 ) } @{$sslhosts_allow} );
  0            
100              
101             ##############################
102             # add the filter default chains
103 0           my $filters = <<"FILTERS";
104             INPUT -i $Lan_if -j slRTR
105              
106             FORWARD -i $Lan_if -j slNET
107              
108             slAUT --protocol tcp --source-port ! 25 -j ACCEPT
109              
110             slAUTads -m state --state RELATED,ESTABLISHED -j ACCEPT
111             slAUTads -p tcp -m tcp --dport 22 -j ACCEPT
112             slAUTads -p tcp -m tcp --dport 80 -j ACCEPT
113             slAUTads -p tcp -m tcp --dport 110 -j ACCEPT
114             slAUTads -p tcp -m tcp --dport 143 -j ACCEPT
115             slAUTads -p tcp -m tcp --dport 443 -j ACCEPT
116             slAUTads -p tcp -m tcp --dport 465 -j ACCEPT
117             slAUTads -p udp -m udp --dport 500 -j ACCEPT
118             slAUTads -p tcp -m tcp --dport 587 -j ACCEPT
119             slAUTads -p tcp -m tcp --dport 993 -j ACCEPT
120             slAUTads -p tcp -m tcp --dport 995 -j ACCEPT
121             slAUTads -p tcp -m tcp --dport 1723 -j ACCEPT
122             slAUTads -p udp -m udp --dport 1701 -j ACCEPT
123             slAUTads -p tcp -m tcp --dport 3389 -j ACCEPT
124             slAUTads -p tcp -m tcp --dport 5050 -j ACCEPT
125             slAUTads -p tcp -m tcp --dport 5190 -j ACCEPT
126             slAUTads -p tcp -m tcp --dport 5222 -j ACCEPT
127             slAUTads -p tcp -m tcp --dport 5223 -j ACCEPT
128              
129             slNET -m mark --mark $Blocked_mark/0x700 -j DROP
130             slNET -m state --state INVALID -j DROP
131             slNET -p tcp -m tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu
132             slNET -m mark --mark $Trusted_mark/0x700 -j ACCEPT
133             slNET -m mark --mark $Paid_mark/0x700 -j slAUT
134             slNET -m mark --mark $Ads_mark/0x700 -j slAUTads
135             slNET -p icmp -j REJECT --reject-with icmp-port-unreachable
136             $hosts_accept
137             $sslhosts_accept
138             slNET -j DROP
139              
140             slRTR -m mark --mark $Blocked_mark/0x700 -j DROP
141             slRTR -m state --state INVALID -j DROP
142             slRTR -m state --state RELATED,ESTABLISHED -j ACCEPT
143             slRTR -p tcp -m tcp ! --tcp-option 2 --tcp-flags SYN SYN -j DROP
144             slRTR -p tcp -m tcp --dport $Perlbal_port -j ACCEPT
145             slRTR -m mark --mark $Trusted_mark/0x700 -j ACCEPT
146             slRTR -p udp -m udp -s 10.69.0.1/16 --dport 53 -j ACCEPT
147             slRTR -p udp -m udp -s 10.69.0.1/16 --dport 67 -j ACCEPT
148             slRTR -p udp -m udp -s 10.69.0.1/16 --dport 68 -j ACCEPT
149             slRTR -p tcp -m tcp -s 10.69.0.1/16 --dport 20022 -j ACCEPT
150             slRTR -p icmp -s 10.69.0.1/16 -j ACCEPT
151             FILTERS
152              
153 0           add_rules( 'filter', $filters );
154              
155             #############################
156             # default mangle chains
157 0           my $mangles = <<"MANGLES";
158             PREROUTING -i $Lan_if -j slOUT
159             PREROUTING -i $Lan_if -j slBLK
160             PREROUTING -i $Lan_if -j slTRU
161             POSTROUTING -o $Lan_if -j slINC
162             MANGLES
163              
164 0           add_rules( 'mangle', $mangles );
165              
166             #############################
167             # default nat chains
168 0           my $nats = <<"NATS";
169             PREROUTING -i $Lan_if -j slOUT
170             POSTROUTING -o $Wan_if -j MASQUERADE
171              
172             slOUT -m mark --mark $Trusted_mark/0x700 -j ACCEPT
173             slOUT -m mark --mark $Paid_mark/0x700 -j ACCEPT
174             slOUT -m mark --mark $Ads_mark/0x700 -j ACCEPT
175             $hosts_slout
176             $sslhosts_slout
177             slOUT -p tcp -m tcp --dport 80 -j DNAT --to-destination $Gateway_ip:$Perlbal_port
178             slOUT -p tcp -m tcp --dport 443 -j DNAT --to-destination $Gateway_ip:$Perlbal_port
179             slOUT -p udp --dport 53 -d $Gateway_ip -j ACCEPT
180             slOUT -p udp --dport 67 -j ACCEPT
181             slOUT -p udp --dport 68 -j ACCEPT
182             slOUT -p tcp --dport 20022 -d $Gateway_ip -j ACCEPT
183             slOUT -p tcp --dport $Perlbal_port -d $Gateway_ip -j ACCEPT
184             slOUT -j DROP
185             NATS
186              
187 0           add_rules( 'nat', $nats );
188              
189             # trusted hosts
190 0           my $trusted_hosts = $class->load_allows('trusted_hosts.txt');
191 0           my $out_rule =
192             "-t mangle -A slTRU -m mac --mac-source %s -j MARK $Mark_op $Trusted_mark";
193              
194             # TODO - arp translation
195             #my $out_rule = "-t mangle -A slTRU -s %s -m mac --mac-source %s -j MARK $Mark_op $Trusted_mark";
196             #my $in_rule = "-t mangle -A slINC -d %s -j ACCEPT";
197              
198 0           foreach my $mac ( @{$trusted_hosts} ) {
  0            
199              
200             # TODO - arp translation
201             # my $ip = App::SilverSplash->ip_from_mac($mac);
202             # iptables($in_rule, $ip);
203 0           iptables( sprintf( $out_rule, $mac ) );
204             }
205              
206             }
207              
208             sub add_rules {
209 0     0 0   my ( $table, $rules ) = @_;
210              
211 0           foreach my $rule ( split( /\n/, $rules ) ) {
212              
213 0           chomp($rule);
214 0 0         next unless $rule =~ m/\S/; # skip blanks
215 0           warn("$$ Adding rule $rule to table $table\n") if DEBUG;
216 0           iptables("-t $table -A $rule");
217             }
218             }
219              
220             sub clear_firewall {
221 0     0 0   my $class = shift;
222              
223             # clear all tables
224 0           iptables("-t $_ -F") for keys %tables_chains;
225              
226             # clear all chains
227 0           iptables("-t $_ -X") for keys %tables_chains;
228              
229             # reset the postrouting rule - unsure if this is needed
230             # iptables("-t nat -A POSTROUTING -o $Wan_if -j MASQUERADE");
231             }
232              
233             sub iptables {
234 0     0 0   my $cmd = shift;
235 0 0 0       system("sudo $Iptables $cmd") == 0
236             or require Carp
237             && Carp::confess "could not $Iptables '$cmd', err: $!, ret: $?\n";
238              
239 0           return 1;
240             }
241              
242             sub fixup_access {
243 0     0 0   my ( $class, $mac, $ip, $type ) = @_;
244              
245 0           my $uc_mac = uc($mac);
246 0           my $iptables_rule = `sudo $Iptables -t mangle -L -v`;
247              
248             # see if the mac address is in a rule
249 0           my ($iptables_ip) = $iptables_rule =~
250             m/(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}).*?MAC\s+$uc_mac/i;
251              
252 0           my $chain = "_$type\_chain";
253 0 0         if ( !$iptables_ip ) {
    0          
    0          
254              
255 0           warn("no rule for authed mac $mac, adding") if DEBUG;
256 0           $class->$chain( 'A', $mac, $ip );
257              
258             }
259             elsif ( $ip ne $iptables_ip ) {
260 0           warn("iptables rules don't match, updating") if DEBUG;
261              
262             # dhcp lease probably expired, delete old rule, create new rule
263 0           my $delete = "delete_from_$type\_chain";
264 0           $class->$delete( $mac, $iptables_ip );
265 0           $class->$chain( 'A', $mac, $ip );
266             }
267             elsif ( $ip eq $iptables_ip ) {
268              
269             # no-op
270             }
271 0           return 1;
272             }
273              
274             sub paid_users {
275 0     0 0   my ($class) = @_;
276              
277 0           return $class->users($Paid_mark);
278             }
279              
280             sub ads_users {
281 0     0 0   my ($class) = @_;
282              
283 0           return $class->users($Ads_mark);
284             }
285              
286             sub users {
287 0     0 0   my ( $class, $mark ) = @_;
288              
289 0           my @users =
290 0           map { [ $_ =~ m/(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}).*?MAC\s(\S+)\s/ ] }
291 0           grep { $_ =~ m/(?:$mark)/ }
292             split( '\n', `sudo $Iptables -t mangle --list` );
293              
294 0           return @users;
295             }
296              
297             sub _paid_chain {
298 0     0     my ( $class, $op, $mac, $ip ) = @_;
299 0           iptables(
300             "-t mangle -$op slOUT -s $ip -m mac --mac-source $mac -j MARK $Mark_op $Paid_mark"
301             );
302 0           iptables("-t mangle -$op slINC -d $ip -j ACCEPT");
303             }
304              
305             sub add_to_paid_chain {
306 0     0 0   my ( $class, $mac, $ip ) = @_;
307              
308 0           my $esc_mac = URI::Escape::uri_escape($mac);
309              
310             # convert minutes to seconds
311 0           my $stay = time() + 240 * 60; # 4 hours
312 0           $class->set( $mac => "$stay|paid" );
313              
314 0           warn("cache set $mac => $stay") if DEBUG;
315              
316             # add the mac to the paid chain
317 0           return $class->_paid_chain( 'A', $mac, $ip );
318             }
319              
320             sub delete_from_paid_chain {
321 0     0 0   my ( $class, $mac, $ip ) = @_;
322              
323 0           return $class->_paid_chain( 'D', $mac, $ip );
324             }
325              
326             sub check_paid_chain_for_mac {
327 0     0 0   my ( $class, $mac ) = @_;
328              
329 0           return $class->_check_chain_for_mac( $Paid_mark, $mac );
330             }
331              
332             sub _check_chain_for_mac {
333 0     0     my ( $class, $mark, $mac ) = @_;
334              
335 0           $mac = uc($mac);
336              
337 0           my @lines = split( '\n', `sudo $Iptables -t mangle --list` );
338              
339 0           my $ip;
340 0           foreach my $line (@lines) {
341              
342 0 0         next unless $line =~ m/^MARK/;
343             last
344 0 0         if ($ip) =
345             $line =~ m/(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}).*?MAC\s+$mac/i;
346             }
347              
348 0 0         return unless $ip;
349 0           return $ip;
350             }
351              
352             sub check_ads_chain_for_mac {
353 0     0 0   my ( $class, $mac ) = @_;
354              
355 0           return $class->_check_chain_for_mac( $Ads_mark, $mac );
356             }
357              
358             sub add_to_ads_chain {
359 0     0 0   my ( $class, $mac, $ip ) = @_;
360              
361 0           my $esc_mac = URI::Escape::uri_escape($mac);
362              
363             # convert minutes to seconds
364 0           my $stay = time() + $Config->sl_visitor_limit * 60;
365 0           $class->set( $mac => "$stay|ads" );
366              
367 0           warn("cache set $mac => $stay") if DEBUG;
368 0           return $class->_ads_chain( 'A', $mac, $ip );
369             }
370              
371             sub delete_from_ads_chain {
372 0     0 0   my ( $class, $mac, $ip ) = @_;
373              
374 0           return $class->_ads_chain( 'D', $mac, $ip );
375             }
376              
377             sub _ads_chain {
378 0     0     my ( $class, $op, $mac, $ip ) = @_;
379              
380 0           iptables(
381             "-t mangle -$op slOUT -s $ip -m mac --mac-source $mac -j MARK $Mark_op $Ads_mark"
382             );
383              
384 0           iptables("-t mangle -$op slINC -d $ip -j ACCEPT");
385             }
386              
387             sub check_overage {
388 0     0 0   my ( $class, $mac, $ip ) = @_;
389              
390 0           my $in = `$Iptables -t mangle -n -v -x -L slINC`;
391 0           my $out = `$Iptables -t mangle -n -v -x -L slOUT`;
392              
393             # check the megabyte limits first
394 0           my ($bytes_in) = $in =~ m/\d+\s+(\d+).*?$ip/;
395 0 0         return 1 if $bytes_in > $Config->sl_down_overage;
396              
397 0           my ($bytes_out) = $out =~ m/\d+\s+(\d+).*?$ip/;
398 0 0         return 1 if $bytes_out > $Config->sl_up_overage;
399              
400 0           return;
401             }
402              
403             1;