Не удается разобрать пакеты DHCP с помощью get_protocol (dhcp.dhcp) Рю.

Я использую контроллер 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)

Единственная проблема заключается в том, что три парсера, которые я перечислил выше, на самом деле возвращают данные, в то время как get_protocol для DHCP неизменно возвращает 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)?


person Jacob    schedule 20.12.2016    source источник


Ответы (1)


Хорошо, я нашел проблему. В строках 200–218 файла dhcp.py есть инструкция try exclude. Я вытащил 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 байта. К сожалению, я использую Open vSwitch на виртуальной машине, предоставленной концентратором SDN, и 128 байт, по-видимому, являются пределом. Итак, у меня конфликт с файлом dhcp.py Рю. Мое решение состояло в том, чтобы изменить dhcp.py. Как это было сделано, следует.

Прежде чем изменять код, я рекомендую вам сначала обновить контроллер Ryu. Процедуры следующие:

Шаг 1: Если вы используете виртуальную машину. Сделайте снимок или клонируйте его прямо сейчас.

Шаг 2: Обновите Рю

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

Ошибка TypeError из шага 2 теперь должна быть устранена. (Надеюсь, вы клонировали свою виртуальную машину на всякий случай.)

Шаг 3. Измените файл dhcp.py Рю.

Файл 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.
person Jacob    schedule 21.12.2016