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