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