如何在C语言中获取接口的IP地址?

17

假设我正在运行一个名为IpAddresses.c的程序,我想让该程序根据每个接口获取此设备拥有的所有IP地址。就像ifconfig一样。我应该如何做呢?

我对ioctl不太了解,但我读到说它可能会帮助我。

7个回答

43

可以直接使用getifaddrs()函数。以下是一个示例:

#include <arpa/inet.h>
#include <sys/socket.h>
#include <ifaddrs.h>
#include <stdio.h>

int main ()
{
    struct ifaddrs *ifap, *ifa;
    struct sockaddr_in *sa;
    char *addr;

    getifaddrs (&ifap);
    for (ifa = ifap; ifa; ifa = ifa->ifa_next) {
        if (ifa->ifa_addr && ifa->ifa_addr->sa_family==AF_INET) {
            sa = (struct sockaddr_in *) ifa->ifa_addr;
            addr = inet_ntoa(sa->sin_addr);
            printf("Interface: %s\tAddress: %s\n", ifa->ifa_name, addr);
        }
    }

    freeifaddrs(ifap);
    return 0;
}

以下是我在我的电脑上得到的输出:

Interface: lo   Address: 127.0.0.1
Interface: eth0 Address: 69.72.234.7
Interface: eth0:1       Address: 10.207.9.3

你为什么有两个 eth0 接口?顺便说一句,谢谢你的回答。 - gvalero87
@gvalero87 第一个eth0告诉网络卡通过互联网进行通信。第二个eth0通过私有连接(光纤线)与第三方通信。这是网络管理员在路由表中设置的。 - chrisaycock
请看下面 Luiz Normando 的回复:你的代码可能会在某些机器上崩溃,因为 ifa->ifa_addr 可能对于某些接口是 NULL。 - Lanzelot
@Lanzelot,我已经使用Luiz Normando的补丁更新了我的答案。 - chrisaycock
我已经添加了错误检查代码:在 stdio.h 行下面添加了一个新的头文件行 "#include <stdlib.h>",并用这一行 "if (getifaddrs(&ifap) == -1) { perror("getifaddrs"); exit(1); }" 替换了这一行 "getifaddrs (&ifap);"。如果没有前面的两行更改,如果 "ifa_addr" 有一个空指针,则会创建分段错误。这些更改受到 Michael-Hampton 在此处显示的代码的启发:https://stackoverflow.com/a/27433320/3553808。这段代码在 macOS(10.15.x/Catalina)上运行良好。 - atErik

9

以下是一些可能对您有所帮助的Linux示例代码。

#include <stdio.h>
#include <net/if.h>
#include <netinet/in.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/socket.h>

#define INT_TO_ADDR(_addr) \
(_addr & 0xFF), \
(_addr >> 8 & 0xFF), \
(_addr >> 16 & 0xFF), \
(_addr >> 24 & 0xFF)

int main()
{
    struct ifconf ifc;
    struct ifreq ifr[10];
    int sd, ifc_num, addr, bcast, mask, network, i;

    /* Create a socket so we can use ioctl on the file 
     * descriptor to retrieve the interface info. 
     */

    sd = socket(PF_INET, SOCK_DGRAM, 0);
    if (sd > 0)
    {
        ifc.ifc_len = sizeof(ifr);
        ifc.ifc_ifcu.ifcu_buf = (caddr_t)ifr;

        if (ioctl(sd, SIOCGIFCONF, &ifc) == 0)
        {
            ifc_num = ifc.ifc_len / sizeof(struct ifreq);
            printf("%d interfaces found\n", ifc_num);

            for (i = 0; i < ifc_num; ++i)
            {
                if (ifr[i].ifr_addr.sa_family != AF_INET)
                {
                    continue;
                }

                /* display the interface name */
                printf("%d) interface: %s\n", i+1, ifr[i].ifr_name);

                /* Retrieve the IP address, broadcast address, and subnet mask. */
                if (ioctl(sd, SIOCGIFADDR, 𝔦[i]) == 0)
                {
                    addr = ((struct sockaddr_in *)(𝔦[i].ifr_addr))->sin_addr.s_addr;
                    printf("%d) address: %d.%d.%d.%d\n", i+1, INT_TO_ADDR(addr));
                }
                if (ioctl(sd, SIOCGIFBRDADDR, 𝔦[i]) == 0)
                {
                    bcast = ((struct sockaddr_in *)(𝔦[i].ifr_broadaddr))->sin_addr.s_addr;
                    printf("%d) broadcast: %d.%d.%d.%d\n", i+1, INT_TO_ADDR(bcast));
                }
                if (ioctl(sd, SIOCGIFNETMASK, 𝔦[i]) == 0)
                {
                    mask = ((struct sockaddr_in *)(𝔦[i].ifr_netmask))->sin_addr.s_addr;
                    printf("%d) netmask: %d.%d.%d.%d\n", i+1, INT_TO_ADDR(mask));
                }                

                /* Compute the current network value from the address and netmask. */
                network = addr & mask;
                printf("%d) network: %d.%d.%d.%d\n", i+1, INT_TO_ADDR(network));
            }                      
        }

        close(sd);
    }

    return 0;
}


感谢提供一个 ioctl 的替代方案。然而我不知道什么时候应该使用 ioctlgetifaddrs - Youda008
我相信即使它不符合任何单一标准,ioctl可能会更具可移植性。它在大多数Unix和类Unix系统上都得到支持,并且ioctl函数调用最初出现在Version 7 AT&T UNIX中。我相信getifaddrs在BSD和Linux上得到支持,并且最初出现在glibc 2.3中。 - jschmier
这在 Android 上也可以工作,因为某些原因它没有 ifaddrs,但在 OS X 上不行。这里有一个例子,可以在两者上都工作。 - Grishka
1
请使用标准的 htonl()/ntohl() 函数,而不是自定义的 INT_TO_ADDR 宏。 - Matthieu
有点困惑,ioctl 怎么工作的?第一次我们在套接字上调用时它可以帮助我们获取机器上所有的网络接口,对于每个接口,下一次调用地址时它是如何正确地工作的?&ifr[i] 只是存储从 ioctl 获取的信息吗?还是其中的信息也被使用了?抱歉问题有些蠢,但是当我搜索时 ioctl 的信息非常少... - AAB

6

使用getifaddrs()解决方案非常好。我建议只有一个改进:

--- chrisaycock
+++ normando
@@ -11,7 +11,7 @@

     getifaddrs (&ifap);
     for (ifa = ifap; ifa; ifa = ifa->ifa_next) {
-        if (ifa->ifa_addr->sa_family==AF_INET) {
+        if (ifa->ifa_addr && ifa->ifa_addr->sa_family==AF_INET) {
             sa = (struct sockaddr_in *) ifa->ifa_addr;
             addr = inet_ntoa(sa->sin_addr);
             printf("Interface: %s\tAddress: %s\n", ifa->ifa_name, addr);

因为我自己遇到了分段错误。


我们在原始代码中也遇到了段错误。根据getifaddrs的man页面,ifa_addr成员对于某些接口可能为NULL... - Lanzelot

2

请参考另一个Stack Overflow问题:列举分配给网络接口的每个IP地址

总之,你可以使用以下方法:

  • ioctl(SIOCGIFCONF) -> 传统的ioctl
  • getifaddrs() -> 来自BSDi,现在也适用于Linux和BSD。
  • RTNETLINK (Linux)

1
你可以尝试这样做:
struct ifreq ifr[MAX_INTERFACES];
struct ifconf ifc;
memset(ifr, 0, sizeof(ifr));
ifc.ifc_len = sizeof(ifr);
ifc.ifc_req = ifr;

// Get the list of interfaces
if (ioctl(sock, SIOCGIFCONF, &ifc) == -1) {
    fprintf(stderr, "ioctl SIOCGIFCONF failed: %d", errno);
}

struct ifreq *ifr_iterator = ifc.ireq;
int i = 0;
size_t len;
while (i < ifc.ifc_len) {
   /* DO STUFF */
   // Maybe some more filtering based on SIOCGIFFLAGS 
   // Your code
   // Use ifr_iterator-> ...

   len = IFNAMSIZ + ifr_iterator->ifr_addr.sa_len;
   ifr_iterator = (struct ifreq *)((char *)ifr_iterator + len);
   i += len;
}

4
请注意,这段代码在 BSD 上会失败,因为 ifreq 结构的大小是可变的。你不能仅仅假设 ifc.ifc_len/sizeof(struct ifreq) 就可以给你接口数量。相反,你需要像这样迭代列表中的元素:struct ifreq *ifr_iterator = ifc.ireq; size_t len; while (i < ifc.ifc_len) { /* DO STUFF */ len = IFNAMSIZ + ifr_iterator->ifr_addr.sa_len; ifr_iterator = (struct ifreq *)((char *)ifr_iterator + len); i += len; } - Joakim
@Joakim 那就意味着 #define ifc_req ifc_ifcu.ifcu_req /* array of structures ret'd */ 从技术上讲不是一个数组,因为大小是可变的?看起来5.9版本添加了更多的东西。此外,ifconfig.c代码似乎没有SIOCGIFCONF。 - GorillaApe
1
@GorillaApe 在某些系统上,它可能是一个数组,而在另一个系统上则不是。我的意思是有不同的实现方式。在Linux上,这是一个数组,每个项目都具有固定大小,但在BSD上,每个项目的大小可以变化,因此正确的做法是始终使用长度字段。如果您想要可移植的代码。 - Joakim

0

请查看(仅限Windows系统)IP Helper API - 幸运的是,在Windows上您不需要ioctl来完成这个操作。


0

如果您还需要IP6地址,您可以像这样扩展接受的答案:

#include <arpa/inet.h>
#include <ifaddrs.h>
#include <stdio.h>
#include <netdb.h>

int main() {
    struct ifaddrs *ifap;
    getifaddrs(&ifap);
    for (struct ifaddrs *ifa = ifap; ifa; ifa = ifa->ifa_next) {
        if (ifa->ifa_addr && (ifa->ifa_addr->sa_family == AF_INET6)) {
            char addr[INET6_ADDRSTRLEN];
            getnameinfo(ifa->ifa_addr, sizeof(struct sockaddr_in6), addr, sizeof(addr), NULL, 0, NI_NUMERICHOST);
            printf("Interface: %s\tAddress: %s\n", ifa->ifa_name, addr);
        } else if (ifa->ifa_addr && ifa->ifa_addr->sa_family == AF_INET) {
            struct sockaddr_in *sa = (struct sockaddr_in *)ifa->ifa_addr;
            char *addr = inet_ntoa(sa->sin_addr);
            printf("Interface: %s\tAddress: %s\n", ifa->ifa_name, addr);
        }
    }
    freeifaddrs(ifap);
    return 0;
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接