首页 文章

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

提问于
浏览
0

我正在使用带有Open vSwitch的Ryu SDN控制器使用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)

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

以下代码片段的作用是什么,我在其中识别具有三个以上值的数据包列表 . 我将值保存在索引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)方法实现类似的结果?

1 回答

  • 0

    好的,我发现了问题 . 在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步:如果您使用的是VM . 拍摄快照或立即克隆它 .

    第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 . (希望你克隆你的VM以防万一 . )

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

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

    发生此错误是因为dhcp.py指定字符串格式为'!BBBBIHH4s4s4s4s16s64s128s' . 在#1中,我创建了第二个选项 . 这样做,允许我插入一些if语句,以便在数据包到达小于100字节时以不同方式处理数据包 . 同样,如果数据包大于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.
    

相关问题