Package roslib :: Module network
[frames] | no frames]

Source Code for Module roslib.network

  1  # Software License Agreement (BSD License) 
  2  # 
  3  # Copyright (c) 2008, Willow Garage, Inc. 
  4  # All rights reserved. 
  5  # 
  6  # Redistribution and use in source and binary forms, with or without 
  7  # modification, are permitted provided that the following conditions 
  8  # are met: 
  9  # 
 10  #  * Redistributions of source code must retain the above copyright 
 11  #    notice, this list of conditions and the following disclaimer. 
 12  #  * Redistributions in binary form must reproduce the above 
 13  #    copyright notice, this list of conditions and the following 
 14  #    disclaimer in the documentation and/or other materials provided 
 15  #    with the distribution. 
 16  #  * Neither the name of Willow Garage, Inc. nor the names of its 
 17  #    contributors may be used to endorse or promote products derived 
 18  #    from this software without specific prior written permission. 
 19  # 
 20  # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 
 21  # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 
 22  # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 
 23  # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 
 24  # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 
 25  # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 
 26  # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 
 27  # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 
 28  # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 
 29  # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 
 30  # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
 31  # POSSIBILITY OF SUCH DAMAGE. 
 32  # 
 33  # Revision $Id$ 
 34   
 35  """ 
 36  Warning: do not use this library.  It is unstable and most of the routines 
 37  here have been superceded by other libraries (e.g. rosgraph).  These 
 38  routines will likely be *deleted* in future releases. 
 39  """ 
 40   
 41  import os 
 42  import platform 
 43  import socket 
 44  import struct 
 45  import sys 
 46   
 47  try: 
 48      # Python 2.x 
 49      from cStringIO import StringIO  # noqa: F401 
 50      python3 = 0 
 51  except ImportError: 
 52      # Python 3.x 
 53      from io import BytesIO  # noqa: F401 
 54      python3 = 1 
 55   
 56  try: 
 57      import urllib.parse as urlparse  # noqa: F401 
 58  except ImportError: 
 59      import urlparse  # noqa: F401 
 60   
 61  # TODO: change this to rosgraph equivalents once we have ported this module 
 62  ROS_IP = 'ROS_IP' 
 63  ROS_HOSTNAME = 'ROS_HOSTNAME' 
 64   
 65  SIOCGIFCONF = 0x8912 
 66  SIOCGIFADDR = 0x8915 
 67  if platform.system() == 'FreeBSD': 
 68      SIOCGIFADDR = 0xc0206921 
 69      if platform.architecture()[0] == '64bit': 
 70          SIOCGIFCONF = 0xc0106924 
 71      else: 
 72          SIOCGIFCONF = 0xc0086924 
 73   
 74  if 0: 
 75      # disabling netifaces as it accounts for 50% of startup latency 
 76      try: 
 77          import netifaces 
 78          _use_netifaces = True 
 79      except Exception: 
 80          # NOTE: in rare cases, I've seen Python fail to extract the egg 
 81          # cache when launching multiple python nodes.  Thus, we do 
 82          # except-all instead of except ImportError (kwc). 
 83          _use_netifaces = False 
 84  else: 
 85      _use_netifaces = False 
 86   
 87   
88 -def _is_unix_like_platform():
89 """ 90 @return: true if the platform conforms to UNIX/POSIX-style APIs 91 @rtype: bool 92 """ 93 # return platform.system() in ['Linux', 'Mac OS X', 'Darwin'] 94 return platform.system() in ['Linux', 'FreeBSD']
95 96
97 -def get_address_override():
98 """ 99 @return: ROS_IP/ROS_HOSTNAME override or None 100 @rtype: str 101 @raise ValueError: if ROS_IP/ROS_HOSTNAME/__ip/__hostname are invalidly specified 102 """ 103 # #998: check for command-line remappings first 104 for arg in sys.argv: 105 if arg.startswith('__hostname:=') or arg.startswith('__ip:='): 106 try: 107 _, val = arg.split(':=') 108 return val 109 except Exception: # split didn't unpack properly 110 raise ValueError("invalid ROS command-line remapping argument '%s'" % arg) 111 112 # check ROS_HOSTNAME and ROS_IP environment variables, which are 113 # aliases for each other 114 if ROS_HOSTNAME in os.environ: 115 return os.environ[ROS_HOSTNAME] 116 elif ROS_IP in os.environ: 117 return os.environ[ROS_IP] 118 return None
119 120
121 -def is_local_address(hostname):
122 """ 123 @param hostname: host name/address 124 @type hostname: str 125 @return True: if hostname maps to a local address, False otherwise. False conditions include invalid hostnames. 126 """ 127 try: 128 reverse_ip = socket.gethostbyname(hostname) 129 except socket.error: 130 return False 131 # 127. check is due to #1260 132 if reverse_ip not in get_local_addresses() and not reverse_ip.startswith('127.'): 133 return False 134 return True
135 136
137 -def get_local_address():
138 """ 139 @return: default local IP address (e.g. eth0). May be overriden by ROS_IP/ROS_HOSTNAME/__ip/__hostname 140 @rtype: str 141 """ 142 override = get_address_override() 143 if override: 144 return override 145 addrs = get_local_addresses() 146 if len(addrs) == 1: 147 return addrs[0] 148 for addr in addrs: 149 # pick first non 127/8 address 150 if not addr.startswith('127.'): 151 return addr 152 else: # loopback 153 return '127.0.0.1'
154 155 156 # cache for performance reasons 157 _local_addrs = None 158 159
160 -def get_local_addresses():
161 """ 162 @return: known local addresses. Not affected by ROS_IP/ROS_HOSTNAME 163 @rtype: [str] 164 """ 165 # cache address data as it can be slow to calculate 166 global _local_addrs 167 if _local_addrs is not None: 168 return _local_addrs 169 170 local_addrs = None 171 if _use_netifaces: 172 # #552: netifaces is a more robust package for looking up 173 # #addresses on multiple platforms (OS X, Unix, Windows) 174 local_addrs = [] 175 # see http://alastairs-place.net/netifaces/ 176 for i in netifaces.interfaces(): 177 try: 178 local_addrs.extend([d['addr'] for d in netifaces.ifaddresses(i)[netifaces.AF_INET]]) 179 except KeyError: 180 pass 181 elif _is_unix_like_platform(): 182 # unix-only branch 183 # adapted from code from Rosen Diankov (rdiankov@cs.cmu.edu) 184 # and from ActiveState recipe 185 186 import fcntl 187 import array 188 189 ifsize = 32 190 if platform.system() == 'Linux' and platform.architecture()[0] == '64bit': 191 ifsize = 40 # untested 192 193 # 32 interfaces allowed, far more than ROS can sanely deal with 194 195 max_bytes = 32 * ifsize 196 # according to http://docs.python.org/library/fcntl.html, the buffer limit is 1024 bytes 197 buff = array.array('B', '\0' * max_bytes) 198 # serialize the buffer length and address to ioctl 199 sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 200 info = fcntl.ioctl(sock.fileno(), SIOCGIFCONF, 201 struct.pack('iL', max_bytes, buff.buffer_info()[0])) 202 retbytes = struct.unpack('iL', info)[0] 203 buffstr = buff.tostring() 204 if platform.system() == 'Linux': 205 local_addrs = [socket.inet_ntoa(buffstr[i+20:i+24]) for i in range(0, retbytes, ifsize)] 206 else: 207 # in FreeBSD, ifsize is variable: 16 + (16 or 28 or 56) bytes 208 # When ifsize is 32 bytes, it contains the interface name and address, 209 # else it contains the interface name and other information 210 # This means the buffer must be traversed in its entirety 211 local_addrs = [] 212 bufpos = 0 213 while bufpos < retbytes: 214 bufpos += 16 215 ifreqsize = ord(buffstr[bufpos]) 216 if ifreqsize == 16: 217 local_addrs += [socket.inet_ntoa(buffstr[bufpos+4:bufpos+8])] 218 bufpos += ifreqsize 219 else: 220 # cross-platform branch, can only resolve one address 221 local_addrs = [socket.gethostbyname(socket.gethostname())] 222 _local_addrs = local_addrs 223 return local_addrs
224 225
226 -def get_bind_address(address=None):
227 """ 228 @param address: (optional) address to compare against 229 @type address: str 230 @return: address TCP/IP sockets should use for binding. This is 231 generally 0.0.0.0, but if \a address or ROS_IP/ROS_HOSTNAME is set 232 to localhost it will return 127.0.0.1 233 @rtype: str 234 """ 235 if address is None: 236 address = get_address_override() 237 if address and (address == 'localhost' or address.startswith('127.')): 238 # localhost or 127/8 239 return '127.0.0.1' # loopback 240 else: 241 return '0.0.0.0'
242 243 244 # #528: semi-complicated logic for determining XML-RPC URI
245 -def get_host_name():
246 """ 247 Determine host-name for use in host-name-based addressing (e.g. XML-RPC URIs): 248 - if ROS_IP/ROS_HOSTNAME is set, use that address 249 - if the hostname returns a non-localhost value, use that 250 - use whatever L{get_local_address()} returns 251 """ 252 hostname = get_address_override() 253 if not hostname: 254 try: 255 hostname = socket.gethostname() 256 except Exception: 257 pass 258 if not hostname or hostname == 'localhost' or hostname.startswith('127.'): 259 hostname = get_local_address() 260 return hostname
261 262
263 -def create_local_xmlrpc_uri(port):
264 """ 265 Determine the XMLRPC URI for local servers. This handles the search 266 logic of checking ROS environment variables, the known hostname, 267 and local interface IP addresses to determine the best possible 268 URI. 269 270 @param port: port that server is running on 271 @type port: int 272 @return: XMLRPC URI 273 @rtype: str 274 """ 275 # TODO: merge logic in roslib.xmlrpc with this routine 276 # in the future we may not want to be locked to http protocol nor root path 277 return 'http://%s:%s/' % (get_host_name(), port)
278 279 280 # handshake utils ########################################### 281
282 -class ROSHandshakeException(Exception):
283 """ 284 Exception to represent errors decoding handshake 285 """ 286 pass
287 288
289 -def decode_ros_handshake_header(header_str):
290 """ 291 Decode serialized ROS handshake header into a Python dictionary 292 293 header is a list of string key=value pairs, each prefixed by a 294 4-byte length field. It is preceeded by a 4-byte length field for 295 the entire header. 296 297 @param header_str: encoded header string. May contain extra data at the end. 298 @type header_str: str 299 @return: key value pairs encoded in \a header_str 300 @rtype: {str: str} 301 """ 302 (size, ) = struct.unpack('<I', header_str[0:4]) 303 size += 4 # add in 4 to include size of size field 304 header_len = len(header_str) 305 if size > header_len: 306 raise ROSHandshakeException('Incomplete header. Expected %s bytes but only have %s' % ((size+4), header_len)) 307 308 d = {} 309 start = 4 310 while start < size: 311 (field_size, ) = struct.unpack('<I', header_str[start:start+4]) 312 if field_size == 0: 313 raise ROSHandshakeException('Invalid 0-length handshake header field') 314 start += field_size + 4 315 if start > size: 316 raise ROSHandshakeException('Invalid line length in handshake header: %s' % size) 317 line = header_str[start-field_size:start] 318 319 # python3 compatibility 320 if python3 == 1: 321 line = line.decode() 322 323 idx = line.find('=') 324 if idx < 0: 325 raise ROSHandshakeException('Invalid line in handshake header: [%s]' % line) 326 key = line[:idx] 327 value = line[idx+1:] 328 d[key.strip()] = value 329 return d
330 331
332 -def read_ros_handshake_header(sock, b, buff_size):
333 """ 334 Read in tcpros header off the socket \a sock using buffer \a b. 335 336 @param sock: socket must be in blocking mode 337 @type sock: socket 338 @param b: buffer to use 339 @type b: StringIO for Python2, BytesIO for Python 3 340 @param buff_size: incoming buffer size to use 341 @type buff_size: int 342 @return: key value pairs encoded in handshake 343 @rtype: {str: str} 344 @raise ROSHandshakeException: If header format does not match expected 345 """ 346 header_str = None 347 while not header_str: 348 d = sock.recv(buff_size) 349 if not d: 350 raise ROSHandshakeException('connection from sender terminated before handshake header received. %s bytes were received. Please check sender for additional details.' % b.tell()) 351 b.write(d) 352 btell = b.tell() 353 if btell > 4: 354 # most likely we will get the full header in the first recv, so 355 # not worth tiny optimizations possible here 356 bval = b.getvalue() 357 (size,) = struct.unpack('<I', bval[0:4]) 358 if btell - 4 >= size: 359 header_str = bval 360 361 # memmove the remnants of the buffer back to the start 362 leftovers = bval[size+4:] 363 b.truncate(len(leftovers)) 364 b.seek(0) 365 b.write(leftovers) 366 367 # process the header 368 return decode_ros_handshake_header(bval)
369 370
371 -def encode_ros_handshake_header(header):
372 """ 373 Encode ROS handshake header as a byte string. Each header 374 field is a string key value pair. The encoded header is 375 prefixed by a length field, as is each field key/value pair. 376 key/value pairs a separated by a '=' equals sign. 377 378 FORMAT: (4-byte length + [4-byte field length + field=value ]*) 379 380 @param header: header field keys/values 381 @type header: dict 382 @return: header encoded as byte string 383 @rtype: str 384 """ 385 fields = ['%s=%s' % (k, v) for k, v in header.items()] 386 387 # in the usual configuration, the error 'TypeError: can't concat bytes to str' appears: 388 if python3 == 0: 389 # python 2 390 s = ''.join(['%s%s' % (struct.pack('<I', len(f)), f) for f in fields]) 391 return struct.pack('<I', len(s)) + s 392 else: 393 # python 3 394 s = b''.join([(struct.pack('<I', len(f)) + f.encode('utf-8')) for f in fields]) 395 return struct.pack('<I', len(s)) + s
396 397
398 -def write_ros_handshake_header(sock, header):
399 """ 400 Write ROS handshake header header to socket sock 401 @param sock: socket to write to (must be in blocking mode) 402 @type sock: socket.socket 403 @param header: header field keys/values 404 @type header: {str : str} 405 @return: Number of bytes sent (for statistics) 406 @rtype: int 407 """ 408 s = encode_ros_handshake_header(header) 409 sock.sendall(s) 410 return len(s) # STATS
411