如何透過 Python 來呼叫 ioctl

首先,先來抄一段 wikipedia 的定義:

In computing, ioctl (an abbreviation of input/output control) is a system call for device-specific input/output operations and other operations which cannot be expressed by regular system calls. It takes a parameter specifying a request code; the effect of a call depends completely on the request code. Request codes are often device-specific.

簡單來說,ioctl 是針對 device 去做輸入輸出(白話一點,get/set)操作的一種方式,在 Linux 底下,所有的 kernel module 都可以被視做是一個 device,所以把 ioctl 當作一種 UK 的溝通機制也未嘗不可。不過如果要傳大量資料的話還是採用其他的方式比較妥當,像是 netlink。在溝通的時候,User Space 和 Kernel Space 必須先定義好雙方溝通的格式。最常見的範例基本上就是 ifconfig 這隻程式了,在這裡也不去看它,反正是一支很簡單的 C 程式。

本篇要紀錄的是,在 Python 裡要怎麼呼叫 ioctl。範例很簡單,,就是實作一支類似 ifconfig 的程式(不過只有 get,懶得寫 set)。直接放在下面:


#!/usr/bin/python

import socket
import fcntl
import sys
import struct
import array

def get_if_list():
    try:
        fd = socket.socket( socket.AF_INET, socket.SOCK_DGRAM )
        
        # #define SIOCGIFCONF     0x8912          /* get iface list               */

        max_possible = 8 # initial value
        struct_size = 40
        
        while True:
            
            # struct ifconf {
            #          int                 ifc_len; /* size of buffer */
            #          union {
            #              char           *ifc_buf; /* buffer address */
            #              struct ifreq   *ifc_req; /* array of structures */
            #          };
            #      };
            # #define IFNAMSIZ        16
            # struct ifreq {
            #    char ifr_name[IFNAMSIZ]; /* Interface name */
            #    union {
            #        struct sockaddr ifr_addr;
            #        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;
            #        struct ifmap    ifr_map;
            #        char            ifr_slave[IFNAMSIZ];
            #        char            ifr_newname[IFNAMSIZ];
            #        char           *ifr_data;
            #    };
            # };
            
            # Initialize ifc_buf
            bytes = max_possible * struct_size
            names = array.array( 'B' )
            for i in range( 0, bytes ):
                names.append( 0 )
            
            # names.buffer_info() is ( address, length )
            # So we can treat names.buffer_info()[0] as a pointer in C
            input_buffer = struct.pack( 'iL', bytes, names.buffer_info()[0] )
            
            # #define SIOCGIFCONF     0x8912          /* get iface list               */
            output_buffer = fcntl.ioctl( fd.fileno(), 0x8912, input_buffer )
            output_size = struct.unpack( 'iL', output_buffer )[0]
            
            # If output_size == bytes, there may be more interfaces.
            if output_size == bytes:
                max_possible *= 2
            else:
                break
        
        namestr = names.tostring()
        ifaces = []
        for i in range( 0, output_size, struct_size ):
            iface_name = namestr[ i : i+16 ].split( '\0', 1 )[0]
            ifaces.append( iface_name )
        fd.close()
        return ifaces
        
    except IOError:
        print "Unable to call ioctl with SIOCGIFCONF"

def get_if_info( ifname ):
    
    print ifname + ":"    
    
    try:
        fd = socket.socket( socket.AF_INET, socket.SOCK_DGRAM )
        input_buffer = struct.pack( '256s', ifname )
        
        # #define SIOCGIFADDR     0x8915          /* get PA address
        # #define SIOCGIFBRDADDR  0x8919          /* get broadcast PA address     */
        # #define SIOCGIFNETMASK  0x891b          /* get network PA mask          */
        # #define SIOCGIFMTU      0x8921          /* get MTU size                 */
        # #define SIOCGIFHWADDR   0x8927          /* Get hardware address         */
        
        output_buffer = fcntl.ioctl( fd.fileno(), 0x8915, input_buffer )
        print "\tIP address: " + socket.inet_ntoa( output_buffer[20:24] )
        
        output_buffer = fcntl.ioctl( fd.fileno(), 0x8919, input_buffer )
        print "\tBroadcast: " + socket.inet_ntoa( output_buffer[20:24] )
        
        output_buffer = fcntl.ioctl( fd.fileno(), 0x891b, input_buffer )
        print "\tNetmask: " + socket.inet_ntoa( output_buffer[20:24] )
        
        # I do not care the endian issue here
        output_buffer = fcntl.ioctl( fd.fileno(), 0x8921, input_buffer )
        print "\tMTU: " + str( ord( output_buffer[16] ) + ord( output_buffer[17] ) * 256 )
        
        output_buffer = fcntl.ioctl( fd.fileno(), 0x8927, input_buffer )
        ether_addr = ""
        for c in output_buffer[18:23]:
            ether_addr = ether_addr + format( ord( c ), '02X' ) + ':'
        ether_addr = ether_addr + format( ord( output_buffer[23] ), '02X' )
        print "\tEthernet address: " + ether_addr

        fd.close()
    except IOError:
        print "\tUnable to call ioctl with SIOCGIFADDR on " + ifname
  
if len( sys.argv ) == 2:
    get_if_info( sys.argv[1] )
else:
    ifaces = get_if_list()
    
    if len( ifaces ) > 0: 
        for iface in ifaces:
            get_if_info( iface )
    else:
        print "No available interface"

留言

這個網誌中的熱門文章

如何將Linux打造成OpenFlow Switch:Openvswitch

我弟家的新居感恩禮拜分享:善頌善禱

Linux Virtual Interface: TUN/TAP