Fórum Root.cz
Hlavní témata => Vývoj => Téma založeno: Jan Novotný 06. 11. 2024, 22:05:00
-
Dobrý den,
prosím, potřebuji poradit, jak správně použít bind socketu na konkrétní síťovou kartu v linuxu.
Jde o tento případ: Linux se 3 síťovkami, default gateway je směrem za síťovkou 1. Adresa serveru 192.168.1.55.
Klientské síťovky 192.168.1.51, 192.168.1.52, 192.168.1.53.
Klient ve výchozím stavu odesílá / přijímá data v pořádku přes síťovku 1.
Odesílat / přijímat potřebuji přes síťovku 3 (enp0s8).
Musím tedy použít bind to device.
setsockopt(udpSocket, SOL_SOCKET, SO_BINDTODEVICE, localEthDeviceName
Pokud nabinduji socket na enp0s8, je vidět v tcpdump, že klient data odešle přes síťovku 3 a také přijímá přes síťovku 3, ale nastává problém, že recvfrom() nezíská ze socketu žádná data a tedy odpověď do aplikace nedorazí.
Nevíte, čím to může být, proč recvfrom v případě bindu na síťovku žádná data nedostane?
Jak bych měl bind správně použít?
Kód jednoduchého UDP klienta přikládám níže.
Děkuju moc za radu.
#include <iostream>
#include <string>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <fstream>
#include <sstream>
#include <cerrno>
using namespace std;
int main(int argc, char** argv) {
#define MAXBUF 1024
int udpSocket, returnStatus, addrlen;
struct sockaddr_in udpClient, udpServer;
char buf[MAXBUF];
string message = getSendMessage();
string localEthDeviceName = "enp0s8";
/* create a socket */
if ((udpSocket = socket(AF_INET, SOCK_DGRAM, 0)) == -1){
fprintf(stderr, "Could not create a socket!\n");
return(1);
}else{
printf("Socket created.\n");
}
if (setsockopt(udpSocket, SOL_SOCKET, SO_BINDTODEVICE, localEthDeviceName.c_str(), static_cast<socklen_t>(strlen(localEthDeviceName.c_str()))) == -1) {
close(udpSocket);
return 1;
}
/* client address */
string localIp = "192.168.1.51";
udpClient.sin_family = AF_INET;
udpClient.sin_addr.s_addr = inet_addr(localIp.c_str());
udpClient.sin_port = htons(5065);
returnStatus = bind(udpSocket, (struct sockaddr*)&udpClient, sizeof(udpClient));
if (returnStatus == 0) {
fprintf(stderr, "Bind completed!\n");
}
else {
fprintf(stderr, "Could not bind to address!\n");
close(udpSocket);
return(1);
}
/* server address */
string serverIp = "192.168.1.55";
udpServer.sin_family = AF_INET;
udpServer.sin_addr.s_addr = inet_addr(serverIp.c_str());
udpServer.sin_port = htons(5060);
returnStatus = static_cast<int>(sendto(udpSocket, message.c_str(), strlen(message.c_str()) + 1, 0, (struct sockaddr*)&udpServer, sizeof(udpServer)));
if (returnStatus == -1) {
fprintf(stderr, "Could not send message!\n");
}
else {
/* message sent: look for confirmation */
addrlen = sizeof(udpServer);
returnStatus = static_cast<int>(recvfrom(udpSocket, buf, MAXBUF, 0, (struct sockaddr*)&udpServer, reinterpret_cast<socklen_t*>(& addrlen)));
if (returnStatus <= 0) {
fprintf(stderr, "Didn't get any response: %s\n", strerror(errno));
}
else {
buf[returnStatus] = 0;
printf("Received: %s\n", buf);
}
}
return 0;
}
-
vypnout globálně rp_filter ?
-
Podle tech IP jsou vsechny tri sitovky v jednom subnetu...? Neuvadite tedy masku site, ale i tak.
A ty pakety vam na sitovku 3 prichazeji? Co rika tcpdump?
-
Podle tech IP jsou vsechny tri sitovky v jednom subnetu...? Neuvadite tedy masku site, ale i tak.
A ty pakety vam na sitovku 3 prichazeji? Co rika tcpdump?
Dobrý den, ano síťovky jsou všechny ve stejném subnetu, síť 192.168.1.0/24, oct: 255.255.255.0. První síťovka má nastavenou gateway. (v této situaci není rozhodující, server je ve stejném segmentu)
V tcpdump je vidět, že po nabindování na 3. síťovku, UDP provoz správně odejde 3. síťovkou a i se 3.síťovkou vrátí správně odpověď ze serveru.
Jenom recvfrom() ze socketu nic nezíská a do aplikace odpověď nedorazí.
Děkuji
-
Proč tam není bridge s jednou adresou? Pak se nemusí řešit takové nestandardní ohejbáky a asi by to i fungovalo ;).
-
Proč tam není bridge s jednou adresou? Pak se nemusí řešit takové nestandardní ohejbáky a asi by to i fungovalo ;).
Dobrý den, zařízení a jeho hw konfigurace je bez nároku na změnu. Díky za pochopení.
Prosím odpovědi, které vedou k získání dat ze socketu.
Děkuji
-
Proč tam není bridge s jednou adresou? Pak se nemusí řešit takové nestandardní ohejbáky a asi by to i fungovalo ;).
Load balancing? Jasně - na to je standardní odpověď "bonding driver (https://wiki.linuxfoundation.org/networking/bonding#bonding_driver_options)" - ten umí balanced režimů několik. Ovšem nejlíp to funguje, když je naproti podpora taky na switchi, do kterého je to zapíchnuté. Takže nakonec, takový "explicitní load-balancing na koleně" nemusí být úplně od věci...
-
Píšete, že příchozí pakety vidíte na správném síťovém rozhraní v tcpdumpu. Ten síťovku uvede hardwarově do promiskuitního režimu a tuším otvírá raw socket (nebo má nějaký háček na ten způsob).
Pokud příchozí paket nevybublá v bindnutém socketu, pátrejte dál, kde se asi tak mohl cestou ztratit.
Cestou = softwarovým stackem vzhůru, protože z hardwaru do kernelu jste ho už dostal.
Příchozí cílová MAC adresa sedí? Pokud ano, driver stack nemá důvod paket zahodit na základě toho, že přišel s nesprávnou cílovou MAC adresou.
Když pogooglíte SO_BINDTODEVICE, najdete historické zmínky od lidí, že to použili a že jim to fungovalo. Najdeta taky zmínku starou snad 20 let, že SO_BINDTODEVICE je nějaká deprecated dočasná obezlička - ale i následné reakce, že to je pitomost, když se to v upstreamu drží dodnes naživu.
Potkal jsem taky zmínku od konkrétního člověka, kterému to nejelo - a vyskytla se tam rada někoho dalšího, zkusit vypnout rp_filter (já na to přišel vlastní úvahou).
rp_filter tradičně zahazuje podle zdrojové IP adresy pakety, které přišly z rozhraní, kam nevede route s odpovídajícím cílem. Klasické použití je například u strojů, které mají dvě upstream rozhraní do divokého internetu ke dvěma ISP, default route vede jedním z nich, ale chcete brát příchozí provoz i druhým uplinkem (a dál si už nějak zařídíte stateful routing odpovědí = to už je jiná pohádka, zde OT).
Vaše situace je jiná. Vy máte více rozhraní do téhož L2 subnetu. Holý Linux se v takové situaci tradičně chová tak, že jako odchozí rozhraní používá tvrdošíjně stále jedno. Dokonce snad i v případě, kdy je na dotyčném fyzickém portu L1 link down (pak to nefunguje vůbec). Proto takové konfigurace běžně nepoužívám a nemám s nimi zkušenosti. Čekal bych, že "locally connected" route pro daný subnet uvidím ve směrovací tabulce třikrát (pro tři fyzická rozhraní, číslovaná z téhož subnetu). Viz příkaz "route" (bez argumentů). Pokud ho vidíte jenom jednou, máte o důvod víc, mračit se na rp_filter. Ale i pokud ten route vidíte třikrát, tak standardní chování je, že reálně funguje jenom ten první. Neznám detaily fungování rp_filtru pod kapotou / na úrovni zdrojáků jádra, abych řekl, zda může za Váš problém.
Jak to prakticky vyzkoušet:
Globální vypnutí rp_filteru:
echo 0 > /proc/sys/net/ipv4/conf/all/rp_filter
Vypnout rp_filter pro jedno konkrétní rozhraní (mělo by přestat dropovat nevyhovující pakety):
echo 0 > /proc/sys/net/ipv4/conf/enp0s8/rp_filter
Pokud to zabere, tak natrvalo to zadáte jedním z několika způsobů:
- /etc/rc.local
- /etc/sysctl.conf (lze editovat ručně, nebo utilitou sysctl z příkazového řádku)
- příslušný ifup skript, nebo ekvivalent pro Váš způsob správy síťových rozhraní...
- konfigurace firewallu
- ...
Pokud se rp_filter ukáže jako falešná stopa, tak se pro jistotu podívejte, jak vypadá konfigurace IPtables:
iptables -L
iptables -t nat -L
A tady moje vědomosti "od stolu" zhruba končí. Následovalo by asi nějaké bádání ve vnitřnostech routovacího stacku, asi by tam šlo něco trasovat i bez zkoumání zdrojáků apod.
-
A ještě mě napadlo, zavřít ten síťový port do network namespace. To by muselo fungovat, samo o sobě.
Tenhle nápad má ale následující vadu: do stejného namespace následně musíte zavřít proces, který skrz to rozhraní bude komunikovat. Tzn. IMO nehrozí, spustit jeden proces, aby bindoval sockety na několika takových rozhraních. Resp. nikdy jsem to nezkoušel, nerad bych kecal.
ip netns add $NAMESPACE
ip link set $NETIF netns $NAMESPACE
ip netns exec $NAMESPACE ip addr add dev $NETIF $IP_ADDR/$IPNET_SLASH
# "netif up" must be done *after* the netif gets assigned to a namespace
ip netns exec $NAMESPACE ip link set $NETIF up
ip netns exec $NAMESPACE $MY_CUSTOM_PROGRAM
...by mě zajímalo, co by se stalo, kdybych spustil nějaký proces přímo, bez "ip netns exec $NAMESPACE", a pak bindoval sockety na rozhraní, již přiřazená popsaným způsobem do izolovaných namespaces...
-
Pokud tu konfiguraci site nehodlas zmenit a bavi te nesmyslne programovani a sitarina, tak si tu sitovku otevri jako SOCK_RAW a vytvarej ty UDP pakety rucne (mela by to byt cesta jakou pouziva pak i tcpdump).
-
V jakém prostředí ti připojí zařízení a nedovolí ho zprovoznit? Když ne tobě, tak někomu jinému.
Normálně nastav routu ať traffic na .55 jde přes "třetí" síťovku, která asi teda má IP .53 a program bude bindovat na .53.
-
Normálně nastav routu ať traffic na .55 jde přes "třetí" síťovku, která asi teda má IP .53 a program bude bindovat na .53.
...měl bych poznámku, snad naivní, že directly connected routy prýští z kernelu samy od sebe, tím že nastavíte na rozhraní adresu a masku... Ještě jsem nezkoušel, vytvářet "directly connected" routy explicitně / ručně...
-
Tak ono je to celkově podivné. Připádá mi to jako XY problém, kdy bez znalosti původního problému realizujeme pravděpodobně nesmyslné řešení :)
-
On ten nápad, připojit jeden stroj do jednoho subnetu a jedné L2 sítě dvěma a více porty, buď kvůli redundanci (failover) nebo dokonce kvůli load balancingu, ten je velmi intuitivní, pochopitelný a starý. Na konkrétním hřejivém lidském příběhu mi v tomto případě až tak nesejde. A jestli se tazateli podaří Linux k tomu přemluvit, tak má u mě palec nahoru :-)
-
On ten nápad, připojit jeden stroj do jednoho subnetu a jedné L2 sítě dvěma a více porty...
To sice ano, ale existuji na to standardizovana reseni, ktera chce zjevne tazatel nahradit tim, ze si bude programovat vlastni sitovy stack. Coz sice muze byt uchvatna zabava na dlouhe zimni vecery, ale ...
A rek bych ze s bondovanymi iface se spokoji i tupy switch.
-
Jan Novotný: A jak máš ty IP k těm síťovkám přiřazené? Uvádíš tři interfejsy a tři IP 192.168.1.51, 192.168.1.52, 192.168.1.53 a potřebu komunikovat přes třetí s iface name enp0s8. Bindneš v kódu k socketu daný iface enp0s8 a client IP 192.168.1.51. Nepatří k té třetí síťovce správně ta IP 192.168.1.53?
jjrsk: Ono ne vždy to standardní dělá co chci, občas se nějaká vylomenina hodí. :-) Třeba něčím podobným, v kombinaci s multicastem, tak obcházím nutnost mít switche s HSR (které stojí nechutný peníz a navíc mají obvykle moc málo portů) a stačí mi k tomu obyč switche s MSTP a 2 VLANy a mám také bezeztrátový přenos a "zero-time recovery" při selhání jednoho switche/propoje.
-
Jan Novotný: A jak máš ty IP k těm síťovkám přiřazené? Uvádíš tři interfejsy a tři IP 192.168.1.51, 192.168.1.52, 192.168.1.53 a potřebu komunikovat přes třetí s iface name enp0s8. Bindneš v kódu k socketu daný iface enp0s8 a client IP 192.168.1.51. Nepatří k té třetí síťovce správně ta IP 192.168.1.53?
Musel přijít ten co dává pozor... :-D
-
Já chválím za ten rp_filter - bez jeho vypnutí to nefunguje, pokud mám víc síťovek ve stejném IP segmentu. :-)
Jsem to zkusil pustit. Funguje to a data dojdou zpět do recvfrom(). Pokud binduji konkrétní iface a client IP, tak by měla být shoda toho IP a iface, datagram musí přijít tím bindnutým ifacem a navíc musí být vypnutý rp_filter pro daný iface (zkrátka musí souhlasit na příchozím daná IP a iface). Pokud jako odchozí IP dám jinou, než odpovídá na vynucený iface, tak ono to odejde přes bindnutý iface se zadanou IP, ale vlivem ARP a forward tabulky ve switchi se odpověď vrátí často jiným ifacem a je smůla (pokud se vrátí tím předepsaným, tak se data také doručí do recvfrom()). Je to celkem striktní v případě unicast provozu.
-
...by mě zajímalo, co by se stalo, kdybych spustil nějaký proces přímo, bez "ip netns exec $NAMESPACE", a pak bindoval sockety na rozhraní, již přiřazená popsaným způsobem do izolovaných namespaces...
V takovém připadě se tomu procesu to nepovede, protože daný iface/IP v jiném namespace nevidí. Zkrátka to dopadne No such device/Cannot assign requested address.
Proces, pokud má práva (CAP_SYS_ADMIN), tak si může přepnout namespace pomocí setns(). Přesněji, je to per vlákno, takže jde pak použít vícevláknová aplikace, kde každé vlákno binduje/používá interfejsy v jiném namespace.
Takže toto je možná i čistější řešení pro tazatele, kdy si tu třetí síťovku hodí do jiného namespace a spustí tu apku nad tím, pokud by potřeboval komunikvoat přes víc sítí, tak víc těch vláken v různých net namespaces...
-
takže server má tři síťové adresy?
Mělo by to jít, když místo IP adresy originu uvedete síťové rozhraní originu , pak se použije daná adresa.
Ale nevím , zda to půjde v kódu. By mě zajímalo, jak to dělá curl.
Nicméně, pokud by to nešlo přepsat, a opravdu bys musel použít IP adresy jako specifikátor originu, tak abys docílil výběru rozhraní, bys musel použít ip virtuální routovací tabulky ( *** ), přičemže v každé bude default routa přes jiné síťové rozraní ... default via. .192.168.1.5x dev ethY + routa pro místní sít, pochopil jsem, že vše je v jednom subnetu)a policy routing (ip rule add from 192.168.1.5x lookup tabulkarozhrani3)
ale mám pocit, že to je rovnák na něco ohnutého
*** víte o způsobu jak přes příkaz ip * přidat virt. rout. tabulku jinak než echo mojetable >> /etc/iproute2/rt_tables?
(sice píšete že server je svatá kráva, že se nemůže sahat na konfiguraci)