在Linux下开发网络程序时,经常会遇到需要取本地网络接口名、IP、广播地址、子网掩码或者MAC地址等信息的需求,最常见的办法是配合宏SIOCGIFHWADDR、SIOCGIFADDR、SIOCGIFBRDADDR与SIOCGIFNETMASK作为参数调用函数ioctl分别获得MAC地址、IP地址、广播地址与子网掩码来实现。一次性获取此类信息的C语言代码实现如下。
1 #include <stdio.h> 2 #include <string.h> 3 #include <net/if.h> 4 #include <sys/ioctl.h> 5 #include <arpa/inet.h> 6 #include <errno.h> 7 8 int getLocalInfo(void) 9 { 10 int fd; 11 int interfaceNum = 0; 12 struct ifreq buf[16]; 13 struct ifconf ifc; 14 struct ifreq ifrcopy; 15 char mac[16] = {0}; 16 char ip[32] = {0}; 17 char broadAddr[32] = {0}; 18 char subnetMask[32] = {0}; 19 20 if ((fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) 21 { 22 perror("socket"); 23 24 close(fd); 25 return -1; 26 } 27 28 ifc.ifc_len = sizeof(buf); 29 ifc.ifc_buf = (caddr_t)buf; 30 if (!ioctl(fd, SIOCGIFCONF, (char *)&ifc)) 31 { 32 interfaceNum = ifc.ifc_len / sizeof(struct ifreq); 33 printf("interface num = %dn", interfaceNum); 34 while (interfaceNum-- > 0) 35 { 36 printf("ndevice name: %sn", buf[interfaceNum].ifr_name); 37 38 //ignore the interface that not up or not runing 39 ifrcopy = buf[interfaceNum]; 40 if (ioctl(fd, SIOCGIFFLAGS, &ifrcopy)) 41 { 42 printf("ioctl: %s [%s:%d]n", strerror(errno), __FILE__, __LINE__); 43 44 close(fd); 45 return -1; 46 } 47 48 //get the mac of this interface 49 if (!ioctl(fd, SIOCGIFHWADDR, (char *)(&buf[interfaceNum]))) 50 { 51 memset(mac, 0, sizeof(mac)); 52 snprintf(mac, sizeof(mac), "%02x%02x%02x%02x%02x%02x", 53 (unsigned char)buf[interfaceNum].ifr_hwaddr.sa_data[0], 54 (unsigned char)buf[interfaceNum].ifr_hwaddr.sa_data[1], 55 (unsigned char)buf[interfaceNum].ifr_hwaddr.sa_data[2], 56 57 (unsigned char)buf[interfaceNum].ifr_hwaddr.sa_data[3], 58 (unsigned char)buf[interfaceNum].ifr_hwaddr.sa_data[4], 59 (unsigned char)buf[interfaceNum].ifr_hwaddr.sa_data[5]); 60 printf("device mac: %sn", mac); 61 } 62 else 63 { 64 printf("ioctl: %s [%s:%d]n", strerror(errno), __FILE__, __LINE__); 65 close(fd); 66 return -1; 67 } 68 69 //get the IP of this interface 70 71 if (!ioctl(fd, SIOCGIFADDR, (char *)&buf[interfaceNum])) 72 { 73 snprintf(ip, sizeof(ip), "%s", 74 (char *)inet_ntoa(((struct sockaddr_in *)&(buf[interfaceNum].ifr_addr))->sin_addr)); 75 printf("device ip: %sn", ip); 76 } 77 else 78 { 79 printf("ioctl: %s [%s:%d]n", strerror(errno), __FILE__, __LINE__); 80 close(fd); 81 return -1; 82 } 83 84 //get the broad address of this interface 85 86 if (!ioctl(fd, SIOCGIFBRDADDR, &buf[interfaceNum])) 87 { 88 snprintf(broadAddr, sizeof(broadAddr), "%s", 89 (char *)inet_ntoa(((struct sockaddr_in *)&(buf[interfaceNum].ifr_broadaddr))->sin_addr)); 90 printf("device broadAddr: %sn", broadAddr); 91 } 92 else 93 { 94 printf("ioctl: %s [%s:%d]n", strerror(errno), __FILE__, __LINE__); 95 close(fd); 96 return -1; 97 } 98 99 100 //get the subnet mask of this interface 101 if (!ioctl(fd, SIOCGIFNETMASK, &buf[interfaceNum])) 102 { 103 snprintf(subnetMask, sizeof(subnetMask), "%s", 104 (char *)inet_ntoa(((struct sockaddr_in *)&(buf[interfaceNum].ifr_netmask))->sin_addr)); 105 printf("device subnetMask: %sn", subnetMask); 106 } 107 else 108 { 109 printf("ioctl: %s [%s:%d]n", strerror(errno), __FILE__, __LINE__); 110 close(fd); 111 return -1; 112 113 } 114 } 115 } 116 else 117 { 118 printf("ioctl: %s [%s:%d]n", strerror(errno), __FILE__, __LINE__); 119 close(fd); 120 return -1; 121 } 122 123 close(fd); 124 125 return 0; 126 } 127 128 int main(void) 129 { 130 getLocalInfo(); 131 132 return 0; 133 }
使用ioctl函数虽然可以获取所有的信息,但是使用起来比较麻烦,如果不需要获取MAC地址,那么使用getifaddrs函数来获取更加方便与简洁。值得一提的是,在MacOS或iOS系统上(如iPhone程序开发),上述iotcl函数没法获得mac地址跟子网掩码,这个使用,使用getifaddrs函数便更有优势了。下面是使用getiaddrs函数获取网卡信息的C语言代码实现。
1 #include <stdio.h> 2 #include <ifaddrs.h> 3 #include <arpa/inet.h> 4 5 int getSubnetMask() 6 { 7 struct sockaddr_in *sin = NULL; 8 struct ifaddrs *ifa = NULL, *ifList; 9 10 if (getifaddrs(&ifList) < 0) 11 { 12 return -1; 13 } 14 15 for (ifa = ifList; ifa != NULL; ifa = ifa->ifa_next) 16 { 17 if(ifa->ifa_addr->sa_family == AF_INET) 18 { 19 printf("n>>> interfaceName: %sn", ifa->ifa_name); 20 21 sin = (struct sockaddr_in *)ifa->ifa_addr; 22 printf(">>> ipAddress: %sn", inet_ntoa(sin->sin_addr)); 23 24 sin = (struct sockaddr_in *)ifa->ifa_dstaddr; 25 printf(">>> broadcast: %sn", inet_ntoa(sin->sin_addr)); 26 27 sin = (struct sockaddr_in *)ifa->ifa_netmask; 28 printf(">>> subnetMask: %sn", inet_ntoa(sin->sin_addr)); 29 } 30 } 31 32 freeifaddrs(ifList); 33 34 return 0; 35 } 36 37 int main(void) 38 { 39 getSubnetMask(); 40 41 return 0; 42 }
ifaddrs结构体定义如下:
1 struct ifaddrs 2 { 3 struct ifaddrs *ifa_next; /* Next item in list */ 4 char *ifa_name; /* Name of interface */ 5 unsigned int ifa_flags; /* Flags from SIOCGIFFLAGS */ 6 struct sockaddr *ifa_addr; /* Address of interface */ 7 struct sockaddr *ifa_netmask; /* Netmask of interface */ 8 union 9 { 10 struct sockaddr *ifu_broadaddr; /* Broadcast address of interface */ 11 struct sockaddr *ifu_dstaddr; /* Point-to-point destination address */ 12 } ifa_ifu; 13 #define ifa_broadaddr ifa_ifu.ifu_broadaddr 14 #define ifa_dstaddr ifa_ifu.ifu_dstaddr 15 void *ifa_data; /* Address-specific data */ 16 };
ifa_next指向链表的下一个成员;ifa_name是接口名称,以0结尾的字符串,比如eth0,lo;ifa_flags是接口的标识位(比如当IFF_BROADCAST或IFF_POINTOPOINT设置到此标识位时,影响联合体变量ifu_broadaddr存储广播地址或ifu_dstaddr记录点对点地址);ifa_netmask存储该接口的子网掩码;结构体变量存储广播地址或点对点地址(见括弧介绍ifa_flags);ifa_data存储了该接口协议族的特殊信息,它通常是NULL(一般不关注他)。
函数getifaddrs(int getifaddrs (struct ifaddrs **__ifap))获取本地网络接口信息,将之存储于链表中,链表头结点指针存储于__ifap中带回,函数执行成功返回0,失败返回-1,且为errno赋值。
很显然,函数getifaddrs用于获取本机接口信息,比如最典型的获取本机IP地址。