Commit | Line | Data |
---|---|---|
3daedf04 CH |
1 | #!/usr/bin/env python3 |
2 | ||
3 | """ Representation for connd state as returned by tell-connd --status | |
4 | ||
5 | Christian Herdtweck, Intra2net, January 2015 | |
6 | (c) Intra2net AG 2015 | |
7 | """ | |
8 | ||
9 | # Version History | |
10 | # 16/01/15 Christian Herdtweck: started creation | |
11 | ||
12 | import subprocess | |
13 | from re import match as regexp | |
14 | from os import EX_OK | |
15 | ||
f5c3fe44 CH |
16 | # constants |
17 | default_tell_connd_binary = '/usr/intranator/bin/tell-connd' | |
3daedf04 CH |
18 | timeout = 1 |
19 | ||
f5c3fe44 CH |
20 | ONLINE_STATE_ALWAYS_ONLINE = 'always online' |
21 | ONLINE_STATE_ALWAYS_OFFLINE = 'always offline' | |
22 | ONLINE_STATE_DIAL_ON_COMMAND = 'dial on command' | |
23 | ONLINE_STATE_DIAL_ON_DEMAND = 'dial on demand' | |
24 | ||
25 | SUBSYS_DNS = 'dns' | |
26 | SUBSYS_DYNDNS = 'dyndns' | |
27 | SUBSYS_MAIL = 'mail' | |
28 | SUBSYS_NTP = 'ntp' | |
29 | SUBSYS_SOCKS = 'socks' | |
30 | SUBSYS_VPN = 'vpn' | |
31 | SUBSYS_WEBPROXY = 'webproxy' | |
32 | SUBSYS_PINGCHECK = 'pingcheck' | |
a85f210b | 33 | SUBSYS_IPONLINE = 'iponline' |
f5c3fe44 | 34 | ALL_SUBSYS = (SUBSYS_DNS, SUBSYS_DYNDNS, SUBSYS_MAIL, SUBSYS_NTP, \ |
a85f210b CH |
35 | SUBSYS_SOCKS, SUBSYS_VPN, SUBSYS_WEBPROXY, SUBSYS_PINGCHECK, \ |
36 | SUBSYS_IPONLINE) | |
f5c3fe44 | 37 | |
3daedf04 CH |
38 | class ConndState: |
39 | """ representation of connd's status as returned by tell-connd --status """ | |
40 | ||
41 | online_mode = None | |
42 | default_provider = None | |
43 | subsys_online = None | |
44 | subsys_offline = None | |
45 | subsys_disabled = None | |
46 | connections = None | |
47 | actions = None | |
48 | online_ips = None | |
49 | connected_vpns = None | |
50 | ||
51 | def __str__(self): | |
52 | return '[ConndState: {0} (default {1}), {2} conn\'s, {3} ips, {4} vpns ]'.format(\ | |
53 | self.online_mode, self.default_provider, len(self.connections), \ | |
54 | len(self.online_ips), len(self.connected_vpns)) | |
55 | ||
f5c3fe44 CH |
56 | def complete_str(self): |
57 | # general | |
58 | parts = ['ConndState: online mode = "{0}" (default provider: {1})\n'.format(\ | |
59 | self.online_mode, self.default_provider), ] | |
60 | ||
61 | # subsys | |
62 | # ' connctns: | |
63 | parts.append(' subsys: online: ') | |
64 | if self.subsys_online: | |
65 | for subsys in self.subsys_online: | |
66 | parts.append(subsys + ' ') | |
67 | else: | |
68 | parts.append('None ') | |
69 | parts.append('; offline: ') | |
70 | if self.subsys_offline: | |
71 | for subsys in self.subsys_offline: | |
72 | parts.append(subsys + ' ') | |
73 | else: | |
74 | parts.append('None ') | |
75 | parts.append('; disabled: ') | |
76 | if self.subsys_disabled: | |
77 | for subsys in self.subsys_disabled: | |
78 | parts.append(subsys + ' ') | |
79 | else: | |
80 | parts.append('None') | |
81 | parts.append('\n') | |
82 | ||
83 | # connections | |
84 | parts.append(' conns: ') | |
85 | if self.connections: | |
86 | name, info, actions = self.connections[0] | |
87 | parts.append('{0}: {1}, {2}\n'.format(name, info, actions)) | |
88 | else: | |
89 | parts.append('None\n') | |
90 | for name, info, actions in self.connections[1:]: | |
91 | # ' connctns: | |
92 | parts.append(' {0}: {1}, {2}\n'.format(name, info, actions)) | |
93 | ||
94 | # actions | |
95 | # ' connctns: | |
96 | parts.append(' actions: ') | |
97 | if self.actions: | |
98 | parts.append(self.actions[0] + '\n') | |
99 | else: | |
100 | parts.append('None\n') | |
101 | for action in self.actions[1:]: | |
102 | # ' connctns: | |
103 | parts.append(' {0}\n'.format(action)) | |
104 | ||
105 | # online IPs | |
106 | # ' connctns: | |
107 | parts.append(' IPs: ') | |
108 | if self.online_ips: | |
109 | parts.append(self.online_ips[0]) | |
110 | for ip in self.online_ips[1:]: | |
111 | parts.append(', {0}'.format(ip)) | |
112 | else: | |
113 | parts.append('None') | |
114 | parts.append('\n') | |
115 | ||
116 | # VPNs | |
117 | # ' connctns: | |
118 | parts.append(' VPNs: ') | |
119 | if self.connected_vpns: | |
120 | parts.append(self.connected_vpns[0]) | |
121 | for vpn in self.connected_vpns[1:]: | |
122 | parts.append(', {0}'.format(vpn)) | |
123 | else: | |
124 | parts.append('None') | |
125 | parts.append('\n') | |
126 | ||
127 | ||
128 | return ''.join(parts) | |
129 | #end: ConndState.complete_str | |
130 | ||
3daedf04 CH |
131 | |
132 | @staticmethod | |
f5c3fe44 CH |
133 | def run_tell_connd(tell_connd_binary=default_tell_connd_binary): |
134 | """ run tell-connd --status, return output iterator and return code | |
135 | ||
136 | catches all it can, so should usually return (output, return_code) | |
137 | where output = [line1, line2, ...] | |
138 | if return_code != 0, output's first line(s) is error message | |
139 | """ | |
3daedf04 CH |
140 | try: |
141 | output = subprocess.check_output([tell_connd_binary, '--status'], \ | |
142 | stderr=subprocess.STDOUT, universal_newlines=True, shell=False, timeout=timeout) | |
f5c3fe44 | 143 | return EX_OK, output.splitlines() |
3daedf04 | 144 | except subprocess.CalledProcessError as cpe: # non-zero return status |
f5c3fe44 CH |
145 | output = ['tell-connd exited with status {0}'.format(cpe.returncode), ] |
146 | output.extend( cpe.output.splitlines() ) | |
147 | return cpe.returncode, output | |
3daedf04 | 148 | except subprocess.TimeoutExpired as te: |
f5c3fe44 CH |
149 | output = ['tell-connd timed out after {0}s. Returning -1'.format(te.timeout), ] |
150 | output.extend( te.output.splitlines() ) | |
151 | return -1, output | |
152 | except Exception as e: | |
153 | output = [str(e),] | |
154 | return -1, output | |
3daedf04 CH |
155 | #end: ConndState.run_tell_connd |
156 | ||
157 | ||
158 | @staticmethod | |
f5c3fe44 CH |
159 | def get_state(tell_connd_binary=default_tell_connd_binary): |
160 | """ get actual state from tell-connd --status | |
161 | ||
162 | returns (err_code, output_lines) if something goes wrong running binary; | |
163 | raises assertion if output from tell-connd does not match expected format | |
164 | """ | |
3daedf04 CH |
165 | |
166 | state = ConndState() | |
167 | ||
f5c3fe44 CH |
168 | err_code, all_lines = ConndState.run_tell_connd(tell_connd_binary) |
169 | if err_code != EX_OK: | |
170 | return err_code, all_lines | |
171 | ||
3daedf04 CH |
172 | output = iter(all_lines) |
173 | ||
174 | # first section | |
175 | line = next(output).strip() | |
176 | state.online_mode = regexp('online mode\s*:\s*(.+)$', line).groups()[0] | |
f5c3fe44 CH |
177 | assert( state.online_mode in (ONLINE_STATE_DIAL_ON_DEMAND, ONLINE_STATE_DIAL_ON_COMMAND, \ |
178 | ONLINE_STATE_ALWAYS_OFFLINE, ONLINE_STATE_ALWAYS_ONLINE) ) | |
3daedf04 CH |
179 | line = next(output).strip() |
180 | state.default_provider = regexp('default provider\s*:\s*(.*)$', \ | |
181 | line).groups()[0] | |
182 | if len(state.default_provider) == 0: | |
183 | state.default_provider = None | |
184 | line = next(output).strip() | |
185 | assert( len(line) == 0 ) | |
186 | ||
187 | # subsys | |
188 | line = next(output).strip() | |
189 | assert( line == 'subsys' ) | |
190 | line = next(output).strip() | |
191 | state.subsys_online = regexp( 'online\s*:\s*(.*)$', line).groups()[0].split() | |
f5c3fe44 CH |
192 | for subsys in state.subsys_online: |
193 | assert(subsys in ALL_SUBSYS) | |
3daedf04 CH |
194 | line = next(output).strip() |
195 | state.subsys_offline = regexp( 'offline\s*:\s*(.*)$', line).groups()[0].split() | |
f5c3fe44 CH |
196 | for subsys in state.subsys_offline: |
197 | assert(subsys in ALL_SUBSYS) | |
3daedf04 CH |
198 | line = next(output).strip() |
199 | state.subsys_disabled = regexp('disabled\s*:\s*(.*)$', line).groups()[0].split() | |
f5c3fe44 CH |
200 | for subsys in state.subsys_disabled: |
201 | assert(subsys in ALL_SUBSYS) | |
3daedf04 CH |
202 | line = next(output).strip() |
203 | assert( len(line) == 0 ) | |
204 | ||
205 | # connection map | |
206 | state.connections = [] | |
207 | line = next(output).strip() | |
208 | assert( line == 'connection map:' ) | |
209 | expect_new = True | |
210 | for line in output: | |
211 | line = line.strip() | |
212 | if len(line) == 0: | |
213 | continue | |
214 | if expect_new: | |
215 | if line == 'end of connection map': | |
216 | break | |
217 | conn_name, conn_info = regexp('\[\s*(.+)\s*\]\s*:\s*\(\s*(.*)\s*\)', line).groups() | |
218 | expect_new = False | |
219 | else: | |
f5c3fe44 | 220 | conn_actions = regexp('actions\s*:\s*\[\s*(.+)\s*\]', line).groups() |
3daedf04 CH |
221 | state.connections.append( (conn_name, conn_info, conn_actions) ) |
222 | expect_new = True | |
223 | #end: for lines | |
224 | assert( expect_new ) | |
225 | line = next(output).strip() | |
226 | assert( len(line) == 0 ) | |
227 | ||
228 | # actions | |
229 | line = next(output).strip() | |
230 | state.actions = regexp('actions\s*:\s*(.*)', line).groups()[0].split() | |
231 | if len(state.actions) == 1 and state.actions[0].strip() == '-': | |
232 | state.actions = [] | |
233 | line = next(output).strip() | |
234 | assert( len(line) == 0 ) | |
235 | ||
236 | # online IPs | |
237 | line = next(output).strip() | |
238 | state.online_ips = regexp('list of online ips\s*:\s*(.*)', \ | |
239 | line).groups()[0].split() | |
240 | if len(state.online_ips) == 1 and state.online_ips[0].strip() == 'NONE': | |
241 | state.online_ips = [] | |
242 | line = next(output).strip() | |
243 | assert( len(line) == 0 ) | |
244 | ||
245 | # VPNs | |
246 | state.connected_vpns = [] | |
247 | line = next(output).strip() | |
248 | assert( line == 'vpns connected:' ) | |
249 | for line in output: | |
250 | line = line.strip() | |
251 | if len(line) == 0: | |
252 | continue | |
253 | elif line == 'end of list of connected vpns': | |
254 | break | |
255 | else: | |
256 | state.connected_vpns.append(line) | |
257 | #end: for lines | |
258 | ||
259 | # done | |
260 | line = next(output).strip() | |
261 | assert( len(line) == 0 ) | |
262 | line = next(output).strip() | |
263 | assert( line == 'Done.' ) | |
264 | ||
265 | return state | |
266 | #end: ConndState.get_state | |
267 | ||
268 | #end: class ConndState | |
269 | ||
270 | ||
271 | def test(): | |
272 | state = ConndState.get_state() | |
273 | print(state) | |
f5c3fe44 | 274 | print(state.complete_str()) |
3daedf04 CH |
275 | |
276 | def main(): | |
277 | """ Main function, called when running file as script; runs test() | |
278 | """ | |
279 | test() | |
280 | #end: function main | |
281 | ||
282 | ||
283 | if __name__ == '__main__': | |
284 | main() | |
285 |