浏览量:102次
使用通常获取ipv4的IP地址的方法是无法获取ipv6地址的,本文介绍了使用C语言获取ipv6地址的三种方法,每种方法均给出了完整的源程序,本文所有实例在ubuntu
1. ipv4的IP地址的获取方法不论是获取ipv4的IP地址还是ipv6的地址,应用程序都需要与内核通讯才可以完成;ioctl 是和内核通讯的一种常用方法,也是用来获取ipv4的IP地址的常用方法,下面代码演示了如何使用ioctl来获取本机所有接口的IP地址:#include#include#include#include#include#includeintmain(){inti=0;intsockfd;structifconfifc;charbuf[512]={0};structifreq*ifr;=512;=buf;if((sockfd=socket(AF_INET,SOCK_DGRAM,0))<0){perror("socket");return-1;}ioctl(sockfd,SIOCGIFCONF,&ifc);ifr=(structifreq*)buf;for(i=((structifreq));i>0;i--){printf("%s:%s\n",ifr->ifr_name,inet_ntoa(((structsockaddr_in*)&(ifr->ifr_addr))->sin_addr));ifr;}return0;}但是使用ioctl无法获取ipv6地址,即便我们建立一个AF_INET6的socket,ioctl仍然只返回ipv4的信息,我们可以试试下面代码;#include#include#include#include#include#includeintmain(){inti=0;intsockfd;structifconfifc;charbuf[1024]={0};structifreq*ifr;=1024;=buf;if((sockfd=socket(AF_INET6,SOCK_DGRAM,0))<0){perror("socket");return-1;}ioctl(sockfd,SIOCGIFCONF,&ifc);ifr=(structifreq*)buf;structsockaddr_in*sa;for(i=((structifreq));i>0;i--){sa=(structsockaddr_in*)&(ifr->ifr_addr);if(sa->sin_family==AF_INET6){printf("%s:AF_INET6\n",ifr->ifr_name);}elseif(sa->sin_family==AF_INET){printf("%s:AF_INET\n",ifr->ifr_name);}else{printf("%s:%.\n",ifr->ifr_name,sa->sin_family);}ifr;}}这段程序在我的机器上的运行结果是这样的:图1:ioctl无法获取ipv6地址
我们看到,不管怎么折腾,返回的仍然只有ipv4的地址,所以我们需要一些其他的方法获得ipv6地址,下面介绍三种使用C语言获得ipv6地址的方法。2. 从文件/proc/net/if_inet6中获取ipv6地址我们先来看看文件/proc/net/if_inet6中有什么内容:图2:文件/proc/net/if_inet6内容
这个文件中,每行为一个网络接口的数据,每行数据分成 6 个字段序号
字段名称
字段说明
1
ipv6address
ipv6地址,16位(4个字符)一组,16进制,中间没有分隔符
2
ifindex
接口设备号,每个设备不同
3
prefixlen
前缀长度,类似子网掩码
4
scopeid
scope id
5
flags
接口标志,标识这个接口的特性
6
devname
接口设备名称
所以从这个文件中可以很容易地获得所有接口的 ipv6 地址:#include#include#include#includeintmain(void){FILE*f;intscope,prefix;unsignedchar_ipv6[16];chardname[IFNAMSIZ];charaddress[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的在线手册:
constchar*inet_ntop(intaf,constvoid*src,char*dst,socklen_tsize);当第1个参数af=AF_INET6时,对于第2个参数,还有说明:
AF_INET6srcpointstoastructin6_addr(innetworkbyteorder).很显然,需要第2个参数指向一个struct in6_addr,这个结构在netinet/:
/*IPv6address*/structin6_addr{union{uint8_t__u6_addr8[16];uint16_t__u6_addr16[8];uint32_t__u6_addr32[4];}__in6_u;##ifdef__USE_MISC###endif};这个结构在一般情况下使用的是uint8_t __u6_addr8[16],也就是16个unsigned char的数组,只有在"混杂模式"时才使用8个unsigned short int或者4个unsigned int的数组;
所以,实际上struct in6_addr的结构如下:
structin6_addr{unsignedchar__u6_addr8[16];}这就是我们在读文件时为什么要一次读入2个字符,读16次,要保证读出的内容符合struct in6_addr的定义;
一次从文件中读入4个字符(16位),读8次,和一次读入2个字符读16次有什么不同呢?我们以16进制的f8e9为例;当我们每次读入 2 个字符,读 2 次时,在内存中的排列是这样的:
unsignedchar_ipv6[16];fscanf(f,"%2hhx2hhx",&_ipv6[0],&_ipv6[1]);unsignedchar*p=_ipv6f8e9--|||-------p1-----------p当我们每次读入 4 个字符,读 1 次时,在内存中的排列是这样的:
unsignedint_ipv6[8]fscanf(f,"%4x",&_ipv6[0])unsignedchar*p=(unsignedchar*)_ipv6e9f8--|||-------p1-----------p这是因为X86系列CPU的存储模式是小端模式,也就是高位字节要存放在高地址上,f8e9这个数,f8是高位字节,e9是低位字节,所以当我们把f8e9作为一个整数读出的时候,e9 将存储在低地址,f8存储在高地址,这和struct in6_addr的定义是不相符的;
所以如果我们一次读4个字符,读8次,我们就不能使用inet_ntop()去把ipv6地址转换成我们所需要的字符串,当然我们可以自己转换,但有些麻烦,参考下面代码:
unsignedshortint_ipv6[8];intzero_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(inti=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的定义:structifaddrs{structifaddrs*ifa_next;/*Nextiteminlist*/char*ifa_name;/*Nameofinterface*/unsignedintifa_flags;/*FlagsfromSIOCGIFFLAGS*/structsockaddr*ifa_addr;/*Addressofinterface*/structsockaddr*ifa_netmask;/*Netmaskofinterface*/union{structsockaddr*ifu_broadaddr;/*Broadcastaddressofinterface*/structsockaddr*ifu_dstaddr;/*Point-to-pointdestinationaddress*/}ifa_ifu;##*ifa_data;/*Address-specificdata*/};ifa_next是结构链表的后向指针,指向链表的下一项,当前项为最后一项时,该指针为NULL;ifa_addr是本文主要用到的项,这是一个struct sockaddr, 看一下struct sockaddr的定义:structsockaddr{sa_family_tsa_family;charsa_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#include#include#includeintmain(){structifaddrs*ifap,*ifa;structsockaddr_in6*sa;charaddr[INET6_ADDRSTRLEN];if(getifaddrs(&ifap)==-1){perror("getifaddrs");exit(1);}for(ifa=ifap;ifa;ifa=ifa->ifa_next){if(ifa->ifa_addr&&ifa->ifa_addr->sa_family==AF_INET6){//打印ipv6地址sa=(structsockaddr_in6*)ifa->ifa_addr;if(inet_ntop(AF_INET6,(void*)&sa->sin6_addr,addr,INET6_ADDRSTRLEN)==NULL)continue;printf("%s:%s\n",ifa->ifa_name,addr);}}freeifaddrs(ifap);return0;}最后要注意的是,使用getifaddrs()后,一定要记得使用freeifaddrs()释放掉链表所占用的内存。这个例子中,我们使用inet_ntop()将sin6_addr结构转换成了字符串形式的ipv6地址,还可以使用getnameinfo()来获取ipv6的字符串形式的地址;可以通过在线手册man getnameinfo了解getnameinfo()的详细信息;下面是使用getifaddrs()获取ipv6地址并使用getnameinfo()将将ipv6地址转变为字符串的源程序:#include#include#include#include#includeintmain(){structifaddrs*ifap,*ifa;charaddr[INET6_ADDRSTRLEN];if(getifaddrs(&ifap)==-1){perror("getifaddrs");exit(1);}for(ifa=ifap;ifa;ifa=ifa->ifa_next){if(ifa->ifa_addr&&ifa->ifa_addr->sa_family==AF_INET6){//打印ipv6地址if(getnameinfo(ifa->ifa_addr,sizeof(structsockaddr_in6),addr,sizeof(addr),NULL,0,NI_NUMERICHOST))continue;printf("%s:%s\n",ifa->ifa_name,addr);}}freeifaddrs(ifap);return0;}和前面那个程序相比,,这里面有getnameinfo()的一些相关定义;在这里使用函数getnameinfo时,要明确ifa->ifa_addr指向的是一个struct sockaddr_in6,后面的常数NI_NUMERICHOST表示返回的主机地址为数字字符串;和上面的例子略有不同的是,使用getnameinfo获取的ipv6地址的最后会使用‘%’连接一个网络接口的名称,如下图所示:图3:使用getnameinfo获取ipv6地址
4. 使用 netlink 获取 ipv6 地址netlink socket是用户空间与内核空间通信的又一种方法,本文并不讨论netlink的编程方法,但给出了使用netlink获取ipv6地址的源程序;与上面两个方法比较,使用netlink获取ipv6地址的方法略显复杂,在实际应用中并不多见,所以本文也就不进行更多的讨论了;下面是使用 netlink 获取 ipv6 地址的源程序:#include#include#include#include#include#include#includeintmain(intargc,char**argv){charbuf1[16384],buf2[16384];struct{structnlmsghdrnlhdr;structifaddrmsgaddrmsg;}msg1;struct{structnlmsghdrnlhdr;structifinfomsginfomsg;}msg2;structnlmsghdr*retmsg1;structnlmsghdr*retmsg2;intlen1,len2;structrtattr*retrta1,*retrta2;intattlen1,attlen2;charpradd[128],prname[128];intsock=socket(AF_NETLINK,SOCK_RAW,NETLINK_ROUTE);memset(&msg1,0,sizeof(msg1));=NLMSG_LENGTH(sizeof(structifaddrmsg));=NLM_F_REQUEST|NLM_F_ROOT;=RTM_GETADDR;=AF_INET6;memset(&msg2,0,sizeof(msg2));=NLMSG_LENGTH(sizeof(structifinfomsg));=NLM_F_REQUEST|NLM_F_ROOT;=RTM_GETLINK;=AF_UNSPEC;send(sock,&msg1,,0);len1=recv(sock,buf1,sizeof(buf1),0);retmsg1=(structnlmsghdr*)buf1;whileNLMSG_OK(retmsg1,len1){structifaddrmsg*retaddr;retaddr=(structifaddrmsg*)NLMSG_DATA(retmsg1);intiface_idx=retaddr->ifa_index;retrta1=(structrtattr*)IFA_RTA(retaddr);attlen1=IFA_PAYLOAD(retmsg1);whileRTA_OK(retrta1,attlen1){if(retrta1->rta_type==IFA_ADDRESS){inet_ntop(AF_INET6,RTA_DATA(retrta1),pradd,sizeof(pradd));len2=recv(sock,buf2,sizeof(buf2),0);send(sock,&msg2,,0);len2=recv(sock,buf2,sizeof(buf2),0);retmsg2=(structnlmsghdr*)buf2;whileNLMSG_OK(retmsg2,len2){structifinfomsg*retinfo;retinfo=NLMSG_DATA(retmsg2);memset(prname,0,sizeof(prname));if(retinfo->ifi_index==iface_idx){retrta2=IFLA_RTA(retinfo);attlen2=IFLA_PAYLOAD(retmsg2);whileRTA_OK(retrta2,attlen2){if(retrta2->rta_type==IFLA_IFNAME){strcpy(prname,RTA_DATA(retrta2));break;}retrta2=RTA_NEXT(retrta2,attlen2);}break;}retmsg2=NLMSG_NEXT(retmsg2,len2);}printf("%s:%s\n",prname,pradd);}retrta1=RTA_NEXT(retrta1,attlen1);}retmsg1=NLMSG_NEXT(retmsg1,len1);}return0;}5. 结语本文给出了三种获取ipv6地址的方法,均给出了完整的源程序;本文对三种方法并没有展开讨论,以免文章冗长;仅就获取ipv6地址而言,前两种方法比较常用而且简单;通常认为,用户程序与内核通讯有四种方法:系统调用虚拟文件系统(/proc、/sys等)ioctlnetlink本文所述的三个方法,正是使用了上述2、3、4三种方法;而获取ipv6地址,简单地使用系统调用无法实现。(欢迎访问我的博客:)
[声明]本网转载网络媒体稿件是为了传播更多的信息,此类稿件不代表本网观点,本网不承担此类稿件侵权行为的连带责任。故此,如果您发现本网站的内容侵犯了您的版权,请您的相关内容发至此邮箱【779898168@qq.com】,我们在确认后,会立即删除,保证您的版权。