Bind socketu na konkrétní síťovou kartu v C

Bind socketu na konkrétní síťovou kartu v C
« kdy: 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.

Kód: [Vybrat]
#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;
}
« Poslední změna: 06. 11. 2024, 22:08:29 od Petr Krčmář »


Re:Bind socketu na konkrétní síťovou kartu v C
« Odpověď #1 kdy: 06. 11. 2024, 23:17:27 »
vypnout globálně rp_filter ?

Re:Bind socketu na konkrétní síťovou kartu v C
« Odpověď #2 kdy: Dnes v 00:10:34 »
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?

Re:Bind socketu na konkrétní síťovou kartu v C
« Odpověď #3 kdy: Dnes v 06:49:22 »
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

CFM

Re:Bind socketu na konkrétní síťovou kartu v C
« Odpověď #4 kdy: Dnes v 06:51:19 »
Proč tam není bridge s jednou adresou? Pak se nemusí řešit takové nestandardní ohejbáky a asi by to i fungovalo  ;).


Re:Bind socketu na konkrétní síťovou kartu v C
« Odpověď #5 kdy: Dnes v 08:04:43 »
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

Re:Bind socketu na konkrétní síťovou kartu v C
« Odpověď #6 kdy: Dnes v 09:08:09 »
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" - 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...

Re:Bind socketu na konkrétní síťovou kartu v C
« Odpověď #7 kdy: Dnes v 10:04:10 »
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.

Re:Bind socketu na konkrétní síťovou kartu v C
« Odpověď #8 kdy: Dnes v 11:27:45 »
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...

RDa

  • *****
  • 2 707
    • Zobrazit profil
    • E-mail
Re:Bind socketu na konkrétní síťovou kartu v C
« Odpověď #9 kdy: Dnes v 11:42:12 »
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).