Scapy 2.4.0rc5中IKEv2配置载荷15位属性类型字段定义方法
解决Scapy中IKEv2配置载荷15位属性类型的字段选择问题
嘿,我刚好在Scapy里扩展过IKEv2的配置载荷,对RFC7296第3.15节的这个属性类型字段很熟悉。首先明确下:配置属性的类型字段是16位结构——最高1位是R位(Reserved/Request标志),剩下15位是实际的属性类型枚举值。Scapy里没有直接的“15位枚举字段”,所以我们有两个实用的方案:
方案1:用ShortEnumField+辅助方法(最快捷)
ShortEnumField是Scapy内置的16位枚举字段,刚好能容纳整个16位的属性类型字段。我们只需要额外处理R位的提取和设置即可,不用从零实现字段逻辑。
示例代码
from scapy.fields import ShortEnumField, FieldLenField, StrField from scapy.packet import Packet # 先定义RFC7296里的配置属性类型枚举(只填低15位的数值) IKEv2_CONFIG_ATTR_TYPES = { 1: "INTERNAL_IP4_ADDRESS", 2: "INTERNAL_IP4_NETMASK", 3: "INTERNAL_IP4_DNS", 4: "INTERNAL_IP4_NBNS", 5: "INTERNAL_IP6_ADDRESS", 6: "INTERNAL_IP6_PREFIX", 7: "INTERNAL_IP6_DNS", # 其他属性类型可参考RFC7296第3.15.1节补充 } class IKEv2ConfigAttribute(Packet): name = "IKEv2 Configuration Attribute" fields_desc = [ # 用ShortEnumField承载完整16位值,枚举映射低15位的属性类型 ShortEnumField("attr_type_raw", 0, IKEv2_CONFIG_ATTR_TYPES), FieldLenField("length", None, length_of="value", fmt="!H"), # 这里用StrField作为通用值字段,实际可根据属性类型动态调整 StrField("value", b""), ] # 添加辅助方法,方便操作R位和实际属性类型 def get_r_bit(self): """提取R位(1=请求属性,0=响应属性)""" return (self.attr_type_raw >> 15) & 0x1 def set_r_bit(self, r_bit): """设置R位,传入0或1""" # 先清除原来的R位,再写入新值 self.attr_type_raw = (self.attr_type_raw & 0x7FFF) | ((r_bit & 0x1) << 15) def get_attr_type(self): """提取实际的15位属性类型(数值或枚举名称)""" raw_type = self.attr_type_raw & 0x7FFF return IKEv2_CONFIG_ATTR_TYPES.get(raw_type, raw_type) def set_attr_type(self, attr_type): """设置属性类型,可传入数值或枚举名称""" # 先把输入转换为数值 if isinstance(attr_type, str): attr_type_num = next(k for k, v in IKEv2_CONFIG_ATTR_TYPES.items() if v == attr_type) else: attr_type_num = attr_type # 保留原来的R位,写入新的属性类型 self.attr_type_raw = (self.attr_type_raw & 0x8000) | (attr_type_num & 0x7FFF)
这个方案的优势是复用Scapy内置的枚举映射逻辑,不用自己处理数值和字符串的转换,只需要通过辅助方法封装R位的操作,代码简洁易维护。
方案2:自定义Field类(更直观)
如果希望字段本身直接区分R位和属性类型,而不是用辅助方法,我们可以继承Scapy的Field类,自定义一个能直接处理( R位, 属性类型 )元组的字段。
示例代码
from scapy.fields import Field, FieldLenField, StrField from scapy.packet import Packet import struct IKEv2_CONFIG_ATTR_TYPES = { 1: "INTERNAL_IP4_ADDRESS", 2: "INTERNAL_IP4_NETMASK", # 同方案1的枚举定义 } class IKEv2ConfigAttrTypeField(Field): def __init__(self, name, default, enum_map): super().__init__(name, default, fmt="!H") self.enum_map = enum_map def i2m(self, pkt, value): """把用户输入的( r_bit, attr_type )转换为16位字节值""" r_bit, attr_type = value # 确保属性类型是15位以内的数值 if isinstance(attr_type, str): attr_type_num = next(k for k, v in self.enum_map.items() if v == attr_type) else: attr_type_num = attr_type return ((r_bit & 0x1) << 15) | (attr_type_num & 0x7FFF) def m2i(self, pkt, raw_value): """把16位字节值解析为( r_bit, attr_type名称/数值 )""" r_bit = (raw_value >> 15) & 0x1 attr_type_num = raw_value & 0x7FFF attr_type = self.enum_map.get(attr_type_num, attr_type_num) return (r_bit, attr_type) def addfield(self, pkt, s, val): """序列化字段到字节流""" return s + struct.pack(self.fmt, self.i2m(pkt, val)) def getfield(self, pkt, s): """从字节流解析字段""" raw_val, rest = struct.unpack(self.fmt, s[:2]), s[2:] return self.m2i(pkt, raw_val[0]), rest # 使用自定义字段的配置属性包 class IKEv2ConfigAttribute(Packet): name = "IKEv2 Configuration Attribute" fields_desc = [ IKEv2ConfigAttrTypeField("type", (0, 0), IKEv2_CONFIG_ATTR_TYPES), FieldLenField("length", None, length_of="value", fmt="!H"), StrField("value", b""), ]
这个方案的优势是字段语义更清晰,直接操作(R位, 属性类型)元组,不用额外调用辅助方法,但需要自己实现字段的序列化/反序列化逻辑。
总结
- 如果追求开发效率,优先选方案1,用
ShortEnumField加辅助方法,快速适配需求; - 如果希望字段结构更直观,后期维护更清晰,可以选方案2,自定义字段。
内容的提问来源于stack exchange,提问作者juan noguera
相关产品推荐
相关产品推荐

