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])
```