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

Source Code for Module Products.ZenUtils.IpUtil

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