免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
查看: 4667 | 回复: 3

python调用fcntl.ioctl的疑问 [复制链接]

论坛徽章:
11
摩羯座
日期:2013-09-29 17:39:09白羊座
日期:2014-11-13 09:38:14技术图书徽章
日期:2014-01-17 15:07:36狮子座
日期:2013-12-25 14:01:52技术图书徽章
日期:2013-12-17 11:33:22技术图书徽章
日期:2013-12-03 10:27:57天秤座
日期:2013-11-08 15:47:19申猴
日期:2013-10-29 13:16:32未羊
日期:2013-10-12 22:28:56辰龙
日期:2013-10-09 14:39:5515-16赛季CBA联赛之山东
日期:2016-07-25 10:23:00
发表于 2016-05-11 17:39 |显示全部楼层
如题,请教各位,通过python的fcntl模块获取指定网络接口的ip地址时,可以通过以下代码:
  1. #! /bin/env python2.7

  2. import sys, socket, fcntl, struct

  3. SIOCGIFADDR = 0x8915

  4. def get_ip_address(ifname):
  5.     s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
  6.     return socket.inet_ntoa(fcntl.ioctl(
  7.         s.fileno(),
  8.         SIOCGIFADDR,
  9.         struct.pack('256s', ifname[:15])
  10.     )[20:24])

  11. if __name__ == '__main__':
  12.     ifname = sys.argv[1]
  13.     print "Interface %s : %s" %(ifname, get_ip_address(ifname))
复制代码
本质上是使用了struct ifreq结构
  1. struct ifreq
  2. {
  3. #define IFHWADDRLEN        6
  4.         union
  5.         {
  6.                 char        ifrn_name[IFNAMSIZ];
  7.         } ifr_ifrn;
  8.        
  9.         union {
  10.                 struct        sockaddr ifru_addr;
  11.                 struct        sockaddr ifru_dstaddr;
  12.                 struct        sockaddr ifru_broadaddr;
  13.                 struct        sockaddr ifru_netmask;
  14.                 struct  sockaddr ifru_hwaddr;
  15.                 short        ifru_flags;
  16.                 int                ifru_ivalue;
  17.                 int                ifru_mtu;
  18.                 struct  ifmap ifru_map;
  19.                 char        ifru_slave[IFNAMSIZ];
  20.                 char        ifru_newname[IFNAMSIZ];
  21.                 void __user *        ifru_data;
  22.                 struct        if_settings ifru_settings;
  23.         } ifr_ifru;
  24. };
复制代码
通过ifreq.ifr_name指定网卡名称,然后调用ioctl(SIOCGIFADDR),将结果返回到ifreq.ifr_addr
但问题在于,Linux下网卡名称的长度由IFNAMSIZ宏定义,即16字节,为什么Python调用ioctl后,IP地址是保存在第20字节开始的4个字节中,而不是紧随网卡名称的16字节之后?
网卡名称对齐在20字节的边界吗?

此处不解,请各位指点一下,谢谢。

论坛徽章:
11
摩羯座
日期:2013-09-29 17:39:09白羊座
日期:2014-11-13 09:38:14技术图书徽章
日期:2014-01-17 15:07:36狮子座
日期:2013-12-25 14:01:52技术图书徽章
日期:2013-12-17 11:33:22技术图书徽章
日期:2013-12-03 10:27:57天秤座
日期:2013-11-08 15:47:19申猴
日期:2013-10-29 13:16:32未羊
日期:2013-10-12 22:28:56辰龙
日期:2013-10-09 14:39:5515-16赛季CBA联赛之山东
日期:2016-07-25 10:23:00
发表于 2016-05-11 21:10 |显示全部楼层
回复 1# superwujc

自解
  1. # vi show_interface_ip_address_ioctl.c
复制代码

  1. #include <stdio.h>
  2. #include <net/if.h>
  3. #include <sys/ioctl.h>
  4. #include <sys/types.h>
  5. #include <sys/socket.h>
  6. #include <netinet/in.h>
  7. #include <arpa/inet.h>
  8. #include <string.h>
  9. #include <stdlib.h>

  10. int main(int argc, char *argv[])
  11. {

  12.     if (argc != 2) {
  13.         fprintf(stderr, "Usage: %s [network interface name]\n", argv[0]);
  14.         exit(EXIT_FAILURE);
  15.     }   

  16.     int sfd;
  17.     struct ifreq ifr;
  18.     char ipaddr[INET_ADDRSTRLEN] = {'\0'};

  19.     sfd = socket(AF_INET, SOCK_DGRAM, 0);

  20.     memset(&ifr, 0, sizeof(ifr));
  21.     ifr.ifr_addr.sa_family = AF_INET;
  22.     strncpy(ifr.ifr_name, argv[1], IFNAMSIZ-1);

  23.     ioctl(sfd, SIOCGIFADDR, &ifr);
  24.     inet_ntop(AF_INET, &(((struct sockaddr_in *)&ifr.ifr_addr)->sin_addr), ipaddr, INET_ADDRSTRLEN);
  25.     printf("Interface %s : %s\n", argv[1], ipaddr);
  26.    
  27.     close(sfd);

  28.     exit(EXIT_SUCCESS);
  29. }
复制代码
  1. # gcc show_interface_ip_address_ioctl.c -o show_interface_ip_address_ioctl -g
复制代码
  1. # gdb -q show_interface_ip_address_ioctl
复制代码

  1. Reading symbols from /code/ifopt/show_interface_ip_address_ioctl...done.
  2. (gdb) #
  3. (gdb)
  4. (gdb) set args eth0
  5. (gdb) #
  6. (gdb)
  7. (gdb)
  8. (gdb) break main
  9. Breakpoint 1 at 0x400754: file show_interface_ip_address_ioctl.c, line 14.
  10. (gdb) #
  11. (gdb)
  12. (gdb)
  13. (gdb) run
  14. Starting program: /code/ifopt/show_interface_ip_address_ioctl eth0

  15. Breakpoint 1, main (argc=2, argv=0x7fffffffe528)
  16.     at show_interface_ip_address_ioctl.c:14
  17. 14                if (argc != 2) {
  18. (gdb) #
  19. (gdb)
  20. (gdb)
  21. (gdb) n 6
  22. 29                ioctl(sfd, SIOCGIFADDR, &ifr);
  23. (gdb) n
  24. 30                inet_ntop(AF_INET, &(((struct sockaddr_in *)&ifr.ifr_addr)->sin_addr), ipaddr, INET_ADDRSTRLEN);
  25. (gdb) #
  26. (gdb)
  27. (gdb)
  28. (gdb) # struct ifreq的地址
  29. (gdb) p &ifr
  30. $1 = (struct ifreq *) 0x7fffffffe400
  31. (gdb) #
  32. (gdb)
  33. (gdb)
  34. (gdb)
  35. (gdb) # 网卡名称
  36. (gdb) p ifr.ifr_ifrn.ifrn_name
  37. $2 = "eth0", '\000' <repeats 11 times>
  38. (gdb) #
  39. (gdb)
  40. (gdb)
  41. (gdb) # 网卡名称所在的地址
  42. (gdb) p &(ifr.ifr_ifrn.ifrn_name)
  43. $3 = (char (*)[16]) 0x7fffffffe400
  44. (gdb) #
  45. (gdb)
  46. (gdb)
  47. (gdb) # 通用套接字地址结构struct sockaddr的地址
  48. (gdb) p &(ifr.ifr_ifru.ifru_addr)
  49. $4 = (struct sockaddr *) 0x7fffffffe410
  50. (gdb) #
  51. (gdb)
  52. (gdb)
  53. (gdb) # ipv4套接字地址结构struct sockaddr_in中,协议族字段的地址
  54. (gdb) p &(((struct sockaddr_in *)&(ifr.ifr_ifru.ifru_addr))->sin_family)
  55. $5 = (sa_family_t *) 0x7fffffffe410
  56. (gdb) #
  57. (gdb)
  58. (gdb)
  59. (gdb) # ipv4套接字地址结构中,ipv4地址字段的地址
  60. (gdb) p &(((struct sockaddr_in *)&(ifr.ifr_ifru.ifru_addr))->sin_addr)
  61. $6 = (struct in_addr *) 0x7fffffffe414
  62. (gdb) #
  63. (gdb)
  64. (gdb)
  65. (gdb) p &((&(((struct sockaddr_in *)&(ifr.ifr_ifru.ifru_addr))->sin_addr))->s_addr)
  66. $7 = (in_addr_t *) 0x7fffffffe414
  67. (gdb)
复制代码
调用ioctl(SIOCGIFADDR)后,ip地址返回到struct ifreq中,地址为0x7fffffffe400,其中,网卡名称的16字节地址范围0x7fffffffe400 ~ 0x7fffffffe40F
随后为通用套接字地址结构,转换为ipv4套接字地址结构后,协议字段占据自0x7fffffffe410开始的4字节,而保存ipv4地址的struct in_addr自0x7fffffffe414开始,与struct ifreq的0x7fffffffe400偏移为0x14字节,即10进制的20字节

论坛徽章:
2
2015年辞旧岁徽章
日期:2015-03-03 16:54:152015年迎新春徽章
日期:2015-03-04 09:57:09
发表于 2016-05-14 20:50 |显示全部楼层
本帖最后由 lolizeppelin 于 2016-05-14 21:12 编辑

ifrn_name后面 也就是16 后面的地址不是字符串是一个结构体

struct        sockaddr ifru_addr;

struct sockaddr

{

unsigned short sa_family;/*addressfamily,AF_xxx*/

char sa_data[14];/*14bytes of protocol address*/

};

有定义
#define ifr_addr ifr_ifru.ifru_addr


ioctl的部分
int dn_dev_ioctl(unsigned int cmd, void __user *arg)
{
        char buffer[DN_IFREQ_SIZE];
        struct ifreq *ifr = (struct ifreq *)buffer;
        struct sockaddr_dn *sdn = (struct sockaddr_dn *)&ifr->ifr_addr;
        struct dn_dev *dn_db;
        struct net_device *dev;
        struct dn_ifaddr *ifa = NULL;
        struct dn_ifaddr __rcu **ifap = NULL;
        int ret = 0;




ifreq.ifr_ifru.ifru_addr被换成了
struct sockaddr_dn

也就是整个sockaddr类型变成了sockaddr_dn


最后
case SIOCGIFADDR:  
        *((__le16 *)sdn->sdn_nodeaddr) = ifa->ifa_local;  #  in_ifaddr  中 __be32                        ifa_local;



#define sdn_nodeaddrl   sdn_add.a_len   /* Node address length  */

struct sockaddr_dn {
         __u16           sdn_family;   #unsigned short   2
         __u8            sdn_flags;      # unsigned char   1
         __u8            sdn_objnum;                             1
         __le16          sdn_objnamel;   #unsigned short  2
         __u8            sdn_objname[DN_MAXOBJL];     #define DN_MAXOBJL        16
         struct   dn_naddr       sdn_add;   2 + 2
};


struct dn_naddr {
        __le16          a_len;
        __u8 a_addr[DN_MAXADDL]; /* Two bytes little endian */ 2
};


前面 3个结构
         __u16           sdn_family;   #unsigned short   2
         __u8            sdn_flags;      # unsigned char   1
         __u8            sdn_objnum;                             1

加起来........?额 我也有点混乱了

论坛徽章:
2
2015年辞旧岁徽章
日期:2015-03-03 16:54:152015年迎新春徽章
日期:2015-03-04 09:57:09
发表于 2016-08-01 19:13 |显示全部楼层
本帖最后由 lolizeppelin 于 2016-08-01 19:20 编辑

看了部分socket创建和ioctl的内核代码终于搞清楚了

ifreq在网卡名后面用的是(也就是16+字节后面)
struct sockaddr
{
unsigned short sa_family;/*addressfamily,AF_xxx*/
char sa_data[14];/*14bytes of protocol address*/
};

这里就产生了问题,为什么不是18字节就开始是IP而是20?

看到inet_ntop(AF_INET, &(((struct sockaddr_in *)&ifr.ifr_addr)->sin_addr), ipaddr, INET_ADDRSTRLEN);
这里强制将sockaddr转换成了sockaddr_in

struct sockaddr_in {
    __kernel_sa_family_t        sin_family;        /* Address family                */
    __be16                sin_port;        /* Port number                        */
    struct in_addr        sin_addr;        /* Internet address                */

    /* Pad to size of `struct sockaddr'. */
    unsigned char                __pad[__SOCK_SIZE__ - sizeof(short int) -
                               sizeof(unsigned short int) - sizeof(struct in_addr)];
};

也就是说网卡的IP不是从18开始,而是还要后移两位(端口),也就是20开始才是IP


读了内核相关部分找到
获取IP最后走的是int devinet_ioctl
int devinet_ioctl(struct net *net, unsigned int cmd, void *arg)
{
        struct ifreq ifr;
        struct sockaddr_in sin_orig;
        struct sockaddr_in *sin = (struct sockaddr_in *)&ifr.ifr_addr;

可以看到一开始就做了强制转换

到后面后面返回如下
        switch (cmd) {
        case SIOCGIFADDR:        /* Get interface address */
                # 匹配到网卡拷贝到sin的sockaddr_in
                sin->sin_addr.s_addr = ifa->ifa_local;
                goto rarok;


python里面
#!/usr/bin/python
# -*- coding: UTF-8 -*-

import socket
import struct
import fcntl

SIOCGIFADDR = 0x8915
ifname = 'eth0'

def get_ip_address(ifname):
    s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    struct_ifaddr = fcntl.ioctl(s.fileno(),SIOCGIFADDR,struct.pack('32s', ifname[:15]))
    a,b,c,d = struct.unpack('16sHH12s', struct_ifaddr)

    print a # eth0
    print b # AF_INET
    print c # port
    ip =  struct.unpack('1s1s1s1s', d[0:4])

    for a in ip:
        print ord(a),
    print ''
    #return socket.inet_ntoa(struct_ifaddr[20:24])

get_ip_address('eth0')




感谢楼主让我逼自己开始读内核  233

您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP