1# Copyright (C) 2012 Nippon Telegraph and Telephone Corporation. 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6# 7# http://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 12# implied. 13# See the License for the specific language governing permissions and 14# limitations under the License. 15 16import abc 17import struct 18 19import six 20 21from . import packet_base 22from . import packet_utils 23from ryu.lib import stringify 24 25 26ICMP_ECHO_REPLY = 0 27ICMP_DEST_UNREACH = 3 28ICMP_SRC_QUENCH = 4 29ICMP_REDIRECT = 5 30ICMP_ECHO_REQUEST = 8 31ICMP_TIME_EXCEEDED = 11 32 33ICMP_ECHO_REPLY_CODE = 0 34ICMP_HOST_UNREACH_CODE = 1 35ICMP_PORT_UNREACH_CODE = 3 36ICMP_TTL_EXPIRED_CODE = 0 37 38 39class icmp(packet_base.PacketBase): 40 """ICMP (RFC 792) header encoder/decoder class. 41 42 An instance has the following attributes at least. 43 Most of them are same to the on-wire counterparts but in host byte order. 44 __init__ takes the corresponding args in this order. 45 46 .. tabularcolumns:: |l|L| 47 48 ============== ==================== 49 Attribute Description 50 ============== ==================== 51 type Type 52 code Code 53 csum CheckSum \ 54 (0 means automatically-calculate when encoding) 55 data Payload. \ 56 Either a bytearray, or \ 57 ryu.lib.packet.icmp.echo or \ 58 ryu.lib.packet.icmp.dest_unreach or \ 59 ryu.lib.packet.icmp.TimeExceeded object \ 60 NOTE for icmp.echo: \ 61 This includes "unused" 16 bits and the following \ 62 "Internet Header + 64 bits of Original Data Datagram" of \ 63 the ICMP header. \ 64 NOTE for icmp.dest_unreach and icmp.TimeExceeded: \ 65 This includes "unused" 8 or 24 bits and the following \ 66 "Internet Header + leading octets of original datagram" \ 67 of the original packet. 68 ============== ==================== 69 """ 70 71 _PACK_STR = '!BBH' 72 _MIN_LEN = struct.calcsize(_PACK_STR) 73 _ICMP_TYPES = {} 74 75 @staticmethod 76 def register_icmp_type(*args): 77 def _register_icmp_type(cls): 78 for type_ in args: 79 icmp._ICMP_TYPES[type_] = cls 80 return cls 81 return _register_icmp_type 82 83 def __init__(self, type_=ICMP_ECHO_REQUEST, code=0, csum=0, data=b''): 84 super(icmp, self).__init__() 85 self.type = type_ 86 self.code = code 87 self.csum = csum 88 self.data = data 89 90 @classmethod 91 def parser(cls, buf): 92 (type_, code, csum) = struct.unpack_from(cls._PACK_STR, buf) 93 msg = cls(type_, code, csum) 94 offset = cls._MIN_LEN 95 96 if len(buf) > offset: 97 cls_ = cls._ICMP_TYPES.get(type_, None) 98 if cls_: 99 msg.data = cls_.parser(buf, offset) 100 else: 101 msg.data = buf[offset:] 102 103 return msg, None, None 104 105 def serialize(self, payload, prev): 106 hdr = bytearray(struct.pack(icmp._PACK_STR, self.type, 107 self.code, self.csum)) 108 109 if self.data: 110 if self.type in icmp._ICMP_TYPES: 111 assert isinstance(self.data, _ICMPv4Payload) 112 hdr += self.data.serialize() 113 else: 114 hdr += self.data 115 else: 116 self.data = echo() 117 hdr += self.data.serialize() 118 119 if self.csum == 0: 120 self.csum = packet_utils.checksum(hdr) 121 struct.pack_into('!H', hdr, 2, self.csum) 122 123 return hdr 124 125 def __len__(self): 126 return self._MIN_LEN + len(self.data) 127 128 129@six.add_metaclass(abc.ABCMeta) 130class _ICMPv4Payload(stringify.StringifyMixin): 131 """ 132 Base class for the payload of ICMPv4 packet. 133 """ 134 135 136@icmp.register_icmp_type(ICMP_ECHO_REPLY, ICMP_ECHO_REQUEST) 137class echo(_ICMPv4Payload): 138 """ICMP sub encoder/decoder class for Echo and Echo Reply messages. 139 140 This is used with ryu.lib.packet.icmp.icmp for 141 ICMP Echo and Echo Reply messages. 142 143 An instance has the following attributes at least. 144 Most of them are same to the on-wire counterparts but in host byte order. 145 __init__ takes the corresponding args in this order. 146 147 .. tabularcolumns:: |l|L| 148 149 ============== ==================== 150 Attribute Description 151 ============== ==================== 152 id Identifier 153 seq Sequence Number 154 data Internet Header + 64 bits of Original Data Datagram 155 ============== ==================== 156 """ 157 158 _PACK_STR = '!HH' 159 _MIN_LEN = struct.calcsize(_PACK_STR) 160 161 def __init__(self, id_=0, seq=0, data=None): 162 super(echo, self).__init__() 163 self.id = id_ 164 self.seq = seq 165 self.data = data 166 167 @classmethod 168 def parser(cls, buf, offset): 169 (id_, seq) = struct.unpack_from(cls._PACK_STR, buf, offset) 170 msg = cls(id_, seq) 171 offset += cls._MIN_LEN 172 173 if len(buf) > offset: 174 msg.data = buf[offset:] 175 176 return msg 177 178 def serialize(self): 179 hdr = bytearray(struct.pack(echo._PACK_STR, self.id, 180 self.seq)) 181 182 if self.data is not None: 183 hdr += self.data 184 185 return hdr 186 187 def __len__(self): 188 length = self._MIN_LEN 189 if self.data is not None: 190 length += len(self.data) 191 return length 192 193 194@icmp.register_icmp_type(ICMP_DEST_UNREACH) 195class dest_unreach(_ICMPv4Payload): 196 """ICMP sub encoder/decoder class for Destination Unreachable Message. 197 198 This is used with ryu.lib.packet.icmp.icmp for 199 ICMP Destination Unreachable Message. 200 201 An instance has the following attributes at least. 202 Most of them are same to the on-wire counterparts but in host byte order. 203 __init__ takes the corresponding args in this order. 204 205 [RFC1191] reserves bits for the "Next-Hop MTU" field. 206 [RFC4884] introduced 8-bit data length attribute. 207 208 .. tabularcolumns:: |l|p{35em}| 209 210 ============== ===================================================== 211 Attribute Description 212 ============== ===================================================== 213 data_len data length 214 mtu Next-Hop MTU 215 216 NOTE: This field is required when icmp code is 4 217 218 code 4 = fragmentation needed and DF set 219 data Internet Header + leading octets of original datagram 220 ============== ===================================================== 221 """ 222 223 _PACK_STR = '!xBH' 224 _MIN_LEN = struct.calcsize(_PACK_STR) 225 226 def __init__(self, data_len=0, mtu=0, data=None): 227 super(dest_unreach, self).__init__() 228 229 if ((data_len >= 0) and (data_len <= 255)): 230 self.data_len = data_len 231 else: 232 raise ValueError('Specified data length (%d) is invalid.' % data_len) 233 234 self.mtu = mtu 235 self.data = data 236 237 @classmethod 238 def parser(cls, buf, offset): 239 (data_len, mtu) = struct.unpack_from(cls._PACK_STR, 240 buf, offset) 241 msg = cls(data_len, mtu) 242 offset += cls._MIN_LEN 243 244 if len(buf) > offset: 245 msg.data = buf[offset:] 246 247 return msg 248 249 def serialize(self): 250 hdr = bytearray(struct.pack(dest_unreach._PACK_STR, 251 self.data_len, self.mtu)) 252 253 if self.data is not None: 254 hdr += self.data 255 256 return hdr 257 258 def __len__(self): 259 length = self._MIN_LEN 260 if self.data is not None: 261 length += len(self.data) 262 return length 263 264 265@icmp.register_icmp_type(ICMP_TIME_EXCEEDED) 266class TimeExceeded(_ICMPv4Payload): 267 """ICMP sub encoder/decoder class for Time Exceeded Message. 268 269 This is used with ryu.lib.packet.icmp.icmp for 270 ICMP Time Exceeded Message. 271 272 An instance has the following attributes at least. 273 Most of them are same to the on-wire counterparts but in host byte order. 274 __init__ takes the corresponding args in this order. 275 276 [RFC4884] introduced 8-bit data length attribute. 277 278 .. tabularcolumns:: |l|L| 279 280 ============== ==================== 281 Attribute Description 282 ============== ==================== 283 data_len data length 284 data Internet Header + leading octets of original datagram 285 ============== ==================== 286 """ 287 288 _PACK_STR = '!xBxx' 289 _MIN_LEN = struct.calcsize(_PACK_STR) 290 291 def __init__(self, data_len=0, data=None): 292 if (data_len >= 0) and (data_len <= 255): 293 self.data_len = data_len 294 else: 295 raise ValueError('Specified data length (%d) is invalid.' % data_len) 296 297 self.data = data 298 299 @classmethod 300 def parser(cls, buf, offset): 301 (data_len, ) = struct.unpack_from(cls._PACK_STR, buf, offset) 302 msg = cls(data_len) 303 offset += cls._MIN_LEN 304 305 if len(buf) > offset: 306 msg.data = buf[offset:] 307 308 return msg 309 310 def serialize(self): 311 hdr = bytearray(struct.pack(TimeExceeded._PACK_STR, self.data_len)) 312 313 if self.data is not None: 314 hdr += self.data 315 316 return hdr 317 318 def __len__(self): 319 length = self._MIN_LEN 320 if self.data is not None: 321 length += len(self.data) 322 return length 323 324 325icmp.set_classes(icmp._ICMP_TYPES) 326