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};
	}
}
Личные инструменты