diff options
author | 竹下昇 <takeshita.noboru1@gmail.com> | 2013-05-30 13:54:07 +0900 |
---|---|---|
committer | FUJITA Tomonori <fujita.tomonori@lab.ntt.co.jp> | 2013-05-31 17:53:56 +0900 |
commit | 6f2716b7ff1895235672f2a08cedf965fa83789e (patch) | |
tree | d8c6bf09a724a0907ec399a7a0d336f0befcaee7 | |
parent | 77effb29a69eeb6d320dd9a7554924b2578c3c95 (diff) |
lib/packet: DHCP packet parser/serializer
Add DHCP encoder/decoder class.
Signed-off-by: TAKESHITA Noboru <takeshita.noboru@yes.nttcom.ne.jp>
Signed-off-by: FUJITA Tomonori <fujita.tomonori@lab.ntt.co.jp>
-rw-r--r-- | ryu/lib/packet/dhcp.py | 296 |
1 files changed, 296 insertions, 0 deletions
diff --git a/ryu/lib/packet/dhcp.py b/ryu/lib/packet/dhcp.py new file mode 100644 index 00000000..58450c99 --- /dev/null +++ b/ryu/lib/packet/dhcp.py @@ -0,0 +1,296 @@ +# Copyright (C) 2013 Nippon Telegraph and Telephone Corporation. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +DHCP packet parser/serializer + +RFC 2131 +DHCP packet format + + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | op (1) | htype (1) | hlen (1) | hops (1) | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | xid (4) | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | secs (2) | flags (2) | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | ciaddr (4) | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | yiaddr (4) | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | siaddr (4) | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | giaddr (4) | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | | + | chaddr (16) | + | | + | | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | | + | sname (64) | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | | + | file (128) | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | | + | options (variable) | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + +""" +import binascii +import random +import socket +import struct + +from . import packet_base + +DHCP_BOOT_REQUEST = 1 +DHCP_BOOT_REPLY = 2 + +# DHCP message type code +DHCP_DISCOVER = 1 +DHCP_OFFER = 2 +DHCP_REQUEST = 3 +DHCP_ACK = 5 + +# DHCP options tag code +DHCP_PAD_OPT = 0 +DHCP_SUBNET_MASK_OPT = 1 +DHCP_GATEWAY_ADDR_OPT = 3 +DHCP_DNS_SERVER_ADDR_OPT = 6 +DHCP_HOST_NAME_OPT = 12 +DHCP_REQUESTED_IP_ADDR_OPT = 50 +DHCP_IP_ADDR_LEASE_TIME_OPT = 51 +DHCP_MESSAGE_TYPE_OPT = 53 +DHCP_SERVER_IDENTIFIER_OPT = 54 +DHCP_PARAMETER_REQUEST_LIST_OPT = 55 +DHCP_RENEWAL_TIME_OPT = 58 +DHCP_REBINDING_TIME_OPT = 59 +DHCP_END_OPT = 255 + + +class dhcp(packet_base.PacketBase): + """DHCP (RFC 2131) header encoder/decoder class. + + The serialized packet would looks like the ones described + in the following sections. + + * RFC 2131 DHCP packet format + + An instance has the following attributes at least. + Most of them are same to the on-wire counterparts but in host byte order. + __init__ takes the correspondig args in this order. + + ============== ==================== + 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, DHCPACK by server. + giaddr Relay agent IP address, used in booting via a\ + relay agent. + chaddr Client hardware address. + sname Optional server host name, null terminated string. + boot_file Boot file name, null terminated string; "generic"\ + name or null in DHCPDISCOVER, fully qualified\ + directory-path name in DHCPOFFER. + options Optional parameters field\ + ('DHCP message type' option must be included in\ + every DHCP message). + ============== ==================== + """ + _HLEN_UNPACK_STR = '!BBB' + _HLEN_UNPACK_LEN = struct.calcsize(_HLEN_UNPACK_STR) + _DHCP_UNPACK_STR = '!BIHHIIII%ds%ds64s128s' + _DHCP_PACK_STR = '!BBBBIHHIIII16s64s128s' + _DHCP_CHADDR_LEN = 16 + _HARDWARE_TYPE_ETHERNET = 1 + + def __init__(self, op, chaddr, options, htype=_HARDWARE_TYPE_ETHERNET, + hlen=0, hops=0, xid=None, secs=0, flags=0, ciaddr=0, yiaddr=0, + siaddr=0, giaddr=0, sname='', boot_file=''): + super(dhcp, self).__init__() + self.op = op + self.htype = htype + if hlen == 0: + self.hlen = len(chaddr) + else: + self.hlen = hlen + self.hops = hops + if xid is None: + self.xid = random.randint(0, 0xffffffff) + else: + self.xid = xid + self.secs = secs + self.flags = flags + self.ciaddr = ciaddr + self.yiaddr = yiaddr + self.siaddr = siaddr + self.giaddr = giaddr + self.chaddr = chaddr + self.sname = sname + self.boot_file = boot_file + self.options = options + + @classmethod + def parser(cls, buf): + (op, htype, hlen) = struct.unpack_from(cls._HLEN_UNPACK_STR, buf) + buf = buf[cls._HLEN_UNPACK_LEN:] + unpack_str = cls._DHCP_UNPACK_STR % (hlen, + (cls._DHCP_CHADDR_LEN - hlen)) + min_len = struct.calcsize(unpack_str) + (hops, xid, secs, flags, ciaddr, yiaddr, siaddr, giaddr, chaddr, + dummy, sname, boot_file + ) = struct.unpack_from(unpack_str, buf) + if len(buf) > min_len: + parse_opt = options.parser(buf[min_len:]) + return cls(op, chaddr, parse_opt, htype, hlen, hops, xid, secs, flags, + ciaddr, yiaddr, siaddr, giaddr, sname, boot_file) + + def serialize(self, payload, prev): + seri_opt = self.options.serialize() + pack_str = '%s%ds' % (self._DHCP_PACK_STR, + self.options.options_len) + return struct.pack(pack_str, self.op, self.htype, self.hlen, + self.hops, self.xid, self.secs, self.flags, + self.ciaddr, self.yiaddr, self.siaddr, self.giaddr, + self.chaddr, self.sname, self.boot_file, seri_opt) + + +class options(object): + """DHCP (RFC 2132) options encoder/decoder class. + + This is used with ryu.lib.packet.dhcp.dhcp. + + An instance has the following attributes at least. + Most of them are same to the on-wire counterparts but in host byte order. + __init__ takes the correspondig args in this order. + + ============== ==================== + Attribute Description + ============== ==================== + option_list 'end option' and 'pad option' are added automatically\ + after the option class is stored in array. + options_len Option's byte length.\ + ('magic cookie', 'end option' and 'pad option'\ + length including.) + magic_cookie The first four octets contain the decimal values\ + 99, 130, 83 and 99. + ============== ==================== + """ + _MAGIC_COOKIE_UNPACK_STR = '!I' + # same magic cookie as is defined in RFC 1497 + _MAGIC_COOKIE = socket.inet_aton("99.130.83.99") + _OPT_TAG_LEN_BYTE = 2 + + def __init__(self, option_list=None, options_len=0, + magic_cookie=_MAGIC_COOKIE): + super(options, self).__init__() + if option_list is None: + self.option_list = [] + else: + self.option_list = option_list + self.options_len = options_len + self.magic_cookie = magic_cookie + + @classmethod + def parser(cls, buf): + opt_parse_list = [] + offset = struct.calcsize(cls._MAGIC_COOKIE_UNPACK_STR) + magic_cookie = struct.unpack_from(cls._MAGIC_COOKIE_UNPACK_STR, buf)[0] + while len(buf) > offset: + opt_buf = buf[offset:] + opt = option.parser(opt_buf) + if opt is None: + break + opt_parse_list.append(opt) + offset += opt.length + cls._OPT_TAG_LEN_BYTE + return cls(opt_parse_list, len(buf), magic_cookie) + + def serialize(self): + seri_opt = self.magic_cookie + for opt in self.option_list: + seri_opt += opt.serialize() + seri_opt += binascii.a2b_hex('%x' % DHCP_END_OPT) + if self.options_len == 0: + self.options_len = len(seri_opt) + return seri_opt + + +class option(object): + """DHCP (RFC 2132) options encoder/decoder class. + + This is used with ryu.lib.packet.dhcp.dhcp.options. + + An instance has the following attributes at least. + Most of them are same to the on-wire counterparts but in host byte order. + __init__ takes the correspondig args in this order. + + ============== ==================== + Attribute Description + ============== ==================== + tag Option type.\ + (except for the 'magic cookie', 'pad option'\ + and 'end option'.) + value Option's value.\ + (set the value that has been converted to hexadecimal.) + length Option's value length.\ + (calculated automatically from the length of value.) + ============== ==================== + """ + _UNPACK_STR = '!B' + _MIN_LEN = struct.calcsize(_UNPACK_STR) + + def __init__(self, tag, value, length=0): + super(option, self).__init__() + self.tag = tag + self.value = value + self.length = length + + @classmethod + def parser(cls, buf): + tag = struct.unpack_from(cls._UNPACK_STR, buf)[0] + if tag == DHCP_END_OPT or tag == DHCP_PAD_OPT: + return None + buf = buf[cls._MIN_LEN:] + length = struct.unpack_from(cls._UNPACK_STR, buf)[0] + buf = buf[cls._MIN_LEN:] + value_unpack_str = '%ds' % length + value = struct.unpack_from(value_unpack_str, buf)[0] + return cls(tag, value, length) + + def serialize(self): + if self.length == 0: + self.length = len(self.value) + options_pack_str = '!BB%ds' % self.length + return struct.pack(options_pack_str, self.tag, self.length, self.value) |