【问题标题】:Can't Parse DHCP packets with Ryu's get_protocol(dhcp.dhcp)无法使用 Ryu 的 get_protocol(dhcp.dhcp) 解析 DHCP 数据包
【发布时间】:2017-05-05 19:03:26
【问题描述】:

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

解析 DHCP 数据包的代码 sn-p 示例如下:

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)

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

下面的代码 sn-p 起作用的是我识别具有三个以上值的数据包列表。我将值保存在索引 3 并将其设置为我的 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) 方法获得类似的结果?

【问题讨论】:

    标签: parsing packet dhcp sdn ryu


    【解决方案1】:

    好的,我发现了问题。在 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 步中的 TypeError。 (希望你克隆了你的虚拟机以防万一。)

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

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

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

    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 文件进行这些调整,那么您将可以访问以下标头字段:

    ============== ====================
    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.
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-07-25
      • 2016-08-08
      相关资源
      最近更新 更多