Řešení, které jsem zjistil, že funguje, je následující. Nejprve musíme změnit nastavení ARP a RP. Do /etc/sysctl.conf přidejte následující a restartujte počítač (existuje také příkaz pro dynamické nastavení):
net.ipv4.conf.default.arp_filter = 1
net.ipv4.conf.default.rp_filter = 2
net.ipv4.conf.all.arp_filter = 1
net.ipv4.conf.all.rp_filter = 2
Filtr arp byl nezbytný, aby umožnil směrování odpovědí z eth0 přes WAN. Volba filtru rp byla nezbytná pro striktní přiřazení příchozích paketů k síťové kartě, na které přišly (na rozdíl od slabého modelu, který je spojuje s jakoukoli síťovou kartou odpovídající podsíti). K tomuto kritickému kroku mě přivedl komentář od EJP.
Poté začal SO_BINDTODEVICE pracovat. Každý ze dvou soketů byl vázán na svou vlastní síťovou kartu, a proto jsem mohl určit, ze které síťové karty přišla zpráva, podle soketu, ze kterého přišla.
s=socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
rc=setsockopt(s, SOL_SOCKET, SO_BINDTODEVICE, nic, IF_NAMESIZE);
memset((char *) &si_me, 0, sizeof(si_me));
si_me.sin_family = AF_INET;
si_me.sin_port = htons(LISTEN_PORT);
si_me.sin_addr.s_addr = htonl(INADDR_ANY);
rc=bind(s, (struct sockaddr *)&si_me, sizeof(si_me))
Dále jsem chtěl reagovat na příchozí datagramy datagramy, jejichž zdrojová adresa je adresa NIC, ze které pochází původní požadavek. Odpovědí je pouze vyhledat adresu NIC a svázat odchozí soket s touto adresou (pomocí bind
).
s=socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)
get_nic_addr(nics, (struct sockaddr *)&sa)
sa.sin_port = 0;
rc = bind(s, (struct sockaddr *)&sa, sizeof(struct sockaddr));
sendto(s, ...);
int get_nic_addr(const char *nic, struct sockaddr *sa)
{
struct ifreq ifr;
int fd, r;
fd = socket(AF_INET, SOCK_DGRAM, 0);
if (fd < 0) return -1;
ifr.ifr_addr.sa_family = AF_INET;
strncpy(ifr.ifr_name, nic, IFNAMSIZ);
r = ioctl(fd, SIOCGIFADDR, &ifr);
if (r < 0) { ... }
close(fd);
*sa = *(struct sockaddr *)&ifr.ifr_addr;
return 0;
}
(Možná, že vyhledávání adresy NIC pokaždé vypadá jako plýtvání, ale je to mnohem více kódu, abyste byli informováni, když se adresa změnila, a tyto transakce probíhají pouze jednou za několik sekund v systému, který nefunguje na baterii.)
Cílovou adresu, kterou používá odesílatel, můžete získat pomocí IP_RECVDSTADDR
pokud to vaše platforma podporuje, pomocí recvmsg()
. Je to poměrně komplikované, popsané v Programování sítí Unix svazek I, 3. vydání, #22.2, a v man stránku.
Při vaší úpravě stojíte proti tomu, co je známé jako „model slabého konce systému“ TCP/IP. V zásadě, jakmile paket dorazí, systém se může rozhodnout jej doručit přes jakékoli vhodné rozhraní naslouchající správnému portu. Někde je to popsáno v TCP/IP RFC.
Do setsockopt
předáváte neplatnou hodnotu .
rc=setsockopt(s, SOL_SOCKET, SO_BINDTODEVICE, nic, strlen(nic));
Manová stránka říká SO_BIND_TO_DEVICE
:
Předaná možnost je ukončená s proměnnou délkou řetězec názvu rozhraní s maximální velikostí IFNAMSIZ
strlen
nezahrnuje ukončovací null. Můžete zkusit:
rc=setsockopt(s, SOL_SOCKET, SO_BINDTODEVICE, nic, 1 + strlen(nic));
dnsmasq
funguje správně a používá
setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, intname, IF_NAMESIZE)