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