子网前缀长度在哪里看,子网划分的正确方法

首页 > 教育 > 作者:YD1662023-04-20 03:58:19

使用通常获取ipv4的IP地址的方法是无法获取IPv6地址的,本文介绍了使用C语言获取ipv6地址的三种方法,每种方法均给出了完整的源程序,本文所有实例在ubuntu 20.04下测试通过,gcc版本9.4.0。

1. ipv4的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 ; } return 0; }

#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)

图1:ioctl无法获取ipv6地址

2. 从文件/proc/net/if_inet6中获取ipv6地址

子网前缀长度在哪里看,子网划分的正确方法(2)

图2:文件/proc/net/if_inet6内容

序号

字段名称

字段说明

1

ipv6address

ipv6地址,16位(4个字符)一组,16进制,中间没有分隔符

2

ifindex

接口设备号,每个设备不同

3

prefixlen

前缀长度,类似子网掩码

4

scopeid

scope id

5

flags

接口标志,标识这个接口的特性

6

devname

接口设备名称

#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; }

这个要去看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的定义;

当我们每次读入 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 地址

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 */ };

struct sockaddr { sa_family_t sa_family; char sa_data[14]; }

#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; }

#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; }

子网前缀长度在哪里看,子网划分的正确方法(3)

图3:使用getnameinfo获取ipv6地址

4. 使用 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. 结语

  1. 系统调用
  2. 虚拟文件系统(/proc、/sys等)
  3. ioctl
  4. netlink

欢迎访问我的博客:https://whowin.cn

栏目热文

文档排行

本站推荐

Copyright © 2018 - 2021 www.yd166.com., All Rights Reserved.