Linux C 获取本机IPV4和IPV6地址列表

发布时间 2023-05-26 16:14:41作者: 川川籽

有时候设备网卡上有多个IPv6,其中只有一个是可用的,另外一个是内网地址,无法使用,如果程序需要绑定一个V6地址的时候,需要获取网卡上的V6地址,并且要求是可用的。

通过ifconfig可用看到,eth0网卡上有2个IP地址,其中只有第一个V6地址的ScopeGlobal

eth0      Link encap:Ethernet  HWaddr 52:54:00:1D:79:D1
          inet addr:192.168.121.120  Bcast:192.168.121.255  Mask:255.255.255.0
          inet6 addr: 2001:250:250:250:250:250:250:222/64 Scope:Global
          inet6 addr: fe80::5054:ff:fe1d:79d1/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:5999959 errors:0 dropped:0 overruns:0 frame:0
          TX packets:104610 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000
          RX bytes:317521212 (302.8 MiB)  TX bytes:8564391 (8.1 MiB)

那么如何用C获取IPv6地址,并且过滤其中Scope为Global的地址:

#define _GNU_SOURCE    # required for NI_NUMERICHOST
#include <arpa/inet.h>
#include <sys/socket.h>
#include <ifaddrs.h>
#include <stdio.h>
#include <netdb.h>

int main ()
{
  struct ifaddrs *ifap, *ifa;
  struct sockaddr_in6 *sa;
  struct sockaddr_in *sa4;
  char addr[INET6_ADDRSTRLEN];
  char addr4[INET_ADDRSTRLEN];

  getifaddrs (&ifap);
  for (ifa = ifap; ifa; ifa = ifa->ifa_next) {
    if (ifa->ifa_addr->sa_family==AF_INET6) {
      sa = (struct sockaddr_in6 *) ifa->ifa_addr;
      getnameinfo(ifa->ifa_addr, sizeof(struct sockaddr_in6), addr,
                  sizeof(addr), NULL, 0, NI_NUMERICHOST);
      printf("Interface: %5s\tAddress: %s, scope: ", ifa->ifa_name, addr);

      // struct sockaddr_in6 sa;
      if (IN6_IS_ADDR_LINKLOCAL(&sa->sin6_addr)) {
        printf ("link-local ");
      }
      if (IN6_IS_ADDR_SITELOCAL(&sa->sin6_addr)) {
        printf ("site-local ");
      }
      if (IN6_IS_ADDR_V4MAPPED(&sa->sin6_addr)) {
        printf ("v4mapped ");
      }
      if (IN6_IS_ADDR_V4COMPAT(&sa->sin6_addr)) {
        printf ("v4compat ");
      }
      if (IN6_IS_ADDR_LOOPBACK(&sa->sin6_addr)) {
        printf ("host ");
      }
      if (IN6_IS_ADDR_UNSPECIFIED(&sa->sin6_addr)) {
        printf ("unspecified ");
      }
      if (IN6_IS_ADDR_MC_GLOBAL(&sa->sin6_addr)){
        printf ("global ");
      }
      printf("\n");
    } else if(ifa->ifa_addr->sa_family == AF_INET){
		  sa4 = (struct sockaddr_in *)ifa->ifa_addr;
		  getnameinfo(ifa->ifa_addr, sizeof(struct sockaddr_in), addr4,
                  sizeof(addr4), NULL, 0, NI_NUMERICHOST);
		  /*inet_ntop(AF_INET, sa4, addr4, INET_ADDRSTRLEN);*/
		  printf("Interface: %5s\tAddress: %s\n", ifa->ifa_name, addr4);
	  }
  }

  freeifaddrs(ifap);
  return 0;
}

编译下:

gcc -o ipa ipa.c

运行输出:

# ./ipa
Interface:    lo	Address: 127.0.0.1
Interface:  eth0	Address: 192.168.121.120
Interface:  eth1	Address: 10.24.0.114
Interface:    lo	Address: ::1, scope: host
Interface:  eth0	Address: 2001:250:250:250:250:250:250:222, scope:
Interface:  eth0	Address: fe80::5054:ff:fe1d:79d1%eth0, scope: link-local
Interface:  eth1	Address: fe80::5054:ff:fedb:bdd7%eth1, scope: link-local

啊,可见IN6_IS_ADDR_MC_GLOBAL无法判断global的地址,我们只能用排除法了,将IN6_IS_ADDR_MC_GLOBAL判断修改为如下代码:

      // if (IN6_IS_ADDR_MC_GLOBAL(&sa->sin6_addr)){
      //   printf ("global ");
      // }
      if (!(IN6_IS_ADDR_LINKLOCAL(&sa->sin6_addr)) && !(IN6_IS_ADDR_SITELOCAL(&sa->sin6_addr)) && 
          !(IN6_IS_ADDR_V4MAPPED(&sa->sin6_addr)) && !(IN6_IS_ADDR_LOOPBACK(&sa->sin6_addr)) &&
          !(IN6_IS_ADDR_UNSPECIFIED(&sa->sin6_addr))
      ) {
        printf ("global ");
      }

重新编译输出:

# ./ipa
Interface:    lo	Address: 127.0.0.1
Interface:  eth0	Address: 192.168.121.120
Interface:  eth1	Address: 10.24.0.114
Interface:    lo	Address: ::1, scope: host
Interface:  eth0	Address: 2001:250:250:250:250:250:250:222, scope: global
Interface:  eth0	Address: fe80::5054:ff:fe1d:79d1%eth0, scope: link-local
Interface:  eth1	Address: fe80::5054:ff:fedb:bdd7%eth1, scope: link-local