简体   繁体   English

如何在Linux中重新绑定udp套接字

[英]How to re bind a udp socket in Linux

I am an experienced Linux socket programmer and am writing a server application which has many outgoing interfaces. 我是一位经验丰富的Linux套接字程序员,正在编写具有许多传出接口的服务器应用程序。 Now server socket binds to a random source port in the start of the process along with INADDR_ANY. 现在,在此过程开始时,服务器套接字将与INADDR_ANY绑定到随机源端口。

Later at some point when submitting response to a specific node, i need to assign a fixed source ip address . 稍后在向特定节点提交响应时,我需要分配一个固定的源ip地址 The standard way to do this is calling bind. 执行此操作的标准方法是调用bind。 However, bind is called once for the port number, successive calls fail with invalid argument error. 但是,bind会为端口号调用一次, 后续调用会失败,并出现无效的参数错误。

Creating a new socket is not really a good choice since i will have to be doing this very often upon responding to some clients. 创建一个新的套接字并不是一个很好的选择,因为在响应某些客户端时,我将不得不经常这样做。

I have also explored SO and a lot of socket options such as IP_FREEBIND, but it doesn't quite suite my scenario. 我还研究了SO和许多套接字选项,例如IP_FREEBIND,但它并不完全适合我的情况。

Perhaps using IP_PKT_INFO and setting source address might work unless it suffers the same problem ie not allowing a socket once bound to INADDRANY to rebind to a fixed source ip latter. 除非遇到相同的问题,即使用IP_PKT_INFO并设置源地址,否则可能会起作用,例如,不允许绑定到INADDRANY的套接字重新绑定到固定源ip。

Is there a way to unbind an existing socket or an alternate way to setting source ip address in outgoing packet? 有没有一种方法可以解除现有套接字的绑定,或者可以通过另一种方法来设置传出数据包中的源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));

Any ideas in this regard will be highly appreciated. 在这方面的任何想法将受到高度赞赏。 I have gone through considerable material on sockets, SO etc. but no success yet. 我已经阅读了有关套接字,SO等的大量材料,但还没有成功。

I finally found the solution myself so accepting my own answer (shameless but correct plugin), supplemented with code sample. 我终于找到了解决方案,因此接受了自己的答案(无耻但正确的插件),并补充了代码示例。

I originally wanted to rewrite source address of an outgoing packet without creating the socket again where the socket was already bound. 我最初想重写传出数据包的源地址,而不必在已绑定套接字的位置再次创建套接字 Calling bind multiple times fail for this case, and (in my particular situation), i was not able to just have separate sockets for each source ip and use it. 在这种情况下, 多次调用bind失败,并且(在我的特定情况下),我不能为每个源ip单独设置套接字并使用它。

I found some references in IP_PACKET_INFO but it was a pain to get it to work correctly. 我在IP_PACKET_INFO中找到了一些引用,但是要使其正常工作是很痛苦的。 Following reference was helpful. 以下参考资料很有帮助。

Setting source of udp socket 设置udp插座的来源

Sample Code 样例代码

Here is a trivial application which creates a udp socket, binds it to a local port, then before sending a particular message, it appends the outgoing source ip address . 这是一个简单的应用程序,它创建了一个udp套接字,将其绑定到本地端口,然后在发送特定消息之前, 它附加了传出的源ip地址 Keeping in mind that in my case, i created a sudo interface and assigned it another ip. 请记住,就我而言,我创建了一个sudo接口并为其分配了另一个IP。 The send call will fail if this is not the case. 如果不是这种情况,发送呼叫将失败。

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);

The key statement is 关键声明是

pktinfo->ipi_spec_dst = src_addr.sin_addr;

where we are specifying the source ip address to be used . 我们在其中指定要使用的源IP地址 The rest of things like cmsg struct etc. are merely used in order to be able to write ipoktinfo struct ourselves 诸如cmsg struct等的其他内容仅用于能够自己编写ipoktinfo struct

无法取消绑定并重新绑定现有的套接字。

Why don't you create a socket for each interface instead? 为什么不为每个接口创建一个套接字呢? Since the UDP/IP protocol is connectionless, you can choose the source IP address by choosing which socket you use to send the reply with; 由于UDP / IP协议是无连接的,因此您可以通过选择用于发送答复的套接字来选择源IP地址。 there is no need to use the same socket the incoming datagram was received on. 无需使用接收传入数据报的套接字。

The downsides are that you can no longer bind to the wildcard address, and you must use select() , poll() , multiple threads, or some other mechanism to receive datagrams from multiple sources concurrently. 缺点是您无法再绑定到通配符地址,并且必须使用select()poll() ,多个线程或其他某种机制来同时从多个源接收数据报。 You'll also need some logic to efficiently pick the socket based on the client IP address. 您还需要一些逻辑以根据客户端IP地址有效地选择套接字。

In most cases, I suspect that adding a few route entries to route each remote IP address to the desired host IP address, and using a separate socket for each host IP address and port combination, solves the issues perfectly -- and using the very efficient kernel functionality to do so. 在大多数情况下,我怀疑添加一些路由条目以将每个远程IP地址路由到所需的主机IP地址,并对每个主机IP地址和端口组合使用单独的套接字可以完美地解决问题-并且使用非常高效内核功能。 While the behaviour may be an application requirement, I suspect it is better solved using the network interface configuration instead. 尽管此行为可能是应用程序的要求,但我怀疑可以改用网络接口配置来解决。 Unfortunately, often the requirements are written by semi-functional idiots better suited for manual labor, and your hands are tied.. if so, I commiserate. 不幸的是,这些要求通常是由更适合体力劳动的半功能白痴编写的,而且双手被绑住了。如果这样的话,我感到很同情。

If you have a test network with workstations having multiple physical network interfaces, I can provide a simple example C99 test program you can use to verify the design works. 如果您的测试网络的工作站具有多个物理网络接口,那么我可以提供一个简单的C99测试程序示例,您可以使用该示例程序来验证设计工作。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM