пятница, 18 марта 2016 г.

Передача номера звонящего другому хосту в Asterisk

Возникла необходимость передавать по HTTP информацию о входящих звонках: на этапе входящего звонка (incoming), трубка поднята (answered), трубка положена (hangup).

Переполнение буферов в свитчах. Кто виноват и что делать?

Недавно перед уходом домой решил заглянуть в IRC. И к своему удивлению обнаружил обсуждение очень интересной темы: микровсплески и дропы на интерфейсах свитча, которые не загружены и наполовину.

Далеко не каждый сетевик сталкивается и знает про микровсплески (microburst) в сетях. Это явление, когда очень короткий промежуток времени, зачастую измеряемый в милисекундах, порт принимает трафик на linerate или значении близком к нему.

среда, 12 февраля 2014 г.

Защита Control Plane на примере Juniper

Любому сетевому администратору известно, что сетевое оборудование состоит из Control Plane и Data Plane:
  • Control plane отвечает за все "высокоуровневые" операции, такие как управление роутингом с помощью протоколов маршрутизации и помещение собранной таблицы маршрутизации/коммутации в Forwarding Information Base (FIB), ARP, SNMP etc.
  • Data plane - компонент, который занимается непосредственно маршрутизацией/коммутацией пакетов, а также их фильтрацией с помощью Firewall/ACL. Как правило, этот компонент исполнен "в железе", то есть является микросхемой специального назначения, ASIC (Application Specific Integrated Circuit). Запас производительности обработки пакетов ASIC-а огромен по сравнению с обычным CPU.
 Обычно control plane работает на слабом процессоре MIPS или ARM и исчерпать его ресурсы совсем несложно, поэтому если ваша сеть подвергается DDoS-атакам, то рано или поздно атакующему может прийти в голову "уложить" control plane машрутизатора, чтобы лишить связи ресурсы, находящиеся за ним.
Для защиты control plane-а достаточно настроить политику, жестко ограничивающую поступающий на него трафик. Так как фильтрацией трафика занимается data plane, весь лишний трафик будет отбрасываться прежде чем дойдёт до control plane.

После чтения этой статьи получились такие политики для IPv4 и IPv6 трафика.

Используем фичу Juniper, которая позволяет формировать списки IP-адресов прямо из текущей конфигурации:
# show policy-options

prefix-list BGP-neighbors-v4 {
  apply-path "protocols bgp group <*> neighbor <*.*>";
}
prefix-list BGP-neighbors-v6 {
  apply-path "protocols bgp group <*> neighbor <*:*>";
}
prefix-list DNS-servers-v4 {
apply-path "system name-server <*.*>;";
}
prefix-list DNS-servers-v6 {
  apply-path "system name-server <*:*>;";
}

Таким образом, если Вы, например, добавите нового BGP-соседа, его IP-адрес сразу же занесётся в разрешающие правила фаерволла.

Код полисера для ICMP. Мы разрешаем ICMP к маршрутизатору, однако в разумных пределах. Ничего страшного, если при атаке наш роутер перестанет отвечать на пинги:
# show firewall policer icmp-control-plane
if-exceeding {
    bandwidth-limit 512k;
    burst-size-limit 1500;
}
then discard;


Политика доступа по IPv4:
# show firewall family inet filter control-plane-ipv4
term icmp {
    from {
        protocol icmp;
    }
    then policer icmp-control-plane;
}
term management {
    from {
        source-address {

            ## Управляющая сеть, через которую происходит 
            ## управление роутером по SSH,
            ## сбор статистики по SNMP
            10.20.30.0/24;
        }
        port [ snmp snmptrap tftp ftp http https ssh telnet ];
    }
    then accept;
}
term bgp {
    from {
        source-prefix-list {
            BGP-neighbors-v4;
        }
        protocol tcp;

        ## Важно фильтровать трафик по TTL, так как
        ## атакующие могут подставить source address
        ## на адрес вашего BGP-соседа, а вот правильно
        ## угадать исходящий TTL на каждом боте не получится.
        ## TTL обычной сессии BGP всегда равен 1
        ttl 1;
        port 179;
    }
    then accept;
}
term dns {
    from {
        source-prefix-list {
            DNS-servers-v4;
        }
        protocol udp;
        source-port 53;
    }
    then accept;
}
term ospf {
    from {
        protocol ospf;
    }
    then accept;
}
term discard-all {
    then {
        discard;
    }
}

Политика доступа по IPv6:
# show firewall family inet6 filter control-plane-ipv6
term icmp-ND {
    from {

        ## Эти типы ICMP-сообщений используются в IPv6 для
        ## обнаружения соседей. Этакий аналог ARP. Если 
        ## будет атака ICMP-сообщениями, то эти будут проходить
        ## без препятствий и не будут отсечены полисером.
        icmp-type [ neighbor-advertisement neighbor-solicit ];
    }
    then accept;
}
term icmp-general {
    from {
        payload-protocol icmp6;
    }
    then policer icmp-control-plane;
}
term bgp {
    from {
        source-prefix-list {
            BGP-neighbors-v6;
        }
        payload-protocol tcp;

        ## А вот возможности фильтрации по TTL в IPv6 нет :(
        port 179;
    }
    then accept;
}
term dns {
    from {
        source-prefix-list {
            DNS-servers-v6;
        }
        payload-protocol udp;
        source-port 53;
    }
    then accept;
}
term discard-all {
    then discard;
}

Вешаем эти фильтры на специальный интерфейс lo0.0 - именно он отвечает за проходящий в control plane трафик:
# show interfaces lo0 unit 0
family inet {
    filter {
        input control-plane-ipv4;
    }
}
family inet6 {
    filter {
        input control-plane-ipv6;
    }
}

Всё. Этого должно быть достаточно, чтобы защитить control plane от непрошенных гостей.

среда, 13 апреля 2011 г.

Site-to-site IPSec VPN между Cisco и Linux

В этой заметке я приведу пример простейшего Site-to-site (S2S) туннеля между Cisco и Linux с объяснениями.

S2S VPN - это туннель, целью которого является соединение нескольких сетей (в отличие от host-to-host, когда целью является соединение двух хостов).

Итак, у нас имеется 2 роутера:
  • 1.1.1.1 - Linux. За ним находится сеть 192.168.1.0/24
  • 2.2.2.2 - Cisco. За ним находится сеть 192.168.2.0/24

Обязательно уясните, что при использовании IPSec Вы не увидите ни отдельного туннельного интерфейса, ни относящихся к туннелю записей в таблице маршрутизации. В IPSec для определения, какой трафик будет шифроваться и отправляться в туннель, а какой нет, существуют так называемые Security Policies. Они абсолютно статичны и объявляются непосредственно при настройке туннеля.

Итак, начнем с Linux. В нашем примере это будет Debian.
Устанавливаем OpenSWAN. С помощью него мы будет создавать туннели:
apt-get install openswan

Нам потребуются следующие файлы:
  • /etc/ipsec.conf - Собственно файл конфигурации IPSEC
  • /etc/ipsec.secrets - Файл с ключами аутенификации, либо общими ключами (PSK). В данном примере будет использоваться аутенификация по PSK.

Открываем конфигурационный файл /etc/ipsec.conf и в конец добавляем следующий блок (отступы имеют значение):
conn my-test-conn
        type=tunnel
        authby=secret
        left=1.1.1.1
        leftnexthop=%defaultroute
        leftsubnet=192.168.1.0/24
        right=2.2.2.2
        rightsubnet=192.168.2.0/24
        esp=3des-md5-96
        keyexchange=ike
        pfs=no
        auto=start

Небольшое пояснение по описанным параметрам:
  • left - Наш внешний адрес, с которого создаем туннель
  • leftsubnet - Наша локальная сеть, трафик из которой будем заводить в туннель. Это используется для формирования Security Policy, о которой я говорил выше.
  • right - Удаленный маршрутизатор, к которому строим туннель
  • rightsubnet - Сеть за удаленным маршрутизатором, трафик к которой будет шифроваться. Используется для Security Policy
  • esp - параметры шифрования.
Теперь необходимо указать общий ключ PSK. Он указывается в файле /etc/ipsec.secrets. Добавимв этот файл следующую строчку:
1.1.1.1 2.2.2.2 : PSK "mysecretkey"

Внимание! Много людей натыкаются на одну и ту же проблему с симптомами "туннель устанавливается, но трафик не идет". Причина тому - NAT на внешнем интерфейсе. Трафик между сетями, входящими в IPSec, не должен НАТиться! Для этого добавим следующее первое правило в таблицу nat, цепочку POSTROUTING в iptables:
iptables -i nat -A POSTROUTING -s 192.168.1.0/24 -d 192.168.2.0/24 -j ACCEPT

Таким образом, никаких действий с трафиком между нашими сетями происходить не будет. На этом настройка Linux закончена. Применим изменения командой:
/etc/init.d/ipsec restart


Настройка Cisco:
Войдем в режим конфигурирования:
configure terminal

Опишем первую фазу IPSec - политику ISAKMP:
isakmp policy 10 authentication pre-share
isakmp policy 10 encryption 3des
isakmp policy 10 hash md5
isakmp policy 10 group 2
isakmp policy 10 lifetime 3600
После этого опишем PSK, котрый будет использоваться для соединения с Linux:
isakmp key 0 mysecretkey address 1.1.1.1 no-xauth
Политик ISAKMP можно указать несколько. При согласовании соединения будет использоваться сначала политика с наименьшим номером, если удаленная сторона ее отвергнет, то будет использоваться политика с следующим по возрастанию номером. Мы точно знаем, чего хотим, поэтому указываем единственную политику :)

Опишем transform-set. Это определит, как мы будем инкапсулировать и шифровать трафик:
crypto ipsec transform-set myset esp-des esp-md5-hmac
mode tunnel
Настала очередь описать, собственно, наш туннель. В Cisco это называеся crypto map:
crypto map mymap 20 ipsec-isakmp
crypto map mymap 20 match address 120
! Здесь маршрутизатор предупредит нас, что такого acces-list не существует. Ничего страшного
crypto map mymap 20 set peer 1.1.1.1
crypto map mymap 20 set transform-set myset
Опишем access-list с номером 120, который у нас будет исполнять роль Security Policy.
access-list 120 permit ip 192.168.2.0 0.0.0.255 192.168.1.0 0.0.0.255

Как видите, здесь указываются не маски подсети, а wildcard: 255 минус текущее значение маски подсети. То есть, wildcard 0.0.0.255 равем маске 255.255.255.0
Заключительная часть - привязываем crypto map на внешний сетевой интерфейс:
interface fastethernet 0/1
crypto map mymap

Готово. Можно проверять коннект с хоста в одной сети, пропинговав хост в другой сети. Маршрутизаторы друг друга пингоать не будут! Вот такая особенность данной реализации.

Проверено на Debian 6, Cisco серии 800, 2600.

понедельник, 4 апреля 2011 г.

Callback на Asterisk с базой callback-номеров в SQL

Однажды нашей компании понадобился коллбэк. Недолго думая, была выведена наиболее оптимальная схема: AGI-скрипт, который каждый входящий CallerID проверяет по базе, и если есть вхождения, перезванивает, соединяя с IVR.

Получился следующий скрипт:
#!/usr/bin/perl -w
use strict;
use DBI;
use Asterisk::AGI;

my $mysqlserver='127.0.0.1'; # сервер MySQL
my $mysqlbase='asterisk'; # база MySQL
my $mysqlport='3306'; # порт MySQL
my $mysqluser='user'; # Пользователь MySQL
my $mysqlpassword='password'; # Пароль MySQL
my $mysqltable='callback'; # таблица с номерами коллбэка
my $numberrow="number"; # столбец с номерами коллбэка
my $outcontext="out"; # контекст, в котором можно набирать исходящие
my $incontext="in"; # контекст, с которым соединяем исходящие вызовы коллбэка (IVR например)
my $inexten="s"; # добавочный, с которым соединяем исходящие вызовы
my $inprio"1"; # приоритет добавочного



my $callfile="/var/spool/asterisk/outgoing/callback".time().".call";

my $dbh=DBI->connect("DBI:mysql:database=$mysqlbase;host=$mysqlserver;port=$mysqlport",$mysqluser,$mysqlpassword)
or die "Can\'t connect to MySQL Server!\n";

sub check_number {
    my $query=$dbh->prepare("select $numberrow from $mysqltable where $numberrow = \"$_[0]\"");
    $query->execute();
    my $queryresult=$query->fetchrow();
    $query->finish();
    return $queryresult;
}

my $agi = new Asterisk::AGI;

my %input = $agi->ReadParse();

if (check_number($input{"callerid"})) {
    open(CF,'>',$callfile) or die "Can\'t create callfile!";
    print(CF    "Channel: Local\/".$input{"callerid"}."\@".$outcontext."\n".
                "MaxRetries: 3\n".
                "RetryTime: 4\n".
                "WaitTime: 30\n".
                "Context: ".$incontext."\n".
                "Extension: ".$inexten."\n".
                "Priority: ".$inprio."\n".
                "Callerid: ".$input{"callerid"}."\n"
          );
    close(CF);
    $dbh->disconnect();
    $agi->hangup();
}

$dbh->disconnect();
Сам код во входящем контексте Астериска выглядит просто:
exten => s,1,NoOp
exten => s,n,AGI(callback.pl)
exnte => s,n,Background(myivr)  ;;;; Дальнейшая обработка звонка (если не прошел проверку на коллбэк)

Привязки к типам данных в ячейках таблицы MySQL нет, главное, чтобы она выглядела как-то так:

+----+--------------+---------------------+
| id | number       | description         |
+----+--------------+---------------------+
| 10 | 89261234567  | User1               |
| 13 | 89262345767  | User2               |
|  3 | 380931352345 | User3               |
| 14 | 380953445434 | User4               |
+----+--------------+---------------------+

Вот, собственно, и все.

Настройка шейпера в Juniper

Настройка разбивается на 2 этапа: фаерволл и полисер.
Фаерволл ловит нужные пакеты и запускает их в полисер, который и ограничивает скорость:

[edit firewall]
root@juniper# show
family inet {
    filter lan-to-inet { ## Здесь указываем правила срабатывания
        term 1 { ## Это правило отключает шейп на VPN-сети
            from {
                source-address {
                    10.1.1.0/24;
                }
                destination-address {
                    10.10.20.0/24;
                    10.10.21.0/24;
                }
            }
            then accept;
        }
        term 2 { ## А вот это правило направляет оставшийся трафик в полисер shape-users-to-inet
            from {
                source-address {
                    10.1.1.0/24;
                }
                destination-address {
                    0.0.0.0/0;
                }
            }
            then policer shape-users-to-inet;
        }
    }
    filter inet-to-lan {
        term 1 {  ## Это правило отключает шейп с VPN-сетей                      
            from {
                source-address {
                    10.10.20.0/24;
                    10.10.21.0/24;
                }
                destination-address {
                    10.1.1.0/24;
                }
            }
            then accept;
        }
        term 2 { ## А это правило направляет весь оставшийся трафик в полисер
            from {
                source-address {
                    0.0.0.0/0;
                }
                destination-address {
                    10.1.1.0/24;
                }
            }
            then policer shape-users-to-inet;
        }
    }
}
policer shape-users-to-inet { ## Собственно полисер. Лимит - 7 мбит
    if-exceeding {
        bandwidth-limit 7m;            
        burst-size-limit 1500;
    }
    then discard;
}

Скрипт аутенификации в OpenVPN по текстовому файлу с паролями

Возникла необходимость создания OpenVPN-сервера с простой авторизацией по юзернему-паролю. В итоге вышел небольшой скрипт на perl, указанный ниже, который вызывался OpenVPN-ом, и читал из файла логины-пароли вида:
пользователь:пароль

Вот конфиг сервера:
port 2002
proto udp
dev tun
script-security 3 ## Обязательно, иначе не будет вызывать наш скрипт
ca keys/ca.crt
cert keys/server.crt
key keys/server.key
dh keys/dh1024.pem
server 10.10.22.0 255.255.255.0
ifconfig-pool-persist ipp.txt
client-config-dir ccd
auth-user-pass-verify keys/checkpasswd.pl via-env ## Вот собственно и вызов нашего скрипта
client-to-client
username-as-common-name
auth MD5
cipher AES-128-CBC
keepalive 10 40
comp-lzo
max-clients 20
user nobody
group nobody
persist-key
persist-tun

Собственно скрпт checkpasswd.pl:
#!/usr/bin/perl
use strict;

## Password file here. Must be an absolute path!
## Example content of passwords file:
## user:password
## foo:bar

my $file = '/usr/local/etc/openvpn/keys/passwords.txt';


## Dont touch below!
my $givenlogin = $ENV{username};
my $givenpass = $ENV{password};

open(CONF,'<',$file) or exit 1;

while() {
    ## Login will be $credentials[0] , password = credentials[1]
    my @credentials=split(':',$_);
    foreach(@credentials){
        chomp($_);
    }
    ## Check if login is in passwords file
    if($givenlogin eq $credentials[0]) {
    ## Check passwords is match
        if($givenpass eq $credentials[1]) {
    close(CONF);
            exit 0;
        }
        else {
    close(CONF);
            exit 1;
        }
    }
}
close(CONF);
exit 1;