2007-03-15 22:55:22 +01:00
#
2007-12-01 18:45:43 +01:00
# Shorewall-perl 4.1 -- /usr/share/shorewall-perl/Shorewall/Tc.pm
2007-03-15 22:55:22 +01:00
#
2007-09-08 18:09:51 +02:00
# This program is under GPL [http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt]
2007-03-15 22:55:22 +01:00
#
# (c) 2007 - Tom Eastep (teastep@shorewall.net)
#
2007-03-20 00:29:22 +01:00
# Traffic Control is from tc4shorewall Version 0.5
# (c) 2005 Arne Bernin <arne@ucbering.de>
# Modified by Tom Eastep for integration into the Shorewall distribution
# published under GPL Version 2#
#
2007-03-15 22:55:22 +01:00
# Complete documentation is available at http://shorewall.net
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of Version 2 of the GNU General Public License
# as published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
2007-09-08 18:09:51 +02:00
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
2007-03-15 22:55:22 +01:00
#
2007-04-19 01:55:25 +02:00
# This module deals with Traffic Shaping and the tcrules file.
2007-03-15 22:55:22 +01:00
#
2007-03-15 02:10:56 +01:00
package Shorewall::Tc ;
require Exporter ;
2007-11-16 00:24:54 +01:00
use Shorewall::Config qw( :DEFAULT :internal ) ;
2008-03-16 17:51:53 +01:00
use Shorewall::IPAddrs ;
2007-03-15 02:10:56 +01:00
use Shorewall::Zones ;
2007-11-16 00:24:54 +01:00
use Shorewall::Chains qw( :DEFAULT :internal ) ;
2007-03-21 00:13:17 +01:00
use Shorewall::Providers ;
2007-03-15 02:10:56 +01:00
use strict ;
our @ ISA = qw( Exporter ) ;
2007-03-21 00:13:17 +01:00
our @ EXPORT = qw( setup_tc ) ;
2007-06-14 01:02:39 +02:00
our @ EXPORT_OK = qw( process_tc_rule initialize ) ;
2008-02-06 00:50:00 +01:00
our $ VERSION = 4.1 .5 ;
2007-03-15 02:10:56 +01:00
2007-06-05 18:49:13 +02:00
our % tcs = ( T = > { chain = > 'tcpost' ,
connmark = > 0 ,
fw = > 1
} ,
2007-04-30 19:55:43 +02:00
CT = > { chain = > 'tcpost' ,
2007-03-15 02:10:56 +01:00
target = > 'CONNMARK --set-mark' ,
connmark = > 1 ,
2007-03-27 01:17:46 +02:00
fw = > 1
2007-03-15 02:10:56 +01:00
} ,
2007-04-30 19:55:43 +02:00
C = > { target = > 'CONNMARK --set-mark' ,
2007-03-15 02:10:56 +01:00
connmark = > 1 ,
2007-04-08 16:42:26 +02:00
fw = > 1
2007-03-15 02:10:56 +01:00
} ,
2007-04-30 19:55:43 +02:00
P = > { chain = > 'tcpre' ,
2007-03-15 02:10:56 +01:00
connmark = > 0 ,
fw = > 0
} ,
2007-04-30 19:55:43 +02:00
CP = > { chain = > 'tcpre' ,
2007-03-15 02:10:56 +01:00
target = > 'CONNMARK --set-mark' ,
connmark = > 1 ,
fw = > 0
} ,
2007-04-30 19:55:43 +02:00
F = > { chain = > 'tcfor' ,
2007-03-15 02:10:56 +01:00
connmark = > 0 ,
fw = > 0
} ,
2007-04-30 19:55:43 +02:00
CF = > { chain = > 'tcfor' ,
2007-03-15 02:10:56 +01:00
connmark = > 1 ,
2008-02-15 18:53:08 +01:00
fw = > 0 ,
2007-03-15 02:10:56 +01:00
} ,
) ;
use constant { NOMARK = > 0 ,
SMALLMARK = > 1 ,
2007-04-08 16:42:26 +02:00
HIGHMARK = > 2
2007-03-15 02:10:56 +01:00
} ;
2007-03-27 01:17:46 +02:00
2007-06-05 18:49:13 +02:00
our @ tccmd = ( { match = > sub ( $ ) { $ _ [ 0 ] eq 'SAVE' } ,
target = > 'CONNMARK --save-mark --mask' ,
mark = > SMALLMARK ,
2008-03-07 01:36:16 +01:00
mask = > '0xFF' ,
connmark = > 1
2007-06-05 18:49:13 +02:00
} ,
2007-03-31 00:38:09 +02:00
{ match = > sub ( $ ) { $ _ [ 0 ] eq 'RESTORE' } ,
target = > 'CONNMARK --restore-mark --mask' ,
2007-03-15 02:10:56 +01:00
mark = > SMALLMARK ,
2008-03-07 01:36:16 +01:00
mask = > '0xFF' ,
connmark = > 1
2007-03-15 02:10:56 +01:00
} ,
2007-03-31 00:38:09 +02:00
{ match = > sub ( $ ) { $ _ [ 0 ] eq 'CONTINUE' } ,
2007-03-15 02:10:56 +01:00
target = > 'RETURN' ,
mark = > NOMARK ,
2008-03-07 01:36:16 +01:00
mask = > '' ,
connmark = > 0
2007-03-15 02:10:56 +01:00
} ,
2007-03-31 00:38:09 +02:00
{ match = > sub ( $ ) { $ _ [ 0 ] =~ '\|.*' } ,
2007-03-15 02:10:56 +01:00
target = > 'MARK --or-mark' ,
mark = > HIGHMARK ,
mask = > '' } ,
2007-03-31 00:38:09 +02:00
{ match = > sub ( $ ) { $ _ [ 0 ] =~ '&.*' } ,
2007-03-15 02:10:56 +01:00
target = > 'MARK --and-mark ' ,
mark = > HIGHMARK ,
2008-03-07 01:36:16 +01:00
mask = > '' ,
connmark = > 0
2007-03-15 02:10:56 +01:00
}
) ;
2007-06-13 20:40:31 +02:00
our % classids ;
2007-06-13 20:56:27 +02:00
our @ deferred_rules ;
2007-06-23 18:06:16 +02:00
#
# Perl version of Arn Bernin's 'tc4shorewall'.
#
# TCDevices Table
#
2008-02-23 00:15:39 +01:00
# %tcdevices { <interface> -> {in_bandwidth => <value> ,
# out_bandwidth => <value> ,
2008-03-16 17:51:53 +01:00
# number => <number>,
2008-03-19 23:25:41 +01:00
# classify => 0|1
# tablenumber => <next u32 table to be allocated for this device>
# default => <default class mark value>
2008-03-20 22:54:27 +01:00
# redirected => [ <dev1>, <dev2>, ... ]
2008-03-21 15:59:14 +01:00
# }
2007-06-23 18:06:16 +02:00
#
our @ tcdevices ;
our % tcdevices ;
2008-03-16 17:51:53 +01:00
our @ devnums ;
our $ devnum ;
2007-06-23 18:06:16 +02:00
#
# TCClasses Table
#
# %tcclasses { device => <device> ,
# mark => <mark> ,
2008-03-16 17:51:53 +01:00
# number => <number> ,
2007-06-23 18:06:16 +02:00
# rate => <rate> ,
# ceiling => <ceiling> ,
# priority => <priority> ,
# options => { tos => [ <value1> , <value2> , ... ];
# tcp_ack => 1 ,
# ...
#
our @ tcclasses ;
our % tcclasses ;
2008-02-14 18:40:38 +01:00
our % restrictions = ( tcpre = > PREROUTE_RESTRICT ,
tcpost = > POSTROUTE_RESTRICT ,
tcfor = > NO_RESTRICT ,
tcout = > OUTPUT_RESTRICT ) ;
2008-03-19 17:02:16 +01:00
2007-06-15 00:07:45 +02:00
#
# Initialize globals -- we take this novel approach to globals initialization to allow
# the compiler to run multiple times in the same process. The
# initialize() function does globals initialization for this
# module and is called from an INIT block below. The function is
# also called by Shorewall::Compiler::compiler at the beginning of
2007-07-26 20:36:18 +02:00
# the second and subsequent calls to that function.
2007-06-15 00:07:45 +02:00
#
2007-06-14 01:02:39 +02:00
sub initialize () {
% classids = ( ) ;
@ deferred_rules = ( ) ;
2007-06-23 18:06:16 +02:00
@ tcdevices = ( ) ;
% tcdevices = ( ) ;
@ tcclasses = ( ) ;
% tcclasses = ( ) ;
2008-03-16 17:51:53 +01:00
@ devnums = ( ) ;
$ devnum = 0 ;
2007-09-10 17:52:57 +02:00
}
INIT {
initialize ;
2007-06-14 01:02:39 +02:00
}
2007-03-15 02:10:56 +01:00
sub process_tc_rule ( $$$$$$$$$$ ) {
2007-03-25 18:38:00 +02:00
my ( $ mark , $ source , $ dest , $ proto , $ ports , $ sports , $ user , $ testval , $ length , $ tos ) = @ _ ;
2007-03-15 02:10:56 +01:00
my $ original_mark = $ mark ;
2007-05-17 16:10:46 +02:00
( $ mark , my ( $ designator , $ remainder ) ) = split ( /:/ , $ mark , 3 ) ;
fatal_error "Invalid MARK" if defined $ remainder ;
2007-03-15 02:10:56 +01:00
2007-03-31 19:44:16 +02:00
my $ chain = $ globals { MARKING_CHAIN } ;
2007-03-15 02:10:56 +01:00
my $ target = 'MARK --set-mark' ;
my $ tcsref ;
my $ connmark = 0 ;
my $ classid = 0 ;
2007-08-26 17:12:04 +02:00
my $ device = '' ;
2007-09-10 17:52:57 +02:00
my $ fw = firewall_zone ;
2007-03-15 02:10:56 +01:00
if ( $ source ) {
2007-09-10 17:52:57 +02:00
if ( $ source eq $ fw ) {
2007-03-15 02:10:56 +01:00
$ chain = 'tcout' ;
$ source = '' ;
} else {
2007-09-10 17:52:57 +02:00
$ chain = 'tcout' if $ source =~ s/^($fw):// ;
2007-03-15 02:10:56 +01:00
}
}
if ( $ designator ) {
$ tcsref = $ tcs { $ designator } ;
2007-03-27 01:17:46 +02:00
2007-03-15 02:10:56 +01:00
if ( $ tcsref ) {
if ( $ chain eq 'tcout' ) {
2007-09-10 17:52:57 +02:00
fatal_error "Invalid chain designator for source $fw" unless $ tcsref - > { fw } ;
2007-03-15 02:10:56 +01:00
}
$ chain = $ tcsref - > { chain } if $ tcsref - > { chain } ;
$ target = $ tcsref - > { target } if $ tcsref - > { target } ;
$ mark = "$mark/0xFF" if $ connmark = $ tcsref - > { connmark } ;
2007-03-27 01:17:46 +02:00
2008-03-07 01:36:16 +01:00
require_capability ( 'CONNMARK' , "CONNMARK Rules" , '' ) if $ connmark ;
2007-03-15 02:10:56 +01:00
} else {
2007-06-13 20:40:31 +02:00
fatal_error "Invalid MARK ($original_mark)" unless $ mark =~ /^([0-9]+|0x[0-9a-f]+)$/ and $ designator =~ /^([0-9]+|0x[0-9a-f]+)$/ ;
if ( $ config { TC_ENABLED } eq 'Internal' ) {
2007-06-13 20:56:27 +02:00
fatal_error "Unknown Class ($original_mark)}" unless ( $ device = $ classids { $ original_mark } ) ;
2007-06-13 20:40:31 +02:00
}
2007-03-15 02:10:56 +01:00
$ chain = 'tcpost' ;
$ classid = 1 ;
$ mark = $ original_mark ;
$ target = 'CLASSIFY --set-class' ;
}
}
2008-02-14 18:40:38 +01:00
2007-03-15 02:10:56 +01:00
my $ mask = 0xffff ;
2007-05-17 16:10:46 +02:00
my ( $ cmd , $ rest ) = split ( '/' , $ mark , 2 ) ;
2007-03-15 02:10:56 +01:00
2007-04-30 19:55:43 +02:00
unless ( $ classid ) {
MARK:
2007-03-15 02:10:56 +01:00
{
2007-04-30 19:55:43 +02:00
for my $ tccmd ( @ tccmd ) {
if ( $ tccmd - > { match } ( $ cmd ) ) {
2007-05-01 00:00:07 +02:00
fatal_error "$mark not valid with :C[FPT]" if $ connmark ;
2008-03-07 01:36:16 +01:00
require_capability ( 'CONNMARK' , "SAVE/RESTORE Rules" , '' ) if $ tccmd - > { connmark } ;
2007-07-26 20:36:18 +02:00
2007-04-30 19:55:43 +02:00
$ target = "$tccmd->{target} " ;
my $ marktype = $ tccmd - > { mark } ;
2007-05-01 17:55:41 +02:00
if ( $ marktype == NOMARK ) {
$ mark = ''
} else {
$ mark =~ s/^[|&]// ;
}
2007-04-30 19:55:43 +02:00
if ( $ rest ) {
fatal_error "Invalid MARK ($original_mark)" if $ marktype == NOMARK ;
2007-07-26 20:36:18 +02:00
2007-04-30 19:55:43 +02:00
$ mark = $ rest if $ tccmd - > { mask } ;
2007-07-26 20:36:18 +02:00
2007-04-30 19:55:43 +02:00
if ( $ marktype == SMALLMARK ) {
verify_small_mark $ mark ;
} else {
validate_mark $ mark ;
2007-03-15 02:10:56 +01:00
}
2007-04-30 19:55:43 +02:00
} elsif ( $ tccmd - > { mask } ) {
$ mark = $ tccmd - > { mask } ;
2007-03-15 02:10:56 +01:00
}
2007-07-26 20:36:18 +02:00
2007-04-30 19:55:43 +02:00
last MARK ;
2007-03-15 02:10:56 +01:00
}
}
2007-03-27 01:17:46 +02:00
2007-03-15 02:10:56 +01:00
validate_mark $ mark ;
2007-12-04 01:02:35 +01:00
if ( $ config { HIGH_ROUTE_MARKS } ) {
fatal_error 'Marks < 256 may not be set in the PREROUTING or OUTPUT chains when HIGH_ROUTE_MARKS=Yes'
if $ cmd && ( $ chain eq 'tcpre' || $ chain eq 'tcout' ) && numeric_value ( $ cmd ) <= 0xFF ;
}
2007-03-15 02:10:56 +01:00
}
2007-04-30 19:55:43 +02:00
}
2007-03-15 02:10:56 +01:00
2007-09-21 18:55:28 +02:00
if ( ( my $ result = expand_rule ( ensure_chain ( 'mangle' , $ chain ) ,
2008-02-14 18:40:38 +01:00
$ restrictions { $ chain } ,
2007-11-16 16:56:42 +01:00
do_proto ( $ proto , $ ports , $ sports ) . do_user ( $ user ) . do_test ( $ testval , $ mask ) . do_tos ( $ tos ) ,
2007-09-21 18:55:28 +02:00
$ source ,
$ dest ,
'' ,
"-j $target $mark" ,
'' ,
'' ,
'' ) )
2007-08-26 17:12:04 +02:00
&& $ device ) {
2007-06-13 20:56:27 +02:00
#
# expand_rule() returns destination device if any
#
2007-08-26 17:12:04 +02:00
fatal_error "Class Id $original_mark is not associated with device $result" if $ device ne $ result ;
2007-06-13 20:56:27 +02:00
}
2007-03-27 01:17:46 +02:00
2007-07-07 18:34:38 +02:00
progress_message " TC Rule \"$currentline\" $done" ;
2007-03-27 01:17:46 +02:00
2007-03-15 02:10:56 +01:00
}
2007-03-27 01:17:46 +02:00
2007-03-20 00:29:22 +01:00
sub rate_to_kbit ( $ ) {
my $ rate = $ _ [ 0 ] ;
2008-03-14 00:27:29 +01:00
return 0 if $ rate eq '-' ;
2007-03-20 00:29:22 +01:00
return $ 1 if $ rate =~ /^(\d+)kbit$/i ;
2007-04-10 02:12:27 +02:00
return $ 1 * 1000 if $ rate =~ /^(\d+)mbit$/i ;
return $ 1 * 8000 if $ rate =~ /^(\d+)mbps$/i ;
2007-03-20 00:29:22 +01:00
return $ 1 * 8 if $ rate =~ /^(\d+)kbps$/i ;
2007-04-10 02:12:27 +02:00
return $ rate / 125 if $rate =~ / ^ \ d + $/ ;
2007-06-16 23:08:12 +02:00
fatal_error "Invalid Rate ($rate)" ;
2007-03-20 00:29:22 +01:00
}
2007-04-10 02:12:27 +02:00
sub calculate_r2q ( $ ) {
2007-03-20 02:16:51 +01:00
my $ rate = rate_to_kbit $ _ [ 0 ] ;
2007-04-10 02:12:27 +02:00
my $ r2q = $ rate / 200 ;
$ r2q <= 5 ? 5 : $ r2q ;
}
sub calculate_quantum ( $$ ) {
my ( $ rate , $ r2q ) = @ _ ;
$ rate = rate_to_kbit $ rate ;
2007-09-21 18:55:28 +02:00
int ( ( $ rate * 125 ) / $ r2q ) ;
2007-03-27 01:17:46 +02:00
}
2007-03-20 00:29:22 +01:00
2008-03-05 17:16:04 +01:00
sub validate_tc_device ( $$$$$ ) {
my ( $ device , $ inband , $ outband , $ options , $ redirected ) = @ _ ;
2007-03-20 00:29:22 +01:00
2008-03-16 17:51:53 +01:00
my $ devnumber ;
if ( $ device =~ /:/ ) {
( my $ number , $ device , my $ rest ) = split /:/ , $ device , 3 ;
fatal_error "Invalid NUMBER:INTERFACE ($device:$number:$rest)" if defined $ rest ;
if ( defined $ number ) {
$ devnumber = numeric_value ( $ number ) ;
fatal_error "Invalid interface NUMBER ($number)" unless defined $ devnumber && $ devnumber ;
fatal_error "Duplicate interface number ($number)" if defined $ devnums [ $ devnumber ] ;
$ devnum = $ devnumber if $ devnumber > $ devnum ;
} else {
fatal_error "Missing interface NUMBER" ;
}
} else {
$ devnumber = + + $ devnum ;
}
$ devnums [ $ devnumber ] = $ device ;
fatal_error "Duplicate INTERFACE ($device)" if $ tcdevices { $ device } ;
fatal_error "Invalid INTERFACE name ($device)" if $ device =~ /[:+]/ ;
2007-03-20 00:29:22 +01:00
2007-12-06 20:26:12 +01:00
my $ classify = 0 ;
2007-12-06 18:26:37 +01:00
if ( $ options ne '-' ) {
2008-01-26 02:07:57 +01:00
for my $ option ( split_list $ options , 'option' ) {
2007-12-06 18:26:37 +01:00
if ( $ option eq 'classify' ) {
2007-12-06 20:26:12 +01:00
$ classify = 1 ;
2007-12-06 18:26:37 +01:00
} else {
fatal_error "Unknown device option ($option)" ;
}
}
}
2007-03-21 00:13:17 +01:00
2008-03-07 01:36:16 +01:00
my @ redirected = ( ) ;
2008-03-05 17:16:04 +01:00
2008-03-14 00:27:29 +01:00
@ redirected = split_list ( $ redirected , 'device' ) if defined $ redirected && $ redirected ne '-' ;
2008-03-05 17:16:04 +01:00
2008-03-16 21:31:50 +01:00
if ( @ redirected ) {
fatal_error "IFB devices may not have IN-BANDWIDTH" if $ inband ne '-' && $ inband ;
$ classify = 1 ;
}
2008-03-05 17:16:04 +01:00
for my $ rdevice ( @ redirected ) {
fatal_error "Invalid device name ($rdevice)" if $ rdevice =~ /[:+]/ ;
2008-03-14 00:27:29 +01:00
my $ rdevref = $ tcdevices { $ rdevice } ;
fatal_error "REDIRECTED device ($rdevice) has not been defined in this file" unless $ rdevref ;
fatal_error "IN-BANDWIDTH must be zero for REDIRECTED devices" if $ rdevref - > { in_bandwidth } ne '0kbit' ;
2008-03-05 17:16:04 +01:00
}
2008-03-16 17:51:53 +01:00
$ tcdevices { $ device } = { in_bandwidth = > rate_to_kbit ( $ inband ) . 'kbit' ,
2007-12-06 20:26:12 +01:00
out_bandwidth = > rate_to_kbit ( $ outband ) . 'kbit' ,
2008-03-16 17:51:53 +01:00
number = > $ devnumber ,
2008-03-05 17:16:04 +01:00
classify = > $ classify ,
2008-03-19 23:25:41 +01:00
tablenumber = > 1 ,
2008-03-20 22:54:27 +01:00
redirected = > \ @ redirected ,
protocols = > [] ,
} ,
2007-12-06 20:26:12 +01:00
2007-03-20 00:29:22 +01:00
push @ tcdevices , $ device ;
2007-07-11 01:09:33 +02:00
progress_message " Tcdevice \"$currentline\" $done." ;
2007-03-20 00:29:22 +01:00
}
sub convert_rate ( $$ ) {
my ( $ full , $ rate ) = @ _ ;
2007-04-16 22:11:09 +02:00
if ( $ rate =~ /\bfull\b/ ) {
$ rate =~ s/\bfull\b/$full/g ;
$ rate = eval "int( $rate )" ;
} else {
$ rate = rate_to_kbit $ rate
}
2007-07-26 20:36:18 +02:00
2007-03-20 02:16:51 +01:00
"${rate}kbit" ;
2007-03-20 00:29:22 +01:00
}
2008-03-16 17:51:53 +01:00
sub dev_by_number ( $ ) {
my $ dev = $ _ [ 0 ] ;
my $ devnum = numeric_value ( $ dev ) ;
my $ devref ;
if ( defined $ devnum ) {
$ dev = $ devnums [ $ devnum ] ;
fatal_error "Undefined INTERFACE number ($_[0])" unless defined $ dev ;
$ devref = $ tcdevices { $ dev } ;
fatal_error "Internal Error in dev_by_number()" unless $ devref ;
} else {
$ devref = $ tcdevices { $ dev } ;
fatal_error "Unknown INTERFACE ($dev)" unless $ devref ;
}
( $ dev , $ devref ) ;
}
2007-03-20 00:29:22 +01:00
sub validate_tc_class ( $$$$$$ ) {
2008-03-16 17:51:53 +01:00
my ( $ devclass , $ mark , $ rate , $ ceil , $ prio , $ options ) = @ _ ;
2007-03-20 00:29:22 +01:00
my % tosoptions = ( 'tos-minimize-delay' = > 'tos=0x10/0x10' ,
'tos-maximize-throughput' = > 'tos=0x08/0x08' ,
'tos-maximize-reliability' = > 'tos=0x04/0x04' ,
'tos-minimize-cost' = > 'tos=0x02/0x02' ,
'tos-normal-service' = > 'tos=0x00/0x1e' ) ;
2007-03-27 01:17:46 +02:00
2008-03-16 17:51:53 +01:00
my $ classnumber = 0 ;
my $ devref ;
my $ device = $ devclass ;
if ( $ devclass =~ /:/ ) {
( $ device , my ( $ number , $ rest ) ) = split /:/ , $ device , 3 ;
fatal_error "Invalid INTERFACE:CLASS ($devclass)" if defined $ rest ;
( $ device , $ devref ) = dev_by_number ( $ device ) ;
if ( defined $ number ) {
if ( $ devref - > { classify } ) {
$ classnumber = numeric_value ( $ number ) ;
fatal_error "Invalid interface NUMBER ($number)" unless defined $ classnumber && $ classnumber ;
fatal_error "Duplicate interface/class number ($number)" if defined $ devnums [ $ classnumber ] ;
} else {
warning_message "Class NUMBER ignored -- INTERFACE $device does not have the 'classify' option" ;
}
} else {
fatal_error "Missing interface NUMBER" ;
}
} else {
( $ device , $ devref ) = dev_by_number ( $ device ) ;
fatal_error "Missing class NUMBER" if $ devref - > { classify } ;
}
2007-03-20 02:16:51 +01:00
my $ full = rate_to_kbit $ devref - > { out_bandwidth } ;
2007-03-20 00:29:22 +01:00
$ tcclasses { $ device } = { } unless $ tcclasses { $ device } ;
my $ tcref = $ tcclasses { $ device } ;
2008-03-16 17:51:53 +01:00
my $ markval ;
2007-03-20 00:29:22 +01:00
2008-03-16 17:51:53 +01:00
if ( $ mark ne '-' ) {
if ( $ devref - > { classify } ) {
warning_message "INTERFACE $device has the 'classify' option - MARK value ($mark) ignored" ;
} else {
fatal_error "Invalid Mark ($mark)" unless $ mark =~ /^([0-9]+|0x[0-9a-f]+)$/ && numeric_value ( $ mark ) <= 0xff ;
2007-03-20 00:29:22 +01:00
2008-03-16 17:51:53 +01:00
$ markval = numeric_value ( $ mark ) ;
fatal_error "Duplicate MARK ($mark)" if $ tcref - > { $ classnumber } ;
2008-03-16 21:31:50 +01:00
$ classnumber = $ devnum . $ mark ;
2008-03-16 17:51:53 +01:00
}
} else {
fatal_error "Missing MARK" unless $ devref - > { classify } ;
fatal_error "Duplicate Class NUMBER ($classnumber)" if $ tcref - > { $ classnumber } ;
}
2007-03-20 00:29:22 +01:00
2008-03-16 17:51:53 +01:00
$ tcref - > { $ classnumber } = { tos = > [] ,
rate = > convert_rate ( $ full , $ rate ) ,
ceiling = > convert_rate ( $ full , $ ceil ) ,
priority = > $ prio eq '-' ? 1 : $ prio
} ;
2007-10-19 21:43:14 +02:00
2008-03-16 17:51:53 +01:00
$ tcref = $ tcref - > { $ classnumber } ;
2007-03-20 00:29:22 +01:00
2007-03-25 18:53:33 +02:00
unless ( $ options eq '-' ) {
2008-01-26 02:07:57 +01:00
for my $ option ( split_list "\L$options" , 'option' ) {
2007-03-25 18:53:33 +02:00
my $ optval = $ tosoptions { $ option } ;
2007-03-27 01:17:46 +02:00
2007-03-25 18:53:33 +02:00
$ option = $ optval if $ optval ;
2007-03-27 01:17:46 +02:00
2007-03-25 18:53:33 +02:00
if ( $ option eq 'default' ) {
fatal_error "Only one default class may be specified for device $device" if $ devref - > { default } ;
2008-03-16 17:51:53 +01:00
$ devref - > { default } = $ classnumber ;
2007-03-25 18:53:33 +02:00
} elsif ( $ option eq 'tcp-ack' ) {
$ tcref - > { tcp_ack } = 1 ;
} elsif ( $ option =~ /^tos=0x[0-9a-f]{2}$/ ) {
( undef , $ option ) = split /=/ , $ option ;
push @ { $ tcref - > { tos } } , "$option/0xff" ;
} elsif ( $ option =~ /^tos=0x[0-9a-f]{2}\/0x[0-9a-f]{2}$/ ) {
( undef , $ option ) = split /=/ , $ option ;
push @ { $ tcref - > { tos } } , $ option ;
} else {
2007-06-16 23:08:12 +02:00
fatal_error "Unknown option ($option)" ;
2007-03-25 18:53:33 +02:00
}
2007-03-20 00:29:22 +01:00
}
}
2008-03-16 17:51:53 +01:00
push @ tcclasses , "$device:$classnumber" ;
2007-07-11 01:09:33 +02:00
progress_message " Tcclass \"$currentline\" $done." ;
2007-03-27 01:17:46 +02:00
}
2007-03-20 00:29:22 +01:00
2008-03-20 19:54:40 +01:00
#
# Process a record from the tcfilters file
#
2008-03-16 17:51:53 +01:00
sub process_tc_filter ( $$$$$$ ) {
2008-03-21 15:59:14 +01:00
my ( $ devclass , $ source , $ dest , $ proto , $ portlist , $ sportlist ) = @ _ ;
2008-03-16 17:51:53 +01:00
my ( $ device , $ class , $ rest ) = split /:/ , $ devclass , 3 ;
fatal_error "Invalid INTERFACE:CLASS ($devclass)" if defined $ rest || ! ( $ device && $ class ) ;
( $ device , my $ devref ) = dev_by_number ( $ device ) ;
2008-03-20 22:54:27 +01:00
my $ devnum = $ devref - > { number } ;
2008-03-16 17:51:53 +01:00
my $ tcref = $ tcclasses { $ device } ;
fatal_error "No Classes were defined for INTERFACE $device" unless $ tcref ;
$ tcref = $ tcref - > { $ class } ;
2008-03-20 19:54:40 +01:00
fatal_error "Unknown CLASS ($devclass)" unless $ tcref ;
2008-03-16 17:51:53 +01:00
2008-03-20 22:54:27 +01:00
my $ rule = "filter add dev $device protocol ip parent $devnum:0 pref 10 u32" ;
2008-03-16 17:51:53 +01:00
my ( $ net , $ mask ) = decompose_net ( $ source ) ;
$ rule . = "\\\n match u32 $net $mask at 12" unless $ mask eq '0x00000000' ;
( $ net , $ mask ) = decompose_net ( $ dest ) ;
$ rule . = "\\\n match u32 $net $mask at 16" unless $ mask eq '0x00000000' ;
my $ protonumber = 0 ;
unless ( $ proto eq '-' ) {
$ protonumber = resolve_proto $ proto ;
fatal_error "Unknown PROTO ($proto)" unless defined $ protonumber ;
2008-03-19 17:02:16 +01:00
if ( $ protonumber ) {
my $ pnumber = in_hex2 $ protonumber ;
2008-03-19 23:40:03 +01:00
$ rule . = "\\\n match u8 $pnumber 0xff at 9" ;
2008-03-19 17:02:16 +01:00
}
2008-03-16 17:51:53 +01:00
}
2008-03-19 17:15:03 +01:00
2008-03-21 15:59:14 +01:00
if ( $ portlist eq '-' && $ sportlist eq '-' ) {
emit ( "\nrun_tc $rule\\" ,
" flowid $devref->{number}:$class" ,
'' ) ;
} else {
2008-03-19 17:15:03 +01:00
#
# In order to be able to access the protocol header, we must create another hash table and link to it.
#
2008-03-21 15:59:14 +01:00
# Create the Table.
2008-03-19 17:15:03 +01:00
#
2008-03-21 15:59:14 +01:00
my $ tnum = in_hex3 $ devref - > { tablenumber } + + ;
2008-03-20 22:54:27 +01:00
2008-03-21 15:59:14 +01:00
emit ( "\nrun_tc filter add dev $device parent $devnum:0 protocol ip pref 10 handle $tnum: u32 divisor 1" ) ;
2008-03-19 17:15:03 +01:00
#
2008-03-20 23:18:35 +01:00
# And link to it using the current contents of $rule
#
2008-03-21 15:59:14 +01:00
emit ( "\nrun_tc $rule\\" ,
2008-03-20 23:18:35 +01:00
" link $tnum:0 offset at 0 mask 0x0F00 shift 6 plus 0 eat" ) ;
#
2008-03-19 17:15:03 +01:00
# The rule to match the port(s) will be inserted into the new table
#
2008-03-20 22:54:27 +01:00
$ rule = "filter add dev $device protocol ip parent $devnum:0 pref 10 u32 ht $tnum:0" ;
2008-03-16 17:51:53 +01:00
2008-03-21 15:59:14 +01:00
if ( $ portlist eq '-' ) {
fatal_error "Only TCP, UDP and SCTP may specify SOURCE PORT"
unless $ protonumber == TCP || $ protonumber == UDP || $ protonumber == SCTP ;
2008-03-21 18:08:55 +01:00
for my $ sportrange ( split_list $ sportlist , 'port list' ) {
my @ sportlist = expand_port_range $ protonumber , $ sportrange ;
while ( @ sportlist ) {
my ( $ sport , $ smask ) = ( shift @ sportlist , shift @ sportlist ) ;
emit ( "\nrun_tc $rule\\" ,
" match u32 0x${sport}0000 0x${smask}0000 at nexthdr+0\\" ,
" flowid $devref->{number}:$class" ) ;
}
2008-03-21 15:59:14 +01:00
}
} else {
2008-03-19 17:15:03 +01:00
fatal_error "Only TCP, UDP, SCTP and ICMP may specify DEST PORT"
unless $ protonumber == TCP || $ protonumber == UDP || $ protonumber == SCTP || $ protonumber == ICMP ;
2008-03-21 18:08:55 +01:00
for my $ portrange ( split_list $ portlist , 'port list' ) {
2008-03-21 15:59:14 +01:00
if ( $ protonumber == ICMP ) {
2008-03-21 18:08:55 +01:00
my ( $ icmptype , $ icmpcode ) = split '//' , validate_icmp ( $ portrange ) ;
2008-03-21 15:59:14 +01:00
$ icmptype = in_hex2 numeric_value $ icmptype ;
$ icmpcode = in_hex2 numeric_value $ icmpcode if defined $ icmpcode ;
2008-03-21 18:08:55 +01:00
2008-03-21 15:59:14 +01:00
my $ rule1 = " match u8 $icmptype 0xff at nexthdr+0" ;
$ rule1 . = "\\\n match u8 $icmpcode 0xff at nexthdr+1" if defined $ icmpcode ;
emit ( "\nrun_tc ${rule}\\" ,
"$rule1\\" ,
" flowid $devref->{number}:$class" ) ;
} else {
2008-03-21 18:08:55 +01:00
my @ portlist = expand_port_range $ protonumber , $ portrange ;
while ( @ portlist ) {
my ( $ port , $ mask ) = ( shift @ portlist , shift @ portlist ) ;
my $ rule1 = "match u32 0x0000${port} 0x0000${mask} at nexthdr+0" ;
if ( $ sportlist eq '-' ) {
emit ( "\nrun_tc ${rule}\\" ,
2008-03-21 15:59:14 +01:00
" $rule1\\" ,
" flowid $devref->{number}:$class" ) ;
2008-03-21 18:08:55 +01:00
} else {
for my $ sportrange ( split_list $ sportlist , 'port list' ) {
my @ sportlist = expand_port_range $ protonumber , $ sportrange ;
while ( @ sportlist ) {
my ( $ sport , $ smask ) = ( shift @ sportlist , shift @ sportlist ) ;
emit ( "\nrun_tc ${rule}\\" ,
" $rule1\\" ,
" match u32 0x${sport}0000 0x${smask}0000 at nexthdr+0\\" ,
" flowid $devref->{number}:$class" ) ;
}
}
}
2008-03-21 15:59:14 +01:00
}
2008-03-21 18:08:55 +01:00
}
}
2008-03-17 23:10:11 +01:00
}
2008-03-16 17:51:53 +01:00
}
2008-03-21 15:59:14 +01:00
emit '' ;
2008-03-17 23:10:11 +01:00
2008-03-16 17:51:53 +01:00
progress_message " TC Filter \"$currentline\" $done" ;
2008-03-17 17:46:30 +01:00
$ currentline =~ s/\s+/ /g ;
save_progress_message_short qq( " TC Filter \"$currentline\" defined." ) ;
emit '' ;
2008-03-16 17:51:53 +01:00
}
2007-03-15 02:38:04 +01:00
sub setup_traffic_shaping () {
2007-03-29 19:02:13 +02:00
save_progress_message "Setting up Traffic Control..." ;
2007-03-30 17:57:08 +02:00
my $ fn = open_file 'tcdevices' ;
2007-03-20 00:29:22 +01:00
2007-03-30 17:57:08 +02:00
if ( $ fn ) {
2007-11-16 00:24:54 +01:00
first_entry "$doing $fn..." ;
2007-03-20 00:29:22 +01:00
2007-03-29 19:02:13 +02:00
while ( read_a_line ) {
2007-03-20 00:29:22 +01:00
2008-03-05 17:16:04 +01:00
my ( $ device , $ inband , $ outband , $ options , $ redirected ) = split_line 3 , 5 , 'tcdevices' ;
2007-05-09 21:03:09 +02:00
2007-04-01 01:53:17 +02:00
fatal_error "Invalid tcdevices entry" if $ outband eq '-' ;
2008-03-05 17:16:04 +01:00
validate_tc_device ( $ device , $ inband , $ outband , $ options , $ redirected ) ;
2007-03-20 00:29:22 +01:00
}
}
2008-03-16 21:31:50 +01:00
$ devnum = $ devnum > 10 ? 10 : 1 ;
2008-03-16 17:51:53 +01:00
2007-03-30 17:57:08 +02:00
$ fn = open_file 'tcclasses' ;
2007-03-20 00:29:22 +01:00
2007-03-30 17:57:08 +02:00
if ( $ fn ) {
2007-11-16 00:24:54 +01:00
first_entry "$doing $fn..." ;
2007-03-20 00:29:22 +01:00
2007-03-29 19:02:13 +02:00
while ( read_a_line ) {
2007-03-20 00:29:22 +01:00
2007-04-01 17:38:05 +02:00
my ( $ device , $ mark , $ rate , $ ceil , $ prio , $ options ) = split_line 4 , 6 , 'tcclasses file' ;
2007-03-27 01:17:46 +02:00
2007-03-20 00:29:22 +01:00
validate_tc_class ( $ device , $ mark , $ rate , $ ceil , $ prio , $ options ) ;
}
}
for my $ device ( @ tcdevices ) {
my $ dev = chain_base ( $ device ) ;
my $ devref = $ tcdevices { $ device } ;
2007-04-10 02:12:27 +02:00
my $ defmark = $ devref - > { default } || 0 ;
2008-03-16 17:51:53 +01:00
my $ devnum = $ devref - > { number } ;
2007-03-20 00:29:22 +01:00
2007-08-03 20:48:56 +02:00
emit "if interface_is_up $device; then" ;
2007-03-27 01:17:46 +02:00
2007-03-20 00:29:22 +01:00
push_indent ;
2007-07-23 20:14:12 +02:00
emit ( "${dev}_exists=Yes" ,
2007-03-27 06:02:58 +02:00
"qt tc qdisc del dev $device root" ,
"qt tc qdisc del dev $device ingress" ,
2007-04-10 02:12:27 +02:00
"run_tc qdisc add dev $device root handle $devnum: htb default $defmark" ,
2007-03-27 06:02:58 +02:00
"${dev}_mtu=\$(get_device_mtu $device)" ,
2007-04-11 19:55:18 +02:00
"${dev}_mtu1=\$(get_device_mtu1 $device)" ,
"run_tc class add dev $device parent $devnum: classid $devnum:1 htb rate $devref->{out_bandwidth} \$${dev}_mtu1"
2007-03-27 06:02:58 +02:00
) ;
2007-03-27 01:17:46 +02:00
2007-03-20 01:06:56 +01:00
my $ inband = rate_to_kbit $ devref - > { in_bandwidth } ;
2007-03-20 00:29:22 +01:00
if ( $ inband ) {
2007-07-23 20:14:12 +02:00
emit ( "run_tc qdisc add dev $device handle ffff: ingress" ,
2008-03-19 17:02:16 +01:00
"run_tc filter add dev $device parent ffff: protocol ip pref 10 u32 match ip src 0.0.0.0/0 police rate ${inband}kbit burst 10k drop flowid :1"
2007-03-27 06:02:58 +02:00
) ;
2007-03-20 00:29:22 +01:00
}
2008-03-05 17:16:04 +01:00
for my $ rdev ( @ { $ devref - > { redirected } } ) {
2008-03-16 21:31:50 +01:00
emit ( "run_tc qdisc add dev $rdev handle ffff: ingress" ) ;
2008-03-17 17:46:30 +01:00
emit ( "run_tc filter add dev $rdev parent ffff: protocol ip u32 match u32 0 0 action mirred egress redirect dev $device > /dev/null" ) ;
2008-03-05 17:16:04 +01:00
}
2007-03-20 00:29:22 +01:00
save_progress_message_short " TC Device $device defined." ;
pop_indent ;
emit 'else' ;
push_indent ;
2007-08-03 20:48:56 +02:00
emit qq( error_message "WARNING: Device $device is not in the UP state -- traffic-shaping configuration skipped" ) ;
2007-03-20 00:29:22 +01:00
emit "${dev}_exists=" ;
pop_indent ;
emit "fi\n" ;
}
2007-03-20 01:27:50 +01:00
my $ lastdevice = '' ;
2007-03-20 00:29:22 +01:00
for my $ class ( @ tcclasses ) {
my ( $ device , $ mark ) = split /:/ , $ class ;
my $ devref = $ tcdevices { $ device } ;
my $ tcref = $ tcclasses { $ device } { $ mark } ;
2008-03-16 17:51:53 +01:00
my $ devicenumber = $ devref - > { number } ;
my $ classid = join ( '' , $ devicenumber , ':' , $ mark ) ;
2007-03-20 00:29:22 +01:00
my $ rate = $ tcref - > { rate } ;
2007-04-12 19:40:22 +02:00
my $ quantum = calculate_quantum $ rate , calculate_r2q ( $ devref - > { out_bandwidth } ) ;
2007-03-20 00:29:22 +01:00
my $ dev = chain_base $ device ;
2007-03-20 01:27:50 +01:00
2007-06-13 20:40:31 +02:00
$ classids { $ classid } = $ device ;
2007-03-20 01:27:50 +01:00
if ( $ lastdevice ne $ device ) {
if ( $ lastdevice ) {
pop_indent ;
emit "fi\n" ;
}
emit qq( if [ -n "\$${dev}_exists" ]; then ) ;
push_indent ;
$ lastdevice = $ device ;
}
2007-03-27 01:17:46 +02:00
2007-07-23 20:14:12 +02:00
emit ( "[ \$${dev}_mtu -gt $quantum ] && quantum=\$${dev}_mtu || quantum=$quantum" ,
2007-04-11 19:55:18 +02:00
"run_tc class add dev $device parent $devref->{number}:1 classid $classid htb rate $rate ceil $tcref->{ceiling} prio $tcref->{priority} \$${dev}_mtu1 quantum \$quantum" ,
2008-03-16 17:51:53 +01:00
"run_tc qdisc add dev $device parent $classid handle ${mark}: sfq perturb 10"
2007-12-21 23:56:36 +01:00
) ;
2007-03-20 00:29:22 +01:00
#
# add filters
#
2008-03-19 17:02:16 +01:00
emit "run_tc filter add dev $device protocol ip parent $devicenumber:0 pref 10 handle $mark fw classid $classid" unless $ devref - > { classify } ;
2007-03-20 00:29:22 +01:00
#
#options
#
2008-03-19 17:02:16 +01:00
emit "run_tc filter add dev $device parent $devref->{number}:0 protocol ip pref 10 u32 match ip protocol 6 0xff match u8 0x05 0x0f at 0 match u16 0x0000 0xffc0 at 2 match u8 0x10 0xff at 33 flowid $classid" if $ tcref - > { tcp_ack } ;
2007-03-20 00:29:22 +01:00
2007-03-20 22:47:13 +01:00
for my $ tospair ( @ { $ tcref - > { tos } } ) {
2007-03-21 00:13:17 +01:00
my ( $ tos , $ mask ) = split q( / ) , $ tospair ;
2008-03-16 17:51:53 +01:00
emit "run_tc filter add dev $device parent $devicenumber:0 protocol ip prio 10 u32 match ip tos $tos $mask flowid $classid" ;
2007-03-20 00:29:22 +01:00
}
2007-03-20 01:27:50 +01:00
save_progress_message_short qq( " TC Class $class defined." ) ;
emit '' ;
}
2007-03-27 01:17:46 +02:00
2007-03-20 01:27:50 +01:00
if ( $ lastdevice ) {
pop_indent ;
emit "fi\n" ;
2007-03-20 00:29:22 +01:00
}
2008-03-16 17:51:53 +01:00
$ fn = open_file 'tcfilters' ;
if ( $ fn ) {
2008-03-17 17:46:30 +01:00
first_entry ( sub { progress_message2 "$doing $fn..." ; save_progress_message "Adding TC Filters" ; } ) ;
2008-03-16 17:51:53 +01:00
while ( read_a_line ) {
my ( $ devclass , $ source , $ dest , $ proto , $ port , $ sport ) = split_line 2 , 6 , 'tcfilters file' ;
process_tc_filter ( $ devclass , $ source , $ dest , $ proto , $ port , $ sport ) ;
}
}
2007-03-15 02:38:04 +01:00
}
2007-03-21 00:13:17 +01:00
#
# Process the tcrules file and setup traffic shaping
#
sub setup_tc () {
2007-03-29 20:57:53 +02:00
if ( $ capabilities { MANGLE_ENABLED } ) {
2007-09-12 17:03:47 +02:00
ensure_mangle_chain 'tcpre' ;
ensure_mangle_chain 'tcout' ;
2007-03-21 00:13:17 +01:00
2007-03-29 20:57:53 +02:00
if ( $ capabilities { MANGLE_FORWARD } ) {
2007-09-12 17:03:47 +02:00
ensure_mangle_chain 'tcfor' ;
ensure_mangle_chain 'tcpost' ;
2007-03-29 20:57:53 +02:00
}
2007-03-21 00:13:17 +01:00
2007-03-29 03:14:13 +02:00
my $ mark_part = '' ;
2007-03-21 00:13:17 +01:00
2007-03-29 03:14:13 +02:00
if ( @ routemarked_interfaces && ! $ config { TC_EXPERT } ) {
2007-12-05 21:08:09 +01:00
$ mark_part = $ config { HIGH_ROUTE_MARKS } ? '-m mark --mark 0/0xFF00' : '-m mark --mark 0/0xFF' ;
2007-03-27 01:17:46 +02:00
2007-03-29 03:14:13 +02:00
for my $ interface ( @ routemarked_interfaces ) {
2007-12-05 21:08:09 +01:00
add_rule $ mangle_table - > { PREROUTING } , "-i $interface -j tcpre" ;
2007-03-29 03:14:13 +02:00
}
2007-03-21 00:13:17 +01:00
}
2007-09-12 17:03:47 +02:00
add_rule $ mangle_table - > { PREROUTING } , "$mark_part -j tcpre" ;
add_rule $ mangle_table - > { OUTPUT } , "$mark_part -j tcout" ;
2007-03-21 00:13:17 +01:00
2007-03-29 03:14:13 +02:00
if ( $ capabilities { MANGLE_FORWARD } ) {
2007-09-12 17:03:47 +02:00
add_rule $ mangle_table - > { FORWARD } , '-j tcfor' ;
add_rule $ mangle_table - > { POSTROUTING } , '-j tcpost' ;
2007-03-29 03:14:13 +02:00
}
2007-03-21 00:13:17 +01:00
2007-03-29 03:14:13 +02:00
if ( $ config { HIGH_ROUTE_MARKS } ) {
for my $ chain qw( INPUT FORWARD POSTROUTING ) {
2007-09-12 17:03:47 +02:00
insert_rule $ mangle_table - > { $ chain } , 1 , '-j MARK --and-mark 0xFF' ;
2007-03-29 03:14:13 +02:00
}
2007-03-21 00:13:17 +01:00
}
}
2007-04-23 21:49:02 +02:00
if ( $ globals { TC_SCRIPT } ) {
2007-03-21 00:13:17 +01:00
save_progress_message 'Setting up Traffic Control...' ;
2007-04-23 21:49:02 +02:00
append_file $ globals { TC_SCRIPT } ;
2007-03-21 00:13:17 +01:00
} elsif ( $ config { TC_ENABLED } eq 'Internal' ) {
2007-03-29 19:02:13 +02:00
setup_traffic_shaping ;
2007-03-21 00:13:17 +01:00
}
2007-07-26 20:36:18 +02:00
2007-06-13 20:40:31 +02:00
if ( my $ fn = open_file 'tcrules' ) {
2007-11-16 00:24:54 +01:00
first_entry ( sub { progress_message2 "$doing $fn..." ; require_capability 'MANGLE_ENABLED' , 'a non-empty tcrules file' , 's' ; } ) ;
2007-06-13 20:40:31 +02:00
2007-11-16 00:24:54 +01:00
while ( read_a_line ) {
2007-06-13 20:40:31 +02:00
my ( $ mark , $ source , $ dest , $ proto , $ ports , $ sports , $ user , $ testval , $ length , $ tos ) = split_line1 2 , 10 , 'tcrules file' ;
if ( $ mark eq 'COMMENT' ) {
process_comment ;
} else {
process_tc_rule $ mark , $ source , $ dest , $ proto , $ ports , $ sports , $ user , $ testval , $ length , $ tos
}
}
2007-09-10 17:52:57 +02:00
clear_comment ;
2007-06-13 20:40:31 +02:00
}
2007-09-12 17:03:47 +02:00
for ( @ deferred_rules ) {
add_rule ensure_chain ( 'mangle' , 'tcpost' ) , $ _ ;
2007-06-13 20:56:27 +02:00
}
2007-03-21 00:13:17 +01:00
}
2007-03-15 02:10:56 +01:00
1 ;