日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

C语言如何获取ipv6地址

發布時間:2024/3/13 编程问答 36 豆豆
生活随笔 收集整理的這篇文章主要介紹了 C语言如何获取ipv6地址 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

使用通常獲取ipv4的IP地址的方法是無法獲取ipv6地址的,本文介紹了使用C語言獲取ipv6地址的三種方法:從proc文件從系統獲取ipv6地址、使用getifaddrs()函數獲取ipv6地址和使用netlink獲取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>#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[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++;} }
  • 這段程序在我的機器上的運行結果是這樣的:

    圖1:ioctl無法獲取ipv6地址

  • 我們看到,不管怎么折騰,返回的仍然只有 ipv4 的地址,所以我們需要一些其他的方法獲得 ipv6 地址,下面介紹三種使用 C 語言獲得 ipv6 地址的方法。

2. 從文件/proc/net/if_inet6中獲取ipv6地址

  • 我們先來看看文件/proc/net/if_inet6中有什么內容

    圖2:文件/proc/net/if_inet6內容

  • 這個文件中,每行為一個網絡接口的數據,每行數據分成 6 個字段

    字段序號字段名稱字段說明
    1ipv6addressIPv6地址,32位16進制一組,中間沒有:分隔符
    2ifindex接口設備號,每個設備都不同,按 16 進制顯示
    3prefixlen16進制顯示的前綴長度,類似 ipv4 的子網掩碼的東西
    4scopeidscope 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);return 0; }
  • 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_INET6src points to a struct in6_addr (in network byte order) which is converted to a representation ofthis address in the most appropriate IPv6 network address format for this address. The buffer dstmust 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 的數組;
    • 所以,實際上 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 f8 e9 -+ -+| || +------- p + 1+----------- p
    • 當我們每次讀入 4 個字符,讀 1 次時,在內存中的排列是這樣的unsigned int _ipv6[8] fscanf(f, "%4x", &_ipv6[0]) unsigned char *p = (unsigned char *)_ipv6 e9 f8 -+ -+| || +------- 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_dstaddrvoid *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> #include <ifaddrs.h> #include <stdio.h> #include <stdlib.h>int main () {struct ifaddrs *ifap, *ifa;struct sockaddr_in6 *sa;char addr[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 = (struct sockaddr_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);return 0; }
  • 最后要注意的是,使用 getifaddrs() 后,一定要記得使用 freeifaddrs() 釋放掉鏈表所占用的內存。

  • 這個例子中,我們使用 inet_ntop() 將 sin6_addr 結構轉換成了字符串形式的 ipv6 地址,還可以使用 getnameinfo() 來獲取 ipv6 的字符串形式的地址;

  • 可以通過在線手冊 man getnameinfo 了解 getnameinfo() 的詳細信息

  • 下面是使用 getifaddrs() 獲取 ipv6 地址并使用 getnameinfo() 將將 ipv6 地址轉變為字符串的源程序

    #include <arpa/inet.h> #include <ifaddrs.h> #include <stdio.h> #include <stdlib.h>#include <netdb.h>int main () {struct ifaddrs *ifap, *ifa;char addr[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(struct sockaddr_in6), addr, sizeof(addr), NULL, 0, NI_NUMERICHOST))continue;printf("%s: %s\n", ifa->ifa_name, addr);}}freeifaddrs(ifap);return 0; }
  • 和前面那個程序相比,這個程序增加了一個包含文件 netdb.h,這里面有 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 <asm/types.h> #include <arpa/inet.h> #include <linux/netlink.h> #include <linux/rtnetlink.h> #include <sys/socket.h> #include <string.h> #include <stdio.h>int main(int argc, char ** argv) {char buf1[16384], buf2[16384];struct {struct nlmsghdr nlhdr;struct ifaddrmsg addrmsg;} msg1;struct {struct nlmsghdr nlhdr;struct ifinfomsg infomsg;} msg2;struct nlmsghdr *retmsg1;struct nlmsghdr *retmsg2;int len1, len2;struct rtattr *retrta1, *retrta2;int attlen1, attlen2;char pradd[128], prname[128];int sock = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);memset(&msg1, 0, sizeof(msg1));msg1.nlhdr.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifaddrmsg));msg1.nlhdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_ROOT;msg1.nlhdr.nlmsg_type = RTM_GETADDR;msg1.addrmsg.ifa_family = AF_INET6;memset(&msg2, 0, sizeof(msg2));msg2.nlhdr.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg));msg2.nlhdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_ROOT;msg2.nlhdr.nlmsg_type = RTM_GETLINK;msg2.infomsg.ifi_family = AF_UNSPEC;send(sock, &msg1, msg1.nlhdr.nlmsg_len, 0);len1 = recv(sock, buf1, sizeof(buf1), 0);retmsg1 = (struct nlmsghdr *)buf1;while NLMSG_OK(retmsg1, len1) {struct ifaddrmsg *retaddr;retaddr = (struct ifaddrmsg *)NLMSG_DATA(retmsg1);int iface_idx = retaddr->ifa_index;retrta1 = (struct rtattr *)IFA_RTA(retaddr);attlen1 = IFA_PAYLOAD(retmsg1);while RTA_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, msg2.nlhdr.nlmsg_len, 0);len2 = recv(sock, buf2, sizeof(buf2), 0);retmsg2 = (struct nlmsghdr *)buf2;while NLMSG_OK(retmsg2, len2) {struct ifinfomsg *retinfo;retinfo = NLMSG_DATA(retmsg2);memset(prname, 0, sizeof(prname));if (retinfo->ifi_index == iface_idx) {retrta2 = IFLA_RTA(retinfo);attlen2 = IFLA_PAYLOAD(retmsg2);while RTA_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); }return 0; }

5. 結語

  • 本文給出了三種獲取 ipv6 地址的方法,均給出了完整的源程序;
  • 本文對三種方法并沒有展開討論,以免文章冗長;
  • 僅就獲取 ipv6 地址而言,前兩種方法比較常用而且簡單;
  • 通常認為,用戶程序與內核通訊有四種方法
  • 系統調用
  • 虛擬文件系統(/proc、/sys等)
  • ioctl
  • netlink
  • 本文所述的三個方法,正是使用了上述 2、3、4 三種方法;而獲取 ipv6 地址,簡單地使用系統調用無法實現。

email: hengch@163.com

總結

以上是生活随笔為你收集整理的C语言如何获取ipv6地址的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。