1
2
3
4
5
6
7
8
9
10
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
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
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
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
62 """
63 ipunwrap + strip interface off link local IPv6 addresses
64 """
65 unwrapped = ipunwrap(ip)
66 return ipstrip(unwrapped)
67
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
84
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
93 ip = ''
94 try:
95 ip = str(IPAddress(ipv6))
96 except ValueError:
97 pass
98 return ip
99
100
102
104 """
105 Attempted to parse an invalid IP range.
106 """
107
108
122
123
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
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
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
172
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
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
192 """
193 Convert a numeric IP address to a string
194
195 Deprecated in favour of decimalIpToStr()
196 """
197 return decimalIpToStr(ip)
198
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
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
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
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
274 """
275 Convert integer number of netbits to a decimal number
276
277 Deprecated in favour of bitsToDecimalMask()
278 """
279 return bitsToDecimalMask(netbits)
280
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
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
318
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
336
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
354 """
355 Turn IP addreses into names using deferreds
356 """
357 if uselibcresolver:
358
359 return threads.deferToThread(lambda : socket.gethostbyaddr(address)[0])
360 else:
361
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
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
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
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
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:
423 raise InvalidIPRangeError('%s is an invalid IP range.' % iprange)
424 elif len(rangeList) == 1:
425 return [iprange]
426
427 beginIp, endIp = rangeList
428
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
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
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
502 stripped = ''.join(c for c in ip if c in '1234567890.')
503 octets = stripped.split('.')
504
505
506 while (len(octets) < 4):
507 octets.append('0')
508
509
510 for (idx, octet) in enumerate(octets):
511
512 try:
513 octet = int(octet)
514 except ValueError:
515 octet = 0
516
517
518 if not (0 < octet < 255):
519 octets[idx] = '0'
520
521 return '.'.join(octets)
522