Rietveld Code Review Tool
Help | Bug tracker | Discussion group | Source code | Sign in
(2062)

Side by Side Diff: ipaddr.py

Issue 67107: collapse_address_list is too slow Base URL: http://ipaddr-py.googlecode.com/svn/trunk/
Patch Set: removed range notation Created 15 years, 10 months ago
Left:
Right:
Use n/p to move between diff chunks; N/P to move between comments. Please Sign in to add in-line comments.
Jump to:
View unified diff | Download patch
« no previous file with comments | « no previous file | ipaddr_test.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 #!/usr/bin/python 1 #!/usr/bin/python
2 # 2 #
3 # Copyright 2007 Google Inc. 3 # Copyright 2007 Google Inc.
4 # Licensed to PSF under a Contributor Agreement. 4 # Licensed to PSF under a Contributor Agreement.
5 # 5 #
6 # Licensed under the Apache License, Version 2.0 (the "License"); 6 # Licensed under the Apache License, Version 2.0 (the "License");
7 # you may not use this file except in compliance with the License. 7 # you may not use this file except in compliance with the License.
8 # You may obtain a copy of the License at 8 # You may obtain a copy of the License at
9 # 9 #
10 # http://www.apache.org/licenses/LICENSE-2.0 10 # http://www.apache.org/licenses/LICENSE-2.0
(...skipping 111 matching lines...) Expand 10 before | Expand all | Expand 10 after
122 122
123 try: 123 try:
124 return IPv6(ipaddr) 124 return IPv6(ipaddr)
125 except (IPv6IpValidationError, IPv6NetmaskValidationError): 125 except (IPv6IpValidationError, IPv6NetmaskValidationError):
126 pass 126 pass
127 127
128 raise ValueError('%r does not appear to be an IPv4 or IPv6 address' % 128 raise ValueError('%r does not appear to be an IPv4 or IPv6 address' %
129 ipaddr) 129 ipaddr)
130 130
131 131
132 def _find_address_range(addresses):
133 """Find a sequence of addresses."""
134 first = last = addresses[0]
135 for ip in addresses[1:]:
136 if ip.ip == last.ip + 1:
137 last = ip
138 else:
139 break
140 return (first, last)
141
142 def _get_prefix_length(number1, number2, bits):
143 """Get the number of leading bits that are same for two numbers."""
144 for i in range(bits):
145 if number1 >> i == number2 >> i:
146 return bits - i
147 return 0
148
149 def _count_righthand_zero_bits(integer, bits):
150 """Count the number of zero bits on the right hand side."""
151 if integer == 0:
152 return bits
153 for i in range(bits):
154 if (integer >> i) % 2:
155 return i
156
157 def summarize_address_range(first, last):
158 """Summarize a network range given the first and last IP addresses."""
159
160 if first.version != last.version:
161 raise ValueError("IP addresses must be same version")
162 if first > last:
163 raise ValueError("last IP address must be greater than first")
164
165 networks = []
166
167 if first.version == 4:
168 ip = IPv4
169 elif first.version == 6:
170 ip = IPv6
171 else:
172 raise ValueError("unknown IP version")
173
174 ip_bits = first.max_prefixlen
175 while first <= last:
176 nbits = _count_righthand_zero_bits(first.ip, ip_bits)
177 current = None
178 while True:
179 addend = 2**nbits - 1
180 current = first.ip + addend
181 nbits -= 1
182 if ip(current) <= last:
183 break
184 prefix = _get_prefix_length(first.ip, current, ip_bits)
185 networks.append(ip('%s/%s' % (first.ip_ext, prefix)))
186 if current == ip._ALL_ONES:
187 break
188 first = ip(current + 1)
189 return networks
190
132 def _collapse_address_list_recursive(addresses): 191 def _collapse_address_list_recursive(addresses):
133 """Loops through the addresses, collapsing concurrent netblocks. 192 """Loops through the addresses, collapsing concurrent netblocks.
134 193
135 Example: 194 Example:
136 195
137 ip1 = IPv4('1.1.0.0/24') 196 ip1 = IPv4('1.1.0.0/24')
138 ip2 = IPv4('1.1.1.0/24') 197 ip2 = IPv4('1.1.1.0/24')
139 ip3 = IPv4('1.1.2.0/24') 198 ip3 = IPv4('1.1.2.0/24')
140 ip4 = IPv4('1.1.3.0/24') 199 ip4 = IPv4('1.1.3.0/24')
141 ip5 = IPv4('1.1.4.0/24') 200 ip5 = IPv4('1.1.4.0/24')
(...skipping 40 matching lines...) Expand 10 before | Expand all | Expand 10 after
182 collapse_address_list([IPv4('1.1.0.0/24'), IPv4('1.1.1.0/24')]) -> 241 collapse_address_list([IPv4('1.1.0.0/24'), IPv4('1.1.1.0/24')]) ->
183 [IPv4('1.1.0.0/23')] 242 [IPv4('1.1.0.0/23')]
184 243
185 Args: 244 Args:
186 addresses: A list of IPv4 or IPv6 objects. 245 addresses: A list of IPv4 or IPv6 objects.
187 246
188 Returns: 247 Returns:
189 A list of IPv4 or IPv6 objects depending on what we were passed. 248 A list of IPv4 or IPv6 objects depending on what we were passed.
190 249
191 """ 250 """
251 _addresses = []
252 i = 0
253 ip_bits = addresses[0].max_prefixlen
Peter Moody 2009/06/02 21:05:19 for internal access, I usually end up using the pr
254 # filter out IP addresses, dedup, and sort
255 addrs = sorted(set([ip for ip in addresses if ip.prefixlen == ip_bits]),
256 key=BaseIP._get_networks_key)
257 nets = [ip for ip in addresses if ip.prefixlen < ip_bits]
258
259 while i < len(addrs):
260 (first, last) = _find_address_range(addrs[i:])
261 i = addrs.index(last) + 1
262 _addresses.extend(
263 [IP(ip) for ip in summarize_address_range(first, last)])
264
192 return _collapse_address_list_recursive( 265 return _collapse_address_list_recursive(
193 sorted(addresses, key=BaseIP._get_networks_key)) 266 sorted(_addresses + nets, key=BaseIP._get_networks_key))
194 267
195 # backwards compatibility 268 # backwards compatibility
196 CollapseAddrList = collapse_address_list 269 CollapseAddrList = collapse_address_list
197 270
198 # Test whether this Python implementation supports byte objects that 271 # Test whether this Python implementation supports byte objects that
199 # are not identical to str ones. 272 # are not identical to str ones.
200 # We need to exclude platforms where bytes == str so that we can 273 # We need to exclude platforms where bytes == str so that we can
201 # distinguish between packed representations and strings, for example 274 # distinguish between packed representations and strings, for example
202 # b'12::' (the IPv4 address 49.50.58.58) and '12::' (an IPv6 address). 275 # b'12::' (the IPv4 address 49.50.58.58) and '12::' (an IPv6 address).
203 try: 276 try:
(...skipping 380 matching lines...) Expand 10 before | Expand all | Expand 10 after
584 IPv4(IPv4('192.168.1.1').ip) == IPv4('192.168.1.1') 657 IPv4(IPv4('192.168.1.1').ip) == IPv4('192.168.1.1')
585 658
586 Raises: 659 Raises:
587 IPv4IpValidationError: If ipaddr isn't a valid IPv4 address. 660 IPv4IpValidationError: If ipaddr isn't a valid IPv4 address.
588 IPv4NetmaskValidationError: If the netmask isn't valid for 661 IPv4NetmaskValidationError: If the netmask isn't valid for
589 an IPv4 address. 662 an IPv4 address.
590 663
591 """ 664 """
592 BaseIP.__init__(self) 665 BaseIP.__init__(self)
593 self._version = 4 666 self._version = 4
667 self._max_prefixlen = 32
594 668
595 # Efficient constructor from integer. 669 # Efficient constructor from integer.
596 if isinstance(ipaddr, int) or isinstance(ipaddr, long): 670 if isinstance(ipaddr, int) or isinstance(ipaddr, long):
597 self.ip = ipaddr 671 self.ip = ipaddr
598 self._prefixlen = 32 672 self._prefixlen = 32
599 self.netmask = self._ALL_ONES 673 self.netmask = self._ALL_ONES
600 if ipaddr < 0 or ipaddr > self._ALL_ONES: 674 if ipaddr < 0 or ipaddr > self._ALL_ONES:
601 raise IPv4IpValidationError(ipaddr) 675 raise IPv4IpValidationError(ipaddr)
602 return 676 return
603 677
(...skipping 169 matching lines...) Expand 10 before | Expand all | Expand 10 after
773 A boolean, True if the address is link-local per RFC 3927. 847 A boolean, True if the address is link-local per RFC 3927.
774 848
775 """ 849 """
776 return self in IPv4('169.254.0.0/16') 850 return self in IPv4('169.254.0.0/16')
777 851
778 @property 852 @property
779 def version(self): 853 def version(self):
780 return self._version 854 return self._version
781 855
782 @property 856 @property
857 def max_prefixlen(self):
Peter Moody 2009/06/02 21:05:19 this could be a property of BaseIP
858 return self._max_prefixlen
859
860 @property
783 def packed(self): 861 def packed(self):
784 """The binary representation of this address.""" 862 """The binary representation of this address."""
785 return struct.pack('!I', self.ip) 863 return struct.pack('!I', self.ip)
786 864
787 def _is_hostmask(self, ip_str): 865 def _is_hostmask(self, ip_str):
788 """Test if the IP string is a hostmask (rather than a netmask). 866 """Test if the IP string is a hostmask (rather than a netmask).
789 867
790 Args: 868 Args:
791 ip_str: A string, the potential hostmask. 869 ip_str: A string, the potential hostmask.
792 870
(...skipping 137 matching lines...) Expand 10 before | Expand all | Expand 10 after
930 IPv6(IPv6('2001:4860::').ip) == IPv6('2001:4860::') 1008 IPv6(IPv6('2001:4860::').ip) == IPv6('2001:4860::')
931 1009
932 Raises: 1010 Raises:
933 IPv6IpValidationError: If ipaddr isn't a valid IPv6 address. 1011 IPv6IpValidationError: If ipaddr isn't a valid IPv6 address.
934 IPv6NetmaskValidationError: If the netmask isn't valid for 1012 IPv6NetmaskValidationError: If the netmask isn't valid for
935 an IPv6 address. 1013 an IPv6 address.
936 1014
937 """ 1015 """
938 BaseIP.__init__(self) 1016 BaseIP.__init__(self)
939 self._version = 6 1017 self._version = 6
1018 self._max_prefixlen = 128
940 1019
941 # Efficient constructor from integer. 1020 # Efficient constructor from integer.
942 if isinstance(ipaddr, long) or isinstance(ipaddr, int): 1021 if isinstance(ipaddr, long) or isinstance(ipaddr, int):
943 self.ip = ipaddr 1022 self.ip = ipaddr
944 self._prefixlen = 128 1023 self._prefixlen = 128
945 self.netmask = self._ALL_ONES 1024 self.netmask = self._ALL_ONES
946 if ipaddr < 0 or ipaddr > self._ALL_ONES: 1025 if ipaddr < 0 or ipaddr > self._ALL_ONES:
947 raise IPv6IpValidationError(ipaddr) 1026 raise IPv6IpValidationError(ipaddr)
948 return 1027 return
949 1028
(...skipping 183 matching lines...) Expand 10 before | Expand all | Expand 10 after
1133 A boolean, True if the address is reserved per RFC 4193. 1212 A boolean, True if the address is reserved per RFC 4193.
1134 1213
1135 """ 1214 """
1136 return self in IPv6('fc00::/7') 1215 return self in IPv6('fc00::/7')
1137 1216
1138 @property 1217 @property
1139 def version(self): 1218 def version(self):
1140 return self._version 1219 return self._version
1141 1220
1142 @property 1221 @property
1222 def max_prefixlen(self):
1223 return self._max_prefixlen
1224
1225 @property
1143 def packed(self): 1226 def packed(self):
1144 """The binary representation of this address.""" 1227 """The binary representation of this address."""
1145 return struct.pack('!QQ', self.ip >> 64, self.ip & (2**64 - 1)) 1228 return struct.pack('!QQ', self.ip >> 64, self.ip & (2**64 - 1))
1146 1229
1147 def _is_shorthand_ip(self, ip_str=None): 1230 def _is_shorthand_ip(self, ip_str=None):
1148 """Determine if the address is shortened. 1231 """Determine if the address is shortened.
1149 1232
1150 Args: 1233 Args:
1151 ip_str: A string, the IPv6 address. 1234 ip_str: A string, the IPv6 address.
1152 1235
(...skipping 224 matching lines...) Expand 10 before | Expand all | Expand 10 after
1377 1460
1378 Returns: 1461 Returns:
1379 An integer. 1462 An integer.
1380 1463
1381 """ 1464 """
1382 return self.prefixlen 1465 return self.prefixlen
1383 1466
1384 # backwards compatibility 1467 # backwards compatibility
1385 Subnet = subnet 1468 Subnet = subnet
1386 Supernet = supernet 1469 Supernet = supernet
OLDNEW
« no previous file with comments | « no previous file | ipaddr_test.py » ('j') | no next file with comments »

Powered by Google App Engine
RSS Feeds Recent Issues | This issue
This is Rietveld f62528b