3 # The software in this package is distributed under the GNU General
4 # Public License version 2 (with a special exception described below).
6 # A copy of GNU General Public License (GPL) is included in this distribution,
7 # in the file COPYING.GPL.
9 # As a special exception, if other files instantiate templates or use macros
10 # or inline functions from this file, or you compile this file and link it
11 # with other works to produce a work based on this file, this file
12 # does not by itself cause the resulting work to be covered
13 # by the GNU General Public License.
15 # However the source code for this file must still be made available
16 # in accordance with section (3) of the GNU General Public License.
18 # This exception does not invalidate any other reasons why a work based
19 # on this file might be covered by the GNU General Public License.
21 # Copyright (c) 2016-2018 Intra2net AG <info@intra2net.com>
26 ------------------------------------------------------
27 Representation for connd state as returned by "tell-connd --status".
29 Copyright: Intra2net AG
33 ------------------------------------------------------
37 from __future__ import print_function
39 from re import match as regexp
43 DEFAULT_TELL_CONND_BINARY = '/usr/intranator/bin/tell-connd'
44 # TIMEOUT = 1 can only be used with python3
46 ONLINE_MODE_ALWAYS_ONLINE = 'always online'
47 ONLINE_MODE_ALWAYS_OFFLINE = 'always offline'
48 ONLINE_MODE_DIAL_ON_COMMAND = 'dial on command'
49 ONLINE_MODE_DIAL_ON_DEMAND = 'dial on demand'
52 SUBSYS_DYNDNS = 'dyndns'
55 SUBSYS_SOCKS = 'socks'
57 SUBSYS_WEBPROXY = 'webproxy'
58 SUBSYS_PINGCHECK = 'pingcheck'
59 SUBSYS_IPONLINE = 'iponline'
60 ALL_SUBSYS = (SUBSYS_DNS, SUBSYS_DYNDNS, SUBSYS_MAIL, SUBSYS_NTP,
61 SUBSYS_SOCKS, SUBSYS_VPN, SUBSYS_WEBPROXY, SUBSYS_PINGCHECK,
64 ALL_MODES = (ONLINE_MODE_DIAL_ON_DEMAND, ONLINE_MODE_DIAL_ON_COMMAND,
65 ONLINE_MODE_ALWAYS_OFFLINE, ONLINE_MODE_ALWAYS_ONLINE)
68 class ConndState(object):
69 """Representation of connd's status as returned by tell-connd --status."""
72 default_provider = None
75 subsys_disabled = None
85 '[ConndState: {0} (default {1}), {2} conn\'s, {3} ips, {4} vpns ]'\
86 .format(self.online_mode, self.default_provider,
87 len(self.connections), len(self.online_ips),
88 len(self.connected_vpns))
90 def complete_str(self):
91 """Return a string representating the complete state."""
95 'ConndState: online mode = "{0}" (default provider: {1})\n'
96 .format(self.online_mode, self.default_provider), ]
99 # ' connctns: (repeated here for correct aligning)
100 parts.append(' subsys: online: ')
101 if self.subsys_online:
102 for subsys in self.subsys_online:
103 parts.append(subsys + ' ')
105 parts.append('None ')
106 parts.append('; offline: ')
107 if self.subsys_offline:
108 for subsys in self.subsys_offline:
109 parts.append(subsys + ' ')
111 parts.append('None ')
112 parts.append('; disabled: ')
113 if self.subsys_disabled:
114 for subsys in self.subsys_disabled:
115 parts.append(subsys + ' ')
121 parts.append(' conns: ')
123 name, info, actions = self.connections[0]
124 parts.append('{0}: {1}, {2}\n'.format(name, info, actions))
126 parts.append('None\n')
127 for name, info, actions in self.connections[1:]:
128 # ' connctns: (repeated here for correct aligning)
129 parts.append(' {0}: {1}, {2}\n'.format(name, info,
133 # ' connctns: (repeated here for correct aligning)
134 parts.append(' actions: ')
136 parts.append(self.actions[0] + '\n')
138 parts.append('None\n')
139 for action in self.actions[1:]:
140 # ' connctns: (repeated here for correct aligning)
141 parts.append(' {0}\n'.format(action))
144 # ' connctns: (repeated here for correct aligning)
145 parts.append(' IPs: ')
147 parts.append(self.online_ips[0])
148 for curr_ip in self.online_ips[1:]:
149 parts.append(', {0}'.format(curr_ip))
155 # ' connctns: (repeated here for correct aligning)
156 parts.append(' VPNs: ')
157 if self.connected_vpns:
158 parts.append(self.connected_vpns[0])
159 for vpn in self.connected_vpns[1:]:
160 parts.append(', {0}'.format(vpn))
165 # log level and target:
166 # ' connctns: (repeated here for correct aligning)
167 parts.append(' Log: level {0}'.format(self.log_level))
169 parts.append(' to {0}'.format(self.log_file))
172 return ''.join(parts)
175 def run_tell_connd(tell_connd_binary=DEFAULT_TELL_CONND_BINARY, *args):
177 Run "tell-connd --status", return output iterator and return code.
179 Catches all it can, so should usually return (output, return_code)
180 where output = [line1, line2, ...]
182 If return_code != 0, output's first line(s) is error message.
184 .. todo:: Use reST parameter description here.
187 cmd_parts = [tell_connd_binary, ]
188 cmd_parts.extend(*args)
189 output = subprocess.check_output(cmd_parts,
190 stderr=subprocess.STDOUT,
191 universal_newlines=True, shell=False) # py3:, timeout=TIMEOUT)
192 return EX_OK, output.splitlines()
193 except subprocess.CalledProcessError as cpe: # non-zero return status
195 'tell-connd exited with status {0}'.format(cpe.returncode), ]
196 output.extend(cpe.output.splitlines())
197 return cpe.returncode, output
198 # not python-2-compatible:
199 # except subprocess.TimeoutExpired as texp:
201 # 'tell-connd timed out after {0}s. Returning -1'.format(
203 # output.extend(te.output.splitlines())
205 except Exception as exp:
206 output = [str(exp), ]
210 def get_state(tell_connd_binary=DEFAULT_TELL_CONND_BINARY):
212 Get actual state from "tell-connd --status".
214 Returns (err_code, output_lines) if something goes wrong running
215 binary; raises assertion if output from tell-connd does not match
218 .. todo:: Use reST parameter description here.
223 err_code, all_lines = ConndState.run_tell_connd(tell_connd_binary,
225 if err_code != EX_OK:
226 return err_code, all_lines
228 output = iter(all_lines)
231 line = next(output).strip()
232 state.online_mode = regexp('online mode\s*:\s*(.+)$', line).groups()[0]
233 assert state.online_mode in ALL_MODES, \
234 'unexpected online mode: {0}'.format(state.online_mode)
236 line = next(output).strip()
237 state.default_provider = regexp('default provider\s*:\s*(.*)$',
239 if len(state.default_provider) == 0:
240 state.default_provider = None
241 line = next(output).strip()
242 assert len(line) == 0, 'expected empty line, but got {0}'.format(line)
245 line = next(output).strip()
246 assert line == 'subsys', 'expected subsys but got {0}'.format(line)
247 line = next(output).strip()
248 state.subsys_online = regexp('online\s*:\s*(.*)$', line)\
250 for subsys in state.subsys_online:
251 assert subsys in ALL_SUBSYS, \
252 'unexpected subsys: {0}'.format(subsys)
253 line = next(output).strip()
254 state.subsys_offline = regexp('offline\s*:\s*(.*)$', line)\
256 for subsys in state.subsys_offline:
257 assert subsys in ALL_SUBSYS, \
258 'unexpected subsys: {0}'.format(subsys)
259 line = next(output).strip()
260 state.subsys_disabled = regexp('disabled\s*:\s*(.*)$', line)\
262 for subsys in state.subsys_disabled:
263 assert subsys in ALL_SUBSYS, \
264 'unexpected subsys: {0}'.format(subsys)
265 line = next(output).strip()
266 assert len(line) == 0, 'expected empty line, but got {0}'.format(line)
269 state.connections = []
270 line = next(output).strip()
271 assert line == 'connection map:', \
272 'expected connection map but got {0}'.format(line)
279 if line == 'end of connection map':
281 conn_name, conn_info = regexp(
282 '\[\s*(.+)\s*\]\s*:\s*\(\s*(.*)\s*\)', line).groups()
285 conn_actions = regexp('actions\s*:\s*\[\s*(.+)\s*\]', line)\
287 state.connections.append((conn_name, conn_info, conn_actions))
290 line = next(output).strip()
291 assert len(line) == 0, 'expected empty line, but got {0}'.format(line)
294 line = next(output).strip()
295 state.actions = regexp('actions\s*:\s*(.*)', line).groups()[0].split()
296 if len(state.actions) == 1 and state.actions[0].strip() == '-':
298 line = next(output).strip()
299 assert len(line) == 0, 'expected empty line, but got {0}'.format(line)
302 line = next(output).strip()
303 state.online_ips = regexp('list of online ips\s*:\s*(.*)', line)\
305 if len(state.online_ips) == 1 \
306 and state.online_ips[0].strip() == 'NONE':
307 state.online_ips = []
308 line = next(output).strip()
309 assert len(line) == 0, 'expected empty line, but got {0}'.format(line)
312 state.connected_vpns = []
313 line = next(output).strip()
314 assert line == 'vpns connected:', \
315 'expected vpns connected, got {0}'.format(line)
320 elif line == 'end of list of connected vpns':
323 state.connected_vpns.append(line)
324 line = next(output).strip()
325 assert len(line) == 0, 'expected empty line, but got {0}'.format(line)
328 line = next(output).strip()
329 state.log_level, state.log_file = \
330 regexp('Logging with level (.+)(?:\s+to\s+(.+))?', line).groups()
333 line = next(output).strip()
334 assert len(line) == 0, 'expected empty line, but got {0}'.format(line)
335 line = next(output).strip()
336 assert line == 'Done.', 'expect Done but got {0}'.format(line)
341 def set_online_mode(state, provider=None,
342 tell_connd_binary=DEFAULT_TELL_CONND_BINARY):
344 Change online state with optional provider.
346 Provider is silently ignored for ONLINE_MODE_ALWAYS_OFFLINE and
349 Returns result of :py:func:`run_tell_connd`: (error_code, output_lines).
354 if state == ONLINE_MODE_DIAL_ON_DEMAND:
355 args = ['--dial-on-demand', provider]
356 elif state == ONLINE_MODE_DIAL_ON_COMMAND:
357 args = ['--dial-on-command', provider]
358 elif state == ONLINE_MODE_ALWAYS_ONLINE:
359 args = ['--online', provider]
360 elif state == ONLINE_MODE_ALWAYS_OFFLINE:
361 args = ['--offline', ]
362 need_provider = False
364 raise ValueError('unknown state: {0}!'.format(state))
365 if need_provider and not provider:
366 raise ValueError('Given state {0} requires a provider!'.format(
370 return ConndState.run_tell_connd(tell_connd_binary, args)
374 """Get state and print it."""
375 state = ConndState.get_state()
376 if not isinstance(state, ConndState):
377 err_code, output_lines = state
378 print('tell-connd failed with error code {0} and output:'.format(
380 for line in output_lines:
381 print('tell-connd: {0}'.format(line))
382 print('(end of tell-connd output)')
385 print(state.complete_str())
389 """Main function, called when running file as script; runs test()."""
393 if __name__ == '__main__':