chiark / gitweb /
svc/conntrack.in: Maintain config groups in a dictionary.
[tripe] / svc / conntrack.in
CommitLineData
2ec90437
MW
1#! @PYTHON@
2### -*-python-*-
3###
4### Service for automatically tracking network connection status
5###
6### (c) 2010 Straylight/Edgeware
7###
8
9###----- Licensing notice ---------------------------------------------------
10###
11### This file is part of Trivial IP Encryption (TrIPE).
12###
11ad66c2
MW
13### TrIPE is free software: you can redistribute it and/or modify it under
14### the terms of the GNU General Public License as published by the Free
15### Software Foundation; either version 3 of the License, or (at your
16### option) any later version.
2ec90437 17###
11ad66c2
MW
18### TrIPE is distributed in the hope that it will be useful, but WITHOUT
19### ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
20### FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
21### for more details.
2ec90437
MW
22###
23### You should have received a copy of the GNU General Public License
11ad66c2 24### along with TrIPE. If not, see <https://www.gnu.org/licenses/>.
2ec90437
MW
25
26VERSION = '@VERSION@'
27
28###--------------------------------------------------------------------------
29### External dependencies.
30
31from ConfigParser import RawConfigParser
32from optparse import OptionParser
33import os as OS
34import sys as SYS
35import socket as S
36import mLib as M
37import tripe as T
38import dbus as D
39for i in ['mainloop', 'mainloop.glib']:
40 __import__('dbus.%s' % i)
a69f4417
MW
41try: from gi.repository import GLib as G
42except ImportError: import gobject as G
2ec90437
MW
43from struct import pack, unpack
44
45SM = T.svcmgr
46##__import__('rmcr').__debug = True
47
48###--------------------------------------------------------------------------
49### Utilities.
50
51class struct (object):
52 """A simple container object."""
53 def __init__(me, **kw):
54 me.__dict__.update(kw)
55
56def toposort(cmp, things):
57 """
58 Generate the THINGS in an order consistent with a given partial order.
59
60 The function CMP(X, Y) should return true if X must precede Y, and false if
61 it doesn't care. If X and Y are equal then it should return false.
62
63 The THINGS may be any finite iterable; it is converted to a list
64 internally.
65 """
66
67 ## Make sure we can index the THINGS, and prepare an ordering table.
68 ## What's going on? The THINGS might not have a helpful equality
69 ## predicate, so it's easier to work with indices. The ordering table will
70 ## remember which THINGS (by index) are considered greater than other
71 ## things.
72 things = list(things)
73 n = len(things)
74 order = [{} for i in xrange(n)]
75 rorder = [{} for i in xrange(n)]
76 for i in xrange(n):
77 for j in xrange(n):
78 if i != j and cmp(things[i], things[j]):
79 order[j][i] = True
80 rorder[i][j] = True
81
82 ## Now we can do the sort.
83 out = []
84 while True:
85 done = True
86 for i in xrange(n):
87 if order[i] is not None:
88 done = False
89 if len(order[i]) == 0:
90 for j in rorder[i]:
91 del order[j][i]
92 yield things[i]
93 order[i] = None
94 if done:
95 break
96
6e8bbeeb
MW
97###--------------------------------------------------------------------------
98### Address manipulation.
99
6aa21132
MW
100class InetAddress (object):
101 def __init__(me, addrstr, maskstr = None):
102 me.addr = me._addrstr_to_int(addrstr)
103 if maskstr is None:
104 me.mask = -1
105 elif maskstr.isdigit():
106 me.mask = (1 << 32) - (1 << 32 - int(maskstr))
107 else:
108 me.mask = me._addrstr_to_int(maskstr)
109 if me.addr&~me.mask:
110 raise ValueError('network contains bits set beyond mask')
111 def _addrstr_to_int(me, addrstr):
112 return unpack('>L', S.inet_aton(addrstr))[0]
113 def _int_to_addrstr(me, n):
114 return S.inet_ntoa(pack('>L', n))
115 def sockaddr(me, port = 0):
116 if me.mask != -1: raise ValueError('not a simple address')
117 return me._int_to_addrstr(me.addr), port
118 def __str__(me):
119 addrstr = me._int_to_addrstr(me.addr)
120 if me.mask == -1:
121 return addrstr
122 else:
123 inv = me.mask ^ ((1 << 32) - 1)
124 if (inv&(inv + 1)) == 0:
125 return '%s/%d' % (addrstr, 32 - inv.bit_length())
126 else:
127 return '%s/%s' % (addrstr, me._int_to_addrstr(me.mask))
128 def withinp(me, net):
129 if (me.mask&net.mask) != net.mask: return False
130 if (me.addr ^ net.addr)&net.mask: return False
131 return True
132 def eq(me, other):
133 if me.mask != other.mask: return False
134 if me.addr != other.addr: return False
135 return True
136 @classmethod
137 def from_sockaddr(cls, sa):
138 addr, port = (lambda a, p: (a, p))(*sa)
139 return cls(addr), port
140
141def parse_address(addrstr, maskstr = None):
142 return InetAddress(addrstr, maskstr)
11ab0da6 143
69bdcb64
MW
144def parse_net(netstr):
145 try: sl = netstr.index('/')
146 except ValueError: raise ValueError('missing mask')
6aa21132 147 return parse_address(netstr[:sl], netstr[sl + 1:])
69bdcb64 148
6aa21132 149def straddr(a): return a is None and '#<none>' or str(a)
6e8bbeeb 150
2ec90437
MW
151###--------------------------------------------------------------------------
152### Parse the configuration file.
153
154## Hmm. Should I try to integrate this with the peers database? It's not a
155## good fit; it'd need special hacks in tripe-newpeers. And the use case for
156## this service are largely going to be satellite notes, I don't think
157## scalability's going to be a problem.
158
6aa21132
MW
159TESTADDR = InetAddress('1.2.3.4')
160
2ec90437
MW
161class Config (object):
162 """
163 Represents a configuration file.
164
165 The most interesting thing is probably the `groups' slot, which stores a
166 list of pairs (NAME, PATTERNS); the NAME is a string, and the PATTERNS a
6aa21132
MW
167 list of (TAG, PEER, NET) triples. The implication is that there should be
168 precisely one peer from the set, and that it should be named TAG, where
169 (TAG, PEER, NET) is the first triple such that the host's primary IP
170 address (if PEER is None -- or the IP address it would use for
171 communicating with PEER) is within the NET.
2ec90437
MW
172 """
173
174 def __init__(me, file):
175 """
176 Construct a new Config object, reading the given FILE.
177 """
178 me._file = file
179 me._fwatch = M.FWatch(file)
180 me._update()
181
182 def check(me):
183 """
184 See whether the configuration file has been updated.
185 """
186 if me._fwatch.update():
187 me._update()
188
189 def _update(me):
190 """
191 Internal function to update the configuration from the underlying file.
192 """
193
194 ## Read the configuration. We have no need of the fancy substitutions,
195 ## so turn them all off.
196 cp = RawConfigParser()
197 cp.read(me._file)
2d4998c4 198 if T._debug: print '# reread config'
2ec90437
MW
199
200 ## Save the test address. Make sure it's vaguely sensible. The default
201 ## is probably good for most cases, in fact, since that address isn't
202 ## actually in use. Note that we never send packets to the test address;
203 ## we just use it to discover routing information.
204 if cp.has_option('DEFAULT', 'test-addr'):
6aa21132 205 testaddr = InetAddress(cp.get('DEFAULT', 'test-addr'))
2ec90437 206 else:
6aa21132 207 testaddr = TESTADDR
2ec90437
MW
208
209 ## Scan the configuration file and build the groups structure.
f8d6fc7b 210 groups = {}
2ec90437
MW
211 for sec in cp.sections():
212 pats = []
213 for tag in cp.options(sec):
214 spec = cp.get(sec, tag).split()
215
216 ## Parse the entry into peer and network.
217 if len(spec) == 1:
218 peer = None
219 net = spec[0]
220 else:
6aa21132
MW
221 peer = InetAddress(spec[0])
222 net = spec[1]
2ec90437
MW
223
224 ## Syntax of a net is ADDRESS/MASK, where ADDRESS is a dotted-quad,
225 ## and MASK is either a dotted-quad or a single integer N indicating
226 ## a mask with N leading ones followed by trailing zeroes.
6aa21132
MW
227 net = parse_net(net)
228 pats.append((tag, peer, net))
2ec90437
MW
229
230 ## Annoyingly, RawConfigParser doesn't preserve the order of options.
231 ## In order to make things vaguely sane, we topologically sort the
232 ## patterns so that more specific patterns are checked first.
6aa21132 233 pats = list(toposort(lambda (t, p, n), (tt, pp, nn): \
2ec90437 234 (p and not pp) or \
6aa21132 235 (p == pp and n.withinp(nn)),
2ec90437 236 pats))
f8d6fc7b 237 groups[sec] = pats
2ec90437
MW
238
239 ## Done.
240 me.testaddr = testaddr
241 me.groups = groups
242
243### This will be a configuration file.
244CF = None
245
2d4998c4
MW
246def cmd_showconfig():
247 T.svcinfo('test-addr=%s' % CF.testaddr)
248def cmd_showgroups():
f8d6fc7b
MW
249 for g in sorted(CF.groups.iterkeys()):
250 T.svcinfo(g)
2d4998c4 251def cmd_showgroup(g):
f8d6fc7b
MW
252 try: pats = CF.groups[g]
253 except KeyError: raise T.TripeJobError('unknown-group', g)
6aa21132 254 for t, p, n in pats:
2d4998c4 255 T.svcinfo('peer', t,
6aa21132
MW
256 'target', p and str(p) or '(default)',
257 'net', str(n))
2d4998c4 258
2ec90437
MW
259###--------------------------------------------------------------------------
260### Responding to a network up/down event.
261
262def localaddr(peer):
263 """
264 Return the local IP address used for talking to PEER.
265 """
266 sk = S.socket(S.AF_INET, S.SOCK_DGRAM)
267 try:
268 try:
6aa21132
MW
269 sk.connect(peer.sockaddr(1))
270 addr = sk.getsockname()
271 return InetAddress.from_sockaddr(addr)[0]
2ec90437
MW
272 except S.error:
273 return None
274 finally:
275 sk.close()
276
277_kick = T.Queue()
f5393555
MW
278_delay = None
279
280def cancel_delay():
281 global _delay
282 if _delay is not None:
283 if T._debug: print '# cancel delayed kick'
284 G.source_remove(_delay)
285 _delay = None
4f6b41b9
MW
286
287def netupdown(upness, reason):
288 """
289 Add or kill peers according to whether the network is up or down.
290
291 UPNESS is true if the network is up, or false if it's down.
292 """
293
294 _kick.put((upness, reason))
295
f5393555
MW
296def delay_netupdown(upness, reason):
297 global _delay
298 cancel_delay()
299 def _func():
300 global _delay
301 if T._debug: print '# delayed %s: %s' % (upness, reason)
302 _delay = None
303 netupdown(upness, reason)
304 return False
305 if T._debug: print '# delaying %s: %s' % (upness, reason)
306 _delay = G.timeout_add(2000, _func)
307
2ec90437
MW
308def kickpeers():
309 while True:
310 upness, reason = _kick.get()
2d4998c4
MW
311 if T._debug: print '# kickpeers %s: %s' % (upness, reason)
312 select = []
f5393555 313 cancel_delay()
2ec90437
MW
314
315 ## Make sure the configuration file is up-to-date. Don't worry if we
316 ## can't do anything useful.
317 try:
318 CF.check()
319 except Exception, exc:
320 SM.warn('conntrack', 'config-file-error',
321 exc.__class__.__name__, str(exc))
322
323 ## Find the current list of peers.
324 peers = SM.list()
325
326 ## Work out the primary IP address.
327 if upness:
328 addr = localaddr(CF.testaddr)
329 if addr is None:
330 upness = False
b10a8c3d
MW
331 else:
332 addr = None
2d4998c4
MW
333 if not T._debug: pass
334 elif addr: print '# local address = %s' % straddr(addr)
335 else: print '# offline'
2ec90437
MW
336
337 ## Now decide what to do.
338 changes = []
f8d6fc7b 339 for g, pp in CF.groups.iteritems():
2d4998c4 340 if T._debug: print '# check group %s' % g
2ec90437
MW
341
342 ## Find out which peer in the group ought to be active.
b10a8c3d
MW
343 ip = None
344 map = {}
345 want = None
6aa21132 346 for t, p, n in pp:
b10a8c3d
MW
347 if p is None or not upness:
348 ipq = addr
349 else:
350 ipq = localaddr(p)
2d4998c4 351 if T._debug:
6aa21132
MW
352 info = 'peer=%s; target=%s; net=%s; local=%s' % (
353 t, p or '(default)', n, straddr(ipq))
b10a8c3d 354 if upness and ip is None and \
6aa21132 355 ipq is not None and ipq.withinp(n):
2d4998c4 356 if T._debug: print '# %s: SELECTED' % info
b10a8c3d 357 map[t] = 'up'
2d4998c4 358 select.append('%s=%s' % (g, t))
f2bdb96e
MW
359 if t == 'down' or t.startswith('down/'):
360 want = None
361 else:
362 want = t
b10a8c3d
MW
363 ip = ipq
364 else:
365 map[t] = 'down'
2d4998c4 366 if T._debug: print '# %s: skipped' % info
2ec90437
MW
367
368 ## Shut down the wrong ones.
369 found = False
2d4998c4 370 if T._debug: print '# peer-map = %r' % map
2ec90437 371 for p in peers:
b10a8c3d
MW
372 what = map.get(p, 'leave')
373 if what == 'up':
2ec90437 374 found = True
2d4998c4 375 if T._debug: print '# peer %s: already up' % p
b10a8c3d 376 elif what == 'down':
cf2e4ea6
MW
377 def _(p = p):
378 try:
379 SM.kill(p)
380 except T.TripeError, exc:
381 if exc.args[0] == 'unknown-peer':
382 ## Inherently racy; don't worry about this.
383 pass
384 else:
385 raise
2d4998c4 386 if T._debug: print '# peer %s: bring down' % p
cf2e4ea6 387 changes.append(_)
2ec90437
MW
388
389 ## Start the right one if necessary.
7b7e3c74 390 if want is not None and not found:
cf2e4ea6
MW
391 def _(want = want):
392 try:
8d1d183e 393 list(SM.svcsubmit('connect', 'active', want))
cf2e4ea6
MW
394 except T.TripeError, exc:
395 SM.warn('conntrack', 'connect-failed', want, *exc.args)
2d4998c4 396 if T._debug: print '# peer %s: bring up' % want
cf2e4ea6 397 changes.append(_)
2ec90437
MW
398
399 ## Commit the changes.
400 if changes:
2d4998c4 401 SM.notify('conntrack', upness and 'up' or 'down', *select + reason)
2ec90437
MW
402 for c in changes: c()
403
2ec90437
MW
404###--------------------------------------------------------------------------
405### NetworkManager monitor.
406
498d9f42
MW
407DBPROPS_IFACE = 'org.freedesktop.DBus.Properties'
408
2ec90437
MW
409NM_NAME = 'org.freedesktop.NetworkManager'
410NM_PATH = '/org/freedesktop/NetworkManager'
411NM_IFACE = NM_NAME
412NMCA_IFACE = NM_NAME + '.Connection.Active'
413
2079efa1
MW
414NM_STATE_CONNECTED = 3 #obsolete
415NM_STATE_CONNECTED_LOCAL = 50
416NM_STATE_CONNECTED_SITE = 60
417NM_STATE_CONNECTED_GLOBAL = 70
418NM_CONNSTATES = set([NM_STATE_CONNECTED,
419 NM_STATE_CONNECTED_LOCAL,
420 NM_STATE_CONNECTED_SITE,
421 NM_STATE_CONNECTED_GLOBAL])
2ec90437
MW
422
423class NetworkManagerMonitor (object):
424 """
425 Watch NetworkManager signals for changes in network state.
426 """
427
428 ## Strategy. There are two kinds of interesting state transitions for us.
429 ## The first one is the global are-we-connected state, which we'll use to
430 ## toggle network upness on a global level. The second is which connection
431 ## has the default route, which we'll use to tweak which peer in the peer
432 ## group is active. The former is most easily tracked using the signal
433 ## org.freedesktop.NetworkManager.StateChanged; for the latter, we track
434 ## org.freedesktop.NetworkManager.Connection.Active.PropertiesChanged and
435 ## look for when a new connection gains the default route.
436
437 def attach(me, bus):
438 try:
439 nm = bus.get_object(NM_NAME, NM_PATH)
498d9f42 440 state = nm.Get(NM_IFACE, 'State', dbus_interface = DBPROPS_IFACE)
2079efa1 441 if state in NM_CONNSTATES:
2ec90437
MW
442 netupdown(True, ['nm', 'initially-connected'])
443 else:
444 netupdown(False, ['nm', 'initially-disconnected'])
bd9bd714
MW
445 except D.DBusException, e:
446 if T._debug: print '# exception attaching to network-manager: %s' % e
2079efa1
MW
447 bus.add_signal_receiver(me._nm_state, 'StateChanged',
448 NM_IFACE, NM_NAME, NM_PATH)
449 bus.add_signal_receiver(me._nm_connchange, 'PropertiesChanged',
450 NMCA_IFACE, NM_NAME, None)
2ec90437
MW
451
452 def _nm_state(me, state):
2079efa1 453 if state in NM_CONNSTATES:
f5393555 454 delay_netupdown(True, ['nm', 'connected'])
2ec90437 455 else:
f5393555 456 delay_netupdown(False, ['nm', 'disconnected'])
2ec90437
MW
457
458 def _nm_connchange(me, props):
f5393555
MW
459 if props.get('Default', False) or props.get('Default6', False):
460 delay_netupdown(True, ['nm', 'default-connection-change'])
2ec90437 461
a95eb44a
MW
462##--------------------------------------------------------------------------
463### Connman monitor.
464
465CM_NAME = 'net.connman'
466CM_PATH = '/'
467CM_IFACE = 'net.connman.Manager'
468
469class ConnManMonitor (object):
470 """
471 Watch ConnMan signls for changes in network state.
472 """
473
474 ## Strategy. Everything seems to be usefully encoded in the `State'
475 ## property. If it's `offline', `idle' or `ready' then we don't expect a
476 ## network connection. During handover from one network to another, the
477 ## property passes through `ready' to `online'.
478
479 def attach(me, bus):
480 try:
481 cm = bus.get_object(CM_NAME, CM_PATH)
482 props = cm.GetProperties(dbus_interface = CM_IFACE)
483 state = props['State']
484 netupdown(state == 'online', ['connman', 'initially-%s' % state])
bd9bd714
MW
485 except D.DBusException, e:
486 if T._debug: print '# exception attaching to connman: %s' % e
a95eb44a
MW
487 bus.add_signal_receiver(me._cm_state, 'PropertyChanged',
488 CM_IFACE, CM_NAME, CM_PATH)
489
490 def _cm_state(me, prop, value):
491 if prop != 'State': return
f5393555 492 delay_netupdown(value == 'online', ['connman', value])
a95eb44a 493
2ec90437
MW
494###--------------------------------------------------------------------------
495### Maemo monitor.
496
497ICD_NAME = 'com.nokia.icd'
498ICD_PATH = '/com/nokia/icd'
499ICD_IFACE = ICD_NAME
500
501class MaemoICdMonitor (object):
502 """
503 Watch ICd signals for changes in network state.
504 """
505
506 ## Strategy. ICd only handles one connection at a time in steady state,
507 ## though when switching between connections, it tries to bring the new one
508 ## up before shutting down the old one. This makes life a bit easier than
509 ## it is with NetworkManager. On the other hand, the notifications are
510 ## relative to particular connections only, and the indicator that the old
511 ## connection is down (`IDLE') comes /after/ the new one comes up
512 ## (`CONNECTED'), so we have to remember which one is active.
513
514 def attach(me, bus):
515 try:
516 icd = bus.get_object(ICD_NAME, ICD_PATH)
517 try:
518 iap = icd.get_ipinfo(dbus_interface = ICD_IFACE)[0]
519 me._iap = iap
520 netupdown(True, ['icd', 'initially-connected', iap])
521 except D.DBusException:
522 me._iap = None
523 netupdown(False, ['icd', 'initially-disconnected'])
bd9bd714
MW
524 except D.DBusException, e:
525 if T._debug: print '# exception attaching to icd: %s' % e
2ec90437
MW
526 me._iap = None
527 bus.add_signal_receiver(me._icd_state, 'status_changed', ICD_IFACE,
528 ICD_NAME, ICD_PATH)
529
530 def _icd_state(me, iap, ty, state, hunoz):
531 if state == 'CONNECTED':
532 me._iap = iap
f5393555 533 delay_netupdown(True, ['icd', 'connected', iap])
2ec90437
MW
534 elif state == 'IDLE' and iap == me._iap:
535 me._iap = None
f5393555 536 delay_netupdown(False, ['icd', 'idle'])
2ec90437
MW
537
538###--------------------------------------------------------------------------
539### D-Bus connection tracking.
540
541class DBusMonitor (object):
542 """
543 Maintains a connection to the system D-Bus, and watches for signals.
544
545 If the connection is initially down, or drops for some reason, we retry
546 periodically (every five seconds at the moment). If the connection
547 resurfaces, we reattach the monitors.
548 """
549
550 def __init__(me):
551 """
552 Initialise the object and try to establish a connection to the bus.
553 """
554 me._mons = []
555 me._loop = D.mainloop.glib.DBusGMainLoop()
7bfa1e06 556 me._state = 'startup'
2ec90437
MW
557 me._reconnect()
558
559 def addmon(me, mon):
560 """
561 Add a monitor object to watch for signals.
562
563 MON.attach(BUS) is called, with BUS being the connection to the system
564 bus. MON should query its service's current status and watch for
565 relevant signals.
566 """
567 me._mons.append(mon)
568 if me._bus is not None:
569 mon.attach(me._bus)
570
16650038 571 def _reconnect(me, hunoz = None):
2ec90437
MW
572 """
573 Start connecting to the bus.
574
575 If we fail the first time, retry periodically.
576 """
7bfa1e06
MW
577 if me._state == 'startup':
578 T.aside(SM.notify, 'conntrack', 'dbus-connection', 'startup')
579 elif me._state == 'connected':
580 T.aside(SM.notify, 'conntrack', 'dbus-connection', 'lost')
581 else:
582 T.aside(SM.notify, 'conntrack', 'dbus-connection',
583 'state=%s' % me._state)
584 me._state == 'reconnecting'
2ec90437
MW
585 me._bus = None
586 if me._try_connect():
587 G.timeout_add_seconds(5, me._try_connect)
588
589 def _try_connect(me):
590 """
591 Actually make a connection attempt.
592
593 If we succeed, attach the monitors.
594 """
595 try:
7bfa1e06
MW
596 addr = OS.getenv('TRIPE_CONNTRACK_BUS')
597 if addr == 'SESSION':
598 bus = D.SessionBus(mainloop = me._loop, private = True)
599 elif addr is not None:
600 bus = D.bus.BusConnection(addr, mainloop = me._loop)
601 else:
602 bus = D.SystemBus(mainloop = me._loop, private = True)
603 for m in me._mons:
604 m.attach(bus)
605 except D.DBusException, e:
2ec90437
MW
606 return True
607 me._bus = bus
7bfa1e06 608 me._state = 'connected'
2ec90437 609 bus.call_on_disconnection(me._reconnect)
7bfa1e06 610 T.aside(SM.notify, 'conntrack', 'dbus-connection', 'connected')
2ec90437
MW
611 return False
612
613###--------------------------------------------------------------------------
614### TrIPE service.
615
616class GIOWatcher (object):
617 """
618 Monitor I/O events using glib.
619 """
620 def __init__(me, conn, mc = G.main_context_default()):
621 me._conn = conn
622 me._watch = None
623 me._mc = mc
624 def connected(me, sock):
625 me._watch = G.io_add_watch(sock, G.IO_IN,
626 lambda *hunoz: me._conn.receive())
627 def disconnected(me):
628 G.source_remove(me._watch)
629 me._watch = None
630 def iterate(me):
631 me._mc.iteration(True)
632
633SM.iowatch = GIOWatcher(SM)
634
635def init():
636 """
637 Service initialization.
638
639 Add the D-Bus monitor here, because we might send commands off immediately,
640 and we want to make sure the server connection is up.
641 """
29807d89 642 global DBM
22b47552 643 T.Coroutine(kickpeers, name = 'kickpeers').switch()
29807d89
MW
644 DBM = DBusMonitor()
645 DBM.addmon(NetworkManagerMonitor())
a95eb44a 646 DBM.addmon(ConnManMonitor())
29807d89 647 DBM.addmon(MaemoICdMonitor())
f5393555
MW
648 G.timeout_add_seconds(30, lambda: (_delay is not None or
649 netupdown(True, ['interval-timer']) or
650 True))
2ec90437
MW
651
652def parse_options():
653 """
654 Parse the command-line options.
655
656 Automatically changes directory to the requested configdir, and turns on
657 debugging. Returns the options object.
658 """
659 op = OptionParser(usage = '%prog [-a FILE] [-d DIR]',
660 version = '%%prog %s' % VERSION)
661
662 op.add_option('-a', '--admin-socket',
663 metavar = 'FILE', dest = 'tripesock', default = T.tripesock,
664 help = 'Select socket to connect to [default %default]')
665 op.add_option('-d', '--directory',
666 metavar = 'DIR', dest = 'dir', default = T.configdir,
667 help = 'Select current diretory [default %default]')
668 op.add_option('-c', '--config',
669 metavar = 'FILE', dest = 'conf', default = 'conntrack.conf',
670 help = 'Select configuration [default %default]')
671 op.add_option('--daemon', dest = 'daemon',
672 default = False, action = 'store_true',
673 help = 'Become a daemon after successful initialization')
674 op.add_option('--debug', dest = 'debug',
675 default = False, action = 'store_true',
676 help = 'Emit debugging trace information')
677 op.add_option('--startup', dest = 'startup',
678 default = False, action = 'store_true',
679 help = 'Being called as part of the server startup')
680
681 opts, args = op.parse_args()
682 if args: op.error('no arguments permitted')
683 OS.chdir(opts.dir)
684 T._debug = opts.debug
685 return opts
686
687## Service table, for running manually.
688def cmd_updown(upness):
689 return lambda *args: T.defer(netupdown, upness, ['manual'] + list(args))
690service_info = [('conntrack', VERSION, {
691 'up': (0, None, '', cmd_updown(True)),
2d4998c4
MW
692 'down': (0, None, '', cmd_updown(False)),
693 'show-config': (0, 0, '', cmd_showconfig),
694 'show-groups': (0, 0, '', cmd_showgroups),
695 'show-group': (1, 1, 'GROUP', cmd_showgroup)
2ec90437
MW
696})]
697
698if __name__ == '__main__':
699 opts = parse_options()
700 CF = Config(opts.conf)
701 T.runservices(opts.tripesock, service_info,
702 init = init, daemon = opts.daemon)
703
704###----- That's all, folks --------------------------------------------------