FreeBSD manad, понимающий изменения правил в тарифах
Материал из BiTel WiKi
Оттестировали на 4.6 ipfw manad и скрипт с поддержкой изменения правил. Попутно в manad внесены изменения для помещения его правил в отдельный set ipfw. За основу были взяты Реализация_стандартного_шлюза_Manad_на_BeanShell и Изменения_в_manad_для_работы_с_одним_pipe_на_множество_IP_адресов. Внесены поправки из-за Не открывается шлюз manad
При модификации преследовались две цели - минимизировать потерю производительности и обеспечить совместимость старого manad с новым скриптом и встроенного скрипта с новым manad.
При отработке смены правил используется последовательность remove-add, так что возможны "разрывы".
Скрипт:
После полного перехода на новый manad строчку
if(rid==null && status.ruleType!=null) ruleChanged=false; // совместимость со старым manad
if(rid==null && status.ruleType!=null) ruleChanged=true;
import java.io.*; import java.net.*; import java.util.*; import bitel.billing.common.module.ipn.*; import bitel.billing.server.ipn.bean.*; 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 ); out.println( "testRID" ); kods = in.readLine(); if ( log.isDebugEnabled() ) { log.debug( 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 ); command = "remove\t" + cid.intValue() + "\t" + rule; out.println( command ); if ( log.isDebugEnabled() ) { log.debug( 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"); } } } flag = true; gateRules.remove( cid ); } // правила нет, а юзер открыт, правило есть, но было изменение типа правила if ( (!flag && status.status == IPNContractStatus.STATUS_OPEN ) ||(ruleChanged && status.status == IPNContractStatus.STATUS_OPEN )) { rule = generateRule( status ); 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 ); } } } in.close(); out.close(); socket.close(); } catch ( e ) { throw new RuntimeException ( e ); } } private generateRule( status ) { rule = null; // пользовательское правило, без типа if( status.ruleType == null ) { rule = status.rule.getRuleText(); } // типизированное правило else { ruleText = ManadUtils.getRule( status.gateType, status.ruleType ); rule = ManadUtils.generateRule( ruleText, status.rule.getRuleText(), null, status.ruleType ); } rule = rule.replaceAll( "\r", "" ); rule = rule.replaceAll( "\n", "|" ); return rule; }
FreeBSD manad:
#!/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 = 15000; $pipe_start = 200; $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]+)/ ) { &delete_rule( $1 ) 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/\|add ([0-9]+)/; \/sbin\/ipfw -q add $1 set $set /g; $rule =~ s/^pipe/\/sbin\/ipfw -q pipe /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]; if ( exists $CLRULE{$kod} ) { 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}; } }