Red IPeE resistente a fallos hecha con materiales disponibles

Hola. Pues hay una red de 5k clientes. Recientemente salió un tema bastante desagradable: en el centro de la red tenemos un Brocade RX8 y ha empezado a enviar muchos paquetes unknown-unicast, ya que la red está dividida en VLANs — por un lado esto no es un problema, PERO hay VLANs especiales para direcciones públicas, etc., y están distribuidas en todas las direcciones de la red. Así que ahora imagina el flujo entrante hacia la dirección del cliente, que el border no aprende y este flujo se dirige hacia un enlace de radio a algún (y así a todos) pueblo — el canal está saturado — los clientes están molestos — tristeza...

La tarea es convertir un bug en una función. Estuve pensando en q-in-q con un VLAN de cliente completo, pero algunos dispositivos como el P3310 dejan de pasar DHCP al activar dot1q, además no saben hacer selective qinq y hay muchas trampas de ese tipo. ¿Qué es ip-unnumbered y cómo funciona? En resumen: dirección del gateway + ruta en la interfaz. Para nuestra tarea necesitamos: limitar el shaper, asignar direcciones a los clientes, agregar rutas a los clientes a través de ciertas interfaces. ¿Cómo hacemos todo esto? Shaper — lisg, DHCP — db2dhcp en dos servidores independientes, en los servidores de acceso corre dhcprelay, y también en los servidores de acceso funciona ucarp — para respaldo. ¿Y cómo añadir rutas? Se pueden añadir todas de antemano con un gran script — pero eso no es verdadero. Así que vamos a improvisar un parche hecho a mano.

Fundamentalmente, tras investigar en Internet, encontré una maravillosa biblioteca de alto nivel para C++ que permite sniffing de tráfico de manera elegante. El algoritmo del programa que agrega rutas es el siguiente: escuchamos en la interfaz las solicitudes ARP, si tenemos una dirección en el servidor en la interfaz lo que se solicita, agregamos la ruta a través de esta interfaz y añadimos una entrada ARP estática para esta IP — en general, un par de copias de código, un poco de creatividad y listo.

Código fuente del 'ruteador'

#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;
    }
}

Script de instalación de libtins

#!/bin/bash

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

Comando para compilar el binario

g++ main.cpp -o arp-rt -O3 -std=c++11 -lpthread -ltins

¿Cómo se ejecuta?


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

Sí, reconfigura las tablas al recibir la señal HUP. ¿Por qué no usaste netlink? Simple pereza, y además Linux es un script sobre otro script, así que todo está bien. Los caminos son los caminos, ¿y después qué? Luego necesitamos enviar al borde las rutas que existen en este servidor; aquí, debido al mismo hardware obsoleto, elegimos el camino de menor resistencia: delegamos esta tarea al BGP.

Configuración de BGPnombre de host *******
contraseña *******
archivo de registro /var/log/bgp.log
!
# номер ас-ки, адреса и сети выдуманы
router bgp 12345
id de router bgp 1.2.3.4
redistribuir conectado
redistribuir estático
vecino 1.2.3.1 remote-as 12345
vecino 1.2.3.1 next-hop-self
vecino 1.2.3.1 route-map none in
vecino 1.2.3.1 route-map export out
!
lista de acceso export permitir 1.2.3.0/24
!
route-map export permitir 10
coincidir dirección ip export
!
route-map export negar 20

Continuamos. Para que el servidor responda a las consultas ARP, es necesario habilitar el proxy ARP.


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

Sigamos adelante: ucarp. Escribimos nosotros mismos los scripts para iniciar este milagro.

Ejemplo de inicio de un daemon


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

Para que dhcprelay funcione en la interfaz, necesita una dirección. Por eso, en las interfaces que usamos, añadiremos direcciones ficticias, como 10.255.255.1/32, 10.255.255.2/32, etc. No voy a explicar cómo configurar el relay, es bastante simple.

Así que, ¿qué tenemos? Backup de gateways, auto-configuración de rutas, DHCP. Este es el conjunto mínimo, además de esto tenemos lisg y ya tenemos un shaper. ¿Por qué es todo tan largo y complicado? ¿No sería más fácil usar accel-pppd y usar PPPoE? No, no es más fácil; a las personas les cuesta meter un patch cord en el router, y ya no hablemos de PPPoE. Accel-ppp es una herramienta genial, pero no funcionó para nosotros: tenía muchos errores en el código, fallaba, cortaba de forma incorrecta y lo más triste es que si se colapsaba, las personas tenían que reiniciar todo; los teléfonos son rojos, en resumen, no fue adecuado. ¿Cuál es la ventaja de usar ucarp en lugar de keepalived? En todo, si hay 100 gateways, keepalived y un error en la configuración, todo deja de funcionar. Con ucarp, si falla un gateway, sigue funcionando. En cuanto a la seguridad, dicen que escribirán direcciones ficticias y las usarán en el pool: para controlar este momento, en todos los switches/OLT/bases configuramos dhcp-snooping + source-guard + arp inspection. Si el cliente no tiene DHCP, sino estática, lista de acceso en el puerto.

¿Por qué se hizo todo esto? Para eliminar el tráfico no deseado. Ahora cada switch tiene su propia VLAN y el unknown-unicast ya no es un problema, ya que solo tiene que ir a un puerto y no a todos... Además, los efectos secundarios son una configuración estandarizada del equipo y una mayor eficiencia en la distribución del espacio de direcciones.

Cómo configurar lisg es un tema aparte. Se adjuntan enlaces a bibliotecas. Tal vez lo expuesto anteriormente ayude a alguien en la realización de sus tareas. La versión 6 aún no se ha implementado en nuestra red, pero habrá un problema: se planea reescribir lisg para la versión 6 y también será necesario ajustar el programa que añade rutas.

Linux ISG
DB2DHCP
Libtins

Fuente: habr.com

Compra un hosting fiable para sitios web con protección contra DDoS, servidores VPS VDS 🔥 Compra un hosting fiable para sitios web con protección contra DDoS, servidores VPS VDS | ProHoster