За основу взят
http://wiki.bgbilling.ru/index.php/FreeBSD_manad,_понимающий_изменения_правил_в_тарифахОбеспечена совместимость со старым вариантом.
Коротко об алгоритме:
Используются теги [OPEN], [CLOSE], но, в отличие от Линуксового манада, эти теги обрабатывает скрип шлюза, а не манад.
При использовании данного манада без тегов [OPEN], [CLOSE] на манад, в команде remove, будут отправлены все правила, но манад, при обработке remove, понимает только команды table. Докручивание обработчика remove до уровня понимания других команд ipfw без использования [OPEN], [CLOSE] черевато.
IPFW:
Код:
34500 pipe tablearg ip from any to table(1) out
34500 pipe tablearg ip from table(2) to any in
34500 skipto 60000 ip from table(1) to any
59999 deny ip from any to any via vlan90 out
Команды:
Код:
[DEFAULT]
[OPEN]
pipe {PA} config bw ${speed}
pipe {PB} config bw ${speed}
<LOOP>
table 1 delete {A}
table 2 delete {A}
table 1 add {A} {PA}
table 2 add {A} {PB}
</LOOP>
[/OPEN]
[CLOSE]
<LOOP>
table 1 delete {A}
table 2 delete {A}
</LOOP>
[/CLOSE]
[/DEFAULT]
Скрипт:
Код:
import java.io.*;
import java.net.*;
import java.util.*;
import bitel.billing.common.module.ipn.*;
import bitel.billing.server.ipn.bean.*;
import ru.bitel.bgbilling.modules.ipn.server.bean.command.*;
protected void doSync()
{
host = gate.getHost();
port = gate.getPort();
gid = gate.getId();
if ( log.isDebugEnabled() )
{
log.debug( gid + " gate: " + host + ":" + port );
}
try
{
socket = new Socket( host, port );
out = new PrintWriter( socket.getOutputStream(), true );
isr = new InputStreamReader( socket.getInputStream() );
in = new BufferedReader( isr );
log.info("I test Manad");
out.println( "testRID" );
kods = in.readLine();
if ( log.isDebugEnabled() )
{
log.debug( gid + " Test => " + kods + "\n" );
}
log.info( gid + " Test => " + kods + "\n" );
// список открытых договоров с шлюза
gateRules = new HashMap( 5, 5 );
st = new StringTokenizer( kods );
while ( st.hasMoreTokens() )
{
sp=st.nextToken().split("-");
if(sp.length==2) gateRules.put( new Integer( sp[0] ),(sp[1]==null)?null:new Integer(sp[1]) );
else gateRules.put( new Integer( sp[0] ),null);
}
for( i = 0; i < statusList.size(); i++ )
{
status = statusList.get(i);
cid = status.contractId;
// флаг того то правило есть на шлюзе
flag = false;
ruleChanged=false;
// правило для этого договора есть на шлюзе
if ( gateRules.containsKey( cid ) )
{
// если правило есть а юзер заблокирован - удаляем правило
if ( status.status > 0 )
{
rule = generateRule( status ,false);
command = "remove\t" + cid.intValue() + "\t" + rule;
out.println( command );
if ( log.isDebugEnabled() ) {
log.debug( gid + " " + command );
}
log.info( gid + " " + command );
}else{
// правило есть и юзер открыт - проверяем тип правила
rid=gateRules.get(cid);
if(rid!=null && status.ruleType==null) ruleChanged=true;
if(rid==null && status.ruleType!=null) ruleChanged=false; // совместимость со старым manad
if(rid!=null && status.ruleType!=null && !rid.equals(status.ruleType.getId())) ruleChanged=true;
if(ruleChanged){
if ( log.isDebugEnabled() ) {
log.debug( gid + " rule changed");
}
log.info( gid + " rule changed");
}
}
flag = true;
gateRules.remove( cid );
}
// правила нет, а юзер открыт, правило есть, но было изменение типа правила
if ( (!flag &&
status.status == IPNContractStatus.STATUS_OPEN ) ||(ruleChanged && status.status == IPNContractStatus.STATUS_OPEN ))
{
rule = generateRule( status, true);
command = "add\t" + cid.intValue() + "\t" + rule; // в случае изменения manad затрет старые правила ipfw
if( status.ruleType != null ) command+=" // RULE"+status.ruleType.getId();
out.println( command );
if ( log.isDebugEnabled() )
{
log.debug( gid + " " + command );
}
log.info( gid + " " + command );
}
}
in.close();
out.close();
socket.close();
}
catch ( e )
{
throw new RuntimeException ( e );
}
}
private generateRule( status ,open)
{
rule = null;
// пользовательское правило, без типа
if( status.ruleType == null )
{
rule = status.rule.getRuleText();
}
// типизированное правило
else
{
ruleText = GateCommandUtil.getRule( status.gateType, status.ruleType );
rule = GateCommandUtil.generateRule( ruleText, status.rule.getRuleText(), null, status.ruleType );
}
rule = rule.replaceAll( "\r", "" );
rule = rule.replaceAll( "\n", "|" );
if(open){
ib=rule.indexOf("[OPEN]")+7;
ie=rule.indexOf("[/OPEN]")-1;
if(ib>-1&&ie>-1) rule=rule.substring(ib,ie);
}else{
ib=rule.indexOf("[CLOSE]")+8;
ie=rule.indexOf("[/CLOSE]")-1;
if(ib>-1&&ie>-1) rule=rule.substring(ib,ie);
}
return rule;
}
Манад:
Код:
#!/usr/bin/perl
use POSIX;
use IO::Socket;
use IO::Select;
use Socket;
use Fcntl;
use Tie::RefHash;
$debug = 1;
$port = 4444;
$ipfw = "/sbin/ipfw";
$rule_start = 35000;
$pipe_start = 800;
$set = 10;
# База данных правил клиентов
%CLRULE = ();
%CLRULE_ID = ();
# База данных используемых номеров правил
%USERULEN = ();
%CLUSERULEN = ();
# База данных используемых номеров труб
%USEPIPEN = ();
%CLUSEPIPEN = ();
# Начать с пустыми буферами
%inbuffer = ();
%outbuffer = ();
%ready = ();
tie %ready, 'Tie::RefHash';
# Прослушивать порт
$server = IO::Socket::INET->new( LocalPort => $port, Listen => 10 )
or die "Can`t make server socket: $@\n";
nonblock( $server );
$SIG{INT} = sub { $server->close(); exit( 0 ); };
$select = IO::Select->new( $server );
$pid = getpid();
open(FILE, ">/var/run/manad.pid");
print FILE $pid;
close(FILE);
# Устанавливаем новый root каталог для процесса
# chroot( $homedir ) or die "Couldn`t chroot to $homedir: $!\n";
# Главный цикл: проверка чтения/принятия, проверка записи,
# проверка готовности к работе
while( 1 )
{
my $client;
my $rv;
my $data;
# Проверить наличие новой информации на имеющихся подключениях
# Есть ли что-нибудь для чтения или подтверждения?
foreach $client ( $select->can_read( 1 ) )
{
if ( $client == $server )
{
# Принять новое подключение
$client = $server->accept();
$select->add( $client );
nonblock( $client );
}
else
{
# Прочитать данные
$data = '';
$rv = $client->recv( $data, POSIX::BUFSIZ, 0 );
unless( defined( $rv ) && length $data )
{
# Это должен быть конец файла, поэтому закрываем клиента
delete $inbuffer{$client};
delete $outbuffer{$client};
delete $ready{$client};
$select->remove( $client );
close $client;
next;
}
$inbuffer{$client} .= $data;
# Проверить, говорят ли данные в буфере или только что прочитанные
# данные о наличии полного запроса, ожидающего выполнения. Если да -
# заполнить $ready{$client} запросами, ожидающими обработки.
while( $inbuffer{$client} =~ s/(.*\n)// ) { push( @{$ready{$client}}, $1 ) }
}
}
# Есть ли полные запросы для обработки?
foreach $client ( keys %ready ) { handle( $client ); }
# Сбрасываем буферы?
foreach $client ( $select->can_write( 1 ) )
{
# Пропустить этого слиента, если нам нечего сказать
next unless $outbuffer{$client};
block( $client );
$rv = $client->send( $outbuffer{$client}, 0 );
nonblock( $client );
unless( defined $rv )
{
# Пожаловаться, но следовать дальше
warn "I was told I could write? but I can`t.\n";
next;
}
if ( $rv == length $outbuffer{$client} || $! == POSIX::EWOULDBLOCK )
{
substr( $outbuffer{$client}, 0, $rv ) = '';
delete $outbuffer{$client} unless length $outbuffer{$client};
}
else
{
# Не удалось записать все данные и не из-за блокировки.
# Очистить буферы и следовать дальше.
delete $inbuffer{$client};
delete $outbuffer{$client};
delete $ready{$client};
$select->remove($client);
close($client);
next;
}
}
}
# handle( $socket ) обрабатывает все необработанные запросы
# для клиента $client
sub handle
{
# Запрос находится в $ready{$client}
# Отправить вывод в $outbuffer{$client}
my $client = shift;
my $request;
foreach $request ( @{$ready{$client}} )
{
print "\nrequest=".$request if ( $debug == 1 );
if ( $request =~ /^testRID/ )
{
my $open_client = "";
foreach my $kod ( keys %CLRULE )
{ $open_client .= $open_client eq "" ? $kod : " ".$kod;
($CLRULE_ID{$kod} ne "")?$open_client.="-".$CLRULE_ID{$kod}:"";
}
$outbuffer{$client} .= $open_client."\n";
}
elsif ( $request =~ /^test/ )
{
my $open_client = "";
foreach my $kod ( keys %CLRULE )
{ $open_client .= $open_client eq "" ? $kod : " ".$kod;}
$outbuffer{$client} .= $open_client."\n";
}
elsif ( $request =~ /^add\t([0-9]+)\t(.*)/ )
{
my ($skip,$rid)=split /RULE/,$2;
print "\n=rule".$rid."\n" if ( $debug == 1 );
my ($kod, $rule) = ($1, $2);
&delete_rule( $kod ) if ( exists $CLRULE{$kod} );
&add_rule( $kod, $rule,$rid ) if ( !exists $CLRULE{$kod} );
}
elsif ( $request =~ /^remove\t([0-9]+)\t(.*)/ )
{
&delete_rule( $1,$2 ) if ( exists $CLRULE{$1} );
}
}
delete $ready{$client};
}
# nonblock( $socket ) переводит сокет в неблокирующий режим
sub nonblock
{
my $socket = shift;
my $flags;
$flags = fcntl( $socket, F_GETFL, 0 )
or die "Can`t get flags for socket: $!\n";
fcntl( $socket, F_SETFL, $flags | O_NONBLOCK )
or die "Can`t make socket nonblocking: $!\n";
}
sub block
{
my $socket = shift;
my $flags;
$flags = fcntl( $socket, F_GETFL, 0 )
or die "Can`t get flags for socket: $!\n";
fcntl( $socket, F_SETFL, $flags ^ O_NONBLOCK )
or die "Can`t make socket nonblocking: $!\n";
}
sub add_rule
{
my $kod = $_[0];
my $rule = $_[1];
my $rid = $_[2];
my %N = ();
my %P = ();
$CLRULE{$kod} = $rule;
$CLRULE_ID{$kod} = $rid;
while ( $rule =~ /\{N([AB0-9]+)\}/ )
{
my $n = $1;
my $i = $rule_start - 1;
my $j = 0;
while( 1 )
{
while( 1 )
{
$i++;
last if ( !exists $USERULEN{$i} );
}
$j++;
last if ( $j == $n );
last if ( $n == 0 );
}
$USERULEN{$i} = $kod;
$N{$n} = $i;
$rule =~ s/\{N$n\}/$N{$n}/g;
}
while ( $rule =~ /\{P([AB0-9]+)\}/ )
{
my $p = $1;
my $i = $pipe_start - 1;
my $j = 0;
while( 1 )
{
while( 1 )
{
$i++;
last if ( !exists $USEPIPEN{$i} );
}
$j++;
last if ( $j == $p );
last if ( $p == 0 );
}
$USEPIPEN{$i} = $kod;
$P{$p} = $i;
$rule =~ s/\{P$p\}/$P{$p}/g;
}
foreach my $i ( keys %N ) { $CLUSERULEN{$kod} .= exists $CLUSERULEN{$kod} && $CLUSERULEN{$kod} ne "" ? " ".$N{$i} : $N{$i}; }
foreach my $i ( keys %P ) { $CLUSEPIPEN{$kod} .= exists $CLUSEPIPEN{$kod} && $CLUSEPIPEN{$kod} ne "" ? " ".$P{$i} : $P{$i}; }
$rule =~ s/\|pipe/; \/sbin\/ipfw -q pipe /g;
$rule =~ s/\|table/; \/sbin\/ipfw -q table /g;
$rule =~ s/\|add ([0-9]+)/; \/sbin\/ipfw -q add $1 set $set /g;
$rule =~ s/^pipe/\/sbin\/ipfw -q pipe /g;
$rule =~ s/^table/\/sbin\/ipfw -q table /g;
$rule =~ s/^add ([0-9]+)/\/sbin\/ipfw -q add $1 set $set /g;
$rule =~ s/\|/;/g;
# print "$ipfw -q $rule\n" if ( $debug == 1 );
# $err = `$ipfw -q $rule`;
print "$rule\n" if ( $debug == 1 );
$err = `$rule`;
}
sub delete_rule
{
my $kod = $_[0];
my $rule= $_[1];
if ( exists $CLRULE{$kod} )
{
$rule =~ s/\|table/; \/sbin\/ipfw -q table /g;
$rule =~ s/^table/\/sbin\/ipfw -q table /g;
$rule =~ s/\|/;/g;
# print "$ipfw -q $rule\n" if ( $debug == 1 );
# $err = `$ipfw -q $rule`;
print "$rule\n" if ( $debug == 1 );
$err = `$rule`;
my @N = split( / /, $CLUSERULEN{$kod} );
foreach my $i ( @N )
{
print "$ipfw delete $i\n" if ( $debug == 1 );
$err = `$ipfw delete $i`;
delete $USERULEN{$i};
}
my @P = split( / /, $CLUSEPIPEN{$kod} );
foreach my $i ( @P )
{
print "$ipfw pipe delete $i\n" if ( $debug == 1 );
$err = `$ipfw pipe delete $i`;
delete $USEPIPEN{$i};
}
delete $CLUSERULEN{$kod};
delete $CLUSEPIPEN{$kod};
delete $CLRULE_ID{$kod};
delete $CLRULE{$kod};
}
}