Solusi yang saya temukan berhasil adalah sebagai berikut. Pertama-tama, kita harus mengubah pengaturan ARP dan RP. Ke /etc/sysctl.conf, tambahkan yang berikut dan reboot (ada juga perintah untuk mengatur ini secara dinamis):
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
Filter arp diperlukan untuk mengizinkan respons dari eth0 ke rute melalui WAN. Opsi filter rp diperlukan untuk secara ketat mengaitkan paket yang masuk dengan NIC tempat mereka masuk (berlawanan dengan model lemah yang mengaitkannya dengan NIC apa pun yang cocok dengan subnet). Komentar dari EJP mengarahkan saya ke langkah kritis ini.
Setelah itu, SO_BINDTODEVICE mulai bekerja. Masing-masing dari dua soket terikat ke NIC-nya sendiri, dan oleh karena itu saya dapat mengetahui dari NIC mana pesan berasal berdasarkan soket asalnya.
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))
Selanjutnya, saya ingin menanggapi datagram yang masuk dengan datagram yang alamat sumbernya adalah dari NIC asal permintaan asli. Jawabannya adalah dengan mencari alamat NIC itu dan mengikat soket keluar ke alamat itu (menggunakan 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;
}
(Mungkin mencari alamat NIC setiap saat sepertinya sia-sia, tetapi itu jauh lebih banyak kode untuk mendapatkan informasi ketika alamat berubah, dan transaksi ini terjadi hanya sekali setiap beberapa detik pada sistem yang tidak menggunakan baterai.)
Anda bisa mendapatkan alamat tujuan yang digunakan oleh pengirim melalui IP_RECVDSTADDR
opsi jika platform Anda mendukungnya, dengan menggunakan recvmsg()
. Ini agak rumit, dijelaskan di Pemrograman Jaringan Unix, jilid I, edisi ke-3, #22.2, dan di man halaman.
Jika Anda mengedit, Anda menghadapi apa yang dikenal sebagai 'model sistem ujung lemah' dari TCP/IP. Pada dasarnya setelah sebuah paket tiba, sistem dapat memilih untuk mengirimkannya melalui antarmuka yang sesuai dengan mendengarkan port yang benar. Ini dibahas di RFC TCP/IP di suatu tempat.
Anda memberikan nilai ilegal ke setsockopt
.
rc=setsockopt(s, SOL_SOCKET, SO_BINDTODEVICE, nic, strlen(nic));
Halaman manual mengatakan tentang SO_BIND_TO_DEVICE
:
Opsi yang diteruskan adalah panjang variabel dihentikan dengan nol string nama antarmuka dengan ukuran maksimum IFNAMSIZ
strlen
tidak termasuk null terminasi. Anda dapat mencoba:
rc=setsockopt(s, SOL_SOCKET, SO_BINDTODEVICE, nic, 1 + strlen(nic));
dnsmasq
apakah ini berfungsi dengan benar, dan menggunakan
setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, intname, IF_NAMESIZE)