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,2008 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 platform 
 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$" 
 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. 
 43  #          Also, just horrible. 
 44   
 45   
 46  # ioctl calls are platform specific 
 47  system = platform.system() 
 48  if system == 'SunOS': 
 49      SIOCGIFCONF = 0xC008695C 
 50      SIOCGIFADDR = 0xC020690D 
 51  else: #FIXME: to find these calls for other OSs (default Linux) 
 52      SIOCGIFCONF = 0x8912 
 53      SIOCGIFADDR = 0x8915 
 54   
 55   
56 -def find_all_interface_names():
57 """ 58 Find the names of all available network interfaces 59 """ 60 import fcntl 61 ptr_size = len(struct.pack('P', 0)) 62 size = 24 + 2 * (ptr_size) 63 max_possible = 128 # arbitrary. raise if needed. 64 bytes = max_possible * size 65 s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 66 names = array.array('B', '\0' * bytes) 67 outbytes = struct.unpack('iP', fcntl.ioctl( 68 s.fileno(), 69 SIOCGIFCONF, 70 struct.pack('iP', bytes, names.buffer_info()[0])))[0] 71 namestr = names.tostring() 72 return [namestr[i:i+size].split('\0', 1)[0] 73 for i in range(0, outbytes, size)]
74 75
76 -def get_address_for_interface(ifname):
77 """ 78 Get the IP address for an interface 79 """ 80 import fcntl 81 s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 82 return socket.inet_ntoa(fcntl.ioctl( 83 s.fileno(), 84 SIOCGIFADDR, 85 struct.pack('256s', ifname[:15]))[20:24])
86 87
88 -def guess_public_ip():
89 """ 90 Attempt to guess a public IP for this system. 91 Returns "127.0.0.1" if it can't come up with anything better. 92 """ 93 # Iterate through them in some vaguely meaningful order. 94 interfaces = find_all_interface_names() 95 interfaces.sort() 96 97 for interface in interfaces: 98 # We have them sorted, so the first such we see will be eth0 99 if interface.startswith('eth'): 100 return get_address_for_interface(interface) 101 102 return '127.0.0.1'
103 104
105 -def guess_public_hostname():
106 """ 107 Attempt to guess a public hostname for this system. 108 """ 109 ip = guess_public_ip() 110 111 try: 112 return socket.gethostbyaddr(ip)[0] 113 except socket.error: 114 return ip
115 116
117 -def ipv4StringToInt(s):
118 try: 119 b1, b2, b3, b4 = map(int, s.split('.')) 120 except TypeError: 121 raise ValueError(s) 122 123 ret = 0 124 for n in b1, b2, b3, b4: 125 ret <<= 8 126 if n < 0 or n > 255: 127 raise ValueError(s) 128 ret += n 129 return ret
130 131
132 -def ipv4IntToString(n):
133 l = [] 134 for i in range(4): 135 l.append((n>>(i*8)) & 0xff) 136 l.reverse() 137 return '.'.join(map(str, l))
138 139
140 -def countTrailingZeroes32(n):
141 tz = 0 142 if n == 0: 143 # max of 32 bits 144 tz = 32 145 else: 146 while not (n & (1<<tz)): 147 tz += 1 148 return tz
149 150
151 -class RoutingTable(object):
152
153 - def fromFile(klass, f, requireNames=True, defaultRouteName='*default*'):
154 """ 155 Make a new routing table, populated from entries in an open 156 file object. 157 158 The entries are expected to have the form: 159 IP-ADDRESS/MASK-BITS ROUTE-NAME 160 161 The `#' character denotes a comment. Empty lines are allowed. 162 163 @param f: file from whence to read a routing table 164 @type f: open file object 165 @param requireNames: whether to require route names in the file 166 @type requireNames: boolean, default to True 167 @param defaultRouteName: default name to give to a route if it 168 does not have a name in the file; only 169 used if requireNames is False 170 @type defaultRouteName: anything, defaults to '*default*' 171 """ 172 comment = re.compile(r'^\s*#') 173 empty = re.compile(r'^\s*$') 174 entry = re.compile(r'^\s*' 175 r'(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})' 176 r'/' 177 r'(\d{1,2})' 178 r'(\s+([^\s](.*[^\s])?))?\s*$') 179 ret = klass() 180 n = 0 181 for line in f: 182 n += 1 183 if comment.match(line) or empty.match(line): 184 continue 185 m = entry.match(line) 186 if not m: 187 raise ValueError('While loading routing table from file' 188 ' %s: line %d: invalid syntax: %r' 189 % (f, n, line)) 190 route = m.group(4) 191 if route is None: 192 if requireNames: 193 raise ValueError('%s:%d: Missing required route name: %r' 194 % (f, n, line)) 195 else: 196 route = defaultRouteName 197 ret.addSubnet(route, m.group(1), int(m.group(2))) 198 if route not in ret.routeNames: 199 ret.routeNames.append(route) 200 201 return ret
202 fromFile = classmethod(fromFile) 203
204 - def __init__(self):
205 self.avltree = avltree.AVLTree() 206 self.routeNames = []
207
208 - def getRouteNames(self):
209 return self.routeNames
210
211 - def _parseSubnet(self, ipv4String, maskBits):
212 return (ipv4StringToInt(ipv4String), 213 ~((1 << (32 - maskBits)) - 1))
214
215 - def addSubnet(self, route, ipv4String, maskBits=32):
216 ipv4Int, mask = self._parseSubnet(ipv4String, maskBits) 217 if not ipv4Int & mask == ipv4Int: 218 raise ValueError('Net %s too specific for mask with %d bits' 219 % (ipv4String, maskBits)) 220 self.avltree.insert((mask, ipv4Int, route))
221
222 - def removeSubnet(self, route, ipv4String, maskBits=32):
223 ipv4Int, mask = self._parseSubnet(ipv4String, maskBits) 224 self.avltree.delete((mask, ipv4Int, route))
225
226 - def __iter__(self):
227 return self.avltree.iterreversed()
228
229 - def iterHumanReadable(self):
230 for mask, net, route in self: 231 yield route, ipv4IntToString(net), 32-countTrailingZeroes32(mask)
232
233 - def __len__(self):
234 return len(self.avltree)
235
236 - def route(self, ip):
237 """ 238 Return the preferred route for this IP. 239 240 @param ip: The IP to use for routing decisions. 241 @type ip: An integer or string representing an IPv4 address 242 """ 243 if isinstance(ip, str): 244 ip = ipv4StringToInt(ip) 245 246 for netmask, net, route in self: 247 if ip & netmask == net: 248 return route 249 250 return None
251
252 - def route_iter(self, ip):
253 """ 254 Return an iterator yielding routes in order of preference. 255 256 @param ip: The IP to use for routing decisions. 257 @type ip: An integer or string representing an IPv4 address 258 """ 259 if isinstance(ip, str): 260 ip = ipv4StringToInt(ip) 261 for mask, net, route in self: 262 if ip & mask == net: 263 yield route 264 # Yield the default route 265 yield None
266 267
268 -def addressGetHost(a):
269 """ 270 Get the host name of an IPv4 address. 271 272 @type a: L{twisted.internet.address.IPv4Address} 273 """ 274 if not isinstance(a, address.IPv4Address) and not isinstance(a, 275 address.UNIXAddress): 276 raise TypeError("object %r is not an IPv4Address or UNIXAddress" % a) 277 if isinstance(a, address.UNIXAddress): 278 return 'localhost' 279 280 try: 281 host = a.host 282 except AttributeError: 283 host = a[1] 284 return host
285 286
287 -def addressGetPort(a):
288 """ 289 Get the port number of an IPv4 address. 290 291 @type a: L{twisted.internet.address.IPv4Address} 292 """ 293 assert(isinstance(a, address.IPv4Address)) 294 try: 295 port = a.port 296 except AttributeError: 297 port = a[2] 298 return port
299 300
301 -def tryPort(port=0):
302 """Checks if the given port is unused 303 @param port: the port number or 0 for a random port 304 @type port: integer 305 @returns: port number or None if in use 306 @rtype: integer or None 307 """ 308 309 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 310 311 try: 312 try: 313 s.bind(('', port)) 314 port = s.getsockname()[1] 315 except socket.error, e: 316 if e.args[0] != errno.EADDRINUSE: 317 raise 318 port = None 319 finally: 320 s.close() 321 322 return port
323