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