无法使用 Ryu 的 get_protocol(dhcp.dhcp) 解析 DHCP 数据包

Can't Parse DHCP packets with Ryu's get_protocol(dhcp.dhcp)

我在 mininet 上使用 Ryu SDN 控制器和 Open vSwitch,使用 OpenFlow 1.3 来解析 DHCP 数据包。根据在线示例和 Ryu 资源,我实现了一个 DHCP 数据包解析器。但是,它并没有像我预期的那样工作,我想知道是否有人知道为什么我的第一个解决方案不起作用?

下面是解析 DHCP 数据包的代码片段示例:

from ryu.lib.packet import dhcp
...
...
@set_ev_cls(ofp_event.EventOFPPacketIn, MAIN_DISPATCHER)
def _packet_in_handler(self, ev):
    msg = ev.msg
    datapath = msg.datapath
    pkt = packet.Packet(msg.data)
    dhcpPacket = pkt.get_protocol(dhcp.dhcp)

我的代码遵循类似的思路:

from ryu.lib.packet import dhcp
...
...
@set_ev_cls(ofp_event.EventOFPPacketIn, MAIN_DISPATCHER)
def _packet_in_handler(self, ev):
    pkt = {}
    pkt['msg'] = ev.msg
    pkt['dp'] = pkt['msg'].datapath
    pkt['pkt'] = packet.Packet(pkt['msg'].data)
    pkt['dhcp'] = pkt['pkt'].get_protocol(dhcp.dhcp)

这似乎是合理的,因为我在使用其他协议(如 ARP、ICMP、IP 等)时遵循这个确切的顺序。示例如下。

pkt['arp'] = pkt['pkt'].get_protocol(arp.arp)
pkt['ip'] = pkt['pkt'].get_protocol(ipv4.ipv4)
pkt['icmp'] = pkt['pkt'].get_protocol(icmp.icmp)

唯一的问题是我上面列出的三个解析器实际上 return 数据,而 DHCP 的 get_protocol 始终 returns None。我已经通过我的交换机发送 DHCP 数据包对此进行了测试。

有效的是以下代码片段,我在其中识别具有三个以上值的数据包列表。我将值保存在索引三处并将其设置为我的 DHCP 数据包。在 DHCP 数据包中,我专注于解析索引 2 处的字符串。其中包含我感兴趣的数据。

# pkt['dhcp'] = pkt['pkt'].get_protocol(dhcp.dhcp)
# Check if pkt['pkt]] > 3 elements, if so, parse DHCP string
#Standard pkt['dhcp'] = (None, None, String)
if len(pkt['pkt']) > 3:
    pkt['dhcp'] = dhcp.dhcp.parser(pkt['pkt'][3])
    pkt['op'] = hex(ord(dhcp_p[2][0]))
    pkt['htype'] = hex(ord(dhcp_p[2][1]))
    pkt['hlen'] = hex(ord(dhcp_p[2][2]))
    pkt['hops'] = hex(ord(dhcp_p[2][3]))

    def parseDHCP(pkt_d,start,stop):
        s_value = ''
        stop += 1
        for val in range(start,stop):
        s_value += str(hex(ord(pkt_d[val])))
        return s_value

    pkt['xid'] = parseDHCP(dhcp_p[2],4,7)
    pkt['secs'] = parseDHCP(dhcp_p[2],8,9)
    pkt['flags'] = parseDHCP(dhcp_p[2],10,11)
    pkt['ciaddr'] = parseDHCP(dhcp_p[2],12,15)
    pkt['yiaddr'] = parseDHCP(dhcp_p[2],16,19)
    pkt['siaddr'] = parseDHCP(dhcp_p[2],20,23)
    pkt['giaddr'] = parseDHCP(dhcp_p[2],24,27)
    pkt['chaddr'] = parseDHCP(dhcp_p[2],28,33)
    pkt['pad'] = parseDHCP(dhcp_p[2],34,43)

这些值的打印结果如下所示:

0x1
0x1
0x6
0x0
0x440x30x980x11
0x00x0
0x00x0
0x00x00x00x0
0x00x00x00x0
0x00x00x00x0
0x00x00x00x0
0x7e0x1d0xcc0xe70xee0x4f
0x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x0

上面的代码允许我观察 DHCP 数据包的内容,但我真的想弄清楚为什么我没有使用 pkt['pkt'].get_protocol(dhcp.dhcp) 方法?

好的,我找到问题了。在 dhcp.py 的第 200 – 218 行中有一个 try except 语句。我把 cls._parser 从尝试中拉出来,看看它抛出的错误,我得到了这个:

Ryuretic_coupler: Exception occurred during handler processing. Backtrace    
from offending handler [initial_event] servicing event [EventOFPPacketIn] follows.
Traceback (most recent call last):
  File "/home/ubuntu/ryu/ryu/base/app_manager.py", line 290, in _event_loop
    handler(ev)
  File "/home/ubuntu/ryu/ryu/app/Ryuretic/Ryuretic.py", line 72, in initial_event
    pkt = parsPkt.handle_pkt(ev)
  File "/home/ubuntu/ryu/ryu/app/Ryuretic/Pkt_Parse13.py", line 81, in handle_pkt
    dhcp_p = pkt['dhcp'] = dhcp.dhcp.parser(pkt['pkt'][3])
  File "/home/ubuntu/ryu/ryu/lib/packet/dhcp.py", line 212, in parser
    return cls._parser(buf)
  File "/home/ubuntu/ryu/ryu/lib/packet/dhcp.py", line 192, in _parser
    ) = struct.unpack_from(unpack_str, buf)
error: unpack_from requires a buffer of at least 233 bytes

因此,dhcp.py 没有收到它需要的 233 个字节。不幸的是,我在 SDN 集线器提供的 VM 上使用 Open vSwitch,128 字节似乎是一个限制。所以,我和 Ryu 的 dhcp.py 文件有冲突。我的解决方案是修改dhcp.py。下面是如何做到的。

在修改代码之前,我建议您先更新您的 Ryu 控制器。程序如下:

第 1 步:如果您使用的是虚拟机。现在拍一张快照或克隆它。

第 2 步:更新 Ryu

cd ryu
git pull

如果你像我一样运行使用旧版本,那么当你下次尝试运行你的 Ryu 控制器时可能会出现以下错误:

Traceback (most recent call last):
File "./bin/ryu-manager", line 18, in <module>
  from ryu.cmd.manager import main
File "/home/ubuntu/ryu/ryu/cmd/manager.py", line 31, in <module>
  from ryu.base.app_manager import AppManager
File "/home/ubuntu/ryu/ryu/base/app_manager.py", line 37, in <module>
  from ryu.controller.controller import Datapath
File "/home/ubuntu/ryu/ryu/controller/controller.py", line 74, in <module>
  help='Maximum number of unreplied echo requests before datapath is disconnected.')
File "/usr/local/lib/python2.7/dist-packages/oslo_config/cfg.py", line 1033, in __init__
  super(IntOpt, self).__init__(name, type=types.Integer(), **kwargs)
TypeError: __init__() got an unexpected keyword argument 'min'

第 3 步:更新您的 oslo_config 文件

sudo pip install oslo.config --upgrade

步骤 2 中的类型错误现在应该已解决。 (希望您克隆了 VM 以防万一。)

第 3 步:修改 Ryu 的 dhcp.py 文件

Ryu 的 dhcp.py 文件(位于 /ryu/ryu/lib/packet)预计接收超过 235 字节的缓冲区。否则,它会向控制器抛出错误和 returns 任何内容。由于我的缓冲区只接收了大约 81 字节的缓冲区大小。我修改了 Ryu dhcp.py 文件如下。

错误发生是因为dhcp.py指定了'!BBBBIHH4s4s4s4s16s64s128s'的字符串格式。在 #1 中,我创建了第二个选项。这样做,允许我插入一些 if 语句,以便在到达的数据包小于 100 字节时以不同方式处理数据包。同样,如果数据包大于 235 字节,则 dhcp.py 会正常处理数据包,returns 会处理额外的值。

class dhcp(packet_base.PacketBase):
    """DHCP (RFC 2131) header encoder/decoder class.
                ....deleted....
    """
    _MIN_LEN = 236
    _HLEN_UNPACK_STR = '!BBB'
    _HLEN_UNPACK_LEN = struct.calcsize(_HLEN_UNPACK_STR)
    _DHCP_UNPACK_STR = '!BIHH4s4s4s4s%ds%ds64s128s'
    ##################################################
    #1(mod) Created second option for unpacking string
    _DHCP_UNPACK_STR2 = '!BIHH4s4s4s4s%ds%ds40s'
    _DHCP_PACK_STR = '!BBBBIHH4s4s4s4s16s64s128s'
    #################################################
    _DHCP_CHADDR_LEN = 16
    _HARDWARE_TYPE_ETHERNET = 1
    _class_prefixes = ['options']
    _TYPE = {
        'ascii': [
            'ciaddr', 'yiaddr', 'siaddr', 'giaddr', 'chaddr', 'sname'
        ]
    }

    def __init__(self, op, chaddr, options, htype=_HARDWARE_TYPE_ETHERNET,
                 hlen=0, hops=0, xid=None, secs=0, flags=0,
                 ciaddr='0.0.0.0', yiaddr='0.0.0.0', siaddr='0.0.0.0',
                 giaddr='0.0.0.0', sname='', boot_file=b''):
        super(dhcp, self).__init__()
        #...Deleted No Changes made to init. 

    @classmethod
    def _parser(cls, buf):
        (op, htype, hlen) = struct.unpack_from(cls._HLEN_UNPACK_STR, buf)
        buf = buf[cls._HLEN_UNPACK_LEN:]
        ####################################################
        #2(mod) provided option for smaller packet sizes
        if len(buf) < 100:
            unpack_str = cls._DHCP_UNPACK_STR2 % (hlen,
                                             (cls._DHCP_CHADDR_LEN - hlen))
        else:
            unpack_str = cls._DHCP_UNPACK_STR % (hlen,
                                             (cls._DHCP_CHADDR_LEN - hlen))            
        #####################################################
        min_len = struct.calcsize(unpack_str)
        ######################################################
        #3(mod) provided option for smaller packets, set bootfile to b''
        if min_len > 233:
            (hops, xid, secs, flags, ciaddr, yiaddr, siaddr, giaddr,  
             chaddr, dummy, sname, boot_file
             ) = struct.unpack_from(unpack_str, buf)
        else:
            boot_file=b''
            (hops, xid, secs, flags, ciaddr, yiaddr, siaddr, giaddr,  
             chaddr, dummy, sname, boot_file
             ) = struct.unpack_from(unpack_str, buf)+(boot_file,)
        ########################################################    
        length = min_len
        ########################################################
        # (mod) provided option for smaller packet sizes, no parse_opt
        if len(buf) > 233: 
            parse_opt = options.parser(buf[min_len:])
            length += parse_opt.options_len
        else:
            parse_opt = None
            length = min_len
        #########################################################   
        return (cls(op, addrconv.mac.bin_to_text(chaddr), parse_opt,
                    htype, hlen, hops, xid, secs, flags,
                    addrconv.ipv4.bin_to_text(ciaddr),
                    addrconv.ipv4.bin_to_text(yiaddr),
                    addrconv.ipv4.bin_to_text(siaddr),
                    addrconv.ipv4.bin_to_text(giaddr),
                    sname.decode('ascii'), boot_file),
                None, buf[length:])

完整的文件即将在https://github.com/Ryuretic/RyureticLabs/tree/master/ryu/ryu/app/Ryuretic/Support_Files

如果您对 dhcp.py 文件进行这些调整,那么您将可以访问以下 header 字段:

============== ====================
Attribute      Description
============== ====================
op             Message op code / message type.\
               1 = BOOTREQUEST, 2 = BOOTREPLY
htype          Hardware address type (e.g.  '1' = 10mb ethernet).
hlen           Hardware address length (e.g.  '6' = 10mb ethernet).
hops           Client sets to zero, optionally used by relay agent\
               when booting via a relay agent.
xid            Transaction ID, a random number chosen by the client,\
               used by the client and serverto associate messages\
               and responses between a client and a server.
secs           Filled in by client, seconds elapsed since client\
               began address acquisition or renewal process.
flags          Flags.
ciaddr         Client IP address; only filled in if client is in\
               BOUND, RENEW or REBINDING state and can respond\
               to ARP requests.
yiaddr         'your' (client) IP address.
siaddr         IP address of next server to use in bootstrap;\
               returned in DHCPOFFER, DHserver.
giaddr         Relay agent IP address, used in booting via a\
               relay agent.
chaddr         Client hardware address.
sname(partial) Optional server host name, null terminated string.