2012-10-03 40 views
3

我是一位经验丰富的Linux套接字编程人员,并且正在编写一个具有多个传出接口的服务器应用程序。现在服务器套接字与INADDR_ANY一起绑定到进程开始时的随机源端口。如何在Linux中重新绑定udp套接字

稍后在提交响应到特定节点时,我需要分配一个固定的源IP地址。执行此操作的标准方法是调用绑定。但是,端口号会调用一次绑定,连续调用失败,参数错误无效。

创建一个新的socket是不是真的一个很好的选择,因为我将不得不在应对一些客户经常这样做。

我也SO探索和大量的socket选项,如IP_FREEBIND,但它并不完全适合我的方案。

也许使用IP_PKT_INFO和设置源地址可能会奏效,除非它遭受了同样的问题,即不允许一旦与INADDRANY重新绑定到一个固定的源IP后者的插座。

有一种方法解除绑定的现有插座或替代的方式在输出分组设定源IP地址?

int sock = socket(AF_INET, SOCK_DGRAM, 0); 

    if(sock < 0) 
     printf("Failed creating socket\n"); 

    struct sockaddr_in addr; 
    memset(&addr, 0, sizeof(struct sockaddr_in)); 
    addr.sin_family = AF_INET; 
    addr.sin_port = htons(1500); 
    addr.sin_addr.s_addr = INADDR_ANY; 

    // first bind succeeds 
    if ((status = bind(sock, (struct sockaddr *) &addr, sizeof(addr))) < 0) 
     printf("bind error with port %s\n", strerror(errno)); 

    struct sockaddr_in src_addr; 
    memset(&src_addr, 0, sizeof(struct sockaddr_in)); 
    src_addr.sin_family = AF_INET; 
    if (inet_aton("10.0.2.17", &(src_addr.sin_addr)) == 0) 
     printf("Failed copying address\n"); 

    // second bind fails 
    if((status = bind(sock, (struct sockaddr *)&src_addr, sizeof(src_addr))) < 0) 
     printf("re bind error with ip %s\n", strerror(errno)); 

在这方面的任何想法将不胜感激。我在插座上经历了相当多的材料,等等,但还没有成功。

+0

它不绑定到recvfrom后接收到数据包的接口? – CrazyCasta

+0

如果同一局域网上有多个接口,则为每个NIC创建一个套接字池。更多的细节表示赞赏。 –

+0

谢谢!单独的套接字被用于发送和接收绑定到接口,因为recvfrom没有影响。我实际上也有一个套接字池。但是他们中的任何一个都可能被要求选择一个外部提供的源IP。这是应用程序要求。 – fayyazkl

回答

5

我终于找到了自己的解决方案,以便接受我自己的答案,辅以代码示例。

我本来想传出包的重写源地址,而无需再次创建套接字在套接字已经绑定。多次调用绑定在这种情况下失败,并且(在我的特殊情况下),我无法为每个源ip分配独立的套接字并使用它。

我在IP_PACKET_INFO中发现了一些参考文献,但让它正常工作是一件很痛苦的事情。以下参考是有帮助的。

Setting source of udp socket

示例代码

下面是一个简单的应用程序,它创建了一个UDP套接字,其结合到本地端口,则发送一个特定的消息之前,附加传出源IP地址。请记住,在我的情况下,我创建了一个sudo界面并为其分配了另一个ip。如果不是这种情况,发送呼叫将失败。

int status=-1; 
int sock = socket(AF_INET, SOCK_DGRAM, 0); 

if(sock < 0) 
    printf("Failed creating socket\n"); 

int opt = 1; 
setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); 

struct sockaddr_in bind_addr; 
memset(&bind_addr, 0, sizeof(struct sockaddr_in)); 
bind_addr.sin_family = AF_INET; 
bind_addr.sin_port = htons(44000); // locally bound port 

if((status = bind(sock, (struct sockaddr *)&bind_addr, sizeof(bind_addr))) < 0) 
    printf("bind error with port %s\n", strerror(errno)); 

// currently using addr as destination 
struct sockaddr_in addr; 
memset(&addr, 0, sizeof(struct sockaddr_in)); 
addr.sin_family = AF_INET; 
addr.sin_port = htons(80); // destination port 
if (inet_aton("74.125.236.35", &(addr.sin_addr)) == 0) 
    printf("Failed copying remote address\n"); 
else 
    printf("Success copying remote address\n"); 

struct sockaddr_in src_addr; 
memset(&src_addr, 0, sizeof(struct sockaddr_in)); 
src_addr.sin_family = AF_INET; 
if (inet_aton("10.0.2.17", &(src_addr.sin_addr)) == 0) 
    printf("Failed copying src address\n"); 
else 
    printf("Success copying src address\n"); 

char cmbuf[CMSG_SPACE(sizeof(struct in_pktinfo))]; 

char msg[10] = "hello"; 
int len = strlen(msg); 

struct msghdr mh; 
memset(&mh, 0, sizeof(mh)); 

struct cmsghdr *cmsg; 
struct in_pktinfo *pktinfo; 

struct iovec iov[1]; 
iov[0].iov_base = msg; 
iov[0].iov_len = len; 

mh.msg_name = &addr; // destination address of packet 
mh.msg_namelen = sizeof(addr); 
mh.msg_control = cmbuf; 
mh.msg_controllen = sizeof(cmbuf); 
mh.msg_flags = 0; 
mh.msg_iov = iov; 
mh.msg_iovlen = 1; 

// after initializing msghdr & control data to 
// CMSG_SPACE(sizeof(struct in_pktinfo)) 
cmsg = CMSG_FIRSTHDR(&mh); 
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); 

//src_interface_index 0 allows choosing interface of the source ip specified 
pktinfo->ipi_ifindex = 0; 
pktinfo->ipi_spec_dst = src_addr.sin_addr; 

int rc = sendmsg(sock, &mh, 0); 
printf("Result %d\n", rc); 

的关键语句是

pktinfo->ipi_spec_dst = src_addr.sin_addr; 

其中我们指定的源IP地址使用。其他的东西,如cmsg结构等仅仅用于为了能够自己写ipoktinfo结构

+1

找到了!尽管'man 7 ip'手册页描述了这个选项,我还没有意识到这是可能的。你并不真的需要超级用户权限:在'CAP_NET_ADMIN'能力不够,即'须藤setcap CAP_NET_ADMIN = PE binary'就足够了,没有必要设置它的setuid/setgid的根。然而,是否有一个原因,你不只是使用一个新的套接字发送响应,绑定到所需的地址?使用UDP,您不需要使用与接收请求相同的套接字发送响应,因为没有连接,只是数据包。或者响应的源端口是否对客户有影响? –

+0

谢谢,其中一个源端口很重要。其次,在我的场景中,绑定到该地址的套接字可能服务于另一个直接请求,而绑定到其中一个接口eth2。这个特定的请求需要出去一些其他的接口。给予好评? :) – fayyazkl

+0

由于没有连接,只有数据报,您可以使用'sendto()'从绑定的套接字发送对任何地址和端口的响应。您可以使用单个套接字将响应发送到不同的客户端,甚至可以同时发送。响应不需要使用收到请求的相同套接字。我仍然不明白你为什么没有固定的套接字绑定到所需的源地址和端口。如果我写了一个简单的示例服务器/客户端作为新的答案,也许会更容易看出我的意思? –

4

无法解除绑定并重新绑定现有套接字。

+0

谢谢。我将在一天之内回到这一点。这是有益的,而不是我仍然认为可能有一种方式。 – fayyazkl

+0

顺便说一下,我仍然可以反复使用SOBINDTODEVICE,并将其有效地绑定到任何传出接口,这不是很有讽刺意味吗?我实际上是在我的应用程序中成功完成它,但不能再次调用bind()。 – fayyazkl

+1

看看我使用IP_PACKETINFO和sendmsg找到的解决方案。这不是完全重新绑定呼叫,而是做这项工作,即我可以在每个数据包上重写我的传出IP地址。 – fayyazkl

1

为什么不为每个接口创建一个套接字呢?由于UDP/IP协议是无连接的,因此您可以通过选择用于发送回复的套接字来选择源IP地址;没有必要使用接收到的数据报的相同套接字。

缺点是您不能再绑定到通配符地址,并且您必须使用select()poll(),多个线程或其他一些机制来同时接收来自多个数据源的数据报。您还需要一些逻辑来根据客户端IP地址有效地选择套接字。

在大多数情况下,我怀疑是添加一些路由条目为路由每个远程IP地址到所需的主机的IP地址,并使用每个主机的IP地址和端口组合的独立插槽,解决了问题完全 - 和使用非常高效的内核功能来做到这一点。虽然行为可能是应用程序要求,但我怀疑使用网络接口配置可以更好地解决此问题。不幸的是,通常这些要求是由半功能白痴编写的,这些白痴更适合手工劳动,而你的手却被束缚住了。如果是这样的话,我会表示相信。

如果你有一个测试网络具有多个物理网络接口的工作站,我可以为您提供可用于验证的设计作品一个简单的例子C99测试程序。

+0

非常感谢您的意见。最初,我继续每个接口策略套接字。但是,有协议/应用程序的具体要求阻止了这一点。为了给出一个想法,因为我只能在这里详细说明一些常规限制,为了鲁棒性,进一步必须查询目标列表的服务器必须并行执行,即至少3个来自服务器的请求被顺序地发送(无需等待任何一个人的回应)。现在,每一项都需要单独的插座,其中有(其中的还有另外一个监听套接字传入RESP。 – fayyazkl

+0

每个单独的插座,可能需要绑定到任何给定的接口,这不断变化的。先说3个请求现在有一个排序列表,我将选择接下来的3个目的地,每个目标可以绑定到不同的接口上,实际上这个服务器运行在一个有10-12个接口的机器上,的要求,我分配3个插座这活虽然他们可能要绑定到任何接口中的任意特定请求的生命。 – fayyazkl

+0

要雪上加霜的是,它是工作,在生产运行实际直到这个看似简单的要求来即除了所有3个同时查询和对接口的动态绑定之外,如果配置指定了目的ip:src ip list,那么您当前的dest匹配o ne列表项,然后使用src ip作为传出ip。因此,我可以在一个经过良好开发和生产测试的应用程序中进行更改。需求人士认为这与在发现数据包之前调用另一个绑定一样简单。非常简单的要求。 – fayyazkl