如何从 udp 套接字获取您自己的(本地)IP 地址 (C/C++)

发布于 2024-07-05 09:23:48 字数 258 浏览 4 评论 0原文

  1. 您有多个网络适配器。
  2. 将 UDP 套接字绑定到本地端口,而不指定地址。
  3. 在其中一个适配器上接收数据包。

如何获取接收数据包的适配器的本地 IP 地址?

问题是“接收器适配器的 IP 地址是什么?” 不是我们在通话中收到的发件人地址

receive_from( ..., &senderAddr, ... );

  1. You have multiple network adapters.
  2. Bind a UDP socket to an local port, without specifying an address.
  3. Receive packets on one of the adapters.

How do you get the local ip address of the adapter which received the packet?

The question is, "What is the ip address from the receiver adapter?" not the address from the sender which we get in the

receive_from( ..., &senderAddr, ... );

call.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

厌倦 2024-07-12 09:23:48

在Linux环境下,您可以使用recvmsg来获取本地IP地址。

    //create socket and bind to local address:INADDR_ANY:
    int s = socket(PF_INET,SOCK_DGRAM,0);
    bind(s,(struct sockaddr *)&myAddr,sizeof(myAddr)) ;
    // set option
    int onFlag=1;
    int ret = setsockopt(s,IPPROTO_IP,IP_PKTINFO,&onFlag,sizeof(onFlag));
    // prepare buffers
    // receive data buffer
    char dataBuf[1024] ;
    struct iovec iov = {
        .iov_base=dataBuf,
        .iov_len=sizeof(dataBuf)
    } ;
    // control buffer
    char cBuf[1024] ;
    // message
    struct msghdr  msg = {
        .msg_name=NULL, // to receive peer addr with struct sockaddr_in
        .msg_namelen=0, // sizeof(struct sockaddr_in)
        .msg_iov=&iov,
        .msg_iovlen=1,
        .msg_control=cBuf,
        .msg_controllen=sizeof(cBuf)
    } ;
    while(1) {
        // reset buffers
        msg.msg_iov[0].iov_base = dataBuf ;
        msg.msg_iov[0].iov_len = sizeof(dataBuf) ;
        msg.msg_control = cBuf ;
        msg.msg_controllen = sizeof(cBuf) ;
        // receive
        recvmsg(s,&msg,0);
        for( struct cmsghdr* pcmsg=CMSG_FIRSTHDR(&msg);
             pcmsg!=NULL; pcmsg=CMSG_NXTHDR(&msg,pcmsg) ) {
            if(pcmsg->cmsg_level==IPPROTO_IP && pcmsg->cmsg_type==IP_PKTINFO) {
                struct in_pktinfo * pktinfo=(struct in_pktinfo *)CMSG_DATA(pcmsg);
                printf("ifindex=%d ip=%s\n", pktinfo->ipi_ifindex, inet_ntoa(pktinfo->ipi_addr)) ;
            }
        }
    }

以下内容在非对称路由环境中不起作用。

先将 SO_REUSEADDR 设置为 true

BOOL bOptVal = 1;
setsockopt(so, SOL_SOCKET, SO_REUSEADDR, (char *)&boOptVal, sizeof(bOptVal));

您可以在 receive_from( ..., &remoteAddr, ... ); 创建另一个套接字后 ,然后连接回remoteAddr。 然后调用getsockname就可以获取ip地址。

SOCKET skNew = socket( )
// Same local address and port as that of your first socket 
// INADDR_ANY
bind(skNew, ,  )
// set SO_REUSEADDR to true again
setsockopt(skNew, SOL_SOCKET, SO_REUSEADDR, (char *)&boOptVal, sizeof(bOptVal));

// connect back  
connect(skNew, remoteAddr)

// get local address of the socket
getsocketname(skNew, )

In Linux environment, you can use recvmsg to get local ip address.

    //create socket and bind to local address:INADDR_ANY:
    int s = socket(PF_INET,SOCK_DGRAM,0);
    bind(s,(struct sockaddr *)&myAddr,sizeof(myAddr)) ;
    // set option
    int onFlag=1;
    int ret = setsockopt(s,IPPROTO_IP,IP_PKTINFO,&onFlag,sizeof(onFlag));
    // prepare buffers
    // receive data buffer
    char dataBuf[1024] ;
    struct iovec iov = {
        .iov_base=dataBuf,
        .iov_len=sizeof(dataBuf)
    } ;
    // control buffer
    char cBuf[1024] ;
    // message
    struct msghdr  msg = {
        .msg_name=NULL, // to receive peer addr with struct sockaddr_in
        .msg_namelen=0, // sizeof(struct sockaddr_in)
        .msg_iov=&iov,
        .msg_iovlen=1,
        .msg_control=cBuf,
        .msg_controllen=sizeof(cBuf)
    } ;
    while(1) {
        // reset buffers
        msg.msg_iov[0].iov_base = dataBuf ;
        msg.msg_iov[0].iov_len = sizeof(dataBuf) ;
        msg.msg_control = cBuf ;
        msg.msg_controllen = sizeof(cBuf) ;
        // receive
        recvmsg(s,&msg,0);
        for( struct cmsghdr* pcmsg=CMSG_FIRSTHDR(&msg);
             pcmsg!=NULL; pcmsg=CMSG_NXTHDR(&msg,pcmsg) ) {
            if(pcmsg->cmsg_level==IPPROTO_IP && pcmsg->cmsg_type==IP_PKTINFO) {
                struct in_pktinfo * pktinfo=(struct in_pktinfo *)CMSG_DATA(pcmsg);
                printf("ifindex=%d ip=%s\n", pktinfo->ipi_ifindex, inet_ntoa(pktinfo->ipi_addr)) ;
            }
        }
    }

The following does not work in asymmetric routing environment.

you can first set SO_REUSEADDR to true

BOOL bOptVal = 1;
setsockopt(so, SOL_SOCKET, SO_REUSEADDR, (char *)&boOptVal, sizeof(bOptVal));

after receive_from( ..., &remoteAddr, ... ); create another socket, and connect back to remoteAddr. Then call getsockname can get the ip address.

SOCKET skNew = socket( )
// Same local address and port as that of your first socket 
// INADDR_ANY
bind(skNew, ,  )
// set SO_REUSEADDR to true again
setsockopt(skNew, SOL_SOCKET, SO_REUSEADDR, (char *)&boOptVal, sizeof(bOptVal));

// connect back  
connect(skNew, remoteAddr)

// get local address of the socket
getsocketname(skNew, )
腻橙味 2024-07-12 09:23:48

不幸的是,当与绑定到“任何 IP”的套接字一起使用时,sendto 和 recvfrom API 调用从根本上被破坏,因为它们没有本地 IP 信息的字段。

所以你对此能做些什么?

  1. 您可以猜测(例如根据路由表)。
  2. 您可以获得本地地址列表并将单独的套接字绑定到每个本地地址。
  3. 您可以使用支持此信息的较新 API。 这有两个部分,首先您必须使用相关套接字选项(对于 IPv4 为 ip_recvif,对于 IPv6 为 ipv6_recvif)来告诉堆栈您需要此信息。 然后你必须使用不同的函数(linux和其他几个类unix系统上的recvmsg,windows上的WSArecvmsg)来接收数据包。

这些选项都不是很好。 猜测有时显然会产生错误的答案。 绑定单独的套接字会增加软件的复杂性,并且如果本地地址列表随着程序运行而更改,则会导致问题。 较新的 API 是正确的技术解决方案,但可能会降低可移植性(特别是看起来 WSArecvmsg 在 Windows XP 上不可用),并且可能需要修改您正在使用的套接字包装器库。

编辑看起来我错了,MS 文档似乎具有误导性,并且 WSArecvmsg 在 Windows XP 上可用。 请参阅https://stackoverflow.com/a/37334943/5083516

Unfortunately the sendto and recvfrom API calls are fundamentally broken when used with sockets bound to "Any IP" because they have no field for local IP information.

So what can you do about it?

  1. You can guess (for example based on the routing table).
  2. You can get a list of local addresses and bind a seperate socket to each local address.
  3. You can use newer APIs that support this information. There are two parts to this, firstly you have to use the relavent socket option (ip_recvif for IPv4, ipv6_recvif for IPv6) to tell the stack you want this information. Then you have to use a different function (recvmsg on linux and several other unix-like systems, WSArecvmsg on windows) to receive the packet.

None of these options are great. Guessing will obviously produce wrong answers soemtimes. Binding seperate sockets increases the complexity of your software and causes problems if the list of local addresses changes will your program is running. The newer APIs are the correct techical soloution but may reduce portability (in particular it looks like WSArecvmsg is not available on windows XP) and may require modifications to the socket wrapper library you are using.

Edit looks like I was wrong, it seems the MS documentation is misleading and that WSArecvmsg is available on windows XP. See https://stackoverflow.com/a/37334943/5083516

时光是把杀猪刀 2024-07-12 09:23:48

您好,

我假设您已经使用 INADDR_ANY 完成绑定来指定地址。

如果是这种情况,那么 INADDR_ANY 的语义是在所有接口上指定的端口上创建 UDP 套接字。 套接字将获取发送到指定端口上所有接口的所有数据包。

使用此套接字发送时,将使用编号最小的接口。 传出发件人的地址字段设置为所使用的第一个传出接口的 IP 地址。

第一个传出接口定义为执行 ifconfig -a 时的顺序。 可能是 eth0。

HTH。

干杯,

G'day,

I assume that you've done your bind using INADDR_ANY to specify the address.

If this is the case, then the semantics of INADDR_ANY is such that a UDP socket is created on the port specified on all of your interfaces. The socket is going to get all packets sent to all interfaces on the port specified.

When sending using this socket, the lowest numbered interface is used. The outgoing sender's address field is set to the IP address of that first outgoing interface used.

First outgoing interface is defined as the sequence when you do an ifconfig -a. It will probably be eth0.

HTH.

cheers,
Rob

夜还是长夜 2024-07-12 09:23:48

您可以枚举所有网络适配器,获取它们的 IP 地址,并将子网掩码覆盖的部分与发送者的地址进行比较。

喜欢:

IPAddress FindLocalIPAddressOfIncomingPacket( senderAddr )
{
    foreach( adapter in EnumAllNetworkAdapters() )
    {
        adapterSubnet = adapter.subnetmask & adapter.ipaddress;
        senderSubnet = adapter.subnetmask & senderAddr;
        if( adapterSubnet == senderSubnet )
        {
            return adapter.ipaddress;
        }
    }
}

You could enumerate all the network adapters, get their IP addresses and compare the part covered by the subnet mask with the sender's address.

Like:

IPAddress FindLocalIPAddressOfIncomingPacket( senderAddr )
{
    foreach( adapter in EnumAllNetworkAdapters() )
    {
        adapterSubnet = adapter.subnetmask & adapter.ipaddress;
        senderSubnet = adapter.subnetmask & senderAddr;
        if( adapterSubnet == senderSubnet )
        {
            return adapter.ipaddress;
        }
    }
}
逆光下的微笑 2024-07-12 09:23:48

timbo 提供的解决方案假设地址范围是唯一的且不重叠。 虽然情况通常如此,但这并不是一个通用的解决方案。

Steven 的书“Unix 网络编程”(第 20.2 节)中提供了一个函数的出色实现,它完全按照您的要求进行操作
这是一个基于recvmsg() 的函数,而不是recvfrom()。 如果您的套接字启用了 IP_RECVIF 选项,则 recvmsg() 将返回接收数据包的接口的索引。 然后可以使用它来查找目标地址。

源代码可在此处获取。 有问题的函数是“recvfrom_flags()”

The solution provided by timbo assumes that the address ranges are unique and not overlapping. While this is usually the case, it isn't a generic solution.

There is an excellent implementation of a function that does exactly what you're after provided in the Steven's book "Unix network programming" (section 20.2)
This is a function based on recvmsg(), rather than recvfrom(). If your socket has the IP_RECVIF option enabled then recvmsg() will return the index of the interface on which the packet was received. This can then be used to look up the destination address.

The source code is available here. The function in question is 'recvfrom_flags()'

无人问我粥可暖 2024-07-12 09:23:48
ssize_t
     recvfrom(int socket, void *restrict buffer, size_t length, int flags,
         struct sockaddr *restrict address, socklen_t *restrict address_len);

     ssize_t
     recvmsg(int socket, struct msghdr *message, int flags);

[..]
     If address is not a null pointer and the socket is not connection-oriented, the
     source address of the message is filled in.

实际代码:

<代码>
int nbytes = recvfrom(sock, buf, MAXBUFSIZE, MSG_WAITALL, (struct sockaddr *)&bindaddr, &addrlen);

fprintf(stdout, "在本地地址 %s 上读取 %d 个字节\n ", nbytes, inet_ntoa(bindaddr.sin_addr.s_addr));
希望

这有帮助。

ssize_t
     recvfrom(int socket, void *restrict buffer, size_t length, int flags,
         struct sockaddr *restrict address, socklen_t *restrict address_len);

     ssize_t
     recvmsg(int socket, struct msghdr *message, int flags);

[..]
     If address is not a null pointer and the socket is not connection-oriented, the
     source address of the message is filled in.

Actual code:


int nbytes = recvfrom(sock, buf, MAXBUFSIZE, MSG_WAITALL, (struct sockaddr *)&bindaddr, &addrlen);

fprintf(stdout, "Read %d bytes on local address %s\n", nbytes, inet_ntoa(bindaddr.sin_addr.s_addr));

hope this helps.

风吹雨成花 2024-07-12 09:23:48

尝试这个:

gethostbyname("localhost");

Try this:

gethostbyname("localhost");
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文