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

Source Code for Module rosgraph.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 15125 2011-10-06 02:51:15Z 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   
 43  import logging 
 44  import os 
 45  import socket 
 46  import struct 
 47  import sys 
 48  import platform 
 49   
 50  try: 
 51      from cStringIO import StringIO #Python 2.x 
 52      python3 = 0 
 53  except ImportError: 
 54      from io import BytesIO #Python 3.x 
 55      python3 = 1 
 56   
 57  try: 
 58      import urllib.parse as urlparse 
 59  except ImportError: 
 60      import urlparse 
 61   
 62  from .rosenv import ROS_IP, ROS_HOSTNAME, ROS_IPV6 
 63   
 64  SIOCGIFCONF = 0x8912 
 65  SIOCGIFADDR = 0x8915 
 66  if platform.system() == 'FreeBSD': 
 67      SIOCGIFADDR = 0xc0206921 
 68      if platform.architecture()[0] == '64bit': 
 69          SIOCGIFCONF = 0xc0106924 
 70      else: 
 71          SIOCGIFCONF = 0xc0086924 
 72   
 73  logger = logging.getLogger('rosgraph.network') 
 74   
75 -def parse_http_host_and_port(url):
76 """ 77 Convenience routine to handle parsing and validation of HTTP URL 78 port due to the fact that Python only provides easy accessors in 79 Python 2.5 and later. Validation checks that the protocol and host 80 are set. 81 82 :param url: URL to parse, ``str`` 83 :returns: hostname and port number in URL or 80 (default), ``(str, int)`` 84 :raises: :exc:`ValueError` If the url does not validate 85 """ 86 # can't use p.port because that's only available in Python 2.5 87 if not url: 88 raise ValueError('not a valid URL') 89 p = urlparse.urlparse(url) 90 if not p[0] or not p[1]: #protocol and host 91 raise ValueError('not a valid URL') 92 if ':' in p[1]: 93 hostname, port = p[1].split(':') 94 port = int(port) 95 else: 96 hostname, port = p[1], 80 97 return hostname, port
98
99 -def _is_unix_like_platform():
100 """ 101 :returns: true if the platform conforms to UNIX/POSIX-style APIs 102 @rtype: bool 103 """ 104 return platform.system() in ['Linux', 'FreeBSD', 'Darwin']
105
106 -def get_address_override():
107 """ 108 :returns: ROS_IP/ROS_HOSTNAME override or None, ``str`` 109 :raises: :exc:`ValueError` If ROS_IP/ROS_HOSTNAME/__ip/__hostname are invalidly specified 110 """ 111 # #998: check for command-line remappings first 112 # TODO IPV6: check for compatibility 113 for arg in sys.argv: 114 if arg.startswith('__hostname:=') or arg.startswith('__ip:='): 115 try: 116 _, val = arg.split(':=') 117 return val 118 except: #split didn't unpack properly 119 raise ValueError("invalid ROS command-line remapping argument '%s'"%arg) 120 121 # check ROS_HOSTNAME and ROS_IP environment variables, which are 122 # aliases for each other 123 if ROS_HOSTNAME in os.environ: 124 hostname = os.environ[ROS_HOSTNAME] 125 if hostname == '': 126 msg = 'invalid ROS_HOSTNAME (an empty string)' 127 sys.stderr.write(msg + '\n') 128 logger.warn(msg) 129 else: 130 parts = urlparse.urlparse(hostname) 131 if parts.scheme: 132 msg = 'invalid ROS_HOSTNAME (protocol ' + ('and port ' if parts.port else '') + 'should not be included)' 133 sys.stderr.write(msg + '\n') 134 logger.warn(msg) 135 elif hostname.find(':') != -1: 136 # this can not be checked with urlparse() 137 # since it does not extract the port for a hostname like "foo:1234" 138 msg = 'invalid ROS_HOSTNAME (port should not be included)' 139 sys.stderr.write(msg + '\n') 140 logger.warn(msg) 141 return hostname 142 elif ROS_IP in os.environ: 143 ip = os.environ[ROS_IP] 144 if ip == '': 145 msg = 'invalid ROS_IP (an empty string)' 146 sys.stderr.write(msg + '\n') 147 logger.warn(msg) 148 elif ip.find('://') != -1: 149 msg = 'invalid ROS_IP (protocol should not be included)' 150 sys.stderr.write(msg + '\n') 151 logger.warn(msg) 152 elif ip.find('.') != -1 and ip.rfind(':') > ip.rfind('.'): 153 msg = 'invalid ROS_IP (port should not be included)' 154 sys.stderr.write(msg + '\n') 155 logger.warn(msg) 156 elif ip.find('.') == -1 and ip.find(':') == -1: 157 msg = 'invalid ROS_IP (must be a valid IPv4 or IPv6 address)' 158 sys.stderr.write(msg + '\n') 159 logger.warn(msg) 160 return ip 161 return None
162
163 -def is_local_address(hostname):
164 """ 165 :param hostname: host name/address, ``str`` 166 :returns True: if hostname maps to a local address, False otherwise. False conditions include invalid hostnames. 167 """ 168 try: 169 if use_ipv6(): 170 reverse_ips = [host[4][0] for host in socket.getaddrinfo(hostname, 0, 0, 0, socket.SOL_TCP)] 171 else: 172 reverse_ips = [host[4][0] for host in socket.getaddrinfo(hostname, 0, socket.AF_INET, 0, socket.SOL_TCP)] 173 except socket.error: 174 return False 175 local_addresses = ['localhost'] + get_local_addresses() 176 # 127. check is due to #1260 177 if ([ip for ip in reverse_ips if (ip.startswith('127.') or ip == '::1')] != []) or (set(reverse_ips) & set(local_addresses) != set()): 178 return True 179 return False
180
181 -def get_local_address():
182 """ 183 :returns: default local IP address (e.g. eth0). May be overriden by ROS_IP/ROS_HOSTNAME/__ip/__hostname, ``str`` 184 """ 185 override = get_address_override() 186 if override: 187 return override 188 addrs = get_local_addresses() 189 if len(addrs) == 1: 190 return addrs[0] 191 for addr in addrs: 192 # pick first non 127/8 address 193 if not addr.startswith('127.') and not addr == '::1': 194 return addr 195 else: # loopback 196 if use_ipv6(): 197 return '::1' 198 else: 199 return '127.0.0.1'
200 201 # cache for performance reasons 202 _local_addrs = None
203 -def get_local_addresses():
204 """ 205 :returns: known local addresses. Not affected by ROS_IP/ROS_HOSTNAME, ``[str]`` 206 """ 207 # cache address data as it can be slow to calculate 208 global _local_addrs 209 if _local_addrs is not None: 210 return _local_addrs 211 212 local_addrs = None 213 if _is_unix_like_platform(): 214 # unix-only branch 215 v4addrs = [] 216 v6addrs = [] 217 import netifaces 218 for iface in netifaces.interfaces(): 219 try: 220 ifaddrs = netifaces.ifaddresses(iface) 221 except ValueError: 222 # even if interfaces() returns an interface name 223 # ifaddresses() might raise a ValueError 224 # https://bugs.launchpad.net/ubuntu/+source/netifaces/+bug/753009 225 continue 226 if socket.AF_INET in ifaddrs: 227 v4addrs.extend([addr['addr'] for addr in ifaddrs[socket.AF_INET]]) 228 if socket.AF_INET6 in ifaddrs: 229 v6addrs.extend([addr['addr'] for addr in ifaddrs[socket.AF_INET6]]) 230 if use_ipv6(): 231 local_addrs = v6addrs + v4addrs 232 else: 233 local_addrs = v4addrs 234 else: 235 # cross-platform branch, can only resolve one address 236 if use_ipv6(): 237 local_addrs = [host[4][0] for host in socket.getaddrinfo(socket.gethostname(), 0, 0, 0, socket.SOL_TCP)] 238 else: 239 local_addrs = [host[4][0] for host in socket.getaddrinfo(socket.gethostname(), 0, socket.AF_INET, 0, socket.SOL_TCP)] 240 _local_addrs = local_addrs 241 return local_addrs
242
243 -def use_ipv6():
244 return ROS_IPV6 in os.environ and os.environ[ROS_IPV6] == 'on'
245
246 -def get_bind_address(address=None):
247 """ 248 :param address: (optional) address to compare against, ``str`` 249 :returns: address TCP/IP sockets should use for binding. This is 250 generally 0.0.0.0, but if \a address or ROS_IP/ROS_HOSTNAME is set 251 to localhost it will return 127.0.0.1, ``str`` 252 """ 253 if address is None: 254 address = get_address_override() 255 if address and (address == 'localhost' or address.startswith('127.') or address == '::1' ): 256 #localhost or 127/8 257 if use_ipv6(): 258 return '::1' 259 elif address.startswith('127.'): 260 return address 261 else: 262 return '127.0.0.1' #loopback 263 else: 264 if use_ipv6(): 265 return '::' 266 else: 267 return '0.0.0.0'
268 269 # #528: semi-complicated logic for determining XML-RPC URI
270 -def get_host_name():
271 """ 272 Determine host-name for use in host-name-based addressing (e.g. XML-RPC URIs): 273 - if ROS_IP/ROS_HOSTNAME is set, use that address 274 - if the hostname returns a non-localhost value, use that 275 - use whatever L{get_local_address()} returns 276 """ 277 hostname = get_address_override() 278 if not hostname: 279 try: 280 hostname = socket.gethostname() 281 except: 282 pass 283 if not hostname or hostname == 'localhost' or hostname.startswith('127.'): 284 hostname = get_local_address() 285 return hostname
286
287 -def create_local_xmlrpc_uri(port):
288 """ 289 Determine the XMLRPC URI for local servers. This handles the search 290 logic of checking ROS environment variables, the known hostname, 291 and local interface IP addresses to determine the best possible 292 URI. 293 294 :param port: port that server is running on, ``int`` 295 :returns: XMLRPC URI, ``str`` 296 """ 297 #TODO: merge logic in rosgraph.xmlrpc with this routine 298 # in the future we may not want to be locked to http protocol nor root path 299 return 'http://%s:%s/'%(get_host_name(), port)
300 301 302 ## handshake utils ########################################### 303
304 -class ROSHandshakeException(Exception):
305 """ 306 Exception to represent errors decoding handshake 307 """ 308 pass
309
310 -def decode_ros_handshake_header(header_str):
311 """ 312 Decode serialized ROS handshake header into a Python dictionary 313 314 header is a list of string key=value pairs, each prefixed by a 315 4-byte length field. It is preceeded by a 4-byte length field for 316 the entire header. 317 318 :param header_str: encoded header string. May contain extra data at the end, ``str`` 319 :returns: key value pairs encoded in \a header_str, ``{str: str}`` 320 """ 321 (size, ) = struct.unpack('<I', header_str[0:4]) 322 size += 4 # add in 4 to include size of size field 323 header_len = len(header_str) 324 if size > header_len: 325 raise ROSHandshakeException("Incomplete header. Expected %s bytes but only have %s"%((size+4), header_len)) 326 327 d = {} 328 start = 4 329 while start < size: 330 (field_size, ) = struct.unpack('<I', header_str[start:start+4]) 331 if field_size == 0: 332 raise ROSHandshakeException("Invalid 0-length handshake header field") 333 start += field_size + 4 334 if start > size: 335 raise ROSHandshakeException("Invalid line length in handshake header: %s"%size) 336 line = header_str[start-field_size:start] 337 338 #python3 compatibility 339 if python3 == 1: 340 line = line.decode() 341 342 idx = line.find("=") 343 if idx < 0: 344 raise ROSHandshakeException("Invalid line in handshake header: [%s]"%line) 345 key = line[:idx] 346 value = line[idx+1:] 347 d[key.strip()] = value 348 return d
349
350 -def read_ros_handshake_header(sock, b, buff_size):
351 """ 352 Read in tcpros header off the socket \a sock using buffer \a b. 353 354 :param sock: socket must be in blocking mode, ``socket`` 355 :param b: buffer to use, ``StringIO`` for Python2, ``BytesIO`` for Python 3 356 :param buff_size: incoming buffer size to use, ``int`` 357 :returns: key value pairs encoded in handshake, ``{str: str}`` 358 :raises: :exc:`ROSHandshakeException` If header format does not match expected 359 """ 360 header_str = None 361 while not header_str: 362 d = sock.recv(buff_size) 363 if not d: 364 raise ROSHandshakeException("connection from sender terminated before handshake header received. %s bytes were received. Please check sender for additional details."%b.tell()) 365 b.write(d) 366 btell = b.tell() 367 if btell > 4: 368 # most likely we will get the full header in the first recv, so 369 # not worth tiny optimizations possible here 370 bval = b.getvalue() 371 (size,) = struct.unpack('<I', bval[0:4]) 372 if btell - 4 >= size: 373 header_str = bval 374 375 # memmove the remnants of the buffer back to the start 376 leftovers = bval[size+4:] 377 b.truncate(len(leftovers)) 378 b.seek(0) 379 b.write(leftovers) 380 header_recvd = True 381 382 # process the header 383 return decode_ros_handshake_header(bval)
384
385 -def encode_ros_handshake_header(header):
386 """ 387 Encode ROS handshake header as a byte string. Each header 388 field is a string key value pair. The encoded header is 389 prefixed by a length field, as is each field key/value pair. 390 key/value pairs a separated by a '=' equals sign. 391 392 FORMAT: (4-byte length + [4-byte field length + field=value ]*) 393 394 :param header: header field keys/values, ``dict`` 395 :returns: header encoded as byte string, ``bytes`` 396 """ 397 str_cls = str if python3 else unicode 398 399 # encode all unicode keys in the header. Ideally, the type of these would be specified by the api 400 encoded_header = {} 401 for k, v in header.items(): 402 if isinstance(k, str_cls): 403 k = k.encode('utf-8') 404 if isinstance(v, str_cls): 405 v = v.encode('utf-8') 406 encoded_header[k] = v 407 408 fields = [k + b"=" + v for k, v in sorted(encoded_header.items())] 409 s = b''.join([struct.pack('<I', len(f)) + f for f in fields]) 410 411 return struct.pack('<I', len(s)) + s
412
413 -def write_ros_handshake_header(sock, header):
414 """ 415 Write ROS handshake header header to socket sock 416 417 :param sock: socket to write to (must be in blocking mode), ``socket.socket`` 418 :param header: header field keys/values, ``{str : str}`` 419 :returns: Number of bytes sent (for statistics), ``int`` 420 """ 421 s = encode_ros_handshake_header(header) 422 sock.sendall(s) 423 return len(s) #STATS
424