Bonjour. Donc nous avons un réseau de 5 000 clients. Récemment, un problème désagréable est survenu : au centre du réseau, nous avons un Brocade RX8 qui a commencé à envoyer beaucoup de paquets unknown-unicast. Étant donné que le réseau est divisé en VLAN, ce n'est pas vraiment un problème PARTIELLEMENT, MAIS il y a des VLAN spéciaux pour les adresses blanches, etc. et ils sont étendus dans toutes les directions du réseau. Imaginez maintenant un flux entrant à l'adresse d'un client qui n'est pas étudié par le border et ce flux se dirige vers un lien radio vers un village quelconque (et ainsi vers tous) — le canal est saturé — les clients sont mécontents — quel désastre…
La tâche consiste à transformer un bug en fonctionnalité. Je pensais à la solution q-in-q avec un VLAN client complet, mais divers appareils comme le P3310 cessent de faire passer le DHCP lorsqu'on active le dot1q, et ils ne savent pas non plus faire du qinq sélectif et il y a beaucoup de solutions de contournement en ce sens. Qu'est-ce que l'ip-unumbered et comment cela fonctionne ? Si je dois résumer — l'adresse de la passerelle + le routage sur l'interface. Pour notre tâche, nous devons : gérer le shaper, distribuer les adresses aux clients, ajouter des routes aux clients via certaines interfaces. Avec quoi allons-nous faire tout cela ? Shaper — lisg, DHCP — db2dhcp sur deux serveurs indépendants, sur les serveurs d'accès, nous avons un dhcprelay qui tourne, et un ucarp fonctionne aussi sur ces serveurs — pour la sauvegarde. Mais comment ajouter des routes ? On peut tout ajouter avec un gros script à l'avance — mais ce n'est pas très vrai. Donc nous allons créer une solution de contournement maison.
En fouillant profondément sur Internet, j'ai trouvé une merveilleuse bibliothèque de haut niveau pour C++, qui permet de sniffer le trafic de manière élégante. L'algorithme de fonctionnement du programme qui ajoute des routes est le suivant : nous écoutons les requêtes ARP sur l'interface, si nous avons une adresse sur l'interface lo qui est demandée, nous ajoutons une route via cette interface et ajoutons une entrée ARP statique pour cette IP — en gros, quelques copiés-collés, un peu d'imagination et c'est prêt.
Sources du 'routage'
#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 d'installation de libtins
#!/bin/bash
git clone https://github.com/mfontanini/libtins.git
cd libtins
mkdir build
cd build
cmake ../
make
make install
ldconfig
Commande pour compiler le binaire
g++ main.cpp -o arp-rt -O3 -std=c++11 -lpthread -ltinsComment le lancer ?
start-stop-daemon --start --exec /opt/ipoe/arp-routes/arp-rt -b -m -p /opt/ipoe/arp-routes/daemons/eth0.800.pid -- eth0.800
Oui — il reconstruit les tables à la signalisation HUP. Pourquoi n'avez-vous pas utilisé Netlink ? C'est juste par paresse, et puis Linux est un script sur un script — donc tout va bien. Bon, les routes c'est une chose, et après ? Ensuite, nous devons envoyer vers le border les routes existantes sur ce serveur — ici, en raison de ces vieux matériels, nous avons choisi la voie de la moindre résistance — nous avons confié cette tâche au BGP.
Configuration BGPhostname *******
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
Continuons. Pour que le serveur réponde aux requêtes ARP, il est nécessaire d'activer le proxy ARP.
echo 1 > /proc/sys/net/ipv4/conf/eth0.800/proxy_arp
Avançons — ucarp. Nous écrivons nous-mêmes les scripts de démarrage de ce produit.
Exemple de démarrage d'un démon
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
Pour que dhcprelay fonctionne sur une interface, il a besoin d'une adresse. Donc, sur les interfaces que nous utilisons, nous allons ajouter des adresses factices — par exemple 10.255.255.1/32, 10.255.255.2/32, etc. Je ne vais pas expliquer comment configurer le relais — c'est assez simple.
Alors, que avons-nous ? Sauvegarde des passerelles, auto-configuration des routes, DHCP. C'est un ensemble minimal — et en plus, nous avons LISG et un shaper. Pourquoi tout cela est-il si long et compliqué ? Ne serait-il pas plus simple de prendre accel-pppd et de simplement utiliser PPPoE ? Non, ce n'est pas plus simple — les gens ont du mal à forcer le mot de passe dans le routeur, sans parler de PPPoE. accel-ppp est un super appareil — mais ça ne nous a pas convenu — plein d'erreurs dans le code — il plante, coupe mal, et le plus triste c'est que s'il plante — les gens doivent tout redémarrer — les téléphones deviennent rouges — en gros, ça n'a pas fonctionné. Quel est l'avantage d'utiliser ucarp plutôt que keepalived ? Tout — il y a 100 passerelles, keepalived et une erreur dans la configuration — tout ne fonctionne pas. Avec ucarp, une seule passerelle ne fonctionne pas. Concernant la sécurité, s'ils écrivent des adresses factices et qu'ils les utilisent dans le réseau — pour contrôler cela, sur tous les commutateurs / routeurs / bases, nous configurons dhcp-snooping + source-guard + arp inspection. Si le client n'a pas DHCP mais des adresses statiques — access-list sur le port.
Pourquoi tout cela était-il nécessaire ? Pour éradiquer le trafic indésirable. Maintenant, chaque switch a son propre VLAN et l'unknown-unicast n'est plus un souci, car il ne doit passer que par un seul port et non pas par tous… Et les effets secondaires – une configuration normalisée de l'équipement, une plus grande efficacité dans la distribution de l'espace d'adresse.
Comment configurer lisg – c'est un sujet à part. Des liens vers les bibliothèques sont inclus. Peut-être que ce qui est décrit ci-dessus aidera certaines personnes dans la réalisation de leurs tâches. La version 6 n'est pas encore déployée dans notre réseau – mais cela posera un problème – il est prévu de réécrire lisg pour la version 6 et il faudra également modifier le programme qui ajoute les routes.
Source : habr.com
