2010-06-17 80 views
22

我有一個綁定到INADDR_ANY的UDP套接字來監聽我服務器上所有IP地址的數據包。我通過相同的套接字發送回覆。設置UDP套接字的源IP地址

現在服務器自動選擇使用哪個IP作爲發送數據包時的源IP,但我希望能夠自己設置傳出源IP。

有沒有辦法做到這一點,而不必爲每個IP創建一個單獨的套接字?

回答

22

尼古拉對每個地址使用單獨的套接字並綁定(2)或者與路由表混淆通常不是可行的選擇,例如,與動態地址。一個單獨的IP_ADDRANY -bound UDP服務器應該能夠看起來在接收到數據包的同一個動態分配的IP地址上進行響應。

幸運的是,還有另一種方法。根據系統的支持,您可以使用IP_PKTINFO套接字選項來設置或接收有關消息的輔助數據。儘管comp.os.linux.development.system具有特定於IP_PKTINFO的完整代碼示例,但在線許多地方都覆蓋了輔助數據(通過cmsg(3))。

在鏈路中的代碼使用IP_PKTINFO(或IP_RECVDSTADDR取決於平臺),以從輔助cmsg(3)數據得到一個UDP消息的目的地地址。在這裏解釋:

struct msghdr msg; 
struct cmsghdr *cmsg; 
struct in_addr addr; 
// after recvmsg(sd, &msg, flags); 
for(cmsg = CMSG_FIRSTHDR(&msg); 
    cmsg != NULL; 
    cmsg = CMSG_NXTHDR(&msg, cmsg)) { 
    if (cmsg->cmsg_level == IPPROTO_IP && cmsg->cmsg_type == IP_PKTINFO) { 
    addr = ((struct in_pktinfo*)CMSG_DATA(cmsg))->ipi_addr; 
    printf("message received on address %s\n", inet_ntoa(addr)); 
    } 
} 

基因,你的問題問如何設置輸出數據包的源地址。使用IP_PKTINFO可以將struct in_pktinfoipi_spec_dst字段設置爲傳遞給sendmsg(2)的輔助數據。請參閱上面提及的帖子,cmsg(3)sendmsg(2),以獲取有關如何創建和操作struct msghdr中的輔助數據的指導原則。一個例子(這裏沒有保證)可能是:

struct msghdr msg; 
struct cmsghdr *cmsg; 
struct in_pktinfo *pktinfo; 
// after initializing msghdr & control data to CMSG_SPACE(sizeof(struct in_pktinfo)) 
cmsg = CMSG_FIRSTHDR(&msg); 
cmsg->cmsg_level = IPPROTO_IP; 
cmsg->cmsg_type = IP_PKTINFO; 
cmsg->cmsg_len = CMSG_LEN(sizeof(struct in_pktinfo)); 
pktinfo = (struct in_pktinfo*) CMSG_DATA(cmsg); 
pktinfo->ipi_ifindex = src_interface_index; 
pktinfo->ipi_spec_dst = src_addr; 
// bytes_sent = sendmsg(sd, &msg, flags); 

注意這是IPv6的不同:在recvmsg和sendmsg情況下都使用struct in6_pktinfo::ipi6_addr

還要注意,Windows不支持在in_pktinfo結構中與ipi_spec_dst等效,因此您不能使用此方法在傳出winsock2數據包上設置源地址。

(參考手冊頁 - 讓周圍1超級鏈接極限)

http:// linux.die.net/man/2/sendmsg 
http:// linux.die.net/man/3/cmsg 
3

您可以爲每個接口地址設置bind(2)並管理多個套接字,也可以讓內核執行隱含的源IP分配,其中包括INADDR_ANY。沒有其他辦法。

我的問題是 - 你爲什麼需要這個?普通的IP路由不適合你嗎?

+0

謝謝,IP路由工作罰款和數​​據包到達他們的目的地,但不幸的是,客戶都連接到他們的特定服務器IP,協議要求他們從這個特定的IP得到答案。現在所有客戶都從同一個IP獲得答案。 – 2010-06-17 16:02:57

+0

我的懷疑就是路由表 - 你有單一的默認路由/網關嗎?添加特定於客戶端地址的路線可能會有幫助 – 2010-06-17 16:18:53

+0

是的,添加主機路線會有所幫助,但我寧願在我的程序中執行此操作。 – 2010-06-17 17:40:44

17

我想我會擴大傑里米對如何做到這一點的IPv6。 Jeremy留下了很多細節,並且一些文檔(如Linux的ipv6手冊頁)顯然是錯誤的。首先在一些發行,你必須定義_GNU_SOURCE,否則一些IPv6的東西沒有定義:

#define _GNU_SOURCE 
#include <netinet/in.h> 
#include <sys/types.h> 
#include <sys/socket.h> 

下一頁設立插座監聽所有的IP包(即一個相當標準的方式,同時支持IPv4和IPv6):

const int on=1, off=0; 
int result; 
struct sockaddr_in6 sin6; 
int soc; 

soc = socket(AF_INET6, SOCK_DGRAM, 0); 
setsockopt(soc, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)); 
setsockopt(soc, IPPROTO_IP, IP_PKTINFO, &on, sizeof(on)); 
setsockopt(soc, IPPROTO_IPV6, IPV6_RECVPKTINFO, &on, sizeof(on)); 
setsockopt(soc, IPPROTO_IPV6, IPV6_V6ONLY, &off, sizeof(off)); 
memset(&sin6, '\0', sizeof(sin6)); 
sin6.sin6_family = htons(AF_INET6); 
sin6.sin6_port = htons(MY_UDP_PORT); 
result = bind(soc, (struct sockaddr*)&sin6, sizeof(sin6)); 

注意上面的代碼爲IPv6套接字設置了IP和IPv6選項。如果數據包到達IPv4地址,您將獲得IP_PKTINFO(即IPv4)cmsg,即使它是IPv6套接字,並且如果您不啓用它們,它們將不會被髮送。另請注意,設置了IPV6_RECPKTINFO選項(man 7 ipv6中沒有提及),而不是IPV6_PKTINFO(在man 7 ipv6中錯誤地描述了該選項)。現在收到UDP包:

int bytes_received; 
struct sockaddr_in6 from; 
struct iovec iovec[1]; 
struct msghdr msg; 
char msg_control[1024]; 
char udp_packet[1500]; 

iovec[0].iov_base = udp_packet; 
iovec[0].iov_len = sizeof(udp_packet); 
msg.msg_name = &from; 
msg.msg_namelen = sizeof(from); 
msg.msg_iov = iovec; 
msg.msg_iovlen = sizeof(iovec)/sizeof(*iovec); 
msg.msg_control = msg_control; 
msg.msg_controllen = sizeof(msg_control); 
msg.msg_flags = 0; 
bytes_received = recvmsg(soc, &msg, 0); 

下一步是提取接口和地址收到的UDP數據包出CMSG的:

struct in_pktinfo in_pktinfo; 
struct in6_pktinfo in6_pktinfo; 
int have_in_pktinfo = 0; 
int have_in6_pktinfo = 0; 
struct cmsghdr* cmsg; 

for (cmsg = CMSG_FIRSTHDR(&msg); cmsg != 0; cmsg = CMSG_NXTHDR(&msg, cmsg)) 
{ 
    if (cmsg->cmsg_level == IPPROTO_IP && cmsg->cmsg_type == IP_PKTINFO) 
    { 
    in_pktinfo = *(struct in_pktinfo*)CMSG_DATA(cmsg); 
    have_in_pktinfo = 1; 
    } 
    if (cmsg->cmsg_level == IPPROTO_IPV6 && cmsg->cmsg_type == IPV6_PKTINFO) 
    { 
    in6_pktinfo = *(struct in6_pktinfo*)CMSG_DATA(cmsg); 
    have_in6_pktinfo = 1; 
    } 
} 

最後,我們得到的響應發回,使用相同的目的地。

int cmsg_space; 

iovec[0].iov_base = udp_response; 
iovec[0].iov_len = udp_response_length; 
msg.msg_name = &from; 
msg.msg_namelen = sizeof(from); 
msg.msg_iov = iovec; 
msg.msg_iovlen = sizeof(iovec)/sizeof(*iovec); 
msg.msg_control = msg_control; 
msg.msg_controllen = sizeof(msg_control); 
msg.msg_flags = 0; 
cmsg_space = 0; 
cmsg = CMSG_FIRSTHDR(&msg); 
if (have_in6_pktinfo) 
{ 
    cmsg->cmsg_level = IPPROTO_IPV6; 
    cmsg->cmsg_type = IPV6_PKTINFO; 
    cmsg->cmsg_len = CMSG_LEN(sizeof(in6_pktinfo)); 
    *(struct in6_pktinfo*)CMSG_DATA(cmsg) = in6_pktinfo; 
    cmsg_space += CMSG_SPACE(sizeof(in6_pktinfo)); 
} 
if (have_in_pktinfo) 
{ 
    cmsg->cmsg_level = IPPROTO_IP; 
    cmsg->cmsg_type = IP_PKTINFO; 
    cmsg->cmsg_len = CMSG_LEN(sizeof(in_pktinfo)); 
    *(struct in_pktinfo*)CMSG_DATA(cmsg) = in_pktinfo; 
    cmsg_space += CMSG_SPACE(sizeof(in_pktinfo)); 
} 
msg.msg_controllen = cmsg_space; 
ret = sendmsg(soc, &msg, 0); 

再注意一下,如果數據包通過IPv4的來到我們必須把IPv4的選項進入CMSG的,即使它是一個AF_INET6插座。至少,這是你必須爲Linux做的事情。

這是一項令人驚訝的工作量,但AFAICT是製作健全的UDP服務器所需的最低限度,它可在所有可能的Linux環境中運行。它大部分不是TCP所必需的,因爲它透明地處理多重歸屬。

0

最近我遇到了同樣的問題。

我做些什麼來解決這個問題是

  1. 從接收的數據包獲取接口名稱
  2. 綁定套接字到特定的接口
  3. 解除綁定插座

例子:

struct ifreq ifr; 
    ... 
    recvmsg(fd, &msg...) 
    ...  
    if (msg.msg_controllen >= sizeof(struct cmsghdr)) 
    for (cmptr = CMSG_FIRSTHDR(&msg); cmptr; cmptr = CMSG_NXTHDR(&msg, cmptr)) 
     if (cmptr->cmsg_level == SOL_IP && cmptr->cmsg_type == IP_PKTINFO) 
     { 
     iface_index = ((struct in_pktinfo *)CMSG_DATA(cmptr))->ipi_ifindex; 
     } 
    if_indextoname(iface_index , ifr.ifr_name); 
    mret=setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, &ifr, sizeof(ifr)); 

    sendmsg(...); 

    memset(&ifr, 0, sizeof(ifr)); 
    snprintf(ifr.ifr_name, sizeof(ifr.ifr_name), ""); 
    mret=setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, &ifr, sizeof(ifr)); 
+0

我從IBM知識中心找到了關於源IP選擇的鏈接: [https://www.ibm.com/support/knowledgecenter/SSLTBW_2.1.0/com.ibm.zos.v2r1.halz002/tcpip_source_ip_addr_selection.htm]( https://www.ibm.com/support/knowledgecenter/SSLTBW_2.1.0/com.ibm.zos.v2r1.halz002/tcpip_source_ip_addr_selection.htm) – 2016-08-25 10:03:30