定义数据包IP头结构体
使用模块 socket & ctypes & struct
1 | class IP(Structure): |
new() & init()
new() 静态方法. 实例化开始后,在init()之前调用. 没有重写,会自动调用父类;
new()会返回cls的实例,然后该类的init()方法作为构造方法会接收这个实例(即self)作为自己的第一个参数,然后依次传入new()方法中接收的位置参数和命名参数;
如果new()没有返回cls(即当前类)的实例,那么当前类的init()方法是不会被调用的;
如果new()返回其他类(自定义类或系统类均可)的实例,那么只会调用被返回的那个类的构造方法.
模块ctypes
Structures和Unions必须继承Structure和Union基础类,它们都在ctypes模块中定义;
每一个子类必须定义fields属性,fields是一个二维的tuples列表,包含着每个field的name及type;
field类型必须是一个ctypes类型,如c_int,或者任何其他的继承ctypes的类型,如Structure, Union, Array, 指针等.
模块struct
pack(fmt, v1, v2, …)
按照给定的格式(fmt),把数据封装成字符串(实际上是类似于c结构体的字节流)unpack(fmt, string)
按照给定的格式(fmt)解析字节流string,返回解析出来的tuplecalcsize(fmt)
计算给定的格式(fmt)占用多少字节的内存
获取数据包IP头数据
1 | if os.name == "nt": |
修改代码适应不同平台
在书中给的例子中,结构体中变量src和dst是c_long类型,但c_ulong在i386是4bytes而在amd64是8bytes, 会运行失败。所以换成了c_uint32, 可以不考虑平台.
获取头数据时,书中所给代码ip_header = IP(raw_buffer[0:20])会造成运行失败,原因是在i386上头基本数据长度是20字节,而在amd64上是32字节。所有平台都可通过的写法是IP(raw_buffer)。
扩展 -> 内存对齐
基本类型不包括struct/class/uinon
原则:
基本类型数据成员对齐规则:结构(struct)(或联合(union))的数据成员,第一个数据成员放在offset为0的地方,结构体每个成员相对于结构体首地址的偏移量(offset)都是成员大小的整数倍,如有需要编译器会在成员之间加上填充字节(internal adding).
比如: int在32位机为4字节, 则要从4的整数倍地址开始存储。结构体作为成员:如果一个结构里有某些结构体成员,则结构体成员要从其内部”最宽基本类型成员”的整数倍地址开始存储.
比如: struct a里存有struct b, b里有char,int ,double等元素,那b应该从8的整数倍开始存储。结构体的总大小,也就是sizeof的结果.必须是其内部最大成员的”最宽基本类型成员”的整数倍, 如有需要编译器会在最末一个成员之后加上填充字节(trailing padding)。
sizeof(union),以结构里面size最大元素为union的size,因为在某一时刻,union只有一个成员真正存储于该地址。