0001-C语言如何获取ipv6地址
C语⾔如何获取ipv6地址
written by whowin
使⽤通常获取ipv4的IP地址的⽅法是⽆法获取ipv6地址的,本⽂介绍了使⽤C语⾔获取ipv6地址的三种⽅法,每种⽅法均给出了完整的源程序,本⽂所有实例在 ubuntu 20.04 下测试通过,gcc 版本 9.4.0。
1. ipv4的IP地址的获取⽅法
不论是获取 ipv4 的 IP 地址还是 ipv6 的地址,应⽤程序都需要与内核通讯才可以完成;
ioctl 是和内核通讯的⼀种常⽤⽅法,也是⽤来获取 ipv4 的 IP 地址的常⽤⽅法,下⾯代码演⽰了如何使⽤ioctl 来获取本机所有接⼝的 IP 地址:
#include <stdio.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <linux/if.h>
#include <arpa/inet.h>
#include <sys/socket.h>
int main() {
int i=0;
int sockfd;
struct ifconf ifc;
char buf[512] = {0};
struct ifreq*ifr;
ifc.ifc_len=512;
ifc.ifc_buf=buf;
if ((sockfd=socket(AF_INET, SOCK_DGRAM, 0)) <0) {
perror("socket");
return-1;
}
ioctl(sockfd, SIOCGIFCONF, &ifc);
ifr= (struct ifreq*)buf;
for (i= (ifc.ifc_len/sizeof(struct ifreq)); i>0; i--) {
printf("%s: %s\n",
ifr->ifr_name,
inet_ntoa(((struct sockaddr_in*)&(ifr->ifr_addr))->sin_addr));
ifr++;
}
}
但是使⽤ ioctl ⽆法获取 ipv6 地址,即便我们建⽴⼀个 AF_INET6 的 socket,ioctl 仍然只返回 ipv4 的信息,我们可以试试下⾯代码;
#include <stdio.h>
#include <stdlib.h>
图1:ioctl ⽆法获取ipv6地址
这段程序在我的机器上的运⾏结果是这样的
我们看到,不管怎么折腾,返回的仍然只有 ipv4 的地址,所以我们需要⼀些其他的⽅法获得 ipv6 地址,下⾯介绍三种使⽤ C 语⾔获得 ipv6 地址的⽅法。
2. 从⽂件/proc/net/if_inet6中获取ipv6地址
#include <sys/ioctl.h>
#include <linux/if.h>
#include <arpa/inet.h>
无法获取ip地址#include <sys/socket.h>
int  main () {
联欢会串词
int  i  = 0;
int  sockfd ;
struct  ifconf  ifc ;
char  buf [1024] = {0};
struct  ifreq  *ifr ;
ifc .ifc_len  = 1024;
ifc .ifc_buf  = buf ;
if  ((sockfd  = socket (AF_INET6, SOCK_DGRAM , 0)) < 0) {
perror ("socket");
return  -1;
}
ioctl (sockfd , SIOCGIFCONF , &ifc );
ifr  = (struct  ifreq *)buf ;
struct  sockaddr_in  *sa ;
for  (i  = (ifc .ifc_len  /sizeof (struct  ifreq )); i  > 0; i --) {
sa  = (struct  sockaddr_in  *)&(ifr ->ifr_addr );
if  (sa ->sin_family  == AF_INET6) {
printf ("%s: AF_INET6\n", ifr ->ifr_name );
} else  if  (sa ->sin_family  == AF_INET ){
printf ("%s: AF_INET\n", ifr ->ifr_name );
} else  {
printf ("%s: %d.  It is an unknown address family.\n",
ifr ->ifr_name , sa ->sin_family );
}
ifr ++;
}
}
我们先来看看⽂件/proc/net/if_inet6中有什么内容
图2:⽂件/proc/net/if_inet6内容
这个⽂件中,每⾏为⼀个⽹络接⼝的数据,每⾏数据分成 6 个字段
字段序号字段名称字段说明
1ipv6address IPv6地址,32位16进制⼀组,中间没有:分隔符
2ifindex接⼝设备号,每个设备都不同,按 16 进制显⽰
3prefixlen16进制显⽰的前缀⻓度,类似 ipv4 的⼦⽹掩码的东西
4scopeid scope id
5flags接⼝标志,这些标志标识着这个接⼝的特性
6devname接⼝设备名称
所以从这个⽂件中可以很容易地获得所有接⼝的 ipv6 地址
#include <stdio.h>
#include <linux/if.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int main(void) {
FILE*f;
int scope, prefix;
unsigned char_ipv6[16];
char dname[IFNAMSIZ];
char address[INET6_ADDRSTRLEN];
f=fopen("/proc/net/if_inet6", "r");
if (f==NULL) {
return-1;
}
小小父母官
while (19==fscanf(f,
" %2hhx%2hhx%2hhx%2hhx%2hhx%2hhx%2hhx%2hhx\
%2hhx%2hhx%2hhx%2hhx%2hhx%2hhx%2hhx%2hhx \
%*x %x %x %*x %s",
&_ipv6[0], &_ipv6[1], &_ipv6[2], &_ipv6[3],
&_ipv6[4], &_ipv6[5], &_ipv6[6], &_ipv6[7],
&_ipv6[8], &_ipv6[9], &_ipv6[10], &_ipv6[11],
&_ipv6[12], &_ipv6[13], &_ipv6[14], &_ipv6[15],
&prefix, &scope, dname)) {
if (inet_ntop(AF_INET6, _ipv6, address, sizeof(address)) ==NULL) { continue;
}
printf("%s: %s\n", dname, address);
}
fclose(f);
return0;
}
fscanf 中的 %2hhx 是⼀种不多⻅的⽤法,hhx 表⽰后⾯的指针 &_ipv6[x] 指向⼀个 unsigned char *,2 表⽰从⽂件中读取的⻓度,这个是常⽤的;
关于 fscanf 中的 hh 和 h 的⽤法,可以查看在线⼿册 man fscanf 了解更多的内容;
ipv6 地址⼀共 128 位,16 位⼀组,⼀共 8 组,但是这⾥为什么不⼀次从⽂件中读⼊ 4 个字符(16 位),读 8 次,⽽要⼀次读⼊ 2 个字符读 16 次呢?
这个要去看 inet_ntop 的参数,我们先使⽤命令 man inet_ntop 看⼀下 inet_ntop 的在线⼿册
const char*inet_ntop(int af, const void*src, char*dst, socklen_t size);
当第 1 个参数 af = AF_INET6 时,对于第 2 个参数,还有说明:
AF_INET6
src  points  to  a struct in6_addr (in network byte order)
which is converted to a representation of this address in
the most appropriate IPv6 network address format for this
address. The buffer dst must be at least INET6_ADDRSTRLEN
bytes long.
很显然,需要第 2 个参数指向⼀个 struct in6_addr,这个结构在 netinet/in.h 中定义:
/* IPv6 address */
struct in6_addr
{
union
{
uint8_t__u6_addr8[16];
uint16_t__u6_addr16[8];
uint32_t__u6_addr32[4];
} __in6_u;
#define s6_addr        __in6_u.__u6_addr8
#ifdef __USE_MISC
# define s6_addr16      __in6_u.__u6_addr16
# define s6_addr32      __in6_u.__u6_addr32
#endif
};
这个结构在⼀般情况下使⽤的是 uint8_t  __u6_addr8[16],也就是 16 个 unsigned char 的数组,只有在"混杂模式"时才使⽤ 8 个 unsigned short int 或者 4 个 unsigned int 的数组;
所以,实际上 struct in6_addr 的结构如下
struct in6_addr {
unsigned char__u6_addr8[16];
}
这就是我们在读⽂件时为什么要⼀次读⼊ 2 个字符,读 16 次,要保证读出的内容符合 struct in6_addr 的定义;
⼀次从⽂件中读⼊ 4 个字符(16 位),读 8 次,和⼀次读⼊ 2 个字符读 16 次有什么不同呢?我们以 16 进制的f8e9 为例
当我们每次读⼊ 2 个字符,读 2 次时,在内存中的排列是这样的
unsigned char_ipv6[16];
fscanf(f, "%2hhx2hhx", &_ipv6[0], &_ipv6[1]);
unsigned char*p=_ipv6
f8e9
-+-+
||
|+-------p+1
+-----------p
周星驰朱茵再次相见当我们每次读⼊ 4 个字符,读 1 次时,在内存中的排列是这样的
unsigned int_ipv6[8]
fscanf(f, "%4x", &_ipv6[0])
unsigned char*p= (unsigned char*)_ipv6
e9f8
-+-+
||
|+-------p+1
+-----------p
这是因为 X86 系列 CPU 的存储模式是⼩端模式,也就是⾼位字节要存放在⾼地址上,f8e9 这个数,f8 是⾼位字节,e9 是低位字节,所以当我们把 f8e9 作为⼀个整数读出的时候,e9 将存储在低地址,f8 存储在⾼地址,这和 struct in6_addr 的定义是不相符的;
所以如果我们⼀次读 4 个字符,读 8 次,我们就不能使⽤ inet_ntop() 去把 ipv6 地址转换成我们所需要的字符串,当然我们可以⾃⼰转换,但有些⿇烦,参考下⾯代码
unsigned short int_ipv6[8];
int zero_flag=0;
while (11==fscanf(f, " %4hx%4hx%4hx%4hx%4hx%4hx%4hx%4hx \清朝官服
%*x %x %x %*x %s",
&_ipv6[0], &_ipv6[1], &_ipv6[2], &_ipv6[3],
&_ipv6[4], &_ipv6[5], &_ipv6[6], &_ipv6[7],
&prefix, &scope, dname)) {
printf("%s: ", dname);
for (int i=0; i<8; ++i) {
if (_ipv6[i] !=0) {
if (i) putc(':', stdout);
printf("%x", _ipv6[i]);
zero_flag=0;
} else {
if (!zero_flag) putc(':', stdout);
zero_flag=1;
}
}
putc('\n', stdout);
}
和上⾯的代码⽐较,多了不少⿇烦,⾃⼰去体会吧。
3. 使⽤getifaddrs()获取 ipv6 地址
可以通过在线⼿册 man getifaddrs 了解详细的关于 getifaddrs 函数的信息;
getifaddrs 函数会创建⼀个本地⽹络接⼝的结构链表,该结构链表定义在 struct ifaddrs 中;
关于 ifaddrs 结构有很多⽂章介绍,本⽂仅简单介绍⼀下与本⽂密切相关的内容,下⾯是 struct ifaddrs 的定义
struct ifaddrs {
struct ifaddrs*ifa_next;    /* Next item in list */
char*ifa_name;    /* Name of interface */
unsigned int ifa_flags;  /* Flags from SIOCGIFFLAGS */
struct sockaddr*ifa_addr;    /* Address of interface */
struct sockaddr*ifa_netmask; /* Netmask of interface */
union {
struct sockaddr*ifu_broadaddr;
/* Broadcast address of interface */
struct sockaddr*ifu_dstaddr;
/* Point-to-point destination address */
} ifa_ifu;
#define              ifa_broadaddr ifa_ifu.ifu_broadaddr
#define              ifa_dstaddr  ifa_ifu.ifu_dstaddr
void*ifa_data;    /* Address-specific data */
};
ifa_next 是结构链表的后向指针,指向链表的下⼀项,当前项为最后⼀项时,该指针为 NULL;
ifa_addr 是本⽂主要⽤到的项,这是⼀个 struct sockaddr,看⼀下 struct sockaddr 的定义:
struct sockaddr {
sa_family_t sa_family;
char sa_data[14];九级工伤赔偿标准
}
实际上,当 ifa_addr->sa_family 为 AF_INET 时,ifa_addr 指向 struct sockaddr_in;当 ifa_addr->sa_family 为 AF_INET6 时,ifa_addr 指向⼀个 struct sockaddr_in6;
sockaddr_in 和 sockaddr_in6 这两个结构同样可以到很多介绍⽂章,这⾥就不多说了,反正这⾥⾯是结构套着结构,要把思路捋顺了才不⾄于搞乱;
下⾯是使⽤ getifaddrs() 获取 ipv6 地址的源程序,可以看到,打印 ipv6 地址的那⼏⾏,与上⾯的那个
例⼦是⼀样的;
#include <arpa/inet.h>

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。