簡體   English   中英

Linux:將 UDP 偵聽套接字綁定到特定接口(或找出數據報來自的接口)?

[英]Linux: Bind UDP listening socket to specific interface (or find out the interface a datagram came in from)?

我有一個正在處理的守護進程,它偵聽 UDP 廣播數據包並通過 UDP 進行響應。 當一個包進來,我想知道哪個IP地址(或NIC)的數據包,這樣我可以用IP地址作為源地址進行響應。 (出於很多痛苦的原因,我們系統的一些用戶希望將同一台機器上的兩個 NIC 連接到同一個子網。我們告訴他們不要這樣做,但他們堅持。我不需要提醒我這是多么丑陋.)

似乎沒有辦法檢查數據報並直接找出它的目標地址或它進入的接口。 基於大量的谷歌搜索,我發現找出數據報目標的唯一方法是每個接口有一個偵聽套接字並將套接字綁定到它們各自的接口。

首先,我的監聽套接字是這樣創建的:

s=socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)

為了綁定套接字,我嘗試的第一件事是這樣,其中nic是接口名稱的char*

// Bind to a single interface
rc=setsockopt(s, SOL_SOCKET, SO_BINDTODEVICE, nic, strlen(nic));
if (rc != 0) { ... }

這已經完全沒有效果,靜靜地失敗。 ASCII 名稱(例如eth0 )是傳遞給此調用的正確名稱類型嗎? 為什么它會默默地失敗? 根據man 7 socket ,“請注意,這僅適用於某些套接字類型,尤其是 AF_INET 套接字。數據包套接字不支持它(在那里使用普通的 bind(8))。” 我不確定“數據包套接字”是什么意思,但這是一個 AF_INET 套接字。

所以我接下來嘗試的是這個(基於bind 與 SO_BINDTODEVICE socket ):

struct sockaddr_ll sock_address;
memset(&sock_address, 0, sizeof(sock_address));
sock_address.sll_family = PF_PACKET;
sock_address.sll_protocol = htons(ETH_P_ALL);
sock_address.sll_ifindex = if_nametoindex(nic);
rc=bind(s, (struct sockaddr*) &sock_address, sizeof(sock_address));
if (rc < 0) { ... }

這也失敗了,但這次出現錯誤Cannot assign requested address 我也嘗試將系列更改為 AF_INET,但失敗並出現相同的錯誤。

剩下的一種選擇是將套接字綁定到特定的 IP 地址。 我可以查找接口地址並綁定到這些地址。 不幸的是,這是一個糟糕的選擇,因為由於 DHCP 和熱插拔以太網電纜,地址可能會即時更改。

當涉及到廣播和多播時,這個選項也可能很糟糕。 我擔心綁定到特定地址將意味着我無法接收廣播(廣播地址不是我綁定的地址)。 我實際上將在今晚晚些時候對此進行測試並更新此問題。

問題:

  • 是否可以將 UDP 偵聽套接字專門綁定到接口?
  • 或者,是否有一種機制可以在發生更改時(而不是輪詢)通知我的程序接口地址已更改?
  • 是否有另一種我可以創建的偵聽套接字(我確實有 root 權限),我可以綁定到特定的接口,該接口的行為與 UDP 完全相同(即除了原始套接字,我基本上必須自己實現 UDP)? 例如,我可以將AF_PACKETSOCK_DGRAM AF_PACKET使用嗎? 我不明白所有的選項。

誰能幫我解決這個問題? 謝謝!

更新:

綁定到特定 IP 地址無法正常工作。 具體來說,我無法接收廣播數據包,這正是我想要接收的。

更新:

我嘗試使用IP_PKTINFOrecvmsg來獲取有關正在接收的數據包的更多信息。 我可以得到接收接口,接收接口地址,發送方的目標地址,發送方的地址。 這是我收到一個廣播數據包時得到的報告示例:

Got message from eth0
Peer address 192.168.115.11
Received from interface eth0
Receiving interface address 10.1.2.47
Desination address 10.1.2.47

真正奇怪的是,eth0 的地址是 10.1.2.9,而 ech1 的地址是 10.1.2.47。 那么究竟為什么 eth0 會接收應該由 eth1 接收的數據包? 這絕對是個問題。

請注意,我啟用了 net.ipv4.conf.all.arp_filter,盡管我認為這僅適用於出站數據包。

我發現有效的解決方案如下。 首先,我們要更改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 開始工作。 兩個套接字中的每一個都綁定到自己的 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到該地址(使用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 的地址似乎是一種浪費,但是當地址更改時需要更多的代碼來獲得通知,並且在不依靠電池運行的系統上,這些事務每隔幾秒鍾才會發生一次。)

如果您的平台支持,您可以使用recvmsg()通過IP_RECVDSTADDR選項獲取發件人使用的目標地址。 它相當復雜,在Unix Network Programming, volume I, 3rd edition, #22.2 和手冊頁中有所描述

重新編輯,您遇到了所謂的 TCP/IP 的“弱端系統模型”。 基本上,一旦數據包到達,系統就可以選擇通過任何適當的接口偵聽正確的端口來傳送它。 它在 TCP/IP RFC 中的某處進行了討論。

您將非法值傳遞給setsockopt

rc=setsockopt(s, SOL_SOCKET, SO_BINDTODEVICE, nic, strlen(nic));

手冊頁說SO_BIND_TO_DEVICE

傳遞的選項是一個可變長度的空終止接口名稱字符串,最大大小為 IFNAMIZ

strlen不包括終止空值。 你可以試試:

rc=setsockopt(s, SOL_SOCKET, SO_BINDTODEVICE, nic, 1 + strlen(nic));

dnsmasq可以正常工作,並使用

setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, intname, IF_NAMESIZE)

我相信您可能從錯誤的角度處理問題。 在一般情況下,接口可以同時擁有多個 IP 地址,因此知道您連接到哪個接口不會為您提供 IP 地址(在一般情況下)

相反,不要過分擔心正在使用的接口,而應關注正在使用的 IP 地址。 首先使用 getifaddrs() 獲取所有 IP 地址的列表,並將一個套接字綁定到每個地址。

select() 可用於一次等待所有套接字上的數據包。 使用接收數據包的套接字來確定數據包的目的地址。 此外,接收數據包的套接字可用於發送回復,該回復將自動適當地設置源地址。

您可能偶爾需要檢查新 IP 地址,但如果 DHCP 為您提供新地址,則會在套接字上出現錯誤。

我知道這是一個舊線程,但我沒有找到我在這里尋找的答案。

通過使用我在此處找到的信息,將原始套接字綁定到接口,以便套接字看不到來自另一個接口(包括廣播、IGMP 等)的任何數據包對我有用:

https://cs.wikipedia.org/wiki/Raw_socket

BindRawSocketToInterface() 函數的功能正是我所需要的。

希望這對其他人有幫助。 干杯!

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM