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