我是一位经验丰富的Linux套接字程序员,正在编写一个具有许多传出接口的服务器应用程序。现在,服务器套接字与INADDR_ANY一起绑定到进程开始时的随机源端口。
稍后在某个时候提交对特定节点的响应时,我需要分配一个固定的源IP地址。执行此操作的标准方法是调用bind。但是,对于端口号调用bind一次,连续调用失败,并且参数无效。
创建一个新的套接字并不是一个好的选择,因为在回复某些客户端时我必须经常这样做。
我还研究了SO和很多套接字选项,比如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));
这方面的任何想法都将受到高度赞赏。我在套接字,SO等方面经历了相当多的材料,但还没有成功。
答案 0 :(得分:6)
我终于找到了解决方案,所以接受了我自己的答案(无耻但正确的插件),并补充了代码示例。
我最初希望重写外发数据包的源地址而不再创建套接字,其中套接字已被绑定。对于这种情况,多次调用绑定失败,并且(在我的特定情况下),我无法为每个源ip分配单独的套接字并使用它。
我在 IP_PACKET_INFO 中找到了一些引用,但要让它正常工作真是太痛苦了。以下参考资料很有帮助。
示例代码
这是一个简单的应用程序,它创建一个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 struct 等仅仅是为了能够自己编写ipoktinfo结构
答案 1 :(得分:4)
无法取消绑定并重新绑定现有套接字。
答案 2 :(得分:1)
为什么不为每个接口创建套接字?由于UDP / IP协议是无连接的,您可以通过选择用于发送回复的套接字来选择源IP地址;没有必要使用相同的套接字来接收传入的数据报。
缺点是您不能再绑定到通配符地址,并且必须使用select()
,poll()
,多个线程或其他一些机制来同时从多个源接收数据报。您还需要一些逻辑来根据客户端IP地址有效地选择套接字。
在大多数情况下,我怀疑添加一些路由条目将每个远程IP地址路由到所需的主机IP地址,并为每个主机IP地址和端口组合使用单独的套接字,完美地解决了问题 - 并使用这样做非常有效的内核功能。虽然行为可能是应用程序要求,但我怀疑使用网络接口配置可以更好地解决此问题。不幸的是,这些要求通常都是由半功能的白痴写成的,这些白痴更适合体力劳动,而你的双手也是如此。如果是这样,我会同情。
如果您的测试网络中有多个物理网络接口的工作站,我可以提供一个简单的示例C99测试程序,您可以使用它来验证设计是否有效。