使用ioctl的SIOCGIFCONF可以读取所有网卡信息。ioctl调用后返回指向ifconf的结构链表,其中包含了指向ifreq的结构指针。ifconf及ifreq定义在net/if.h中。
《UNIX网络编程》中提供了get_ifi_info函数的实现方法,使用这种方式来获取网络信息。在LINUX下,这种方式不能获得IPV6的网卡信息。《UNIX网络编程》中有如下描述:
在支持IPV6的系统中,没有关于对SIOCGIFCONF请求是否返回IPV6地址的标准。我们给支持IPV6的新系统增加了一个case语句, 这是为了预防万一。问题在于ifreq中的联合把返回的地址定义成一个通用的16字节套接口地址结构,适合16字节的IPV4 socket_in结构,但对于24字节的IPV6 socket_in6结构太小了。如果返回IPV6地址,将可能破环现有的在每个ifreq结构中采用固定大小的套接口地址结构的代码。
经测试,在fedor6-2.6.18kernel中无法返回ipv6地址,事实上,返回的地址簇总是AF_INET,而并非AF_INET6。
这种方法的实现代码如下:
net_if.h
#ifndef __NET_INF_H #define __NET_INF_H #include #include #include #include #include #include #include #include #include #include #include #define IFI_NAME 16 #define IFI_HADDR 8 typedef struct ifi_info { char ifi_name[IFI_NAME]; u_char ifi_haddr[IFI_HADDR]; u_short ifi_hlen; short ifi_flags; short ifi_myflags; struct sockaddr *ifi_addr; struct sockaddr *ifi_brdaddr; struct sockaddr *ifi_dstaddr; struct ifi_info *ifi_next; }ifi_info; #define IFI_ALIAS 1 struct ifi_info *get_ifi_info(int, int); void free_ifi_info(struct ifi_info *); #endif
net_if.c
#include "net_if.h" ifi_info *get_ifi_info(int family, int doaliases) { ifi_info *ifi, *ifihead, **ifipnext; int sockfd, len, lastlen, flags, myflags; char *ptr, *buf, lastname[IFNAMSIZ], *cptr; struct ifconf ifc; struct ifreq *ifr, ifrcopy; struct sockaddr_in *sinptr; if ((sockfd=socket(family, SOCK_DGRAM, 0))<0) { printf("socket error.\n"); exit(1); } lastlen = 0; len = 10*sizeof(struct ifreq); while (1) { buf = (char*)malloc(len); ifc.ifc_len = len; ifc.ifc_buf = buf; if (ioctl(sockfd, SIOCGIFCONF, &ifc)<0) { if (errno!=EINVAL||lastlen!=0) { printf("ioctl error.\n"); } } else { if (ifc.ifc_len == lastlen) break; lastlen = ifc.ifc_len; } len += 10*sizeof(struct ifreq); free(buf); } ifihead = NULL; ifipnext = &ifihead; lastname[0] = 0; for (ptr = buf; ptrifr->ifr_addr.sa_len?sizeof(struct sockaddr):ifr->ifr_addr.sa_len; #else switch (ifr->ifr_addr.sa_family) { #ifdef IPV6 case AF_INET6: len = sizeof(struct sockaddr_in6); break; #endif case AF_INET: default: len = sizeof(struct sockaddr); break; } #endif ptr += sizeof(ifr->ifr_name) + len; if (ifr->ifr_addr.sa_family != family) continue; myflags = 0; if ((cptr=strchr(ifr->ifr_name, ':'))!=NULL) *cptr = 0; if (strncmp(lastname, ifr->ifr_name, IFNAMSIZ)==0) { if (doaliases == 0) continue; myflags = IFI_ALIAS; } memcpy(lastname, ifr->ifr_name, IFNAMSIZ); ifrcopy = *ifr; ioctl(sockfd, SIOCGIFFLAGS, &ifrcopy); flags = ifrcopy.ifr_flags; if ((flags&IFF_UP)==0) continue; /* if ((flags&IFF_BROADCAST)==0) continue; */ ifi = calloc(1, sizeof(struct ifi_info)); *ifipnext = ifi; ifipnext = &ifi->ifi_next; ifi->ifi_flags = flags; ifi->ifi_myflags = myflags; memcpy(ifi->ifi_name, ifr->ifr_name, IFI_NAME); ifi->ifi_name[IFI_NAME-1] = '\0'; switch (ifr->ifr_addr.sa_family) { case AF_INET: sinptr = (struct sockaddr_in *)&ifr->ifr_addr; if (ifi->ifi_addr == NULL) { ifi->ifi_addr = calloc(1, sizeof(struct sockaddr_in)); memcpy(ifi->ifi_addr, sinptr, sizeof(struct sockaddr_in)); #ifdef SIOCGIFBRDADDR if (flags & IFF_BROADCAST) { ioctl(sockfd, SIOCGIFBRDADDR, &ifrcopy); sinptr = (struct sockaddr_in *)&ifrcopy.ifr_broadaddr; ifi->ifi_brdaddr = calloc(1, sizeof(struct sockaddr_in)); memcpy(ifi->ifi_brdaddr, sinptr, sizeof(struct sockaddr_in)); } #endif #ifdef SIOCGIFDSTADDR if (flags & IFF_POINTOPOINT) { ioctl(sockfd, SIOCGIFDSTADDR, &ifrcopy); sinptr = (struct sockaddr_in*)&ifrcopy.ifr_dstaddr; ifi->ifi_dstaddr = calloc(1, sizeof(struct sockaddr_in)); memcpy(ifi->ifi_dstaddr, sinptr, sizeof(struct sockaddr_in)); } #endif } break; default: break; } } free(buf); return(ifihead); } void free_ifi_info(ifi_info *ifihead) { ifi_info *ifi, *ifinext; for (ifi=ifihead; ifi!=NULL; ifi=ifinext) { if (ifi->ifi_addr!=NULL) free(ifi->ifi_addr); if (ifi->ifi_brdaddr!=NULL) free(ifi->ifi_brdaddr); if (ifi->ifi_dstaddr!=NULL) free(ifi->ifi_dstaddr); ifinext = ifi->ifi_next; free(ifi); } } char *sock_ntop(const struct sockaddr *sa, socklen_t salen) { char portstr[7]; static char str[128]; switch (sa->sa_family) { case AF_INET: { struct sockaddr_in *sin = (struct sockaddr_in *)sa; if (inet_ntop(AF_INET, &sin->sin_addr, str, sizeof(str))==NULL) return NULL; if (ntohs(sin->sin_port)!=0) { snprintf(portstr, sizeof(portstr), ".%d", ntohs(sin->sin_port)); strcat(str, portstr); } return str; } break; case AF_INET6: { struct sockaddr_in6 *sin = (struct sockaddr_in6 *)sa; if (inet_ntop(AF_INET6, &sin->sin6_addr, str, sizeof(str))==NULL) return NULL; if (ntohs(sin->sin6_port)!=0) { snprintf(portstr, sizeof(portstr), ".%d", ntohs(sin->sin6_port)); strcat(str, portstr); } return str; } break; default: return NULL; break; } } int main(int argc, char *argv[]) { ifi_info *ifi, *ifihead; struct sockaddr *sa; u_char *ptr; int i, family, doaliases; if (argc!=3) { printf("usage: ./prifinfo "); exit(1); } if (strcmp(argv[1], "inet4") == 0) family = AF_INET; #ifdef IPV6 else if (strcmp(argv[1], "inet6") == 0) family =AF_INET6; #endif else { printf("invalid "); exit(1); } doaliases = atoi(argv[2]); for(ifihead = ifi = get_ifi_info(family, doaliases); ifi!=NULL;ifi=ifi->ifi_next) { printf("%s:<", ifi->ifi_name); if (ifi->ifi_flags&IFF_UP) printf("UP"); if (ifi->ifi_flags&IFF_BROADCAST) printf("BCAST"); if (ifi->ifi_flags&IFF_MULTICAST) printf("MCAST"); if (ifi->ifi_flags&IFF_LOOPBACK) printf("LOOP"); if (ifi->ifi_flags&IFF_POINTOPOINT) printf("P2P"); printf(">\n"); if ((i=ifi->ifi_hlen)>0) { ptr = ifi->ifi_haddr; do { printf("%s%x", (i==ifi->ifi_hlen)?" ":":", *ptr++); }while(--i>0); printf("\n"); } if ((sa=ifi->ifi_addr)!=NULL) printf(" IP addr: %s\n", sock_ntop(sa, sizeof(*sa))); if ((sa=ifi->ifi_brdaddr)!=NULL) printf(" broadcast addr: %s\n", sock_ntop(sa, sizeof(*sa))); if ((sa=ifi->ifi_dstaddr)!=NULL) printf(" destnation addr: %s\n", sock_ntop(sa, sizeof(*sa))); } free_ifi_info(ifihead); exit(0); }
使用gcc net_if.c -o net_if -DIPV6编译,在IPV4模式下运行输出为:
[root@localhost net_if]./net_if inet4 1
lo:
IP addr: 127.0.0.1
eth1:
IP addr: 192.168.1.2
broadcast addr: 192.168.1.255
eth0:
IP addr: 192.168.125.99
broadcast addr: 192.168.125.255
执行./net_if inet6 1在输出为空。
第二种方式是使用getifaddrs函数获取,需要包含ifaddrs.h头文件,这种方式可以获得IPV6地址,改写的《UNIX网络编程》中的get_ifi_info函数如下所示:
znet.h
#ifndef __ZNET_H__ #define __ZNET_H__ #include #include #include #include #include #include #include #include #define IFI_NAME 16 /* same as IFNAMSIZ in */ #define IFI_HADDR 8 /* allow for 64-bit EUI-64 in future */ struct ifi_info { char ifi_name[IFI_NAME]; /* interface name, null-terminated */ short ifi_index; /* interface index */ short ifi_flags; /* IFF_xxx constants from */ struct sockaddr *ifi_addr; /* primary address */ struct sockaddr *ifi_brdaddr;/* broadcast address */ struct ifi_info *ifi_next; /* next of these structures */ }; struct ifi_info* get_ifi_info(int, int); void free_ifi_info(struct ifi_info *); #endif
znet.c
#include "znet.h" struct ifi_info* get_ifi_info(int family, int doaliases) { struct ifi_info *ifi, *ifihead, **ifipnext,*p; struct sockaddr_in *sinptr; struct sockaddr_in6 *sin6ptr; struct ifaddrs *ifas; // char addr[128]; int sockfd; ifihead = NULL; ifipnext = &ifihead; if(getifaddrs(&ifas)!=0) return ; for(;ifas!=NULL;ifas=(*ifas).ifa_next) { if (((*ifas).ifa_addr)->sa_family != family) continue; // ignore if not desired address family /* printf("%s %d\n",(*ifas).ifa_name,((*ifas).ifa_addr)->sa_family); if(((*ifas).ifa_addr)->sa_family!=AF_INET6) inet_ntop(AF_INET,&(((struct sockaddr_in *)((*ifas).ifa_addr))->sin_addr),addr,sizeof(addr)); else inet_ntop(AF_INET6,&(((struct sockaddr_in6 *)((*ifas).ifa_addr))->sin6_addr),addr,sizeof(addr)); printf("%s\t",addr); printf("\n"); */ ifi = (struct ifi_info*)calloc(1,sizeof(struct ifi_info)); *ifipnext = ifi; ifipnext = &ifi->ifi_next; ifi->ifi_flags = (*ifas).ifa_flags; memcpy(ifi->ifi_name, (*ifas).ifa_name, IFI_NAME); ifi->ifi_name[IFI_NAME-1] = '\0'; switch (((*ifas).ifa_addr)->sa_family) { case AF_INET: sinptr = (struct sockaddr_in *) (*ifas).ifa_addr; ifi->ifi_addr = (struct sockaddr*)calloc(1, sizeof(struct sockaddr_in)); memcpy(ifi->ifi_addr, sinptr, sizeof(struct sockaddr_in)); #ifdef SIOCGIFBRDADDR if (ifi->ifi_flags & IFF_BROADCAST) { sinptr = (struct sockaddr_in *) (*ifas).ifa_broadaddr; ifi->ifi_brdaddr = (struct sockaddr*)calloc(1, sizeof(struct sockaddr_in)); memcpy(ifi->ifi_brdaddr, sinptr, sizeof(struct sockaddr_in)); } #endif break; case AF_INET6: sin6ptr = (struct sockaddr_in6 *) (*ifas).ifa_addr; ifi->ifi_addr = (struct sockaddr*)calloc(1, sizeof(struct sockaddr_in6)); memcpy(ifi->ifi_addr, sin6ptr, sizeof(struct sockaddr_in6)); break; default: break; } } freeifaddrs(ifas); return(ifihead); } int main(int argc, char *argv[]) { int family; if (argc!=2) { printf("usage: ./znet \n"); exit(1); } if (strcmp(argv[1], "inet4") == 0) family = AF_INET; else if (strcmp(argv[1], "inet6") == 0) family =AF_INET6; else { printf("invalid \n"); exit(1); } char addr[128]; struct ifi_info *ifi, *ifihead; printf("name\tflag\tIP\t\tbroadcastaddr\n"); for (ifihead = ifi = get_ifi_info(family,1); ifi != NULL; ifi = ifi->ifi_next) { printf("%s\t",ifi->ifi_name); printf("%d\t",ifi->ifi_flags); if((ifi->ifi_addr)->sa_family!=AF_INET6) inet_ntop(AF_INET,&(((struct sockaddr_in *)(ifi->ifi_addr))->sin_addr),addr,sizeof(addr)); else inet_ntop(AF_INET6,&(((struct sockaddr_in6 *)(ifi->ifi_addr))->sin6_addr),addr,sizeof(addr)); printf("%s\t",addr); #ifdef SIOCGIFBRDADDR if ((ifi->ifi_flags & IFF_BROADCAST) && (ifi->ifi_addr)->sa_family!=AF_INET6) { inet_ntop(AF_INET,&(((struct sockaddr_in *) (ifi->ifi_brdaddr))->sin_addr),addr,sizeof(addr)); printf("%s\t",addr); } #endif printf("\n+++++++++++++++++++++++++++++++++++++++++++\n"); } return 0; }
这段代码输出如下:
[root@localhost net_if]./znet inet4
name flag IP broadcastaddr
lo 73 127.0.0.1
++++++++++++++++++++++++++++++
eth1 4099 192.168.1.2 192.168.1.255
++++++++++++++++++++++++++++++
eth0 4163 192.168.125.99 192.168.125.255
++++++++++++++++++++++++++++++
[root@localhost net_if]./znet inet6
name flag IP broadcastaddr
lo 73 ::1
++++++++++++++++++++++++++++++
eth1 4163 2001:250:1800:1::1
++++++++++++++++++++++++++++++
eth0 4163 2001:250:1888:1::1
++++++++++++++++++++++++++++++