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