Package Products :: Package ZenUtils :: Module IpUtil
[hide private]
[frames] | no frames]

Source Code for Module Products.ZenUtils.IpUtil

  1  ########################################################################### 
  2  # 
  3  # This program is part of Zenoss Core, an open source monitoring platform. 
  4  # Copyright (C) 2007, 2009 Zenoss Inc. 
  5  # 
  6  # This program is free software; you can redistribute it and/or modify it 
  7  # under the terms of the GNU General Public License version 2 or (at your 
  8  # option) any later version as published by the Free Software Foundation. 
  9  # 
 10  # For complete information please visit: http://www.zenoss.com/oss/ 
 11  # 
 12  ########################################################################### 
 13   
 14  __doc__ = """IpUtil 
 15   
 16  IPv4 and IPv6 utility functions 
 17   
 18  Internally, IPv6 addresses will use another character rather than ':' as an underscore 
 19  is representable in URLs.  Zope object names *MUST* be okay in URLs. 
 20   
 21  """ 
 22   
 23  import re 
 24  import socket 
 25   
 26  from ipaddr import IPAddress, IPNetwork 
 27   
 28  from Products.ZenUtils.Exceptions import ZentinelException 
 29  from twisted.names.client import lookupPointer 
 30  from twisted.internet import threads 
 31   
 32  IP_DELIM = '..' 
 33  INTERFACE_DELIM = '...' 
 34   
35 -def ipwrap(ip):
36 """ 37 Convert IP addresses to a Zope-friendly format. 38 """ 39 if isinstance(ip, str): 40 wrapped = ip.replace(':', IP_DELIM) 41 return wrapped.replace('%', INTERFACE_DELIM) 42 return ip
43
44 -def ipunwrap(ip):
45 """ 46 Convert IP addresses from a Zope-friendly format to a real address. 47 """ 48 if isinstance(ip, str): 49 unwrapped = ip.replace(IP_DELIM, ':') 50 return unwrapped.replace(INTERFACE_DELIM, '%') 51 return ip
52
53 -def ipstrip(ip):
54 "strip interface off link local IPv6 addresses" 55 if '%' in ip: 56 address = ip[:ip.index('%')] 57 else: 58 address = ip 59 return address
60
61 -def ipunwrap_strip(ip):
62 """ 63 ipunwrap + strip interface off link local IPv6 addresses 64 """ 65 unwrapped = ipunwrap(ip) 66 return ipstrip(unwrapped)
67
68 -def bytesToCanonIpv6(byteString):
69 """ 70 SNMP provides a 16-byte index to table items, where the index 71 represents the IPv6 address. 72 Return an empty string or the canonicalized IPv6 address. 73 74 >>> bytesToCanonIpv6( ['254','128','0','0','0','0','0','0','2','80','86','255','254','138','46','210']) 75 'fe80::250:56ff:fe8a:2ed2' 76 >>> bytesToCanonIpv6( ['253','0','0','0','0','0','0','0','0','0','0','0','10','175','210','5']) 77 'fd00::aaf:d205' 78 >>> bytesToCanonIpv6( ['hello','world']) 79 '' 80 >>> bytesToCanonIpv6( ['253','0','0','0','0','0','0','0','0','0','0','0','10','175','210','5']) 81 'fd00::aaf:d205' 82 """ 83 # To form an IPv6 address, need to combine pairs of octets (in hex) 84 # and join them with a colon 85 try: 86 left = map(int, byteString[::2]) 87 right = map(int, byteString[1::2]) 88 except ValueError: 89 return '' 90 ipv6 = ':'.join("%x%02x" % tuple(x) for x in zip(left, right)) 91 92 # Now canonicalize the IP 93 ip = '' 94 try: 95 ip = str(IPAddress(ipv6)) 96 except ValueError: 97 pass 98 return ip
99 100
101 -class IpAddressError(ZentinelException): pass
102
103 -class InvalidIPRangeError(Exception):
104 """ 105 Attempted to parse an invalid IP range. 106 """
107 108
109 -def isip(ip):
110 uIp = str(ipunwrap(ip)) 111 # Twisted monkey patches socket.inet_pton on systems that have IPv6 112 # disabled and raise a ValueError instead of the expected socket.error 113 # if the passed in address is invalid. 114 try: 115 socket.inet_pton(socket.AF_INET, uIp) 116 except (socket.error, ValueError): 117 try: 118 socket.inet_pton(socket.AF_INET6, uIp) 119 except (socket.error, ValueError): 120 return False 121 return True
122 123
124 -def checkip(ip):
125 """ 126 Check that an IPv4 address is valid. Return true 127 or raise an exception(!) 128 129 >>> checkip('10.10.20.5') 130 True 131 >>> try: checkip(10) 132 ... except IpAddressError, ex: print ex 133 10 is an invalid address 134 >>> try: checkip('10') 135 ... except IpAddressError, ex: print ex 136 10 is an invalid address 137 >>> try: checkip('10.10.20.500') 138 ... except IpAddressError, ex: print ex 139 10.10.20.500 is an invalid address 140 >>> checkip('10.10.20.0') 141 True 142 >>> checkip('10.10.20.255') 143 True 144 """ 145 if not isip(ip): 146 raise IpAddressError( "%s is an invalid address" % ip ) 147 return True
148 149
150 -def numbip(ip):
151 """ 152 Convert a string IP to a decimal representation easier for 153 calculating netmasks etc. 154 155 Deprecated in favour of ipToDecimal() 156 """ 157 return ipToDecimal(ip)
158
159 -def ipToDecimal(ip):
160 """ 161 Convert a string IP to a decimal representation easier for 162 calculating netmasks etc. 163 164 >>> ipToDecimal('10.10.20.5') 165 168432645L 166 >>> try: ipToDecimal('10.10.20.500') 167 ... except IpAddressError, ex: print ex 168 10.10.20.500 is an invalid address 169 """ 170 checkip(ip) 171 # The unit tests expect to always get a long, while the 172 # ipaddr.IPaddress class doesn't provide a direct "to long" capability 173 unwrapped = ipunwrap(ip) 174 if '%' in unwrapped: 175 address = unwrapped[:unwrapped.index('%')] 176 else: 177 address = unwrapped 178 return long(int(IPAddress(address)))
179
180 -def ipFromIpMask(ipmask):
181 """ 182 Get just the IP address from an CIDR string like 1.1.1.1/24 183 184 >>> ipFromIpMask('1.1.1.1') 185 '1.1.1.1' 186 >>> ipFromIpMask('1.1.1.1/24') 187 '1.1.1.1' 188 """ 189 return ipmask.split("/")[0]
190
191 -def strip(ip):
192 """ 193 Convert a numeric IP address to a string 194 195 Deprecated in favour of decimalIpToStr() 196 """ 197 return decimalIpToStr(ip)
198
199 -def decimalIpToStr(ip):
200 """ 201 Convert a decimal IP address (as returned by ipToDecimal) 202 to a regular IPv4 dotted quad address. 203 204 >>> decimalIpToStr(ipToDecimal('10.23.44.57')) 205 '10.23.44.57' 206 """ 207 return str(IPAddress(ipunwrap(ip)))
208
209 -def hexToBits(hex):
210 """ 211 Convert hex netbits (0xff000000) to decimal netmask (8) 212 213 >>> hexToBits("0xff000000") 214 8 215 >>> hexToBits("0xffffff00") 216 24 217 """ 218 return maskToBits(hexToMask(hex))
219 220
221 -def hexToMask(hex):
222 """ 223 Converts a netmask represented in hex to octets represented in 224 decimal. 225 226 >>> hexToMask("0xffffff00") 227 '255.255.255.0' 228 >>> hexToMask("0xffffffff") 229 '255.255.255.255' 230 >>> hexToMask("0x00000000") 231 '0.0.0.0' 232 >>> hexToMask("trash") 233 '255.255.255.255' 234 """ 235 if hex.find('x') < 0: 236 return "255.255.255.255" 237 238 hex = list(hex.lower().split('x')[1]) 239 octets = [] 240 while len(hex) > 0: 241 snippit = list(hex.pop() + hex.pop()) 242 snippit.reverse() 243 decimal = int(''.join(snippit), 16) 244 octets.append(str(decimal)) 245 246 octets.reverse() 247 return '.'.join(octets)
248 249
250 -def maskToBits(netmask):
251 """ 252 Convert string rep of netmask to number of bits 253 254 >>> maskToBits('255.255.255.255') 255 32 256 >>> maskToBits('255.255.224.0') 257 19 258 >>> maskToBits('0.0.0.0') 259 0 260 """ 261 if isinstance(netmask, basestring) and '.' in netmask: 262 test = 0xffffffffL 263 if netmask[0]=='0': return 0 264 masknumb = ipToDecimal(netmask) 265 for i in range(32): 266 if test == masknumb: return 32-i 267 test = test - 2 ** i 268 return None 269 else: 270 return int(netmask)
271 272
273 -def bitsToMaskNumb(netbits):
274 """ 275 Convert integer number of netbits to a decimal number 276 277 Deprecated in favour of bitsToDecimalMask() 278 """ 279 return bitsToDecimalMask(netbits)
280
281 -def bitsToDecimalMask(netbits):
282 """ 283 Convert integer number of netbits to a decimal number 284 285 >>> bitsToDecimalMask(32) 286 4294967295L 287 >>> bitsToDecimalMask(19) 288 4294959104L 289 >>> bitsToDecimalMask(0) 290 0L 291 """ 292 masknumb = 0L 293 netbits=int(netbits) 294 for i in range(32-netbits, 32): 295 masknumb += 2L ** i 296 return masknumb
297 298
299 -def bitsToMask(netbits):
300 """ 301 Convert netbits into a dotted-quad subnetmask 302 303 >>> bitsToMask(12) 304 '255.240.0.0' 305 >>> bitsToMask(0) 306 '0.0.0.0' 307 >>> bitsToMask(32) 308 '255.255.255.255' 309 """ 310 return decimalIpToStr(bitsToDecimalMask(netbits))
311 312
313 -def getnet(ip, netmask):
314 """ 315 Deprecated in favour of decimalNetFromIpAndNet() 316 """ 317 return decimalNetFromIpAndNet(ip, netmask)
318
319 -def decimalNetFromIpAndNet(ip, netmask):
320 """ 321 Get network address of IP as string netmask as in the form 255.255.255.0 322 323 >>> getnet('10.12.25.33', 24) 324 168564992L 325 >>> getnet('10.12.25.33', '255.255.255.0') 326 168564992L 327 """ 328 checkip(ip) 329 return long(int(IPNetwork( ipunwrap(ip) + '/' + str(netmask)).network))
330
331 -def getnetstr(ip, netmask):
332 """ 333 Deprecated in favour of netFromIpAndNet() 334 """ 335 return netFromIpAndNet(ip, netmask)
336
337 -def netFromIpAndNet(ip, netmask):
338 """ 339 Return network number as string 340 341 >>> netFromIpAndNet('10.12.25.33', 24) 342 '10.12.25.0' 343 >>> netFromIpAndNet('250.12.25.33', 1) 344 '128.0.0.0' 345 >>> netFromIpAndNet('10.12.25.33', 16) 346 '10.12.0.0' 347 >>> netFromIpAndNet('10.12.25.33', 32) 348 '10.12.25.33' 349 """ 350 checkip(ip) 351 return str(IPNetwork( ipunwrap(ip) + '/' + str(netmask)).network)
352
353 -def asyncNameLookup(address, uselibcresolver = True):
354 """ 355 Turn IP addreses into names using deferreds 356 """ 357 if uselibcresolver: 358 # This is the most reliable way to do a lookup use it 359 return threads.deferToThread(lambda : socket.gethostbyaddr(address)[0]) 360 else: 361 # There is a problem with this method because it will ignore /etc/hosts 362 address = '.'.join(address.split('.')[::-1]) + '.in-addr.arpa' 363 d = lookupPointer(address, [1,2,4]) 364 def ip(result): 365 return str(result[0][0].payload.name)
366 d.addCallback(ip) 367 return d 368
369 -def asyncIpLookup(name):
370 """ 371 Look up an IP based on the name passed in. We use gethostbyname to make 372 sure that we use /etc/hosts as mentioned above. 373 374 This hasn't been tested. 375 """ 376 return threads.deferToThread(lambda : socket.gethostbyname(name))
377
378 -def generateAddrInfos(hostname):
379 """ 380 generator for dicts from addrInfo structs for hostname 381 """ 382 for addrInfo in socket.getaddrinfo(hostname, None): 383 yield {"ipAddress": addrInfo[-1][0], 384 "ipFamily": addrInfo[0]}
385
386 -def getHostByName(hostname, preferredIpVersion=None):
387 """ 388 Look up an IP based on the name passed in, synchronously. Not using 389 socket.gethostbyname() because it does not support IPv6. 390 391 preferredIpVersion should equal something like socket.AF_INET or 392 socket.AF_INET6 393 """ 394 addrInfos = generateAddrInfos(hostname) 395 if preferredIpVersion is not None: 396 for addrInfo in addrInfos: 397 if addrInfo['ipFamily'] == preferredIpVersion: 398 return addrInfo['ipAddress'] 399 firstInfo = addrInfos.next() 400 return firstInfo['ipAddress']
401
402 -def parse_iprange(iprange):
403 """ 404 Turn a string specifying an IP range into a list of IPs. 405 406 @param iprange: The range string, in the format '10.0.0.a-b' 407 @type iprange: str 408 409 >>> parse_iprange('10.0.0.1-5') 410 ['10.0.0.1', '10.0.0.2', '10.0.0.3', '10.0.0.4', '10.0.0.5'] 411 >>> parse_iprange('10.0.0.2-5') 412 ['10.0.0.2', '10.0.0.3', '10.0.0.4', '10.0.0.5'] 413 >>> parse_iprange('10.0.0.1') 414 ['10.0.0.1'] 415 >>> try: parse_iprange('10.0.0.1-2-3') 416 ... except InvalidIPRangeError: print "Invalid" 417 Invalid 418 >>> parse_iprange('fd00::aaf:d201-d203') 419 ['fd00::aaf:d201', 'fd00::aaf:d202', 'fd00::aaf:d203'] 420 """ 421 rangeList = iprange.split('-') 422 if len(rangeList) > 2: # Nothing we can do about this 423 raise InvalidIPRangeError('%s is an invalid IP range.' % iprange) 424 elif len(rangeList) == 1: # A single IP was passed 425 return [iprange] 426 427 beginIp, endIp = rangeList 428 # Is it just an int? 429 separator = '.' 430 strFn = lambda x:x 431 base = 10 432 if beginIp.find(':')> -1: 433 separator=':' 434 base=16 435 strFn = lambda x: '{0:x}'.format(x) 436 net, start = beginIp.rsplit(separator, 1) 437 start = int(start, base) 438 end = int(endIp, base) 439 440 return ['%s%s%s' % (net, separator, strFn(x)) for x in xrange(start, end+1)]
441 442
443 -def getSubnetBounds(ip):
444 """ 445 Given a string representing the lower limit of a subnet, return decimal 446 representations of the first and last IP of that subnet. 447 448 0 is considered to define the beginning of a subnet, so x.x.x.0 represents 449 a /24, x.x.0.0 represents a /16, etc. An octet of 0 followed by a non-zero 450 octet, of course, is not considered to define a lower limit. 451 452 >>> map(decimalIpToStr, getSubnetBounds('10.1.1.0')) 453 ['10.1.1.0', '10.1.1.255'] 454 >>> map(decimalIpToStr, getSubnetBounds('10.1.1.1')) 455 ['10.1.1.1', '10.1.1.1'] 456 >>> map(decimalIpToStr, getSubnetBounds('10.0.1.0')) 457 ['10.0.1.0', '10.0.1.255'] 458 >>> map(decimalIpToStr, getSubnetBounds('0.0.0.0')) 459 ['0.0.0.0', '255.255.255.255'] 460 >>> map(decimalIpToStr, getSubnetBounds('10.0.0.0')) 461 ['10.0.0.0', '10.255.255.255'] 462 >>> map(decimalIpToStr, getSubnetBounds('100.0.0.0')) 463 ['100.0.0.0', '100.255.255.255'] 464 >>> map(decimalIpToStr, getSubnetBounds('::100.0.0.0')) 465 ['100.0.0.0', '100.255.255.255'] 466 467 """ 468 octets = ip.split('.') 469 otherend = [] 470 while octets: 471 o = octets.pop() 472 if o=='0': 473 otherend.append('255') 474 else: 475 otherend.append(o) 476 break 477 otherend.reverse() 478 octets.extend(otherend) 479 upper = '.'.join(octets) 480 return numbip(ip), numbip(upper)
481
482 -def ensureIp(ip):
483 """ 484 Given a partially formed IP address this will return a complete Ip address 485 with four octets with the invalid or missing entries replaced by 0 486 487 @param ip partially formed ip (will strip out alpha characters) 488 @return valid IP address field 489 490 >>> from Products.ZenUtils.IpUtil import ensureIp 491 >>> ensureIp('20') 492 '20.0.0.0' 493 >>> ensureIp('2000') 494 '0.0.0.0' 495 >>> ensureIp('10.175.X') 496 '10.175.0.0' 497 >>> ensureIp('10.0.1') 498 '10.0.1.0' 499 >>> 500 """ 501 # filter out the alpha characters 502 stripped = ''.join(c for c in ip if c in '1234567890.') 503 octets = stripped.split('.') 504 505 # make sure we always have 4 506 while (len(octets) < 4): 507 octets.append('0') 508 509 # validate each octet 510 for (idx, octet) in enumerate(octets): 511 # cast it to an integer 512 try: 513 octet = int(octet) 514 except ValueError: 515 octet = 0 516 517 # make it 0 if not in the valid ip range 518 if not (0 < octet < 255): 519 octets[idx] = '0' 520 521 return '.'.join(octets)
522