Coverage for /root/GitHubProjects/impacket/impacket/IP6_Address.py : 83%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1# Impacket - Collection of Python classes for working with network protocols.
2#
3# SECUREAUTH LABS. Copyright (C) 2018 SecureAuth Corporation. All rights reserved.
4#
5# This software is provided under a slightly modified version
6# of the Apache Software License. See the accompanying LICENSE file
7# for more information.
8#
10import array
11from six import string_types
13class IP6_Address:
14 ADDRESS_BYTE_SIZE = 16
15 #A Hex Group is a 16-bit unit of the address
16 TOTAL_HEX_GROUPS = 8
17 HEX_GROUP_SIZE = 4 #Size in characters
18 TOTAL_SEPARATORS = TOTAL_HEX_GROUPS - 1
19 ADDRESS_TEXT_SIZE = (TOTAL_HEX_GROUPS * HEX_GROUP_SIZE) + TOTAL_SEPARATORS
20 SEPARATOR = ":"
21 SCOPE_SEPARATOR = "%"
23#############################################################################################################
24# Constructor and construction helpers
26 def __init__(self, address):
27 #The internal representation of an IP6 address is a 16-byte array
28 self.__bytes = array.array('B', b'\0' * self.ADDRESS_BYTE_SIZE)
29 self.__scope_id = ""
31 #Invoke a constructor based on the type of the argument
32 if isinstance(address, string_types):
33 self.__from_string(address)
34 else:
35 self.__from_bytes(address)
38 def __from_string(self, address):
39 #Separate the Scope ID, if present
40 if self.__is_a_scoped_address(address):
41 split_parts = address.split(self.SCOPE_SEPARATOR)
42 address = split_parts[0]
43 if split_parts[1] == "":
44 raise Exception("Empty scope ID")
45 self.__scope_id = split_parts[1]
47 #Expand address if it's in compressed form
48 if self.__is_address_in_compressed_form(address):
49 address = self.__expand_compressed_address(address)
51 #Insert leading zeroes where needed
52 address = self.__insert_leading_zeroes(address)
54 #Sanity check
55 if len(address) != self.ADDRESS_TEXT_SIZE:
56 raise Exception('IP6_Address - from_string - address size != ' + str(self.ADDRESS_TEXT_SIZE))
58 #Split address into hex groups
59 hex_groups = address.split(self.SEPARATOR)
60 if len(hex_groups) != self.TOTAL_HEX_GROUPS:
61 raise Exception('IP6_Address - parsed hex groups != ' + str(self.TOTAL_HEX_GROUPS))
63 #For each hex group, convert it into integer words
64 offset = 0
65 for group in hex_groups:
66 if len(group) != self.HEX_GROUP_SIZE: 66 ↛ 67line 66 didn't jump to line 67, because the condition on line 66 was never true
67 raise Exception('IP6_Address - parsed hex group length != ' + str(self.HEX_GROUP_SIZE))
69 group_as_int = int(group, 16)
70 self.__bytes[offset] = (group_as_int & 0xFF00) >> 8
71 self.__bytes[offset + 1] = (group_as_int & 0x00FF)
72 offset += 2
74 def __from_bytes(self, theBytes):
75 if len(theBytes) != self.ADDRESS_BYTE_SIZE:
76 raise Exception ("IP6_Address - from_bytes - array size != " + str(self.ADDRESS_BYTE_SIZE))
77 self.__bytes = theBytes
79#############################################################################################################
80# Projectors
81 def as_string(self, compress_address = True, scoped_address = True):
82 s = ""
83 for i, v in enumerate(self.__bytes):
84 s += hex(v)[2:].rjust(2, '0')
85 if i % 2 == 1:
86 s += self.SEPARATOR
87 s = s[:-1].upper()
89 if compress_address:
90 s = self.__trim_leading_zeroes(s)
91 s = self.__trim_longest_zero_chain(s)
93 if scoped_address and self.get_scope_id() != "":
94 s += self.SCOPE_SEPARATOR + self.__scope_id
95 return s
97 def as_bytes(self):
98 return self.__bytes
100 def __str__(self):
101 return self.as_string()
103 def get_scope_id(self):
104 return self.__scope_id
106 def get_unscoped_address(self):
107 return self.as_string(True, False) #Compressed address = True, Scoped address = False
109#############################################################################################################
110# Semantic helpers
111 def is_multicast(self):
112 return self.__bytes[0] == 0xFF
114 def is_unicast(self):
115 return self.__bytes[0] == 0xFE
117 def is_link_local_unicast(self):
118 return self.is_unicast() and (self.__bytes[1] & 0xC0 == 0x80)
120 def is_site_local_unicast(self):
121 return self.is_unicast() and (self.__bytes[1] & 0xC0 == 0xC0)
123 def is_unique_local_unicast(self):
124 return self.__bytes[0] == 0xFD
127 def get_human_readable_address_type(self):
128 if self.is_multicast():
129 return "multicast"
130 elif self.is_unicast():
131 if self.is_link_local_unicast():
132 return "link-local unicast"
133 elif self.is_site_local_unicast():
134 return "site-local unicast"
135 else:
136 return "unicast"
137 elif self.is_unique_local_unicast():
138 return "unique-local unicast"
139 else:
140 return "unknown type"
142#############################################################################################################
143#Expansion helpers
145 #Predicate - returns whether an address is in compressed form
146 def __is_address_in_compressed_form(self, address):
147 #Sanity check - triple colon detection (not detected by searches of double colon)
148 if address.count(self.SEPARATOR * 3) > 0:
149 raise Exception('IP6_Address - found triple colon')
151 #Count the double colon marker
152 compression_marker_count = self.__count_compression_marker(address)
153 if compression_marker_count == 0:
154 return False
155 elif compression_marker_count == 1: 155 ↛ 158line 155 didn't jump to line 158, because the condition on line 155 was never false
156 return True
157 else:
158 raise Exception('IP6_Address - more than one compression marker (\"::\") found')
160 #Returns how many hex groups are present, in a compressed address
161 def __count_compressed_groups(self, address):
162 trimmed_address = address.replace(self.SEPARATOR * 2, self.SEPARATOR) #Replace "::" with ":"
163 return trimmed_address.count(self.SEPARATOR) + 1
165 #Counts how many compression markers are present
166 def __count_compression_marker(self, address):
167 return address.count(self.SEPARATOR * 2) #Count occurrences of "::"
169 #Inserts leading zeroes in every hex group
170 def __insert_leading_zeroes(self, address):
171 hex_groups = address.split(self.SEPARATOR)
173 new_address = ""
174 for hex_group in hex_groups:
175 if len(hex_group) < 4:
176 hex_group = hex_group.rjust(4, "0")
177 new_address += hex_group + self.SEPARATOR
179 return new_address[:-1] #Trim the last colon
182 #Expands a compressed address
183 def __expand_compressed_address(self, address):
184 group_count = self.__count_compressed_groups(address)
185 groups_to_insert = self.TOTAL_HEX_GROUPS - group_count
187 pos = address.find(self.SEPARATOR * 2) + 1
188 while groups_to_insert:
189 address = address[:pos] + "0000" + self.SEPARATOR + address[pos:]
190 pos += 5
191 groups_to_insert -= 1
193 #Replace the compression marker with a single colon
194 address = address.replace(self.SEPARATOR * 2, self.SEPARATOR)
195 return address
198#############################################################################################################
199#Compression helpers
201 def __trim_longest_zero_chain(self, address):
202 chain_size = 8
204 while chain_size > 0:
205 groups = address.split(self.SEPARATOR)
207 for index, group in enumerate(groups):
208 #Find the first zero
209 if group == "0":
210 start_index = index
211 end_index = index
212 #Find the end of this chain of zeroes
213 while end_index < 7 and groups[end_index + 1] == "0":
214 end_index += 1
216 #If the zero chain matches the current size, trim it
217 found_size = end_index - start_index + 1
218 if found_size == chain_size:
219 address = self.SEPARATOR.join(groups[0:start_index]) + self.SEPARATOR * 2 + self.SEPARATOR.join(groups[(end_index+1):])
220 return address
222 #No chain of this size found, try with a lower size
223 chain_size -= 1
224 return address
227 #Trims all leading zeroes from every hex group
228 def __trim_leading_zeroes(self, theStr):
229 groups = theStr.split(self.SEPARATOR)
230 theStr = ""
232 for group in groups:
233 group = group.lstrip("0") + self.SEPARATOR
234 if group == self.SEPARATOR:
235 group = "0" + self.SEPARATOR
236 theStr += group
237 return theStr[:-1]
240#############################################################################################################
241 @classmethod
242 def is_a_valid_text_representation(cls, text_representation):
243 try:
244 #Capitalize on the constructor's ability to detect invalid text representations of an IP6 address
245 IP6_Address(text_representation)
246 return True
247 except Exception:
248 return False
250 def __is_a_scoped_address(self, text_representation):
251 return text_representation.count(self.SCOPE_SEPARATOR) == 1