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.