Package flumotion :: Package common :: Module netutils
[hide private]

Source Code for Module flumotion.common.netutils

  1  # -*- Mode: Python; test-case-name: flumotion.test.test_common_messages -*- 
  2  # vi:si:et:sw=4:sts=4:ts=4 
  3  # 
  4  # Flumotion - a streaming media server 
  5  # Copyright (C) 2004,2005,2006,2007 Fluendo, S.L. (www.fluendo.com). 
  6  # All rights reserved. 
  7   
  8  # This file may be distributed and/or modified under the terms of 
  9  # the GNU General Public License version 2 as published by 
 10  # the Free Software Foundation. 
 11  # This file is distributed without any warranty; without even the implied 
 12  # warranty of merchantability or fitness for a particular purpose. 
 13  # See "LICENSE.GPL" in the source distribution for more information. 
 14   
 15  # Licensees having purchased or holding a valid Flumotion Advanced 
 16  # Streaming Server license may use this file in accordance with the 
 17  # Flumotion Advanced Streaming Server Commercial License Agreement. 
 18  # See "LICENSE.Flumotion" in the source distribution for more information. 
 19   
 20  # Headers in this file shall remain intact. 
 21   
 22  """miscellaneous network functions. 
 23  """ 
 24   
 25  import array 
 26  import errno 
 27  import fcntl 
 28  import re 
 29  import socket 
 30  import struct 
 31   
 32  from twisted.internet import address 
 33   
 34  from flumotion.common import avltree 
 35   
 36  __version__ = "$Rev: 7056 $" 
 37   
 38   
 39  # Thanks to Paul Cannon, see 
 40  # http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/439093 
 41  # 
 42  # WARNING: Horribly linux-specific. Horribly IPv4 specific. Also, just horrible. 
 43   
44 -def find_all_interface_names():
45 """ 46 Find the names of all available network interfaces 47 """ 48 ptr_size = len(struct.pack('P', 0)) 49 size = 24 + 2 * (ptr_size) 50 max_possible = 128 # arbitrary. raise if needed. 51 bytes = max_possible * size 52 s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 53 names = array.array('B', '\0' * bytes) 54 outbytes = struct.unpack('iP', fcntl.ioctl( 55 s.fileno(), 56 0x8912, #SIOCGIFCONF 57 struct.pack('iP', bytes, names.buffer_info()[0]) 58 ))[0] 59 namestr = names.tostring() 60 return [namestr[i:i+size].split('\0', 1)[0] for i in range(0, outbytes, size)]
61
62 -def get_address_for_interface(ifname):
63 """ 64 Get the IP address for an interface 65 """ 66 s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 67 return socket.inet_ntoa(fcntl.ioctl( 68 s.fileno(), 69 0x8915, #SIOCGIFADDR 70 struct.pack('256s', ifname[:15]) 71 )[20:24])
72
73 -def guess_public_ip():
74 """ 75 Attempt to guess a public IP for this system. 76 Returns "127.0.0.1" if it can't come up with anything better. 77 """ 78 # Iterate through them in some vaguely meaningful order. 79 interfaces = find_all_interface_names() 80 interfaces.sort() 81 82 for interface in interfaces: 83 # We have them sorted, so the first such we see will be eth0 84 if interface.startswith('eth'): 85 return get_address_for_interface(interface) 86 87 return '127.0.0.1'
88
89 -def guess_public_hostname():
90 """ 91 Attempt to guess a public hostname for this system. 92 """ 93 ip = guess_public_ip() 94 95 try: 96 return socket.gethostbyaddr(ip)[0] 97 except socket.error: 98 return ip
99
100 -def ipv4StringToInt(s):
101 try: 102 b1, b2, b3, b4 = map(int, s.split('.')) 103 except TypeError: 104 raise ValueError(s) 105 106 ret = 0 107 for n in b1, b2, b3, b4: 108 ret <<= 8 109 if n < 0 or n > 255: 110 raise ValueError(s) 111 ret += n 112 return ret
113
114 -def ipv4IntToString(n):
115 l = [] 116 for i in range(4): 117 l.append((n>>(i*8)) & 0xff) 118 l.reverse() 119 return '.'.join(map(str, l))
120
121 -def countTrailingZeroes32(n):
122 tz = 0 123 if n == 0: 124 # max of 32 bits 125 tz = 32 126 else: 127 while not (n & (1<<tz)): 128 tz += 1 129 return tz
130
131 -class RoutingTable(object):
132 - def fromFile(klass, f, requireNames=True, defaultRouteName='*default*'):
133 """ 134 Make a new routing table, populated from entries in an open 135 file object. 136 137 The entries are expected to have the form: 138 IP-ADDRESS/MASK-BITS ROUTE-NAME 139 140 The `#' character denotes a comment. Empty lines are allowed. 141 142 @param f: file from whence to read a routing table 143 @type f: open file object 144 @param requireNames: whether to require route names in the file 145 @type requireNames: boolean, default to True 146 @param defaultRouteName: default name to give to a route if it 147 does not have a name in the file; only 148 used if requireNames is False 149 @type defaultRouteName: anything, defaults to '*default*' 150 """ 151 comment = re.compile(r'^\s*#') 152 empty = re.compile(r'^\s*$') 153 entry = re.compile(r'^\s*' 154 r'(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})' 155 r'/' 156 r'(\d{1,2})' 157 r'(\s+([^\s](.*[^\s])?))?\s*$') 158 ret = klass() 159 n = 0 160 for line in f: 161 n += 1 162 if comment.match(line) or empty.match(line): 163 continue 164 m = entry.match(line) 165 if not m: 166 raise ValueError('While loading routing table from file' 167 ' %s: line %d: invalid syntax: %r' 168 % (f, n, line)) 169 route = m.group(4) 170 if route is None: 171 if requireNames: 172 raise ValueError('%s:%d: Missing required route name: %r' 173 % (f, n, line)) 174 else: 175 route = defaultRouteName 176 ret.addSubnet(route, m.group(1), int(m.group(2))) 177 if route not in ret.routeNames: 178 ret.routeNames.append(route) 179 180 return ret
181 fromFile = classmethod(fromFile) 182
183 - def __init__(self):
184 self.avltree = avltree.AVLTree() 185 self.routeNames = []
186
187 - def getRouteNames(self):
188 return self.routeNames
189
190 - def _parseSubnet(self, ipv4String, maskBits):
191 return (ipv4StringToInt(ipv4String), 192 ~((1 << (32 - maskBits)) - 1))
193
194 - def addSubnet(self, route, ipv4String, maskBits=32):
195 ipv4Int, mask = self._parseSubnet(ipv4String, maskBits) 196 if not ipv4Int & mask == ipv4Int: 197 raise ValueError('Net %s too specific for mask with %d bits' 198 % (ipv4String, maskBits)) 199 self.avltree.insert((mask, ipv4Int, route))
200
201 - def removeSubnet(self, route, ipv4String, maskBits=32):
202 ipv4Int, mask = self._parseSubnet(ipv4String, maskBits) 203 self.avltree.delete((mask, ipv4Int, route))
204
205 - def __iter__(self):
206 return self.avltree.iterreversed()
207
208 - def iterHumanReadable(self):
209 for mask, net, route in self: 210 yield route, ipv4IntToString(net), 32-countTrailingZeroes32(mask)
211
212 - def __len__(self):
213 return len(self.avltree)
214
215 - def route(self, ip):
216 """ 217 Return the preferred route for this IP. 218 219 @param ip: The IP to use for routing decisions. 220 @type ip: An integer or string representing an IPv4 address 221 """ 222 if isinstance(ip, str): 223 ip = ipv4StringToInt(ip) 224 225 for netmask, net, route in self: 226 if ip & netmask == net: 227 return route 228 229 return None
230
231 - def route_iter(self, ip):
232 """ 233 Return an iterator yielding routes in order of preference. 234 235 @param ip: The IP to use for routing decisions. 236 @type ip: An integer or string representing an IPv4 address 237 """ 238 if isinstance(ip, str): 239 ip = ipv4StringToInt(ip) 240 for mask, net, route in self: 241 if ip & mask == net: 242 yield route 243 # Yield the default route 244 yield None
245
246 -def addressGetHost(a):
247 """ 248 Get the host name of an IPv4 address. 249 250 @type a: L{twisted.internet.address.IPv4Address} 251 """ 252 if not isinstance(a, address.IPv4Address) and not isinstance(a, 253 address.UNIXAddress): 254 raise TypeError("object %r is not an IPv4Address or UNIXAddress" % a) 255 if isinstance(a, address.UNIXAddress): 256 return 'localhost' 257 258 try: 259 host = a.host 260 except AttributeError: 261 host = a[1] 262 return host
263
264 -def addressGetPort(a):
265 """ 266 Get the port number of an IPv4 address. 267 268 @type a: L{twisted.internet.address.IPv4Address} 269 """ 270 assert(isinstance(a, address.IPv4Address)) 271 try: 272 port = a.port 273 except AttributeError: 274 port = a[2] 275 return port
276
277 -def tryPort(port=0):
278 """Checks if the given port is unused 279 @param port: the port number or 0 for a random port 280 @type port: integer 281 @returns: port number or None if in use 282 @rtype: integer or None 283 """ 284 285 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 286 287 try: 288 try: 289 s.bind(('', port)) 290 port = s.getsockname()[1] 291 except socket.error, e: 292 if e.args[0] != errno.EADDRINUSE: 293 raise 294 port = None 295 finally: 296 s.close() 297 298 return port
299