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