Python 取得網路介面卡資訊 === 之前有寫過如何利用netdevice [link](https://hackmd.io/bYGrbS9YQx27PDkD4Duf3w)和wireless.h [link](https://hackmd.io/_gRTSEjmS7e7S11YV0dPLw)取得網路介面卡資訊和無線網路連線資訊。 那時是透過C language和system call ioctl操作獲取結果的。 如果是透過python要如何獲取網路介面卡資訊和無線網路連線資訊呢? 其實方法大同小異,python提供了linux system call ioctl方法和socket方法,步驟和方法是一樣的。 :::info 建議在閱讀本篇文章之前,先了解C語言版本的如何實作[netdevice link](https://hackmd.io/bYGrbS9YQx27PDkD4Duf3w) ::: --- ### 取得interface name: 我們來看一個範例,如下是取得interface name的範例程式[python-ioctl](http://neokentblog.blogspot.com/2016/12/python-ioctl.html) ```python= import socket import fcntl import sys import struct import array ary = array.array('B') fd = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) max_possible = 8 struct_size = 40 byte = max_possible * struct_size names = array.array('B') for i in range(0, byte): names.append(0) input_buffer = struct.pack('iL', byte, names.buffer_info()[0]) output_buffer = fcntl.ioctl(fd.fileno(), 0x8912, input_buffer) output_size = struct.unpack( 'iL', output_buffer )[0] namestr = names.tostring() for i in range( 0, output_size, struct_size ): print(namestr[i : i+16].decode().split('\0',1)[0]) fd.close() ``` 詳細解說之前,我們簡單來看一下C語言是如何實作的。C語言經由socket和ioctl取得interface name,其中經過struct ifconf和struct ifreq這兩個結構解析資訊。 #### C語言取得interface name ```Clike= /** * @brief Get all of interface name. * * @return true * @return false */ bool netinfo_list_all_interface_name(char (*ary)[IFACE_NAME_LENGTH]) { int fd = 0; int ret = 0; // Create an structure of ifreq for save all of interface name. struct ifreq ifr[IFACE_MAXIMUN_LENGTH]; // Create an structure of interface conf. struct ifconf ifc; // Alloc new memory space to save interface information. ifc.ifc_len = IFACE_MAXIMUN_LENGTH * sizeof(struct ifreq); ifc.ifc_buf = (char*)(ifr); // Create socket connection by IPV4, SOCK_DGRAM. fd = socket(AF_INET, SOCK_DGRAM, 0); // Send system call ioctl and argument SIOCGIFCONF. ret = ioctl(fd, SIOCGIFCONF, (char*)&ifc); close(fd); if (ret == -1) { return false; } // The loop will print all of interface name. for (int iface_idx = 0; iface_idx < ifc.ifc_len / sizeof(struct ifreq); iface_idx++) { strncpy(ary[iface_idx], ifr[iface_idx].ifr_name, sizeof(char)*IFACE_NAME_LENGTH); } return true; } ``` #### struct ifconf ```Clike= struct ifconf { int ifc_len; /* size of buffer */ union { char __user *ifcu_buf; struct ifreq __user *ifcu_req; } ifc_ifcu; }; ``` #### struct ifreq ```Clike= struct ifreq { char ifr_name[IFNAMSIZ]; /* 16 */ union { struct sockaddr ifr_addr; // 16 struct sockaddr ifr_dstaddr; struct sockaddr ifr_broadaddr; struct sockaddr ifr_netmask; struct sockaddr ifr_hwaddr; short ifr_flags; int ifr_ifindex; int ifr_metric; int ifr_mtu; // 4 struct ifmap ifr_map; // 24 char ifr_slave[IFNAMSIZ]; char ifr_newname[IFNAMSIZ]; char *ifr_data; }; }; ``` 因此我們在撰寫python的時候,需要經過module struct實際生成這兩個結構。 struct ifconf: ```python= struct.pack('iL', buffer大小, 指向struct ifreq) # struct.pack('iL', int ifc_len, char __user *ifcu_buf) # i 表示第二個參數是int, L表示第三個參數是long ``` struct ifreq: ```python= max_possible = 8 struct_size = 40 byte = max_possible * struct_size names = array.array('B') for i in range(0, byte): names.append(0) ``` :::info 後面會解說為何這樣分配 ::: --- 了解上述之後,我們接著詳細介紹python程式碼。 1. 生成一個array,型別是unisgned char ```python= ary = array.array('B') ``` 2. 開啟一個socket ```python= fd = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) ``` 3. 假設最大interface name數量為8,而每個結構大小為40 bytes ```python= max_possible = 8 struct_size = 40 byte = max_possible * struct_size ``` 這個struct_size=40 bytes是怎麼來的呢? 這就要說到C語言的作法: 透過struct ifreq傳送和接收資料: ```Clike= struct ifreq { char ifr_name[IFNAMSIZ]; /* 16 */ union { struct sockaddr ifr_addr; // 16 struct sockaddr ifr_dstaddr; struct sockaddr ifr_broadaddr; struct sockaddr ifr_netmask; struct sockaddr ifr_hwaddr; short ifr_flags; int ifr_ifindex; int ifr_metric; int ifr_mtu; // 4 struct ifmap ifr_map; // 24 char ifr_slave[IFNAMSIZ]; char ifr_newname[IFNAMSIZ]; char *ifr_data; }; }; ``` 這個結構分兩個部分: ```Clike= struct ifreq { char ifr_name[IFNAMSIZ]; union { ... }; }; ``` 第一部分,大小為16 bytes。 ```Clike= char ifr_name[IFNAMSIZ]; ``` 第二部分,因為是共用記憶體空間,所以取最大的結構struct ifmap,24 bytes。 ```Clike= union { struct sockaddr ifr_addr; // 16 struct sockaddr ifr_dstaddr; struct sockaddr ifr_broadaddr; struct sockaddr ifr_netmask; struct sockaddr ifr_hwaddr; short ifr_flags; int ifr_ifindex; int ifr_metric; int ifr_mtu; // 4 struct ifmap ifr_map; // 24 char ifr_slave[IFNAMSIZ]; char ifr_newname[IFNAMSIZ]; char *ifr_data; }; ``` 合起來是40 bytes。因此,我們總共需要分配320 byte。 ```Clike= byte = max_possible * struct_size ``` 4. 清空這個空間 ```python= names = array.array('B') for i in range(0, byte): names.append(0) ``` 5. 分配到結構之中 ```python= input_buffer = struct.pack('iL', byte, names.buffer_info()[0]) # struct.pack('iL', buffer大小, 指向struct ifreq) ``` ```Clike= struct ifconf { int ifc_len; /* size of buffer */ union { char __user *ifcu_buf; struct ifreq __user *ifcu_req; } ifc_ifcu; }; ``` :::info names 是由module array分配出來的陣列。空間大小為byte = max_possible * struct_size = 320 bytes。 names.buffer_info()回傳[address, length],其中address相當於C語言的pointer。 names.buffer_info()[0]為指標地址。 ::: 6. 發送ioctl(file description, request, pointer to input_buffer) ```python= output_buffer = fcntl.ioctl(fd.fileno(), 0x8912, input_buffer) ``` Request 0x8912被定義在netdevice(7)之中,表示SIOCGIFCONF :::info SIOCGIFCONF = 0x8912 # get iface list ::: 7. 從結構中獲取結果: ```python= output_size = struct.unpack( 'iL', output_buffer)[0] ``` 8. 將結果轉成字串,並從中提取中interface name ```python= namestr = names.tostring() for i in range( 0, output_size, struct_size ): print(namestr[i : i+16].decode().split('\0',1)[0]) ``` :::info 為什麼是namestr[i : i+16]? 因為struct ifreq之中,紀錄interface name的變數ifr_name[IFNAMSIZ],位於前16 bytes。 ::: 9. 關閉socket ```python= fd.close() ``` 上述就是一個簡單取得interface name的方法。 --- ### 取得IP Address: 取得網路介面卡IP不用像取得介面卡名稱一樣,透過struct ifconf,直接使用struct ifreq傳送和接收資料就可以了。 ```python= def ip_addr(self, iface): # A interface require a struct ifreq. A struct ifreq size is 40 bytes(char). input_buffer = struct.pack('40s', bytes(iface, 'utf8')) output_buffer = fcntl.ioctl(self.fd.fileno(), SIOCGIFADDR, input_buffer) return socket.inet_ntoa(output_buffer[20:24]) ``` :::info 上一章節提到過struct ifreq大小為40 bytes。因此,直接分配40 bytes實體空間給結構。 ::: 1. 分配40 bytes空間給結構 ```python= input_buffer = struct.pack('40s', bytes(iface, 'utf8')) ``` 2. system call ioctl 取回 IP ```python= output_buffer = fcntl.ioctl(self.fd.fileno(), SIOCGIFADDR, input_buffer) ``` 3. 將IP轉回10進位 ```python= socket.inet_ntoa(output_buffer[20:24]) ```