|
4 | 4 | """ |
5 | 5 |
|
6 | 6 | from pyof.foundation.base import GenericStruct |
7 | | -from pyof.foundation.basic_types import BinaryData, HWAddress, UBInt8, UBInt16 |
| 7 | +from pyof.foundation.basic_types import ( |
| 8 | + BinaryData, HWAddress, IPAddress, UBInt8, UBInt16) |
8 | 9 | from pyof.foundation.exceptions import PackException |
9 | 10 |
|
10 | | -__all__ = ('Ethernet', 'GenericTLV', 'TLVWithSubType', 'LLDP') |
| 11 | +__all__ = ('Ethernet', 'GenericTLV', 'IPv4', 'TLVWithSubType', 'LLDP') |
11 | 12 |
|
12 | 13 |
|
13 | 14 | class Ethernet(GenericStruct): |
@@ -138,6 +139,140 @@ def get_size(self, value=None): |
138 | 139 | return 2 + self.length |
139 | 140 |
|
140 | 141 |
|
| 142 | +class IPv4(GenericStruct): |
| 143 | + """IPv4 packet "struct". |
| 144 | +
|
| 145 | + Contains all fields of an IP version 4 packet header, plus the upper layer |
| 146 | + content as binary data. |
| 147 | + Some of the fields were merged together because of their size being |
| 148 | + inferior to 8 bits. They are represented as a single class attribute, but |
| 149 | + pack/unpack methods will take into account the values in individual |
| 150 | + instance attributes. |
| 151 | + """ |
| 152 | + |
| 153 | + #: _version_ihl (:class:`UBInt8`): IP protocol version + Internet Header |
| 154 | + #: Length (words) |
| 155 | + _version_ihl = UBInt8() |
| 156 | + #: _dscp_ecn (:class:`UBInt8`): Differentiated Services Code Point |
| 157 | + #: (ToS - Type of Service) + Explicit Congestion Notification |
| 158 | + _dscp_ecn = UBInt8() |
| 159 | + #: length (:class:`UBInt16`): IP packet length (bytes) |
| 160 | + length = UBInt16() |
| 161 | + #: identification (:class:`UBInt16`): Packet ID - common to all fragments |
| 162 | + identification = UBInt16() |
| 163 | + #: _flags_offset (:class:`UBInt16`): Fragmentation flags + fragmentation |
| 164 | + #: offset |
| 165 | + _flags_offset = UBInt16() |
| 166 | + #: ttl (:class:`UBInt8`): Packet time-to-live |
| 167 | + ttl = UBInt8() |
| 168 | + #: protocol (:class:`UBInt8`): Upper layer protocol number |
| 169 | + protocol = UBInt8() |
| 170 | + #: checksum (:class:`UBInt16`): Header checksum |
| 171 | + checksum = UBInt16() |
| 172 | + #: source (:class:`IPAddress`): Source IPv4 address |
| 173 | + source = IPAddress() |
| 174 | + #: destination (:class:`IPAddress`): Destination IPv4 address |
| 175 | + destination = IPAddress() |
| 176 | + #: options (:class:`BinaryData`): IP Options - up to 320 bits, always |
| 177 | + #: padded to 32 bits |
| 178 | + options = BinaryData() |
| 179 | + #: data (:class:`BinaryData`): Packet data |
| 180 | + data = BinaryData() |
| 181 | + |
| 182 | + def __init__(self, version=4, ihl=5, dscp=0, ecn=0, length=0, # noqa |
| 183 | + identification=0, flags=0, offset=0, ttl=255, protocol=0, |
| 184 | + checksum=0, source="0.0.0.0", destination="0.0.0.0", |
| 185 | + options=b'', data=b''): |
| 186 | + """Create the Packet and set instance attributes.""" |
| 187 | + super().__init__() |
| 188 | + self.version = version |
| 189 | + self.ihl = ihl |
| 190 | + self.dscp = dscp |
| 191 | + self.ecn = ecn |
| 192 | + self.length = length |
| 193 | + self.identification = identification |
| 194 | + self.flags = flags |
| 195 | + self.offset = offset |
| 196 | + self.ttl = ttl |
| 197 | + self.protocol = protocol |
| 198 | + self.checksum = checksum |
| 199 | + self.source = source |
| 200 | + self.destination = destination |
| 201 | + self.options = options |
| 202 | + self.data = data |
| 203 | + |
| 204 | + def _update_checksum(self): |
| 205 | + """Update the packet checksum to enable integrity check.""" |
| 206 | + source_list = [int(octet) for octet in self.source.split(".")] |
| 207 | + destination_list = [int(octet) for octet in |
| 208 | + self.destination.split(".")] |
| 209 | + source_upper = (source_list[0] << 8) + source_list[1] |
| 210 | + source_lower = (source_list[2] << 8) + source_list[3] |
| 211 | + destination_upper = (destination_list[0] << 8) + destination_list[1] |
| 212 | + destination_lower = (destination_list[2] << 8) + destination_list[3] |
| 213 | + |
| 214 | + block_sum = ((self._version_ihl << 8 | self._dscp_ecn) + self.length + |
| 215 | + self.identification + self._flags_offset + |
| 216 | + (self.ttl << 8 | self.protocol) + source_upper + |
| 217 | + source_lower + destination_upper + destination_lower) |
| 218 | + |
| 219 | + while block_sum > 65535: |
| 220 | + carry = block_sum >> 16 |
| 221 | + block_sum = (block_sum & 65535) + carry |
| 222 | + |
| 223 | + self.checksum = ~block_sum & 65535 |
| 224 | + |
| 225 | + def pack(self, value=None): |
| 226 | + """Pack the struct in a binary representation. |
| 227 | +
|
| 228 | + Merge some fields to ensure correct packing. |
| 229 | + """ |
| 230 | + # Set the correct IHL based on options size |
| 231 | + if self.options: |
| 232 | + self.ihl += int(len(self.options) / 4) |
| 233 | + |
| 234 | + # Set the correct packet length based on header length and data |
| 235 | + self.length = int(self.ihl * 4 + len(self.data)) |
| 236 | + |
| 237 | + self._version_ihl = self.version << 4 | self.ihl |
| 238 | + self._dscp_ecn = self.dscp << 2 | self.ecn |
| 239 | + self._flags_offset = self.flags << 13 | self.offset |
| 240 | + |
| 241 | + # Set the checksum field before packing |
| 242 | + self._update_checksum() |
| 243 | + |
| 244 | + return super().pack() |
| 245 | + |
| 246 | + def unpack(self, buff, offset=0): |
| 247 | + """Unpack a binary struct into this object's attributes. |
| 248 | +
|
| 249 | + Return the values instead of the lib's basic types. |
| 250 | + """ |
| 251 | + super().unpack(buff, offset) |
| 252 | + |
| 253 | + self.version = self._version_ihl.value >> 4 |
| 254 | + self.ihl = self._version_ihl.value & 15 |
| 255 | + self.dscp = self._dscp_ecn.value >> 2 |
| 256 | + self.ecn = self._dscp_ecn.value & 3 |
| 257 | + self.length = self.length.value |
| 258 | + self.identification = self.identification.value |
| 259 | + self.flags = self._flags_offset.value >> 13 |
| 260 | + self.offset = self._flags_offset.value & 8191 |
| 261 | + self.ttl = self.ttl.value |
| 262 | + self.protocol = self.protocol.value |
| 263 | + self.checksum = self.checksum.value |
| 264 | + self.source = self.source.value |
| 265 | + self.destination = self.destination.value |
| 266 | + |
| 267 | + if self.ihl > 5: |
| 268 | + options_size = (self.ihl - 5) * 4 |
| 269 | + self.data = self.options.value[options_size:] |
| 270 | + self.options = self.options.value[:options_size] |
| 271 | + else: |
| 272 | + self.data = self.options.value |
| 273 | + self.options = b'' |
| 274 | + |
| 275 | + |
141 | 276 | class TLVWithSubType(GenericTLV): |
142 | 277 | """Modify the :class:`GenericTLV` to a Organization Specific TLV structure. |
143 | 278 |
|
|
0 commit comments