Linux编程获取本机IP地址 + socket相关系统调用的调用流程
linux网络接口,struct ifreq struct ifconf结构
网络相关的ioctl请求的request参数及arg地址必须指向的数据类型如下表所示:
接口 |
SIOCGIFCONF SIOCSIFADDR SIOCGIFADDR SIOCSIFBRDADDR SIOCGIFBRDADDR SIOCSIFNETMASK SIOCGIFNETMASK |
获取所有接口列表 设置接口地址 获取接口地址 设置广播地址 获取广播地址 设置子网掩码 获取子网掩码 |
Struct ifconf Struct ifreq Struct ifreq Struct ifreq Struct ifreq Struct ifreq Struct ifreq |
Ifreq结构用来配置ip地址,激活接口,配置MTU。在Linux系统中获取IP地址通常都是通过ifconfig命令来实现的,然而ifconfig命令实际是通过ioctl接口与内核通信,ifconfig命令首先打开一个socket,然后调用ioctl将request传递到内核,从而获取request请求数据。处理网络接口的许多程序沿用的初始步骤之一就是从内核获取配置在系统中的所有接口。
struct ifreq data; fd = socket(AF_NET,SOCK_DGRAM,0); ioctl(fd,SIOCGIFADDR,&data);
struct ifconf结构体
struct ifconf{ lint ifc_len; union{ caddr_t ifcu_buf struct ifreq *ifcu_req; }ifc_ifcu } #define ifc_buf ifc_ifcu.ifcu_buf #define ifc_req ifc_ifcu.ifcu_req
struct ifreq接口
struct ifreq{ char ifr_name[IFNAMSIZ]; union{ struct sockaddr ifru_addr; struct sockaddr ifru_dstaddr; struct sockaddr ifru_broadaddr; struct sockaddr ifru_netmask; struct sockaddr ifru_hwaddr; short ifru_flags; int ifru_metric; caddr_t ifru_data; }ifr_ifru; }; #define ifr_addr ifr_ifru.ifru_addr #define ifr_broadaddr ifr_ifru.ifru_broadadd #define ifr_hwaddr ifr_ifru_hwaddr
对于ifconf中ifc_buf,其实就是N个ifc_req,从上面的结构体中可以看出来,通过下面两幅图可以更加明显。
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地址。
sockaddr和sockaddr_in的区别
套接字编程需要指定套接字的地址作为参数,不同的协议族有不同的地址结构定义方式。这些地址结构通常以sockeaddr_开头,每一个协议族有一个唯一的后缀,例如对于以太网,其结构名称为sockaddr_in. 1.通用套接字数据结构 通用的套接字地址类型的定义如下,它可以在不同协议族之间进行强制转换。 include <netinet/in.h>
struct sockaddr_in { 4.一般的用法为: 程序员把类型、ip地址、端口填充sockaddr_in结构体,然后强制转换成sockaddr,作为参数传递给系统调用函数 网络编程中一段典型的代码为: int sockfd;
|
#include <sys/types.h> #include <sys/ioctl.h> #include <sys/socket.h> #include <net/if.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <netdb.h> #include <string.h> #include <fcntl.h> #include <string.h> #include <errno.h> typedef uint32_t uint32; #define MAX_IF 10 int main() { struct ifreq ifVec[MAX_IF]; //保存所有借口 int sock = -1; if((sock = socket(AF_INET, SOCK_DGRAM, 0)) < 0) fprintf(stderr, "Error:%d, cannot open RAM;\n"); // get if verctor struct ifconf ioIfConf; ioIfConf.ifc_buf = (void *)ifVec; ioIfConf.ifc_len = sizeof(ifVec); printf("Len:%d\n", ioIfConf.ifc_len); if(ioctl(sock, SIOCGIFCONF, &ioIfConf) < 0) //获取所有网络接口信息 fprintf(stderr, "Error:%d ioctl IFCONF\n"); printf("Len:%d\n", ioIfConf.ifc_len); //和前面到len对比,发现ioctl修改里len到大小 //循环打印每个网络接口到信息 { struct ifreq *ifPt; struct ifreq *ifEndPt; ifPt = ifVec; ifEndPt = (void *)((char *)ifVec + ioIfConf.ifc_len); for(ifPt = ifVec; ifPt < ifEndPt; ifPt++) { struct ifreq ifReq; if(ifPt->ifr_addr.sa_family != AF_INET) { continue; } // Temp keepers of interface params ... uint32 u32_addr, u32_mask; /* 打印ip地址地址 */ char ipDotBuf[16], subnetDotBuf[16], maskDotBuf[16]; //保存点分十进制到ip地址 u32_addr = ((struct sockaddr_in *)&ifPt->ifr_addr)->sin_addr.s_addr; inet_ntop(AF_INET, &u32_addr, ipDotBuf, (socklen_t)sizeof(ipDotBuf)); printf("IP Address: %s\n", ipDotBuf); /* 打印地址掩码 */ bzero(&ifReq, sizeof(struct ifreq)); memcpy(ifReq.ifr_name, ifPt->ifr_name, sizeof(ifReq.ifr_name)); if(ioctl(sock, SIOCGIFNETMASK, &ifReq) < 0) { fprintf(stderr, "Error: %d, cannot get mask\n", errno); } else { u32_mask = ((struct sockaddr_in *)&ifReq.ifr_addr)->sin_addr.s_addr; inet_ntop(AF_INET, &u32_mask, maskDotBuf, (socklen_t)sizeof(maskDotBuf)); printf("Mask: %s\n", maskDotBuf); } /* 打印MTU */ bzero(&ifReq, sizeof(struct ifreq)); memcpy(ifReq.ifr_name, ifPt->ifr_name, sizeof(ifReq.ifr_name)); if(ioctl(sock, SIOCGIFMTU, &ifReq) < 0) { fprintf(stderr, "Error: %d, cannot get MTU\n", errno); } else { printf("SIOCGIFMTU: %d\n", ifReq.ifr_mtu); } /* 其他信息的打印方式与掩码和MTU相同 */ } } }
2 linux编程获取本机IP地址的三种方法
分类: C/C++ Linux Tech2011-04-14 20:33 861人阅读 评论(0) 收藏 举报
这 是一项不太清晰而且没有多大意义的工作。一个原因是网络地址的设置非常灵活而且都是允许用户进行个性化设置的,比如一台计算机上可以有多块物理网卡或者虚 拟网卡,一个网卡上可以绑定多个IP地址,用户可以为网卡设置别名,可以重命名网卡,用户计算机所在网络拓扑结构未知,主机名设置是一个可选项并且同样可 以为一个计算机绑定多个主机名等,这些信息都会有影响。脱离了网络连接,单独的网络地址没有任何意义。编程中遇到必须获取计算机IP的场景,应该考虑将这 一选项放到配置文件中,由用户自己来选择。
通过google,编程获取IP地址大约有以下三种思路:
1. 通过gethostname()和gethostbyname()
1 #include <stdio.h> 2 #include <unistd.h> 3 #include <netdb.h> 4 #include <sys/socket.h> 5 #include <netinet/in.h> 6 #include <arpa/inet.h> 7 8 int main() { 9 char hname[128]; 10 struct hostent *hent; 11 int i; 12 13 gethostname(hname, sizeof(hname)); 14 15 //hent = gethostent(); 16 hent = gethostbyname(hname); 17 18 printf("hostname: %s/naddress list: ", hent->h_name); 19 for(i = 0; hent->h_addr_list[i]; i++) { 20 printf("%s/t", inet_ntoa(*(struct in_addr*)(hent->h_addr_list[i]))); 21 } 22 return 0; 23 }
运行:
[whb@jcwkyl c]$ ./local_ip
hostname: jcwkyl.jlu.edu.cn
address list: 10.60.56.90
2. 通过枚举网卡,API接口可查看man 7 netdevice
/*代码来自StackOverflow: http://stackoverflow.com/questions/212528/linux-c-get-the-ip-address-of-local-computer */ #include <stdio.h> #include <sys/types.h> #include <ifaddrs.h> #include <netinet/in.h> #include <string.h> #include <arpa/inet.h> int main (int argc, const char * argv[]) { struct ifaddrs * ifAddrStruct=NULL; void * tmpAddrPtr=NULL; getifaddrs(&ifAddrStruct); while (ifAddrStruct!=NULL) { if (ifAddrStruct->ifa_addr->sa_family==AF_INET) { // check it is IP4 // is a valid IP4 Address tmpAddrPtr=&((struct sockaddr_in *)ifAddrStruct->ifa_addr)->sin_addr; char addressBuffer[INET_ADDRSTRLEN]; inet_ntop(AF_INET, tmpAddrPtr, addressBuffer, INET_ADDRSTRLEN); printf("%s IP Address %s/n", ifAddrStruct->ifa_name, addressBuffer); } else if (ifAddrStruct->ifa_addr->sa_family==AF_INET6) { // check it is IP6 // is a valid IP6 Address tmpAddrPtr=&((struct sockaddr_in *)ifAddrStruct->ifa_addr)->sin_addr; char addressBuffer[INET6_ADDRSTRLEN]; inet_ntop(AF_INET6, tmpAddrPtr, addressBuffer, INET6_ADDRSTRLEN); printf("%s IP Address %s/n", ifAddrStruct->ifa_name, addressBuffer); } ifAddrStruct=ifAddrStruct->ifa_next; } return 0; }
运行 :
[whb@jcwkyl c]$ ./local_ip2
lo IP Address 127.0.0.1
eth0 IP Address 10.60.56.90
eth0:1 IP Address 192.168.1.3
lo IP Address ::
eth0 IP Address ::2001:da8:b000:6213:20f:1fff
eth0 IP Address 0:0:fe80::20f:1fff
3. 打开一个对外界服务器的网络连接,通过getsockname()反查自己的IP
3 在linux下 获取,修改本机IP地址的两个函数
//获取本机IP地址函数
view plaincopy to clipboardprint?
QString GetLocalIp() { int sock_get_ip; char ipaddr[50]; struct sockaddr_in *sin; struct ifreq ifr_ip; if ((sock_get_ip=socket(AF_INET, SOCK_STREAM, 0)) == -1) { printf("socket create failse...GetLocalIp!/n"); return ""; } memset(&ifr_ip, 0, sizeof(ifr_ip)); strncpy(ifr_ip.ifr_name, "eth0", sizeof(ifr_ip.ifr_name) - 1); if( ioctl( sock_get_ip, SIOCGIFADDR, &ifr_ip) < 0 ) { return ""; } sin = (struct sockaddr_in *)&ifr_ip.ifr_addr; strcpy(ipaddr,inet_ntoa(sin->sin_addr)); printf("local ip:%s /n",ipaddr); close( sock_get_ip ); return QString( ipaddr ); }
//修改本机IP地址的函数
int SetLocalIp( const char *ipaddr ) { int sock_set_ip; struct sockaddr_in sin_set_ip; struct ifreq ifr_set_ip; bzero( &ifr_set_ip,sizeof(ifr_set_ip)); if( ipaddr == NULL ) return -1; if((sock_set_ip = socket( AF_INET, SOCK_STREAM, 0 )) == -1); { perror("socket create failse...SetLocalIp!/n"); return -1; } memset( &sin_set_ip, 0, sizeof(sin_set_ip)); strncpy(ifr_set_ip.ifr_name, "eth0", sizeof(ifr_set_ip.ifr_name)-1); sin_set_ip.sin_family = AF_INET; sin_set_ip.sin_addr.s_addr = inet_addr(ipaddr); memcpy( &ifr_set_ip.ifr_addr, &sin_set_ip, sizeof(sin_set_ip)); if( ioctl( sock_set_ip, SIOCSIFADDR, &ifr_set_ip) < 0 ) { perror( "Not setup interface/n"); return -1; } //设置激活标志 ifr_set_ip.ifr_flags |= IFF_UP |IFF_RUNNING; //get the status of the device if( ioctl( sock_set_ip, SIOCSIFFLAGS, &ifr_set_ip ) < 0 ) { perror("SIOCSIFFLAGS"); return -1; } close( sock_set_ip ); return 0; }
4 在linux下 获取本机MAC地址的函数
QString GetLocalMac() { int sock_mac; struct ifreq ifr_mac; char mac_addr[30]; sock_mac = socket( AF_INET, SOCK_STREAM, 0 ); if( sock_mac == -1) { perror("create socket falise...mac/n"); return ""; } memset(&ifr_mac,0,sizeof(ifr_mac)); strncpy(ifr_mac.ifr_name, "eth0", sizeof(ifr_mac.ifr_name)-1); if( (ioctl( sock_mac, SIOCGIFHWADDR, &ifr_mac)) < 0) { printf("mac ioctl error/n"); return ""; } sprintf(mac_addr,"%02x%02x%02x%02x%02x%02x", (unsigned char)ifr_mac.ifr_hwaddr.sa_data[0], (unsigned char)ifr_mac.ifr_hwaddr.sa_data[1], (unsigned char)ifr_mac.ifr_hwaddr.sa_data[2], (unsigned char)ifr_mac.ifr_hwaddr.sa_data[3], (unsigned char)ifr_mac.ifr_hwaddr.sa_data[4], (unsigned char)ifr_mac.ifr_hwaddr.sa_data[5]); printf("local mac:%s /n",mac_addr); close( sock_mac ); return QString( mac_addr ); }
5 在linux下 获取,修改子网掩码NETMASK的两个函数
//获取子网掩码的函数
QString GetLocalNetMask() { int sock_netmask; char netmask_addr[50]; struct ifreq ifr_mask; struct sockaddr_in *net_mask; sock_netmask = socket( AF_INET, SOCK_STREAM, 0 ); if( sock_netmask == -1) { perror("create socket failture...GetLocalNetMask/n"); return ""; } memset(&ifr_mask, 0, sizeof(ifr_mask)); strncpy(ifr_mask.ifr_name, ifname, sizeof(ifr_mask.ifr_name )-1); if( (ioctl( sock_netmask, SIOCGIFNETMASK, &ifr_mask ) ) < 0 ) { printf("mac ioctl error/n"); return ""; } net_mask = ( struct sockaddr_in * )&( ifr_mask.ifr_netmask ); strcpy( netmask_addr, inet_ntoa( net_mask -> sin_addr ) ); printf("local netmask:%s/n",netmask_addr); close( sock_netmask ); return QString( netmask_addr ); } //修改子NETMASK的函数 QString SetLocalNetMask(const char *szNetMask) { int sock_netmask; char netmask_addr[32]; struct ifreq ifr_mask; struct sockaddr_in *sin_net_mask; sock_netmask = socket( AF_INET, SOCK_STREAM, 0 ); if( sock_netmask == -1) { perror("Not create network socket connect/n"); return ""; } memset(&ifr_mask, 0, sizeof(ifr_mask)); strncpy(ifr_mask.ifr_name, "eth0", sizeof(ifr_mask.ifr_name )-1); sin_net_mask = (struct sockaddr_in *)&ifr_mask.ifr_addr; sin_net_mask -> sin_family = AF_INET; inet_pton(AF_INET, szNetMask, &sin_net_mask ->sin_addr); if(ioctl(sock_netmask, SIOCSIFNETMASK, &ifr_mask ) < 0) { printf("sock_netmask ioctl error/n"); return ""; } } //获去GateWay QString GetGateWay() { FILE *fp; char buf[512]; char cmd[128]; char gateway[30]; char *tmp; strcpy(cmd, "ip route"); fp = popen(cmd, "r"); if(NULL == fp) { perror("popen error"); return ""; } while(fgets(buf, sizeof(buf), fp) != NULL) { tmp =buf; while(*tmp && isspace(*tmp)) ++ tmp; if(strncmp(tmp, "default", strlen("default")) == 0) break; } sscanf(buf, "%*s%*s%s", gateway); printf("default gateway:%s/n", gateway); pclose(fp); return QString(gateway); } //设置网关 int SetGateWay(const char *szGateWay) { int ret = 0; char cmd[128]; QString DefGW = GetGateWay(); const char *strGW = DefGW.latin1(); strcpy(cmd, "route del default gw "); strcat(cmd, strGW); ret = system(cmd); if(ret < 0) { perror("route error"); return -1; } strcpy(cmd, "route add default gw "); strcat(cmd, szGateWay); ret = system(cmd); if(ret < 0) { perror("route error"); return -1; } return ret; }
Linux下如何获取网卡信息
2010-12-17 00:12:26| 分类: Linux应用 | 标签:网卡 dns 网关 mac linux |字号 订阅
有时候,写程序的时候需要获取计算机的网络信息,比如IP地址、电脑名称、DNS等信息。IP地址和电脑名称是比较容易获取到的,而要想获取地址掩码、DNS、网关等信息就有些麻烦了。
在Windows下我们一般都是通过从注册表读取这些信息。在Linux怎么做呢?其实,Linux下更加容易一些。因为我们可以拿现成的程序看它的源代码。通过阅读其源代码找到解决该问题的方法。那么,看哪个程序的源代码呢?如果你使用过Linux,并且比较熟悉的话就肯定知道一个命令ifconfig。这个命令和Windows下的ipconfig差不多,都可以输出网卡的信息,其中就包含DNS、掩码等信息。所以,我们可以通过看它的源代码来找到解决该问题的方法。
获取系统中的网卡数量
并没有那个系统调用提供网卡数量的获取。但是,我们可以通过强大的proc文件系统获取网卡数量的信息。实际上,ifconfig也是这样做的,请看示例代码如下:
0001 #include <stdio.h>
0002 #include <string.h>
0003 #include <errno.h>
0004
0005 int GetNetCardCount()
0006 {
0007 int nCount = 0;
0008 FILE* f = fopen("/proc/net/dev", "r");
0009 if (!f)
0010 {
0011 fprintf(stderr, "Open /proc/net/dev failed!errno:%d\n", errno);
0012 return nCount;
0013 }
0014
0015 char szLine[512];
0016
0017 fgets(szLine, sizeof(szLine), f); /* eat line */
0018 fgets(szLine, sizeof(szLine), f);
0019
0020 while(fgets(szLine, sizeof(szLine), f))
0021 {
0022 char szName[128] = {0};
0023 sscanf(szLine, "%s", szName);
0024 int nLen = strlen(szName);
0025 if (nLen <= 0)continue;
0026 if (szName[nLen - 1] == ':') szName[nLen - 1] = 0;
0027 if (strcmp(szName, "lo") == 0)continue;
0028 nCount++;
0029 }
0030
0031 fclose(f);
0032 f = NULL;
0033 return nCount;
0034 }
0035
0036 int main(int argc, char* argv[])
0037 {
0038 printf("NetCardCount: %d\n", GetNetCardCount());
0039 return 0;
0040 }
获取IP、掩码、MAC及网关
获取IP、掩码、MAC和广播地址是比较容易的,只需要调用对应的IOCTL即可。只是大家对Linux下的IOCTL可能不太熟悉。却看示例代码:
0001 void DispNetInfo(const char* szDevName)
0002 {
0003 int s = socket(AF_INET, SOCK_DGRAM, 0);
0004 if (s < 0)
0005 {
0006 fprintf(stderr, "Create socket failed!errno=%d", errno);
0007 return;
0008 }
0009
0010 struct ifreq ifr;
0011 unsigned char mac[6];
0012 unsigned long nIP, nNetmask, nBroadIP;
0013
0014 printf("%s:\n", szDevName);
0015
0016 strcpy(ifr.ifr_name, szDevName);
0017 if (ioctl(s, SIOCGIFHWADDR, &ifr) < 0)
0018 {
0019 return;
0020 }
0021 memcpy(mac, ifr.ifr_hwaddr.sa_data, sizeof(mac));
0022 printf("\tMAC: %02x-%02x-%02x-%02x-%02x-%02x\n",
0023 mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
0024
0025 strcpy(ifr.ifr_name, szDevName);
0026 if (ioctl(s, SIOCGIFADDR, &ifr) < 0)
0027 {
0028 nIP = 0;
0029 }
0030 else
0031 {
0032 nIP = *(unsigned long*)&ifr.ifr_broadaddr.sa_data[2];
0033 }
0034 printf("\tIP: %s\n", inet_ntoa(*(in_addr*)&nIP));
0035
0036 strcpy(ifr.ifr_name, szDevName);
0037 if (ioctl(s, SIOCGIFBRDADDR, &ifr) < 0)
0038 {
0039 nBroadIP = 0;
0040 }
0041 else
0042 {
0043 nBroadIP = *(unsigned long*)&ifr.ifr_broadaddr.sa_data[2];
0044 }
0045 printf("\tBroadIP: %s\n", inet_ntoa(*(in_addr*)&nBroadIP));
0046
0047 strcpy(ifr.ifr_name, szDevName);
0048 if (ioctl(s, SIOCGIFNETMASK, &ifr) < 0)
0049 {
0050 nNetmask = 0;
0051 }
0052 else
0053 {
0054 nNetmask = *(unsigned long*)&ifr.ifr_netmask.sa_data[2];
0055 }
0056 printf("\tNetmask: %s\n", inet_ntoa(*(in_addr*)&nNetmask));
0057 close(s);
0058 }
那么如何获取网关地址呢?更加容易,但是,好像很少有人知道。反正我在网上没有找到有人知道。最后看了nslookup的源代码以后才知道正确的做法。代码如下:
res_init();
for (int i = 0; i < _res.nscount; i++)
{
struct sockaddr* server = (struct sockaddr*)&_res.nsaddr_list[i];
printf("Server: %s\n", inet_ntoa(*(in_addr*)&(server->sa_data[2])));
}
代码很简单,就不做解释了。
怎么获取网关呢?这个稍微有点麻烦一些,不过和获取网卡数量相似,都是通过proc文件系统。这次分析的/proc/net/route文件。我就不再贴出示例代码了。
最后,我把运行示例程序获取到的信息附上,以供大家有个直观的认识:
eth0:
MAC: 08-00-27-98-bf-f3
IP: 192.168.1.106
BroadIP: 255.255.255.255
Netmask: 255.255.255.0
Gateway: 192.168.1.1
eth1:
MAC: 08-00-27-16-f4-bf
IP: 192.168.1.108
BroadIP: 192.168.1.255
Netmask: 255.255.255.0
Gateway: 0.0.0.0
eth2:
MAC: 08-00-27-37-9c-91
IP: 0.0.0.0
BroadIP: 0.0.0.0
Netmask: 0.0.0.0
Gateway: 0.0.0.0
eth3:
MAC: 08-00-27-5a-d2-39
IP: 0.0.0.0
BroadIP: 0.0.0.0
Netmask: 0.0.0.0
Gateway: 0.0.0.0
NetCardCount: 4
DNS 0: 218.2.135.1
DNS 1: 61.147.37.1
Linux下C语言配置网络与获取网络配置信息的方法
2009-01-19 21:06
Linux下的网络配置包含三个要素,分别是IP地址、子网掩码和网关。本文将介绍如何在C语言中进行网络的配置和配置信息的获取。
【配置】
方法一
使用system()或exec*()调用ifconfig和route命令进行配置。这种方法的优点是使用简单,缺点是效率比较低,且依赖于ifconfig与route命令。
示例: 见所附代码中的函数ip_config_system()和ip_config_exec()。
方法二
建立一个socket,用ioctl()进行配置。这种方法的优点是效率较高,缺点是程序实现起来比较麻烦。
示例: 见所附代码中的函数ip_config_ioctl()。
【获取】
方法一
用popen()建立一个管道,管道的一端执行命令ifconfig和route,管道的另一端读取收到的数据并进行相应的解析。这种方法的优点是使用简单,缺点是效率比较低,且依赖于ifconfig与route命令。
示例: 见所附代码中的函数ip_get_pipe()。
方法二
用fopen()打开/proc/net/route,可以获取网关(在/proc/net中尚未发现比较好的获取IP地址和掩码的方法,知道的请发邮件至cugfeng at gamil.com,谢谢)。这种方法的优点是使用简单,效率比执行命令高,缺点是依赖于proc文件系统。
示例: 见所附代码中的函数ip_get_proc()。
方法三
建立一个socket,用ioctl()进行获取(用ioctl()尚未发现比较好的获取网关的方法,知道的请发邮件至cugfeng at gamil.com,谢谢)。这种方法的优点是效率较高,缺点是程序实现起来比较麻烦。
示例: 见所附代码中的函数ip_get_ioctl()。
BTW,用ioctl()的方法还可以获取MAC地址,ioctl()命令为SIOCGIFHWADDR,具体用法与ioctl()获取IP地址的方法相同,这里就不多说了。 |
LINUX下的getifaddrs()函数的内存释放问题
LINUX下的getifaddrs()函数的内存释放问题
在LINUX下获取网卡信息需要用到IOCTL或者getifaddrs
而我在用getifaddrs的时候遇到了内存方面的问题
先看相关定义:
==========
函数定义:
/* Create a linked list of `struct ifaddrs' structures, one for each
network interface on the host machine. If successful, store the
list in *IFAP and return 0. On errors, return -1 and set `errno'.
The storage returned in *IFAP is allocated dynamically and can
only be properly freed by passing it to `freeifaddrs'. */
extern int getifaddrs (struct ifaddrs **__ifap) __THROW;
/* Reclaim the storage allocated by a previous `getifaddrs' call. */
extern void freeifaddrs (struct ifaddrs *__ifa) __THROW;
==============
此函数需要的结构体定义:
struct ifaddrs
{
struct ifaddrs *ifa_next; /* Pointer to the next structure. */
char *ifa_name; /* Name of this network interface. */
unsigned int ifa_flags; /* Flags as from SIOCGIFFLAGS ioctl. */
struct sockaddr *ifa_addr; /* Network address of this interface. */
struct sockaddr *ifa_netmask; /* Netmask of this interface. */
union
{
/* At most one of the following two is valid. If the IFF_BROADCAST
bit is set in `ifa_flags', then `ifa_broadaddr' is valid. If the
IFF_POINTOPOINT bit is set, then `ifa_dstaddr' is valid.
It is never the case that both these bits are set at once. */
struct sockaddr *ifu_broadaddr; /* Broadcast address of this interface. */
struct sockaddr *ifu_dstaddr; /* Point-to-point destination address. */
} ifa_ifu;
/* These very same macros are defined by <net/if.h> for `struct ifaddr'.
So if they are defined already, the existing definitions will be fine. */
# ifndef ifa_broadaddr
# define ifa_broadaddr ifa_ifu.ifu_broadaddr
# endif
# ifndef ifa_dstaddr
# define ifa_dstaddr ifa_ifu.ifu_dstaddr
# endif
void *ifa_data; /* Address-specific data (may be unused). */
};
=============
我在调用了getifaddrs()之后,正常地完成了需要的工作
但是最后如果用freeifaddrs,则出现运行时错误
*** glibc detected *** d: free(): invalid pointer: 0x0804a4d4 ***
======= Backtrace: =========
/lib/libc.so.6[0xb7eda911]
/lib/libc.so.6(__libc_free+0x84)[0xb7edbf84]
/lib/libc.so.6(freeifaddrs+0x1d)[0xb7f512dd]
d[0x8048989]
d[0x80486a5]
/lib/libc.so.6(__libc_start_main+0xdc)[0xb7e8c87c]
d[0x8048491]
======= Memory map: ========
08048000-08049000 r-xp 00000000 03:07 48637 /home/souldump/bin/d
08049000-0804a000 rw-p 00000000 03:07 48637 /home/souldump/bin/d
0804a000-0806b000 rw-p 0804a000 00:00 0 [heap]
b7d00000-b7d21000 rw-p b7d00000 00:00 0
b7d21000-b7e00000 ---p b7d21000 00:00 0
b7e76000-b7e77000 rw-p b7e76000 00:00 0
b7e77000-b7f90000 r-xp 00000000 03:05 16184 /lib/libc-2.4.so
b7f90000-b7f92000 r--p 00118000 03:05 16184 /lib/libc-2.4.so
b7f92000-b7f94000 rw-p 0011a000 03:05 16184 /lib/libc-2.4.so
b7f94000-b7f98000 rw-p b7f94000 00:00 0
b7fab000-b7fb5000 r-xp 00000000 03:05 20108 /lib/libgcc_s.so.1
b7fb5000-b7fb6000 rw-p 00009000 03:05 20108 /lib/libgcc_s.so.1
b7fb6000-b7fb7000 rw-p b7fb6000 00:00 0
b7fb7000-b7fd1000 r-xp 00000000 03:05 16177 /lib/ld-2.4.so
b7fd1000-b7fd3000 rw-p 00019000 03:05 16177 /lib/ld-2.4.so
bfb2b000-bfb41000 rw-p bfb2b000 00:00 0 [stack]
ffffe000-fffff000 ---p 00000000 00:00 0 [vdso]
实际上也有人出现相同问题:
http://p.g.yupoo.com/nph-proxy.cgi/000110A/http/www.linuxdby.com/bbs/viewthread.php=3ftid=3d10756
此人说:"这说明不是真正的链表,指针非法"
但是又没有进一步说明怎么解决
他干脆没有调用freeifaddrs,自然会内存泄漏.....
我去看了afaddrs.c
freeifaddrs的定义居然是:
void
freeifaddrs (struct ifaddrs *ifa)
{
free (ifa);
}
怎么样,很囧吧,明明在头文件里说"必须用freeifaddrs才能正确free..."
然后我看了一下getifaddrs的函数体
他在getifaddrs内部定义了一个结构
struct ifaddrs_storage
{
struct ifaddrs ifa;
union
{
/* Save space for the biggest of the four used sockaddr types and
avoid a lot of casts. */
struct sockaddr sa;
struct sockaddr_ll sl;
struct sockaddr_in s4;
struct sockaddr_in6 s6;
} addr, netmask, broadaddr;
char name[IF_NAMESIZE + 1];
};
然后把获取的各网卡信息一个个填充到此结构的struct ifaddrs ifa中,ifa的next值手动设置为下一个struct ifaddrs_storage中的ifa的地址...
这酒是所谓的"伪链表"吧?
这就是我无法正确free掉它的原因?
我究竟要怎么把它free掉?freeifaddrs一运行就运行时错误
LINUX取得本机IP的简单C程序
注意这里用了两个struct ifaddrs
//代码根据UNP和man手册编写
//适用于LINUX/BSD(FreeBSD, MacOS X)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <ifaddrs.h>
int main(void)
{
struct ifaddrs *ifc, *ifc1;
char ip[64];
char nm[64];
if (0 != getifaddrs(&ifc)) return(-1);
ifc1 = ifc;
printf("Iface\tIP address\tNetmask\n");
for(; NULL != ifc; ifc = (*ifc).ifa_next) {
printf("%s", (*ifc).ifa_name);
if (NULL != (*ifc).ifa_addr) {
inet_ntop(AF_INET, &(((struct sockaddr_in*)((*ifc).ifa_addr))->sin_addr), ip, 64);
printf("\t%s", ip);
} else {
printf("\t\t");
}
if (NULL != (*ifc).ifa_netmask) {
inet_ntop(AF_INET, &(((struct sockaddr_in*)((*ifc).ifa_netmask))->sin_addr), nm, 64);
printf("\t%s", nm);
} else {
printf("\t\t");
}
printf("\n");
}
freeifaddrs(ifc1);
return(0);
}
--------------------------------------------------------------------------------
============原来的============
struct ifaddrs *ifap, *ifaphead, *ifaTmp;
getifaddrs(&ifap);
ifaphead = ifap;
while((ifapTmp = ifap) != NULL)
{
//实际任务代码
ifap = ifapTmp->ifa_next;
}
freeifaddrs(ifaphead);
=========修改后========
struct ifaddrs *ifap, *ifaphead;
getifaddrs(&ifap);
ifaphead = ifap;
while(ifap != NULL)
{
//实际任务代码
ifap = ifa_next;
}
freeifaddrs(ifaphead);
==================
仅仅是用了一个ifapTmp来代替ifap做事,区别仅此而已(而且我也忘了一开始为什么要用ifapTmp....)
但是最后都是用了freeifaddrs(ifaphead)啊,并没有传错指针啊????
中间的代码并没有对这段数据做任何修改啊.....
请指教一下,这唯一的区别为什么会造成我原先的代码freeifaddrs失败?谢谢!
ifaphead = ifap; //这里用ifaphead保存ifap指针地址
while((ifapTmp = ifap) != NULL)
{
ifap = ifapTmp->ifa_next; //这里修改了ifap的地址
}
freeifaddrs(ifaphead); //由于ifap的地址修改,所以ifaphead已经是无效指针。
ioctl及getifaddrs读取IPv4,IPv6网卡信息
2011-05-08 21:18:30
标签:c vin_do,vin_do学习笔记,笔记 休闲 职场
使用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
转载自 https://www.cnblogs.com/wanpengcoder/p/7623101.html
最近一直在读内核网络协议栈源码,这里以ipv4/tcp为例对socket相关系统调用的流程做一个简要整理,这些相关系统调用的内部细节虽然各有不同,但其调用流程则基本一致;
调用流程:
(1)系统调用 --> (2)查找socket --> (3)执行socket的对应操作函数 --> (4)执行传输层协议的对应操作函数;
中间核心数据结构为inetws_array[],位于af_inet.c,以第一个元素type=SOCK_STREAM,protocol=IPPROTO_TCP为例,该类型适用与tcp协议,当创建tcp socket时,其操作socket->ops赋值为&inet_stream_ops,对应的传输控制块操作sock->sk_prot赋值为&tcp_prot;
-
1 /* Upon startup we insert all the elements in inetsw_array[] into
-
2 * the linked list inetsw.
-
3 */
-
4 static struct inet_protosw inetsw_array[] =
-
5 {
-
6 {
-
7 .type = SOCK_STREAM,
-
8 .protocol = IPPROTO_TCP,
-
9 .prot = &tcp_prot,
-
10 .ops = &inet_stream_ops,
-
11 .flags = INET_PROTOSW_PERMANENT |
-
12 INET_PROTOSW_ICSK,
-
13 },
-
14
-
15 {
-
16 .type = SOCK_DGRAM,
-
17 .protocol = IPPROTO_UDP,
-
18 .prot = &udp_prot,
-
19 .ops = &inet_dgram_ops,
-
20 .flags = INET_PROTOSW_PERMANENT,
-
21 },
-
22
-
23 {
-
24 .type = SOCK_DGRAM,
-
25 .protocol = IPPROTO_ICMP,
-
26 .prot = &ping_prot,
-
27 .ops = &inet_sockraw_ops,
-
28 .flags = INET_PROTOSW_REUSE,
-
29 },
-
30
-
31 {
-
32 .type = SOCK_RAW,
-
33 .protocol = IPPROTO_IP, /* wild card */
-
34 .prot = &raw_prot,
-
35 .ops = &inet_sockraw_ops,
-
36 .flags = INET_PROTOSW_REUSE,
-
37 }
-
38 };
查看inet_stream_ops结构会发现,其中包含了各种socket系统调用的对应的处理函数;
-
1 const struct proto_ops inet_stream_ops = {
-
2 .family = PF_INET,
-
3 .owner = THIS_MODULE,
-
4 .release = inet_release,
-
5 .bind = inet_bind,
-
6 .connect = inet_stream_connect,
-
7 .socketpair = sock_no_socketpair,
-
8 .accept = inet_accept,
-
9 .getname = inet_getname,
-
10 .poll = tcp_poll,
-
11 .ioctl = inet_ioctl,
-
12 .listen = inet_listen,
-
13 .shutdown = inet_shutdown,
-
14 .setsockopt = sock_common_setsockopt,
-
15 .getsockopt = sock_common_getsockopt,
-
16 .sendmsg = inet_sendmsg,
-
17 .recvmsg = inet_recvmsg,
-
18 .mmap = sock_no_mmap,
-
19 .sendpage = inet_sendpage,
-
20 .splice_read = tcp_splice_read,
-
21 .read_sock = tcp_read_sock,
-
22 .peek_len = tcp_peek_len,
-
23 #ifdef CONFIG_COMPAT
-
24 .compat_setsockopt = compat_sock_common_setsockopt,
-
25 .compat_getsockopt = compat_sock_common_getsockopt,
-
26 .compat_ioctl = inet_compat_ioctl,
-
27 #endif
-
28 };
查看tcp_prot可见,其中对应了实现了传输层tcp的各种socket操作;
-
1 struct proto tcp_prot = {
-
2 .name = "TCP",
-
3 .owner = THIS_MODULE,
-
4 .close = tcp_close,
-
5 .connect = tcp_v4_connect,
-
6 .disconnect = tcp_disconnect,
-
7 .accept = inet_csk_accept,
-
8 .ioctl = tcp_ioctl,
-
9 .init = tcp_v4_init_sock,
-
10 .destroy = tcp_v4_destroy_sock,
-
11 .shutdown = tcp_shutdown,
-
12 .setsockopt = tcp_setsockopt,
-
13 .getsockopt = tcp_getsockopt,
-
14 .keepalive = tcp_set_keepalive,
-
15 .recvmsg = tcp_recvmsg,
-
16 .sendmsg = tcp_sendmsg,
-
17 .sendpage = tcp_sendpage,
-
18 .backlog_rcv = tcp_v4_do_rcv,
-
19 .release_cb = tcp_release_cb,
-
20 .hash = inet_hash,
-
21 .unhash = inet_unhash,
-
22 .get_port = inet_csk_get_port,
-
23 .enter_memory_pressure = tcp_enter_memory_pressure,
-
24 .stream_memory_free = tcp_stream_memory_free,
-
25 .sockets_allocated = &tcp_sockets_allocated,
-
26 .orphan_count = &tcp_orphan_count,
-
27 .memory_allocated = &tcp_memory_allocated,
-
28 .memory_pressure = &tcp_memory_pressure,
-
29 .sysctl_mem = sysctl_tcp_mem,
-
30 .sysctl_wmem = sysctl_tcp_wmem,
-
31 .sysctl_rmem = sysctl_tcp_rmem,
-
32 .max_header = MAX_TCP_HEADER,
-
33 .obj_size = sizeof(struct tcp_sock),
-
34 .slab_flags = SLAB_TYPESAFE_BY_RCU,
-
35 .twsk_prot = &tcp_timewait_sock_ops,
-
36 .rsk_prot = &tcp_request_sock_ops,
-
37 .h.hashinfo = &tcp_hashinfo,
-
38 .no_autobind = true,
-
39 #ifdef CONFIG_COMPAT
-
40 .compat_setsockopt = compat_tcp_setsockopt,
-
41 .compat_getsockopt = compat_tcp_getsockopt,
-
42 #endif
-
43 .diag_destroy = tcp_abort,
-
44 };
具体实例,以tcp bind系统调用为例,其中红色部分为上面提到的步骤:
-
1 /*
-
2 * Bind a name to a socket. Nothing much to do here since it's
-
3 * the protocol's responsibility to handle the local address.
-
4 *
-
5 * We move the socket address to kernel space before we call
-
6 * the protocol layer (having also checked the address is ok).
-
7 */
-
8
-
9 SYSCALL_DEFINE3(bind, int, fd, struct sockaddr __user *, umyaddr, int, addrlen)
-
10 {
-
11 struct socket *sock;
-
12 struct sockaddr_storage address;
-
13 int err, fput_needed;
-
14
-
15 /* 获取socket ,fput_need标识是否需要减少文件引用计数*/
-
16 sock = sockfd_lookup_light(fd, &err, &fput_needed);
-
17 if (sock) {
-
18 /* 将用户空间地址复制到内核空间 */
-
19 err = move_addr_to_kernel(umyaddr, addrlen, &address);
-
20 if (err >= 0) {
-
21 /* 安全模块的bind检查 */
-
22 err = security_socket_bind(sock,
-
23 (struct sockaddr *)&address,
-
24 addrlen);
-
25 if (!err)
-
26 /* 调用socket的bind操作 */
-
27 err = sock->ops->bind(sock,
-
28 (struct sockaddr *)
-
29 &address, addrlen);
-
30 }
-
31
-
32 /* 根据fput_needed决定是否减少引用计数 */
-
33 fput_light(sock->file, fput_needed);
-
34 }
-
35 return err;
-
36 }
上面红色的sock->ops->bind操作实际是调用了inet_stream_ops.bind
-
1 /* 地址绑定 */
-
2 int inet_bind(struct socket *sock, struct sockaddr *uaddr, int addr_len)
-
3 {
-
4 struct sockaddr_in *addr = (struct sockaddr_in *)uaddr;
-
5 struct sock *sk = sock->sk;
-
6 struct inet_sock *inet = inet_sk(sk);
-
7 struct net *net = sock_net(sk);
-
8 unsigned short snum;
-
9 int chk_addr_ret;
-
10 u32 tb_id = RT_TABLE_LOCAL;
-
11 int err;
-
12
-
13 /* If the socket has its own bind function then use it. (RAW) */
-
14 /*
-
15 如果传输控制块有自己的bind操作则调用,
-
16 目前只有raw实现了自己的bind
-
17 */
-
18 if (sk->sk_prot->bind) {
-
19 err = sk->sk_prot->bind(sk, uaddr, addr_len);
-
20 goto out;
-
21 }
-
22
-
23 err = -EINVAL;
-
24 /* 地址长度错误 */
-
25 if (addr_len < sizeof(struct sockaddr_in))
-
26 goto out;
-
27
-
28 /* 如果不是AF_INET协议族 */
-
29 if (addr->sin_family != AF_INET) {
-
30 /* Compatibility games : accept AF_UNSPEC (mapped to AF_INET)
-
31 * only if s_addr is INADDR_ANY.
-
32 */
-
33 err = -EAFNOSUPPORT;
-
34
-
35 /* 接受AF_UNSPEC && s_addr=htonl(INADDR_ANY)的情况 */
-
36 if (addr->sin_family != AF_UNSPEC ||
-
37 addr->sin_addr.s_addr != htonl(INADDR_ANY))
-
38 goto out;
-
39 }
-
40
-
41 tb_id = l3mdev_fib_table_by_index(net, sk->sk_bound_dev_if) ? : tb_id;
-
42 chk_addr_ret = inet_addr_type_table(net, addr->sin_addr.s_addr, tb_id);
-
43
-
44 /* Not specified by any standard per-se, however it breaks too
-
45 * many applications when removed. It is unfortunate since
-
46 * allowing applications to make a non-local bind solves
-
47 * several problems with systems using dynamic addressing.
-
48 * (ie. your servers still start up even if your ISDN link
-
49 * is temporarily down)
-
50 */
-
51 err = -EADDRNOTAVAIL;
-
52
-
53 /* 合法性检查 */
-
54 if (!net->ipv4.sysctl_ip_nonlocal_bind &&
-
55 !(inet->freebind || inet->transparent) &&
-
56 addr->sin_addr.s_addr != htonl(INADDR_ANY) &&
-
57 chk_addr_ret != RTN_LOCAL &&
-
58 chk_addr_ret != RTN_MULTICAST &&
-
59 chk_addr_ret != RTN_BROADCAST)
-
60 goto out;
-
61
-
62 /* 源端口 */
-
63 snum = ntohs(addr->sin_port);
-
64 err = -EACCES;
-
65
-
66 /* 绑定特权端口的权限检查 */
-
67 if (snum && snum < inet_prot_sock(net) &&
-
68 !ns_capable(net->user_ns, CAP_NET_BIND_SERVICE))
-
69 goto out;
-
70
-
71 /* We keep a pair of addresses. rcv_saddr is the one
-
72 * used by hash lookups, and saddr is used for transmit.
-
73 *
-
74 * In the BSD API these are the same except where it
-
75 * would be illegal to use them (multicast/broadcast) in
-
76 * which case the sending device address is used.
-
77 */
-
78 lock_sock(sk);
-
79
-
80 /* Check these errors (active socket, double bind). */
-
81 err = -EINVAL;
-
82
-
83 /* 传输控制块的状态不是CLOSE || 存在本地端口 */
-
84 if (sk->sk_state != TCP_CLOSE || inet->inet_num)
-
85 goto out_release_sock;
-
86
-
87 /* 设置源地址rcv_addr用作hash查找,saddr用作传输 */
-
88 inet->inet_rcv_saddr = inet->inet_saddr = addr->sin_addr.s_addr;
-
89
-
90 /* 组播或者广播,使用设备地址 */
-
91 if (chk_addr_ret == RTN_MULTICAST || chk_addr_ret == RTN_BROADCAST)
-
92 inet->inet_saddr = 0; /* Use device */
-
93
-
94 /* Make sure we are allowed to bind here. */
-
95
-
96 /*
-
97 端口不为0,或者端口为0允许绑定
-
98 则使用协议的具体获取端口函数绑定端口
-
99 */
-
100 if ((snum || !inet->bind_address_no_port) &&
-
101 sk->sk_prot->get_port(sk, snum)) {
-
102
-
103 /* 绑定失败 */
-
104 inet->inet_saddr = inet->inet_rcv_saddr = 0;
-
105
-
106 /* 端口在使用中 */
-
107 err = -EADDRINUSE;
-
108 goto out_release_sock;
-
109 }
-
110
-
111 /* 传输控制块已经绑定本地地址或端口标志 */
-
112 if (inet->inet_rcv_saddr)
-
113 sk->sk_userlocks |= SOCK_BINDADDR_LOCK;
-
114 if (snum)
-
115 sk->sk_userlocks |= SOCK_BINDPORT_LOCK;
-
116
-
117 /* 设置源端口 */
-
118 inet->inet_sport = htons(inet->inet_num);
-
119
-
120 /* 设置目的地址和端口默认值 */
-
121 inet->inet_daddr = 0;
-
122 inet->inet_dport = 0;
-
123
-
124 /* 设置路由默认值 */
-
125 sk_dst_reset(sk);
-
126 err = 0;
-
127 out_release_sock:
-
128 release_sock(sk);
-
129 out:
-
130 return err;
-
131 }
上面的sk->sk_prot->bind以及sk->sk_prot->get_port为具体传输层实现的对应操作函数,其中只有raw socket实现了bind操作,我们不关注,而以tcp的get_port操作为例,实际上也就是调用了tcp_prot.get_port,具体tcp实现为inet_csk_get_port;(该函数尚未分析,后续补充)
-
1 /* Obtain a reference to a local port for the given sock,
-
2 * if snum is zero it means select any available local port.
-
3 * We try to allocate an odd port (and leave even ports for connect())
-
4 */
-
5 int inet_csk_get_port(struct sock *sk, unsigned short snum)
-
6 {
-
7 bool reuse = sk->sk_reuse && sk->sk_state != TCP_LISTEN;
-
8 struct inet_hashinfo *hinfo = sk->sk_prot->h.hashinfo;
-
9 int ret = 1, port = snum;
-
10 struct inet_bind_hashbucket *head;
-
11 struct net *net = sock_net(sk);
-
12 struct inet_bind_bucket *tb = NULL;
-
13 kuid_t uid = sock_i_uid(sk);
-
14
-
15 if (!port) {
-
16 head = inet_csk_find_open_port(sk, &tb, &port);
-
17 if (!head)
-
18 return ret;
-
19 if (!tb)
-
20 goto tb_not_found;
-
21 goto success;
-
22 }
-
23 head = &hinfo->bhash[inet_bhashfn(net, port,
-
24 hinfo->bhash_size)];
-
25 spin_lock_bh(&head->lock);
-
26 inet_bind_bucket_for_each(tb, &head->chain)
-
27 if (net_eq(ib_net(tb), net) && tb->port == port)
-
28 goto tb_found;
-
29 tb_not_found:
-
30 tb = inet_bind_bucket_create(hinfo->bind_bucket_cachep,
-
31 net, head, port);
-
32 if (!tb)
-
33 goto fail_unlock;
-
34 tb_found:
-
35 if (!hlist_empty(&tb->owners)) {
-
36 if (sk->sk_reuse == SK_FORCE_REUSE)
-
37 goto success;
-
38
-
39 if ((tb->fastreuse > 0 && reuse) ||
-
40 sk_reuseport_match(tb, sk))
-
41 goto success;
-
42 if (inet_csk_bind_conflict(sk, tb, true, true))
-
43 goto fail_unlock;
-
44 }
-
45 success:
-
46 if (!hlist_empty(&tb->owners)) {
-
47 tb->fastreuse = reuse;
-
48 if (sk->sk_reuseport) {
-
49 tb->fastreuseport = FASTREUSEPORT_ANY;
-
50 tb->fastuid = uid;
-
51 tb->fast_rcv_saddr = sk->sk_rcv_saddr;
-
52 tb->fast_ipv6_only = ipv6_only_sock(sk);
-
53 #if IS_ENABLED(CONFIG_IPV6)
-
54 tb->fast_v6_rcv_saddr = sk->sk_v6_rcv_saddr;
-
55 #endif
-
56 } else {
-
57 tb->fastreuseport = 0;
-
58 }
-
59 } else {
-
60 if (!reuse)
-
61 tb->fastreuse = 0;
-
62 if (sk->sk_reuseport) {
-
63 /* We didn't match or we don't have fastreuseport set on
-
64 * the tb, but we have sk_reuseport set on this socket
-
65 * and we know that there are no bind conflicts with
-
66 * this socket in this tb, so reset our tb's reuseport
-
67 * settings so that any subsequent sockets that match
-
68 * our current socket will be put on the fast path.
-
69 *
-
70 * If we reset we need to set FASTREUSEPORT_STRICT so we
-
71 * do extra checking for all subsequent sk_reuseport
-
72 * socks.
-
73 */
-
74 if (!sk_reuseport_match(tb, sk)) {
-
75 tb->fastreuseport = FASTREUSEPORT_STRICT;
-
76 tb->fastuid = uid;
-
77 tb->fast_rcv_saddr = sk->sk_rcv_saddr;
-
78 tb->fast_ipv6_only = ipv6_only_sock(sk);
-
79 #if IS_ENABLED(CONFIG_IPV6)
-
80 tb->fast_v6_rcv_saddr = sk->sk_v6_rcv_saddr;
-
81 #endif
-
82 }
-
83 } else {
-
84 tb->fastreuseport = 0;
-
85 }
-
86 }
-
87 if (!inet_csk(sk)->icsk_bind_hash)
-
88 inet_bind_hash(sk, tb, port);
-
89 WARN_ON(inet_csk(sk)->icsk_bind_hash != tb);
-
90 ret = 0;
-
91
-
92 fail_unlock:
-
93 spin_unlock_bh(&head->lock);
-
94 return ret;
-
95 }
-
96 EXPORT_SYMBOL_GPL(inet_csk_get_port);
posted on 2023-02-13 09:38 bailinjun 阅读(1166) 评论(0) 编辑 收藏 举报