# Copyright (C) 2012 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. import abc import struct import six from . import packet_base from . import packet_utils from os_ken.lib import stringify ICMP_ECHO_REPLY = 0 ICMP_DEST_UNREACH = 3 ICMP_SRC_QUENCH = 4 ICMP_REDIRECT = 5 ICMP_ECHO_REQUEST = 8 ICMP_TIME_EXCEEDED = 11 ICMP_ECHO_REPLY_CODE = 0 ICMP_HOST_UNREACH_CODE = 1 ICMP_PORT_UNREACH_CODE = 3 ICMP_TTL_EXPIRED_CODE = 0 class icmp(packet_base.PacketBase): """ICMP (RFC 792) header encoder/decoder class. 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 corresponding args in this order. .. tabularcolumns:: |l|L| ============== ==================== Attribute Description ============== ==================== type Type code Code csum CheckSum \ (0 means automatically-calculate when encoding) data Payload. \ Either a bytearray, or \ os_ken.lib.packet.icmp.echo or \ os_ken.lib.packet.icmp.dest_unreach or \ os_ken.lib.packet.icmp.TimeExceeded object \ NOTE for icmp.echo: \ This includes "unused" 16 bits and the following \ "Internet Header + 64 bits of Original Data Datagram" of \ the ICMP header. \ NOTE for icmp.dest_unreach and icmp.TimeExceeded: \ This includes "unused" 8 or 24 bits and the following \ "Internet Header + leading octets of original datagram" \ of the original packet. ============== ==================== """ _PACK_STR = '!BBH' _MIN_LEN = struct.calcsize(_PACK_STR) _ICMP_TYPES = {} @staticmethod def register_icmp_type(*args): def _register_icmp_type(cls): for type_ in args: icmp._ICMP_TYPES[type_] = cls return cls return _register_icmp_type def __init__(self, type_=ICMP_ECHO_REQUEST, code=0, csum=0, data=b''): super(icmp, self).__init__() self.type = type_ self.code = code self.csum = csum self.data = data @classmethod def parser(cls, buf): (type_, code, csum) = struct.unpack_from(cls._PACK_STR, buf) msg = cls(type_, code, csum) offset = cls._MIN_LEN if len(buf) > offset: cls_ = cls._ICMP_TYPES.get(type_, None) if cls_: msg.data = cls_.parser(buf, offset) else: msg.data = buf[offset:] return msg, None, None def serialize(self, payload, prev): hdr = bytearray(struct.pack(icmp._PACK_STR, self.type, self.code, self.csum)) if self.data: if self.type in icmp._ICMP_TYPES: assert isinstance(self.data, _ICMPv4Payload) hdr += self.data.serialize() else: hdr += self.data else: self.data = echo() hdr += self.data.serialize() if self.csum == 0: self.csum = packet_utils.checksum(hdr) struct.pack_into('!H', hdr, 2, self.csum) return hdr def __len__(self): return self._MIN_LEN + len(self.data) @six.add_metaclass(abc.ABCMeta) class _ICMPv4Payload(stringify.StringifyMixin): """ Base class for the payload of ICMPv4 packet. """ @icmp.register_icmp_type(ICMP_ECHO_REPLY, ICMP_ECHO_REQUEST) class echo(_ICMPv4Payload): """ICMP sub encoder/decoder class for Echo and Echo Reply messages. This is used with os_ken.lib.packet.icmp.icmp for ICMP Echo and Echo Reply messages. 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 corresponding args in this order. .. tabularcolumns:: |l|L| ============== ==================== Attribute Description ============== ==================== id Identifier seq Sequence Number data Internet Header + 64 bits of Original Data Datagram ============== ==================== """ _PACK_STR = '!HH' _MIN_LEN = struct.calcsize(_PACK_STR) def __init__(self, id_=0, seq=0, data=None): super(echo, self).__init__() self.id = id_ self.seq = seq self.data = data @classmethod def parser(cls, buf, offset): (id_, seq) = struct.unpack_from(cls._PACK_STR, buf, offset) msg = cls(id_, seq) offset += cls._MIN_LEN if len(buf) > offset: msg.data = buf[offset:] return msg def serialize(self): hdr = bytearray(struct.pack(echo._PACK_STR, self.id, self.seq)) if self.data is not None: hdr += self.data return hdr def __len__(self): length = self._MIN_LEN if self.data is not None: length += len(self.data) return length @icmp.register_icmp_type(ICMP_DEST_UNREACH) class dest_unreach(_ICMPv4Payload): """ICMP sub encoder/decoder class for Destination Unreachable Message. This is used with os_ken.lib.packet.icmp.icmp for ICMP Destination Unreachable Message. 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 corresponding args in this order. [RFC1191] reserves bits for the "Next-Hop MTU" field. [RFC4884] introduced 8-bit data length attribute. .. tabularcolumns:: |l|p{35em}| ============== ===================================================== Attribute Description ============== ===================================================== data_len data length mtu Next-Hop MTU NOTE: This field is required when icmp code is 4 code 4 = fragmentation needed and DF set data Internet Header + leading octets of original datagram ============== ===================================================== """ _PACK_STR = '!xBH' _MIN_LEN = struct.calcsize(_PACK_STR) def __init__(self, data_len=0, mtu=0, data=None): super(dest_unreach, self).__init__() if ((data_len >= 0) and (data_len <= 255)): self.data_len = data_len else: raise ValueError('Specified data length (%d) is invalid.' % data_len) self.mtu = mtu self.data = data @classmethod def parser(cls, buf, offset): (data_len, mtu) = struct.unpack_from(cls._PACK_STR, buf, offset) msg = cls(data_len, mtu) offset += cls._MIN_LEN if len(buf) > offset: msg.data = buf[offset:] return msg def serialize(self): hdr = bytearray(struct.pack(dest_unreach._PACK_STR, self.data_len, self.mtu)) if self.data is not None: hdr += self.data return hdr def __len__(self): length = self._MIN_LEN if self.data is not None: length += len(self.data) return length @icmp.register_icmp_type(ICMP_TIME_EXCEEDED) class TimeExceeded(_ICMPv4Payload): """ICMP sub encoder/decoder class for Time Exceeded Message. This is used with os_ken.lib.packet.icmp.icmp for ICMP Time Exceeded Message. 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 corresponding args in this order. [RFC4884] introduced 8-bit data length attribute. .. tabularcolumns:: |l|L| ============== ==================== Attribute Description ============== ==================== data_len data length data Internet Header + leading octets of original datagram ============== ==================== """ _PACK_STR = '!xBxx' _MIN_LEN = struct.calcsize(_PACK_STR) def __init__(self, data_len=0, data=None): if (data_len >= 0) and (data_len <= 255): self.data_len = data_len else: raise ValueError('Specified data length (%d) is invalid.' % data_len) self.data = data @classmethod def parser(cls, buf, offset): (data_len, ) = struct.unpack_from(cls._PACK_STR, buf, offset) msg = cls(data_len) offset += cls._MIN_LEN if len(buf) > offset: msg.data = buf[offset:] return msg def serialize(self): hdr = bytearray(struct.pack(TimeExceeded._PACK_STR, self.data_len)) if self.data is not None: hdr += self.data return hdr def __len__(self): length = self._MIN_LEN if self.data is not None: length += len(self.data) return length icmp.set_classes(icmp._ICMP_TYPES)