首页 > 系统相关 >Linux编程获取本机IP地址 + socket相关系统调用的调用流程

Linux编程获取本机IP地址 + socket相关系统调用的调用流程

时间:2023-02-13 09:44:37浏览次数:61  
标签:sockaddr 调用 addr ifr socket Linux ifi inet struct

1  ifaddrs结构体定义如下

C代码  收藏代码

 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地址。

 

  

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()函数的内存释放问题

LinuxXPFreeBSD数据结构PHP

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

  1.   #ifndef __NET_INF_H
  2.   #define __NET_INF_H
  3.    
  4.   #include
  5.   #include
  6.   #include
  7.   #include
  8.   #include
  9.   #include
  10.   #include
  11.   #include
  12.   #include
  13.   #include
  14.   #include
  15.    
  16.   #define IFI_NAME 16
  17.   #define IFI_HADDR 8
  18.    
  19.   typedef struct ifi_info
  20.   {
  21.   char ifi_name[IFI_NAME];
  22.   u_char ifi_haddr[IFI_HADDR];
  23.   u_short ifi_hlen;
  24.   short ifi_flags;
  25.   short ifi_myflags;
  26.   struct sockaddr *ifi_addr;
  27.   struct sockaddr *ifi_brdaddr;
  28.   struct sockaddr *ifi_dstaddr;
  29.   struct ifi_info *ifi_next;
  30.   }ifi_info;
  31.    
  32.   #define IFI_ALIAS 1
  33.    
  34.   struct ifi_info *get_ifi_info(int, int);
  35.    
  36.   void free_ifi_info(struct ifi_info *);
  37.    
  38.   #endif

net_if.c

  1.   #include "net_if.h"
  2.    
  3.   ifi_info *get_ifi_info(int family, int doaliases)
  4.   {
  5.   ifi_info *ifi, *ifihead, **ifipnext;
  6.   int sockfd, len, lastlen, flags, myflags;
  7.   char *ptr, *buf, lastname[IFNAMSIZ], *cptr;
  8.   struct ifconf ifc;
  9.   struct ifreq *ifr, ifrcopy;
  10.   struct sockaddr_in *sinptr;
  11.    
  12.   if ((sockfd=socket(family, SOCK_DGRAM, 0))<0)
  13.   {
  14.   printf("socket error.\n");
  15.   exit(1);
  16.   }
  17.    
  18.   lastlen = 0;
  19.    
  20.   len = 10*sizeof(struct ifreq);
  21.   while (1)
  22.   {
  23.   buf = (char*)malloc(len);
  24.   ifc.ifc_len = len;
  25.   ifc.ifc_buf = buf;
  26.   if (ioctl(sockfd, SIOCGIFCONF, &ifc)<0)
  27.   {
  28.   if (errno!=EINVAL||lastlen!=0)
  29.   {
  30.   printf("ioctl error.\n");
  31.   }
  32.   }
  33.   else
  34.   {
  35.   if (ifc.ifc_len == lastlen)
  36.   break;
  37.   lastlen = ifc.ifc_len;
  38.   }
  39.   len += 10*sizeof(struct ifreq);
  40.   free(buf);
  41.   }
  42.    
  43.   ifihead = NULL;
  44.   ifipnext = &ifihead;
  45.   lastname[0] = 0;
  46.    
  47.   for (ptr = buf; ptrifr->ifr_addr.sa_len?sizeof(struct sockaddr):ifr->ifr_addr.sa_len;
  48.   #else
  49.   switch (ifr->ifr_addr.sa_family)
  50.   {
  51.   #ifdef IPV6
  52.   case AF_INET6:
  53.   len = sizeof(struct sockaddr_in6);
  54.   break;
  55.   #endif
  56.   case AF_INET:
  57.   default:
  58.   len = sizeof(struct sockaddr);
  59.   break;
  60.   }
  61.   #endif
  62.    
  63.   ptr += sizeof(ifr->ifr_name) + len;
  64.    
  65.   if (ifr->ifr_addr.sa_family != family)
  66.   continue;
  67.    
  68.   myflags = 0;
  69.   if ((cptr=strchr(ifr->ifr_name, ':'))!=NULL)
  70.   *cptr = 0;
  71.   if (strncmp(lastname, ifr->ifr_name, IFNAMSIZ)==0)
  72.   {
  73.   if (doaliases == 0)
  74.   continue;
  75.   myflags = IFI_ALIAS;
  76.   }
  77.    
  78.   memcpy(lastname, ifr->ifr_name, IFNAMSIZ);
  79.    
  80.   ifrcopy = *ifr;
  81.   ioctl(sockfd, SIOCGIFFLAGS, &ifrcopy);
  82.   flags = ifrcopy.ifr_flags;
  83.   if ((flags&IFF_UP)==0)
  84.   continue;
  85.   /*
  86.   if ((flags&IFF_BROADCAST)==0)
  87.   continue;
  88.   */
  89.   ifi = calloc(1, sizeof(struct ifi_info));
  90.   *ifipnext = ifi;
  91.   ifipnext = &ifi->ifi_next;
  92.   ifi->ifi_flags = flags;
  93.   ifi->ifi_myflags = myflags;
  94.   memcpy(ifi->ifi_name, ifr->ifr_name, IFI_NAME);
  95.   ifi->ifi_name[IFI_NAME-1] = '\0';
  96.    
  97.   switch (ifr->ifr_addr.sa_family)
  98.   {
  99.   case AF_INET:
  100.   sinptr = (struct sockaddr_in *)&ifr->ifr_addr;
  101.   if (ifi->ifi_addr == NULL)
  102.   {
  103.   ifi->ifi_addr = calloc(1, sizeof(struct sockaddr_in));
  104.   memcpy(ifi->ifi_addr, sinptr, sizeof(struct sockaddr_in));
  105.   #ifdef SIOCGIFBRDADDR
  106.   if (flags & IFF_BROADCAST)
  107.   {
  108.   ioctl(sockfd, SIOCGIFBRDADDR, &ifrcopy);
  109.   sinptr = (struct sockaddr_in *)&ifrcopy.ifr_broadaddr;
  110.   ifi->ifi_brdaddr = calloc(1, sizeof(struct sockaddr_in));
  111.   memcpy(ifi->ifi_brdaddr, sinptr, sizeof(struct sockaddr_in));
  112.   }
  113.   #endif
  114.   #ifdef SIOCGIFDSTADDR
  115.   if (flags & IFF_POINTOPOINT)
  116.   {
  117.   ioctl(sockfd, SIOCGIFDSTADDR, &ifrcopy);
  118.   sinptr = (struct sockaddr_in*)&ifrcopy.ifr_dstaddr;
  119.   ifi->ifi_dstaddr = calloc(1, sizeof(struct sockaddr_in));
  120.   memcpy(ifi->ifi_dstaddr, sinptr, sizeof(struct sockaddr_in));
  121.   }
  122.   #endif
  123.   }
  124.   break;
  125.   default:
  126.   break;
  127.   }
  128.   }
  129.   free(buf);
  130.   return(ifihead);
  131.   }
  132.    
  133.   void free_ifi_info(ifi_info *ifihead)
  134.   {
  135.   ifi_info *ifi, *ifinext;
  136.   for (ifi=ifihead; ifi!=NULL; ifi=ifinext)
  137.   {
  138.   if (ifi->ifi_addr!=NULL)
  139.   free(ifi->ifi_addr);
  140.    
  141.   if (ifi->ifi_brdaddr!=NULL)
  142.   free(ifi->ifi_brdaddr);
  143.   if (ifi->ifi_dstaddr!=NULL)
  144.   free(ifi->ifi_dstaddr);
  145.   ifinext = ifi->ifi_next;
  146.    
  147.   free(ifi);
  148.   }
  149.    
  150.   }
  151.    
  152.   char *sock_ntop(const struct sockaddr *sa, socklen_t salen)
  153.   {
  154.   char portstr[7];
  155.   static char str[128];
  156.    
  157.   switch (sa->sa_family)
  158.   {
  159.   case AF_INET:
  160.   {
  161.   struct sockaddr_in *sin = (struct sockaddr_in *)sa;
  162.    
  163.   if (inet_ntop(AF_INET, &sin->sin_addr, str, sizeof(str))==NULL)
  164.   return NULL;
  165.    
  166.   if (ntohs(sin->sin_port)!=0)
  167.   {
  168.   snprintf(portstr, sizeof(portstr), ".%d", ntohs(sin->sin_port));
  169.   strcat(str, portstr);
  170.   }
  171.   return str;
  172.   }
  173.   break;
  174.   case AF_INET6:
  175.   {
  176.   struct sockaddr_in6 *sin = (struct sockaddr_in6 *)sa;
  177.    
  178.   if (inet_ntop(AF_INET6, &sin->sin6_addr, str, sizeof(str))==NULL)
  179.   return NULL;
  180.    
  181.   if (ntohs(sin->sin6_port)!=0)
  182.   {
  183.   snprintf(portstr, sizeof(portstr), ".%d", ntohs(sin->sin6_port));
  184.   strcat(str, portstr);
  185.   }
  186.   return str;
  187.   }
  188.   break;
  189.   default:
  190.   return NULL;
  191.   break;
  192.   }
  193.    
  194.   }
  195.    
  196.   int main(int argc, char *argv[])
  197.   {
  198.   ifi_info *ifi, *ifihead;
  199.   struct sockaddr *sa;
  200.   u_char *ptr;
  201.   int i, family, doaliases;
  202.   if (argc!=3)
  203.   {
  204.   printf("usage: ./prifinfo ");
  205.   exit(1);
  206.   }
  207.    
  208.   if (strcmp(argv[1], "inet4") == 0)
  209.   family = AF_INET;
  210.   #ifdef IPV6
  211.   else if (strcmp(argv[1], "inet6") == 0)
  212.   family =AF_INET6;
  213.   #endif
  214.   else
  215.   {
  216.   printf("invalid
  217.   ");
  218.   exit(1);
  219.   }
  220.    
  221.   doaliases = atoi(argv[2]);
  222.    
  223.   for(ifihead = ifi = get_ifi_info(family, doaliases);
  224.   ifi!=NULL;ifi=ifi->ifi_next)
  225.   {
  226.   printf("%s:<", ifi->ifi_name);
  227.   if (ifi->ifi_flags&IFF_UP) printf("UP");
  228.   if (ifi->ifi_flags&IFF_BROADCAST) printf("BCAST");
  229.   if (ifi->ifi_flags&IFF_MULTICAST) printf("MCAST");
  230.   if (ifi->ifi_flags&IFF_LOOPBACK) printf("LOOP");
  231.   if (ifi->ifi_flags&IFF_POINTOPOINT) printf("P2P");
  232.   printf(">\n");
  233.    
  234.   if ((i=ifi->ifi_hlen)>0)
  235.   {
  236.   ptr = ifi->ifi_haddr;
  237.   do
  238.   {
  239.   printf("%s%x", (i==ifi->ifi_hlen)?" ":":", *ptr++);
  240.   }while(--i>0);
  241.    
  242.   printf("\n");
  243.   }
  244.    
  245.   if ((sa=ifi->ifi_addr)!=NULL)
  246.   printf(" IP addr: %s\n",
  247.   sock_ntop(sa, sizeof(*sa)));
  248.   if ((sa=ifi->ifi_brdaddr)!=NULL)
  249.   printf(" broadcast addr: %s\n",
  250.   sock_ntop(sa, sizeof(*sa)));
  251.   if ((sa=ifi->ifi_dstaddr)!=NULL)
  252.   printf(" destnation addr: %s\n",
  253.   sock_ntop(sa, sizeof(*sa)));
  254.   }
  255.    
  256.   free_ifi_info(ifihead);
  257.    
  258.   exit(0);
  259.   }

使用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

  1.   #ifndef __ZNET_H__
  2.   #define __ZNET_H__
  3.    
  4.   #include
  5.   #include
  6.   #include
  7.   #include
  8.   #include
  9.   #include
  10.   #include
  11.   #include
  12.    
  13.   #define IFI_NAME 16 /* same as IFNAMSIZ in */
  14.   #define IFI_HADDR 8 /* allow for 64-bit EUI-64 in future */
  15.    
  16.   struct ifi_info {
  17.   char ifi_name[IFI_NAME]; /* interface name, null-terminated */
  18.   short ifi_index; /* interface index */
  19.   short ifi_flags; /* IFF_xxx constants from */
  20.   struct sockaddr *ifi_addr; /* primary address */
  21.   struct sockaddr *ifi_brdaddr;/* broadcast address */
  22.   struct ifi_info *ifi_next; /* next of these structures */
  23.   };
  24.    
  25.   struct ifi_info* get_ifi_info(int, int);
  26.   void free_ifi_info(struct ifi_info *);
  27.    
  28.   #endif

znet.c

  1.   #include "znet.h"
  2.    
  3.   struct ifi_info* get_ifi_info(int family, int doaliases) {
  4.   struct ifi_info *ifi, *ifihead, **ifipnext,*p;
  5.   struct sockaddr_in *sinptr;
  6.   struct sockaddr_in6 *sin6ptr;
  7.   struct ifaddrs *ifas;
  8.   // char addr[128];
  9.   int sockfd;
  10.    
  11.   ifihead = NULL;
  12.   ifipnext = &ifihead;
  13.    
  14.   if(getifaddrs(&ifas)!=0)
  15.   return ;
  16.    
  17.   for(;ifas!=NULL;ifas=(*ifas).ifa_next) {
  18.   if (((*ifas).ifa_addr)->sa_family != family)
  19.   continue; // ignore if not desired address family
  20.   /*
  21.   printf("%s %d\n",(*ifas).ifa_name,((*ifas).ifa_addr)->sa_family);
  22.   if(((*ifas).ifa_addr)->sa_family!=AF_INET6)
  23.   inet_ntop(AF_INET,&(((struct sockaddr_in *)((*ifas).ifa_addr))->sin_addr),addr,sizeof(addr));
  24.   else
  25.   inet_ntop(AF_INET6,&(((struct sockaddr_in6 *)((*ifas).ifa_addr))->sin6_addr),addr,sizeof(addr));
  26.   printf("%s\t",addr);
  27.   printf("\n");
  28.   */
  29.   ifi = (struct ifi_info*)calloc(1,sizeof(struct ifi_info));
  30.   *ifipnext = ifi;
  31.   ifipnext = &ifi->ifi_next;
  32.    
  33.   ifi->ifi_flags = (*ifas).ifa_flags;
  34.   memcpy(ifi->ifi_name, (*ifas).ifa_name, IFI_NAME);
  35.   ifi->ifi_name[IFI_NAME-1] = '\0';
  36.    
  37.   switch (((*ifas).ifa_addr)->sa_family) {
  38.   case AF_INET:
  39.   sinptr = (struct sockaddr_in *) (*ifas).ifa_addr;
  40.   ifi->ifi_addr = (struct sockaddr*)calloc(1, sizeof(struct sockaddr_in));
  41.   memcpy(ifi->ifi_addr, sinptr, sizeof(struct sockaddr_in));
  42.   #ifdef SIOCGIFBRDADDR
  43.   if (ifi->ifi_flags & IFF_BROADCAST) {
  44.   sinptr = (struct sockaddr_in *) (*ifas).ifa_broadaddr;
  45.   ifi->ifi_brdaddr = (struct sockaddr*)calloc(1, sizeof(struct sockaddr_in));
  46.   memcpy(ifi->ifi_brdaddr, sinptr, sizeof(struct sockaddr_in));
  47.   }
  48.   #endif
  49.   break;
  50.    
  51.   case AF_INET6:
  52.   sin6ptr = (struct sockaddr_in6 *) (*ifas).ifa_addr;
  53.   ifi->ifi_addr = (struct sockaddr*)calloc(1, sizeof(struct sockaddr_in6));
  54.   memcpy(ifi->ifi_addr, sin6ptr, sizeof(struct sockaddr_in6));
  55.   break;
  56.    
  57.   default:
  58.   break;
  59.   }
  60.   }
  61.   freeifaddrs(ifas);
  62.    
  63.   return(ifihead);
  64.   }
  65.    
  66.   int main(int argc, char *argv[]) {
  67.   int family;
  68.   if (argc!=2) {
  69.   printf("usage: ./znet \n");
  70.   exit(1);
  71.   }
  72.   if (strcmp(argv[1], "inet4") == 0)
  73.   family = AF_INET;
  74.   else if (strcmp(argv[1], "inet6") == 0)
  75.   family =AF_INET6;
  76.   else {
  77.   printf("invalid
  78.   \n");
  79.   exit(1);
  80.   }
  81.    
  82.   char addr[128];
  83.   struct ifi_info *ifi, *ifihead;
  84.   printf("name\tflag\tIP\t\tbroadcastaddr\n");
  85.   for (ifihead = ifi = get_ifi_info(family,1); ifi != NULL; ifi = ifi->ifi_next) {
  86.    
  87.   printf("%s\t",ifi->ifi_name);
  88.   printf("%d\t",ifi->ifi_flags);
  89.   if((ifi->ifi_addr)->sa_family!=AF_INET6)
  90.   inet_ntop(AF_INET,&(((struct sockaddr_in *)(ifi->ifi_addr))->sin_addr),addr,sizeof(addr));
  91.   else
  92.   inet_ntop(AF_INET6,&(((struct sockaddr_in6 *)(ifi->ifi_addr))->sin6_addr),addr,sizeof(addr));
  93.   printf("%s\t",addr);
  94.   #ifdef SIOCGIFBRDADDR
  95.   if ((ifi->ifi_flags & IFF_BROADCAST) && (ifi->ifi_addr)->sa_family!=AF_INET6) {
  96.   inet_ntop(AF_INET,&(((struct sockaddr_in *) (ifi->ifi_brdaddr))->sin_addr),addr,sizeof(addr));
  97.   printf("%s\t",addr);
  98.   }
  99.   #endif
  100.   printf("\n+++++++++++++++++++++++++++++++++++++++++++\n");
  101.   }
  102.   return 0;
  103.   }

这段代码输出如下:

[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.   1 /* Upon startup we insert all the elements in inetsw_array[] into
  2.   2 * the linked list inetsw.
  3.   3 */
  4.   4 static struct inet_protosw inetsw_array[] =
  5.   5 {
  6.   6 {
  7.   7 .type = SOCK_STREAM,
  8.   8 .protocol = IPPROTO_TCP,
  9.   9 .prot = &tcp_prot,
  10.   10 .ops = &inet_stream_ops,
  11.   11 .flags = INET_PROTOSW_PERMANENT |
  12.   12 INET_PROTOSW_ICSK,
  13.   13 },
  14.   14
  15.   15 {
  16.   16 .type = SOCK_DGRAM,
  17.   17 .protocol = IPPROTO_UDP,
  18.   18 .prot = &udp_prot,
  19.   19 .ops = &inet_dgram_ops,
  20.   20 .flags = INET_PROTOSW_PERMANENT,
  21.   21 },
  22.   22
  23.   23 {
  24.   24 .type = SOCK_DGRAM,
  25.   25 .protocol = IPPROTO_ICMP,
  26.   26 .prot = &ping_prot,
  27.   27 .ops = &inet_sockraw_ops,
  28.   28 .flags = INET_PROTOSW_REUSE,
  29.   29 },
  30.   30
  31.   31 {
  32.   32 .type = SOCK_RAW,
  33.   33 .protocol = IPPROTO_IP, /* wild card */
  34.   34 .prot = &raw_prot,
  35.   35 .ops = &inet_sockraw_ops,
  36.   36 .flags = INET_PROTOSW_REUSE,
  37.   37 }
  38.   38 };

复制代码

 

查看inet_stream_ops结构会发现,其中包含了各种socket系统调用的对应的处理函数;

复制代码

  1.   1 const struct proto_ops inet_stream_ops = {
  2.   2 .family = PF_INET,
  3.   3 .owner = THIS_MODULE,
  4.   4 .release = inet_release,
  5.   5 .bind = inet_bind,
  6.   6 .connect = inet_stream_connect,
  7.   7 .socketpair = sock_no_socketpair,
  8.   8 .accept = inet_accept,
  9.   9 .getname = inet_getname,
  10.   10 .poll = tcp_poll,
  11.   11 .ioctl = inet_ioctl,
  12.   12 .listen = inet_listen,
  13.   13 .shutdown = inet_shutdown,
  14.   14 .setsockopt = sock_common_setsockopt,
  15.   15 .getsockopt = sock_common_getsockopt,
  16.   16 .sendmsg = inet_sendmsg,
  17.   17 .recvmsg = inet_recvmsg,
  18.   18 .mmap = sock_no_mmap,
  19.   19 .sendpage = inet_sendpage,
  20.   20 .splice_read = tcp_splice_read,
  21.   21 .read_sock = tcp_read_sock,
  22.   22 .peek_len = tcp_peek_len,
  23.   23 #ifdef CONFIG_COMPAT
  24.   24 .compat_setsockopt = compat_sock_common_setsockopt,
  25.   25 .compat_getsockopt = compat_sock_common_getsockopt,
  26.   26 .compat_ioctl = inet_compat_ioctl,
  27.   27 #endif
  28.   28 };

复制代码

 

查看tcp_prot可见,其中对应了实现了传输层tcp的各种socket操作;

复制代码

  1.   1 struct proto tcp_prot = {
  2.   2 .name = "TCP",
  3.   3 .owner = THIS_MODULE,
  4.   4 .close = tcp_close,
  5.   5 .connect = tcp_v4_connect,
  6.   6 .disconnect = tcp_disconnect,
  7.   7 .accept = inet_csk_accept,
  8.   8 .ioctl = tcp_ioctl,
  9.   9 .init = tcp_v4_init_sock,
  10.   10 .destroy = tcp_v4_destroy_sock,
  11.   11 .shutdown = tcp_shutdown,
  12.   12 .setsockopt = tcp_setsockopt,
  13.   13 .getsockopt = tcp_getsockopt,
  14.   14 .keepalive = tcp_set_keepalive,
  15.   15 .recvmsg = tcp_recvmsg,
  16.   16 .sendmsg = tcp_sendmsg,
  17.   17 .sendpage = tcp_sendpage,
  18.   18 .backlog_rcv = tcp_v4_do_rcv,
  19.   19 .release_cb = tcp_release_cb,
  20.   20 .hash = inet_hash,
  21.   21 .unhash = inet_unhash,
  22.   22 .get_port = inet_csk_get_port,
  23.   23 .enter_memory_pressure = tcp_enter_memory_pressure,
  24.   24 .stream_memory_free = tcp_stream_memory_free,
  25.   25 .sockets_allocated = &tcp_sockets_allocated,
  26.   26 .orphan_count = &tcp_orphan_count,
  27.   27 .memory_allocated = &tcp_memory_allocated,
  28.   28 .memory_pressure = &tcp_memory_pressure,
  29.   29 .sysctl_mem = sysctl_tcp_mem,
  30.   30 .sysctl_wmem = sysctl_tcp_wmem,
  31.   31 .sysctl_rmem = sysctl_tcp_rmem,
  32.   32 .max_header = MAX_TCP_HEADER,
  33.   33 .obj_size = sizeof(struct tcp_sock),
  34.   34 .slab_flags = SLAB_TYPESAFE_BY_RCU,
  35.   35 .twsk_prot = &tcp_timewait_sock_ops,
  36.   36 .rsk_prot = &tcp_request_sock_ops,
  37.   37 .h.hashinfo = &tcp_hashinfo,
  38.   38 .no_autobind = true,
  39.   39 #ifdef CONFIG_COMPAT
  40.   40 .compat_setsockopt = compat_tcp_setsockopt,
  41.   41 .compat_getsockopt = compat_tcp_getsockopt,
  42.   42 #endif
  43.   43 .diag_destroy = tcp_abort,
  44.   44 };

复制代码

 

 

具体实例,以tcp bind系统调用为例,其中红色部分为上面提到的步骤:

复制代码

  1.   1 /*
  2.   2 * Bind a name to a socket. Nothing much to do here since it's
  3.   3 * the protocol's responsibility to handle the local address.
  4.   4 *
  5.   5 * We move the socket address to kernel space before we call
  6.   6 * the protocol layer (having also checked the address is ok).
  7.   7 */
  8.   8
  9.   9 SYSCALL_DEFINE3(bind, int, fd, struct sockaddr __user *, umyaddr, int, addrlen)
  10.   10 {
  11.   11 struct socket *sock;
  12.   12 struct sockaddr_storage address;
  13.   13 int err, fput_needed;
  14.   14
  15.   15 /* 获取socket ,fput_need标识是否需要减少文件引用计数*/
  16.   16 sock = sockfd_lookup_light(fd, &err, &fput_needed);
  17.   17 if (sock) {
  18.   18 /* 将用户空间地址复制到内核空间 */
  19.   19 err = move_addr_to_kernel(umyaddr, addrlen, &address);
  20.   20 if (err >= 0) {
  21.   21 /* 安全模块的bind检查 */
  22.   22 err = security_socket_bind(sock,
  23.   23 (struct sockaddr *)&address,
  24.   24 addrlen);
  25.   25 if (!err)
  26.   26 /* 调用socket的bind操作 */
  27.   27 err = sock->ops->bind(sock,
  28.   28 (struct sockaddr *)
  29.   29 &address, addrlen);
  30.   30 }
  31.   31
  32.   32 /* 根据fput_needed决定是否减少引用计数 */
  33.   33 fput_light(sock->file, fput_needed);
  34.   34 }
  35.   35 return err;
  36.   36 }

复制代码

 

上面红色的sock->ops->bind操作实际是调用了inet_stream_ops.bind

复制代码

  1.   1 /* 地址绑定 */
  2.   2 int inet_bind(struct socket *sock, struct sockaddr *uaddr, int addr_len)
  3.   3 {
  4.   4 struct sockaddr_in *addr = (struct sockaddr_in *)uaddr;
  5.   5 struct sock *sk = sock->sk;
  6.   6 struct inet_sock *inet = inet_sk(sk);
  7.   7 struct net *net = sock_net(sk);
  8.   8 unsigned short snum;
  9.   9 int chk_addr_ret;
  10.   10 u32 tb_id = RT_TABLE_LOCAL;
  11.   11 int err;
  12.   12
  13.   13 /* If the socket has its own bind function then use it. (RAW) */
  14.   14 /*
  15.   15 如果传输控制块有自己的bind操作则调用,
  16.   16 目前只有raw实现了自己的bind
  17.   17 */
  18.   18 if (sk->sk_prot->bind) {
  19.   19 err = sk->sk_prot->bind(sk, uaddr, addr_len);
  20.   20 goto out;
  21.   21 }
  22.   22
  23.   23 err = -EINVAL;
  24.   24 /* 地址长度错误 */
  25.   25 if (addr_len < sizeof(struct sockaddr_in))
  26.   26 goto out;
  27.   27
  28.   28 /* 如果不是AF_INET协议族 */
  29.   29 if (addr->sin_family != AF_INET) {
  30.   30 /* Compatibility games : accept AF_UNSPEC (mapped to AF_INET)
  31.   31 * only if s_addr is INADDR_ANY.
  32.   32 */
  33.   33 err = -EAFNOSUPPORT;
  34.   34
  35.   35 /* 接受AF_UNSPEC && s_addr=htonl(INADDR_ANY)的情况 */
  36.   36 if (addr->sin_family != AF_UNSPEC ||
  37.   37 addr->sin_addr.s_addr != htonl(INADDR_ANY))
  38.   38 goto out;
  39.   39 }
  40.   40
  41.   41 tb_id = l3mdev_fib_table_by_index(net, sk->sk_bound_dev_if) ? : tb_id;
  42.   42 chk_addr_ret = inet_addr_type_table(net, addr->sin_addr.s_addr, tb_id);
  43.   43
  44.   44 /* Not specified by any standard per-se, however it breaks too
  45.   45 * many applications when removed. It is unfortunate since
  46.   46 * allowing applications to make a non-local bind solves
  47.   47 * several problems with systems using dynamic addressing.
  48.   48 * (ie. your servers still start up even if your ISDN link
  49.   49 * is temporarily down)
  50.   50 */
  51.   51 err = -EADDRNOTAVAIL;
  52.   52
  53.   53 /* 合法性检查 */
  54.   54 if (!net->ipv4.sysctl_ip_nonlocal_bind &&
  55.   55 !(inet->freebind || inet->transparent) &&
  56.   56 addr->sin_addr.s_addr != htonl(INADDR_ANY) &&
  57.   57 chk_addr_ret != RTN_LOCAL &&
  58.   58 chk_addr_ret != RTN_MULTICAST &&
  59.   59 chk_addr_ret != RTN_BROADCAST)
  60.   60 goto out;
  61.   61
  62.   62 /* 源端口 */
  63.   63 snum = ntohs(addr->sin_port);
  64.   64 err = -EACCES;
  65.   65
  66.   66 /* 绑定特权端口的权限检查 */
  67.   67 if (snum && snum < inet_prot_sock(net) &&
  68.   68 !ns_capable(net->user_ns, CAP_NET_BIND_SERVICE))
  69.   69 goto out;
  70.   70
  71.   71 /* We keep a pair of addresses. rcv_saddr is the one
  72.   72 * used by hash lookups, and saddr is used for transmit.
  73.   73 *
  74.   74 * In the BSD API these are the same except where it
  75.   75 * would be illegal to use them (multicast/broadcast) in
  76.   76 * which case the sending device address is used.
  77.   77 */
  78.   78 lock_sock(sk);
  79.   79
  80.   80 /* Check these errors (active socket, double bind). */
  81.   81 err = -EINVAL;
  82.   82
  83.   83 /* 传输控制块的状态不是CLOSE || 存在本地端口 */
  84.   84 if (sk->sk_state != TCP_CLOSE || inet->inet_num)
  85.   85 goto out_release_sock;
  86.   86
  87.   87 /* 设置源地址rcv_addr用作hash查找,saddr用作传输 */
  88.   88 inet->inet_rcv_saddr = inet->inet_saddr = addr->sin_addr.s_addr;
  89.   89
  90.   90 /* 组播或者广播,使用设备地址 */
  91.   91 if (chk_addr_ret == RTN_MULTICAST || chk_addr_ret == RTN_BROADCAST)
  92.   92 inet->inet_saddr = 0; /* Use device */
  93.   93
  94.   94 /* Make sure we are allowed to bind here. */
  95.   95
  96.   96 /*
  97.   97 端口不为0,或者端口为0允许绑定
  98.   98 则使用协议的具体获取端口函数绑定端口
  99.   99 */
  100.   100 if ((snum || !inet->bind_address_no_port) &&
  101.   101 sk->sk_prot->get_port(sk, snum)) {
  102.   102
  103.   103 /* 绑定失败 */
  104.   104 inet->inet_saddr = inet->inet_rcv_saddr = 0;
  105.   105
  106.   106 /* 端口在使用中 */
  107.   107 err = -EADDRINUSE;
  108.   108 goto out_release_sock;
  109.   109 }
  110.   110
  111.   111 /* 传输控制块已经绑定本地地址或端口标志 */
  112.   112 if (inet->inet_rcv_saddr)
  113.   113 sk->sk_userlocks |= SOCK_BINDADDR_LOCK;
  114.   114 if (snum)
  115.   115 sk->sk_userlocks |= SOCK_BINDPORT_LOCK;
  116.   116
  117.   117 /* 设置源端口 */
  118.   118 inet->inet_sport = htons(inet->inet_num);
  119.   119
  120.   120 /* 设置目的地址和端口默认值 */
  121.   121 inet->inet_daddr = 0;
  122.   122 inet->inet_dport = 0;
  123.   123
  124.   124 /* 设置路由默认值 */
  125.   125 sk_dst_reset(sk);
  126.   126 err = 0;
  127.   127 out_release_sock:
  128.   128 release_sock(sk);
  129.   129 out:
  130.   130 return err;
  131.   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.   1 /* Obtain a reference to a local port for the given sock,
  2.   2 * if snum is zero it means select any available local port.
  3.   3 * We try to allocate an odd port (and leave even ports for connect())
  4.   4 */
  5.   5 int inet_csk_get_port(struct sock *sk, unsigned short snum)
  6.   6 {
  7.   7 bool reuse = sk->sk_reuse && sk->sk_state != TCP_LISTEN;
  8.   8 struct inet_hashinfo *hinfo = sk->sk_prot->h.hashinfo;
  9.   9 int ret = 1, port = snum;
  10.   10 struct inet_bind_hashbucket *head;
  11.   11 struct net *net = sock_net(sk);
  12.   12 struct inet_bind_bucket *tb = NULL;
  13.   13 kuid_t uid = sock_i_uid(sk);
  14.   14
  15.   15 if (!port) {
  16.   16 head = inet_csk_find_open_port(sk, &tb, &port);
  17.   17 if (!head)
  18.   18 return ret;
  19.   19 if (!tb)
  20.   20 goto tb_not_found;
  21.   21 goto success;
  22.   22 }
  23.   23 head = &hinfo->bhash[inet_bhashfn(net, port,
  24.   24 hinfo->bhash_size)];
  25.   25 spin_lock_bh(&head->lock);
  26.   26 inet_bind_bucket_for_each(tb, &head->chain)
  27.   27 if (net_eq(ib_net(tb), net) && tb->port == port)
  28.   28 goto tb_found;
  29.   29 tb_not_found:
  30.   30 tb = inet_bind_bucket_create(hinfo->bind_bucket_cachep,
  31.   31 net, head, port);
  32.   32 if (!tb)
  33.   33 goto fail_unlock;
  34.   34 tb_found:
  35.   35 if (!hlist_empty(&tb->owners)) {
  36.   36 if (sk->sk_reuse == SK_FORCE_REUSE)
  37.   37 goto success;
  38.   38
  39.   39 if ((tb->fastreuse > 0 && reuse) ||
  40.   40 sk_reuseport_match(tb, sk))
  41.   41 goto success;
  42.   42 if (inet_csk_bind_conflict(sk, tb, true, true))
  43.   43 goto fail_unlock;
  44.   44 }
  45.   45 success:
  46.   46 if (!hlist_empty(&tb->owners)) {
  47.   47 tb->fastreuse = reuse;
  48.   48 if (sk->sk_reuseport) {
  49.   49 tb->fastreuseport = FASTREUSEPORT_ANY;
  50.   50 tb->fastuid = uid;
  51.   51 tb->fast_rcv_saddr = sk->sk_rcv_saddr;
  52.   52 tb->fast_ipv6_only = ipv6_only_sock(sk);
  53.   53 #if IS_ENABLED(CONFIG_IPV6)
  54.   54 tb->fast_v6_rcv_saddr = sk->sk_v6_rcv_saddr;
  55.   55 #endif
  56.   56 } else {
  57.   57 tb->fastreuseport = 0;
  58.   58 }
  59.   59 } else {
  60.   60 if (!reuse)
  61.   61 tb->fastreuse = 0;
  62.   62 if (sk->sk_reuseport) {
  63.   63 /* We didn't match or we don't have fastreuseport set on
  64.   64 * the tb, but we have sk_reuseport set on this socket
  65.   65 * and we know that there are no bind conflicts with
  66.   66 * this socket in this tb, so reset our tb's reuseport
  67.   67 * settings so that any subsequent sockets that match
  68.   68 * our current socket will be put on the fast path.
  69.   69 *
  70.   70 * If we reset we need to set FASTREUSEPORT_STRICT so we
  71.   71 * do extra checking for all subsequent sk_reuseport
  72.   72 * socks.
  73.   73 */
  74.   74 if (!sk_reuseport_match(tb, sk)) {
  75.   75 tb->fastreuseport = FASTREUSEPORT_STRICT;
  76.   76 tb->fastuid = uid;
  77.   77 tb->fast_rcv_saddr = sk->sk_rcv_saddr;
  78.   78 tb->fast_ipv6_only = ipv6_only_sock(sk);
  79.   79 #if IS_ENABLED(CONFIG_IPV6)
  80.   80 tb->fast_v6_rcv_saddr = sk->sk_v6_rcv_saddr;
  81.   81 #endif
  82.   82 }
  83.   83 } else {
  84.   84 tb->fastreuseport = 0;
  85.   85 }
  86.   86 }
  87.   87 if (!inet_csk(sk)->icsk_bind_hash)
  88.   88 inet_bind_hash(sk, tb, port);
  89.   89 WARN_ON(inet_csk(sk)->icsk_bind_hash != tb);
  90.   90 ret = 0;
  91.   91
  92.   92 fail_unlock:
  93.   93 spin_unlock_bh(&head->lock);
  94.   94 return ret;
  95.   95 }
  96.   96 EXPORT_SYMBOL_GPL(inet_csk_get_port);
  文章知识点与官方知识档案匹配,可进一步学习相关知识 CS入门技能树Linux入门初识Linux26453 人正在系统学习中 chenpuo 关注 获取本机MAC地址函数

标签:sockaddr,调用,addr,ifr,socket,Linux,ifi,inet,struct
From: https://www.cnblogs.com/blj28/p/17115316.html

相关文章

  • linux 下 dynv6 更新 ddns 域名对应ip
    基本原理:1.通过某些网站提供的api返回公网ip2.与本地文件记录的上次ip对照,如果相同,结束;如果不同使用 dynv6提供的api进行更新3.将上述逻辑写成脚本,利用......
  • Sockets / net Module Review
    Node'snetModuleWhat'sthe net moduleusedfor? →It'sforcreatingTCP/IPserversandclients.Whataretheobjectsthatweusedwhencreatingaserver?......
  • Networking and Sockets
    SomeDefinitionsBeforewestart,weshouldprobablygetafewdefinitionsoutoftheway!ipaddress -numbergiventoacomputer/deviceonanetworkport......
  • https//mirrors.tuna.tsinghua.edu.cn/anaconda/cloud/bioconda/linux-64'
    CondaHTTPError:HTTP000CONNECTIONFAILEDforurl<https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud/bioconda/linux-64/current_repodata.json> Elap......
  • Linux命令篇 - tar 命令
    tarGNU`tar'savesmanyfilestogetherintoasingletapeordiskarchive,andcanrestoreindividualfilesfromthearchive.tar:用于压缩和解压缩文件;格式:tar......
  • Linux入门
    Linux目录结构Linux的目录结构是一个树形结构Windows系统可以有很多盘符,如C盘、D盘、E盘Linux没有盘符这个概念,只有一个根目录/,所有文件都在它下面。在Linux操作系统......
  • MAC 安装docker,下载镜像并压缩转移到LINUX服务器
    MAC安装docker前提-已经安装homebrew(如果未安装,可以打开终端,输入以下命令安装Homebrew)/usr/bin/ruby-e"$(curl-fsSLhttps://raw.githubusercontent.com/Homebrew/i......
  • linux007之文件、目录操作命令
    查看当前所在目录:pwd:查看当前所在目录路径ls:查看当前目录所有文件ls路径:查看指定目录下所有文件ls-l:以列的形式查看当前目录所有文件ls-l路......
  • Linux入门
    Linux目录结构Linux的目录结构是一个树形结构Windows系统可以有很多盘符,如C盘、D盘、E盘Linux没有盘符这个概念,只有一个根目录/,所有文件都在它下面。在Linux操作系统中......
  • Linux内核机制—smp_hotplug_thread
    基于Linux-5.10一、简介1.只是一个创建per-cpu线程执行用户提供的回调的机制。2.内核中已存在的注册staticstructsmp_hotplug_threadidle_inject_threads={//......