1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
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
54 python3 = 0
55 except ImportError:
56 from io import BytesIO
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
78 try:
79 import netifaces
80 _use_netifaces = True
81 except:
82
83
84
85 _use_netifaces = False
86 else:
87 _use_netifaces = False
88
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
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]:
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
122
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
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:
136 raise ValueError("invalid ROS command-line remapping argument '%s'"%arg)
137
138
139
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
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
157 if reverse_ip not in get_local_addresses() and not reverse_ip.startswith('127.'):
158 return False
159 return True
160
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
174 if not addr.startswith('127.'):
175 return addr
176 else:
177 return '127.0.0.1'
178
179
180 _local_addrs = None
182 """
183 @return: known local addresses. Not affected by ROS_IP/ROS_HOSTNAME
184 @rtype: [str]
185 """
186
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
194
195 local_addrs = []
196
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
203
204
205
206 import fcntl
207 import array
208
209 ifsize = 32
210 if platform.system() == 'Linux' and platform.architecture()[0] == '64bit':
211 ifsize = 40
212
213
214
215 max_bytes = 32 * ifsize
216
217 buff = array.array('B', '\0' * max_bytes)
218
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
228
229
230
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
241 local_addrs = [socket.gethostbyname(socket.gethostname())]
242 _local_addrs = local_addrs
243 return local_addrs
244
245
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
260 return '127.0.0.1'
261 else:
262 return '0.0.0.0'
263
264
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
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
295
296 return 'http://%s:%s/'%(get_host_name(), port)
297
298
299
300
302 """
303 Exception to represent errors decoding handshake
304 """
305 pass
306
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
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
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
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
372
373 bval = b.getvalue()
374 (size,) = struct.unpack('<I', bval[0:4])
375 if btell - 4 >= size:
376 header_str = bval
377
378
379 leftovers = bval[size+4:]
380 b.truncate(len(leftovers))
381 b.seek(0)
382 b.write(leftovers)
383 header_recvd = True
384
385
386 return decode_ros_handshake_header(bval)
387
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
405 if python3 == 0:
406
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
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
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)
427