瀏覽量:59次
使用通常獲取ipv4的IP地址的方法是無法獲取ipv6地址的,本文介紹了使用C語言獲取ipv6地址的三種方法,每種方法均給出了完整的源程序,本文所有實(shí)例在ubuntu
1. ipv4的IP地址的獲取方法不論是獲取ipv4的IP地址還是ipv6的地址,應(yīng)用程序都需要與內(nèi)核通訊才可以完成;ioctl 是和內(nèi)核通訊的一種常用方法,也是用來獲取ipv4的IP地址的常用方法,下面代碼演示了如何使用ioctl來獲取本機(jī)所有接口的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地址,即便我們建立一個(gè)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;}}這段程序在我的機(jī)器上的運(yùn)行結(jié)果是這樣的:圖1:ioctl無法獲取ipv6地址
我們看到,不管怎么折騰,返回的仍然只有ipv4的地址,所以我們需要一些其他的方法獲得ipv6地址,下面介紹三種使用C語言獲得ipv6地址的方法。2. 從文件/proc/net/if_inet6中獲取ipv6地址我們先來看看文件/proc/net/if_inet6中有什么內(nèi)容:圖2:文件/proc/net/if_inet6內(nèi)容
這個(gè)文件中,每行為一個(gè)網(wǎng)絡(luò)接口的數(shù)據(jù),每行數(shù)據(jù)分成 6 個(gè)字段序號
字段名稱
字段說明
1
ipv6address
ipv6地址,16位(4個(gè)字符)一組,16進(jìn)制,中間沒有分隔符
2
ifindex
接口設(shè)備號,每個(gè)設(shè)備不同
3
prefixlen
前綴長度,類似子網(wǎng)掩碼
4
scopeid
scope id
5
flags
接口標(biāo)志,標(biāo)識這個(gè)接口的特性
6
devname
接口設(shè)備名稱
所以從這個(gè)文件中可以很容易地獲得所有接口的 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]指向一個(gè)unsigned char *,2表示從文件中讀取的長度,這個(gè)是常用的;關(guān)于fscanf中的hh和h的用法,可以查看在線手冊man fscanf了解更多的內(nèi)容;ipv6地址一共128位,16位一組,一共8組,但是這里為什么不一次從文件中讀入4個(gè)字符(16 位),讀8次,而要一次讀入2個(gè)字符讀16次呢?這個(gè)要去看inet_ntop的參數(shù),我們先使用命令man inet_ntop看一下inet_ntop的在線手冊:
constchar*inet_ntop(intaf,constvoid*src,char*dst,socklen_tsize);當(dāng)?shù)?個(gè)參數(shù)af=AF_INET6時(shí),對于第2個(gè)參數(shù),還有說明:
AF_INET6srcpointstoastructin6_addr(innetworkbyteorder).很顯然,需要第2個(gè)參數(shù)指向一個(gè)struct in6_addr,這個(gè)結(jié)構(gòu)在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};這個(gè)結(jié)構(gòu)在一般情況下使用的是uint8_t __u6_addr8[16],也就是16個(gè)unsigned char的數(shù)組,只有在"混雜模式"時(shí)才使用8個(gè)unsigned short int或者4個(gè)unsigned int的數(shù)組;
所以,實(shí)際上struct in6_addr的結(jié)構(gòu)如下:
structin6_addr{unsignedchar__u6_addr8[16];}這就是我們在讀文件時(shí)為什么要一次讀入2個(gè)字符,讀16次,要保證讀出的內(nèi)容符合struct in6_addr的定義;
一次從文件中讀入4個(gè)字符(16位),讀8次,和一次讀入2個(gè)字符讀16次有什么不同呢?我們以16進(jìn)制的f8e9為例;當(dāng)我們每次讀入 2 個(gè)字符,讀 2 次時(shí),在內(nèi)存中的排列是這樣的:
unsignedchar_ipv6[16];fscanf(f,"%2hhx2hhx",&_ipv6[0],&_ipv6[1]);unsignedchar*p=_ipv6f8e9--|||-------p1-----------p當(dāng)我們每次讀入 4 個(gè)字符,讀 1 次時(shí),在內(nèi)存中的排列是這樣的:
unsignedint_ipv6[8]fscanf(f,"%4x",&_ipv6[0])unsignedchar*p=(unsignedchar*)_ipv6e9f8--|||-------p1-----------p這是因?yàn)閄86系列CPU的存儲模式是小端模式,也就是高位字節(jié)要存放在高地址上,f8e9這個(gè)數(shù),f8是高位字節(jié),e9是低位字節(jié),所以當(dāng)我們把f8e9作為一個(gè)整數(shù)讀出的時(shí)候,e9 將存儲在低地址,f8存儲在高地址,這和struct in6_addr的定義是不相符的;
所以如果我們一次讀4個(gè)字符,讀8次,我們就不能使用inet_ntop()去把ipv6地址轉(zhuǎn)換成我們所需要的字符串,當(dāng)然我們可以自己轉(zhuǎn)換,但有些麻煩,參考下面代碼:
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);}和上面的代碼比較,多了不少麻煩,自己去體會(huì)吧。
3. 使用getifaddrs()獲取 ipv6 地址可以通過在線手冊man getifaddrs了解詳細(xì)的關(guān)于getifaddrs函數(shù)的信息;getifaddrs函數(shù)會(huì)創(chuàng)建一個(gè)本地網(wǎng)絡(luò)接口的結(jié)構(gòu)鏈表,該結(jié)構(gòu)鏈表定義在struct ifaddrs中;關(guān)于ifaddrs結(jié)構(gòu)有很多文章介紹,本文僅簡單介紹一下與本文密切相關(guān)的內(nèi)容,下面是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是結(jié)構(gòu)鏈表的后向指針,指向鏈表的下一項(xiàng),當(dāng)前項(xiàng)為最后一項(xiàng)時(shí),該指針為NULL;ifa_addr是本文主要用到的項(xiàng),這是一個(gè)struct sockaddr, 看一下struct sockaddr的定義:structsockaddr{sa_family_tsa_family;charsa_data[14];}實(shí)際上,當(dāng)ifa_addr->sa_family為AF_INET時(shí),ifa_addr指向struct sockaddr_in;當(dāng)ifa_addr->sa_family為AF_INET6時(shí),ifa_addr指向一個(gè)struct sockaddr_in6;sockaddr_in和sockaddr_in6這兩個(gè)結(jié)構(gòu)同樣可以找到很多介紹文章,這里就不多說了,反正這里面是結(jié)構(gòu)套著結(jié)構(gòu),要把思路捋順了才不至于搞亂;下面是使用getifaddrs()獲取ipv6地址的源程序,可以看到,打印ipv6地址的那幾行,與上面的那個(gè)例子是一樣的;#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()釋放掉鏈表所占用的內(nèi)存。這個(gè)例子中,我們使用inet_ntop()將sin6_addr結(jié)構(gòu)轉(zhuǎn)換成了字符串形式的ipv6地址,還可以使用getnameinfo()來獲取ipv6的字符串形式的地址;可以通過在線手冊man getnameinfo了解getnameinfo()的詳細(xì)信息;下面是使用getifaddrs()獲取ipv6地址并使用getnameinfo()將將ipv6地址轉(zhuǎn)變?yōu)樽址脑闯绦颍?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;}和前面那個(gè)程序相比,,這里面有g(shù)etnameinfo()的一些相關(guān)定義;在這里使用函數(shù)getnameinfo時(shí),要明確ifa->ifa_addr指向的是一個(gè)struct sockaddr_in6,后面的常數(shù)NI_NUMERICHOST表示返回的主機(jī)地址為數(shù)字字符串;和上面的例子略有不同的是,使用getnameinfo獲取的ipv6地址的最后會(huì)使用‘%’連接一個(gè)網(wǎng)絡(luò)接口的名稱,如下圖所示:圖3:使用getnameinfo獲取ipv6地址
4. 使用 netlink 獲取 ipv6 地址netlink socket是用戶空間與內(nèi)核空間通信的又一種方法,本文并不討論netlink的編程方法,但給出了使用netlink獲取ipv6地址的源程序;與上面兩個(gè)方法比較,使用netlink獲取ipv6地址的方法略顯復(fù)雜,在實(shí)際應(yīng)用中并不多見,所以本文也就不進(jìn)行更多的討論了;下面是使用 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. 結(jié)語本文給出了三種獲取ipv6地址的方法,均給出了完整的源程序;本文對三種方法并沒有展開討論,以免文章冗長;僅就獲取ipv6地址而言,前兩種方法比較常用而且簡單;通常認(rèn)為,用戶程序與內(nèi)核通訊有四種方法:系統(tǒng)調(diào)用虛擬文件系統(tǒng)(/proc、/sys等)ioctlnetlink本文所述的三個(gè)方法,正是使用了上述2、3、4三種方法;而獲取ipv6地址,簡單地使用系統(tǒng)調(diào)用無法實(shí)現(xiàn)。(歡迎訪問我的博客:)
[聲明]本網(wǎng)轉(zhuǎn)載網(wǎng)絡(luò)媒體稿件是為了傳播更多的信息,此類稿件不代表本網(wǎng)觀點(diǎn),本網(wǎng)不承擔(dān)此類稿件侵權(quán)行為的連帶責(zé)任。故此,如果您發(fā)現(xiàn)本網(wǎng)站的內(nèi)容侵犯了您的版權(quán),請您的相關(guān)內(nèi)容發(fā)至此郵箱【779898168@qq.com】,我們在確認(rèn)后,會(huì)立即刪除,保證您的版權(quán)。
官網(wǎng)優(yōu)化
整站優(yōu)化
渠道代理
400-655-5776