Hallo. Es gibt also ein Netzwerk mit 5.000 Kunden. Kürzlich gab es ein unangenehmes Problem — im Zentrum des Netzwerks steht ein Brocade RX8, der viele unknown-unicast-Pakete sendet. Da das Netzwerk in VLANs unterteilt ist, ist das teilweise kein Problem, aber es gibt spezielle VLANs für weiße Adressen usw., die sich in alle Richtungen des Netzwerks erstrecken. Stellen Sie sich nun den eingehenden Datenstrom zu einer Kundenadresse vor, die nicht über den Border-Router gelernt wird, und dieser Datenstrom fliegt in Richtung eines Funksenders in ein bestimmtes (und somit in alle) Dörfer — der Kanal ist überlastet — die Kunden sind verärgert — Traurigkeit…
Die Aufgabe besteht darin, einen Fehler in ein Feature zu verwandeln. Ich habe in Richtung q-in-q mit einem vollständigen Client-VLAN gedacht, aber verschiedene Geräte wie P3310 hören auf, DHCP durchzulassen, wenn dot1q aktiviert ist. Sie unterstützen auch kein selektives qinq und haben viele potenzielle Probleme dieser Art. Was ist ip-unumbered und wie funktioniert es? Kurz gesagt — Gateway-Adresse + Route auf dem Interface. Für unsere Aufgabe müssen wir Folgendes tun: Shaping durchführen, Adressen an Kunden verteilen, Routen zu Kunden über bestimmte Interfaces hinzufügen. Womit machen wir das? Shaping — lisg, DHCP — db2dhcp auf zwei unabhängigen Servern. Auf den Zugangservern läuft dhcprelay, ebenso läuft ucarp auf den Zugangservern — als Backup. Aber wie fügt man Routen hinzu? Man könnte alles im Voraus mit einem großen Skript hinzufügen — aber das wäre nicht wahrhaftig. Also werden wir eine eigene Lösung erstellen.
Nach gründlicher Recherche im Internet habe ich eine großartige High-Level-Bibliothek für C++ gefunden, die es ermöglicht, den Datenverkehr elegant zu sniffen. Der Algorithmus des Programms, das Routen hinzufügt, funktioniert folgendermaßen: Wir hören auf ARP-Anfragen an der Schnittstelle und wenn wir auf dem Server eine Adresse an der Schnittstelle lo haben, die angefragt wird, fügen wir über diese Schnittstelle eine Route hinzu und erstellen einen statischen ARP-Eintrag für diese IP — im Grunde genommen ein paar Kopie-und-Einfüge-Projekte, ein wenig Eigenes und fertig.
Quellcodes des 'Routenprogramms'
#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;
}
}Installationsskript für libtins
#!/bin/bash
git clone https://github.com/mfontanini/libtins.git
cd libtins
mkdir build
cd build
cmake ../
make
make install
ldconfig
Befehl zum Erstellen der Binärdatei
g++ main.cpp -o arp-rt -O3 -std=c++11 -lpthread -ltinsWie wird es gestartet?
start-stop-daemon --start --exec /opt/ipoe/arp-routes/arp-rt -b -m -p /opt/ipoe/arp-routes/daemons/eth0.800.pid -- eth0.800
Ja — er passt die Tabellen bei HUP-Signalen an. Warum wurde kein Netlink verwendet? Einfach zu faul, und Linux ist ein Skript auf einem Skript — also ist alles in Ordnung. So, die Routen sind gesetzt, was kommt als Nächstes? Weiterhin müssen wir die Routen, die auf diesem Server vorhanden sind, an den Border senden — hier sind wir aufgrund der veralteten Hardware den Weg des geringsten Widerstands gegangen und haben diese Aufgabe auf BGP gelegt.
BGP-Konfigurationhostname ******
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
Wir machen weiter. Damit der Server auf ARP-Anfragen antwortet, muss Proxy ARP aktiviert werden.
echo 1 > /proc/sys/net/ipv4/conf/eth0.800/proxy_arp
Gehen wir weiter — ucarp. Die Startskripte für dieses Wunder schreiben wir selbst.
Beispiel zum Starten eines Daemons
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
Damit dhcprelay auf der Schnittstelle funktioniert, benötigt es eine Adresse. Daher fügen wir auf den Schnittstellen, die wir verwenden, stellvertretende Adressen hinzu — zum Beispiel 10.255.255.1/32, 10.255.255.2/32 usw. Wie man den Relay konfiguriert, werde ich nicht erklären — das ist ganz einfach.
Also, was haben wir? Backup-Gateways, automatische Routenanpassung, DHCP. Das ist das Minimum — zusätzlich kommt das LISG dazu und wir haben schon einen Shaper. Warum ist alles so langwierig und verwickelt? Ist es nicht einfacher, accel-pppd zu verwenden und einfach PPPoE zu nutzen? Nein, es ist nicht einfacher — die Leute haben schon Schwierigkeiten, ein Patchkabel in den Router zu stecken, geschweige denn bei PPPoE. Accel-PPP ist großartig — aber es hat uns nicht überzeugt — viele Fehler im Code — es stürzt ab, schneidet schlecht und das Schlimmste ist, dass, wenn es abstürzt, die Leute alles neu starten müssen — die Telefone läuten rot — es kam einfach nicht in Frage. Was sind die Vorteile von ucarp im Vergleich zu keepalived? Überall — es gibt 100 Gateways, keepalived und ein Fehler in der Konfiguration — alles funktioniert nicht. Bei ucarp funktioniert ein Gateway nicht. Was die Sicherheit betrifft, dass sie sich falsche Adressen eintragen und auf den Netzteilen verwenden — zur Kontrolle dieses Aspekts aktivieren wir DHCP-Snooping + Source-Guard + ARP-Inspektion auf allen Switches/OLTs/Datenbanken. Wenn der Kunde kein DHCP hat, sondern eine statische IP — Access-List am Port.
Warum wurde das alles gemacht? Zur Zerschlagung des uns nicht genehmen Traffics. Jetzt hat jeder Switch sein VLAN und unknown-unicast ist kein Problem mehr, da es nur noch in einen einzelnen Port gehen muss und nicht mehr in alle... Und die Nebeneffekte – standardisierte Konfiguration der Geräte, größere Effizienz in der Adressverteilung.
Wie man lisg einrichtet – das ist ein eigenes Thema. Links zu den Bibliotheken sind angefügt. Vielleicht hilft das oben Gesagte jemandem bei der Umsetzung seiner Aufgaben. Die Version 6 wird bei uns im Netz noch nicht implementiert – aber es wird ein Problem geben – es ist geplant, lisg für Version 6 neu zu schreiben und das Programm, das Routen hinzufügt, muss ebenfalls angepasst werden.
Quelle: habr.com
