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