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 Warning: do not use this library. It is unstable and most of the routines
37 here have been superceded by other libraries (e.g. rosgraph). These
38 routines will likely be *deleted* in future releases.
39 """
40
41 import os
42 import platform
43 import socket
44 import struct
45 import sys
46
47 try:
48
49 from cStringIO import StringIO
50 python3 = 0
51 except ImportError:
52
53 from io import BytesIO
54 python3 = 1
55
56 try:
57 import urllib.parse as urlparse
58 except ImportError:
59 import urlparse
60
61
62 ROS_IP = 'ROS_IP'
63 ROS_HOSTNAME = 'ROS_HOSTNAME'
64
65 SIOCGIFCONF = 0x8912
66 SIOCGIFADDR = 0x8915
67 if platform.system() == 'FreeBSD':
68 SIOCGIFADDR = 0xc0206921
69 if platform.architecture()[0] == '64bit':
70 SIOCGIFCONF = 0xc0106924
71 else:
72 SIOCGIFCONF = 0xc0086924
73
74 if 0:
75
76 try:
77 import netifaces
78 _use_netifaces = True
79 except Exception:
80
81
82
83 _use_netifaces = False
84 else:
85 _use_netifaces = False
86
87
95
96
98 """
99 @return: ROS_IP/ROS_HOSTNAME override or None
100 @rtype: str
101 @raise ValueError: if ROS_IP/ROS_HOSTNAME/__ip/__hostname are invalidly specified
102 """
103
104 for arg in sys.argv:
105 if arg.startswith('__hostname:=') or arg.startswith('__ip:='):
106 try:
107 _, val = arg.split(':=')
108 return val
109 except Exception:
110 raise ValueError("invalid ROS command-line remapping argument '%s'" % arg)
111
112
113
114 if ROS_HOSTNAME in os.environ:
115 return os.environ[ROS_HOSTNAME]
116 elif ROS_IP in os.environ:
117 return os.environ[ROS_IP]
118 return None
119
120
122 """
123 @param hostname: host name/address
124 @type hostname: str
125 @return True: if hostname maps to a local address, False otherwise. False conditions include invalid hostnames.
126 """
127 try:
128 reverse_ip = socket.gethostbyname(hostname)
129 except socket.error:
130 return False
131
132 if reverse_ip not in get_local_addresses() and not reverse_ip.startswith('127.'):
133 return False
134 return True
135
136
138 """
139 @return: default local IP address (e.g. eth0). May be overriden by ROS_IP/ROS_HOSTNAME/__ip/__hostname
140 @rtype: str
141 """
142 override = get_address_override()
143 if override:
144 return override
145 addrs = get_local_addresses()
146 if len(addrs) == 1:
147 return addrs[0]
148 for addr in addrs:
149
150 if not addr.startswith('127.'):
151 return addr
152 else:
153 return '127.0.0.1'
154
155
156
157 _local_addrs = None
158
159
161 """
162 @return: known local addresses. Not affected by ROS_IP/ROS_HOSTNAME
163 @rtype: [str]
164 """
165
166 global _local_addrs
167 if _local_addrs is not None:
168 return _local_addrs
169
170 local_addrs = None
171 if _use_netifaces:
172
173
174 local_addrs = []
175
176 for i in netifaces.interfaces():
177 try:
178 local_addrs.extend([d['addr'] for d in netifaces.ifaddresses(i)[netifaces.AF_INET]])
179 except KeyError:
180 pass
181 elif _is_unix_like_platform():
182
183
184
185
186 import fcntl
187 import array
188
189 ifsize = 32
190 if platform.system() == 'Linux' and platform.architecture()[0] == '64bit':
191 ifsize = 40
192
193
194
195 max_bytes = 32 * ifsize
196
197 buff = array.array('B', '\0' * max_bytes)
198
199 sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
200 info = fcntl.ioctl(sock.fileno(), SIOCGIFCONF,
201 struct.pack('iL', max_bytes, buff.buffer_info()[0]))
202 retbytes = struct.unpack('iL', info)[0]
203 buffstr = buff.tostring()
204 if platform.system() == 'Linux':
205 local_addrs = [socket.inet_ntoa(buffstr[i+20:i+24]) for i in range(0, retbytes, ifsize)]
206 else:
207
208
209
210
211 local_addrs = []
212 bufpos = 0
213 while bufpos < retbytes:
214 bufpos += 16
215 ifreqsize = ord(buffstr[bufpos])
216 if ifreqsize == 16:
217 local_addrs += [socket.inet_ntoa(buffstr[bufpos+4:bufpos+8])]
218 bufpos += ifreqsize
219 else:
220
221 local_addrs = [socket.gethostbyname(socket.gethostname())]
222 _local_addrs = local_addrs
223 return local_addrs
224
225
227 """
228 @param address: (optional) address to compare against
229 @type address: str
230 @return: address TCP/IP sockets should use for binding. This is
231 generally 0.0.0.0, but if \a address or ROS_IP/ROS_HOSTNAME is set
232 to localhost it will return 127.0.0.1
233 @rtype: str
234 """
235 if address is None:
236 address = get_address_override()
237 if address and (address == 'localhost' or address.startswith('127.')):
238
239 return '127.0.0.1'
240 else:
241 return '0.0.0.0'
242
243
244
246 """
247 Determine host-name for use in host-name-based addressing (e.g. XML-RPC URIs):
248 - if ROS_IP/ROS_HOSTNAME is set, use that address
249 - if the hostname returns a non-localhost value, use that
250 - use whatever L{get_local_address()} returns
251 """
252 hostname = get_address_override()
253 if not hostname:
254 try:
255 hostname = socket.gethostname()
256 except Exception:
257 pass
258 if not hostname or hostname == 'localhost' or hostname.startswith('127.'):
259 hostname = get_local_address()
260 return hostname
261
262
264 """
265 Determine the XMLRPC URI for local servers. This handles the search
266 logic of checking ROS environment variables, the known hostname,
267 and local interface IP addresses to determine the best possible
268 URI.
269
270 @param port: port that server is running on
271 @type port: int
272 @return: XMLRPC URI
273 @rtype: str
274 """
275
276
277 return 'http://%s:%s/' % (get_host_name(), port)
278
279
280
281
283 """
284 Exception to represent errors decoding handshake
285 """
286 pass
287
288
290 """
291 Decode serialized ROS handshake header into a Python dictionary
292
293 header is a list of string key=value pairs, each prefixed by a
294 4-byte length field. It is preceeded by a 4-byte length field for
295 the entire header.
296
297 @param header_str: encoded header string. May contain extra data at the end.
298 @type header_str: str
299 @return: key value pairs encoded in \a header_str
300 @rtype: {str: str}
301 """
302 (size, ) = struct.unpack('<I', header_str[0:4])
303 size += 4
304 header_len = len(header_str)
305 if size > header_len:
306 raise ROSHandshakeException('Incomplete header. Expected %s bytes but only have %s' % ((size+4), header_len))
307
308 d = {}
309 start = 4
310 while start < size:
311 (field_size, ) = struct.unpack('<I', header_str[start:start+4])
312 if field_size == 0:
313 raise ROSHandshakeException('Invalid 0-length handshake header field')
314 start += field_size + 4
315 if start > size:
316 raise ROSHandshakeException('Invalid line length in handshake header: %s' % size)
317 line = header_str[start-field_size:start]
318
319
320 if python3 == 1:
321 line = line.decode()
322
323 idx = line.find('=')
324 if idx < 0:
325 raise ROSHandshakeException('Invalid line in handshake header: [%s]' % line)
326 key = line[:idx]
327 value = line[idx+1:]
328 d[key.strip()] = value
329 return d
330
331
333 """
334 Read in tcpros header off the socket \a sock using buffer \a b.
335
336 @param sock: socket must be in blocking mode
337 @type sock: socket
338 @param b: buffer to use
339 @type b: StringIO for Python2, BytesIO for Python 3
340 @param buff_size: incoming buffer size to use
341 @type buff_size: int
342 @return: key value pairs encoded in handshake
343 @rtype: {str: str}
344 @raise ROSHandshakeException: If header format does not match expected
345 """
346 header_str = None
347 while not header_str:
348 d = sock.recv(buff_size)
349 if not d:
350 raise ROSHandshakeException('connection from sender terminated before handshake header received. %s bytes were received. Please check sender for additional details.' % b.tell())
351 b.write(d)
352 btell = b.tell()
353 if btell > 4:
354
355
356 bval = b.getvalue()
357 (size,) = struct.unpack('<I', bval[0:4])
358 if btell - 4 >= size:
359 header_str = bval
360
361
362 leftovers = bval[size+4:]
363 b.truncate(len(leftovers))
364 b.seek(0)
365 b.write(leftovers)
366
367
368 return decode_ros_handshake_header(bval)
369
370
372 """
373 Encode ROS handshake header as a byte string. Each header
374 field is a string key value pair. The encoded header is
375 prefixed by a length field, as is each field key/value pair.
376 key/value pairs a separated by a '=' equals sign.
377
378 FORMAT: (4-byte length + [4-byte field length + field=value ]*)
379
380 @param header: header field keys/values
381 @type header: dict
382 @return: header encoded as byte string
383 @rtype: str
384 """
385 fields = ['%s=%s' % (k, v) for k, v in header.items()]
386
387
388 if python3 == 0:
389
390 s = ''.join(['%s%s' % (struct.pack('<I', len(f)), f) for f in fields])
391 return struct.pack('<I', len(s)) + s
392 else:
393
394 s = b''.join([(struct.pack('<I', len(f)) + f.encode('utf-8')) for f in fields])
395 return struct.pack('<I', len(s)) + s
396
397
399 """
400 Write ROS handshake header header to socket sock
401 @param sock: socket to write to (must be in blocking mode)
402 @type sock: socket.socket
403 @param header: header field keys/values
404 @type header: {str : str}
405 @return: Number of bytes sent (for statistics)
406 @rtype: int
407 """
408 s = encode_ros_handshake_header(header)
409 sock.sendall(s)
410 return len(s)
411