Salve. Abbiamo una rete con 5k clienti. Recentemente è emerso un problema non molto piacevole: nel centro della rete abbiamo un Brocade RX8 che ha iniziato a inviare molti pacchetti unknown-unicast, poiché la rete è suddivisa in VLAN — in parte questo non è un problema MA ci sono VLAN dedicate per indirizzi bianchi, ecc., e sono distribuite in tutte le direzioni della rete. Quindi immaginate il flusso in ingresso su un indirizzo cliente che il border non gestisce e questo flusso va verso un link radio verso un qualche (in effetti verso tutti) villaggio — il canale è intasato — i clienti sono arrabbiati — tristezza...
L'obiettivo è trasformare un bug in una funzionalità. Ho pensato a soluzioni q-in-q con un client VLAN completo, ma vari dispositivi come il P3310 smettono di inoltrare DHCP quando viene attivato dot1q, inoltre non supportano qinq selettivo e ci sono molte complicazioni di questo tipo. Cos'è ip-unnumbered e come funziona? In breve: è l'indirizzo del gateway più il percorso sull'interfaccia. Per il nostro compito dobbiamo: ridurre il traffico, assegnare indirizzi ai clienti, aggiungere rotte ai clienti attraverso interfacce specifiche. Cosa utilizziamo per fare tutto questo? Per il shaping utilizziamo lisg, per DHCP va bene db2dhcp su due server indipendenti, e sui server di accesso gira dhcprelay, mentre su questi stessi server lavora anche ucarp per il backup. Ma come aggiungiamo le rotte? Possiamo aggiungerle tutte in anticipo con uno script, ma non è la soluzione ideale. Dunque, creeremo un workaround personalizzato.
Dopo aver cercato su internet, ho trovato una fantastica libreria di alto livello per C++ che permette di sniffare il traffico in modo elegante. L'algoritmo del programma che aggiunge le route è il seguente: ascoltiamo le richieste ARP sull'interfaccia, se abbiamo un indirizzo sull'interfaccia looppback che viene richiesto, allora aggiungiamo la route tramite questa interfaccia e aggiungiamo una registrazione ARP statica su questo IP. Insomma, un po' di copiaincolla, un po' di personalizzazione e il gioco è fatto.
Codice sorgente del 'router'
#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 di installazione di libtins
#!/bin/bash
git clone https://github.com/mfontanini/libtins.git
cd libtins
mkdir build
cd build
cmake ../
make
make install
ldconfig
Comando per la compilazione del binario
g++ main.cpp -o arp-rt -O3 -std=c++11 -lpthread -ltinsCome avviarlo?
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ì — riordina le tabelle al segnale HUP. Perché non ho usato netlink? Semplicemente non ne avevo voglia, e poi Linux è uno script su uno script — quindi va tutto bene. Ma parlando di route, cosa c'è dopo? Adesso dobbiamo inviare al border le route presenti su questo server — quindi, a causa delle stesse vecchie apparecchiature, abbiamo scelto la via del minore sforzo: abbiamo assegnato questo compito al BGP.
Configurazione BGPhostname *******
password *******
log file /var/log/bgp.log
!
# номер ас-ки, адреса и сети выдуманы
router bgp 12345
bgp router-id 1.2.3.4
redistribuire connesso
ridistribuire statico
vicino 1.2.3.1 remote-as 12345
vicino 1.2.3.1 next-hop-self
vicino 1.2.3.1 route-map none in
vicino 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
Continuiamo. Affinché il server risponda alle richieste ARP, è necessario abilitare il proxy ARP.
echo 1 > /proc/sys/net/ipv4/conf/eth0.800/proxy_arp
Andiamo avanti — ucarp. Scriviamo noi stessi gli script di avvio per questa meraviglia.
Esempio di avvio di un demone.
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
Affinché dhcprelay funzioni su un'interfaccia, ha bisogno di un indirizzo. Per questo, sugli indirizzi delle interfacce che utilizziamo, aggiungeremo indirizzi fittizi — ad esempio 10.255.255.1/32, 10.255.255.2/32 e così via. Non parlerò di come configurare il relay — è tutto molto semplice.
Quindi, cosa abbiamo? Backup dei gateway, configurazione automatica delle rotte, DHCP. Questa è la dotazione minima — a tutto ciò si aggiunge LISG e già abbiamo anche un shaper. Perché tutto è così lungo e complesso? Non è più semplice usare accel-pppd e utilizzare direttamente PPPoE? No, non è così semplice — le persone hanno difficoltà a inserire un patch cord nel router, per non parlare del PPPoE. accel-ppp è un ottimo strumento — ma non ci ha soddisfatto — ci sono molti errori nel codice — si blocca, taglia male, e la cosa più triste è che se si blocca, le persone devono riavviare tutto — i telefoni diventano rossi — insomma, non è andato bene. Qual è il vantaggio di usare ucarp invece di keepalived? Beh, in tutto — ci sono 100 gateway, keepalived e un errore nella configurazione — tutto non funziona. Con ucarp non funziona 1 gateway. Per quanto riguarda la sicurezza, si dirà che scriveranno indirizzi falsi e utilizzeranno su circolare — per controllare questo aspetto, su tutti gli switch/olt/basi configuriamo dhcp-snooping + source-guard + arp inspection. Se il cliente non ha DHCP ma statico — access-list sulla porta.
Perché è stato fatto tutto questo? Per eliminare il traffico indesiderato. Ora, ogni switch ha la sua VLAN e l'unknown-unicast non è più un problema, poiché può viaggiare solo su una porta e non su tutte… Inoltre, ci sono effetti collaterali: una configurazione standardizzata dell'hardware e una maggiore efficienza nella distribuzione dello spazio indirizzi.
Come configurare lisg — questo è un tema a parte. I collegamenti alle librerie sono disponibili. Forse quanto esposto sopra aiuterà qualcuno nella realizzazione dei propri compiti. La versione 6 non è ancora implementata nella nostra rete — ma ci sarà un problema: è in programma di riscrivere lisg per la versione 6 e sarà necessario modificare il programma che aggiunge le rotte.
Fonte: habr.com
