1
2
3
4
5
6
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
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
50
51 _PREFERRED_IP_VERSION = _getPreferedIpVersion()
52
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
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
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
80 """
81 ipunwrap + strip interface off link local IPv6 addresses
82 """
83 unwrapped = ipunwrap(ip)
84 return ipstrip(unwrapped)
85
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
102
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
111 ip = ''
112 try:
113 ip = str(IPAddress(ipv6))
114 except ValueError:
115 pass
116 return ip
117
118
120
122 """
123 Attempted to parse an invalid IP range.
124 """
125
126
140
141
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
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
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
190
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
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
210 """
211 Convert a numeric IP address to a string
212
213 Deprecated in favour of decimalIpToStr()
214 """
215 return decimalIpToStr(ip)
216
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
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
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)
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
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
300 """
301 Convert integer number of netbits to a decimal number
302
303 Deprecated in favour of bitsToDecimalMask()
304 """
305 return bitsToDecimalMask(netbits)
306
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
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
344
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
362
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
380 """
381 Turn IP addreses into names using deferreds
382 """
383 if uselibcresolver:
384
385 return threads.deferToThread(lambda : socket.gethostbyaddr(address)[0])
386 else:
387
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
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
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
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
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:
449 raise InvalidIPRangeError('%s is an invalid IP range.' % iprange)
450 elif len(rangeList) == 1:
451 return [iprange]
452
453 beginIp, endIp = rangeList
454
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
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
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
528 stripped = ''.join(c for c in ip if c in '1234567890.')
529 octets = stripped.split('.')
530
531
532 while (len(octets) < 4):
533 octets.append('0')
534
535
536 for (idx, octet) in enumerate(octets):
537
538 try:
539 octet = int(octet)
540 except ValueError:
541 octet = 0
542
543
544 if not (0 < octet < 255):
545 octets[idx] = '0'
546
547 return '.'.join(octets)
548