Как стать автором
Поиск
Написать публикацию
Обновить

Отказоустойчивая IPoE сеть из подручных средств

Время на прочтение7 мин
Количество просмотров4.6K

Здравствуйте. Значит есть сеть из 5к клиентов. Недавно вылез не очень приятный момент — в центре сети и у нас стоит Brocade RX8 и он начал слать много unknown-unicast пакетов, так как сеть поделена на вланы — то частично это не проблема НО есть спец вланы для белых адресов и т.д. и они растянуты во все стороны сети. Так вот теперь представьте входящий поток на адрес клиента, который бордером не учится и этот поток летит в сторону радио линка на какоето (а так на все) село — канал забит — клиенты злые — печаль…


Задача — превратить баг в фичу. Думал в сторону q-in-q с полноценным клиент-вланом но всякие железяки типа P3310 при включении dot1q перестают пропускать DHCP, ещё они не умеют selective qinq и много подводых костылей в этом роде. Что такое ip-unnambered и как это работает? Если очень в кратце — адрес шлюза + маршрут на интерфейсе. Для нашей задачи нам надо: резать шейпера, раздавать адреса клиентам, добавлять маршруты к клиентам через определенные интерфейсы. Чем все это делать? Шейпер — lisg, dhcp — db2dhcp на двух независимых серверах, на серверах доступа крутится dhcprelay, так же на серверах доступа работает ucarp — для бекапа. А вот как добавлять маршруты? Можно большим скриптом заранее все добавить — но это не трувей. Значит будем городить самописный костыль.


Основателно порывшись в интернете я нашел замечательную библиотеку высокого уровня для c++, которая позволяет красиво снифить траффик. Алгоритм работы программки, добавляющей маршруты следующий — слушаем на интерфейсе арп запросы, если у нас на сервере есть адресс на интерфейсе lo, который запрашивают то добавляем маршрут через этот интерфейс и добавляем статическую запись арп на этот ip — вообщем несколько копипастов, немного отсебятины и готово


Исходники 'маршрутчика'
#include <stdio.h>
#include <sys/types.h>
#include <ifaddrs.h>
#include <netinet/in.h>
#include <string.h>
#include <arpa/inet.h>

#include <tins/tins.h>
#include <map>
#include <iostream>
#include <functional>
#include <sstream>

using std::cout;
using std::endl;
using std::map;
using std::bind;
using std::string;
using std::stringstream;

using namespace Tins;

class arp_monitor {
public:
    void run(Sniffer &sniffer);
    void reroute();
    void makegws();
    string iface;
    map <string, string> gws;
private:
    bool callback(const PDU &pdu);
    map <string, string> route_map;
    map <string, string> mac_map;
    map <IPv4Address, HWAddress<6>> addresses;
};

void  arp_monitor::makegws() {
    struct ifaddrs *ifAddrStruct = NULL;
    struct ifaddrs *ifa = NULL;
    void *tmpAddrPtr = NULL;
    gws.clear();
    getifaddrs(&ifAddrStruct);
    for (ifa = ifAddrStruct; ifa != NULL; ifa = ifa->ifa_next) {
        if (!ifa->ifa_addr) {
            continue;
        }
        string ifName = ifa->ifa_name;
        if (ifName == "lo") {
            char addressBuffer[INET_ADDRSTRLEN];
            if (ifa->ifa_addr->sa_family == AF_INET) { // check it is IP4
                // is a valid IP4 Address
                tmpAddrPtr = &((struct sockaddr_in *) ifa->ifa_addr)->sin_addr;
                inet_ntop(AF_INET, tmpAddrPtr, addressBuffer, INET_ADDRSTRLEN);
            } else if (ifa->ifa_addr->sa_family == AF_INET6) { // check it is IP6
                // is a valid IP6 Address
                tmpAddrPtr = &((struct sockaddr_in6 *) ifa->ifa_addr)->sin6_addr;
                inet_ntop(AF_INET6, tmpAddrPtr, addressBuffer, INET6_ADDRSTRLEN);
            } else {
                continue;
            }
            gws[addressBuffer] = addressBuffer;
            cout << "GW " << addressBuffer << " is added" << endl;
        }
    }
    if (ifAddrStruct != NULL) freeifaddrs(ifAddrStruct);
}

void arp_monitor::run(Sniffer &sniffer) {
    cout << "RUNNED" << endl;
    sniffer.sniff_loop(
            bind(
                    &arp_monitor::callback,
                    this,
                    std::placeholders::_1
            )
    );
}

void arp_monitor::reroute() {
    cout << "REROUTING" << endl;
    map<string, string>::iterator it;
    for ( it = route_map.begin(); it != route_map.end(); it++ ) {
        if (this->gws.count(it->second) && !this->gws.count(it->second)) {
            string cmd = "ip route replace ";
            cmd += it->first;
            cmd += " dev " + this->iface;
            cmd += " src " + it->second;
            cmd += " proto static";
            cout << cmd << std::endl;
            cout << "REROUTE " << it->first << " SRC " << it->second << endl;
            system(cmd.c_str());
            cmd = "arp -s ";
            cmd += it->first;
            cmd += " ";
            cmd += mac_map[it->first];
            cout << cmd << endl;
            system(cmd.c_str());

        }
    }
    for ( it = gws.begin(); it != gws.end(); it++ ) {
	string cmd = "arping -U -s ";
	cmd += it->first;
	cmd += " -I ";
	cmd += this->iface;
	cmd += " -b -c 1 ";
	cmd += it->first;
        system(cmd.c_str());
    }
    cout << "REROUTED" << endl;
}

bool arp_monitor::callback(const PDU &pdu) {
    // Retrieve the ARP layer
    const ARP &arp = pdu.rfind_pdu<ARP>();

    if (arp.opcode() == ARP::REQUEST) {
	
        string target = arp.target_ip_addr().to_string();
        string sender = arp.sender_ip_addr().to_string();
        this->route_map[sender] = target;
        this->mac_map[sender] = arp.sender_hw_addr().to_string();
        cout << "save sender " << sender << ":" << this->mac_map[sender] << " want taregt " << target << endl;
        if (this->gws.count(target) && !this->gws.count(sender)) {
            string cmd = "ip route replace ";
            cmd += sender;
            cmd += " dev " + this->iface;
            cmd += " src " + target;
            cmd += " proto static";
//            cout << cmd << std::endl;
/*            cout << "ARP REQUEST FROM " << arp.sender_ip_addr()
                 << " for address " << arp.target_ip_addr()
                 << " sender hw address " << arp.sender_hw_addr() << std::endl
                 << " run cmd: " << cmd << endl;*/
            system(cmd.c_str());
            cmd = "arp -s ";
            cmd += arp.sender_ip_addr().to_string();
            cmd += " ";
            cmd += arp.sender_hw_addr().to_string();
            cout << cmd << endl;
            system(cmd.c_str());
        }
    }
    return true;
}

arp_monitor monitor;
void reroute(int signum) {
    monitor.makegws();
    monitor.reroute();
}

int main(int argc, char *argv[]) {
    string test;
    cout << sizeof(string) << endl;

    if (argc != 2) {
        cout << "Usage: " << *argv << " <interface>" << endl;
        return 1;
    }
    signal(SIGHUP, reroute);
    monitor.iface = argv[1];
    // Sniffer configuration
    SnifferConfiguration config;
    config.set_promisc_mode(true);
    config.set_filter("arp");

    monitor.makegws();

    try {
        // Sniff on the provided interface in promiscuous mode
        Sniffer sniffer(argv[1], config);

        // Only capture arp packets
        monitor.run(sniffer);
    }
    catch (std::exception &ex) {
        std::cerr << "Error: " << ex.what() << std::endl;
    }
}


Скрипт установки libtins
#!/bin/bash

git clone https://github.com/mfontanini/libtins.git
cd libtins
mkdir build
cd build
cmake ../
make
make install
ldconfig


Команда для сборки бинарника
g++ main.cpp -o arp-rt -O3 -std=c++11 -lpthread -ltins


Как его запускать?

start-stop-daemon --start --exec  /opt/ipoe/arp-routes/arp-rt -b -m -p /opt/ipoe/arp-routes/daemons/eth0.800.pid -- eth0.800


Да — он по сигналу HUP перестравиает таблицы. Почему не использовал нетлинк? Лень просто да и линукс это скрипт на скрипте — так, что все нормально. Ну маршруты маршрутами, что дальше? Дальше нам надо отослать на бордер маршруты, которые есть на этом сервере — тут, в силу тех же устаревших железяк, мы пошли путем наименьшего сопротивления — положили эту задачу на BGP.


Конфиг bgp
hostname *******
password *******
log file /var/log/bgp.log
!
# номер ас-ки, адреса и сети выдуманы
router bgp 12345
bgp router-id 1.2.3.4
redistribute connected
redistribute static
neighbor 1.2.3.1 remote-as 12345
neighbor 1.2.3.1 next-hop-self
neighbor 1.2.3.1 route-map none in
neighbor 1.2.3.1 route-map export out
!
access-list export permit 1.2.3.0/24
!
route-map export permit 10
match ip address export
!
route-map export deny 20

Продолжаем. Для того, чтобы сервер отвечал на арп запросы необходимо включить прокси арп.




echo 1 > /proc/sys/net/ipv4/conf/eth0.800/proxy_arp

Идем дальше — ucarp. Скрипты запуска сего чуда пишем сами


Пример запуска одного демона

start-stop-daemon --start --exec  /usr/sbin/ucarp -b -m -p /opt/ipoe/ucarp-gen2/daemons/$iface.$vhid.$virtualaddr.pid -- --interface=eth0.800 --srcip=1.2.3.4 --vhid=1 --pass=carpasword --addr=10.10.10.1 --upscript=/opt/ipoe/ucarp-gen2/up.sh --downscript=/opt/ipoe/ucarp-gen2/down.sh -z -k 10 -P --xparam="10.10.10.0/24"


up.sh

#!/bin/bash

iface=$1
addr=$2
gw=$3

vlan=`echo $1 | sed "s/eth0.//"`


ip ad ad $addr/32 dev lo
ip ro add blackhole $gw
echo 1 > /proc/sys/net/ipv4/conf/$iface/proxy_arp

killall -9 dhcrelay
/etc/init.d/dhcrelay zap
/etc/init.d/dhcrelay start


killall -HUP arp-rt


down.sh

#!/bin/bash

iface=$1
addr=$2
gw=$3

ip ad d $addr/32 dev lo
ip ro de blackhole $gw
echo 0 > /proc/sys/net/ipv4/conf/$iface/proxy_arp


killall -9 dhcrelay
/etc/init.d/dhcrelay zap
/etc/init.d/dhcrelay start



Чтобы dhcprelay работал на интерфейсе — ему нужен адресс. По этому на интерфейсах, которые мы используем мы добавим левые адреса — например 10.255.255.1/32, 10.255.255.2/32 и т.д. Как настраивать релей рассказывать не буду — там все просто.


Итак, что мы имеем. Бекап шлюзов, автонастройка маршрутов, dhcp. Это минимальный набор — еще на это все накручивается lisg и у нас уже есть и шейпер. Почему все так длинно и заморочно? Не проще ли взять accel-pppd и вообще юзать pppoe? Нет не проще — люди с трудом пачкорд в роутер впихнуть могут, не говоря уже про pppoe. accel-ppp штука крутая — но нам не зашла — куча ошибок в коде — сыпется, режит криво, и самое печальное то, что если он скрашился — то, людям надо все перезаружать — телефоны красные — вообщем не подошел он. В чем плюс использования ucarp а не keepalived? Да во всем — есть 100 шлюзов, keepalived и одна ошибка в конфиге — не работает все. С ucarp не работает 1 шлюз. По поводу безопасности, мол пропишут себе адреса левые и будут пользоваться на шарика — для контроля этого момента на всех свичах/олтах/базах настраиваем dhcp-snooping + source-guard + arp inspection. Если у клиента не dhpc а статика — acces-list на порту.


Зачем это все делалось? Для уничтожения неугодного нам траффика. Теперь на каждый свич свой влан и unknown-unicast уже не страшен, так как ходить то ему уже только в один порт надо а не во все… Ну и побочные эффекты — стандартизированный конфиг оборудования, большая эффективность распределения адресного пространства.


Как настраивать lisg — это отдельная тема. Ссылки на библиотеки прилагаются. Возможно комуто поможет изложенное выше в реализации своих задач. Версию 6 у нас в сети пока не внедряют — но там будет проблемка — в планах переписать lisg под 6 версию ну и надо будет подправить програмку, что маршруты добавляет.


Linux ISG
DB2DHCP
Libtins

UPD.

Все оказалось немного сложнее… Пришлось написать более-менее нормальный демон. И обойтись без прокси арп. Теперь на арп отвечает мой демон, так же он добавляет-удаляет маршруты на подсети к клиентам, также пришлось научится работать с нетлинком. И выяснилась одна особенность — когда линукс узнает арп на какой либо адрес, то после нахождения интерфейса — он берет первый попавшийся адресс с последнего — на что клиенты не отвечают некоторые — решается с помошью arptables.

Вообщем уже нормальный вариант по ссылке есть — там кстате реализованы прослушивание изменений маршрутов и адресов и добавление удаление маршрута через нетлинк (с последним головная боль была ужасная)

github
Теги:
Хабы:
Всего голосов 12: ↑9 и ↓3+6
Комментарии9

Публикации

Ближайшие события