私がうまくいった解決策は次のとおりです。まず、ARP と RP の設定を変更する必要があります。 /etc/sysctl.conf に以下を追加して再起動します (これを動的に設定するコマンドもあります):
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
arp フィルターは、eth0 からの応答を WAN 経由でルーティングできるようにするために必要でした。 rp フィルター オプションは、受信パケットを受信した NIC に厳密に関連付けるために必要でした (サブネットに一致する任意の NIC にパケットを関連付ける弱いモデルとは対照的です)。 EJP からのコメントにより、この重要なステップに進みました。
その後、SO_BINDTODEVICE が動作するようになりました。 2 つのソケットのそれぞれが独自の NIC にバインドされているため、メッセージの送信元のソケットに基づいて、メッセージがどの NIC から送信されたかを知ることができました。
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))
次に、送信元アドレスが元の要求の送信元である NIC のアドレスであるデータグラムを使用して、着信データグラムに応答したいと考えました。その答えは、その NIC のアドレスを調べて、発信ソケットをそのアドレスにバインドすることです (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;
}
(毎回 NIC のアドレスを調べるのは無駄に思えるかもしれませんが、アドレスが変更されたときに通知を受けるには、コードがかなり多くなります。これらのトランザクションは、バッテリで動作しないシステムでは数秒に 1 回しか発生しません。)
IP_RECVDSTADDR
を介して、送信者が使用する宛先アドレスを取得できます。 recvmsg()
を使用して、プラットフォームがサポートしている場合はオプション . Unix Network Programming で説明されているように、かなり複雑です。 第 1 巻、第 3 版、#22.2、および 男 ページ。
あなたの編集によれば、あなたは TCP/IP の「ウィーク エンド システム モデル」として知られているものに反対しています。基本的に、パケットが到着すると、システムは正しいポートをリッスンする適切なインターフェイスを介して配信することを選択できます。どこかの TCP/IP RFC で議論されています。
setsockopt
に不正な値を渡しています .
rc=setsockopt(s, SOL_SOCKET, SO_BINDTODEVICE, nic, strlen(nic));
マニュアルページには SO_BIND_TO_DEVICE
と書かれています :
渡されたオプションは可変長のヌル終端です 最大サイズ IFNAMSIZ のインターフェイス名文字列
strlen
終端の null は含まれません。試すことができます:
rc=setsockopt(s, SOL_SOCKET, SO_BINDTODEVICE, nic, 1 + strlen(nic));
dnsmasq
これは正しく機能しており、使用しています
setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, intname, IF_NAMESIZE)